PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2005      Bruce Schurmann <austinbp@schurmann.org>
0004 ### Copyright (C) 2003-2005 Roger Binns <rogerb@rogerbinns.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_lgvx3200.py 3918 2007-01-19 05:15:12Z djpham $
0010 
0011 """Communicate with the LG VX3200 cell phone
0012 
0013 The VX3200 is somewhat similar to the VX4400
0014 
0015 """
0016 
0017 # standard modules
0018 import time
0019 import cStringIO
0020 import sha
0021 import re
0022 
0023 # my modules
0024 import common
0025 import copy
0026 import helpids
0027 import p_lgvx3200
0028 import com_lgvx4400
0029 import com_brew
0030 import com_phone
0031 import com_lg
0032 import prototypes
0033 import phone_media_codec
0034 import conversions
0035 
0036 media_codec=phone_media_codec.codec_name
0037 
0038 
0039 class Phone(com_lgvx4400.Phone):
0040     "Talk to the LG VX3200 cell phone"
0041 
0042     desc="LG-VX3200"
0043     helpid=helpids.ID_PHONE_LGVX3200
0044 
0045     wallpaperindexfilename="download/dloadindex/brewImageIndex.map"
0046     ringerindexfilename="download/dloadindex/brewRingerIndex.map"
0047 
0048     protocolclass=p_lgvx3200
0049     serialsname='lgvx3200'
0050 
0051     # more VX3200 indices
0052     imagelocations=(
0053         # offset, index file, files location, type, maximumentries
0054         ( 11, "download/dloadindex/brewImageIndex.map", "download", "images", 3) ,
0055         )
0056 
0057     ringtonelocations=(
0058         # offset, index file, files location, type, maximumentries
0059         ( 27, "download/dloadindex/brewRingerIndex.map", "user/sound/ringer", "ringers", 30),
0060         )
0061 
0062 
0063     builtinimages= ('Sport 1', 'Sport 2', 'Nature 1', 'Nature 2',
0064                     'Animal', 'Martini', 'Goldfish', 'Umbrellas',
0065                     'Mountain climb', 'Country road')
0066 
0067     # There are a few more builtins but they cannot be sent to the phone in phonebook
0068     # entries so I am leaving them out.
0069     builtinringtones= ('Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5', 'Ring 6',
0070                        'Ring 7', 'Ring 8', 'Annen Polka', 'Pachelbel Canon', 
0071                        'Hallelujah', 'La Traviata', 'Leichte Kavallerie Overture', 
0072                        'Mozart Symphony No.40', 'Bach Minuet', 'Farewell', 
0073                        'Mozart Piano Sonata', 'Sting', 'O solemio', 
0074                        'Pizzicata Polka', 'Stars and Stripes Forever', 
0075                        'Pineapple Rag', 'When the Saints Go Marching In', 'Latin', 
0076                        'Carol 1', 'Carol 2') 
0077                        
0078     def __init__(self, logtarget, commport):
0079         com_lgvx4400.Phone.__init__(self,logtarget,commport)
0080         self.mode=self.MODENONE
0081         self.mediacache=self.DirCache(self)
0082 
0083     def makeentry(self, counter, entry, dict):
0084         e=com_lgvx4400.Phone.makeentry(self, counter, entry, dict)
0085         e.entrysize=0x202
0086         return e
0087 
0088     def getindex(self, indexfile):
0089         "Read an index file"
0090         index={}
0091         # Hack for LG-VX3200 - wallpaper index file is not real
0092         if re.search("ImageIndex", indexfile) is not None:
0093             # A little special treatment for wallpaper related media files
0094             # Sneek a peek at the file because if it is zero len we do not index it
0095             ind=0
0096             for ifile in 'wallpaper', 'poweron', 'poweroff':
0097                 ifilefull="download/"+ifile+".bit"
0098                 try:
0099                     mediafiledata=self.mediacache.readfile(ifilefull)
0100                     if len(mediafiledata)!=0:
0101                         index[ind]=ifile
0102                         ind = ind + 1
0103                         self.log("Index file "+indexfile+" entry added: "+ifile)
0104                 except:
0105                     pass
0106         else:
0107             # A little special treatment for ringer related media files
0108             try:
0109                 buf=prototypes.buffer(self.getfilecontents(indexfile))
0110             except com_brew.BrewNoSuchFileException:
0111                 # file may not exist
0112                 return index
0113             g=self.protocolclass.indexfile()
0114             g.readfrombuffer(buf, logtitle="Read index file "+indexfile)
0115             for i in g.items:
0116                 if i.index!=0xffff:
0117                     ifile=re.sub("\.mid|\.MID", "", i.name)
0118                     self.log("Index file "+indexfile+" entry added: "+ifile)
0119                     index[i.index]=ifile
0120         return index
0121         
0122     def getmedia(self, maps, result, key):
0123         """Returns the contents of media as a dict where the key is a name as returned
0124         by getindex, and the value is the contents of the media"""
0125         media={}
0126         # the maps
0127         type=None
0128         for offset,indexfile,location,type,maxentries in maps:
0129             index=self.getindex(indexfile)
0130             for i in index:
0131                 if type=="images":
0132                     # Wallpaper media files are all BIT type
0133                     mediafilename=index[i]+".bit"
0134                 else:
0135                     # Ringer media files are all MID suffixed
0136                     mediafilename=index[i]+".mid"
0137                 try:
0138                     media[index[i]]=self.mediacache.readfile(location+"/"+mediafilename)
0139                 except com_brew.BrewNoSuchFileException:
0140                     self.log("Missing index file: "+location+"/"+mediafilename)
0141         result[key]=media
0142         return result
0143 
0144     def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
0145         """Actually saves out the media
0146 
0147         @param mediakey: key of the media (eg 'wallpapers' or 'ringtone')
0148         @param mediaindexkey:  index key (eg 'wallpaper-index')
0149         @param maps: list index files and locations
0150         @param results: results dict
0151         @param merge: are we merging or overwriting what is there?
0152         @param reindexfunction: the media is re-indexed at the end.  this function is called to do it
0153         """
0154         print results.keys()
0155         # I humbly submit this as the longest function in the bitpim code ...
0156         # wp and wpi are used as variable names as this function was originally
0157         # written to do wallpaper.  it works just fine for ringtones as well
0158         #
0159         # LG-VX3200 special notes:
0160         # The index entries for both the wallpaper and ringers are
0161         # hacked just a bit AND are hacked in slightly different
0162         # ways. In addition to that the media for the wallpapers is
0163         # currently in BMP format inside the program and must be
0164         # converted to BIT (LG proprietary) on the way out.
0165         #
0166         # In the following:
0167         #   wp has file references that nave no suffixes
0168         #   wpi has file references that are mixed, i.e. some have file suffixes (comes from UI that way)
0169         wp=results[mediakey].copy()
0170         wpi=results[mediaindexkey].copy()
0171         
0172         # Walk through wp and remove any suffixes that make their way in via the UI
0173         for k in wp.keys():
0174             wp[k]['name']=re.sub("\....$", "", wp[k]['name'])
0175             
0176         # remove builtins
0177         for k in wpi.keys():
0178             if wpi[k]['origin']=='builtin':
0179                 del wpi[k]
0180                 
0181         # build up list into init
0182         init={}
0183         for offset,indexfile,location,type,maxentries in maps:
0184             init[type]={}
0185             for k in wpi.keys():
0186                 if wpi[k]['origin']==type:
0187                     index=k-offset
0188                     name=wpi[k]['name']
0189                     data=None
0190                     del wpi[k]
0191                     for w in wp.keys():
0192                         if wp[w]['name']==name and wp[w]['origin']==type:
0193                             data=wp[w]['data']
0194                             del wp[w]
0195                     if not merge and data is None:
0196                         # delete the entry
0197                         continue
0198                     init[type][index]={'name': name, 'data': data}
0199 
0200         # init now contains everything from wallpaper-index
0201         print init.keys()
0202         # now look through wallpapers and see if anything remaining was assigned a particular
0203         # origin
0204         for w in wp.keys():
0205             o=wp[w].get("origin", "")
0206             if o is not None and len(o) and o in init:
0207                 idx=-1
0208                 while idx in init[o]:
0209                     idx-=1
0210                 init[o][idx]=wp[w]
0211                 del wp[w]
0212             
0213         # we now have init[type] with the entries and index number as key (negative indices are
0214         # unallocated).  Proceed to deal with each one, taking in stuff from wp as we have space
0215         for offset,indexfile,location,type,maxentries in maps:
0216             if type=="camera": break
0217             index=init[type]
0218             try:
0219                 dirlisting=self.getfilesystem(location)
0220             except com_brew.BrewNoSuchDirectoryException:
0221                 self.mkdirs(location)
0222                 dirlisting={}
0223             # rename keys to basename
0224             for i in dirlisting.keys():
0225                 dirlisting[i[len(location)+1:]]=dirlisting[i]
0226                 del dirlisting[i]
0227             # what we will be deleting
0228             dellist=[]
0229             if not merge:
0230                 # get existing wpi for this location
0231                 wpi=results[mediaindexkey]
0232                 for i in wpi:
0233                     entry=wpi[i]
0234                     if entry['origin']==type:
0235                         # it is in the original index, are we writing it back out?
0236                         delit=True
0237                         for idx in index:
0238                             if index[idx]['name']==entry['name']:
0239                                 delit=False
0240                                 break
0241                         if delit:
0242                             # Add the media file suffixes back to match real filenames on phone
0243                             if type=="ringers":
0244                                 entryname=entry['name']+".mid"
0245                             else:
0246                                 entryname=entry['name']+".bit"
0247                             if entryname in dirlisting:
0248                                 dellist.append(entryname)
0249                             else:
0250                                 self.log("%s in %s index but not filesystem" % (entryname, type))
0251             # go ahead and delete unwanted files
0252             print "deleting",dellist
0253             for f in dellist:
0254                 self.mediacache.rmfile(location+"/"+f)
0255             # LG-VX3200 special case:
0256             # We only keep (upload) wallpaper images if there was a legit
0257             # one on the phone to begin with. This code will weed out any
0258             # attempts to the contrary.
0259             if type=="images":
0260                 losem=[]
0261                 # get existing wpi for this location
0262                 wpi=results[mediaindexkey]
0263                 for idx in index:
0264                     delit=True
0265                     for i in wpi:
0266                         entry=wpi[i]
0267                         if entry['origin']==type:
0268                             if index[idx]['name']==entry['name']:
0269                                 delit=False
0270                                 break
0271                     if delit:
0272                         self.log("Inhibited upload of illegit image (not originally on phone): "+index[idx]['name'])
0273                         losem.append(idx)
0274                 # Now actually remove any illegit images from upload attempt
0275                 for idx in losem:
0276                     del index[idx]
0277             #  slurp up any from wp we can take
0278             while len(index)<maxentries and len(wp):
0279                 idx=-1
0280                 while idx in index:
0281                     idx-=1
0282                 k=wp.keys()[0]
0283                 index[idx]=wp[k]
0284                 del wp[k]
0285             # normalise indices
0286             index=self._normaliseindices(index)  # hey look, I called a function!
0287             # move any overflow back into wp
0288             if len(index)>maxentries:
0289                 keys=index.keys()
0290                 keys.sort()
0291                 for k in keys[maxentries:]:
0292                     idx=-1
0293                     while idx in wp:
0294                         idx-=1
0295                     wp[idx]=index[k]
0296                     del index[k]
0297 
0298             # Now add the proper media file suffixes back to the internal index
0299             # prior to the finish up steps coming
0300             for k in index.keys():
0301                 if type=="ringers":
0302                     index[k]['name']=index[k]['name']+".mid"
0303                 else:
0304                     index[k]['name']=index[k]['name']+".bit"
0305                     
0306             # write out the new index
0307             keys=index.keys()
0308             keys.sort()
0309             ifile=self.protocolclass.indexfile()
0310             ifile.numactiveitems=len(keys)
0311             for k in keys:
0312                 entry=self.protocolclass.indexentry()
0313                 entry.index=k
0314                 entry.name=index[k]['name']
0315                 ifile.items.append(entry)
0316             while len(ifile.items)<maxentries:
0317                 ifile.items.append(self.protocolclass.indexentry())
0318             buffer=prototypes.buffer()
0319             ifile.writetobuffer(buffer, autolog=False)
0320             if type!="images":
0321                 # The images index file on the LG-VX3200 is a noop - don't write
0322                 self.logdata("Updated index file "+indexfile, buffer.getvalue(), ifile)
0323                 self.writefile(indexfile, buffer.getvalue())
0324                 
0325             # Write out files - we compare against existing dir listing and don't rewrite if they
0326             # are the same size
0327             for k in keys:
0328                 entry=index[k]
0329                 entryname=entry['name']
0330                 data=entry.get("data", None)
0331                 # Special test for wallpaper files - LG-VX3200 will ONLY accept these files
0332                 if type=="images":
0333                     if entryname!="wallpaper.bit" and entryname!="poweron.bit" and entryname!="poweroff.bit":
0334                         self.log("The wallpaper files can only be wallpaper.bmp, poweron.bmp or poweroff.bmp. "+entry['name']+" does not conform - skipping upload.")
0335                         continue
0336                 if data is None:
0337                     if entryname not in dirlisting:
0338                         self.log("Index error.  I have no data for "+entryname+" and it isn't already in the filesystem - skipping upload.")
0339                     continue
0340                 # Some of the wallpaper media files came here from the UI as BMP files.
0341                 # These need to be converted to LGBIT files on the way out.
0342                 if type=="images" and data[0:2]=="BM":
0343                     data=conversions.convertbmptolgbit(data)
0344                     if data is None:
0345                         self.log("The wallpaper BMP images must be 8BPP or 24BPP, "+entry['name']+", does not comply - skipping upload.")
0346                         continue
0347                 # Can only upload 128x128 images
0348                 if type=="images" and (common.LSBUint16(data[0:2])!=128 or common.LSBUint16(data[2:4])!=128):
0349                         self.log("The wallpaper must be 128x128, "+entry['name']+", does not comply - skipping upload.")
0350                         continue
0351                 # This following test does not apply to wallpaper - it is always the same size on the LG-VX3200!
0352                 if type!="images":
0353                     if entryname in dirlisting and len(data)==dirlisting[entryname]['size']:
0354                         self.log("Skipping writing %s/%s as there is already a file of the same length" % (location,entryname))
0355                         continue
0356                 self.mediacache.writefile(location+"/"+entryname, data)
0357                 self.log("Wrote media file: "+location+"/"+entryname)
0358         # did we have too many
0359         if len(wp):
0360             for k in wp:
0361                 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],))
0362                 
0363         # Note that we don't write to the camera area
0364 
0365         # tidy up - reread indices
0366         del results[mediakey] # done with it
0367         # This excessive and expensive so do not do
0368         # self.mediacache.flush()
0369         # self.log("Flushed ringer cache")
0370         reindexfunction(results)
0371         return results
0372 
0373     my_model='AX3200'
0374 
0375 parentprofile=com_lgvx4400.Profile
0376 class Profile(parentprofile):
0377     protocolclass=Phone.protocolclass
0378     serialsname=Phone.serialsname
0379     phone_manufacturer='LG Electronics Inc'
0380     phone_model='VX3200'
0381 
0382     # use for auto-detection
0383     phone_manufacturer='LG Electronics Inc.'
0384     phone_model='VX3200 107'
0385 
0386     # no direct usb interface
0387     usbids=com_lgvx4400.Profile.usbids_usbtoserial
0388 
0389     def convertphonebooktophone(self, helper, data):
0390         """Converts the data to what will be used by the phone
0391 
0392         @param data: contains the dict returned by getfundamentals
0393                      as well as where the results go"""
0394         results={}
0395 
0396         speeds={}
0397 
0398         self.normalisegroups(helper, data)
0399 
0400         for pbentry in data['phonebook']:
0401             if len(results)==self.protocolclass.NUMPHONEBOOKENTRIES:
0402                 break
0403             e={} # entry out
0404             entry=data['phonebook'][pbentry] # entry in
0405             try:
0406                 # serials
0407                 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', 0)
0408                 serial2=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial2', serial1)
0409 
0410                 e['serial1']=serial1
0411                 e['serial2']=serial2
0412                 for ss in entry["serials"]:
0413                     if ss["sourcetype"]=="bitpim":
0414                         e['bitpimserial']=ss
0415                 assert e['bitpimserial']
0416 
0417                 # name
0418                 e['name']=helper.getfullname(entry.get('names', []),1,1,22)[0]
0419 
0420                 # categories/groups
0421                 cat=helper.makeone(helper.getcategory(entry.get('categories', []),0,1,22), None)
0422                 if cat is None:
0423                     e['group']=0
0424                 else:
0425                     key,value=self._getgroup(cat, data['groups'])
0426                     if key is not None:
0427                         # lgvx3200 fix
0428                         if key>5:
0429                             e['group']=0
0430                             #self.log("Custom Groups in PB not supported - setting to No Group for "+e['name'])
0431                             print "Custom Groups in PB not supported - setting to No Group for "+e['name']
0432                         else:
0433                             e['group']=key
0434                     else:
0435                         # sorry no space for this category
0436                         e['group']=0
0437 
0438                 # email addresses
0439                 emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.NUMEMAILS,48)
0440                 e['emails']=helper.filllist(emails, self.protocolclass.NUMEMAILS, "")
0441 
0442                 # url
0443                 e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "")
0444 
0445                 # memo (-1 is to leave space for null terminator - not all software puts it in, but we do)
0446                 e['memo']=helper.makeone(helper.getmemos(entry.get('memos', []), 0, 1, self.protocolclass.MEMOLENGTH-1), "")
0447 
0448                 # phone numbers
0449                 # there must be at least one email address or phonenumber
0450                 minnumbers=1
0451                 if len(emails): minnumbers=0
0452                 numbers=helper.getnumbers(entry.get('numbers', []),minnumbers,self.protocolclass.NUMPHONENUMBERS)
0453                 e['numbertypes']=[]
0454                 e['numbers']=[]
0455                 for numindex in range(len(numbers)):
0456                     num=numbers[numindex]
0457                     # deal with type
0458                     b4=len(e['numbertypes'])
0459                     type=num['type']
0460                     for i,t in enumerate(self.protocolclass.numbertypetab):
0461                         if type==t:
0462                             # some voodoo to ensure the second home becomes home2
0463                             if i in e['numbertypes'] and t[-1]!='2':
0464                                 type+='2'
0465                                 continue
0466                             e['numbertypes'].append(i)
0467                             break
0468                         if t=='none': # conveniently last entry
0469                             e['numbertypes'].append(i)
0470                             break
0471                     if len(e['numbertypes'])==b4:
0472                         # we couldn't find a type for the number
0473                         continue 
0474                     # deal with number
0475                     number=self.phonize(num['number'])
0476                     if len(number)==0:
0477                         # no actual digits in the number
0478                         continue
0479                     if len(number)>48: # get this number from somewhere sensible
0480                         # ::TODO:: number is too long and we have to either truncate it or ignore it?
0481                         number=number[:48] # truncate for moment
0482                     e['numbers'].append(number)
0483                     # deal with speed dial
0484                     sd=num.get("speeddial", -1)
0485                     if self.protocolclass.NUMSPEEDDIALS:
0486                         if sd>=self.protocolclass.FIRSTSPEEDDIAL and sd<=self.protocolclass.LASTSPEEDDIAL:
0487                             speeds[sd]=(e['bitpimserial'], numindex)
0488 
0489                 e['numbertypes']=helper.filllist(e['numbertypes'], 5, 0)
0490                 e['numbers']=helper.filllist(e['numbers'], 5, "")
0491 
0492                 # ringtones, wallpaper
0493                 # LG VX3200 only supports writing the first 26 builtin ringers in pb
0494                 ecring=helper.getringtone(entry.get('ringtones', []), 'call', None)
0495                 if ecring is not None:
0496                     if ecring not in Phone.builtinringtones:
0497                         #self.log("Ringers past Carol 2 in PB not supported - setting to Default Ringer for "+e['name'])
0498                         print "Ringers past Carol 2 in PB not supported - setting to Default Ringer for "+e['name']+" id was: "+ecring
0499                         ecring=None
0500                 e['ringtone']=ecring
0501                 emring=helper.getringtone(entry.get('ringtones', []), 'message', None)
0502                 if emring is not None:
0503                     if emring not in Phone.builtinringtones:
0504                         #self.log("Ringers past Carol 2 in PB not supported - setting to Default MsgRinger for "+e['name'])
0505                         print "Ringers past Carol 2 in PB not supported - setting to Default MsgRinger for "+e['name']+" id was: "+emring
0506                         emring=None
0507                 e['msgringtone']=emring
0508                 # LG VX3200 does not support writing wallpaper in pb
0509                 ewall=helper.getwallpaper(entry.get('wallpapers', []), 'call', None)
0510                 if ewall is not None:
0511                     #self.log("Custom Wallpapers in PB not supported - setting to Default Wallpaper for "+e['name'])
0512                     print "Custom Wallpapers in PB not supported - setting to Default Wallpaper for "+e['name']
0513                 e['wallpaper']=None
0514 
0515                 # flags
0516                 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False)
0517 
0518                 results[pbentry]=e
0519                 
0520             except helper.ConversionFailed:
0521                 continue
0522 
0523         if self.protocolclass.NUMSPEEDDIALS:
0524             data['speeddials']=speeds
0525         data['phonebook']=results
0526         return data
0527 
0528     _supportedsyncs=(
0529         ('phonebook', 'read', None),  # all phonebook reading
0530         ('calendar', 'read', None),   # all calendar reading
0531         ('wallpaper', 'read', None),  # all wallpaper reading
0532         ('ringtone', 'read', None),   # all ringtone reading
0533         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
0534         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
0535      #   ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
0536         ('wallpaper', 'write', 'OVERWRITE'),   # merge and overwrite wallpaper
0537         ('ringtone', 'write', 'MERGE'),      # merge and overwrite ringtone
0538         ('ringtone', 'write', 'OVERWRITE'),
0539         ('call_history', 'read', None),
0540         ('memo', 'read', None),     # all memo list reading DJP
0541         ('memo', 'write', 'OVERWRITE'),  # all memo list writing DJP
0542         ('sms', 'read', None),
0543         ('sms', 'write', 'OVERWRITE'),
0544         )
0545 
0546     WALLPAPER_WIDTH=128
0547     WALLPAPER_HEIGHT=128
0548     MAX_WALLPAPER_BASENAME_LENGTH=19
0549     WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
0550     WALLPAPER_CONVERT_FORMAT="bmp"
0551 
0552     MAX_RINGTONE_BASENAME_LENGTH=19
0553     RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvxwyz0123456789 ."
0554 
0555     imageorigins={}
0556     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
0557 
0558     def GetImageOrigins(self):
0559         return self.imageorigins
0560 
0561     # our targets are the same for all origins
0562     imagetargets={}
0563     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
0564                                       {'width': 128, 'height': 128, 'format': "BMP"}))
0565 
0566     def GetTargetsForImageOrigin(self, origin):
0567         return self.imagetargets
0568     
0569     def __init__(self):
0570         parentprofile.__init__(self)
0571 
0572 
0573 

Generated by PyXR 0.9.4