PyXR

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



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