PyXR

c:\projects\bitpim\src \ phones \ com_lg.py



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