0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2003-2006 Roger Binns <rogerb@rogerbinns.com> 0004 ### Copyright (C) 2006 Michael Cohen <mikepublic@nc.rr.com> 0005 ### 0006 ### This program is free software; you can redistribute it and/or modify 0007 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0008 ### 0009 ### $Id: com_lg.py 4693 2008-08-15 22:30:51Z djpham $ 0010 0011 """Phonebook conversations with LG phones""" 0012 0013 import functools 0014 import threading 0015 0016 import com_brew 0017 import com_phone 0018 import p_lg 0019 import prototypes 0020 import common 0021 import struct 0022 0023 class LGPhonebook: 0024 0025 pbterminator="\x7e" 0026 MODEPHONEBOOK="modephonebook" # can speak the phonebook protocol 0027 0028 def __init__(self): 0029 self.pbseq=0 0030 0031 def _setmodelgdmgo(self): 0032 # see if we can turn on dm mode 0033 for baud in (0, 115200, 19200, 38400, 230400): 0034 if baud: 0035 if not self.comm.setbaudrate(baud): 0036 continue 0037 try: 0038 self.comm.write("AT$LGDMGO\r\n") 0039 except: 0040 self.mode=self.MODENONE 0041 self.comm.shouldloop=True 0042 raise 0043 try: 0044 if self.comm.readsome().find("OK")>=0: 0045 return True 0046 except com_phone.modeignoreerrortypes: 0047 self.log("No response to setting DM mode") 0048 return False 0049 0050 0051 def _setmodephonebook(self): 0052 req=p_lg.pbinitrequest() 0053 respc=p_lg.pbinitresponse 0054 0055 for baud in 0,38400,115200,230400,19200: 0056 if baud: 0057 if not self.comm.setbaudrate(baud): 0058 continue 0059 try: 0060 self.sendpbcommand(req, respc, callsetmode=False) 0061 return True 0062 except com_phone.modeignoreerrortypes: 0063 pass 0064 0065 self._setmodelgdmgo() 0066 0067 for baud in 0,38400,115200: 0068 if baud: 0069 if not self.comm.setbaudrate(baud): 0070 continue 0071 try: 0072 self.sendpbcommand(req, respc, callsetmode=False) 0073 return True 0074 except com_phone.modeignoreerrortypes: 0075 pass 0076 return False 0077 0078 def sendpbcommand(self, request, responseclass, callsetmode=True): 0079 if callsetmode: 0080 self.setmode(self.MODEPHONEBOOK) 0081 buffer=prototypes.buffer() 0082 request.header.sequence=self.pbseq 0083 self.pbseq+=1 0084 if self.pbseq>0xff: 0085 self.pbseq=0 0086 request.writetobuffer(buffer, logtitle="lg phonebook request") 0087 data=buffer.getvalue() 0088 data=common.pppescape(data+common.crcs(data))+common.pppterminator 0089 firsttwo=data[:2] 0090 try: 0091 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 0092 except com_phone.modeignoreerrortypes: 0093 self.mode=self.MODENONE 0094 self.raisecommsdnaexception("manipulating the phonebook") 0095 self.comm.success=True 0096 0097 origdata=data 0098 # sometimes there is junk at the begining, eg if the user 0099 # turned off the phone and back on again. So if there is more 0100 # than one 7e in the escaped data we should start after the 0101 # second to last one 0102 d=data.rfind(common.pppterminator,0,-1) 0103 if d>=0: 0104 self.log("Multiple LG packets in data - taking last one starting at "+`d+1`) 0105 self.logdata("Original LG data", origdata, None) 0106 data=data[d+1:] 0107 0108 # turn it back to normal 0109 data=common.pppunescape(data) 0110 0111 # take off crc and terminator 0112 crc=data[-3:-1] 0113 data=data[:-3] 0114 # check the CRC at this point to see if we might have crap at the beginning 0115 calccrc=common.crcs(data) 0116 if calccrc!=crc: 0117 # sometimes there is other crap at the begining 0118 d=data.find(firsttwo) 0119 if d>0: 0120 self.log("Junk at begining of LG packet, data at "+`d`) 0121 self.logdata("Original LG data", origdata, None) 0122 self.logdata("Working on LG data", data, None) 0123 data=data[d:] 0124 # recalculate CRC without the crap 0125 calccrc=common.crcs(data) 0126 # see if the crc matches now 0127 if calccrc!=crc: 0128 self.logdata("Original LG data", origdata, None) 0129 self.logdata("Working on LG data", data, None) 0130 raise common.CommsDataCorruption("LG packet failed CRC check", self.desc) 0131 0132 # phone will respond with 0x13 and 0x14 if we send a bad or malformed command 0133 if ord(data[0])==0x13: 0134 raise com_brew.BrewBadBrewCommandException() 0135 if ord(data[0])==0x14: 0136 raise com_brew.BrewMalformedBrewCommandException() 0137 0138 # parse data 0139 buffer=prototypes.buffer(data) 0140 res=responseclass() 0141 res.readfrombuffer(buffer, logtitle="lg phonebook response") 0142 return res 0143 0144 def cleanupstring(str): 0145 str=str.replace("\r", "\n") 0146 str=str.replace("\n\n", "\n") 0147 str=str.strip() 0148 return str.split("\n") 0149 0150 class LGIndexedMedia: 0151 "Implements media for LG phones that use index files" 0152 0153 def __init__(self): 0154 pass 0155 0156 def getwallpapers(self, result): 0157 return self.getmedia(self.imagelocations, result, 'wallpapers') 0158 0159 def getringtones(self, result): 0160 return self.getmedia(self.ringtonelocations, result, 'ringtone') 0161 0162 def savewallpapers(self, results, merge): 0163 return self.savemedia('wallpapers', 'wallpaper-index', self.imagelocations, results, merge, self.getwallpaperindices) 0164 0165 def saveringtones(self, results, merge): 0166 return self.savemedia('ringtone', 'ringtone-index', self.ringtonelocations, results, merge, self.getringtoneindices) 0167 0168 def getmediaindex(self, builtins, maps, results, key): 0169 """Gets the media (wallpaper/ringtone) index 0170 0171 @param builtins: the builtin list on the phone 0172 @param results: places results in this dict 0173 @param maps: the list of index files and locations 0174 @param key: key to place results in 0175 """ 0176 0177 self.log("Reading "+key) 0178 media={} 0179 0180 # builtins 0181 c=1 0182 for name in builtins: 0183 media[c]={'name': name, 'origin': 'builtin' } 0184 c+=1 0185 0186 # the maps 0187 type=None 0188 for offset,indexfile,location,type,maxentries in maps: 0189 if type=="camera": break 0190 index=self.getindex(indexfile) 0191 for i in index: 0192 media[i+offset]={'name': index[i], 'origin': type} 0193 0194 # camera must be last 0195 if type=="camera": 0196 index=self.getcameraindex() 0197 for i in index: 0198 media[i+offset]=index[i] 0199 0200 results[key]=media 0201 return media 0202 0203 def getindex(self, indexfile): 0204 "Read an index file" 0205 index={} 0206 try: 0207 buf=prototypes.buffer(self.getfilecontents(indexfile)) 0208 except com_brew.BrewNoSuchFileException: 0209 # file may not exist 0210 return index 0211 g=self.protocolclass.indexfile() 0212 g.readfrombuffer(buf, logtitle="Index file %s read" % (indexfile,)) 0213 for i in g.items: 0214 if i.index!=0xffff and len(i.name): 0215 index[i.index]=i.name 0216 return index 0217 0218 def getmedia(self, maps, result, key): 0219 """Returns the contents of media as a dict where the key is a name as returned 0220 by getindex, and the value is the contents of the media""" 0221 media={} 0222 # the maps 0223 type=None 0224 for offset,indexfile,location,type,maxentries in maps: 0225 if type=="camera": break 0226 index=self.getindex(indexfile) 0227 for i in index: 0228 try: 0229 media[index[i]]=self.getfilecontents(location+"/"+index[i], True) 0230 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 0231 self.log("It was in the index, but not on the filesystem") 0232 0233 if type=="camera": 0234 # now for the camera stuff 0235 index=self.getcameraindex() 0236 for i in index: 0237 try: 0238 media[index[i]['name']]=self.getfilecontents("cam/pic%02d.jpg" % (i,), True) 0239 except com_brew.BrewNoSuchFileException: 0240 self.log("It was in the index, but not on the filesystem") 0241 0242 result[key]=media 0243 return result 0244 0245 def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction): 0246 """Actually saves out the media 0247 0248 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 0249 @param mediaindexkey: index key (eg 'wallpaper-index') 0250 @param maps: list index files and locations 0251 @param results: results dict 0252 @param merge: are we merging or overwriting what is there? 0253 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 0254 """ 0255 print results.keys() 0256 # I humbly submit this as the longest function in the bitpim code ... 0257 # wp and wpi are used as variable names as this function was originally 0258 # written to do wallpaper. it works just fine for ringtones as well 0259 wp=results[mediakey].copy() 0260 wpi=results[mediaindexkey].copy() 0261 # remove builtins 0262 for k in wpi.keys(): 0263 if wpi[k]['origin']=='builtin': 0264 del wpi[k] 0265 0266 # sort results['mediakey'+'-index'] into origin buckets 0267 0268 # build up list into init 0269 init={} 0270 for offset,indexfile,location,type,maxentries in maps: 0271 init[type]={} 0272 for k in wpi.keys(): 0273 if wpi[k]['origin']==type: 0274 index=k-offset 0275 name=wpi[k]['name'] 0276 data=None 0277 del wpi[k] 0278 for w in wp.keys(): 0279 if wp[w]['name']==name and wp[w]['origin']==type: 0280 data=wp[w]['data'] 0281 del wp[w] 0282 if not merge and data is None: 0283 # delete the entry 0284 continue 0285 init[type][index]={'name': name, 'data': data} 0286 0287 # init now contains everything from wallpaper-index 0288 print init.keys() 0289 # now look through wallpapers and see if anything remaining was assigned a particular 0290 # origin 0291 for w in wp.keys(): 0292 o=wp[w].get("origin", "") 0293 if o is not None and len(o) and o in init: 0294 idx=-1 0295 while idx in init[o]: 0296 idx-=1 0297 init[o][idx]=wp[w] 0298 del wp[w] 0299 0300 # we now have init[type] with the entries and index number as key (negative indices are 0301 # unallocated). Proceed to deal with each one, taking in stuff from wp as we have space 0302 for offset,indexfile,location,type,maxentries in maps: 0303 if type=="camera": break 0304 index=init[type] 0305 try: 0306 dirlisting=self.getfilesystem(location) 0307 except com_brew.BrewNoSuchDirectoryException: 0308 self.mkdirs(location) 0309 dirlisting={} 0310 # rename keys to basename 0311 for i in dirlisting.keys(): 0312 dirlisting[i[len(location)+1:]]=dirlisting[i] 0313 del dirlisting[i] 0314 # what we will be deleting 0315 dellist=[] 0316 if not merge: 0317 # get existing wpi for this location 0318 wpi=results[mediaindexkey] 0319 for i in wpi: 0320 entry=wpi[i] 0321 if entry['origin']==type: 0322 # it is in the original index, are we writing it back out? 0323 delit=True 0324 for idx in index: 0325 if index[idx]['name']==entry['name']: 0326 delit=False 0327 break 0328 if delit: 0329 if entry['name'] in dirlisting: 0330 dellist.append(entry['name']) 0331 else: 0332 self.log("%s in %s index but not filesystem" % (entry['name'], type)) 0333 # go ahead and delete unwanted files 0334 print "deleting",dellist 0335 for f in dellist: 0336 self.rmfile(location+"/"+f) 0337 # slurp up any from wp we can take 0338 while len(index)<maxentries and len(wp): 0339 idx=-1 0340 while idx in index: 0341 idx-=1 0342 k=wp.keys()[0] 0343 index[idx]=wp[k] 0344 del wp[k] 0345 # normalise indices 0346 index=self._normaliseindices(index) # hey look, I called a function! 0347 # move any overflow back into wp 0348 if len(index)>maxentries: 0349 keys=index.keys() 0350 keys.sort() 0351 for k in keys[maxentries:]: 0352 idx=-1 0353 while idx in wp: 0354 idx-=1 0355 wp[idx]=index[k] 0356 del index[k] 0357 # write out the new index 0358 keys=index.keys() 0359 keys.sort() 0360 ifile=self.protocolclass.indexfile() 0361 ifile.numactiveitems=len(keys) 0362 for k in keys: 0363 entry=self.protocolclass.indexentry() 0364 entry.index=k 0365 entry.name=index[k]['name'] 0366 ifile.items.append(entry) 0367 while len(ifile.items)<maxentries: 0368 ifile.items.append(self.protocolclass.indexentry()) 0369 buffer=prototypes.buffer() 0370 ifile.writetobuffer(buffer, logtitle="Updated index file "+indexfile) 0371 self.writefile(indexfile, buffer.getvalue()) 0372 # Write out files - we compare against existing dir listing and don't rewrite if they 0373 # are the same size 0374 for k in keys: 0375 entry=index[k] 0376 data=entry.get("data", None) 0377 if data is None: 0378 if entry['name'] not in dirlisting: 0379 self.log("Index error. I have no data for "+entry['name']+" and it isn't already in the filesystem") 0380 continue 0381 if entry['name'] in dirlisting and len(data)==dirlisting[entry['name']]['size']: 0382 self.log("Skipping writing %s/%s as there is already a file of the same length" % (location,entry['name'])) 0383 continue 0384 self.writefile(location+"/"+entry['name'], data) 0385 # did we have too many 0386 if len(wp): 0387 for k in wp: 0388 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],)) 0389 0390 # Note that we don't write to the camera area 0391 0392 # tidy up - reread indices 0393 del results[mediakey] # done with it 0394 reindexfunction(results) 0395 return results 0396 0397 class LGNewIndexedMedia: 0398 "Implements media for LG phones that use the new index format such as the VX7000/8000" 0399 0400 # tables for minor type number 0401 _minor_typemap={ 0402 0: { # images 0403 ".jpg": 3, 0404 ".bmp": 1, 0405 }, 0406 0407 1: { # audio 0408 ".qcp": 5, 0409 ".mid": 4, 0410 }, 0411 0412 2: { # video 0413 ".3g2": 3, 0414 } 0415 } 0416 0417 0418 def __init__(self): 0419 pass 0420 0421 def getmediaindex(self, builtins, maps, results, key): 0422 """Gets the media (wallpaper/ringtone) index 0423 0424 @param builtins: the builtin list on the phone 0425 @param results: places results in this dict 0426 @param maps: the list of index files and locations 0427 @param key: key to place results in 0428 """ 0429 0430 self.log("Reading "+key) 0431 media={} 0432 0433 # builtins 0434 for i,n in enumerate(builtins): # nb zero based index whereas previous phones used 1 0435 media[i]={'name': n, 'origin': 'builtin'} 0436 0437 # maps 0438 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor in maps: 0439 for item in self.getindex(indexfile): 0440 if item.type&0xff!=typemajor: 0441 self.log("Entry "+item.filename+" has wrong type for this index. It is %d and should be %d" % (item.type&0xff, typemajor)) 0442 self.log("This is going to cause you all sorts of problems.") 0443 media[item.index]={ 0444 'name': basename(item.filename), 0445 'filename': item.filename, 0446 'origin': type, 0447 'vtype': item.type, 0448 } 0449 if item.date!=0: 0450 media[item.index]['date']=item.date 0451 0452 # finish 0453 results[key]=media 0454 0455 def getindex(self, filename): 0456 "read an index file" 0457 try: 0458 buf=prototypes.buffer(self.getfilecontents(filename)) 0459 except com_brew.BrewNoSuchFileException: 0460 return [] 0461 0462 g=self.protocolclass.indexfile() 0463 # some media indexes have crap appended to the end, prevent this error from messing up everything 0464 # valid entries at the start of the file will still get read OK. 0465 try: 0466 g.readfrombuffer(buf, logtitle="Index file "+filename) 0467 except: 0468 self.log("Corrupt index file "+`filename`+", this might cause you all sorts of problems.") 0469 return g.items 0470 0471 def getmedia(self, maps, results, key): 0472 origins={} 0473 # signal that we are using the new media storage that includes the origin and timestamp 0474 origins['new_media_version']=1 0475 0476 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor in maps: 0477 media={} 0478 for item in self.getindex(indexfile): 0479 data=None 0480 timestamp=None 0481 try: 0482 stat_res=self.statfile(item.filename) 0483 if stat_res!=None: 0484 timestamp=stat_res['date'][0] 0485 data=self.getfilecontents(item.filename, True) 0486 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 0487 self.log("It was in the index, but not on the filesystem") 0488 if data!=None: 0489 media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp} 0490 origins[type]=media 0491 0492 results[key]=origins 0493 return results 0494 0495 def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction): 0496 """Actually saves out the media 0497 0498 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 0499 @param mediaindexkey: index key (eg 'wallpaper-index') 0500 @param maps: list index files and locations 0501 @param results: results dict 0502 @param merge: are we merging or overwriting what is there? 0503 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 0504 """ 0505 0506 # take copies of the lists as we modify them 0507 wp=results[mediakey].copy() # the media we want to save 0508 wpi=results[mediaindexkey].copy() # what is already in the index files 0509 0510 # remove builtins 0511 for k in wpi.keys(): 0512 if wpi[k].get('origin', "")=='builtin': 0513 del wpi[k] 0514 0515 # build up list into init 0516 init={} 0517 for type,_,_,_,lowestindex,_,typemajor in maps: 0518 init[type]={} 0519 for k in wpi.keys(): 0520 if wpi[k]['origin']==type: 0521 index=k 0522 name=wpi[k]['name'] 0523 fullname=wpi[k]['filename'] 0524 vtype=wpi[k]['vtype'] 0525 data=None 0526 del wpi[k] 0527 for w in wp.keys(): 0528 # does wp contain a reference to this same item? 0529 if wp[w]['name']==name and wp[w]['origin']==type: 0530 data=wp[w]['data'] 0531 del wp[w] 0532 if not merge and data is None: 0533 # delete the entry 0534 continue 0535 assert index>=lowestindex 0536 init[type][index]={'name': name, 'data': data, 'filename': fullname, 'vtype': vtype} 0537 0538 # init now contains everything from wallpaper-index 0539 # wp contains items that we still need to add, and weren't in the existing index 0540 assert len(wpi)==0 0541 print init.keys() 0542 0543 # now look through wallpapers and see if anything was assigned a particular 0544 # origin 0545 for w in wp.keys(): 0546 o=wp[w].get("origin", "") 0547 if o is not None and len(o) and o in init: 0548 idx=-1 0549 while idx in init[o]: 0550 idx-=1 0551 init[o][idx]=wp[w] 0552 del wp[w] 0553 0554 # wp will now consist of items that weren't assigned any particular place 0555 # so put them in the first available space 0556 for type,_,_,_,lowestindex,maxentries,typemajor in maps: 0557 # fill it up 0558 for w in wp.keys(): 0559 if len(init[type])>=maxentries: 0560 break 0561 idx=-1 0562 while idx in init[type]: 0563 idx-=1 0564 init[type][idx]=wp[w] 0565 del wp[w] 0566 0567 # time to write the files out 0568 dircache=self.DirCache(self) 0569 for type, indexfile, sizefile, directory, lowestindex, maxentries,typemajor in maps: 0570 # get the index file so we can work out what to delete 0571 names=[init[type][x]['name'] for x in init[type]] 0572 for item in self.getindex(indexfile): 0573 if basename(item.filename) not in names: 0574 self.log(item.filename+" is being deleted") 0575 try: 0576 dircache.rmfile(item.filename) 0577 except com_brew.BrewNoSuchFileException: 0578 self.log("Hmm, it didn't exist!") 0579 # fixup the indices 0580 fixups=[k for k in init[type].keys() if k<lowestindex] 0581 fixups.sort() 0582 for f in fixups: 0583 for ii in xrange(lowestindex, lowestindex+maxentries): 0584 # allocate an index 0585 if ii not in init[type]: 0586 init[type][ii]=init[type][f] 0587 del init[type][f] 0588 break 0589 # any left over? 0590 fixups=[k for k in init[type].keys() if k<lowestindex] 0591 for f in fixups: 0592 self.log("There is no space in the index for "+type+" for "+init[type][f]['name']) 0593 del init[type][f] 0594 # write each entry out 0595 for idx in init[type].keys(): 0596 entry=init[type][idx] 0597 filename=entry.get('filename', directory+"/"+entry['name']) 0598 entry['filename']=filename 0599 fstat=dircache.stat(filename) 0600 if 'data' not in entry: 0601 # must be in the filesystem already 0602 if fstat is None: 0603 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.") 0604 del init[type][idx] 0605 continue 0606 # check len(data) against fstat->length 0607 data=entry['data'] 0608 if data is None: 0609 assert merge 0610 continue # we are doing an add and don't have data for this existing entry 0611 if fstat is not None and len(data)==fstat['size']: 0612 self.log("Not writing "+filename+" as a file of the same name and length already exists.") 0613 else: 0614 dircache.writefile(filename, data) 0615 # write out index 0616 ifile=self.protocolclass.indexfile() 0617 idxlist=init[type].keys() 0618 idxlist.sort() 0619 idxlist.reverse() # the phone has them in reverse order for some reason so we do the same 0620 for idx in idxlist: 0621 ie=self.protocolclass.indexentry() 0622 ie.index=idx 0623 vtype=init[type][idx].get("vtype", None) 0624 if vtype is None: 0625 vtype=self._guessvtype(init[type][idx]['filename'], typemajor) 0626 print "guessed vtype of "+`vtype`+" for "+init[type][idx]['filename'] 0627 else: 0628 print init[type][idx]['filename']+" already had a vtype of "+`vtype` 0629 ie.type=vtype 0630 ie.filename=init[type][idx]['filename'] 0631 # ie.date left as zero 0632 ie.dunno=0 # mmmm 0633 ifile.items.append(ie) 0634 buf=prototypes.buffer() 0635 ifile.writetobuffer(buf, logtitle="Index file "+indexfile) 0636 self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(idxlist)`+" entries.") 0637 dircache.writefile(indexfile, buf.getvalue()) # doesn't really need to go via dircache 0638 # write out size file 0639 size=0 0640 for idx in idxlist: 0641 fstat=dircache.stat(init[type][idx]['filename']) 0642 size+=fstat['size'] 0643 szfile=self.protocolclass.sizefile() 0644 szfile.size=size 0645 buf=prototypes.buffer() 0646 szfile.writetobuffer(buf, logtitle="size file for "+type) 0647 self.log("You are using a total of "+`size`+" bytes for "+type) 0648 dircache.writefile(sizefile, buf.getvalue()) 0649 return reindexfunction(results) 0650 0651 def _guessvtype(self, filename, typemajor): 0652 lookin=self._minor_typemap[typemajor] 0653 for ext,val in lookin.items(): 0654 if filename.lower().endswith(ext): 0655 return typemajor+(256*val) 0656 return typemajor # implicit val of zero 0657 0658 def getwallpaperindices(self, results): 0659 return self.getmediaindex(self.builtinwallpapers, self.wallpaperlocations, results, 'wallpaper-index') 0660 0661 def getringtoneindices(self, results): 0662 return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index') 0663 0664 def getwallpapers(self, result): 0665 return self.getmedia(self.wallpaperlocations, result, 'wallpapers') 0666 0667 def getringtones(self, result): 0668 return self.getmedia(self.ringtonelocations, result, 'ringtone') 0669 0670 def savewallpapers(self, results, merge): 0671 return self.savemedia('wallpapers', 'wallpaper-index', self.wallpaperlocations, results, merge, self.getwallpaperindices) 0672 0673 def saveringtones(self, results, merge): 0674 return self.savemedia('ringtone', 'ringtone-index', self.ringtonelocations, results, merge, self.getringtoneindices) 0675 0676 class LGNewIndexedMedia2(LGNewIndexedMedia): 0677 """Implements media for LG phones that use the newer index format such as the VX8100/5200 0678 Similar ot the other new media type hence the subclassing, but the type field in the 0679 PACKET has a different meaning so different code is required and it has support for an icon 0680 field that affects whcih icon is displayed next to the tone on the phone 0681 """ 0682 0683 # tables for minor type number 0684 _minor_typemap={ 0685 ".jpg": 3, 0686 ".bmp": 1, 0687 ".qcp": 5, 0688 ".mid": 4, 0689 ".3g2": 3, 0690 } 0691 0692 def _guessvtype(self, filename, typemajor): 0693 if typemajor > 2: # some index files are hard coded 0694 return typemajor 0695 for ext,val in self._minor_typemap.items(): 0696 if filename.lower().endswith(ext): 0697 return typemajor+(256*val) 0698 return typemajor # implicit val of zero 0699 0700 def getmediaindex(self, builtins, maps, results, key): 0701 """Gets the media (wallpaper/ringtone) index 0702 0703 @param builtins: the builtin list on the phone 0704 @param results: places results in this dict 0705 @param maps: the list of index files and locations 0706 @param key: key to place results in 0707 """ 0708 0709 self.log("Reading "+key) 0710 media={} 0711 0712 # builtins 0713 for i,n in enumerate(builtins): # nb zero based index whereas previous phones used 1 0714 media[i]={'name': n, 'origin': 'builtin'} 0715 0716 # maps 0717 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 0718 for item in self.getindex(indexfile): 0719 if item.type&0xff!=typemajor&0xff: 0720 self.log("Entry "+item.filename+" has wrong type for this index. It is %d and should be %d" % (item.type&0xff, typemajor)) 0721 self.log("This is going to cause you all sorts of problems.") 0722 _idx=item.index+idx_ofs 0723 media[_idx]={ 0724 'name': basename(item.filename), 0725 'filename': item.filename, 0726 'origin': type, 0727 'vtype': item.type, 0728 'icon': item.icon 0729 } 0730 if item.date!=0: 0731 media[_idx]['date']=item.date 0732 0733 # finish 0734 results[key]=media 0735 0736 def getmedia(self, maps, results, key): 0737 origins={} 0738 # signal that we are using the new media storage that includes the origin and timestamp 0739 origins['new_media_version']=1 0740 0741 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 0742 media={} 0743 for item in self.getindex(indexfile): 0744 data=None 0745 timestamp=None 0746 try: 0747 stat_res=self.statfile(item.filename) 0748 if stat_res!=None: 0749 timestamp=stat_res['date'][0] 0750 data=self.getfilecontents(item.filename, True) 0751 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 0752 self.log("It was in the index, but not on the filesystem") 0753 except com_brew.BrewAccessDeniedException: 0754 # firmware wouldn't let us read this file, just mark it then 0755 self.log('Failed to read file: '+item.filename) 0756 data='' 0757 if data!=None: 0758 media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp} 0759 origins[type]=media 0760 0761 results[key]=origins 0762 return results 0763 0764 def getmedia(self, maps, results, key): 0765 media={} 0766 0767 for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs in maps: 0768 for item in self.getindex(indexfile): 0769 try: 0770 media[basename(item.filename)]=self.getfilecontents(item.filename, 0771 True) 0772 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException): 0773 self.log("It was in the index, but not on the filesystem") 0774 0775 results[key]=media 0776 return results 0777 0778 def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction): 0779 """Actually saves out the media 0780 0781 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 0782 @param mediaindexkey: index key (eg 'wallpaper-index') 0783 @param maps: list index files and locations 0784 @param results: results dict 0785 @param merge: are we merging or overwriting what is there? 0786 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 0787 """ 0788 0789 # take copies of the lists as we modify them 0790 wp=results[mediakey].copy() # the media we want to save 0791 wpi=results[mediaindexkey].copy() # what is already in the index files 0792 0793 # remove builtins 0794 for k in wpi.keys(): 0795 if wpi[k].get('origin', "")=='builtin': 0796 del wpi[k] 0797 0798 # build up list into init 0799 init={} 0800 for type,_,_,_,lowestindex,_,typemajor,_,idx_ofs in maps: 0801 init[type]={} 0802 for k in wpi.keys(): 0803 if wpi[k]['origin']==type: 0804 index=k-idx_ofs 0805 name=wpi[k]['name'] 0806 fullname=wpi[k]['filename'] 0807 vtype=wpi[k]['vtype'] 0808 icon=wpi[k]['icon'] 0809 data=None 0810 del wpi[k] 0811 for w in wp.keys(): 0812 # does wp contain a reference to this same item? 0813 if wp[w]['name']==name and wp[w]['origin']==type: 0814 data=wp[w]['data'] 0815 del wp[w] 0816 if not merge and data is None: 0817 # delete the entry 0818 continue 0819 assert index>=lowestindex 0820 init[type][index]={'name': name, 'data': data, 'filename': fullname, 'vtype': vtype, 'icon': icon} 0821 0822 # init now contains everything from wallpaper-index 0823 # wp contains items that we still need to add, and weren't in the existing index 0824 assert len(wpi)==0 0825 print init.keys() 0826 0827 # now look through wallpapers and see if anything was assigned a particular 0828 # origin 0829 for w in wp.keys(): 0830 o=wp[w].get("origin", "") 0831 if o is not None and len(o) and o in init: 0832 idx=-1 0833 while idx in init[o]: 0834 idx-=1 0835 init[o][idx]=wp[w] 0836 del wp[w] 0837 0838 # wp will now consist of items that weren't assigned any particular place 0839 # so put them in the first available space 0840 for type,_,_,_,lowestindex,maxentries,typemajor,def_icon,_ in maps: 0841 # fill it up 0842 for w in wp.keys(): 0843 if len(init[type])>=maxentries: 0844 break 0845 idx=-1 0846 while idx in init[type]: 0847 idx-=1 0848 init[type][idx]=wp[w] 0849 del wp[w] 0850 0851 # time to write the files out 0852 dircache=self.DirCache(self) 0853 for type, indexfile, sizefile, directory, lowestindex, maxentries,typemajor,def_icon,_ in maps: 0854 # get the index file so we can work out what to delete 0855 names=[init[type][x]['name'] for x in init[type]] 0856 for item in self.getindex(indexfile): 0857 if basename(item.filename) not in names: 0858 self.log(item.filename+" is being deleted") 0859 try: 0860 dircache.rmfile(item.filename) 0861 except com_brew.BrewNoSuchFileException: 0862 self.log("Hmm, it didn't exist!") 0863 # fixup the indices 0864 fixups=[k for k in init[type].keys() if k<lowestindex] 0865 fixups.sort() 0866 for f in fixups: 0867 for ii in xrange(lowestindex, lowestindex+maxentries): 0868 # allocate an index 0869 if ii not in init[type]: 0870 init[type][ii]=init[type][f] 0871 del init[type][f] 0872 break 0873 # any left over? 0874 fixups=[k for k in init[type].keys() if k<lowestindex] 0875 for f in fixups: 0876 self.log("There is no space in the index for "+type+" for "+init[type][f]['name']) 0877 del init[type][f] 0878 # write each entry out 0879 for idx in init[type].keys(): 0880 entry=init[type][idx] 0881 filename=entry.get('filename', directory+"/"+entry['name']) 0882 entry['filename']=filename 0883 fstat=dircache.stat(filename) 0884 if 'data' not in entry: 0885 # must be in the filesystem already 0886 if fstat is None: 0887 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.") 0888 del init[type][idx] 0889 continue 0890 # check len(data) against fstat->length 0891 data=entry['data'] 0892 if data is None: 0893 assert merge 0894 continue # we are doing an add and don't have data for this existing entry 0895 if fstat is not None and len(data)==fstat['size']: 0896 self.log("Not writing "+filename+" as a file of the same name and length already exists.") 0897 else: 0898 dircache.writefile(filename, data) 0899 # write out index 0900 ifile=self.protocolclass.indexfile() 0901 idxlist=init[type].keys() 0902 idxlist.sort() 0903 idxlist.reverse() # the phone has them in reverse order for some reason so we do the same 0904 for idx in idxlist: 0905 ie=self.protocolclass.indexentry() 0906 ie.index=idx 0907 vtype=init[type][idx].get("vtype", None) 0908 if vtype is None: 0909 vtype=self._guessvtype(init[type][idx]['filename'], typemajor) 0910 ie.type=vtype 0911 ie.filename=init[type][idx]['filename'] 0912 # ie.date left as zero 0913 ie.dunno=0 # mmmm 0914 icon=init[type][idx].get("icon", None) 0915 if icon is None: 0916 icon=def_icon 0917 ie.icon=icon 0918 ifile.items.append(ie) 0919 buf=prototypes.buffer() 0920 ifile.writetobuffer(buf, logtitle="Index file "+indexfile) 0921 self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(idxlist)`+" entries.") 0922 dircache.writefile(indexfile, buf.getvalue()) # doesn't really need to go via dircache 0923 # write out size file, if it is required 0924 if sizefile != '': 0925 size=0 0926 for idx in idxlist: 0927 fstat=dircache.stat(init[type][idx]['filename']) 0928 size+=fstat['size'] 0929 szfile=self.protocolclass.sizefile() 0930 szfile.size=size 0931 buf=prototypes.buffer() 0932 szfile.writetobuffer(buf, logtitle="Writing size file "+sizefile) 0933 self.log("You are using a total of "+`size`+" bytes for "+type) 0934 dircache.writefile(sizefile, buf.getvalue()) 0935 return reindexfunction(results) 0936 0937 0938 0939 class LGDirectoryMedia: 0940 """The media is stored one per directory with .desc and body files""" 0941 0942 def __init__(self): 0943 pass 0944 0945 def getmediaindex(self, builtins, maps, results, key): 0946 """Gets the media (wallpaper/ringtone) index 0947 0948 @param builtins: the builtin list on the phone 0949 @param results: places results in this dict 0950 @param maps: the list of index files and locations 0951 @param key: key to place results in 0952 """ 0953 self.log("Reading "+key) 0954 media={} 0955 0956 # builtins 0957 c=1 0958 for name in builtins: 0959 media[c]={'name': name, 'origin': 'builtin' } 0960 c+=1 0961 0962 # directory 0963 for offset,location,origin,maxentries in maps: 0964 index=self.getindex(location) 0965 for i in index: 0966 media[i+offset]={'name': index[i], 'origin': origin} 0967 0968 results[key]=media 0969 return media 0970 0971 __mimetoextensionmapping={ 0972 'image/jpg': '.jpg', 0973 'image/bmp': '.bmp', 0974 'image/png': '.png', 0975 'image/gif': '.gif', 0976 'image/bci': '.bci', 0977 'audio/mp3': '.mp3', 0978 'audio/mid': '.mid', 0979 'audio/qcp': '.qcp' 0980 } 0981 0982 def _createnamewithmimetype(self, name, mt): 0983 name=basename(name) 0984 if mt=="image/jpeg": 0985 mt="image/jpg" 0986 try: 0987 return name+self.__mimetoextensionmapping[mt] 0988 except KeyError: 0989 self.log("Unable to figure out extension for mime type "+mt) 0990 return name 0991 0992 def _getmimetype(self, name): 0993 ext=getext(name.lower()) 0994 if len(ext): ext="."+ext 0995 if ext==".jpeg": 0996 return "image/jpg" # special case 0997 for mt,extension in self.__mimetoextensionmapping.items(): 0998 if ext==extension: 0999 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
Generated by PyXR 0.9.4