0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2006 Simon Capper <skyjunky@sbcglobal.net> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 0009 """Communicate with the LG 6190 (bell mobility) cell phone""" 0010 0011 # standard modules 0012 import re 0013 import time 0014 import cStringIO 0015 import sha 0016 0017 # my modules 0018 import p_lglg6190 0019 import p_brew 0020 import common 0021 import commport 0022 import com_brew 0023 import com_phone 0024 import com_lg 0025 import com_lgvx4400 0026 import prototypes 0027 import call_history 0028 import sms 0029 import fileinfo 0030 import memo 0031 0032 0033 class Phone(com_lgvx4400.Phone): 0034 "Talk to the LG 6190 cell phone" 0035 0036 desc="LG 6190" 0037 helpid=None 0038 protocolclass=p_lglg6190 0039 serialsname='lg6190' 0040 0041 wallpaperindexfilename="download/dloadindex/brewImageIndex.map" 0042 ringerindexfilename="download/dloadindex/brewRingerIndex.map" 0043 0044 imagelocations=( 0045 # offset, index file, files location, type, maximumentries 0046 ( 10, "download/dloadindex/brewImageIndex.map", "usr/Wallpaper", "images", 30), 0047 ( 50, "cam/pics.dat", "cam", "camera", 60), 0048 ) 0049 0050 ringtonelocations=( 0051 # offset, index file, files location, type, maximumentries 0052 ( 50, "download/dloadindex/brewRingerIndex.map", "usr/Ringtone", "ringers", 30), 0053 ) 0054 0055 builtinimages=() 0056 0057 builtinringtones=( 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5', 0058 'Default tone', 'Fearwell', 'Arabesque', 'Piano Sonata', 'Latin', 0059 'When the Saints', 'Bach Cello Suite', 'Speedy Way', 'Cancan', 'String', 0060 'Toccata and Fuge', 'Mozart Symphony 40', 'Nutcracker March', 'Funiculi', 0061 'Ploka', 'Hallelujah', 'Mozart Aria', 'Leichte', 'Spring', 'Slavonic', 'Fantasy') 0062 0063 0064 0065 def __init__(self, logtarget, commport): 0066 "Calls all the constructors and sets initial modes" 0067 com_phone.Phone.__init__(self, logtarget, commport) 0068 com_brew.BrewProtocol.__init__(self) 0069 com_lg.LGPhonebook.__init__(self) 0070 self.log("Attempting to contact phone") 0071 self._cal_has_voice_id=hasattr(self.protocolclass, 'cal_has_voice_id') \ 0072 and self.protocolclass.cal_has_voice_id 0073 self.mode=self.MODENONE 0074 0075 0076 # media functions. In addition to the usual brew index this silly phone puts the media files in a directory with the same name as 0077 # the original file (without the extension) renames the media file "body" and then creates a '.desc' file that contains all the 0078 # file info that would have been there anyway if they had not renamed it in the first place. Unlike the .desc file on the 4600 that 0079 # also doubles up as the index, the .desc file seems to serve no useful purpose. 0080 # they also store the cam images in a different way than other media. I guess they never heard of code reuse, modular design etc. at 0081 # least the phone looks good, far more important that being well designed and actually working well 0082 # As a result we have to override a bunch of the media functions to make this phone work... 0083 0084 def getmediaindex(self, builtins, maps, results, key): 0085 """Gets the media (wallpaper/ringtone) index 0086 0087 @param builtins: the builtin list on the phone 0088 @param results: places results in this dict 0089 @param maps: the list of index files and locations 0090 @param key: key to place results in 0091 """ 0092 0093 self.log("Reading "+key) 0094 media={} 0095 0096 # builtins 0097 c=1 0098 for name in builtins: 0099 media[c]={'name': name, 'origin': 'builtin' } 0100 c+=1 0101 0102 # the maps 0103 type=None 0104 for offset,indexfile,location,type,maxentries in maps: 0105 if type=='camera': 0106 index=self.getcamindex(indexfile, location) 0107 else: 0108 index=self.getindex(indexfile, location) 0109 for i in index: 0110 media[i+offset]={'name': index[i], 'origin': type} 0111 0112 results[key]=media 0113 return media 0114 0115 def getcamindex(self, indexfile, location, getmedia=False): 0116 "Read an index file" 0117 index={} 0118 try: 0119 buf=prototypes.buffer(self.getfilecontents(indexfile)) 0120 except com_brew.BrewNoSuchFileException: 0121 # file may not exist 0122 return index 0123 g=self.protocolclass.camindexfile() 0124 g.readfrombuffer(buf, logtitle="Camera index file") 0125 self.log("Cam index file read") 0126 for i in g.items: 0127 if len(i.name): 0128 # the name in the index file is for display purposes only 0129 filename="pic%02d.jpg" % i.index 0130 if not getmedia: 0131 index[i.index]=filename 0132 else: 0133 try: 0134 contents=self.getfilecontents(location+"/"+filename+"/body") 0135 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException,com_brew.BrewNameTooLongException): 0136 self.log("Can't find the actual content in "+location+"/"+filename+"/body") 0137 continue 0138 index[filename]=contents 0139 return index 0140 0141 def getindex(self, indexfile, location, getmedia=False): 0142 "Read an index file" 0143 index={} 0144 try: 0145 buf=prototypes.buffer(self.getfilecontents(indexfile)) 0146 except com_brew.BrewNoSuchFileException: 0147 # file may not exist 0148 return index 0149 g=self.protocolclass.indexfile() 0150 g.readfrombuffer(buf, logtitle="Index file %s read" % (indexfile,)) 0151 for i in g.items: 0152 if i.index!=0xffff and len(i.name): 0153 # read the .desc file 0154 try: 0155 buf=prototypes.buffer(self.getfilecontents(location+"/"+i.name+"/.desc")) 0156 except com_brew.BrewNoSuchFileException: 0157 self.log("No .desc file in "+location+"/"+i.name+" - ignoring directory") 0158 continue 0159 desc=self.protocolclass.mediadesc() 0160 desc.readfrombuffer(buf, logtitle=".desc file %s/.desc read" % (location+"/"+i.name,)) 0161 filename=self._createnamewithmimetype(i.name, desc.mimetype) 0162 if not getmedia: 0163 index[i.index]=filename 0164 else: 0165 try: 0166 # try to read it using name in desc file 0167 contents=self.getfilecontents(location+"/"+i.name+"/"+desc.filename) 0168 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException): 0169 try: 0170 # then try using "body" 0171 contents=self.getfilecontents(location+"/"+i.name+"/body") 0172 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException,com_brew.BrewNameTooLongException): 0173 self.log("Can't find the actual content in "+location+"/"+i.name+"/body") 0174 continue 0175 index[filename]=contents 0176 return index 0177 0178 def _createnamewithmimetype(self, name, mt): 0179 name=common.basename(name) 0180 if mt=="image/jpeg": 0181 mt="image/jpg" 0182 try: 0183 return name+self.__mimetoextensionmapping[mt] 0184 except KeyError: 0185 self.log("Unable to figure out extension for mime type "+mt) 0186 return name 0187 0188 __mimetoextensionmapping={ 0189 'image/jpg': '.jpg', 0190 'image/bmp': '.bmp', 0191 'image/png': '.png', 0192 'image/gif': '.gif', 0193 'image/bci': '.bci', 0194 'audio/mpeg': '.mp3', 0195 'audio/midi': '.mid', 0196 'audio/qcp': '.qcp' 0197 } 0198 0199 def _getmimetype(self, name): 0200 ext=common.getext(name.lower()) 0201 if len(ext): ext="."+ext 0202 if ext==".jpeg": 0203 return "image/jpg" # special case 0204 for mt,extension in self.__mimetoextensionmapping.items(): 0205 if ext==extension: 0206 return mt 0207 self.log("Unable to figure out a mime type for "+name) 0208 assert False, "No idea what type "+ext+" is" 0209 return "x-unknown/x-unknown" 0210 0211 def getmedia(self, maps, result, key): 0212 """Returns the contents of media as a dict where the key is a name as returned 0213 by getindex, and the value is the contents of the media""" 0214 media={} 0215 for offset,indexfile,location,origin,maxentries in maps: 0216 if origin=='camera': 0217 media.update(self.getcamindex(indexfile, location, getmedia=True)) 0218 else: 0219 media.update(self.getindex(indexfile, location, getmedia=True)) 0220 result[key]=media 0221 return result 0222 0223 def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction): 0224 """Actually saves out the media 0225 0226 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 0227 @param mediaindexkey: index key (eg 'wallpaper-index') 0228 @param maps: list index files and locations 0229 @param results: results dict 0230 @param merge: are we merging or overwriting what is there? 0231 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 0232 """ 0233 print results.keys() 0234 # I humbly submit this as the longest function in the bitpim code ... 0235 # wp and wpi are used as variable names as this function was originally 0236 # written to do wallpaper. it works just fine for ringtones as well 0237 wp=results[mediakey].copy() 0238 wpi=results[mediaindexkey].copy() 0239 # remove builtins 0240 for k in wpi.keys(): 0241 if wpi[k]['origin']=='builtin': 0242 del wpi[k] 0243 0244 # sort results['mediakey'+'-index'] into origin buckets 0245 0246 # build up list into init 0247 init={} 0248 for offset,indexfile,location,type,maxentries in maps: 0249 if type=='camera': 0250 continue 0251 init[type]={} 0252 for k in wpi.keys(): 0253 if wpi[k]['origin']==type: 0254 index=k-offset 0255 name=wpi[k]['name'] 0256 data=None 0257 del wpi[k] 0258 for w in wp.keys(): 0259 if wp[w]['name']==name and wp[w]['origin']==type: 0260 data=wp[w]['data'] 0261 del wp[w] 0262 if not merge and data is None: 0263 # delete the entry 0264 continue 0265 init[type][index]={'name': name, 'data': data} 0266 0267 # init now contains everything from wallpaper-index 0268 print init.keys() 0269 # now look through wallpapers and see if anything remaining was assigned a particular 0270 # origin 0271 for w in wp.keys(): 0272 o=wp[w].get("origin", "") 0273 if o is not None and len(o) and o in init: 0274 idx=-1 0275 while idx in init[o]: 0276 idx-=1 0277 init[o][idx]=wp[w] 0278 del wp[w] 0279 0280 # we now have init[type] with the entries and index number as key (negative indices are 0281 # unallocated). Proceed to deal with each one, taking in stuff from wp as we have space 0282 for offset,indexfile,location,type,maxentries in maps: 0283 if type=='camera': 0284 continue 0285 index=init[type] 0286 try: 0287 dirlisting=self.getfilesystem(location) 0288 except com_brew.BrewNoSuchDirectoryException: 0289 self.mkdirs(location) 0290 dirlisting={} 0291 # rename keys to basename, allow for debug filesystem not putting full path in key 0292 for i in dirlisting.keys(): 0293 if len(i)>len(location) and i[len(location)]==location: 0294 dirlisting[i[len(location)+1:]]=dirlisting[i] 0295 del dirlisting[i] 0296 # what we will be deleting 0297 dellist=[] 0298 if not merge: 0299 # get existing wpi for this location 0300 wpi=results[mediaindexkey] 0301 for i in wpi: 0302 entry=wpi[i] 0303 if entry['origin']==type: 0304 # it is in the original index, are we writing it back out? 0305 delit=True 0306 for idx in index: 0307 if index[idx]['name']==entry['name']: 0308 delit=False 0309 break 0310 if delit: 0311 if common.stripext(entry['name']) in dirlisting: 0312 dellist.append(common.stripext(entry['name'])) 0313 else: 0314 self.log("%s in %s index but not filesystem" % (entry['name'], type)) 0315 # go ahead and delete unwanted files 0316 print "deleting",dellist 0317 for f in dellist: 0318 self.rmdirs(location+"/"+f) 0319 # slurp up any from wp we can take 0320 while len(index)<maxentries and len(wp): 0321 idx=-1 0322 while idx in index: 0323 idx-=1 0324 k=wp.keys()[0] 0325 index[idx]=wp[k] 0326 del wp[k] 0327 # normalise indices 0328 index=self._normaliseindices(index) # hey look, I called a function! 0329 # move any overflow back into wp 0330 if len(index)>maxentries: 0331 keys=index.keys() 0332 keys.sort() 0333 for k in keys[maxentries:]: 0334 idx=-1 0335 while idx in wp: 0336 idx-=1 0337 wp[idx]=index[k] 0338 del index[k] 0339 # write out the new index 0340 keys=index.keys() 0341 keys.sort() 0342 ifile=self.protocolclass.indexfile() 0343 ifile.numactiveitems=len(keys) 0344 for k in keys: 0345 entry=self.protocolclass.indexentry() 0346 entry.index=k 0347 entry.name=common.stripext(index[k]['name']) 0348 ifile.items.append(entry) 0349 while len(ifile.items)<maxentries: 0350 ifile.items.append(self.protocolclass.indexentry()) 0351 buffer=prototypes.buffer() 0352 ifile.writetobuffer(buffer, logtitle="Updated index file "+indexfile) 0353 self.writefile(indexfile, buffer.getvalue()) 0354 # Write out files - we compare against existing dir listing and don't rewrite if they 0355 # are the same size 0356 for k in keys: 0357 entry=index[k] 0358 data=entry.get("data", None) 0359 dirname=common.stripext(entry['name']) 0360 if data is None: 0361 if dirname not in dirlisting: 0362 self.log("Index error. I have no data for "+dirname+" and it isn't already in the filesystem") 0363 continue 0364 if dirname in dirlisting: 0365 try: 0366 stat=self.statfile(location+"/"+dirname+"/body") 0367 if stat['size']==len(data): 0368 # check that the .desc file exists 0369 stat=self.statfile(location+"/"+dirname+"/.desc") 0370 if stat['size']==152: 0371 self.log("Skipping writing %s/%s/body as there is already a file of the same length" % (location,dirname)) 0372 continue 0373 except com_brew.BrewNoSuchFileException: 0374 pass 0375 elif dirname not in dirlisting: 0376 try: 0377 self.mkdir(location+"/"+dirname) 0378 except com_brew.BrewDirectoryExistsException: 0379 pass 0380 # write out media 0381 self.writefile(location+"/"+dirname+"/body", data) 0382 mimetype=self._getmimetype(entry['name']) 0383 desc=self.protocolclass.mediadesc() 0384 desc.mimetype=mimetype 0385 desc.totalsize=0 0386 desc.totalsize=desc.packetsize()+len(data) 0387 buf=prototypes.buffer() 0388 descfile="%s/%s/.desc" % (location, dirname) 0389 desc.writetobuffer(buf, logtitle="Desc file at "+descfile) 0390 self.writefile(descfile, buf.getvalue()) 0391 0392 # did we have too many 0393 if len(wp): 0394 for k in wp: 0395 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],)) 0396 0397 # tidy up - reread indices 0398 del results[mediakey] # done with it 0399 reindexfunction(results) 0400 return results 0401 0402 #----- SMS --------------------------------------------------------------------------- 0403 0404 def _getinboxmessage(self, sf): 0405 entry=sms.SMSEntry() 0406 entry.folder=entry.Folder_Inbox 0407 entry.datetime="%d%02d%02dT%02d%02d%02d" % (sf.GPStime) 0408 entry._from=self._getsender(sf.sender, sf.sender_length) 0409 entry.subject=sf.subject 0410 # entry.locked=sf.locked 0411 # if sf.priority==0: 0412 # entry.priority=sms.SMSEntry.Priority_Normal 0413 # else: 0414 # entry.priority=sms.SMSEntry.Priority_High 0415 entry.read=sf.read 0416 entry.text=sf.msg 0417 entry.callback=sf.callback 0418 return entry 0419 0420 def _getoutboxmessage(self, sf): 0421 entry=sms.SMSEntry() 0422 entry.folder=entry.Folder_Sent 0423 entry.datetime="%d%02d%02dT%02d%02d00" % ((sf.timesent)) 0424 # add all the recipients 0425 for r in sf.recipients: 0426 if r.number: 0427 confirmed=(r.status==2) 0428 confirmed_date=None 0429 if confirmed: 0430 confirmed_date="%d%02d%02dT%02d%02d00" % r.time 0431 entry.add_recipient(r.number, confirmed, confirmed_date) 0432 entry.subject=sf.msg[:28] 0433 entry.text=sf.msg 0434 # if sf.priority==0: 0435 # entry.priority=sms.SMSEntry.Priority_Normal 0436 # else: 0437 # entry.priority=sms.SMSEntry.Priority_High 0438 # entry.locked=sf.locked 0439 entry.callback=sf.callback 0440 return entry 0441 0442 #----- Phone Detection ----------------------------------------------------------- 0443 0444 # this phone has no version file, but the string is in one of the nvm files 0445 brew_version_file='OWS/paramtable1.fil' 0446 brew_version_txt_key='LG6190_version_data' 0447 my_model='lg6190' 0448 0449 def eval_detect_data(self, res): 0450 found=False 0451 if res.get(self.brew_version_txt_key, None) is not None: 0452 found=res[self.brew_version_txt_key][0x1ae:0x1ae+len(self.my_model)]==self.my_model 0453 if found: 0454 res['model']=self.my_model 0455 res['manufacturer']='LG Electronics Inc' 0456 s=res.get(self.esn_file_key, None) 0457 if s: 0458 res['esn']=self.get_esn(s) 0459 0460 def getphoneinfo(self, phone_info): 0461 self.log('Getting Phone Info') 0462 try: 0463 s=self.getfilecontents(self.brew_version_file) 0464 if s[0x1ae:0x1ae+len(self.my_model)]==self.my_model: 0465 phone_info.append('Model:', self.my_model) 0466 phone_info.append('ESN:', self.get_brew_esn()) 0467 req=p_brew.firmwarerequest() 0468 #res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse) 0469 #phone_info.append('Firmware Version:', res.firmware) 0470 txt=self.getfilecontents("nvm/nvm/nvm_0000")[0x241:0x24b] 0471 phone_info.append('Phone Number:', txt) 0472 except: 0473 pass 0474 return 0475 0476 parentprofile=com_lgvx4400.Profile 0477 class Profile(parentprofile): 0478 protocolclass=Phone.protocolclass 0479 serialsname=Phone.serialsname 0480 BP_Calendar_Version=3 0481 phone_manufacturer='LG Electronics Inc' 0482 phone_model='lg6190' # from Bell Mobility 0483 brew_required=True 0484 RINGTONE_LIMITS= { 0485 'MAXSIZE': 250000 0486 } 0487 0488 WALLPAPER_WIDTH=160 0489 WALLPAPER_HEIGHT=120 0490 MAX_WALLPAPER_BASENAME_LENGTH=30 0491 WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789." 0492 WALLPAPER_CONVERT_FORMAT="jpg" 0493 0494 MAX_RINGTONE_BASENAME_LENGTH=30 0495 RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789." 0496 DIALSTRING_CHARS="[^0-9PT#*]" 0497 0498 # our targets are the same for all origins 0499 imagetargets={} 0500 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper", 0501 {'width': 128, 'height': 160, 'format': "JPEG"})) 0502 0503 _supportedsyncs=( 0504 ('phonebook', 'read', None), # all phonebook reading 0505 ('calendar', 'read', None), # all calendar reading 0506 ('wallpaper', 'read', None), # all wallpaper reading 0507 ('ringtone', 'read', None), # all ringtone reading 0508 ('call_history', 'read', None),# all call history list reading 0509 ('memo', 'read', None), # all memo list reading 0510 ('sms', 'read', None), # all SMS list reading 0511 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 0512 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 0513 ('wallpaper', 'write', 'MERGE'), # merge and overwrite wallpaper 0514 ('wallpaper', 'write', 'OVERWRITE'), 0515 ('ringtone', 'write', 'MERGE'), # merge and overwrite ringtone 0516 ('ringtone', 'write', 'OVERWRITE'), 0517 ('memo', 'write', 'OVERWRITE'), # all memo list writing 0518 ('sms', 'write', 'OVERWRITE'), # all SMS list writing 0519 ) 0520 0521 field_color_data={ 0522 'phonebook': { 0523 'name': { 0524 'first': 0, 'middle': 0, 'last': 0, 'full': 1, 0525 'nickname': 0, 'details': 1 }, 0526 'number': { 0527 'type': 5, 'speeddial': 5, 'number': 5, 'details': 5 }, 0528 'email': 3, 0529 'address': { 0530 'type': 0, 'company': 0, 'street': 0, 'street2': 0, 0531 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0, 0532 'details': 0 }, 0533 'url': 1, 0534 'memo': 1, 0535 'category': 1, 0536 'wallpaper': 0, 0537 'ringtone': 1, 0538 'storage': 0, 0539 }, 0540 'calendar': { 0541 'description': True, 'location': False, 'allday': True, 0542 'start': True, 'end': True, 'priority': False, 0543 'alarm': True, 'vibrate': False, 0544 'repeat': True, 0545 'memo': False, 0546 'category': False, 0547 'wallpaper': False, 0548 'ringtone': True, 0549 }, 0550 'memo': { 0551 'subject': True, 0552 'date': False, 0553 'secret': False, 0554 'category': False, 0555 'memo': True, 0556 }, 0557 'todo': { 0558 'summary': False, 0559 'status': False, 0560 'due_date': False, 0561 'percent_complete': False, 0562 'completion_date': False, 0563 'private': False, 0564 'priority': False, 0565 'category': False, 0566 'memo': False, 0567 }, 0568 } 0569
Generated by PyXR 0.9.4