Package phones :: Module com_lg
[hide private]
[frames] | no frames]

Source Code for Module phones.com_lg

   1  ### BITPIM 
   2  ### 
   3  ### Copyright (C) 2003-2006 Roger Binns <rogerb@rogerbinns.com> 
   4  ### Copyright (C) 2006 Michael Cohen <mikepublic@nc.rr.com> 
   5  ### 
   6  ### This program is free software; you can redistribute it and/or modify 
   7  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
   8  ### 
   9  ### $Id: com_lg.py 4680 2008-08-14 22:40:38Z djpham $ 
  10   
  11  """Phonebook conversations with LG phones""" 
  12   
  13  import functools 
  14  import threading 
  15   
  16  import com_brew 
  17  import com_phone 
  18  import p_lg 
  19  import prototypes 
  20  import common 
  21  import struct 
  22   
23 -class LGPhonebook:
24 25 pbterminator="\x7e" 26 MODEPHONEBOOK="modephonebook" # can speak the phonebook protocol 27
28 - def __init__(self):
29 self.pbseq=0
30
31 - def _setmodelgdmgo(self):
32 # see if we can turn on dm mode 33 for baud in (0, 115200, 19200, 38400, 230400): 34 if baud: 35 if not self.comm.setbaudrate(baud): 36 continue 37 try: 38 self.comm.write("AT$LGDMGO\r\n") 39 except: 40 self.mode=self.MODENONE 41 self.comm.shouldloop=True 42 raise 43 try: 44 if self.comm.readsome().find("OK")>=0: 45 return True 46 except com_phone.modeignoreerrortypes: 47 self.log("No response to setting DM mode") 48 return False
49 50
51 - def _setmodephonebook(self):
52 req=p_lg.pbinitrequest() 53 respc=p_lg.pbinitresponse 54 55 for baud in 0,38400,115200,230400,19200: 56 if baud: 57 if not self.comm.setbaudrate(baud): 58 continue 59 try: 60 self.sendpbcommand(req, respc, callsetmode=False) 61 return True 62 except com_phone.modeignoreerrortypes: 63 pass 64 65 self._setmodelgdmgo() 66 67 for baud in 0,38400,115200: 68 if baud: 69 if not self.comm.setbaudrate(baud): 70 continue 71 try: 72 self.sendpbcommand(req, respc, callsetmode=False) 73 return True 74 except com_phone.modeignoreerrortypes: 75 pass 76 return False
77
78 - def sendpbcommand(self, request, responseclass, callsetmode=True):
79 if callsetmode: 80 self.setmode(self.MODEPHONEBOOK) 81 buffer=prototypes.buffer() 82 request.header.sequence=self.pbseq 83 self.pbseq+=1 84 if self.pbseq>0xff: 85 self.pbseq=0 86 request.writetobuffer(buffer, logtitle="lg phonebook request") 87 data=buffer.getvalue() 88 data=common.pppescape(data+common.crcs(data))+common.pppterminator 89 firsttwo=data[:2] 90 try: 91 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 92 except com_phone.modeignoreerrortypes: 93 self.mode=self.MODENONE 94 self.raisecommsdnaexception("manipulating the phonebook") 95 self.comm.success=True 96 97 origdata=data 98 # sometimes there is junk at the begining, eg if the user 99 # turned off the phone and back on again. So if there is more 100 # than one 7e in the escaped data we should start after the 101 # second to last one 102 d=data.rfind(common.pppterminator,0,-1) 103 if d>=0: 104 self.log("Multiple LG packets in data - taking last one starting at "+`d+1`) 105 self.logdata("Original LG data", origdata, None) 106 data=data[d+1:] 107 108 # turn it back to normal 109 data=common.pppunescape(data) 110 111 # take off crc and terminator 112 crc=data[-3:-1] 113 data=data[:-3] 114 # check the CRC at this point to see if we might have crap at the beginning 115 calccrc=common.crcs(data) 116 if calccrc!=crc: 117 # sometimes there is other crap at the begining 118 d=data.find(firsttwo) 119 if d>0: 120 self.log("Junk at begining of LG packet, data at "+`d`) 121 self.logdata("Original LG data", origdata, None) 122 self.logdata("Working on LG data", data, None) 123 data=data[d:] 124 # recalculate CRC without the crap 125 calccrc=common.crcs(data) 126 # see if the crc matches now 127 if calccrc!=crc: 128 self.logdata("Original LG data", origdata, None) 129 self.logdata("Working on LG data", data, None) 130 raise common.CommsDataCorruption("LG packet failed CRC check", self.desc) 131 132 # phone will respond with 0x13 and 0x14 if we send a bad or malformed command 133 if ord(data[0])==0x13: 134 raise com_brew.BrewBadBrewCommandException() 135 if ord(data[0])==0x14: 136 raise com_brew.BrewMalformedBrewCommandException() 137 138 # parse data 139 buffer=prototypes.buffer(data) 140 res=responseclass() 141 res.readfrombuffer(buffer, logtitle="lg phonebook response") 142 return res
143
144 -def cleanupstring(str):
145 str=str.replace("\r", "\n") 146 str=str.replace("\n\n", "\n") 147 str=str.strip() 148 return str.split("\n")
149
150 -class LGIndexedMedia:
151 "Implements media for LG phones that use index files" 152
153 - def __init__(self):
154 pass
155
156 - def getwallpapers(self, result):
157 return self.getmedia(self.imagelocations, result, 'wallpapers')
158
159 - def getringtones(self, result):
160 return self.getmedia(self.ringtonelocations, result, 'ringtone')
161
162 - def savewallpapers(self, results, merge):
163 return self.savemedia('wallpapers', 'wallpaper-index', self.imagelocations, results, merge, self.getwallpaperindices)
164
165 - def saveringtones(self, results, merge):
166 return self.savemedia('ringtone', 'ringtone-index', self.ringtonelocations, results, merge, self.getringtoneindices)
167
168 - def getmediaindex(self, builtins, maps, results, key):
169 """Gets the media (wallpaper/ringtone) index 170 171 @param builtins: the builtin list on the phone 172 @param results: places results in this dict 173 @param maps: the list of index files and locations 174 @param key: key to place results in 175 """ 176 177 self.log("Reading "+key) 178 media={} 179 180 # builtins 181 c=1 182 for name in builtins: 183 media[c]={'name': name, 'origin': 'builtin' } 184 c+=1 185 186 # the maps 187 type=None 188 for offset,indexfile,location,type,maxentries in maps: 189 if type=="camera": break 190 index=self.getindex(indexfile) 191 for i in index: 192 media[i+offset]={'name': index[i], 'origin': type} 193 194 # camera must be last 195 if type=="camera": 196 index=self.getcameraindex() 197 for i in index: 198 media[i+offset]=index[i] 199 200 results[key]=media 201 return media
202
203 - def getindex(self, indexfile):
204 "Read an index file" 205 index={} 206 try: 207 buf=prototypes.buffer(self.getfilecontents(indexfile)) 208 except com_brew.BrewNoSuchFileException: 209 # file may not exist 210 return index 211 g=self.protocolclass.indexfile() 212 g.readfrombuffer(buf, logtitle="Index file %s read" % (indexfile,)) 213 for i in g.items: 214 if i.index!=0xffff and len(i.name): 215 index[i.index]=i.name 216 return index
217
218 - def getmedia(self, maps, result, key):
219 """Returns the contents of media as a dict where the key is a name as returned 220 by getindex, and the value is the contents of the media""" 221 media={} 222 # the maps 223 type=None 224 for offset,indexfile,location,type,maxentries in maps: 225 if type=="camera": break 226 index=self.getindex(indexfile) 227 for i in index: 228 try: 229 media[index[i]]=self.getfilecontents(location+"/"+index[i], True) 230 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 231 self.log("It was in the index, but not on the filesystem") 232 233 if type=="camera": 234 # now for the camera stuff 235 index=self.getcameraindex() 236 for i in index: 237 try: 238 media[index[i]['name']]=self.getfilecontents("cam/pic%02d.jpg" % (i,), True) 239 except com_brew.BrewNoSuchFileException: 240 self.log("It was in the index, but not on the filesystem") 241 242 result[key]=media 243 return result
244
245 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
246 """Actually saves out the media 247 248 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 249 @param mediaindexkey: index key (eg 'wallpaper-index') 250 @param maps: list index files and locations 251 @param results: results dict 252 @param merge: are we merging or overwriting what is there? 253 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 254 """ 255 print results.keys() 256 # I humbly submit this as the longest function in the bitpim code ... 257 # wp and wpi are used as variable names as this function was originally 258 # written to do wallpaper. it works just fine for ringtones as well 259 wp=results[mediakey].copy() 260 wpi=results[mediaindexkey].copy() 261 # remove builtins 262 for k in wpi.keys(): 263 if wpi[k]['origin']=='builtin': 264 del wpi[k] 265 266 # sort results['mediakey'+'-index'] into origin buckets 267 268 # build up list into init 269 init={} 270 for offset,indexfile,location,type,maxentries in maps: 271 init[type]={} 272 for k in wpi.keys(): 273 if wpi[k]['origin']==type: 274 index=k-offset 275 name=wpi[k]['name'] 276 data=None 277 del wpi[k] 278 for w in wp.keys(): 279 if wp[w]['name']==name and wp[w]['origin']==type: 280 data=wp[w]['data'] 281 del wp[w] 282 if not merge and data is None: 283 # delete the entry 284 continue 285 init[type][index]={'name': name, 'data': data} 286 287 # init now contains everything from wallpaper-index 288 print init.keys() 289 # now look through wallpapers and see if anything remaining was assigned a particular 290 # origin 291 for w in wp.keys(): 292 o=wp[w].get("origin", "") 293 if o is not None and len(o) and o in init: 294 idx=-1 295 while idx in init[o]: 296 idx-=1 297 init[o][idx]=wp[w] 298 del wp[w] 299 300 # we now have init[type] with the entries and index number as key (negative indices are 301 # unallocated). Proceed to deal with each one, taking in stuff from wp as we have space 302 for offset,indexfile,location,type,maxentries in maps: 303 if type=="camera": break 304 index=init[type] 305 try: 306 dirlisting=self.getfilesystem(location) 307 except com_brew.BrewNoSuchDirectoryException: 308 self.mkdirs(location) 309 dirlisting={} 310 # rename keys to basename 311 for i in dirlisting.keys(): 312 dirlisting[i[len(location)+1:]]=dirlisting[i] 313 del dirlisting[i] 314 # what we will be deleting 315 dellist=[] 316 if not merge: 317 # get existing wpi for this location 318 wpi=results[mediaindexkey] 319 for i in wpi: 320 entry=wpi[i] 321 if entry['origin']==type: 322 # it is in the original index, are we writing it back out? 323 delit=True 324 for idx in index: 325 if index[idx]['name']==entry['name']: 326 delit=False 327 break 328 if delit: 329 if entry['name'] in dirlisting: 330 dellist.append(entry['name']) 331 else: 332 self.log("%s in %s index but not filesystem" % (entry['name'], type)) 333 # go ahead and delete unwanted files 334 print "deleting",dellist 335 for f in dellist: 336 self.rmfile(location+"/"+f) 337 # slurp up any from wp we can take 338 while len(index)<maxentries and len(wp): 339 idx=-1 340 while idx in index: 341 idx-=1 342 k=wp.keys()[0] 343 index[idx]=wp[k] 344 del wp[k] 345 # normalise indices 346 index=self._normaliseindices(index) # hey look, I called a function! 347 # move any overflow back into wp 348 if len(index)>maxentries: 349 keys=index.keys() 350 keys.sort() 351 for k in keys[maxentries:]: 352 idx=-1 353 while idx in wp: 354 idx-=1 355 wp[idx]=index[k] 356 del index[k] 357 # write out the new index 358 keys=index.keys() 359 keys.sort() 360 ifile=self.protocolclass.indexfile() 361 ifile.numactiveitems=len(keys) 362 for k in keys: 363 entry=self.protocolclass.indexentry() 364 entry.index=k 365 entry.name=index[k]['name'] 366 ifile.items.append(entry) 367 while len(ifile.items)<maxentries: 368 ifile.items.append(self.protocolclass.indexentry()) 369 buffer=prototypes.buffer() 370 ifile.writetobuffer(buffer, logtitle="Updated index file "+indexfile) 371 self.writefile(indexfile, buffer.getvalue()) 372 # Write out files - we compare against existing dir listing and don't rewrite if they 373 # are the same size 374 for k in keys: 375 entry=index[k] 376 data=entry.get("data", None) 377 if data is None: 378 if entry['name'] not in dirlisting: 379 self.log("Index error. I have no data for "+entry['name']+" and it isn't already in the filesystem") 380 continue 381 if entry['name'] in dirlisting and len(data)==dirlisting[entry['name']]['size']: 382 self.log("Skipping writing %s/%s as there is already a file of the same length" % (location,entry['name'])) 383 continue 384 self.writefile(location+"/"+entry['name'], data) 385 # did we have too many 386 if len(wp): 387 for k in wp: 388 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],)) 389 390 # Note that we don't write to the camera area 391 392 # tidy up - reread indices 393 del results[mediakey] # done with it 394 reindexfunction(results) 395 return results
396
397 -class LGNewIndexedMedia:
398 "Implements media for LG phones that use the new index format such as the VX7000/8000" 399 400 # tables for minor type number 401 _minor_typemap={ 402 0: { # images 403 ".jpg": 3, 404 ".bmp": 1, 405 }, 406 407 1: { # audio 408 ".qcp": 5, 409 ".mid": 4, 410 }, 411 412 2: { # video 413 ".3g2": 3, 414 } 415 } 416 417
418 - def __init__(self):
419 pass
420
421 - def getmediaindex(self, builtins, maps, results, key):
422 """Gets the media (wallpaper/ringtone) index 423 424 @param builtins: the builtin list on the phone 425 @param results: places results in this dict 426 @param maps: the list of index files and locations 427 @param key: key to place results in 428 """ 429 430 self.log("Reading "+key) 431 media={} 432 433 # builtins 434 for i,n in enumerate(builtins): # nb zero based index whereas previous phones used 1 435 media[i]={'name': n, 'origin': 'builtin'} 436 437 # maps 438 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor in maps: 439 for item in self.getindex(indexfile): 440 if item.type&0xff!=typemajor: 441 self.log("Entry "+item.filename+" has wrong type for this index. It is %d and should be %d" % (item.type&0xff, typemajor)) 442 self.log("This is going to cause you all sorts of problems.") 443 media[item.index]={ 444 'name': basename(item.filename), 445 'filename': item.filename, 446 'origin': type, 447 'vtype': item.type, 448 } 449 if item.date!=0: 450 media[item.index]['date']=item.date 451 452 # finish 453 results[key]=media
454
455 - def getindex(self, filename):
456 "read an index file" 457 try: 458 buf=prototypes.buffer(self.getfilecontents(filename)) 459 except com_brew.BrewNoSuchFileException: 460 return [] 461 462 g=self.protocolclass.indexfile() 463 # some media indexes have crap appended to the end, prevent this error from messing up everything 464 # valid entries at the start of the file will still get read OK. 465 try: 466 g.readfrombuffer(buf, logtitle="Index file "+filename) 467 except: 468 self.log("Corrupt index file "+`filename`+", this might cause you all sorts of problems.") 469 return g.items
470
471 - def getmedia(self, maps, results, key):
472 origins={} 473 # signal that we are using the new media storage that includes the origin and timestamp 474 origins['new_media_version']=1 475 476 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor in maps: 477 media={} 478 for item in self.getindex(indexfile): 479 data=None 480 timestamp=None 481 try: 482 stat_res=self.statfile(item.filename) 483 if stat_res!=None: 484 timestamp=stat_res['date'][0] 485 data=self.getfilecontents(item.filename, True) 486 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 487 self.log("It was in the index, but not on the filesystem") 488 if data!=None: 489 media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp} 490 origins[type]=media 491 492 results[key]=origins 493 return results
494
495 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
496 """Actually saves out the media 497 498 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 499 @param mediaindexkey: index key (eg 'wallpaper-index') 500 @param maps: list index files and locations 501 @param results: results dict 502 @param merge: are we merging or overwriting what is there? 503 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 504 """ 505 506 # take copies of the lists as we modify them 507 wp=results[mediakey].copy() # the media we want to save 508 wpi=results[mediaindexkey].copy() # what is already in the index files 509 510 # remove builtins 511 for k in wpi.keys(): 512 if wpi[k].get('origin', "")=='builtin': 513 del wpi[k] 514 515 # build up list into init 516 init={} 517 for type,_,_,_,lowestindex,_,typemajor in maps: 518 init[type]={} 519 for k in wpi.keys(): 520 if wpi[k]['origin']==type: 521 index=k 522 name=wpi[k]['name'] 523 fullname=wpi[k]['filename'] 524 vtype=wpi[k]['vtype'] 525 data=None 526 del wpi[k] 527 for w in wp.keys(): 528 # does wp contain a reference to this same item? 529 if wp[w]['name']==name and wp[w]['origin']==type: 530 data=wp[w]['data'] 531 del wp[w] 532 if not merge and data is None: 533 # delete the entry 534 continue 535 assert index>=lowestindex 536 init[type][index]={'name': name, 'data': data, 'filename': fullname, 'vtype': vtype} 537 538 # init now contains everything from wallpaper-index 539 # wp contains items that we still need to add, and weren't in the existing index 540 assert len(wpi)==0 541 print init.keys() 542 543 # now look through wallpapers and see if anything was assigned a particular 544 # origin 545 for w in wp.keys(): 546 o=wp[w].get("origin", "") 547 if o is not None and len(o) and o in init: 548 idx=-1 549 while idx in init[o]: 550 idx-=1 551 init[o][idx]=wp[w] 552 del wp[w] 553 554 # wp will now consist of items that weren't assigned any particular place 555 # so put them in the first available space 556 for type,_,_,_,lowestindex,maxentries,typemajor in maps: 557 # fill it up 558 for w in wp.keys(): 559 if len(init[type])>=maxentries: 560 break 561 idx=-1 562 while idx in init[type]: 563 idx-=1 564 init[type][idx]=wp[w] 565 del wp[w] 566 567 # time to write the files out 568 dircache=self.DirCache(self) 569 for type, indexfile, sizefile, directory, lowestindex, maxentries,typemajor in maps: 570 # get the index file so we can work out what to delete 571 names=[init[type][x]['name'] for x in init[type]] 572 for item in self.getindex(indexfile): 573 if basename(item.filename) not in names: 574 self.log(item.filename+" is being deleted") 575 try: 576 dircache.rmfile(item.filename) 577 except com_brew.BrewNoSuchFileException: 578 self.log("Hmm, it didn't exist!") 579 # fixup the indices 580 fixups=[k for k in init[type].keys() if k<lowestindex] 581 fixups.sort() 582 for f in fixups: 583 for ii in xrange(lowestindex, lowestindex+maxentries): 584 # allocate an index 585 if ii not in init[type]: 586 init[type][ii]=init[type][f] 587 del init[type][f] 588 break 589 # any left over? 590 fixups=[k for k in init[type].keys() if k<lowestindex] 591 for f in fixups: 592 self.log("There is no space in the index for "+type+" for "+init[type][f]['name']) 593 del init[type][f] 594 # write each entry out 595 for idx in init[type].keys(): 596 entry=init[type][idx] 597 filename=entry.get('filename', directory+"/"+entry['name']) 598 entry['filename']=filename 599 fstat=dircache.stat(filename) 600 if 'data' not in entry: 601 # must be in the filesystem already 602 if fstat is None: 603 self.log("Entry "+entry['name']+" is in index "+indexfile+" but there is no data for it and it isn't in the filesystem. The index entry will be removed.") 604 del init[type][idx] 605 continue 606 # check len(data) against fstat->length 607 data=entry['data'] 608 if data is None: 609 assert merge 610 continue # we are doing an add and don't have data for this existing entry 611 if fstat is not None and len(data)==fstat['size']: 612 self.log("Not writing "+filename+" as a file of the same name and length already exists.") 613 else: 614 dircache.writefile(filename, data) 615 # write out index 616 ifile=self.protocolclass.indexfile() 617 idxlist=init[type].keys() 618 idxlist.sort() 619 idxlist.reverse() # the phone has them in reverse order for some reason so we do the same 620 for idx in idxlist: 621 ie=self.protocolclass.indexentry() 622 ie.index=idx 623 vtype=init[type][idx].get("vtype", None) 624 if vtype is None: 625 vtype=self._guessvtype(init[type][idx]['filename'], typemajor) 626 print "guessed vtype of "+`vtype`+" for "+init[type][idx]['filename'] 627 else: 628 print init[type][idx]['filename']+" already had a vtype of "+`vtype` 629 ie.type=vtype 630 ie.filename=init[type][idx]['filename'] 631 # ie.date left as zero 632 ie.dunno=0 # mmmm 633 ifile.items.append(ie) 634 buf=prototypes.buffer() 635 ifile.writetobuffer(buf, logtitle="Index file "+indexfile) 636 self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(idxlist)`+" entries.") 637 dircache.writefile(indexfile, buf.getvalue()) # doesn't really need to go via dircache 638 # write out size file 639 size=0 640 for idx in idxlist: 641 fstat=dircache.stat(init[type][idx]['filename']) 642 size+=fstat['size'] 643 szfile=self.protocolclass.sizefile() 644 szfile.size=size 645 buf=prototypes.buffer() 646 szfile.writetobuffer(buf, logtitle="size file for "+type) 647 self.log("You are using a total of "+`size`+" bytes for "+type) 648 dircache.writefile(sizefile, buf.getvalue()) 649 return reindexfunction(results)
650
651 - def _guessvtype(self, filename, typemajor):
652 lookin=self._minor_typemap[typemajor] 653 for ext,val in lookin.items(): 654 if filename.lower().endswith(ext): 655 return typemajor+(256*val) 656 return typemajor # implicit val of zero
657
658 - def getwallpaperindices(self, results):
659 return self.getmediaindex(self.builtinwallpapers, self.wallpaperlocations, results, 'wallpaper-index')
660
661 - def getringtoneindices(self, results):
662 return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index')
663
664 - def getwallpapers(self, result):
665 return self.getmedia(self.wallpaperlocations, result, 'wallpapers')
666
667 - def getringtones(self, result):
668 return self.getmedia(self.ringtonelocations, result, 'ringtone')
669
670 - def savewallpapers(self, results, merge):
671 return self.savemedia('wallpapers', 'wallpaper-index', self.wallpaperlocations, results, merge, self.getwallpaperindices)
672
673 - def saveringtones(self, results, merge):
674 return self.savemedia('ringtone', 'ringtone-index', self.ringtonelocations, results, merge, self.getringtoneindices)
675
676 -class LGNewIndexedMedia2(LGNewIndexedMedia):
677 """Implements media for LG phones that use the newer index format such as the VX8100/5200 678 Similar ot the other new media type hence the subclassing, but the type field in the 679 PACKET has a different meaning so different code is required and it has support for an icon 680 field that affects whcih icon is displayed next to the tone on the phone 681 """ 682 683 # tables for minor type number 684 _minor_typemap={ 685 ".jpg": 3, 686 ".bmp": 1, 687 ".qcp": 5, 688 ".mid": 4, 689 ".3g2": 3, 690 } 691
692 - def _guessvtype(self, filename, typemajor):
693 if typemajor > 2: # some index files are hard coded 694 return typemajor 695 for ext,val in self._minor_typemap.items(): 696 if filename.lower().endswith(ext): 697 return typemajor+(256*val) 698 return typemajor # implicit val of zero
699
700 - def getmediaindex(self, builtins, maps, results, key):
701 """Gets the media (wallpaper/ringtone) index 702 703 @param builtins: the builtin list on the phone 704 @param results: places results in this dict 705 @param maps: the list of index files and locations 706 @param key: key to place results in 707 """ 708 709 self.log("Reading "+key) 710 media={} 711 712 # builtins 713 for i,n in enumerate(builtins): # nb zero based index whereas previous phones used 1 714 media[i]={'name': n, 'origin': 'builtin'} 715 716 # maps 717 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 718 for item in self.getindex(indexfile): 719 if item.type&0xff!=typemajor&0xff: 720 self.log("Entry "+item.filename+" has wrong type for this index. It is %d and should be %d" % (item.type&0xff, typemajor)) 721 self.log("This is going to cause you all sorts of problems.") 722 _idx=item.index+idx_ofs 723 media[_idx]={ 724 'name': basename(item.filename), 725 'filename': item.filename, 726 'origin': type, 727 'vtype': item.type, 728 'icon': item.icon 729 } 730 if item.date!=0: 731 media[_idx]['date']=item.date 732 733 # finish 734 results[key]=media
735
736 - def getmedia(self, maps, results, key):
737 origins={} 738 # signal that we are using the new media storage that includes the origin and timestamp 739 origins['new_media_version']=1 740 741 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 742 media={} 743 for item in self.getindex(indexfile): 744 data=None 745 timestamp=None 746 try: 747 stat_res=self.statfile(item.filename) 748 if stat_res!=None: 749 timestamp=stat_res['date'][0] 750 data=self.getfilecontents(item.filename, True) 751 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 752 self.log("It was in the index, but not on the filesystem") 753 except com_brew.BrewAccessDeniedException: 754 # firmware wouldn't let us read this file, just mark it then 755 self.log('Failed to read file: '+item.filename) 756 data='' 757 if data!=None: 758 media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp} 759 origins[type]=media 760 761 results[key]=origins 762 return results
763
764 - def getmedia(self, maps, results, key):
765 media={} 766 767 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 768 for item in self.getindex(indexfile): 769 try: 770 media[basename(item.filename)]=self.getfilecontents(item.filename, 771 True) 772 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 773 self.log("It was in the index, but not on the filesystem") 774 775 results[key]=media 776 return results
777
778 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
779 """Actually saves out the media 780 781 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 782 @param mediaindexkey: index key (eg 'wallpaper-index') 783 @param maps: list index files and locations 784 @param results: results dict 785 @param merge: are we merging or overwriting what is there? 786 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 787 """ 788 789 # take copies of the lists as we modify them 790 wp=results[mediakey].copy() # the media we want to save 791 wpi=results[mediaindexkey].copy() # what is already in the index files 792 793 # remove builtins 794 for k in wpi.keys(): 795 if wpi[k].get('origin', "")=='builtin': 796 del wpi[k] 797 798 # build up list into init 799 init={} 800 for type,_,_,_,lowestindex,_,typemajor,_,idx_ofs in maps: 801 init[type]={} 802 for k in wpi.keys(): 803 if wpi[k]['origin']==type: 804 index=k-idx_ofs 805 name=wpi[k]['name'] 806 fullname=wpi[k]['filename'] 807 vtype=wpi[k]['vtype'] 808 icon=wpi[k]['icon'] 809 data=None 810 del wpi[k] 811 for w in wp.keys(): 812 # does wp contain a reference to this same item? 813 if wp[w]['name']==name and wp[w]['origin']==type: 814 data=wp[w]['data'] 815 del wp[w] 816 if not merge and data is None: 817 # delete the entry 818 continue 819 assert index>=lowestindex 820 init[type][index]={'name': name, 'data': data, 'filename': fullname, 'vtype': vtype, 'icon': icon} 821 822 # init now contains everything from wallpaper-index 823 # wp contains items that we still need to add, and weren't in the existing index 824 assert len(wpi)==0 825 print init.keys() 826 827 # now look through wallpapers and see if anything was assigned a particular 828 # origin 829 for w in wp.keys(): 830 o=wp[w].get("origin", "") 831 if o is not None and len(o) and o in init: 832 idx=-1 833 while idx in init[o]: 834 idx-=1 835 init[o][idx]=wp[w] 836 del wp[w] 837 838 # wp will now consist of items that weren't assigned any particular place 839 # so put them in the first available space 840 for type,_,_,_,lowestindex,maxentries,typemajor,def_icon,_ in maps: 841 # fill it up 842 for w in wp.keys(): 843 if len(init[type])>=maxentries: 844 break 845 idx=-1 846 while idx in init[type]: 847 idx-=1 848 init[type][idx]=wp[w] 849 del wp[w] 850 851 # time to write the files out 852 dircache=self.DirCache(self) 853 for type, indexfile, sizefile, directory, lowestindex, maxentries,typemajor,def_icon,_ in maps: 854 # get the index file so we can work out what to delete 855 names=[init[type][x]['name'] for x in init[type]] 856 for item in self.getindex(indexfile): 857 if basename(item.filename) not in names: 858 self.log(item.filename+" is being deleted") 859 try: 860 dircache.rmfile(item.filename) 861 except com_brew.BrewNoSuchFileException: 862 self.log("Hmm, it didn't exist!") 863 # fixup the indices 864 fixups=[k for k in init[type].keys() if k<lowestindex] 865 fixups.sort() 866 for f in fixups: 867 for ii in xrange(lowestindex, lowestindex+maxentries): 868 # allocate an index 869 if ii not in init[type]: 870 init[type][ii]=init[type][f] 871 del init[type][f] 872 break 873 # any left over? 874 fixups=[k for k in init[type].keys() if k<lowestindex] 875 for f in fixups: 876 self.log("There is no space in the index for "+type+" for "+init[type][f]['name']) 877 del init[type][f] 878 # write each entry out 879 for idx in init[type].keys(): 880 entry=init[type][idx] 881 filename=entry.get('filename', directory+"/"+entry['name']) 882 entry['filename']=filename 883 fstat=dircache.stat(filename) 884 if 'data' not in entry: 885 # must be in the filesystem already 886 if fstat is None: 887 self.log("Entry "+entry['name']+" is in index "+indexfile+" but there is no data for it and it isn't in the filesystem. The index entry will be removed.") 888 del init[type][idx] 889 continue 890 # check len(data) against fstat->length 891 data=entry['data'] 892 if data is None: 893 assert merge 894 continue # we are doing an add and don't have data for this existing entry 895 if fstat is not None and len(data)==fstat['size']: 896 self.log("Not writing "+filename+" as a file of the same name and length already exists.") 897 else: 898 dircache.writefile(filename, data) 899 # write out index 900 ifile=self.protocolclass.indexfile() 901 idxlist=init[type].keys() 902 idxlist.sort() 903 idxlist.reverse() # the phone has them in reverse order for some reason so we do the same 904 for idx in idxlist: 905 ie=self.protocolclass.indexentry() 906 ie.index=idx 907 vtype=init[type][idx].get("vtype", None) 908 if vtype is None: 909 vtype=self._guessvtype(init[type][idx]['filename'], typemajor) 910 ie.type=vtype 911 ie.filename=init[type][idx]['filename'] 912 # ie.date left as zero 913 ie.dunno=0 # mmmm 914 icon=init[type][idx].get("icon", None) 915 if icon is None: 916 icon=def_icon 917 ie.icon=icon 918 ifile.items.append(ie) 919 buf=prototypes.buffer() 920 ifile.writetobuffer(buf, logtitle="Index file "+indexfile) 921 self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(idxlist)`+" entries.") 922 dircache.writefile(indexfile, buf.getvalue()) # doesn't really need to go via dircache 923 # write out size file, if it is required 924 if sizefile != '': 925 size=0 926 for idx in idxlist: 927 fstat=dircache.stat(init[type][idx]['filename']) 928 size+=fstat['size'] 929 szfile=self.protocolclass.sizefile() 930 szfile.size=size 931 buf=prototypes.buffer() 932 szfile.writetobuffer(buf, logtitle="Writing size file "+sizefile) 933 self.log("You are using a total of "+`size`+" bytes for "+type) 934 dircache.writefile(sizefile, buf.getvalue()) 935 return reindexfunction(results)
936 937 938
939 -class LGDirectoryMedia:
940 """The media is stored one per directory with .desc and body files""" 941
942 - def __init__(self):
943 pass
944
945 - def getmediaindex(self, builtins, maps, results, key):
946 """Gets the media (wallpaper/ringtone) index 947 948 @param builtins: the builtin list on the phone 949 @param results: places results in this dict 950 @param maps: the list of index files and locations 951 @param key: key to place results in 952 """ 953 self.log("Reading "+key) 954 media={} 955 956 # builtins 957 c=1 958 for name in builtins: 959 media[c]={'name': name, 'origin': 'builtin' } 960 c+=1 961 962 # directory 963 for offset,location,origin,maxentries in maps: 964 index=self.getindex(location) 965 for i in index: 966 media[i+offset]={'name': index[i], 'origin': origin} 967 968 results[key]=media 969 return media
970 971 __mimetoextensionmapping={ 972 'image/jpg': '.jpg', 973 'image/bmp': '.bmp', 974 'image/png': '.png', 975 'image/gif': '.gif', 976 'image/bci': '.bci', 977 'audio/mp3': '.mp3', 978 'audio/mid': '.mid', 979 'audio/qcp': '.qcp' 980 } 981
982 - def _createnamewithmimetype(self, name, mt):
983 name=basename(name) 984 if mt=="image/jpeg": 985 mt="image/jpg" 986 try: 987 return name+self.__mimetoextensionmapping[mt] 988 except KeyError: 989 self.log("Unable to figure out extension for mime type "+mt) 990 return name
991
992 - def _getmimetype(self, name):
993 ext=getext(name.lower()) 994 if len(ext): ext="."+ext 995 if ext==".jpeg": 996 return "image/jpg" # special case 997 for mt,extension in self.__mimetoextensionmapping.items(): 998 if ext==extension: 999 return mt 1000 self.log("Unable to figure out a mime type for "+name) 1001 assert False, "No idea what type "+ext+" is" 1002 return "x-unknown/x-unknown"
1003
1004 - def getindex(self, location, getmedia=False):
1005 """Returns an index based on the sub-directories of location. 1006 The key is an integer, and the value is the corresponding name""" 1007 index={} 1008 try: 1009 dirlisting=self.getfilesystem(location) 1010 except com_brew.BrewNoSuchDirectoryException: 1011 return index 1012 1013 for item in dirlisting: 1014 if dirlisting[item]['type']!='directory': 1015 continue 1016 try: 1017 buf=prototypes.buffer(self.getfilecontents(dirlisting[item]['name']+"/.desc")) 1018 except com_brew.BrewNoSuchFileException: 1019 self.log("No .desc file in "+dirlisting[item]['name']+" - ignoring directory") 1020 continue 1021 desc=self.protocolclass.mediadesc() 1022 desc.readfrombuffer(buf, logtitle=".desc file %s/.desc read" % (dirlisting[item]['name'],)) 1023 filename=self._createnamewithmimetype(dirlisting[item]['name'], desc.mimetype) 1024 if not getmedia: 1025 index[desc.index]=filename 1026 else: 1027 try: 1028 # try to read it using name in desc file 1029 contents=self.getfilecontents(dirlisting[item]['name']+"/"+desc.filename) 1030 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException): 1031 try: 1032 # then try using "body" 1033 contents=self.getfilecontents(dirlisting[item]['name']+"/body") 1034 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException,com_brew.BrewNameTooLongException): 1035 self.log("Can't find the actual content in "+dirlisting[item]['name']) 1036 continue 1037 index[filename]=contents 1038 return index
1039
1040 - def getmedia(self, maps, result, key):
1041 """Returns the contents of media as a dict where the key is a name as returned 1042 by getindex, and the value is the contents of the media""" 1043 media={} 1044 for offset,location,origin,maxentries in maps: 1045 media.update(self.getindex(location, getmedia=True)) 1046 result[key]=media 1047 return result
1048
1049 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
1050 """Actually saves out the media 1051 1052 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 1053 @param mediaindexkey: index key (eg 'wallpaper-index') 1054 @param maps: list index files and locations 1055 @param results: results dict 1056 @param merge: are we merging or overwriting what is there? 1057 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 1058 """ 1059 # this is based on the IndexedMedia function and they are frustratingly similar 1060 print results.keys() 1061 # I humbly submit this as the longest function in the bitpim code ... 1062 # wp and wpi are used as variable names as this function was originally 1063 # written to do wallpaper. it works just fine for ringtones as well 1064 wp=results[mediakey].copy() 1065 wpi=results[mediaindexkey].copy() 1066 # remove builtins 1067 for k in wpi.keys(): 1068 if wpi[k]['origin']=='builtin': 1069 del wpi[k] 1070 1071 # sort results['mediakey'+'-index'] into origin buckets 1072 1073 # build up list into init 1074 init={} 1075 for offset,location,type,maxentries in maps: 1076 init[type]={} 1077 for k in wpi.keys(): 1078 if wpi[k]['origin']==type: 1079 index=k-offset 1080 name=wpi[k]['name'] 1081 data=None 1082 del wpi[k] 1083 for w in wp.keys(): 1084 if wp[w]['name']==name and wp[w]['origin']==type: 1085 data=wp[w]['data'] 1086 del wp[w] 1087 if not merge and data is None: 1088 # delete the entry 1089 continue 1090 init[type][index]={'name': name, 'data': data} 1091 1092 # init now contains everything from wallpaper-index 1093 print init.keys() 1094 # now look through wallpapers and see if anything remaining was assigned a particular 1095 # origin 1096 for w in wp.keys(): 1097 o=wp[w].get("origin", "") 1098 if o is not None and len(o) and o in init: 1099 idx=-1 1100 while idx in init[o]: 1101 idx-=1 1102 init[o][idx]=wp[w] 1103 del wp[w] 1104 1105 # we now have init[type] with the entries and index number as key (negative indices are 1106 # unallocated). Proceed to deal with each one, taking in stuff from wp as we have space 1107 for offset,location,type,maxentries in maps: 1108 if type=="camera": break 1109 index=init[type] 1110 try: 1111 dirlisting=self.getfilesystem(location) 1112 except com_brew.BrewNoSuchDirectoryException: 1113 self.mkdirs(location) 1114 dirlisting={} 1115 # rename keys to basename 1116 for i in dirlisting.keys(): 1117 dirlisting[i[len(location)+1:]]=dirlisting[i] 1118 del dirlisting[i] 1119 # what we will be deleting 1120 dellist=[] 1121 if not merge: 1122 # get existing wpi for this location 1123 wpi=results[mediaindexkey] 1124 for i in wpi: 1125 entry=wpi[i] 1126 if entry['origin']==type: 1127 # it is in the original index, are we writing it back out? 1128 delit=True 1129 for idx in index: 1130 if index[idx]['name']==entry['name']: 1131 delit=False 1132 break 1133 if delit: 1134 if stripext(entry['name']) in dirlisting: 1135 dellist.append(entry['name']) 1136 else: 1137 self.log("%s in %s index but not filesystem" % (entry['name'], type)) 1138 # go ahead and delete unwanted directories 1139 print "deleting",dellist 1140 for f in dellist: 1141 self.rmdirs(location+"/"+f) 1142 # slurp up any from wp we can take 1143 while len(index)<maxentries and len(wp): 1144 idx=-1 1145 while idx in index: 1146 idx-=1 1147 k=wp.keys()[0] 1148 index[idx]=wp[k] 1149 del wp[k] 1150 # normalise indices 1151 index=self._normaliseindices(index) # hey look, I called a function! 1152 # move any overflow back into wp 1153 if len(index)>maxentries: 1154 keys=index.keys() 1155 keys.sort() 1156 for k in keys[maxentries:]: 1157 idx=-1 1158 while idx in wp: 1159 idx-=1 1160 wp[idx]=index[k] 1161 del index[k] 1162 1163 # write out the content 1164 1165 #### index is dict, key is index number, value is dict 1166 #### value['name'] = filename 1167 #### value['data'] is contents 1168 listing=self.getfilesystem(location, 1) 1169 1170 for key in index: 1171 efile=index[key]['name'] 1172 content=index[key]['data'] 1173 if content is None: 1174 continue # in theory we could rewrite .desc file in case index number has changed 1175 mimetype=self._getmimetype(efile) 1176 dirname=stripext(efile) 1177 desc=self.protocolclass.mediadesc() 1178 desc.index=key 1179 desc.filename="body" 1180 desc.mimetype=mimetype 1181 desc.totalsize=0 1182 desc.totalsize=desc.packetsize()+len(content) 1183 buf=prototypes.buffer() 1184 descfile="%s/%s/.desc" % (location, dirname) 1185 desc.writetobuffer(buf, logtitle="Desc file at "+descfile) 1186 try: 1187 self.mkdir("%s/%s" % (location,dirname)) 1188 except com_brew.BrewDirectoryExistsException: 1189 pass 1190 self.writefile(descfile, buf.getvalue()) 1191 bodyfile="%s/%s/body" % (location, dirname) 1192 if bodyfile in listing and len(content)==listing[bodyfile]['size']: 1193 self.log("Skipping writing %s as there is already a file of the same length" % (bodyfile,)) 1194 else: 1195 self.writefile(bodyfile, content) 1196 1197 # did we have too many 1198 if len(wp): 1199 for k in wp: 1200 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],)) 1201 1202 # Note that we don't write to the camera area 1203 1204 # tidy up - reread indices 1205 del results[mediakey] # done with it 1206 reindexfunction(results) 1207 return results
1208
1209 -class LGUncountedIndexedMedia:
1210 """Implements media for LG phones that use the new index format with index file with no counters such as the VX8300 1211 Allow external media to be managed without downloading files, can detect if external media is present. 1212 Also contains 'hack' for ringtones to allow users to store ringtones on the external media""" 1213
1214 - def __init__(self):
1215 pass
1216
1217 - def getmediaindex(self, builtins, maps, results, key):
1218 """Gets the media (wallpaper/ringtone) index 1219 1220 @param builtins: the builtin list on the phone 1221 @param results: places results in this dict 1222 @param maps: the list of index files and locations 1223 @param key: key to place results in 1224 """ 1225 1226 self.log("Reading "+key) 1227 media={} 1228 1229 # builtins 1230 index = 0; # MIC Initialize counter here, instead of the next stanza 1231 for i,n in enumerate(builtins): # nb zero based index whereas previous phones used 1 1232 media[i]={'name': n, 'origin': 'builtin'} 1233 index+=1; 1234 1235 # maps 1236 # index=0 # MIC Do not want to reset index; builtins start at 0 1237 # Otherwise, the builtins are overwriten by 1238 # these 1239 for type, indexfile, directory, external_dir, maxentries, typemajor,_idx in maps: 1240 if _idx is not None: 1241 index=_idx 1242 for item in self.getindex(indexfile): 1243 media[index]={ 1244 'name': basename(item.filename), 1245 'filename': item.filename, 1246 'origin': type, 1247 } 1248 if item.date!=0: 1249 media[index]['date']=item.date 1250 index+=1 1251 1252 # finish 1253 results[key]=media
1254
1255 - def getindex(self, filename):
1256 "read an index file" 1257 try: 1258 buf=prototypes.buffer(self.getfilecontents(filename)) 1259 except com_brew.BrewNoSuchFileException: 1260 return [] 1261 1262 g=self.protocolclass.indexfile() 1263 # some media indexes have crap appended to the end, prevent this error from messing up everything 1264 # valid entries at the start of the file will still get read OK. 1265 try: 1266 g.readfrombuffer(buf, logtitle="Index file "+filename) 1267 except: 1268 self.log("Corrupt index file "+`filename`+", this might cause you all sorts of problems.") 1269 return g.items
1270
1271 - def getmedia(self, maps, results, key):
1272 origins={} 1273 # signal that we are using the new media storage that includes the origin and timestamp 1274 origins['new_media_version']=1 1275 1276 for type, indexfile, directory, external_dir, maxentries, typemajor, _idx in maps: 1277 media={} 1278 for item in self.getindex(indexfile): 1279 data=None 1280 # skip files that are not in the actual directory 1281 # these are external media files that are handled 1282 # differently, this will prevent them from showing 1283 # in the media views 1284 if not item.filename.startswith(directory): 1285 continue 1286 timestamp=None 1287 try: 1288 stat_res=self.statfile(item.filename) 1289 if stat_res!=None: 1290 timestamp=stat_res['date'][0] 1291 if not self.is_external_media(item.filename): 1292 data=self.getfilecontents(item.filename, True) 1293 else: 1294 # for external memory skip reading it is very slow 1295 # the file will show up in bitpim allowing it to 1296 # be managed, files added from bitpim will be 1297 # visible because we will have a copy of the 1298 # file we copied to the phone. 1299 data='' 1300 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 1301 self.log("It was in the index, but not on the filesystem") 1302 except (com_brew.BrewFileLockedException): 1303 self.log("Could not read " + item.filename + " possibly due to SD card not being present.") 1304 except com_brew.BrewAccessDeniedException: 1305 # firmware wouldn't let us read this file, just mark it then 1306 self.log('Failed to read file: '+item.filename) 1307 data='' 1308 if data!=None: 1309 media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp} 1310 origins[type]=media 1311 1312 results[key]=origins 1313 return results
1314
1315 - def is_external_media(self, filename):
1316 return filename.startswith(self.external_storage_root)
1317
1318 - def external_storage_present(self):
1319 dircache=self.DirCache(self) 1320 test_name=self.external_storage_root+"bitpim_test" 1321 try: 1322 dircache.writefile(test_name, "bitpim_test") 1323 except: 1324 return False 1325 dircache.rmfile(test_name) 1326 return True
1327
1328 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, 1329 reindexfunction, update_index_file=True):
1330 """Actually saves out the media 1331 1332 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 1333 @param mediaindexkey: index key (eg 'wallpaper-index') 1334 @param maps: list index files and locations 1335 @param results: results dict 1336 @param merge: are we merging or overwriting what is there? 1337 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 1338 """ 1339 1340 # take copies of the lists as we modify them 1341 wp=results[mediakey].copy() # the media we want to save 1342 wpi=results[mediaindexkey].copy() # what is already in the index files 1343 1344 # remove builtins 1345 for k in wpi.keys(): 1346 if wpi[k].get('origin', "")=='builtin': 1347 del wpi[k] 1348 1349 use_external_media=self.external_storage_present() 1350 skip_origin=[] 1351 1352 for type, indexfile, directory, external_dir, maxentries, typemajor, _idx in maps: 1353 # if no external media is present skip indexes which refer 1354 # to external media 1355 if self.is_external_media(directory) and not use_external_media: 1356 self.log(" No external storage detected. Skipping "+type+" media.") 1357 skip_origin.append(type) 1358 else: 1359 # make sure the media directory exists 1360 try: 1361 self.mkdirs(directory) 1362 except: 1363 pass 1364 1365 1366 # build up list into init 1367 init={} 1368 for type, indexfile, directory, external_dir, maxentries, typemajor, _idx in maps: 1369 init[type]={} 1370 for k in wpi.keys(): 1371 if wpi[k]['origin']==type: 1372 name=wpi[k]['name'] 1373 fullname=wpi[k]['filename'] 1374 data=None 1375 del wpi[k] 1376 for w in wp.keys(): 1377 # does wp contain a reference to this same item? 1378 if wp[w]['name']==name and wp[w]['origin']==type: 1379 data=wp[w]['data'] 1380 del wp[w] 1381 if not merge and data is None: 1382 # delete the entry 1383 continue 1384 if type in skip_origin: 1385 self.log("skipping "+name+" in index "+type+" no external media detected") 1386 elif fullname.startswith(directory): 1387 # only add in files that are really in the media directory on the phone 1388 # fake files will be added in later in this function 1389 init[type][name]={'data': data, 'filename': fullname} 1390 1391 # init now contains what is in the original indexes that should stay on the phone 1392 # wp contains items that we still need to add, and weren't in the existing index 1393 assert len(wpi)==0 1394 1395 # now look through the media and see if anything was assigned a particular 1396 # origin and put it in the indexes, things that were not assigned are dropped 1397 for w in wp.keys(): 1398 o=wp[w].get("origin", "") 1399 if o is not None and len(o) and o in init and o not in skip_origin: 1400 init[o][wp[w]['name']]={'data': wp[w]['data']} 1401 del wp[w] 1402 1403 # if external media is specified, add all the extra files to the index 1404 for type, indexfile, directory, external_dir, maxentries, typemajor, _idx in maps: 1405 if type not in skip_origin and len(external_dir): 1406 if self.is_external_media(external_dir) and not use_external_media: 1407 continue 1408 try: 1409 dirlisting=self.listfiles(external_dir) 1410 except: 1411 self.log("Unable to list files in external directory "+external_dir) 1412 continue 1413 for file in dirlisting: 1414 init[type][basename(file)]={'data': 'bitpim:)', 'filename': file} 1415 1416 1417 # time to write the files out 1418 dircache=self.DirCache(self) 1419 for type, indexfile, directory, external_dir, maxentries, typemajor, _idx in maps: 1420 if type not in skip_origin: 1421 # get the old index file so we can work out what to delete 1422 for item in self.getindex(indexfile): 1423 # force the name to the correct directory 1424 # this will then cleanup fake external files that are 1425 # no longer in the index 1426 real_filename=directory+"/"+basename(item.filename) 1427 if basename(item.filename) not in init[type]: 1428 self.log(real_filename+" is being deleted") 1429 try: 1430 dircache.rmfile(real_filename) 1431 except com_brew.BrewNoSuchFileException: 1432 self.log("Hmm, it didn't exist!") 1433 # write each entry out 1434 for idx in init[type].keys(): 1435 entry=init[type][idx] 1436 # we force the use of the correct directory, regardless of the 1437 # actual path the file is in, the phone requires the file to be in the correct 1438 # location or it will rewrite the index file on reboot 1439 # the actual index file can point to a different location 1440 # as long as the filename exists, allowing a hack. 1441 filename=directory+"/"+idx 1442 if not entry.has_key('filename'): 1443 entry['filename']=filename 1444 fstat=dircache.stat(filename) 1445 if 'data' not in entry: 1446 # must be in the filesystem already 1447 if fstat is None: 1448 self.log("Entry "+idx+" is in index "+indexfile+" but there is no data for it and it isn't in the filesystem. The index entry will be removed.") 1449 del init[type][idx] 1450 continue 1451 # check len(data) against fstat->length 1452 data=entry['data'] 1453 if data is None: 1454 assert merge 1455 continue # we are doing an add and don't have data for this existing entry 1456 if not data: 1457 # check for files with no data, probably due to access denied or external media read 1458 self.log('Not writing '+filename+', no data available') 1459 continue 1460 if fstat is not None and len(data)==fstat['size']: 1461 self.log("Not writing "+filename+" as a file of the same name and length already exists.") 1462 else: 1463 dircache.writefile(filename, data) 1464 # write out index 1465 if update_index_file: 1466 ifile=self.protocolclass.indexfile() 1467 idxlist=init[type].keys() 1468 idxlist.sort() 1469 idxlist.reverse() 1470 for idx in idxlist: 1471 ie=self.protocolclass.indexentry() 1472 ie.type=typemajor 1473 ie.filename=init[type][idx]['filename'] 1474 fstat=dircache.stat(init[type][idx]['filename']) 1475 if fstat is not None: 1476 ie.size=fstat['size'] 1477 else: 1478 ie.size=0 1479 # ie.date left as zero 1480 ifile.items.append(ie) 1481 buf=prototypes.buffer() 1482 ifile.writetobuffer(buf, logtitle="Index file "+indexfile) 1483 self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(idxlist)`+" entries.") 1484 dircache.writefile(indexfile, buf.getvalue()) # doesn't really need to go via dircache 1485 return reindexfunction(results)
1486
1487 - def getwallpaperindices(self, results):
1488 return self.getmediaindex(self.builtinwallpapers, self.wallpaperlocations, results, 'wallpaper-index')
1489
1490 - def getringtoneindices(self, results):
1491 return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index')
1492
1493 - def getwallpapers(self, result):
1494 return self.getmedia(self.wallpaperlocations, result, 'wallpapers')
1495
1496 - def getringtones(self, result):
1497 return self.getmedia(self.ringtonelocations, result, 'ringtone')
1498
1499 - def savewallpapers(self, results, merge):
1500 return self.savemedia('wallpapers', 'wallpaper-index', self.wallpaperlocations, results, merge, self.getwallpaperindices)
1501
1502 - def saveringtones(self, results, merge):
1503 return self.savemedia('ringtone', 'ringtone-index', self.ringtonelocations, results, merge, self.getringtoneindices)
1504
1505 -class EnterDMError(Exception):
1506 pass
1507
1508 -class LGDMPhone:
1509 """Class to handle getting the phone into Diagnostic Mode (DM) for later 1510 Lg model phones. Subclass should set the following in __init__: 1511 self._timeout: how long (in seconds) before the phone gets kicked out of DM 1512 Subclass may also override the following methods: 1513 self.setDMversion(): set self._DMv5 to True or False. 1514 """ 1515
1516 - def _rotate_left(self, value, nbits):
1517 return ((value << nbits) | (value >> (32-nbits))) & 0xffffffffL
1518
1519 - def get_challenge_response(self, challenge):
1520 # Reverse engineered and contributed by Nathan Hjelm <hjelmn@users.sourceforge.net> 1521 # get_challenge_response(challenge): 1522 # - Takes the SHA-1 hash of a 16-byte block containing the challenge and returns the proper response. 1523 # - The hash used by the vx8700 differs from the standard implementation only in that it does not append a 1524 # 1 bit before padding the message with 0's. 1525 # 1526 # Return value: 1527 # Last three bytes of the SHA-1 hash or'd with 0x80000000. 1528 # IV = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) 1529 input_vector = [0x67452301L, 0xefcdab89L, 0x98badcfeL, 0x10325476L, 0xc3d2e1f0L] 1530 hash_result = [0x67452301L, 0xefcdab89L, 0x98badcfeL, 0x10325476L, 0xc3d2e1f0L] 1531 hash_data = [] 1532 hash_data.append(long(challenge)) 1533 # pad message with zeros as well and zero first word of bit length 1534 # if this were standard SHA-1 then 0x00000080 would be appended here as its a 56-bit message 1535 for i in range(14): 1536 hash_data.append(0L) 1537 # append second word of the bit length (56 bit message?) 1538 hash_data.append(56L) 1539 for i in range(80): 1540 j = i & 0x0f 1541 if i > 15: 1542 index1 = (i - 3) & 0x0f 1543 index2 = (i - 8) & 0x0f 1544 index3 = (i - 14) & 0x0f 1545 hash_data[j] = hash_data[index1] ^ hash_data[index2] ^ hash_data[index3] ^ hash_data[j] 1546 hash_data[j] = self._rotate_left (hash_data[j], 1) 1547 if i < 20: 1548 # f = (B and C) or ((not B) and C), k = 0x5a827999 1549 f = (hash_result[1] & hash_result[2]) | ((~hash_result[1]) & hash_result[3]) 1550 k = 0x5a827999L 1551 elif i < 40: 1552 # f = B xor C xor D, k = 0x6ed9eba1 1553 f = hash_result[1] ^ hash_result[2] ^ hash_result[3] 1554 k = 0x6ed9eba1L 1555 elif i < 60: 1556 # f = (B and C) or (B and D) or (B and C), k = 0x8f1bbcdc 1557 f = (hash_result[1] & hash_result[2]) | (hash_result[1] & hash_result[3]) | (hash_result[2] & hash_result[3]) 1558 k = 0x8f1bbcdcL 1559 else: 1560 # f = B xor C xor D, k = 0xca62c1d6 1561 f = hash_result[1] ^ hash_result[2] ^ hash_result[3] 1562 k = 0xca62c1d6L 1563 # A = E + rotate_left (A, 5) + w[j] + f + k 1564 newA = (hash_result[4] + self._rotate_left(hash_result[0], 5) + hash_data[j] + f + k) & 0xffffffffL 1565 # B = oldA, C = rotate_left(B, 30), D = C, E = D 1566 hash_result = [newA] + hash_result[0:4] 1567 hash_result[2] = self._rotate_left (hash_result[2], 30) 1568 for i in range(5): 1569 hash_result[i] = (hash_result[i] + input_vector[i]) & 0xffffffffL 1570 return 0x80000000L | (hash_result[4] & 0x00ffffffL)
1571
1572 - def _unlock_key(self):
1573 _req=self.protocolclass.LockKeyReq(lock=1) 1574 self.sendbrewcommand(_req, self.protocolclass.data)
1575
1576 - def _lock_key(self):
1577 _req=self.protocolclass.LockKeyReq() 1578 self.sendbrewcommand(_req, self.protocolclass.data)
1579
1580 - def _press_key(self, keys):
1581 # simulate a series of keypress 1582 if not keys: 1583 return 1584 _req=self.protocolclass.KeyPressReq() 1585 for _k in keys: 1586 _req.key=_k 1587 self.sendbrewcommand(_req, self.protocolclass.data)
1588
1589 - def _enter_DMv4(self):
1590 self._lock_key() 1591 self._press_key('\x06\x513733929\x51') 1592 self._unlock_key()
1593
1594 - def _enter_DMv5(self):
1595 # request the seed 1596 _req=self.protocolclass.ULReq(unlock_key=0) 1597 _resp=self.sendbrewcommand(_req, self.protocolclass.ULRes) 1598 1599 # respond with the key 1600 _key=self.get_challenge_response(_resp.unlock_key) 1601 if _key is None: 1602 self.log('Failed to get the key.') 1603 raise EnterDMError('Failed to get the key') 1604 1605 _req=self.protocolclass.ULReq(unlock_code=1, unlock_key=_key) 1606 _resp=self.sendbrewcommand(_req, self.protocolclass.ULRes) 1607 if _resp.unlock_ok!=1: 1608 raise EnterDMError('Bad response - unlock_ok: %d'%_resp.unlock_ok)
1609
1610 - def _DMv6_get_esn(self):
1611 _req=self.protocolclass.NVReq(field=0x0000) 1612 _res=self.sendbrewcommand(_req, self.protocolclass.NVRes) 1613 1614 return struct.unpack_from ('<L', _res.data, 0)[0]
1615
1616 - def _DMv6_get_extra_data(self,data_field):
1617 _req=self.protocolclass.NVReq(field=data_field) 1618 _res=self.sendbrewcommand(_req, self.protocolclass.NVRes) 1619 1620 return struct.unpack_from ('<L', _res.data, 0)[0]
1621
1622 - def _DMv6_get_compile_time(self):
1623 _req=self.protocolclass.FWInfoReq() 1624 _res=self.sendbrewcommand(_req, self.protocolclass.FWInfoRes) 1625 1626 return _res.get_compile_time()
1627
1628 - def _enter_DMv6(self):
1629 # this loop is a hack -- different LG phones use different commands to get _extra. once 1630 # the command is figured out we won't have to try every possible shift. 1631 if self._shift is None: 1632 _shifts=range(4) 1633 else: 1634 _shifts=[self._shift] 1635 for _shift in _shifts: 1636 # similar but slightly different from v5, the enV2 started this! 1637 # request the seed 1638 _req=self.protocolclass.DMKeyReq() 1639 _resp=self.sendbrewcommand(_req, self.protocolclass.DMKeyResp) 1640 _key=self.get_challenge_response(_resp.unlock_key) 1641 if _key is None: 1642 self.log('Failed to get the key.') 1643 raise EnterDMError('Failed to get the key') 1644 1645 #_esn=self._DMv6_get_esn() 1646 #_extra=self._DMv6_get_extra_data(0x13d7) # VX-9100 uses data field 0x13d7 1647 #_ctime=self._DMv6_get_compile_time() 1648 1649 # determine how many bytes the key needs be be shifted 1650 #_shift_bytes = (_esn + ((_extra >> 16) & 0xf) + ((_extra >> 11) & 0x1f) + _ctime) % 16 1651 #_shift = _shift_bytes / 4 1652 1653 _req=self.protocolclass.DMEnterReq(unlock_key=_key) 1654 if _resp.unlock_code==0: 1655 # this response usually occurs in error. rebooting the phone seems to clear it 1656 _req.unlock_code=1 1657 elif _resp.unlock_code==2: 1658 _req.unlock_code=3 1659 _req.convert_to_key2(_shift) 1660 else: 1661 raise EnterDMError('Unknown unlock_code: %d'%_resp.unlock_code) 1662 _resp=self.sendbrewcommand(_req, self.protocolclass.DMEnterResp) 1663 if _resp.result == 1: 1664 if self._shift is None: 1665 self._shift=_shift 1666 if __debug__: 1667 self.log('Shift key: %d'%self._shift) 1668 return 1669 raise EnterDMError('Failed to guess the shift/key')
1670
1671 - def enter_DM(self, e=None):
1672 # do nothing if the phone failed to previously enter DM 1673 if self._in_DM is False: 1674 return 1675 # check for DMv5 applicability 1676 if self._DMv5 is None: 1677 self.setDMversion() 1678 try: 1679 if self._DMv6: 1680 # new DM scheme 1681 self._enter_DMv6() 1682 elif self._DMv5: 1683 # enter DMv5 1684 self._enter_DMv5() 1685 else: 1686 # enter DMv4 1687 self._enter_DMv4() 1688 self._in_DM=True 1689 except: 1690 self.log('Failed to transition to DM') 1691 self._in_DM=False 1692 return
1693
1694 - def _OnTimer(self):
1695 if self._in_DM: 1696 self.log('Transition out of DM assumed.') 1697 self._in_DM=None 1698 del self._timer 1699 self._timer=None
1700
1701 - def _filefunc(self, func, *args, **kwargs):
1702 self.enter_DM() 1703 return func(*args, **kwargs)
1704
1705 - def _sendbrewcommand(self, func, *args, **kwargs):
1706 # A wrapper function to address the issue of DM timing out in a 1707 # middle of a file read or write (usually of a large file). 1708 # Not quite happy with this hack, but couldn't figure out a better way! 1709 # If you have a better solution, feel free to share. 1710 try: 1711 return func(*args, **kwargs) 1712 except com_brew.BrewAccessDeniedException: 1713 # DM may have timed out, retry. 1714 pass 1715 self.enter_DM() 1716 return func(*args, **kwargs)
1717
1718 - def setDMversion(self):
1719 """Define the DM version required for this phone, default to DMv5""" 1720 self._DMv5=True 1721 self._DMv6=False
1722
1723 - def __init__(self):
1724 self._in_DM=None 1725 self._timer=None 1726 self._DMv5=None 1727 self._DMv6=None 1728 self._timeout=None 1729 self._shift=None 1730 # wrap our functions 1731 self.getfilecontents=functools.partial(self._filefunc, 1732 self.getfilecontents) 1733 self.writefile=functools.partial(self._filefunc, 1734 self.writefile) 1735 self.statfile=functools.partial(self._filefunc, 1736 self.statfile) 1737 self.sendbrewcommand=functools.partial(self._sendbrewcommand, 1738 self.sendbrewcommand)
1739
1740 - def __del__(self):
1741 if self._timer: 1742 self._timer.cancel() 1743 del self._timer
1744
1745 -def basename(name):
1746 if name.rfind('/')>=0: 1747 pos=name.rfind('/') 1748 name=name[pos+1:] 1749 return name
1750
1751 -def dirname(name):
1752 if name.rfind("/")<1: 1753 return "" 1754 return name[:name.rfind("/")]
1755
1756 -def stripext(name):
1757 if name.rfind('.')>=0: 1758 name=name[:name.rfind('.')] 1759 return name
1760
1761 -def getext(name):
1762 name=basename(name) 1763 if name.rfind('.')>=0: 1764 return name[name.rfind('.')+1:] 1765 return ''
1766