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