Package phones :: Module com_lglg6190
[hide private]
[frames] | no frames]

Source Code for Module phones.com_lglg6190

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2006 Simon Capper <skyjunky@sbcglobal.net> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8   
  9  """Communicate with the LG 6190 (bell mobility) cell phone""" 
 10   
 11  # standard modules 
 12  import re 
 13  import time 
 14  import cStringIO 
 15  import sha 
 16   
 17  # my modules 
 18  import p_lglg6190 
 19  import p_brew 
 20  import common 
 21  import commport 
 22  import com_brew 
 23  import com_phone 
 24  import com_lg 
 25  import com_lgvx4400 
 26  import prototypes 
 27  import call_history 
 28  import sms 
 29  import fileinfo 
 30  import memo 
 31   
 32   
33 -class Phone(com_lgvx4400.Phone):
34 "Talk to the LG 6190 cell phone" 35 36 desc="LG 6190" 37 helpid=None 38 protocolclass=p_lglg6190 39 serialsname='lg6190' 40 41 wallpaperindexfilename="download/dloadindex/brewImageIndex.map" 42 ringerindexfilename="download/dloadindex/brewRingerIndex.map" 43 44 imagelocations=( 45 # offset, index file, files location, type, maximumentries 46 ( 10, "download/dloadindex/brewImageIndex.map", "usr/Wallpaper", "images", 30), 47 ( 50, "cam/pics.dat", "cam", "camera", 60), 48 ) 49 50 ringtonelocations=( 51 # offset, index file, files location, type, maximumentries 52 ( 50, "download/dloadindex/brewRingerIndex.map", "usr/Ringtone", "ringers", 30), 53 ) 54 55 builtinimages=() 56 57 builtinringtones=( 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5', 58 'Default tone', 'Fearwell', 'Arabesque', 'Piano Sonata', 'Latin', 59 'When the Saints', 'Bach Cello Suite', 'Speedy Way', 'Cancan', 'String', 60 'Toccata and Fuge', 'Mozart Symphony 40', 'Nutcracker March', 'Funiculi', 61 'Ploka', 'Hallelujah', 'Mozart Aria', 'Leichte', 'Spring', 'Slavonic', 'Fantasy') 62 63 64
65 - def __init__(self, logtarget, commport):
66 "Calls all the constructors and sets initial modes" 67 com_phone.Phone.__init__(self, logtarget, commport) 68 com_brew.BrewProtocol.__init__(self) 69 com_lg.LGPhonebook.__init__(self) 70 self.log("Attempting to contact phone") 71 self._cal_has_voice_id=hasattr(self.protocolclass, 'cal_has_voice_id') \ 72 and self.protocolclass.cal_has_voice_id 73 self.mode=self.MODENONE
74 75 76 # media functions. In addition to the usual brew index this silly phone puts the media files in a directory with the same name as 77 # the original file (without the extension) renames the media file "body" and then creates a '.desc' file that contains all the 78 # 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 79 # also doubles up as the index, the .desc file seems to serve no useful purpose. 80 # 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 81 # least the phone looks good, far more important that being well designed and actually working well 82 # As a result we have to override a bunch of the media functions to make this phone work... 83
84 - def getmediaindex(self, builtins, maps, results, key):
85 """Gets the media (wallpaper/ringtone) index 86 87 @param builtins: the builtin list on the phone 88 @param results: places results in this dict 89 @param maps: the list of index files and locations 90 @param key: key to place results in 91 """ 92 93 self.log("Reading "+key) 94 media={} 95 96 # builtins 97 c=1 98 for name in builtins: 99 media[c]={'name': name, 'origin': 'builtin' } 100 c+=1 101 102 # the maps 103 type=None 104 for offset,indexfile,location,type,maxentries in maps: 105 if type=='camera': 106 index=self.getcamindex(indexfile, location) 107 else: 108 index=self.getindex(indexfile, location) 109 for i in index: 110 media[i+offset]={'name': index[i], 'origin': type} 111 112 results[key]=media 113 return media
114
115 - def getcamindex(self, indexfile, location, getmedia=False):
116 "Read an index file" 117 index={} 118 try: 119 buf=prototypes.buffer(self.getfilecontents(indexfile)) 120 except com_brew.BrewNoSuchFileException: 121 # file may not exist 122 return index 123 g=self.protocolclass.camindexfile() 124 g.readfrombuffer(buf, logtitle="Camera index file") 125 self.log("Cam index file read") 126 for i in g.items: 127 if len(i.name): 128 # the name in the index file is for display purposes only 129 filename="pic%02d.jpg" % i.index 130 if not getmedia: 131 index[i.index]=filename 132 else: 133 try: 134 contents=self.getfilecontents(location+"/"+filename+"/body") 135 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException,com_brew.BrewNameTooLongException): 136 self.log("Can't find the actual content in "+location+"/"+filename+"/body") 137 continue 138 index[filename]=contents 139 return index
140
141 - def getindex(self, indexfile, location, getmedia=False):
142 "Read an index file" 143 index={} 144 try: 145 buf=prototypes.buffer(self.getfilecontents(indexfile)) 146 except com_brew.BrewNoSuchFileException: 147 # file may not exist 148 return index 149 g=self.protocolclass.indexfile() 150 g.readfrombuffer(buf, logtitle="Index file %s read" % (indexfile,)) 151 for i in g.items: 152 if i.index!=0xffff and len(i.name): 153 # read the .desc file 154 try: 155 buf=prototypes.buffer(self.getfilecontents(location+"/"+i.name+"/.desc")) 156 except com_brew.BrewNoSuchFileException: 157 self.log("No .desc file in "+location+"/"+i.name+" - ignoring directory") 158 continue 159 desc=self.protocolclass.mediadesc() 160 desc.readfrombuffer(buf, logtitle=".desc file %s/.desc read" % (location+"/"+i.name,)) 161 filename=self._createnamewithmimetype(i.name, desc.mimetype) 162 if not getmedia: 163 index[i.index]=filename 164 else: 165 try: 166 # try to read it using name in desc file 167 contents=self.getfilecontents(location+"/"+i.name+"/"+desc.filename) 168 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException): 169 try: 170 # then try using "body" 171 contents=self.getfilecontents(location+"/"+i.name+"/body") 172 except (com_brew.BrewNoSuchFileException,com_brew.BrewNoSuchDirectoryException,com_brew.BrewNameTooLongException): 173 self.log("Can't find the actual content in "+location+"/"+i.name+"/body") 174 continue 175 index[filename]=contents 176 return index
177
178 - def _createnamewithmimetype(self, name, mt):
179 name=common.basename(name) 180 if mt=="image/jpeg": 181 mt="image/jpg" 182 try: 183 return name+self.__mimetoextensionmapping[mt] 184 except KeyError: 185 self.log("Unable to figure out extension for mime type "+mt) 186 return name
187 188 __mimetoextensionmapping={ 189 'image/jpg': '.jpg', 190 'image/bmp': '.bmp', 191 'image/png': '.png', 192 'image/gif': '.gif', 193 'image/bci': '.bci', 194 'audio/mpeg': '.mp3', 195 'audio/midi': '.mid', 196 'audio/qcp': '.qcp' 197 } 198
199 - def _getmimetype(self, name):
200 ext=common.getext(name.lower()) 201 if len(ext): ext="."+ext 202 if ext==".jpeg": 203 return "image/jpg" # special case 204 for mt,extension in self.__mimetoextensionmapping.items(): 205 if ext==extension: 206 return mt 207 self.log("Unable to figure out a mime type for "+name) 208 assert False, "No idea what type "+ext+" is" 209 return "x-unknown/x-unknown"
210
211 - def getmedia(self, maps, result, key):
212 """Returns the contents of media as a dict where the key is a name as returned 213 by getindex, and the value is the contents of the media""" 214 media={} 215 for offset,indexfile,location,origin,maxentries in maps: 216 if origin=='camera': 217 media.update(self.getcamindex(indexfile, location, getmedia=True)) 218 else: 219 media.update(self.getindex(indexfile, location, getmedia=True)) 220 result[key]=media 221 return result
222
223 - def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
224 """Actually saves out the media 225 226 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 227 @param mediaindexkey: index key (eg 'wallpaper-index') 228 @param maps: list index files and locations 229 @param results: results dict 230 @param merge: are we merging or overwriting what is there? 231 @param reindexfunction: the media is re-indexed at the end. this function is called to do it 232 """ 233 print results.keys() 234 # I humbly submit this as the longest function in the bitpim code ... 235 # wp and wpi are used as variable names as this function was originally 236 # written to do wallpaper. it works just fine for ringtones as well 237 wp=results[mediakey].copy() 238 wpi=results[mediaindexkey].copy() 239 # remove builtins 240 for k in wpi.keys(): 241 if wpi[k]['origin']=='builtin': 242 del wpi[k] 243 244 # sort results['mediakey'+'-index'] into origin buckets 245 246 # build up list into init 247 init={} 248 for offset,indexfile,location,type,maxentries in maps: 249 if type=='camera': 250 continue 251 init[type]={} 252 for k in wpi.keys(): 253 if wpi[k]['origin']==type: 254 index=k-offset 255 name=wpi[k]['name'] 256 data=None 257 del wpi[k] 258 for w in wp.keys(): 259 if wp[w]['name']==name and wp[w]['origin']==type: 260 data=wp[w]['data'] 261 del wp[w] 262 if not merge and data is None: 263 # delete the entry 264 continue 265 init[type][index]={'name': name, 'data': data} 266 267 # init now contains everything from wallpaper-index 268 print init.keys() 269 # now look through wallpapers and see if anything remaining was assigned a particular 270 # origin 271 for w in wp.keys(): 272 o=wp[w].get("origin", "") 273 if o is not None and len(o) and o in init: 274 idx=-1 275 while idx in init[o]: 276 idx-=1 277 init[o][idx]=wp[w] 278 del wp[w] 279 280 # we now have init[type] with the entries and index number as key (negative indices are 281 # unallocated). Proceed to deal with each one, taking in stuff from wp as we have space 282 for offset,indexfile,location,type,maxentries in maps: 283 if type=='camera': 284 continue 285 index=init[type] 286 try: 287 dirlisting=self.getfilesystem(location) 288 except com_brew.BrewNoSuchDirectoryException: 289 self.mkdirs(location) 290 dirlisting={} 291 # rename keys to basename, allow for debug filesystem not putting full path in key 292 for i in dirlisting.keys(): 293 if len(i)>len(location) and i[len(location)]==location: 294 dirlisting[i[len(location)+1:]]=dirlisting[i] 295 del dirlisting[i] 296 # what we will be deleting 297 dellist=[] 298 if not merge: 299 # get existing wpi for this location 300 wpi=results[mediaindexkey] 301 for i in wpi: 302 entry=wpi[i] 303 if entry['origin']==type: 304 # it is in the original index, are we writing it back out? 305 delit=True 306 for idx in index: 307 if index[idx]['name']==entry['name']: 308 delit=False 309 break 310 if delit: 311 if common.stripext(entry['name']) in dirlisting: 312 dellist.append(common.stripext(entry['name'])) 313 else: 314 self.log("%s in %s index but not filesystem" % (entry['name'], type)) 315 # go ahead and delete unwanted files 316 print "deleting",dellist 317 for f in dellist: 318 self.rmdirs(location+"/"+f) 319 # slurp up any from wp we can take 320 while len(index)<maxentries and len(wp): 321 idx=-1 322 while idx in index: 323 idx-=1 324 k=wp.keys()[0] 325 index[idx]=wp[k] 326 del wp[k] 327 # normalise indices 328 index=self._normaliseindices(index) # hey look, I called a function! 329 # move any overflow back into wp 330 if len(index)>maxentries: 331 keys=index.keys() 332 keys.sort() 333 for k in keys[maxentries:]: 334 idx=-1 335 while idx in wp: 336 idx-=1 337 wp[idx]=index[k] 338 del index[k] 339 # write out the new index 340 keys=index.keys() 341 keys.sort() 342 ifile=self.protocolclass.indexfile() 343 ifile.numactiveitems=len(keys) 344 for k in keys: 345 entry=self.protocolclass.indexentry() 346 entry.index=k 347 entry.name=common.stripext(index[k]['name']) 348 ifile.items.append(entry) 349 while len(ifile.items)<maxentries: 350 ifile.items.append(self.protocolclass.indexentry()) 351 buffer=prototypes.buffer() 352 ifile.writetobuffer(buffer, logtitle="Updated index file "+indexfile) 353 self.writefile(indexfile, buffer.getvalue()) 354 # Write out files - we compare against existing dir listing and don't rewrite if they 355 # are the same size 356 for k in keys: 357 entry=index[k] 358 data=entry.get("data", None) 359 dirname=common.stripext(entry['name']) 360 if data is None: 361 if dirname not in dirlisting: 362 self.log("Index error. I have no data for "+dirname+" and it isn't already in the filesystem") 363 continue 364 if dirname in dirlisting: 365 try: 366 stat=self.statfile(location+"/"+dirname+"/body") 367 if stat['size']==len(data): 368 # check that the .desc file exists 369 stat=self.statfile(location+"/"+dirname+"/.desc") 370 if stat['size']==152: 371 self.log("Skipping writing %s/%s/body as there is already a file of the same length" % (location,dirname)) 372 continue 373 except com_brew.BrewNoSuchFileException: 374 pass 375 elif dirname not in dirlisting: 376 try: 377 self.mkdir(location+"/"+dirname) 378 except com_brew.BrewDirectoryExistsException: 379 pass 380 # write out media 381 self.writefile(location+"/"+dirname+"/body", data) 382 mimetype=self._getmimetype(entry['name']) 383 desc=self.protocolclass.mediadesc() 384 desc.mimetype=mimetype 385 desc.totalsize=0 386 desc.totalsize=desc.packetsize()+len(data) 387 buf=prototypes.buffer() 388 descfile="%s/%s/.desc" % (location, dirname) 389 desc.writetobuffer(buf, logtitle="Desc file at "+descfile) 390 self.writefile(descfile, buf.getvalue()) 391 392 # did we have too many 393 if len(wp): 394 for k in wp: 395 self.log("Unable to put %s on the phone as there weren't any spare index entries" % (wp[k]['name'],)) 396 397 # tidy up - reread indices 398 del results[mediakey] # done with it 399 reindexfunction(results) 400 return results
401 402 #----- SMS --------------------------------------------------------------------------- 403
404 - def _getinboxmessage(self, sf):
405 entry=sms.SMSEntry() 406 entry.folder=entry.Folder_Inbox 407 entry.datetime="%d%02d%02dT%02d%02d%02d" % (sf.GPStime) 408 entry._from=self._getsender(sf.sender, sf.sender_length) 409 entry.subject=sf.subject 410 # entry.locked=sf.locked 411 # if sf.priority==0: 412 # entry.priority=sms.SMSEntry.Priority_Normal 413 # else: 414 # entry.priority=sms.SMSEntry.Priority_High 415 entry.read=sf.read 416 entry.text=sf.msg 417 entry.callback=sf.callback 418 return entry
419
420 - def _getoutboxmessage(self, sf):
421 entry=sms.SMSEntry() 422 entry.folder=entry.Folder_Sent 423 entry.datetime="%d%02d%02dT%02d%02d00" % ((sf.timesent)) 424 # add all the recipients 425 for r in sf.recipients: 426 if r.number: 427 confirmed=(r.status==2) 428 confirmed_date=None 429 if confirmed: 430 confirmed_date="%d%02d%02dT%02d%02d00" % r.time 431 entry.add_recipient(r.number, confirmed, confirmed_date) 432 entry.subject=sf.msg[:28] 433 entry.text=sf.msg 434 # if sf.priority==0: 435 # entry.priority=sms.SMSEntry.Priority_Normal 436 # else: 437 # entry.priority=sms.SMSEntry.Priority_High 438 # entry.locked=sf.locked 439 entry.callback=sf.callback 440 return entry
441 442 #----- Phone Detection ----------------------------------------------------------- 443 444 # this phone has no version file, but the string is in one of the nvm files 445 brew_version_file='OWS/paramtable1.fil' 446 brew_version_txt_key='LG6190_version_data' 447 my_model='lg6190' 448
449 - def eval_detect_data(self, res):
450 found=False 451 if res.get(self.brew_version_txt_key, None) is not None: 452 found=res[self.brew_version_txt_key][0x1ae:0x1ae+len(self.my_model)]==self.my_model 453 if found: 454 res['model']=self.my_model 455 res['manufacturer']='LG Electronics Inc' 456 s=res.get(self.esn_file_key, None) 457 if s: 458 res['esn']=self.get_esn(s)
459
460 - def getphoneinfo(self, phone_info):
461 self.log('Getting Phone Info') 462 try: 463 s=self.getfilecontents(self.brew_version_file) 464 if s[0x1ae:0x1ae+len(self.my_model)]==self.my_model: 465 phone_info.append('Model:', self.my_model) 466 phone_info.append('ESN:', self.get_brew_esn()) 467 req=p_brew.firmwarerequest() 468 #res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse) 469 #phone_info.append('Firmware Version:', res.firmware) 470 txt=self.getfilecontents("nvm/nvm/nvm_0000")[0x241:0x24b] 471 phone_info.append('Phone Number:', txt) 472 except: 473 pass 474 return
475 476 parentprofile=com_lgvx4400.Profile
477 -class Profile(parentprofile):
478 protocolclass=Phone.protocolclass 479 serialsname=Phone.serialsname 480 BP_Calendar_Version=3 481 phone_manufacturer='LG Electronics Inc' 482 phone_model='lg6190' # from Bell Mobility 483 brew_required=True 484 RINGTONE_LIMITS= { 485 'MAXSIZE': 250000 486 } 487 488 WALLPAPER_WIDTH=160 489 WALLPAPER_HEIGHT=120 490 MAX_WALLPAPER_BASENAME_LENGTH=30 491 WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789." 492 WALLPAPER_CONVERT_FORMAT="jpg" 493 494 MAX_RINGTONE_BASENAME_LENGTH=30 495 RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789." 496 DIALSTRING_CHARS="[^0-9PT#*]" 497 498 # our targets are the same for all origins 499 imagetargets={} 500 imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper", 501 {'width': 128, 'height': 160, 'format': "JPEG"})) 502 503 _supportedsyncs=( 504 ('phonebook', 'read', None), # all phonebook reading 505 ('calendar', 'read', None), # all calendar reading 506 ('wallpaper', 'read', None), # all wallpaper reading 507 ('ringtone', 'read', None), # all ringtone reading 508 ('call_history', 'read', None),# all call history list reading 509 ('memo', 'read', None), # all memo list reading 510 ('sms', 'read', None), # all SMS list reading 511 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 512 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 513 ('wallpaper', 'write', 'MERGE'), # merge and overwrite wallpaper 514 ('wallpaper', 'write', 'OVERWRITE'), 515 ('ringtone', 'write', 'MERGE'), # merge and overwrite ringtone 516 ('ringtone', 'write', 'OVERWRITE'), 517 ('memo', 'write', 'OVERWRITE'), # all memo list writing 518 ('sms', 'write', 'OVERWRITE'), # all SMS list writing 519 ) 520 521 field_color_data={ 522 'phonebook': { 523 'name': { 524 'first': 0, 'middle': 0, 'last': 0, 'full': 1, 525 'nickname': 0, 'details': 1 }, 526 'number': { 527 'type': 5, 'speeddial': 5, 'number': 5, 'details': 5 }, 528 'email': 3, 529 'address': { 530 'type': 0, 'company': 0, 'street': 0, 'street2': 0, 531 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0, 532 'details': 0 }, 533 'url': 1, 534 'memo': 1, 535 'category': 1, 536 'wallpaper': 0, 537 'ringtone': 1, 538 'storage': 0, 539 }, 540 'calendar': { 541 'description': True, 'location': False, 'allday': True, 542 'start': True, 'end': True, 'priority': False, 543 'alarm': True, 'vibrate': False, 544 'repeat': True, 545 'memo': False, 546 'category': False, 547 'wallpaper': False, 548 'ringtone': True, 549 }, 550 'memo': { 551 'subject': True, 552 'date': False, 553 'secret': False, 554 'category': False, 555 'memo': True, 556 }, 557 'todo': { 558 'summary': False, 559 'status': False, 560 'due_date': False, 561 'percent_complete': False, 562 'completion_date': False, 563 'private': False, 564 'priority': False, 565 'category': False, 566 'memo': False, 567 }, 568 }
569