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