PyXR

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



0001 #!/usr/bin/env python
0002 
0003 ### BITPIM
0004 ###
0005 ### Copyright (C) 2006 Joe Pham <djpham@bitpim.org>
0006 ###
0007 ### This program is free software; you can redistribute it and/or modify
0008 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0009 ###
0010 ### $Id: com_lgvx8500.py 4305 2007-07-16 04:05:25Z djpham $
0011 
0012 """
0013 Communicate with the LG VX8500 cell phone
0014 """
0015 # standard modules
0016 import datetime
0017 
0018 # wx modules
0019 
0020 # BitPim modules
0021 import common
0022 import com_brew
0023 import com_lg
0024 import com_lgvx8300
0025 import helpids
0026 import fileinfo
0027 import guihelper
0028 import os
0029 import p_brew
0030 import p_lgvx8500
0031 import playlist
0032 import prototypes
0033 import prototypeslg
0034 import t9editor
0035 
0036 parentphone=com_lgvx8300.Phone
0037 class Phone(parentphone):
0038     desc="LG-VX8500"
0039     helpid=helpids.ID_PHONE_LGVX8500
0040 
0041     protocolclass=p_lgvx8500
0042     serialsname='lgvx8500'
0043 
0044     my_model='VX8500'
0045     builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps', 'VZW Default Ringtone') + \
0046                       tuple(['Ringtone '+`n` for n in range(1,13)]) + \
0047                       ('No Ring',)
0048 
0049     ringtonelocations= (
0050         #  type          index file            default dir        external dir    max  type Index
0051         ( 'ringers',    'dload/myringtone.dat','brew/16452/lk/mr','mmc1/ringers', 100, 0x01, 100),
0052         ( 'sounds',     'dload/mysound.dat',   'brew/16452/ms',   '',             100, 0x02, None),
0053         ( 'sounds(sd)', 'dload/sd_sound.dat',  'mmc1/my_sounds',  '',             100, 0x02, None),
0054         ( 'music',      'dload/efs_music.dat', 'my_music',        '',             100, 0x104, None),
0055         ( 'music(sd)',  'dload/sd_music.dat',  'mmc1/my_music',   '',             100, 0x14, None),
0056         )
0057 
0058     wallpaperlocations= (
0059         #  type          index file            default dir     external dir  max  type Index
0060         ( 'images',     'dload/image.dat',    'brew/16452/mp', '',           100, 0x00, 100),
0061         ( 'images(sd)', 'dload/sd_image.dat', 'mmc1/my_pix',   '',           100, 0x10, None),
0062         ( 'video',      'dload/video.dat',    'brew/16452/mf', '',           100, 0x03, None),
0063         ( 'video(sd)',  'dload/sd_video.dat', 'mmc1/my_flix',  '',           100, 0x13, None),
0064         )
0065 
0066     def setDMversion(self):
0067         """Define the DM version required for this phone, default to DMv5"""
0068         _fw_version=self.get_firmware_version()[-1]
0069         self._DMv5=self.my_model=='VX8500' and _fw_version>'4'
0070 
0071     # Fundamentals:
0072     #  - get_esn             - same as LG VX-8300
0073     #  - getgroups           - same as LG VX-8100
0074     #  - getwallpaperindices - LGUncountedIndexedMedia
0075     #  - getrintoneindices   - LGUncountedIndexedMedia
0076     #  - DM Version          - T85VZV01 - T85VZV04: 4, T85VZV05 - : 5
0077 
0078     # Phonebook stuff-----------------------------------------------------------
0079     def _build_pb_info(self, fundamentals):
0080         # build a dict of info to update pbentry
0081         pbook=fundamentals.get('phonebook', {})
0082         wallpaper_index=fundamentals.get('wallpaper-index', {})
0083         ringtone_index=fundamentals.get('ringtone-index', {})
0084         r1={}
0085         for k,e in pbook.items():
0086             r1[e['bitpimserial']['id']]={ 'wallpaper': \
0087                                           self._findmediainindex(wallpaper_index,
0088                                                                  e['wallpaper'],
0089                                                                  e['name'],
0090                                                                  'wallpaper'),
0091                                           'msgringtone': \
0092                                           self._findmediainindex(ringtone_index,
0093                                                                  e['msgringtone'],
0094                                                                  e['name'],
0095                                                                  'message ringtone')}
0096         serialupdates=fundamentals.get("serialupdates", [])
0097         r2={}
0098         for bps, serials in serialupdates:
0099             r2[serials['serial1']]=r1[bps['id']]
0100         return r2
0101     def _update_pb_file(self, pb, fundamentals, pbinfo):
0102         # update the pbentry file
0103         update_flg=False
0104         for e in pb.items:
0105             _info=pbinfo.get(e.serial1, None)
0106             if _info:
0107                 wp=_info.get('wallpaper', None)
0108                 if wp is not None and wp!=e.wallpaper:
0109                     update_flg=True
0110                     e.wallpaper=wp
0111                 msgrt=_info.get('msgringtone', None)
0112                 if msgrt is not None and msgrt!=e.msgringtone:
0113                     update_flg=True
0114                     e.msgringtone=msgrt
0115         if update_flg:
0116             self.log('Updating wallpaper index')
0117             buf=prototypes.buffer()
0118             pb.writetobuffer(buf, logtitle="Updated index "+self.protocolclass.pb_file_name)
0119             self.writefile(self.protocolclass.pb_file_name, buf.getvalue())
0120     def _update_pb_info(self, pbentries, fundamentals):
0121         # Manually update phonebook data that the normal protocol should have
0122         _pbinfo=self._build_pb_info(fundamentals)
0123         self._update_pb_file(pbentries, fundamentals, _pbinfo)
0124     def _write_path_index(self, pbentries, pbmediakey, media_index,
0125                           index_file, invalid_values):
0126         _path_entry=self.protocolclass.PathIndexEntry()
0127         _path_file=self.protocolclass.PathIndexFile()
0128         for _ in range(self.protocolclass.NUMPHONEBOOKENTRIES):
0129             _path_file.items.append(_path_entry)
0130         for _entry in pbentries.items:
0131             _idx=getattr(_entry, pbmediakey)
0132             if _idx in invalid_values or not media_index.has_key(_idx):
0133                 continue
0134             if media_index[_idx].get('origin', None)=='builtin':
0135                 _filename=media_index[_idx]['name']
0136             elif media_index[_idx].get('filename', None):
0137                 _filename=media_index[_idx]['filename']
0138             else:
0139                 continue
0140             _path_file.items[_entry.entrynumber]=self.protocolclass.PathIndexEntry(
0141                 pathname=_filename)
0142         _buf=prototypes.buffer()
0143         _path_file.writetobuffer(_buf, logtitle='Writing Path ID')
0144         self.writefile(index_file, _buf.getvalue())
0145 
0146     def savephonebook(self, data):
0147         "Saves out the phonebook"
0148         res=parentphone.savephonebook(self, data)
0149         # retrieve the phonebook entries
0150         _buf=prototypes.buffer(self.getfilecontents(
0151             self.protocolclass.pb_file_name))
0152         _pb_entries=self.protocolclass.pbfile()
0153         _pb_entries.readfrombuffer(_buf, logtitle="Read phonebook file "+self.protocolclass.pb_file_name)
0154         _rt_index=data.get('ringtone-index', {})
0155         _wp_index=data.get('wallpaper-index', {})
0156         # update info that the phone software failed to do!!
0157         self._update_pb_info(_pb_entries, data)
0158         # fix up ringtone index
0159         self._write_path_index(_pb_entries, 'ringtone',
0160                                _rt_index,
0161                                self.protocolclass.RTPathIndexFile,
0162                                (0xffff,))
0163         # fix up msg ringtone index
0164         self._write_path_index(_pb_entries, 'msgringtone',
0165                                _rt_index,
0166                                self.protocolclass.MsgRTIndexFile,
0167                                (0xffff,))
0168         # fix up wallpaer index
0169         self._write_path_index(_pb_entries, 'wallpaper',
0170                                _wp_index,
0171                                self.protocolclass.WPPathIndexFile,
0172                                (0, 0xffff,))
0173         return res
0174 
0175     # Playlist stuff------------------------------------------------------------
0176     def _get_all_songs(self, fundamentals):
0177         # return a list of all available songs
0178         _rt_index=fundamentals.get('ringtone-index', {})
0179         return [x['name'] for _,x in _rt_index.items() \
0180                 if x.get('origin', None) in ('music', 'music(sd)') ]
0181     def _read_pl_list(self, filename):
0182         # read and return the contents of the specified playlist
0183         _req=self.protocolclass.PLPlayListFile()
0184         _buf=prototypes.buffer(self.getfilecontents(filename))
0185         _req.readfrombuffer(_buf, logtitle="Read playlist "+filename)
0186         _songs=[]
0187         for _item in _req.items:
0188             _songs.append(common.basename(_item.pathname))
0189         _entry=playlist.PlaylistEntry()
0190         _entry.name=common.stripext(common.basename(filename))
0191         _entry.songs=_songs
0192         return _entry
0193     def getplaylist(self, result):
0194         # return a list of all available songs and playlists
0195         # first, retrieve the list of all songs
0196         result[playlist.masterlist_key]=self._get_all_songs(result)
0197         # get a list of playlists and their contents
0198         _pl_list=[]
0199         try:
0200             _req=self.protocolclass.PLIndexFile()
0201             _buf=prototypes.buffer(
0202                 self.getfilecontents(self.protocolclass.PLIndexFileName))
0203             _req.readfrombuffer(_buf, logtitle='Reading Playlist Index')
0204             for _item in _req.items:
0205                 try:
0206                     _pl_list.append(self._read_pl_list(_item.pathname))
0207                 except com_brew.BrewNoSuchFileException:
0208                     pass
0209                 except:
0210                     if __debug__:
0211                         raise
0212         except com_brew.BrewNoSuchFileException:
0213             pass
0214         except:
0215             if __debug__:
0216                 raise
0217         result[playlist.playlist_key]=_pl_list
0218         return result
0219 
0220     def _get_info_from_index(self, filename):
0221         _res={}
0222         try:
0223             _buf=prototypes.buffer(
0224                 self.getfilecontents(filename))
0225             _req=self.protocolclass.indexfile()
0226             _req.readfrombuffer(_buf, logtitle='Reading Index File')
0227             for _item in _req.items:
0228                 _res[common.basename(_item.filename)]={ 'size': _item.size,
0229                                                         'filename': _item.filename }
0230         except com_brew.BrewNoSuchFileException:
0231             pass
0232         except:
0233             if __debug__:
0234                 raise
0235         return _res
0236     def _get_song_info(self):
0237         # return a dict of all songs & their info
0238         _res=self._get_info_from_index('dload/efs_music.dat')
0239         _res.update(self._get_info_from_index('dload/sd_music.dat'))
0240         return _res
0241     def _write_a_playlist(self, filename, pl, song_info):
0242         # write a single playlist
0243         _pl_file=self.protocolclass.PLPlayListFile()
0244         _dt=datetime.datetime.now()
0245         _onesec=datetime.timedelta(seconds=1)
0246         _plsize=0
0247         for _song in pl.songs:
0248             if song_info.has_key(_song):
0249                 _dt-=_onesec
0250                 _entry=self.protocolclass.PLSongEntry(
0251                     pathname=song_info[_song]['filename'],
0252                     date=_dt.timetuple()[:6],
0253                     size=song_info[_song]['size'])
0254                 _pl_file.items.append(_entry)
0255                 _plsize+=1
0256                 if _plsize>=self.protocolclass.PLMaxSize:
0257                     # reached max size
0258                     break
0259         _buf=prototypes.buffer()
0260         _pl_file.writetobuffer(_buf,
0261                                logtitle='Writing Playlist '+pl.name)
0262         self.writefile(filename, _buf.getvalue())
0263     def _write_playlists(self, playlists, song_info):
0264         _pl_index=self.protocolclass.PLIndexFile()
0265         for _pl in playlists:
0266             try:
0267                 _filename=self.protocolclass.PLFilePath+'/'+_pl.name+\
0268                            self.protocolclass.PLExt
0269                 self._write_a_playlist(_filename, _pl, song_info)
0270                 _pl_index.items.append(
0271                     self.protocolclass.PLIndexEntry(pathname=_filename))
0272             except:
0273                 self.log('Failed to write playlist '+_pl.name)
0274                 if __debug__:
0275                     raise
0276         try:
0277             _buf=prototypes.buffer()
0278             _pl_index.writetobuffer(_buf,
0279                                     logtitle='Writing Playlist Index')
0280             self.writefile(self.protocolclass.PLIndexFileName,
0281                            _buf.getvalue())
0282         except:
0283             self.log('Failed to write Playlist Index file')
0284             if __debug__:
0285                 raise
0286     def saveplaylist(self, result, merge):
0287         # delete all existing playlists
0288         _buf=prototypes.buffer(self.getfilecontents(
0289             self.protocolclass.PLIndexFileName))
0290         _req=self.protocolclass.PLIndexFile()
0291         _req.readfrombuffer(_buf, logtitle='Reading Playlist Index')
0292         for _item in _req.items:
0293             try:
0294                 self.rmfile(_item.pathname)
0295             except com_brew. BrewNoSuchFileException:
0296                 pass
0297             except:
0298                 if __debug__:
0299                     raise
0300         # get info on all songs
0301         _song_info=self._get_song_info()
0302         # update the new playlists
0303         self._write_playlists(result.get(playlist.playlist_key, []),
0304                               _song_info)
0305         return result
0306 
0307     # T9 DB stuff
0308     def _get_current_db(self):
0309         _current_db=None
0310         try:
0311             _data=self.getfilecontents(
0312                 self.protocolclass.T9USERDBFILENAME)
0313             if _data:
0314                 _current_db=self.protocolclass.t9udbfile()
0315                 _current_db.readfrombuffer(prototypes.buffer(_data),
0316                                            logtitle='Reading T9 User DB File')
0317         except com_brew.BrewNoSuchFileException:
0318             pass
0319         return _current_db
0320     def _copy_fields(self, old, new):
0321         for _field in ('unknown1', 'unknown2', 'unknown3', 'unknown4'):
0322             setattr(new, _field, getattr(old, _field))
0323     def gett9db(self, result):
0324         _req=self._get_current_db()
0325         _t9list=t9editor.T9WordsList()
0326         if _req:
0327             for _item in _req.blocks:
0328                 _blk=_item.block
0329                 if _blk['type']==prototypeslg.T9USERDBBLOCK.WordsList_Type:
0330                     for _word in _blk['value']:
0331                         _t9list.append_word(_word['word'])
0332         result[t9editor.dict_key]=_t9list
0333         return result
0334     _buffer_data={
0335         '1': 0x7FA, '2': 0x7FA, '3': 0x7FA, '4': 0x7FA,
0336         '5': 0x7FA, '6': 0x7FA, '7': 0x7FA, '8': 0x7FA,
0337         '9': 0x7FA, '0': 0x800 }
0338     _t9keys=('1', '2', '3', '4', '5', '6', '7', '8', '9', '0')
0339     def savet9db(self, result, _):
0340         _new_db=self.protocolclass.t9udbfile()
0341         try:
0342             _current_db=self._get_current_db()
0343             if _current_db:
0344                 self._copy_fields(_current_db, _new_db)
0345         except:
0346             _current_db=None
0347         _t9words={}
0348         _t9list=result.get(t9editor.dict_key, t9editor.T9WordsList())
0349         _wordcount=0
0350         for _key in _t9list.keys:
0351             for _word in _t9list.get_words(_key):
0352                 _t9words.setdefault(_key[0], []).append(_word)
0353                 _wordcount+=1
0354         _new_db.word_count=_wordcount
0355         _total_free_size=0
0356         for _key in self._t9keys:
0357             _lst=_t9words.get(_key, [])
0358             if _lst:
0359                 _val={ 'type': prototypeslg.T9USERDBBLOCK.WordsList_Type,
0360                        'value': [ { 'word': x } for x in _lst ] }
0361                 _blk=prototypeslg.T9USERDBBLOCK(_val)
0362                 _free_len=self._buffer_data[_key]-_blk.packetsize()
0363                 # ugly hack!
0364                 if _key!='1':
0365                     _free_len+=1
0366                 _total_free_size+=_free_len
0367                 _new_db.blocks.append(_val)
0368                 _new_db.blocks.append({ 'type': prototypeslg.T9USERDBBLOCK.FreeBlock_Type,
0369                                         'value': _free_len })
0370             else:
0371                 if _key!='1':
0372                     _new_db.blocks.append({ 'type': prototypeslg.T9USERDBBLOCK.A0_Type,
0373                                             'value': 0xA0 })
0374                 _type=prototypeslg.T9USERDBBLOCK.FreeBlock_Type
0375                 _new_db.blocks.append({ 'type': _type,
0376                                         'value': self._buffer_data[_key] })
0377                 _total_free_size+=self._buffer_data[_key]
0378         _new_db.free_space=_total_free_size
0379         _buf=prototypes.buffer()
0380         _new_db.writetobuffer(_buf)
0381         self.writefile(self.protocolclass.T9USERDBFILENAME,
0382                        _buf.getvalue())
0383         # Need to reboot the phone to take effect
0384         result['rebootphone']=True
0385         return result
0386 
0387     # Misc Stuff----------------------------------------------------------------
0388     def get_firmware_version(self):
0389         # return the firmware version
0390         req=p_brew.firmwarerequest()
0391         res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse)
0392         return res.firmware
0393 
0394 #-------------------------------------------------------------------------------
0395 parentprofile=com_lgvx8300.Profile
0396 class Profile(parentprofile):
0397     protocolclass=Phone.protocolclass
0398     serialsname=Phone.serialsname
0399 
0400     BP_Calendar_Version=3
0401     phone_manufacturer='LG Electronics Inc'
0402     phone_model='VX8500'
0403 
0404     WALLPAPER_WIDTH=240
0405     WALLPAPER_HEIGHT=320
0406     MAX_WALLPAPER_BASENAME_LENGTH=32
0407     WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_() ."
0408     WALLPAPER_CONVERT_FORMAT="jpg"
0409 
0410     # the 8300 uses "W" for wait in the dialstring, it does not support "T"
0411     DIALSTRING_CHARS="[^0-9PW#*]"
0412 
0413     MAX_RINGTONE_BASENAME_LENGTH=32
0414     RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_() ."
0415     RINGTONE_LIMITS= {
0416         'MAXSIZE': 200000
0417     }
0418 
0419     imageorigins={}
0420     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
0421     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "video"))
0422     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images(sd)"))
0423     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "video(sd)"))
0424     def GetImageOrigins(self):
0425         return self.imageorigins
0426 
0427     ringtoneorigins=('ringers', 'sounds', 'sounds(sd)',' music', 'music(sd)')
0428     excluded_ringtone_origins=('sounds', 'sounds(sd)', 'music', 'music(sd)')
0429 
0430     # our targets are the same for all origins
0431     imagetargets={}
0432     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
0433                                       {'width': 240, 'height': 275, 'format': "JPEG"}))
0434 
0435     def GetTargetsForImageOrigin(self, origin):
0436         return self.imagetargets
0437 
0438 
0439     def __init__(self):
0440         parentprofile.__init__(self)
0441 
0442     def QueryAudio(self, origin, currentextension, afi):
0443         _max_size=self.RINGTONE_LIMITS['MAXSIZE']
0444         setattr(afi, 'MAXSIZE', _max_size)
0445         # we don't modify any of these
0446         if afi.format in ("MIDI", "QCP", "PMD", "WMA"):
0447             return currentextension, afi
0448         # examine mp3
0449         if afi.format=="MP3":
0450             if afi.channels==1 and 8<=afi.bitrate<=64 and 16000<=afi.samplerate<=22050:
0451                 return currentextension, afi
0452         # convert it
0453         return ("mp3", fileinfo.AudioFileInfo(afi, **{'format': 'MP3',
0454                                                       'channels': 2,
0455                                                       'bitrate': 48,
0456                                                       'samplerate': 44100,
0457                                                       'MAXSIZE': _max_size }))
0458 
0459     _supportedsyncs=(
0460         ('phonebook', 'read', None),  # all phonebook reading
0461         ('calendar', 'read', None),   # all calendar reading
0462         ('wallpaper', 'read', None),  # all wallpaper reading
0463         ('ringtone', 'read', None),   # all ringtone reading
0464         ('call_history', 'read', None),# all call history list reading
0465         ('sms', 'read', None),         # all SMS list reading
0466         ('memo', 'read', None),        # all memo list reading
0467         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
0468         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
0469         ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
0470         ('wallpaper', 'write', 'OVERWRITE'),
0471         ('ringtone', 'write', 'MERGE'),      # merge and overwrite ringtone
0472         ('ringtone', 'write', 'OVERWRITE'),
0473         ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
0474         ('memo', 'write', 'OVERWRITE'),       # all memo list writing
0475         ('playlist', 'read', 'OVERWRITE'),
0476         ('playlist', 'write', 'OVERWRITE'),
0477         ('t9_udb', 'write', 'OVERWRITE'),
0478         )
0479     if __debug__:
0480         _supportedsyncs+=(
0481         ('t9_udb', 'read', 'OVERWRITE'),
0482         )
0483    
0484     field_color_data={
0485         'phonebook': {
0486             'name': {
0487                 'first': 1, 'middle': 1, 'last': 1, 'full': 1,
0488                 'nickname': 0, 'details': 1 },
0489             'number': {
0490                 'type': 5, 'speeddial': 5, 'number': 5, 'details': 5 },
0491             'email': 2,
0492             'address': {
0493                 'type': 0, 'company': 0, 'street': 0, 'street2': 0,
0494                 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0,
0495                 'details': 0 },
0496             'url': 0,
0497             'memo': 0,
0498             'category': 1,
0499             'wallpaper': 1,
0500             'ringtone': 2,
0501             'storage': 0,
0502             },
0503         'calendar': {
0504             'description': True, 'location': False, 'allday': False,
0505             'start': True, 'end': True, 'priority': False,
0506             'alarm': True, 'vibrate': True,
0507             'repeat': True,
0508             'memo': False,
0509             'category': False,
0510             'wallpaper': False,
0511             'ringtone': True,
0512             },
0513         'memo': {
0514             'subject': True,
0515             'date': True,
0516             'secret': False,
0517             'category': False,
0518             'memo': True,
0519             },
0520         'todo': {
0521             'summary': False,
0522             'status': False,
0523             'due_date': False,
0524             'percent_complete': False,
0525             'completion_date': False,
0526             'private': False,
0527             'priority': False,
0528             'category': False,
0529             'memo': False,
0530             },
0531         }
0532 

Generated by PyXR 0.9.4