PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2006 Roger Binns <rogerb@rogerbinns.com>
0004 ### Copyright (C) 2006-2006 Simon Capper <skyjunky@sbcglobal.net>
0005 ### Copyright (C) 2006 Michael Cohen <mikepublic@nc.rr.com>
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_lgvx8100.py 4481 2007-12-05 22:39:17Z djpham $
0011 
0012 """Communicate with the LG VX8100 cell phone
0013 
0014 The VX8100 is similar to the VX7000 but also supports video.
0015 
0016 """
0017 
0018 # standard modules
0019 import re
0020 import time
0021 import cStringIO
0022 import sha
0023 
0024 # my modules
0025 import common
0026 import commport
0027 import copy
0028 import com_lgvx4400
0029 import p_brew
0030 import p_lgvx8100
0031 import com_lgvx7000
0032 import com_brew
0033 import com_phone
0034 import com_lg
0035 import prototypes
0036 import bpcalendar
0037 import call_history
0038 import sms
0039 import memo
0040 import helpids
0041 
0042 
0043 from prototypes import *
0044 
0045 
0046 
0047 class Phone(com_lg.LGNewIndexedMedia2, com_lg.LGDMPhone, com_lgvx7000.Phone):
0048     "Talk to the LG VX8100 cell phone"
0049 
0050     desc="LG-VX8100"
0051     helpid=helpids.ID_PHONE_LGVX8100
0052     protocolclass=p_lgvx8100
0053     serialsname='lgvx8100'
0054 
0055     builtinringtones= ('Low Beep Once', 'Low Beeps', 'Loud Beep Once', 'Loud Beeps', 'VZW Default Ringtone') + \
0056                       tuple(['Ringtone '+`n` for n in range(1,11)]) + \
0057                       ('No Ring',)
0058 
0059     ringtonelocations= (
0060         # type       index-file   size-file directory-to-use lowest-index-to-use maximum-entries type-major icon
0061         ( 'ringers', 'dload/my_ringtone.dat', 'dload/my_ringtonesize.dat', 'brew/16452/lk/mr', 100, 150, 0x201, 1, 0),
0062         # the sound index file uses the same index as the ringers, bitpim does not support this (yet)
0063         ( 'sounds', 'dload/mysound.dat', 'dload/mysoundsize.dat', 'brew/16452/ms', 100, 150, 2, 0, 151),
0064         )
0065 
0066     calendarlocation="sch/newschedule.dat"
0067     calendarexceptionlocation="sch/newschexception.dat"
0068     calenderrequiresreboot=0
0069     memolocation="sch/neomemo.dat"
0070 
0071     builtinwallpapers = () # none
0072 
0073     wallpaperlocations= (
0074         ( 'images', 'dload/image.dat', 'dload/imagesize.dat', 'brew/16452/mp', 100, 50, 0, 0, 0),
0075         ( 'video', 'dload/video.dat', None, 'brew/16452/mf', 1000, 50, 0x0304, 0, 0),
0076         )
0077 
0078     # for removable media (miniSD cards)
0079     _rs_path='mmc1/'
0080     _rs_ringers_path=_rs_path+'ringers'
0081     _rs_images_path=_rs_path+'images'
0082     media_info={ 'ringers': {
0083             'localpath': 'brew/16452/lk/mr',
0084             'rspath': _rs_ringers_path,
0085             'vtype': protocolclass.MEDIA_TYPE_RINGTONE,
0086             'icon': protocolclass.MEDIA_RINGTONE_DEFAULT_ICON,
0087             'index': 100,  # starting index
0088             'maxsize': 155,
0089             'indexfile': 'dload/my_ringtone.dat',
0090             'sizefile': 'dload/my_ringtonesize.dat',
0091             'dunno': 0, 'date': False,
0092         },
0093          'sounds': {
0094              'localpath': 'brew/16452/ms',
0095              'rspath': None,
0096              'vtype': protocolclass.MEDIA_TYPE_SOUND,
0097              'icon': protocolclass.MEDIA_IMAGE_DEFAULT_ICON,
0098              'index': 100,
0099              'maxsize': 155,
0100              'indexfile': 'dload/mysound.dat',
0101              'sizefile': 'dload/mysoundsize.dat',
0102              'dunno': 0, 'date': False },
0103          'images': {
0104              'localpath': 'brew/16452/mp',
0105              'rspath': _rs_images_path,
0106              'vtype': protocolclass.MEDIA_TYPE_IMAGE,
0107              'icon': protocolclass.MEDIA_IMAGE_DEFAULT_ICON,
0108              'index': 100,
0109              'maxsize': 155,
0110              'indexfile': 'dload/image.dat',
0111              'sizefile': 'dload/imagesize.dat',
0112              'dunno': 0, 'date': False },
0113          'video': {
0114              'localpath': 'brew/16452/mf',
0115              'rspath': None,
0116              'vtype': protocolclass.MEDIA_TYPE_VIDEO,
0117              'icon': protocolclass.MEDIA_VIDEO_DEFAULT_ICON,
0118              'index': 1000,
0119              'maxsize': 155,
0120              'indexfile': 'dload/video.dat',
0121              'sizefile': 'dload/videosize.dat',
0122              'dunno': 0, 'date': True },
0123          }
0124 
0125     def __init__(self, logtarget, commport):
0126         com_lgvx4400.Phone.__init__(self, logtarget, commport)
0127         com_lg.LGDMPhone.__init__(self)
0128         self.mode=self.MODENONE
0129 
0130     def get_firmware_version(self):
0131         # return the firmware version
0132         req=p_brew.firmwarerequest()
0133         res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse)
0134         return res.firmware
0135 
0136     def setDMversion(self):
0137         """Define the DM version required for this phone, default to DMv5"""
0138         # not sure what the 8100 requirements are, default to v4 only
0139         self._DMv5=False
0140 
0141     # Fundamentals:
0142     #  - get_esn             - /nvm/$ESN.SYS (same as LG VX-4400)
0143     #  - getgroups           - defined below
0144     #  - getwallpaperindices - LGNewIndexedMedia2
0145     #  - getringtoneindices  - LGNewIndexedMedia2
0146     #  - DM Version          - 4 to access brew/16452/lk/mr on newer firmwares
0147 
0148     def getgroups(self, results):
0149         self.log("Reading group information")
0150         buf=prototypes.buffer(self.getfilecontents("pim/pbgroup.dat"))
0151         g=self.protocolclass.pbgroups()
0152         g.readfrombuffer(buf, logtitle="Groups read")
0153         groups={}
0154         for i in range(len(g.groups)):
0155             if len(g.groups[i].name): # sometimes have zero length names
0156                 groups[i]={'name': g.groups[i].name }
0157         results['groups']=groups
0158         return groups
0159 
0160     def savegroups(self, data):
0161         groups=data['groups']
0162         keys=groups.keys()
0163         keys.sort()
0164         g=self.protocolclass.pbgroups()
0165         for k in keys:
0166             e=self.protocolclass.pbgroup()
0167             e.name=groups[k]['name']
0168             g.groups.append(e)
0169         buffer=prototypes.buffer()
0170         g.writetobuffer(buffer, logtitle="New group file")
0171         self.writefile("pim/pbgroup.dat", buffer.getvalue())
0172 
0173     def getmemo(self, result):
0174         # read the memo file
0175         res={}
0176         try:
0177             stat_res=self.statfile(self.memolocation)
0178             # allow for zero length file
0179             if stat_res!=None and stat_res['size']!=0:
0180                 buf=prototypes.buffer(self.getfilecontents(self.memolocation))
0181                 text_memo=self.protocolclass.textmemofile()
0182                 text_memo.readfrombuffer(buf, logtitle="Read memo file"+self.memolocation)
0183                 for m in text_memo.items:
0184                     entry=memo.MemoEntry()
0185                     entry.text=m.text
0186                     try:
0187                         entry.set_date_isostr("%d%02d%02dT%02d%02d00" % ((m.memotime)))
0188                     except ValueError:
0189                         # deleted memos can remain in file but have a bogus date
0190                         continue
0191                     res[entry.id]=entry
0192         except com_brew.BrewNoSuchFileException:
0193             pass
0194         result['memo']=res
0195         return result
0196 
0197     def savememo(self, result, merge):
0198         text_memo=self.protocolclass.textmemofile()
0199         memo_dict=result.get('memo', {})
0200         keys=memo_dict.keys()
0201         keys.sort()
0202         text_memo.itemcount=len(keys)
0203         for k in keys:
0204             entry=self.protocolclass.textmemo()
0205             entry.text=memo_dict[k].text
0206             t=time.strptime(memo_dict[k].date, '%b %d, %Y %H:%M')
0207             entry.memotime=(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min)
0208             if 'GPStime' in entry.getfields():
0209                 entry.GPStime = (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
0210             text_memo.items.append(entry)
0211         buf=prototypes.buffer()
0212         text_memo.writetobuffer(buf, logtitle="Updated memo "+self.memolocation)
0213         self.writefile(self.memolocation, buf.getvalue())
0214         return result
0215 
0216     def getexceptions (self):
0217         try:
0218             buf=prototypes.buffer(self.getfilecontents(self.calendarexceptionlocation))
0219             ex=self.protocolclass.scheduleexceptionfile()
0220             ex.readfrombuffer(buf, logtitle="Calendar exceptions")
0221             exceptions={}
0222             for i in ex.items:
0223                 try:
0224                     exceptions[i.pos].append( (i.year,i.month,i.day) )
0225                 except KeyError:
0226                     exceptions[i.pos]=[ (i.year,i.month,i.day) ]
0227         except com_brew.BrewNoSuchFileException:
0228             exceptions={}
0229 
0230         return exceptions
0231 
0232     def getcalendarcommon(self, entry, event):
0233         entry.desc_loc=event.description
0234         entry.start=event.start
0235         if self.protocolclass.CALENDAR_HAS_SEPARATE_END_TIME_AND_DATE:
0236             if event.repeat[0] == 0:           # MIC:  If non-repeating event
0237                 entry.end = event.end_time     # MIC:  Set entry.end to full end_time
0238             else:
0239                 _,_,_,hour,minute=event.end_time
0240                 year,month,day,_,_=event.end_date
0241                 entry.end=(year,month,day,hour,minute)
0242         else:
0243             entry.end=event.end
0244         if event.alarmindex_vibrate&0x1:
0245             entry.vibrate=0 # vibarate bit is inverted in phone 0=on, 1=off
0246         else:
0247             entry.vibrate=1
0248         entry.repeat = self.makerepeat(event.repeat)
0249         min=event.alarmminutes
0250         hour=event.alarmhours
0251         if min==0x64 or hour==0x64:
0252             entry.alarm=None # no alarm set
0253         else:
0254             entry.alarm=hour*60+min
0255         entry.snoozedelay=0
0256 
0257     def getcalendar(self,result):
0258         res={}
0259         # Read exceptions file first
0260         exceptions = self.getexceptions ()
0261         
0262         # Now read schedule
0263         try:
0264             buf=prototypes.buffer(self.getfilecontents(self.calendarlocation))
0265             if len(buf.getdata())<3:
0266                 # file is empty, and hence same as non-existent
0267                 raise com_brew.BrewNoSuchFileException()
0268             sc=self.protocolclass.schedulefile()
0269             sc.readfrombuffer(buf, logtitle="Calendar")
0270             for event in sc.events:
0271                 # the vx8100 has a bad entry when the calender is empty
0272                 # stop processing the calender when we hit this record
0273                 if event.pos==0: #invalid entry
0274                     continue
0275                 entry=bpcalendar.CalendarEntry()
0276                 try: # delete events are still in the calender file but have garbage dates
0277                     self.getcalendarcommon (entry, event)
0278                 except ValueError:
0279                     continue
0280                 if self.protocolclass.CALENDAR_HAS_SEPARATE_END_TIME_AND_DATE:
0281                     # MIC Unlike previous phones, the VX8300 passes the ringtone
0282                     # via both the index, and a path.  If the index is set to 100
0283                     # (0x64), then the ringtone information will be found in the
0284                     # "ringpath", the last 256 bytes of the calendar packet.  If
0285                     # the index is between 0 and 15, inclusive, then it is using
0286                     # one of the builtin ringers, and the ringpath is set to
0287                     # null.
0288                     try:
0289                         if (event.ringtone == 100):   # MIC Ringer is downloaded to phone or microSD
0290                             entry.ringtone = common.basename(event.ringpath)
0291                         else:                         # MIC Ringer is built-in
0292                             entry.ringtone=self.builtinringtones[event.ringtone]
0293                     except:
0294                         # hack, not having a phone makes it hard to figure out the best approach
0295                         if entry.alarm==None:
0296                             entry.ringtone='No Ring'
0297                         else:
0298                             entry.ringtone='Loud Beeps'
0299                 else:
0300                     entry.ringtone=result['ringtone-index'][event.ringtone]['name']
0301                 # check for exceptions and remove them
0302                 if event.repeat[3] and exceptions.has_key(event.pos):
0303                     for year, month, day in exceptions[event.pos]:
0304                         entry.suppress_repeat_entry(year, month, day)
0305                 res[entry.id]=entry
0306 
0307             assert sc.numactiveitems==len(res)
0308         except com_brew.BrewNoSuchFileException:
0309             pass # do nothing if file doesn't exist
0310         result['calendar']=res
0311         return result
0312 
0313     def makerepeat(self, repeat):
0314         # get all the variables out of the repeat tuple
0315         # and convert into a bpcalender RepeatEntry
0316         type,dow,interval,interval2,exceptions=repeat
0317         if type==0:
0318             repeat_entry=None
0319         else:
0320             repeat_entry=bpcalendar.RepeatEntry()
0321             if type==1: #daily
0322                 repeat_entry.repeat_type=repeat_entry.daily
0323                 repeat_entry.interval=interval
0324             elif type==5: #'monfri'
0325                 repeat_entry.repeat_type=repeat_entry.daily
0326                 repeat_entry.interval=0
0327             elif type==2: #'weekly'
0328                 repeat_entry.repeat_type=repeat_entry.weekly
0329                 repeat_entry.dow=dow
0330                 repeat_entry.interval=interval
0331             elif type==3: #'monthly'
0332                 repeat_entry.repeat_type=repeat_entry.monthly
0333                 repeat_entry.interval2=interval2
0334                 repeat_entry.dow=0
0335             elif type==6: #'monthly' #Xth Y day (e.g. 2nd friday each month)
0336                 repeat_entry.repeat_type=repeat_entry.monthly
0337                 repeat_entry.interval=interval #X
0338                 repeat_entry.dow=dow #Y
0339                 repeat_entry.interval2=interval2
0340             else: # =4 'yearly'
0341                 repeat_entry.repeat_type=repeat_entry.yearly
0342         return repeat_entry
0343 
0344     def _scheduleexceptions(self, entry, data, exceptionsf):
0345         exceptions=0
0346         if entry.repeat!=None:
0347             if self.protocolclass.CALENDAR_HAS_SEPARATE_END_TIME_AND_DATE:
0348                 if data.end_date[:3]==entry.no_end_date:
0349                     year_s,month_s,day_s,hour_s,minute_s=entry.start
0350                     data.end_date=(year_s + 5, month_s, day_s, 31, 63)
0351             else:
0352                 if data.end[:3]==entry.no_end_date:
0353                     data.end=(2100, 12, 31)+data.end[3:]
0354             for i in entry.repeat.suppressed:
0355                 de=self.protocolclass.scheduleexception()
0356                 de.pos=data.pos
0357                 de.day=i.date.day
0358                 de.month=i.date.month
0359                 de.year=i.date.year
0360                 exceptions=1
0361                 exceptionsf.items.append(de)
0362         if entry.repeat != None:
0363             data.repeat=(self.getrepeattype(entry, exceptions))
0364         else:
0365             data.repeat=((0,0,0,0,0))
0366 
0367     def _schedulecommon(self, entry, data):
0368         data.description=entry.desc_loc
0369         data.start=entry.start
0370         if self.protocolclass.CALENDAR_HAS_SEPARATE_END_TIME_AND_DATE:
0371             year_e,month_e,day_e,hour_e,minute_e=entry.end
0372             year_s,month_s,day_s,hour_s,minute_s=entry.start
0373             if entry.repeat!=None:           # MIC:  Added check for repeating event
0374                 data.end_date = (year_e,month_e,day_e,31,63)
0375             else:
0376                 data.end_date = (year_s+5,month_s,day_s,31,63)
0377             data.end_time = (year_s,month_s,day_s,hour_e,minute_e)
0378         else:
0379             data.end=entry.end
0380         data.ringtone=0
0381         data.unknown1=0
0382 
0383     def savecalendar(self, dict, merge):
0384         # ::TODO::
0385         # what will be written to the files
0386         eventsf=self.protocolclass.schedulefile()
0387         exceptionsf=self.protocolclass.scheduleexceptionfile()
0388         # what are we working with
0389         cal=dict['calendar']
0390         newcal={}
0391         #sort into start order, makes it possible to see if the calendar has changed
0392         keys=[(x.start, k) for k,x in cal.items()]
0393         keys.sort()
0394         # apply limiter
0395         keys=keys[:self.protocolclass.NUMCALENDARENTRIES]
0396         # number of entries
0397         eventsf.numactiveitems=len(keys)
0398         pos=1
0399         contains_alarms=False
0400         # play with each entry
0401         for (_,k) in keys:
0402             # entry is what we will return to user
0403             entry=cal[k]
0404             data=self.protocolclass.scheduleevent()
0405             data.pos=eventsf.packetsize()
0406             self._schedulecommon(entry, data)
0407             alarm_set=self.setalarm(entry, data)
0408             if alarm_set:
0409                 if entry.ringtone=="No Ring" and not entry.vibrate:
0410                     alarm_name="Low Beep Once"
0411                 else:
0412                     alarm_name=entry.ringtone
0413             else: # set alarm to "No Ring" gets rid of alarm icon on phone
0414                 alarm_name="No Ring"
0415             if self.protocolclass.CALENDAR_HAS_SEPARATE_END_TIME_AND_DATE:
0416                 # MIC VX8300 handles the ringtone differently than previous
0417                 # phones.  Except for the 16 builtin ringers, ringers stored
0418                 # directly on the phone or on the microSD are specified by
0419                 # their fully qualified path, and an index of 100.
0420                 for i in dict['ringtone-index']:
0421                     if dict['ringtone-index'][i]['name']==alarm_name:
0422                         if dict['ringtone-index'][i].get('filename', None):
0423                             data.ringtone=100
0424                             data.ringpath=dict['ringtone-index'][i]['filename']
0425                         else:
0426                             # builtin ringer
0427                             data.ringtone=i      # Set to proper index
0428                         break
0429             else:
0430                 data.unknown2=0        # MIC Only unknown in VX8100 and prior
0431                                        # Moved up from line below, as we do not
0432                                        # want it defined for the VX8300+
0433                 for i in dict['ringtone-index']:
0434                     if dict['ringtone-index'][i]['name']==alarm_name:
0435                         data.ringtone=i
0436                         break
0437             # check for exceptions and add them to the exceptions list
0438             self._scheduleexceptions(entry, data, exceptionsf)
0439             if data.alarmindex_vibrate!=1: # if alarm set
0440                 contains_alarms=True
0441             # put entry in nice shiny new dict we are building
0442             entry=copy.copy(entry)
0443             newcal[data.pos]=entry
0444             eventsf.events.append(data)
0445 
0446         buf=prototypes.buffer()
0447         eventsf.writetobuffer(buf, logtitle="New Calendar")
0448         self.writefile(self.calendarlocation, buf.getvalue())
0449         if contains_alarms or self.calenderrequiresreboot:
0450             self.log("Your phone has to be rebooted due to the calendar changing")
0451             dict["rebootphone"]=True
0452 
0453         buf=prototypes.buffer()
0454         exceptionsf.writetobuffer(buf, logtitle="Writing calendar exceptions")
0455         self.writefile(self.calendarexceptionlocation, buf.getvalue())
0456 
0457         # fix passed in dict
0458         dict['calendar']=newcal
0459 
0460         return dict
0461 
0462     def getrepeattype(self, entry, exceptions):
0463         #convert the bpcalender type into vx8100 type
0464         repeat_entry=bpcalendar.RepeatEntry()
0465         interval2=0
0466         if entry.repeat.repeat_type==repeat_entry.monthly:
0467             dow=entry.repeat.dow
0468             interval2=entry.repeat.interval2
0469             if entry.repeat.dow==0:
0470                 # set interval for month type 4 to start day of month, (required by vx8100)
0471                 interval=entry.start[2]
0472                 type=3
0473             else:
0474                 interval=entry.repeat.interval
0475                 type=6
0476         elif entry.repeat.repeat_type==repeat_entry.daily:
0477             dow=entry.repeat.dow
0478             interval=entry.repeat.interval
0479             if entry.repeat.interval==0:
0480                 type=5
0481             else:
0482                 type=1
0483         elif entry.repeat.repeat_type==repeat_entry.weekly:
0484             dow=entry.repeat.dow
0485             interval=entry.repeat.interval
0486             type=2
0487         elif entry.repeat.repeat_type==repeat_entry.yearly:
0488             # set interval to start day of month, (required by vx8100)
0489             interval=entry.start[2]
0490             # set dow to start month, (required by vx8100)
0491             dow=entry.start[1]
0492             type=4
0493         return (type, dow, interval, interval2, exceptions)
0494 
0495     def setalarm(self, entry, data):
0496         # vx8100 only allows certain repeat intervals, adjust to fit, it also stores an index to the interval
0497         rc=True
0498         if entry.alarm>=2880:
0499             entry.alarm=2880
0500             data.alarmminutes=0
0501             data.alarmhours=48
0502             data.alarmindex_vibrate=0x10
0503         elif entry.alarm>=1440:
0504             entry.alarm=1440
0505             data.alarmminutes=0
0506             data.alarmhours=24
0507             data.alarmindex_vibrate=0xe
0508         elif entry.alarm>=120:
0509             entry.alarm=120
0510             data.alarmminutes=0
0511             data.alarmhours=2
0512             data.alarmindex_vibrate=0xc
0513         elif entry.alarm>=60:
0514             entry.alarm=60
0515             data.alarmminutes=0
0516             data.alarmhours=1
0517             data.alarmindex_vibrate=0xa
0518         elif entry.alarm>=15:
0519             entry.alarm=15
0520             data.alarmminutes=15
0521             data.alarmhours=0
0522             data.alarmindex_vibrate=0x8
0523         elif entry.alarm>=10:
0524             entry.alarm=10
0525             data.alarmminutes=10
0526             data.alarmhours=0
0527             data.alarmindex_vibrate=0x6
0528         elif entry.alarm>=5:
0529             entry.alarm=5
0530             data.alarmminutes=5
0531             data.alarmhours=0
0532             data.alarmindex_vibrate=0x4
0533         elif entry.alarm>=0:
0534             entry.alarm=0
0535             data.alarmminutes=0
0536             data.alarmhours=0
0537             data.alarmindex_vibrate=0x2
0538         else: # no alarm
0539             data.alarmminutes=0x64
0540             data.alarmhours=0x64
0541             data.alarmindex_vibrate=1
0542             rc=False
0543 
0544         # set the vibrate bit
0545         if data.alarmindex_vibrate > 1 and entry.vibrate==0:
0546             data.alarmindex_vibrate+=1
0547         return rc
0548 
0549     my_model='VX8100'
0550 
0551     def getphoneinfo(self, phone_info):
0552         self.log('Getting Phone Info')
0553         try:
0554             s=self.getfilecontents('brew/version.txt')
0555             if s[:6]==self.my_model:
0556                 phone_info.append('Model:', self.my_model)
0557                 phone_info.append('ESN:', self.get_brew_esn())
0558                 req=p_brew.firmwarerequest()
0559                 res=self.sendbrewcommand(req, self.protocolclass.firmwareresponse)
0560                 phone_info.append('Firmware Version:', res.firmware)
0561                 txt=self.getfilecontents("nvm/nvm/nvm_cdma")[180:190]
0562                 phone_info.append('Phone Number:', txt)
0563         except:
0564             pass
0565         return
0566 
0567     # Media stuff---------------------------------------------------------------
0568     def _is_rs_file(self, filename):
0569         return filename.startswith(self._rs_path)
0570 
0571     def getmedia(self, maps, results, key):
0572         origins={}
0573         # signal that we are using the new media storage that includes the origin and timestamp
0574         origins['new_media_version']=1
0575 
0576         for type, indexfile, sizefile, directory, lowestindex, maxentries, typemajor, def_icon, idx_ofs  in maps:
0577             media={}
0578             for item in self.getindex(indexfile):
0579                 data=None
0580                 timestamp=None
0581                 try:
0582                     stat_res=self.statfile(item.filename)
0583                     if stat_res!=None:
0584                         timestamp=stat_res['date'][0]
0585                     if not self._is_rs_file(item.filename):
0586                         data=self.getfilecontents(item.filename, True)
0587                 except (com_brew.BrewNoSuchFileException,com_brew.BrewBadPathnameException,com_brew.BrewNameTooLongException):
0588                     self.log("It was in the index, but not on the filesystem")
0589                 except com_brew.BrewAccessDeniedException:
0590                     # firmware wouldn't let us read this file, just mark it then
0591                     self.log('Failed to read file: '+item.filename)
0592                     data=''
0593                 if data!=None:
0594                     media[common.basename(item.filename)]={ 'data': data, 'timestamp': timestamp}
0595             origins[type]=media
0596 
0597         results[key]=origins
0598         return results
0599 
0600     def _mark_files(self, local_files, rs_files, local_dir):
0601         # create empty local files as markers for remote files
0602         _empty_files=[common.basename(x) for x,_entry in local_files.items() \
0603                       if not _entry['size']]
0604         _remote_files=[common.basename(x) for x in rs_files]
0605         for _file in _remote_files:
0606             if _file not in _empty_files:
0607                 # mark this one
0608                 self.writefile(local_dir+'/'+_file, '')
0609         for _file in _empty_files:
0610             if _file not in _remote_files:
0611                 # remote file no longer exists, del the marker
0612                 self.rmfile(local_dir+'/'+_file)
0613 
0614     def _write_index_file(self, type):
0615         _info=self.media_info.get(type, None)
0616         if not _info:
0617             return
0618         _files={}
0619         _local_dir=_info['localpath']
0620         _rs_dir=_info['rspath']
0621         _vtype=_info['vtype']
0622         _icon=_info['icon']
0623         _index=_info['index']
0624         _maxsize=_info['maxsize']
0625         _dunno=_info['dunno']
0626         indexfile=_info['indexfile']
0627         sizefile=_info['sizefile']
0628         _need_date=_info['date']
0629         try:
0630             _files=self.listfiles(_local_dir)
0631         except (com_brew.BrewNoSuchDirectoryException,
0632                 com_brew.BrewBadPathnameException):
0633             pass
0634         try:
0635             if _rs_dir:
0636                 _rs_files=self.listfiles(_rs_dir)
0637                 if type=='ringers':
0638                     self._mark_files(_files, _rs_files, _local_dir)
0639                 _files.update(_rs_files)
0640         except (com_brew.BrewNoSuchDirectoryException,
0641                 com_brew.BrewBadPathnameException):
0642             # dir does not exist, no media files available
0643             pass
0644         # del all the markers (empty files) ringers
0645         if type=='ringers':
0646             _keys=_files.keys()
0647             for _key in _keys:
0648                 if not _files[_key]['size']:
0649                     del _files[_key]
0650         # dict of all indices
0651         _idx_keys={}
0652         for _i in xrange(_index, _index+_maxsize):
0653             _idx_keys[_i]=True
0654         # assign existing indices
0655         for _item in self.getindex(indexfile):
0656             if _files.has_key(_item.filename):
0657                 _files[_item.filename]['index']=_item.index
0658                 _idx_keys[_item.index]=False
0659         # available new indices
0660         _idx_keys_list=[k for k,x in _idx_keys.items() if x]
0661         _idx_keys_list.sort()
0662         _idx_cnt=0
0663         # assign new indices
0664         _file_list=[x for x in _files if not _files[x].get('index', None)]
0665         _file_list.sort()
0666         if len(_file_list)>len(_idx_keys_list):
0667             _file_list=_file_list[:len(_idx_keys_list)]
0668         for i in _file_list:
0669             _files[i]['index']=_idx_keys_list[_idx_cnt]
0670             _idx_cnt+=1
0671         # (index, file name) list for writing
0672         _res_list=[(x['index'],k) for k,x in _files.items() if x.get('index', None)]
0673         _res_list.sort()
0674         _res_list.reverse()
0675         # writing the index file
0676         ifile=self.protocolclass.indexfile()
0677         _file_size=0
0678         for index,idx in _res_list:
0679             _fs_size=_files[idx]['size']
0680             ie=self.protocolclass.indexentry()
0681             ie.index=index
0682             ie.type=_vtype
0683             ie.filename=idx
0684             if _need_date:
0685                 # need to fill in the date value
0686                 _stat=self.statfile(_files[idx]['name'])
0687                 if _stat:
0688                     ie.date=_stat['datevalue']-time.timezone
0689             ie.dunno=_dunno
0690             ie.icon=_icon
0691             ie.size=_fs_size
0692             ifile.items.append(ie)
0693             if not self._is_rs_file(idx):
0694                 _file_size+=_fs_size
0695         buf=prototypes.buffer()
0696         ifile.writetobuffer(buf, logtitle="Index file "+indexfile)
0697         self.log("Writing index file "+indexfile+" for type "+type+" with "+`len(_res_list)`+" entries.")
0698         self.writefile(indexfile, buf.getvalue())
0699         # writing the size file
0700         if sizefile:
0701             szfile=self.protocolclass.sizefile()
0702             szfile.size=_file_size
0703             buf=prototypes.buffer()
0704             szfile.writetobuffer(buf, logtitle="Updated size file for "+type)
0705             self.log("You are using a total of "+`_file_size`+" bytes for "+type)
0706             self.writefile(sizefile, buf.getvalue())
0707 
0708     def savemedia(self, mediakey, mediaindexkey, maps, results, merge, reindexfunction):
0709         """Actually saves out the media
0710 
0711         @param mediakey: key of the media (eg 'wallpapers' or 'ringtones')
0712         @param mediaindexkey:  index key (eg 'wallpaper-index')
0713         @param maps: list index files and locations
0714         @param results: results dict
0715         @param merge: are we merging or overwriting what is there?
0716         @param reindexfunction: the media is re-indexed at the end.  this function is called to do it
0717         """
0718 
0719         # take copies of the lists as we modify them
0720         wp=results[mediakey].copy()  # the media we want to save
0721         wpi=results[mediaindexkey].copy() # what is already in the index files
0722 
0723         # remove builtins
0724         for k in wpi.keys():
0725             if wpi[k].get('origin', "")=='builtin':
0726                 del wpi[k]
0727 
0728         # build up list into init
0729         init={}
0730         for type,_,_,_,lowestindex,_,typemajor,_,_ in maps:
0731             init[type]={}
0732             for k in wpi.keys():
0733                 if wpi[k]['origin']==type:
0734                     index=k
0735                     name=wpi[k]['name']
0736                     fullname=wpi[k]['filename']
0737                     vtype=wpi[k]['vtype']
0738                     icon=wpi[k]['icon']
0739                     data=None
0740                     del wpi[k]
0741                     for w in wp.keys():
0742                         # does wp contain a reference to this same item?
0743                         if wp[w]['name']==name and wp[w]['origin']==type:
0744                             data=wp[w]['data']
0745                             del wp[w]
0746                     if not merge and data is None:
0747                         # delete the entry
0748                         continue
0749 ##                    assert index>=lowestindex
0750                     init[type][index]={'name': name, 'data': data, 'filename': fullname, 'vtype': vtype, 'icon': icon}
0751 
0752         # init now contains everything from wallpaper-index
0753         # wp contains items that we still need to add, and weren't in the existing index
0754         assert len(wpi)==0
0755         print init.keys()
0756 
0757         # now look through wallpapers and see if anything was assigned a particular
0758         # origin
0759         for w in wp.keys():
0760             o=wp[w].get("origin", "")
0761             if o is not None and len(o) and o in init:
0762                 idx=-1
0763                 while idx in init[o]:
0764                     idx-=1
0765                 init[o][idx]=wp[w]
0766                 del wp[w]
0767 
0768         # wp will now consist of items that weren't assigned any particular place
0769         # so put them in the first available space
0770         for type,_,_,_,lowestindex,maxentries,typemajor,def_icon,_ in maps:
0771             # fill it up
0772             for w in wp.keys():
0773                 if len(init[type])>=maxentries:
0774                     break
0775                 idx=-1
0776                 while idx in init[type]:
0777                     idx-=1
0778                 init[type][idx]=wp[w]
0779                 del wp[w]
0780 
0781         # time to write the files out
0782         for type, indexfile, sizefile, directory, lowestindex, maxentries,typemajor,def_icon,_  in maps:
0783             # get the index file so we can work out what to delete
0784             names=[init[type][x]['name'] for x in init[type]]
0785             for item in self.getindex(indexfile):
0786                 if common.basename(item.filename) not in names and \
0787                    not self._is_rs_file(item.filename):
0788                     self.log(item.filename+" is being deleted")
0789                     try:
0790                         self.rmfile(item.filename)
0791                     except (com_brew.BrewNoSuchDirectoryException,
0792                             com_brew.BrewBadPathnameException,
0793                             com_brew.BrewNoSuchFileException):
0794                         pass
0795                     except:
0796                         if __debug__:
0797                             raise
0798             # fixup the indices
0799             fixups=[k for k in init[type].keys() if k<lowestindex]
0800             fixups.sort()
0801             for f in fixups:
0802                 for ii in xrange(lowestindex, lowestindex+maxentries):
0803                     # allocate an index
0804                     if ii not in init[type]:
0805                         init[type][ii]=init[type][f]
0806                         del init[type][f]
0807                         break
0808             # any left over?
0809             fixups=[k for k in init[type].keys() if k<lowestindex]
0810             for f in fixups:
0811                 self.log("There is no space in the index for "+type+" for "+init[type][f]['name'])
0812                 del init[type][f]
0813             # write each entry out
0814             for idx in init[type].keys():
0815                 entry=init[type][idx]
0816                 filename=entry.get('filename', directory+"/"+entry['name'])
0817                 entry['filename']=filename
0818                 fstat=self.statfile(filename)
0819                 if 'data' not in entry:
0820                     # must be in the filesystem already
0821                     if fstat is None:
0822                         self.log("Entry "+entry['name']+" is in index "+indexfile+" but there is no data for it and it isn't in the filesystem.  The index entry will be removed.")
0823                         del init[type][idx]
0824                         continue
0825                 # check len(data) against fstat->length
0826                 data=entry['data']
0827                 if data is None:
0828                     assert merge
0829                     continue # we are doing an add and don't have data for this existing entry
0830                 elif not data:
0831                     self.log('Not writing file: '+filename)
0832                     continue # data is blank, could be a result of not being able to do a previous get
0833                 if fstat is not None and len(data)==fstat['size']:
0834                     self.log("Not writing "+filename+" as a file of the same name and length already exists.")
0835                 else:
0836                     self.writefile(filename, data)
0837             # write out index
0838             self._write_index_file(type)
0839         return reindexfunction(results)
0840 
0841 #-------------------------------------------------------------------------------
0842 parentprofile=com_lgvx7000.Profile
0843 class Profile(parentprofile):
0844     protocolclass=Phone.protocolclass
0845     serialsname=Phone.serialsname
0846 
0847     BP_Calendar_Version=3
0848     phone_manufacturer='LG Electronics Inc'
0849     phone_model='VX8100'
0850 
0851     WALLPAPER_WIDTH=176
0852     WALLPAPER_HEIGHT=184
0853     MAX_WALLPAPER_BASENAME_LENGTH=32
0854     WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()_ .-"
0855     WALLPAPER_CONVERT_FORMAT="jpg"
0856 
0857     # the 8100 uses "W" for wait in the dialstring, it does not support "T"
0858     DIALSTRING_CHARS="[^0-9PW#*]"
0859 
0860     MAX_RINGTONE_BASENAME_LENGTH=32
0861     RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()_ .-"
0862 
0863     # there is an origin named 'aod' - no idea what it is for except maybe
0864     # 'all other downloads'
0865 
0866     # the vx8100 supports bluetooth for connectivity to the PC, define the "bluetooth_mgd_id"
0867     # to enable bluetooth discovery during phone detection
0868     # the bluetooth address starts with LG's the three-octet OUI, all LG phone
0869     # addresses start with this, it provides a way to identify LG bluetooth devices
0870     # during phone discovery
0871     # OUI=Organizationally Unique Identifier
0872     # see http://standards.ieee.org/regauth/oui/index.shtml for more info
0873     bluetooth_mfg_id="001256"
0874 
0875     ringtoneorigins=('ringers', 'sounds')
0876     excluded_ringtone_origins=('sounds')
0877     excluded_wallpaper_origins=('video')
0878 
0879 
0880     # the 8100 doesn't have seperate origins - they are all dumped in "images"
0881     imageorigins={}
0882     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
0883     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "video"))
0884     def GetImageOrigins(self):
0885         return self.imageorigins
0886 
0887     # our targets are the same for all origins
0888     imagetargets={}
0889     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
0890                                       {'width': 176, 'height': 184, 'format': "JPEG"}))
0891     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "outsidelcd",
0892                                       {'width': 128, 'height': 112, 'format': "JPEG"}))
0893 
0894     def GetTargetsForImageOrigin(self, origin):
0895         return self.imagetargets
0896 
0897 
0898     def __init__(self):
0899         parentprofile.__init__(self)
0900 
0901     _supportedsyncs=(
0902         ('phonebook', 'read', None),   # all phonebook reading
0903         ('calendar', 'read', None),    # all calendar reading
0904         ('wallpaper', 'read', None),   # all wallpaper reading
0905         ('ringtone', 'read', None),    # all ringtone reading
0906         ('call_history', 'read', None),# all call history list reading
0907         ('sms', 'read', None),         # all SMS list reading
0908         ('memo', 'read', None),        # all memo list reading
0909         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
0910         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
0911         ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
0912         ('wallpaper', 'write', 'OVERWRITE'),
0913         ('ringtone', 'write', 'MERGE'),       # merge and overwrite ringtone
0914         ('ringtone', 'write', 'OVERWRITE'),
0915         ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
0916         ('memo', 'write', 'OVERWRITE'),       # all memo list writing
0917         )
0918 
0919     field_color_data={
0920         'phonebook': {
0921             'name': {
0922                 'first': 1, 'middle': 1, 'last': 1, 'full': 1,
0923                 'nickname': 0, 'details': 1 },
0924             'number': {
0925                 'type': 5, 'speeddial': 5, 'number': 5, 'details': 5 },
0926             'email': 2,
0927             'address': {
0928                 'type': 0, 'company': 0, 'street': 0, 'street2': 0,
0929                 'city': 0, 'state': 0, 'postalcode': 0, 'country': 0,
0930                 'details': 0 },
0931             'url': 0,
0932             'memo': 1,
0933             'category': 1,
0934             'wallpaper': 1,
0935             'ringtone': 2,
0936             'storage': 0,
0937             },
0938         'calendar': {
0939             'description': True, 'location': False, 'allday': True,
0940             'start': True, 'end': True, 'priority': False,
0941             'alarm': True, 'vibrate': True,
0942             'repeat': True,
0943             'memo': False,
0944             'category': False,
0945             'wallpaper': False,
0946             'ringtone': True,
0947             },
0948         'memo': {
0949             'subject': True,
0950             'date': True,
0951             'secret': False,
0952             'category': False,
0953             'memo': True,
0954             },
0955         'todo': {
0956             'summary': False,
0957             'status': False,
0958             'due_date': False,
0959             'percent_complete': False,
0960             'completion_date': False,
0961             'private': False,
0962             'priority': False,
0963             'category': False,
0964             'memo': False,
0965             },
0966         }
0967 

Generated by PyXR 0.9.4