PyXR

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



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2007 Stephen Wood <sawecw@users.sf.net>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: com_sanyo.py 4618 2008-06-19 03:55:00Z sawecw $
0009 
0010 """Phonebook conversations with Sanyo phones"""
0011 
0012 # standard modules
0013 import re
0014 import time
0015 import cStringIO
0016 import sha
0017 import datetime
0018 
0019 # BitPim modules
0020 import com_brew
0021 import com_phone
0022 import p_sanyo
0023 import prototypes
0024 import common
0025 import conversions
0026 import bpcalendar
0027 import call_history
0028 import sms
0029 import todo
0030 import helpids
0031 
0032 numbertypetab=( 'home', 'office', 'cell', 'pager',
0033                     'data', 'fax', 'none' )
0034 
0035 class SanyoCommandException(Exception):
0036     def __init__(self, errnum, str=None):
0037         if str is None:
0038             str="Sanyo Packet Error 0x%02x" % (errnum,)
0039         Exception.__init__(self, str)
0040         self.errnum=errnum
0041 
0042 class SanyoPhonebook:
0043     "Talk to a Sanyo Sprint Phone such as SCP-4900, SCP-5300, or SCP-8100"
0044     
0045     # phone uses Jan 1, 1980 as epoch.  Python uses Jan 1, 1970.  This is difference
0046     # plus a fudge factor of 5 days for no reason I can find
0047     _sanyoepochtounix=315532800+432000;
0048 
0049     pbterminator="\x7e"
0050     MODEPHONEBOOK="modephonebook" # can speak the phonebook protocol
0051 
0052     calendar_defaultringtone=0
0053     calendar_defaultcaringtone=0
0054     calendar_voicenumber=0
0055     phonebook_voicenumber=0
0056 
0057     def __init__(self):
0058         self.numbertypetab=numbertypetab
0059     
0060     def get_esn(self):
0061         req=self.protocolclass.esnrequest()
0062         res=self.sendpbcommand(req, self.protocolclass.esnresponse)
0063         return '%08X'%res.esn
0064 
0065     def _setmodephonebook(self):
0066         self.setmode(self.MODEBREW)
0067         req=p_sanyo.firmwarerequest()
0068         respc=p_sanyo.firmwareresponse
0069         try:
0070             self.sendpbcommand(req, respc, callsetmode=False)
0071             return 1
0072         except com_phone.modeignoreerrortypes:
0073             pass
0074         try:
0075             self.comm.setbaudrate(38400)
0076             self.sendpbcommand(req, respc, callsetmode=False)
0077             return 1
0078         except com_phone.modeignoreerrortypes:
0079             pass
0080         return 0
0081         
0082     def getmediaindices(self, results):
0083         """Get all the media indices
0084 
0085         @param results: places results in this dict
0086         """
0087         # Do all media types in one function to avoid having to scan through
0088         # directories more than once
0089         ringermedia={}
0090         imagemedia={}
0091 
0092         c=1
0093         for name in self.builtinimages:
0094             if name:
0095                 imagemedia[c]={'name': name, 'origin': 'builtin' }
0096                 # print c,name
0097             c+=1
0098         results['wallpaper-index']=imagemedia
0099         c=1
0100         for name in self.builtinringtones:
0101             if name:
0102                 ringermedia[c]={'name': name, 'origin': 'builtin' }
0103                 # print c,name
0104             c+=1
0105         results['ringtone-index']=ringermedia
0106 
0107         return
0108 
0109         wallpaper = self.getsanyobuffer(self.protocolclass.wallpaperbuffer)
0110         for i in range(0, self.protocolclass._NUMPBSLOTS):
0111             if wallpaper.wallpapers[i].flag:
0112                 print i, wallpaper.wallpapers[i].word1, wallpaper.wallpapers[i].word2
0113 
0114         req=self.protocolclass.bufferpartrequest()
0115 
0116         req.header.command=0xc8
0117         req.header.packettype=0x0c
0118         res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0119         #req.header.command=0x69
0120         #req.header.packettype=0x0f
0121         #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0122         #req.header.command=0x6a
0123         #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0124         #req.header.command=0x6b
0125         #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0126         
0127         req.header.packettype=0x0c
0128         req.header.command=0x49
0129         res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0130         req.header.command=0xc8
0131         res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0132 
0133         if not self.serialsname == 'scp4900':
0134             # 4900 doesn't like this
0135             req.header.command=0xc9
0136             res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse)
0137 
0138         return
0139         
0140     def getmediaindex(self, builtins, maps, results, key):
0141         """Gets the media (wallpaper/ringtone) index
0142 
0143         @param builtins: the builtin list on the phone
0144         @param results: places results in this dict
0145         @param maps: the list of index files and locations
0146         @param key: key to place results in
0147         """
0148 
0149         self.log("Reading "+key)
0150         media={}
0151 
0152         # builtins
0153         c=1
0154         for name in builtins:
0155             if name:
0156                 media[c]={'name': name, 'origin': 'builtin' }
0157                 # print c,name
0158             c+=1
0159 
0160         results[key]=media
0161         return media
0162 
0163     def getwallpaperindices(self, results):
0164         return self.getmediaindex(self.builtinimages, self.imagelocations, results, 'wallpaper-index')
0165 
0166     def getringtoneindices(self, results):
0167         return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index')
0168 
0169     def getsanyobuffer(self, responseclass):
0170         # Read buffer parts and concatenate them together
0171         bufres=responseclass()
0172         desc="Reading "+bufres.comment
0173         data=cStringIO.StringIO()
0174         bufp=0
0175         command=bufres.startcommand
0176         buffersize=bufres.bufsize
0177         req=self.protocolclass.bufferpartrequest()
0178         if command==0xd7:
0179             req.header.packettype=0x0c # Special Case for 5500 Class phones
0180         for offset in range(0, buffersize, req.bufpartsize):
0181             self.progress(data.tell(), buffersize, desc)
0182             req.header.command=command
0183             res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse);
0184             data.write(res.data)
0185             command+=1
0186 
0187         self.progress(1,1,desc)
0188 
0189         data=data.getvalue()
0190         self.log("expected size "+`buffersize`+"  actual "+`len(data)`)
0191         assert buffersize==len(data)
0192         buffer=prototypes.buffer(data)
0193         bufres.readfrombuffer(buffer, logtitle="Sanyo buffer response")
0194         return bufres
0195 
0196     def sendsanyobuffer(self, bufreq):
0197         comment=bufreq.comment
0198         buffer=prototypes.buffer()
0199         bufreq.writetobuffer(buffer, logtitle="Send sanyo buffer")
0200         buffer=buffer.getvalue()
0201         self.log("Writing "+comment+" "+` len(buffer) `+" bytes")
0202         desc="Writing "+comment
0203         req=self.protocolclass.bufferpartupdaterequest()
0204         bufpartsize=req.bufpartsize
0205         numblocks=len(buffer)/bufpartsize
0206         offset=0
0207         command=bufreq.startcommand
0208         if command==0xd7:
0209             req.header.packettype=0x0c
0210         for offset in range(0, len(buffer), bufpartsize):
0211             self.progress(offset/bufpartsize, numblocks, desc)
0212             req.header.command=command
0213             block=buffer[offset:]
0214             l=min(len(block), bufpartsize)
0215             block=block[:l]
0216             req.data=block
0217             command+=1
0218             self.sendpbcommand(req, self.protocolclass.bufferpartresponse, writemode=True)
0219         
0220     def sendpbcommand(self, request, responseclass, callsetmode=True, writemode=False, numsendretry=0, returnerror=False):
0221         if writemode:
0222             numretry=3
0223         else:
0224             numretry=0
0225             
0226         if callsetmode:
0227             self.setmode(self.MODEPHONEBOOK)
0228         buffer=prototypes.buffer()
0229 
0230         request.writetobuffer(buffer, logtitle="Sanyo phonebook request")
0231         data=buffer.getvalue()
0232         firsttwo=data[:2]
0233         data=common.pppescape(data+common.crcs(data))+common.pppterminator
0234         isendretry=numsendretry
0235         while isendretry>=0:
0236             try:
0237                 rdata=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False, numfailures=numretry)
0238                 break
0239             except com_phone.modeignoreerrortypes:
0240                 if isendretry>0:
0241                     self.log("Resending request packet...")
0242                     time.sleep(0.3)
0243                 else:
0244                     self.comm.success=False
0245                     self.mode=self.MODENONE
0246                     self.raisecommsdnaexception("manipulating the phonebook")
0247                 isendretry-=1
0248 
0249         self.comm.success=True
0250 
0251         origdata=rdata
0252         # sometimes there is junk at the beginning, eg if the user
0253         # turned off the phone and back on again.  So if there is more
0254         # than one 7e in the escaped data we should start after the
0255         # second to last one
0256         d=rdata.rfind(common.pppterminator,0,-1)
0257         if d>=0:
0258             self.log("Multiple Sanyo packets in data - taking last one starting at "+`d+1`)
0259             self.logdata("Original Sanyo data", origdata, None)
0260             rdata=rdata[d+1:]
0261 
0262         # turn it back to normal
0263         data=common.pppunescape(rdata)
0264 
0265         # Sometimes there is other crap at the beginning.  But it might
0266         # be a Sanyo error byte.  So strip off bytes from the beginning
0267         # until the crc agrees, or we get to the first two bytes of the
0268         # request packet.
0269         d=data.find(firsttwo)
0270         crc=data[-3:-1]
0271         crcok=False
0272         for i in range(0,d+1):
0273             trydata=data[i:-3]
0274             if common.crcs(trydata)==crc:
0275                 crcok=True
0276                 break
0277 
0278         if not crcok:
0279             self.logdata("first two",firsttwo, None)
0280             self.logdata("Original Sanyo data", origdata, None)
0281             self.logdata("Working on Sanyo data", data, None)
0282             raise common.CommsDataCorruption("Sanyo packet failed CRC check", self.desc)
0283 
0284         res=responseclass()
0285         if d>0:
0286             if d==i:
0287                 self.log("Junk at beginning of Sanyo packet, data at "+`d`)
0288                 self.logdata("Original Sanyo data", origdata, None)
0289                 self.logdata("Working on Sanyo data", data, None)
0290             else:
0291                 if returnerror:
0292                     res=self.protocolclass.sanyoerror()
0293                 else:
0294                     self.log("Sanyo Error code "+`ord(data[0])`)
0295                     self.logdata("sanyo phonebook response", data, None)
0296                     raise SanyoCommandException(ord(data[0]))
0297             
0298         data=trydata
0299 
0300         # parse data
0301         buffer=prototypes.buffer(data)
0302         res.readfrombuffer(buffer, logtitle="sanyo phonebook response")
0303         return res
0304 
0305     def getfundamentals(self, results):
0306         """Gets information fundamental to interopating with the phone and UI."""
0307 
0308         # use a hash of ESN and other stuff (being paranoid)
0309         print "HASRINGPICBUF=",self.protocolclass.HASRINGPICBUF
0310         self.log("Retrieving fundamental phone information")
0311         self.log("Phone serial number")
0312         results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest()
0313 
0314         #self.getwallpaperindices(results)
0315         #self.getringtoneindices(results)
0316         self.getmediaindices(results)
0317         self.log("Fundamentals retrieved")
0318         return results
0319 
0320     def sanyosort(self, a, b):
0321         "Sanyo sort order.  Case insensitive, letters first"
0322         x=a[1]
0323         y=b[1]
0324         # This is right only for first column
0325         if(x[0:1].isalpha() and not y[0:1].isalpha()):
0326             return -1
0327         if(y[0:1].isalpha() and not x[0:1].isalpha()):
0328             return 1
0329         return cmp(x.lower(), y.lower())
0330     
0331     def getsms(self, result):
0332         gsms = {}
0333         self.log("Getting sms entries")
0334         req=self.protocolclass.messagerequest()
0335         for box in range(2):
0336             if box==0:
0337                 req=self.protocolclass.messagerequest()
0338                 respc=self.protocolclass.messageresponse
0339             else:
0340                 req=self.protocolclass.messagesentrequest()
0341                 respc=self.protocolclass.messagesentresponse
0342             for slot in range(self.protocolclass.NUMMESSAGESLOTS):
0343                 req.slot=slot
0344                 res=self.sendpbcommand(req, respc)
0345                 if res.entry.dunno4==2:
0346                     entry=sms.SMSEntry()
0347                     entry.datetime="200%d%02d%02dT%02d%02d%02d" % ((res.entry.year, res.entry.month, res.entry.day, res.entry.hour,res.entry.minute, res.entry.second))
0348                     if box==0:
0349                         entry.folder=entry.Folder_Inbox
0350                         entry._from=res.entry.phonenum
0351                     else:
0352                         entry.folder=entry.Folder_Sent
0353                         entry._to=res.entry.phonenum
0354                         #entry.add_recipient(res.entry.phonenum,confirmed=True)
0355                     if res.entry.read==17:
0356                         entry.read=1
0357                     else:
0358                         entry.read=0
0359                     entry.callback=res.entry.callback
0360                     entry.subject=res.entry.message[:12]
0361                     self.log(res.entry.message[:8])
0362                     if res.entry.priority==100:
0363                         entry.priority=sms.SMSEntry.Priority_Normal
0364                     if res.entry.priority==200:
0365                         entry.priority=sms.SMSEntry.Priority_High
0366                     entry.text=res.entry.message
0367                     gsms[entry.id]=entry
0368                     result['sms']=gsms
0369         return result
0370 
0371     def gettodo(self, result):
0372         gtodo = {}
0373         self.log("Getting todo entries")
0374         req=self.protocolclass.todorequest()
0375         for slot in range(self.protocolclass.NUMTODOSLOTS):
0376             req.slot=slot
0377             res=self.sendpbcommand(req, self.protocolclass.todoresponse)
0378             entry=todo.TodoEntry()
0379             if res.entry.flag:
0380                 entry.summary=res.entry.todo
0381                 if res.entry.priority==2:
0382                     entry.status=4
0383                 if res.entry.priority==0:
0384                     entry.priority=5
0385                 if res.entry.priority==1:
0386                     entry.priority=1
0387                 gtodo[entry.id]=entry
0388                 result['todo']=gtodo
0389         return result
0390 
0391     def getphonebook(self,result):
0392         pbook={}
0393         # Get Sort buffer so we know which of the 300 phone book slots
0394         # are in use.
0395         sortstuff = self.getsanyobuffer(self.protocolclass.pbsortbuffer)
0396 
0397         # Get the ringer and wall paper assignments
0398         if self.protocolclass.HASRINGPICBUF:
0399             ringpic = self.getsanyobuffer(self.protocolclass.ringerpicbuffer)
0400 
0401         speedslot=[]
0402         speedtype=[]
0403         for i in range(self.protocolclass._NUMSPEEDDIALS):
0404             speedslot.append(sortstuff.speeddialindex[i].pbslotandtype & 0xfff)
0405             numtype=(sortstuff.speeddialindex[i].pbslotandtype>>12)-1
0406             if(numtype >= 0 and numtype <= len(self.numbertypetab)):
0407                 speedtype.append(self.numbertypetab[numtype])
0408             else:
0409                 speedtype.append("")
0410 
0411         numentries=sortstuff.slotsused
0412         self.log("There are %d entries" % (numentries,))
0413         
0414         count = 0 # Number of phonebook entries
0415         numcount = 0 # Number of phone numbers
0416         numemail = 0 # Number of emails
0417         numurl = 0 # Number of urls
0418         res=self.protocolclass.phonebookentry()
0419         usedefaultnum = 'defaultnum' in res.getfields()
0420         print "usedefaultnum =",usedefaultnum
0421 
0422         req=self.protocolclass.phonebookslotrequest()
0423         for i in range(0, self.protocolclass._NUMPBSLOTS):
0424             if sortstuff.usedflags[i].used:
0425                 ### Read current entry
0426                 req.slot = i
0427                 res=self.sendpbcommand(req, self.protocolclass.phonebookslotresponse)
0428                 self.log("Read entry "+`i`+" - "+res.entry.name)
0429 
0430                 entry=self.extractphonebookentry(res.entry, result)
0431                 # Speed dials
0432                 for j in range(len(speedslot)):
0433                     if(speedslot[j]==req.slot):
0434                         for k in range(len(entry['numbers'])):
0435                             if(entry['numbers'][k]['type']==speedtype[j]):
0436                                 entry['numbers'][k]['speeddial']=j+2
0437                                 break
0438 
0439                 if self.protocolclass.HASRINGPICBUF:
0440                     # ringtones
0441                     if ringpic.ringtones[i].ringtone>0:
0442                         print res.entry.name,ringpic.ringtones[i].ringtone
0443                         try:
0444                             tone=result['ringtone-index'][ringpic.ringtones[i].ringtone]['name']
0445                         except:
0446                             tone=self.serialsname+"Index_"+`ringpic.ringtones[i].ringtone`
0447                         entry['ringtones']=[{'ringtone': tone, 'use': 'call'}]
0448 
0449                     # wallpapers
0450                     if ringpic.wallpapers[i].wallpaper>0:
0451                         try:
0452                             paper=result['wallpaper-index'][ringpic.wallpapers[i].wallpaper]['name']
0453                         except:
0454                             paper=self.serialsname+"Index_"+`ringpic.wallpapers[i].wallpaper`
0455                         entry['wallpapers']=[{'wallpaper': paper, 'use': 'call'}]
0456                     
0457                 # Set default number, swap with first number
0458                 if usedefaultnum:
0459                     firsttype=res.entry.defaultnum-1
0460                     if firsttype < len(self.numbertypetab):
0461                         defaulttype=self.numbertypetab[firsttype]
0462                         k=0
0463                         for j in range(len(entry['numbers'])):
0464                             if entry['numbers'][j]['type'] == defaulttype:
0465                                 k=j
0466                                 break
0467                         if k>0:
0468                             exchange=entry['numbers'][k]
0469                             for kk in range(k,0,-1):
0470                                 entry['numbers'][kk]=entry['numbers'][kk-1]
0471                             entry['numbers'][0]=exchange
0472             
0473                 pbook[count]=entry 
0474                 self.progress(count, numentries, res.entry.name)
0475                 count+=1
0476                 numcount+=len(entry['numbers'])
0477                 if entry.has_key('emails'):
0478                     numemail+=len(entry['emails'])
0479                 if entry.has_key('urls'):
0480                     numurl+=len(entry['urls'])
0481         
0482         self.progress(numentries, numentries, "Phone book read completed")
0483         self.log("Phone contains "+`count`+" contacts, "+`numcount`+" phone numbers, "+`numemail`+" Emails, "+`numurl`+" URLs")
0484         result['phonebook']=pbook
0485         return pbook
0486 
0487     def extractphonebookentry(self, entry, fundamentals):
0488         """Return a phonebook entry in BitPim format"""
0489         res={}
0490         # serials
0491         res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.slot, 'serial2': entry.slotdup,
0492                           'sourceuniqueid': fundamentals['uniqueserial']} ]
0493         # only one name
0494         res['names']=[ {'full': entry.name} ]
0495         # only one email
0496         if len(entry.email):
0497             res['emails']=[ {'email': entry.email} ]
0498         # only one url
0499         if len(entry.url):
0500             res['urls']=[ {'url': entry.url} ]
0501         # private
0502         res['flags']=[ {'secret': entry.secret } ]
0503         # 7 phone numbers
0504         res['numbers']=[]
0505         numberindex = 0
0506         for type in self.numbertypetab:
0507             if len(entry.numbers[numberindex].number):
0508                 res['numbers'].append({'number': entry.numbers[numberindex].number, 'type': type })
0509             
0510             numberindex+=1
0511         return res
0512 
0513     def _findmediaindex(self, index, name, pbentryname, type):
0514         if name is None:
0515             return 0
0516         for i in index:
0517             if index[i]['name']==name:
0518                 return i
0519         # Not found in index, assume Vision download
0520         pos=name.find('_')
0521         if(pos>=0):
0522             i=int(name[pos+1:])
0523             return i
0524         
0525         return 0
0526         
0527     def makeentry(self, entry, dict):
0528         # dict is unused at moment, will be used later to convert string ringtone/wallpaper to numbers??
0529         # This is stolen from com_lgvx4400 and modified for the Sanyo as
0530         # we start to develop a vague understanding of what it is for.
0531         e=self.protocolclass.phonebookentry()
0532         
0533         for k in entry:
0534             # special treatment for lists
0535             if k=='ringtones' or k=='wallpapers' or k=='numbertypes':
0536                 continue
0537             if k=='numbers':
0538                 for numberindex in range(self.protocolclass.NUMPHONENUMBERS):
0539                     enpn=self.protocolclass.phonenumber()
0540                     e.numbers.append(enpn)
0541                     
0542                 for i in range(len(entry[k])):
0543                     numberindex=entry['numbertypes'][i]
0544                     e.numbers[numberindex].number=entry[k][i]
0545                     e.numbers[numberindex].number_len=len(e.numbers[numberindex].number)
0546                 continue
0547             # everything else we just set
0548             setattr(e,k,entry[k])
0549         return e
0550 
0551     def writewait(self):
0552         """Loop until phone status indicates ready to write"""
0553         for i in range(100):
0554             req=self.protocolclass.statusrequest()
0555             res=self.sendpbcommand(req, self.protocolclass.statusresponse)
0556             # print res.flag0, res.ready, res.flag2, res.flag3
0557             if res.ready==res.readyvalue:
0558                 return
0559             time.sleep(0.1)
0560 
0561         self.log("Phone did not transfer to ready to write state")
0562         self.log("Waiting a bit longer and trying anyway")
0563         return
0564     
0565     def savephonebook(self, data):
0566         # Overwrite the phonebook in the phone with the data.
0567         # As we write the phone book slots out, we need to build up
0568         # the indices in the callerid, ringpic and pbsort buffers.
0569         # We could save some writing by reading the phonebook slots first
0570         # and then only writing those that are different, but all the buffers
0571         # would still need to be written.
0572         #
0573 
0574         newphonebook={}
0575         self.mode=self.MODENONE
0576         self.setmode(self.MODEBREW) # see note in getphonebook in com_lgvx4400 for why this is necessary
0577         self.setmode(self.MODEPHONEBOOK)
0578 
0579 ###
0580 ### Create Sanyo buffers and Initialize lists
0581 ###
0582         sortstuff=self.protocolclass.pbsortbuffer()
0583         ringpic=self.protocolclass.ringerpicbuffer()
0584         callerid=self.protocolclass.calleridbuffer()
0585 
0586         res=self.protocolclass.phonebookentry()
0587         usedefaultnum = 'defaultnum' in res.getfields()
0588 
0589         for i in range(self.protocolclass._NUMPBSLOTS):
0590             sortstuff.usedflags.append(0)
0591             sortstuff.firsttypes.append(0)
0592             sortstuff.sortorder.append(0xffff)
0593             sortstuff.sortorder2.append(0xffff)
0594             sortstuff.emails.append(0xffff)
0595             sortstuff.urls.append(0xffff)
0596             ringpic.ringtones.append(0)
0597             ringpic.wallpapers.append(0)
0598 
0599         for i in range(self.protocolclass._NUMSPEEDDIALS):
0600             sortstuff.speeddialindex.append(0xffff)
0601 
0602         for i in range(self.protocolclass._NUMLONGNUMBERS):
0603             sortstuff.longnumbersindex.append(0xffff)
0604         
0605             
0606 ###
0607 ### Initialize mappings
0608 ###
0609         namemap=[]
0610         emailmap=[]
0611         urlmap=[]
0612 
0613         callerid.numentries=0
0614         
0615         pbook=data['phonephonebook'] # Get converted phonebook
0616         self.log("Putting phone into write mode")
0617         req=self.protocolclass.beginendupdaterequest()
0618         req.beginend=1 # Start update
0619         res=self.sendpbcommand(req, self.protocolclass.beginendupdateresponse, writemode=True)
0620 
0621         self.writewait()
0622         
0623         keys=pbook.keys()
0624         keys.sort()
0625         sortstuff.slotsused=len(keys)
0626         sortstuff.numemail=0
0627         sortstuff.numurl=0
0628 
0629         progresscur=0
0630         progressmax=len(keys)
0631         self.log("Writing %d entries to phone" % (len(keys),))
0632         nlongphonenumbers=0
0633         nonumbercount=0
0634         for ikey in keys:
0635             ii=pbook[ikey]
0636             slot=ii['slot'] # Or should we just use i for the slot
0637             # Will depend on Profile to keep the serial numbers in range
0638             progresscur+=1
0639             self.progress(progresscur, progressmax, "Writing "+ii['name'])
0640             self.log("Writing entry "+`slot`+" - "+ii['name'])
0641             entry=self.makeentry(ii, data)
0642             if not usedefaultnum:
0643                 delattr(entry,'defaultnum')
0644             req=self.protocolclass.phonebookslotupdaterequest()
0645             req.entry=entry
0646             res=self.sendpbcommand(req, self.protocolclass.phonebookslotresponse, writemode=True)
0647             # Put entry into newphonebooks
0648             entry=self.extractphonebookentry(entry, data)
0649             entry['ringtones']=[{'ringtone': ii['ringtone'], 'use': 'call'}]
0650             entry['wallpapers']=[{'wallpaper': ii['wallpaper'], 'use': 'call'}]
0651             
0652 
0653 # Accumulate information in and needed for buffers
0654             sortstuff.usedflags[slot].used=1
0655             if(len(ii['numbers'])):
0656                 sortstuff.firsttypes[slot].firsttype=min(ii['numbertypes'])+1
0657             else:
0658                 if(len(ii['email'])):
0659                     sortstuff.firsttypes[slot].firsttype=8
0660                     nonumbercount+=1
0661                 elif(len(ii['url'])):
0662                     sortstuff.firsttypes[slot].firsttype=9
0663                     nonumbercount+=1
0664                 else:
0665                     sortstuff.firsttypes[slot].firsttype=0
0666                     
0667 # Fill in Caller ID buffer
0668 # Want to move this out of this loop.  Callerid buffer is 500 numbers, but
0669 # can potentially hold 2100 numbers.  Would like to preferentially put the
0670 # first number for each name in this buffer.
0671 # If more than 500 numbers are in phone, the phone won't let you add
0672 # any more. So we should probably respect this limit.
0673             for i in range(len(ii['numbers'])):
0674                 nindex=ii['numbertypes'][i]
0675                 speeddial=ii['speeddials'][i]
0676                 code=slot+((nindex+1)<<12)
0677                 if(speeddial>=2 and speeddial<=self.protocolclass._NUMSPEEDDIALS+1):
0678                     sortstuff.speeddialindex[speeddial-2]=code
0679                     for k in range(len(entry['numbers'])):
0680                         if(entry['numbers'][k]['type']==nindex):
0681                             entry['numbers'][k]['speeddial']=speeddial
0682                             break
0683                 if(callerid.numentries<callerid.maxentries):
0684                     phonenumber=ii['numbers'][i]
0685                     cidentry=self.makecidentry(phonenumber,slot,nindex)
0686                     callerid.items.append(cidentry)
0687                     callerid.numentries+=1
0688                     if(len(phonenumber)>self.protocolclass._LONGPHONENUMBERLEN):
0689                         if(nlongphonenumbers<self.protocolclass._NUMLONGNUMBERS):
0690                             sortstuff.longnumbersindex[nlongphonenumbers].pbslotandtype=code
0691 
0692             namemap.append((slot,ii['name']))
0693             if(len(ii['email'])):
0694                 emailmap.append((slot,ii['email']))
0695                 sortstuff.numemail+=1
0696             if(len(ii['url'])):
0697                 urlmap.append((slot,ii['url']))
0698                 sortstuff.numurl+=1
0699             # Add ringtone and wallpaper
0700             ringpic.ringtones[slot].ringtone=self._findmediaindex(data['ringtone-index'], ii['ringtone'],ii['name'],'ringtone')
0701             ringpic.wallpapers[slot].wallpaper=self._findmediaindex(data['wallpaper-index'], ii['wallpaper'],ii['name'],'wallpaper')
0702 
0703             newphonebook[slot]=entry
0704 
0705         sortstuff.slotsused2=len(keys)-nonumbercount
0706         # Sort Names, Emails and Urls for the sort buffer
0707         # The phone sorts case insensitive and puts numbers after the
0708         # letters.
0709         i=0
0710         j=0
0711         sortstuff.pbfirstletters=""
0712         namemap.sort(self.sanyosort)
0713         for (slot, name) in namemap:
0714             sortstuff.sortorder[i].pbslot=slot
0715             if sortstuff.firsttypes[slot].firsttype<=7:
0716                 sortstuff.sortorder2[j].pbslot=slot
0717                 j+=1
0718             if name:
0719                 sortstuff.pbfirstletters+=name[0]
0720             else:
0721                 sortstuff.pbfirstletters+=chr(0)
0722             i+=1
0723 
0724         i=0
0725         sortstuff.emailfirstletters=""
0726         emailmap.sort(self.sanyosort)
0727         for (slot, email) in emailmap:
0728             sortstuff.emails[i].pbslot=slot
0729             sortstuff.emailfirstletters+=email[0]
0730             i+=1
0731 
0732         i=0
0733         sortstuff.urlfirstletters=""
0734         urlmap.sort(self.sanyosort)
0735         for (slot, url) in urlmap:
0736             sortstuff.urls[i].pbslot=slot
0737             sortstuff.urlfirstletters+=url[0]
0738             i+=1
0739 
0740         # Now write out the 3 buffers
0741         self.sendsanyobuffer(sortstuff)
0742 
0743         if self.protocolclass.HASRINGPICBUF:
0744             self.sendsanyobuffer(ringpic)
0745         
0746         self.sendsanyobuffer(callerid)
0747         
0748         time.sleep(1.0)
0749 
0750         data['phonebook']=newphonebook
0751         del data['phonephonebook']
0752         data['rebootphone'] = 1
0753 
0754     def makecidentry(self, number, slot, nindex):
0755         "Prepare entry for caller ID lookup buffer"
0756         
0757         numstripped=re.sub("[^0-9PT#*]", "", number)
0758         numonly=re.sub("^(\\d*).*$", "\\1", numstripped)
0759 
0760         cidentry=self.protocolclass.calleridentry()
0761         cidentry.pbslotandtype=slot+((nindex+1)<<12)
0762         cidentry.actualnumberlen=len(numonly)
0763         cidentry.numberfragment=numonly[-10:]
0764 
0765         return cidentry
0766     
0767 
0768     def savewallpapers(self, results, merge):
0769         # print "savewallpapers ",results['wallpaper-index']
0770         return self.savemedia('wallpapers', 'wallpaper-index', 'images', results, merge)
0771                               
0772     # Note:  Was able to write 6 ringers to phone.  If there are ringers
0773     # already on phone, this counts against the 6.  Don't know yet if the limit
0774     # is a count limit or a byte limit.
0775     def saveringtones(self, results, merge):
0776         return self.savemedia('ringtone', 'ringtone-index', 'ringers', results, merge)
0777     
0778     def savemedia(self, mediakey, mediaindexkey, mediatype, results, merge):
0779         """Actually saves out the media
0780 
0781         @param mediakey: key of the media (eg 'wallpapers' or 'ringtones')
0782                          Index of media in wallpaper or ringer tab
0783         @param mediaindexkey:  index key (eg 'wallpaper-index')
0784                          Index of media on the phone
0785         @param results: results dict
0786         """
0787 
0788         wp=results[mediakey].copy()
0789         wpi=results[mediaindexkey].copy()
0790 
0791         # remove builtins
0792         for k in wpi.keys():
0793             if wpi[k]['origin']=='builtin':
0794                 del wpi[k]
0795 
0796         # Remove camera pictures
0797         for w in wp.keys():
0798             name=wp[w]['name']
0799             if wp[w].get("origin", "")=='camera':
0800                 self.log("Not transferring camera picture "+name+" to phone")
0801                 del wp[w]
0802             else:
0803                 for k in wpi.keys():
0804                     if name==wpi[k]['name']:
0805                         self.log("Not transferring "+name+" as it is already on the phone")
0806                         del wp[w]
0807                         break
0808         
0809         init={}
0810         init[mediatype]={}
0811         idx=0
0812         for w in wp.keys():
0813             idx-=1
0814             data=wp[w]['data']
0815             name=wp[w]['name']
0816             init[mediatype][idx]={'name': name, 'data': data}
0817         
0818         index=init[mediatype]
0819 
0820         # write out the content
0821 
0822         ####  index is dict, key is index number, value is dict
0823         ####  value['name'] = filename
0824         ####  value['data'] is contents
0825 
0826         errors=False
0827         for key in index:
0828             efile=index[key]['name']
0829             content=index[key]['data']
0830             if content is None:
0831                 continue # in theory we could rewrite .desc file in case index number has changed
0832             # dirname=stripext(efile)
0833 
0834             if mediatype=='images':
0835                 content = conversions.convertto8bitpng(content,16383)
0836             if not self.writesanyofile(efile, content):
0837                 errors=True
0838 
0839         if errors:
0840             self.log("One or more files were rejected by the phone, due to")
0841             self.log("invalid file type (only PNG or MIDI are allowed), file")
0842             self.log("size too big, or too many ringers or wallpaper on the phone")
0843             self.log("See Sanyo Error Codes section of the Sanyh Phone Notes")
0844             self.log("in the help for more information")
0845             
0846         # Note that we don't write to the camera area
0847 
0848         # tidy up - reread indices
0849         # del results[mediakey] # done with it
0850         # reindexfunction(results)
0851         return results
0852         
0853 
0854     # Error codes
0855     # 0x13 Unknown command
0856     # 0x14 Bad media command
0857     # 0x6d on sendfileterminator
0858     # 0x65 not in sync mode on sendfilename
0859     # 0x6a on sendfilefragment when buffer full, for ringers  6 max?
0860     # 0x6b ??
0861     # 0x6c on sendfilefragment when full of pictures         11 max?
0862     # 0x6d
0863     # 0x69 on sendfilefragment.  Invalid file type.  PNG works, jpg, bmp don't
0864     # 0x68 on sendfilefragment.  Bad picture size
0865     def writesanyofile(self, name, contents):
0866         start=time.time()
0867         self.log("Writing file '"+name+"' bytes "+`len(contents)`)
0868         desc="Writing "+name
0869 
0870         # strip .png and .mid from name because phone will add them
0871         # back on
0872         try:
0873             name=name[:name.index(".mid")]
0874         except:
0875             try:
0876                 name=name[:name.index(".png")]
0877             except:
0878                 pass
0879         
0880         # The newer phones can be put in PC Sync mode with these commands
0881         if hasattr(self.protocolclass,"sanyomediathingyrequest"):
0882             req=self.protocolclass.sanyomediathingyrequest()
0883             req.faset = 0x10
0884             res=self.sendpbcommand(req, self.protocolclass.sanyomediathingyresponse)
0885             time.sleep(10.0)
0886             req.faset = 0x13
0887             res=self.sendpbcommand(req, self.protocolclass.sanyomediathingyresponse)
0888         
0889         req=self.protocolclass.sanyosendfilename()
0890         req.filename=name
0891         res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, returnerror=True)
0892         if 'errorcode' in res.getfields():
0893             if res.errorcode==0x65:
0894                 self.alert("Please put your phone into PC Sync Mode", False)
0895             else:
0896                 raise SanyoCommandException(res.errorcode)
0897             # Wait about 5 minutes before giving up
0898             waitcount=120
0899             while waitcount>0:
0900                 time.sleep(1.0)
0901                 res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, returnerror=True)
0902                 if not 'errorcode' in res.getfields():
0903                     break
0904                 waitcount-=1
0905             if waitcount==0:
0906                 raise SanyoCommandException(res.errorcode)
0907                 
0908         req=self.protocolclass.sanyosendfilesize()
0909         req.filesize=len(contents)
0910         self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse)
0911 
0912         req=self.protocolclass.sanyosendfilefragment()
0913         packetsize=req.payloadsize
0914 
0915 #        time.sleep(1.0)
0916         offset=0
0917         count=0
0918         numblocks=len(contents)/packetsize+1
0919         for offset in range(0, len(contents), packetsize):
0920             count+=1
0921             if count % 5==0:
0922                 self.progress(count,numblocks,desc)
0923             req.header.command=offset
0924             req.data=contents[offset:min(offset+packetsize,len(contents))]
0925             # self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, numsendretry=2)
0926             self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, writemode=True, returnerror=True)
0927             if 'errorcode' in res.getfields():
0928                 self.log(name+" not written due to error code "+`res.errorcode`)
0929                 return False
0930                 
0931         req=self.protocolclass.sanyosendfileterminator()
0932         try:
0933             res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, writemode=True, returnerror=True)
0934         # The returned value in res.header.faset may mean something
0935             if 'errorcode' in res.getfields():
0936                 self.log(name+" not written due to error code "+`res.errorcode`)
0937                 return False
0938         except:
0939             self.log("Exception on writing terminator for file "+name)
0940             self.log("Continuing...")
0941         
0942         end=time.time()
0943         if end-start>3:
0944             self.log("Wrote "+`len(contents)`+" bytes at "+`int(len(contents)/(end-start))`+" bytes/second")
0945         return True
0946 
0947 
0948     def getcalendar(self,result):
0949         # Read the event list from the phone.  Proof of principle code.
0950         # For now, join the event name and location into a single event.
0951         # description.
0952         # Todo:
0953         #   Read Call Alarms (reminder to call someone)
0954         #   Read call history into calendar.
0955         #   
0956         calres={}
0957 
0958         progressmax=self.protocolclass._NUMEVENTSLOTS+self.protocolclass._NUMCALLALARMSLOTS
0959         req=self.protocolclass.eventrequest()
0960         count=0
0961 
0962         try:
0963             reqflag=self.protocolclass.eventslotinuserequest()
0964         except:
0965             reqflag=0
0966 
0967         for i in range(0, self.protocolclass._NUMEVENTSLOTS):
0968             self.progress(i,progressmax,"Events")
0969             if reqflag:
0970                 reqflag.slot=i
0971                 resflag=self.sendpbcommand(reqflag, self.protocolclass.eventslotinuseresponse)
0972                 if not resflag.flag:
0973                     continue
0974             req.slot = i
0975             res=self.sendpbcommand(req, self.protocolclass.eventresponse)
0976             if not reqflag:
0977                 if not res.entry.flag:
0978                     continue
0979             self.log("Read calendar event "+`i`+" - "+res.entry.eventname+", alarm ID "+`res.entry.ringtone`)
0980             entry=bpcalendar.CalendarEntry()
0981             #entry.pos=i
0982             entry.changeserial=res.entry.serial
0983             entry.description=res.entry.eventname
0984             entry.location=res.entry.location
0985             starttime=res.entry.start
0986             entry.start=self.decodedate(starttime)
0987             entry.end=self.decodedate(res.entry.end)
0988             repeat=self._calrepeatvalues[res.entry.period]
0989             entry.repeat = self.makerepeat(repeat,entry.start)
0990 
0991             if res.entry.alarm==0xffffffff:
0992                 entry.alarm=res.entry.alarmdiff/60
0993             else:
0994                 alarmtime=res.entry.alarm
0995                 entry.alarm=(starttime-alarmtime)/60
0996             ringtone=res.entry.ringtone
0997             print "ringtone=",ringtone
0998             if self.calendar_voicenumber and ringtone == self.calendar_voicenumber:
0999                 ringtone=self.phonebook_voicenumber
1000             if ringtone in self.calendar_tonerange:
1001                 print "Adjusting ringtone by",-self.calendar_toneoffset
1002                 ringtone-=self.calendar_toneoffset
1003                 print "ringtone=",ringtone
1004             if ringtone!=self.calendar_defaultringtone:
1005                 if result['ringtone-index'].has_key(ringtone):
1006                     entry.ringtone=result['ringtone-index'][ringtone]['name']
1007 
1008             entry.snoozedelay=0
1009             calres[entry.id]=entry
1010             count+=1
1011 
1012         req=self.protocolclass.callalarmrequest()
1013         for i in range(0, self.protocolclass._NUMCALLALARMSLOTS):
1014             self.progress(self.protocolclass._NUMEVENTSLOTS,progressmax,"Call Alarms")
1015             req.slot=i
1016             res=self.sendpbcommand(req, self.protocolclass.callalarmresponse)
1017             if res.entry.flag and res.entry.date:
1018                 self.log("Read call alarm entry "+`i`+" - "+res.entry.phonenum+", alarm ID "+`res.entry.ringtone`)
1019                 entry=bpcalendar.CalendarEntry()
1020                 #entry.pos=i+self.protocolclass._NUMEVENTSLOTS # Make unique
1021                 entry.changeserial=res.entry.serial
1022                 entry.description=res.entry.phonenum
1023                 starttime=res.entry.date
1024                 entry.start=self.decodedate(starttime)
1025                 entry.end=entry.start
1026                 repeat=self._calrepeatvalues[res.entry.period]
1027                 entry.repeat = self.makerepeat(repeat,entry.start)
1028                 entry.alarm=0
1029                 if res.entry.ringtone!=self.calendar_defaultcaringtone:
1030                     entry.ringtone=result['ringtone-index'][res.entry.ringtone]['name']
1031                 entry.snoozedelay=0
1032                 calres[entry.id]=entry
1033                 count+=1
1034 
1035         result['calendar']=calres
1036         return result
1037 
1038     def makerepeat(self, repeatword, startdate):
1039         if repeatword is None:
1040             repeat_entry=None
1041         else:
1042             repeat_entry=bpcalendar.RepeatEntry()
1043             if repeatword=='daily':
1044                 repeat_entry.repeat_type=repeat_entry.daily
1045                 repeat_entry.interval=1
1046             elif repeatword=='monfri':
1047                 repeat_entry.repeat_type=repeat_entry.daily
1048                 repeat_entry.interval=0
1049             elif repeatword=='weekly':
1050                 repeat_entry.repeat_type=repeat_entry.weekly
1051                 repeat_entry.interval=1
1052                 dow=datetime.date(*startdate[:3]).isoweekday()%7
1053                 repeat_entry.dow=1<<dow
1054             elif repeatword=='monthly':
1055                 repeat_entry.repeat_type=repeat_entry.monthly
1056             else:
1057                 repeat_entry.repeat_type=repeat_entry.yearly
1058 
1059         return repeat_entry
1060         
1061     def savecalendar(self, dict, merge):
1062         # ::TODO:: obey merge param
1063         # what will be written to the files
1064         #   Handle Change Serial better.
1065         #   Advance date on repeating entries to after now so that they
1066         #     won't all go off when the phone gets turned on.
1067         #   Sort by date so that that already happened entries don't get
1068         #     loaded if we don't have room
1069         #
1070         cal=dict['calendar']
1071         newcal={}
1072         keys=cal.keys()
1073 
1074         # Value to subtract from mktime results since there is no inverse
1075         # of gmtime
1076         zonedif=time.mktime(time.gmtime(0))-time.mktime(time.localtime(0))
1077         save_tonerange = xrange(self.calendar_tonerange[0]-self.calendar_toneoffset,self.calendar_tonerange[-1]-self.calendar_toneoffset+1)
1078 
1079         try:
1080             reqflag=self.protocolclass.eventslotinuseupdaterequest()
1081         except:
1082             reqflag=0
1083 
1084         eventslot=0
1085         callslot=0
1086         progressmax=self.protocolclass._NUMEVENTSLOTS+self.protocolclass._NUMCALLALARMSLOTS
1087         for k in keys:
1088             entry=cal[k]
1089             
1090             descloc=entry.description
1091             self.progress(eventslot+callslot, progressmax, "Writing "+descloc)
1092 
1093             rp=entry.repeat
1094             if rp is None:
1095                 repeat=0
1096             else:
1097                 repeatname=None
1098                 if rp.repeat_type==rp.daily:
1099                     repeatname='daily'
1100                 elif rp.repeat_type==rp.weekly:
1101                     repeatname='weekly'
1102                 elif rp.repeat_type==rp.monthly:
1103                     repeatname='monthly'
1104                 elif rp.repeat_type==rp.yearly:
1105                     repeatname='yearly'
1106                 for k,v in self._calrepeatvalues.items():
1107                     if repeatname==v:
1108                         repeat=k
1109                         break
1110                     if repeatname is None:
1111                         self.log(descloc+": Repeat type "+`entry.repeat`+" not valid for this phone")
1112                         repeat=0
1113 
1114             phonenum=re.sub("\-","",descloc)
1115             now=time.mktime(time.localtime(time.time()))-zonedif
1116             if(phonenum.isdigit()):  # This is a phone number, use call alarm
1117                 self.log("Write calendar call alarm slot "+`callslot`+ " - "+descloc)
1118                 e=self.protocolclass.callalarmentry()
1119                 e.slot=callslot
1120                 e.phonenum=phonenum
1121                 e.phonenum_len=len(e.phonenum)
1122                 
1123 
1124                 timearray=list(entry.start)+[0,0,0,0]
1125                 starttimelocal=time.mktime(timearray)-zonedif
1126                 if(starttimelocal<now and repeat==0):
1127                     e.flag=2 # In the past
1128                 else:
1129                     e.flag=1 # In the future
1130                 e.date=starttimelocal-self._sanyoepochtounix
1131                 e.datedup=e.date
1132                 e.phonenumbertype=0
1133                 e.phonenumberslot=0
1134                 e.name="" # Could get this by reading phone book
1135                           # But it would take a lot more time
1136                 e.name_len=len(e.name)
1137                 #e.ringtone=self.calendar_defaultcaringtone
1138                 print "Setting ringtone "+`e.ringtone`
1139 
1140                 req=self.protocolclass.callalarmupdaterequest()
1141                 callslot+=1
1142                 respc=self.protocolclass.callalarmresponse
1143             else: # Normal calender event
1144                 self.log("Write calendar event slot "+`eventslot`+ " - "+descloc)
1145                 e=self.protocolclass.evententry()
1146                 e.slot=eventslot
1147 
1148                 e.eventname=descloc
1149                 e.eventname_len=len(e.eventname)
1150                 e.location=entry.location
1151                 e.location_len=len(e.location)
1152 
1153                 timearray=list(entry.start)+[0,0,0,0]
1154                 starttimelocal=time.mktime(timearray)-zonedif
1155                 e.start=starttimelocal-self._sanyoepochtounix
1156 
1157                 try:
1158                     timearray=list(entry.end)+[0,0,0,0]
1159                     endtimelocal=time.mktime(timearray)-zonedif
1160                     e.end=endtimelocal-self._sanyoepochtounix
1161                 except: # If no valid end date, make end
1162                     e.end=e.start+60 #  one minute later
1163                 
1164                 alarmdiff=entry.alarm
1165                 if alarmdiff<0:
1166                     alarmdiff=0
1167                 alarmdiff=max(alarmdiff,0)*60
1168                 e.alarmdiff=alarmdiff
1169                 e.alarm=starttimelocal-self._sanyoepochtounix-alarmdiff
1170 
1171                 if reqflag:
1172                     reqflag.slot=eventslot
1173                     if(e.alarm+self._sanyoepochtounix<now and repeat==0):
1174                         reqflag.flag=4 # In the past
1175                     else:
1176                         reqflag.flag=1 # In the future
1177                     res=self.sendpbcommand(reqflag, self.protocolclass.eventslotinuseresponse)
1178                 else:
1179                     if(starttimelocal<now and repeat==0):
1180                         e.flag=2 # In the past
1181                     else:
1182                         e.flag=1 # In the future
1183 
1184                 #timearray=list(entry.get('end', entry['start']))+[0,0,0,0]
1185                 #e.end=time.mktime(timearray)-self._sanyoepochtounix-zonedif
1186                 try:
1187                     timearray=list(entry.end)+[0,0,0,0]
1188                     endtimelocal=time.mktime(timearray)-zonedif
1189                     e.end=endtimelocal-self._sanyoepochtounix
1190                 except: # If no valid end date, make end
1191                     e.end=e.start+60 #  one minute later 
1192 
1193                 ringtoneindex=self._findmediaindex(dict['ringtone-index'],entry.ringtone,'Calendar','ringtone')
1194                 if ringtoneindex==0:
1195                     e.ringtone=self.calendar_defaultringtone
1196                 else:
1197                     e.ringtone=ringtoneindex
1198                     if e.ringtone in save_tonerange:
1199                         e.ringtone+=self.calendar_toneoffset
1200                     if self.calendar_voicenumber and e.ringtone==self.phonebook_voicenumber:
1201                         e.ringtone=self.calendar_voicenumber
1202                 # Use default ringtone for now
1203                 #e.ringtone=self.calendar_defaultringtone
1204                 print "Setting ringtone "+`e.ringtone`
1205 
1206 # What we should do is first find largest changeserial, and then increment
1207 # whenever we have one that is undefined or zero.
1208             
1209                 req=self.protocolclass.eventupdaterequest()
1210                 eventslot+=1
1211                 respc=self.protocolclass.eventresponse
1212 
1213             e.period=repeat
1214             e.dom=entry.start[2]
1215             if entry.id>=0 and entry.id<256:
1216                 e.serial=entry.id
1217             else:
1218                 e.serial=0
1219             req.entry=e
1220             res=self.sendpbcommand(req, respc, writemode=True)
1221 
1222 
1223             # Blank out unused slots
1224 
1225         if reqflag:
1226             req=reqflag
1227             req.flag=0
1228         else:
1229             e=self.protocolclass.evententry()
1230             e.flag=0 # Unused slot
1231             e.eventname=""
1232             e.eventname_len=0
1233             e.location=""
1234             e.location_len=0
1235             e.start=0
1236             e.end=0
1237             e.period=0
1238             e.dom=0
1239             e.ringtone=0
1240             e.alarm=0
1241             e.alarmdiff=0
1242             req=self.protocolclass.eventupdaterequest()
1243             req.entry=e
1244             
1245         for eventslot in range(eventslot,self.protocolclass._NUMEVENTSLOTS):
1246             self.progress(eventslot+callslot, progressmax, "Writing unused")
1247             self.log("Write calendar event slot "+`eventslot`+ " - Unused")
1248             if reqflag:
1249                 req.slot=eventslot
1250             else:
1251                 req.entry.slot=eventslot
1252             res=self.sendpbcommand(req, self.protocolclass.eventresponse, writemode=True)
1253 
1254         e=self.protocolclass.callalarmentry()
1255         e.flag=0 # Unused slot
1256         e.name=""
1257         e.name_len=0
1258         e.phonenum=""
1259         e.phonenum_len=0
1260         e.date=0
1261         e.datedup=0
1262         e.period=0
1263         e.dom=0
1264         e.ringtone=0
1265         e.phonenumbertype=0
1266         e.phonenumberslot=0
1267         req=self.protocolclass.callalarmupdaterequest()
1268         req.entry=e
1269         for callslot in range(callslot,self.protocolclass._NUMCALLALARMSLOTS):
1270             self.progress(eventslot+callslot, progressmax, "Writing unused")
1271             self.log("Write calendar call alarm slot "+`callslot`+ " - Unused")
1272             req.entry.slot=callslot
1273             res=self.sendpbcommand(req, self.protocolclass.callalarmresponse, writemode=True)
1274 
1275         self.progress(progressmax, progressmax, "Calendar write done")
1276 
1277         dict['rebootphone'] = True
1278         return dict
1279 
1280     def getcallhistory(self, result):
1281         res={}
1282         self._readhistory(self.protocolclass.OUTGOING,'Outgoing',res)
1283         self._readhistory(self.protocolclass.MISSED,'Missed',res)
1284         self._readhistory(self.protocolclass.INCOMING,'Incoming',res)
1285         result['call_history']=res
1286         return result
1287 
1288     def _readhistory(self, type, folder, res):
1289         req=self.protocolclass.historyrequest()
1290         req.type=type
1291         for slot in range(self.protocolclass.NUMCALLHISTORY):
1292             req.slot=slot
1293             call=self.sendpbcommand(req, self.protocolclass.historyresponse)
1294             if call.entry.phonenum=='' and call.entry.name=='':
1295                 continue
1296             entry=call_history.CallHistoryEntry()
1297             entry.folder=folder
1298             entry.name=call.entry.name
1299             entry.number=call.entry.phonenum
1300             entry.datetime=((call.entry.date))
1301             res[entry.id]=entry
1302     
1303     def getphoneinfo(self, phone_info):
1304         self.log('Getting Phone Info')
1305         try:
1306             phone_info.append('ESN:', self.get_esn())
1307             phone_info.append('Manufacturer:', 'Sanyo')
1308             req=self.protocolclass.lockcoderequest()
1309             try:
1310                 res=self.sendpbcommand(req, self.protocolclass.lockcoderesponse)
1311                 phone_info.append('Lock Code:', res.lockcode)
1312             except:
1313                 pass
1314                 
1315             req=self.protocolclass.sanyofirmwarerequest()
1316             res=self.sendpbcommand(req, self.protocolclass.sanyofirmwareresponse)
1317             phone_info.append('Firmware Version:',res.firmware)
1318             if 'prl' in res.getfields():
1319                 phone_info.append('PRL:', res.prl)
1320             if 'phonemodel' in res.getfields():
1321                 phone_info.append('Model:', res.phonemodel)
1322             else:
1323                 phone_info.append('Model:', self.desc)
1324             req=self.protocolclass.phonenumberrequest()
1325             res=self.sendpbcommand(req, self.protocolclass.phonenumberresponse)
1326             phone_info.append('Phone Number:', res.myphonenumber)
1327            
1328             req=self.protocolclass.reconditionedrequest()
1329             res=self.sendpbcommand(req, self.protocolclass.reconditionedresponse)
1330             if res.reconditioned:
1331                 recon='Yes'
1332             else:
1333                 recon='No'
1334             phone_info.append('Reconditioned:',recon)
1335         except:
1336             pass
1337 
1338         return
1339 
1340     def decodedate(self,val):
1341         """Unpack 32 bit value into date/time
1342 
1343         @rtype: tuple
1344         @return: (year, month, day, hour, minute)
1345         """
1346         return list(time.gmtime(val+self._sanyoepochtounix)[:5])
1347 
1348     _calrepeatvalues={
1349         0: None,
1350         1: 'daily',
1351         2: 'weekly',
1352         3: 'monthly',
1353         4: 'yearly'
1354         }
1355 
1356 
1357 def phonize(str):
1358     """Convert the phone number into something the phone understands
1359 
1360     All digits, P, T, * and # are kept, everything else is removed"""
1361     # Note: when looking at phone numbers on the phone, you will see
1362     # "H" instead of "P".  However, phone saves this internally as "P".
1363     return re.sub("[^0-9PT#*]", "", str)
1364 
1365 class Profile(com_phone.Profile):
1366     serialsname='sanyo'
1367 
1368     WALLPAPER_WIDTH=120
1369     WALLPAPER_HEIGHT=128
1370     OVERSIZE_PERCENTAGE=100
1371     
1372     MAX_WALLPAPER_BASENAME_LENGTH=19
1373     WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .`~!@#$%^&()-_=+[{]};\'"
1374     WALLPAPER_CONVERT_FORMAT="png"
1375     
1376     MAX_RINGTONE_BASENAME_LENGTH=19
1377     RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .`~!@#$%^&()-_=+[{]};\'"
1378 
1379     # which usb ids correspond to us
1380     usbids=( ( 0x0474, 0x0701, 1),  # VID=Sanyo, PID=4900 internal USB interface
1381         )
1382     # which device classes we are.
1383     deviceclasses=("modem",)
1384 
1385     BP_Calendar_Version=3
1386 
1387     def __init__(self):
1388         self.numbertypetab=numbertypetab
1389         com_phone.Profile.__init__(self)
1390 
1391     # which sync types we deal with
1392     _supportedsyncs=(
1393         ('phonebook', 'read', None),  # all phonebook reading
1394         ('calendar', 'read', None),   # all calendar reading
1395         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
1396         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
1397         ('wallpaper', 'write', 'MERGE'),
1398         ('ringtone', 'write', 'MERGE'),
1399         ('call_history', 'read', None),# all call history list reading
1400         ('sms', 'read', None),
1401         )
1402 ###
1403 
1404     # Processes phone book for writing to Sanyo.  But does not leave phone book
1405     # in a bitpim compatible format.  Need to understand exactly what bitpim
1406     # is expecting the method to do.
1407 
1408     def convertphonebooktophone(self, helper, data):
1409         "Converts the data to what will be used by the phone"
1410         results={}
1411 
1412         slotsused={}
1413         for pbentry in data['phonebook']:
1414             entry=data['phonebook'][pbentry]
1415             serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1)
1416             if(serial1 >= 0 and serial1 < self.protocolclass._NUMPBSLOTS):
1417                 slotsused[serial1]=1
1418 
1419         lastunused=0 # One more than last unused slot
1420         
1421         for pbentry in data['phonebook']:
1422             e={} # entry out
1423             entry=data['phonebook'][pbentry] # entry in
1424             try:
1425                 try:
1426                     e['name']=helper.getfullname(entry.get('names', []),1,1,16)[0]
1427                 except:
1428                     e['name']=''
1429                 e['name_len']=len(e['name'])
1430 
1431                 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1)
1432 
1433                 if(serial1 >= 0 and serial1 < self.protocolclass._NUMPBSLOTS):
1434                     e['slot']=serial1
1435                 else:  # A new entry.  Must find unused slot
1436                     while(slotsused.has_key(lastunused)):
1437                         lastunused+=1
1438                         if(lastunused >= self.protocolclass._NUMPBSLOTS):
1439                             raise helper.ConversionFailed()
1440                     e['slot']=lastunused
1441                     slotsused[lastunused]=1
1442                 
1443                 e['slotdup']=e['slot']
1444 
1445                 e['email']=helper.makeone(helper.getemails(entry.get('emails', []),0,1,self.protocolclass._MAXEMAILLEN), "")
1446                 e['email_len']=len(e['email'])
1447 
1448                 e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,self.protocolclass._MAXEMAILLEN), "")
1449                 e['url_len']=len(e['url'])
1450 # Could put memo in email or url
1451 
1452                 numbers=helper.getnumbers(entry.get('numbers', []),0,7)
1453                 e['numbertypes']=[]
1454                 e['numbers']=[]
1455                 e['speeddials']=[]
1456                 unusednumbers=[] # Hold duplicate types here
1457                 typesused={}
1458                 defaultnum=0
1459                 for num in numbers:
1460                     typename=num['type']
1461                     if(typesused.has_key(typename)):
1462                         unusednumbers.append(num)
1463                         continue
1464                     typesused[typename]=1
1465                     for typenum,tnsearch in enumerate(self.numbertypetab):
1466                         if typename==tnsearch:
1467                             if defaultnum==0:
1468                                 defaultnum=typenum+1
1469                             number=phonize(num['number'])
1470                             if len(number)>self.protocolclass._MAXNUMBERLEN: # get this number from somewhere sensible
1471                                 # :: TODO:: number is too long and we have to either truncate it or ignore it?
1472                                 number=number[:self.protocolclass._MAXNUMBERLEN]
1473                             e['numbers'].append(number)
1474                             if(num.has_key('speeddial')):
1475                                 e['speeddials'].append(num['speeddial'])
1476                             else:
1477                                 e['speeddials'].append(-1)
1478 
1479                             e['numbertypes'].append(typenum)
1480 
1481                             break
1482 
1483 # Now stick the unused numbers in unused types
1484                 trytype=len(self.numbertypetab)
1485                 for num in unusednumbers:
1486                     while trytype>0:
1487                         trytype-=1
1488                         if not typesused.has_key(self.numbertypetab[trytype]):
1489                             break
1490                     else:
1491                         break
1492                     if defaultnum==0:
1493                         defaultnum=trytype+1
1494                     number=phonize(num['number'])
1495                     if len(number)>self.protocolclass._MAXNUMBERLEN: # get this number from somewhere sensible
1496                         # :: TODO:: number is too long and we have to either truncate it or ignore it?
1497                         number=number[:self.protocolclass._MAXNUMBERLEN]
1498                     e['numbers'].append(number)
1499                     e['numbertypes'].append(trytype)
1500                     if(num.has_key('speeddial')):
1501                         e['speeddials'].append(num['speeddial'])
1502                     else:
1503                         e['speeddials'].append(-1)
1504 
1505                 if defaultnum==0:
1506                     if e['url_len'] > 0:
1507                         defaultnum=8
1508                     elif e['email_len'] > 0:
1509                         defaultnum=9
1510 
1511                 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None)
1512                 e['wallpaper']=helper.getwallpaper(entry.get('wallpapers', []), 'call', None)
1513 
1514                 e['secret']=helper.getflag(entry.get('flags', []), 'secret', False)
1515                 e['defaultnum']=defaultnum
1516 
1517                 results[pbentry]=e
1518                 
1519             except helper.ConversionFailed:
1520                 #self.log("No Free Slot for "+e['name'])
1521                 print "No Free Slot for "+e['name']
1522                 continue
1523 
1524         data['phonephonebook']=results
1525         return data
1526 
1527 
1528 class Phone(SanyoPhonebook,com_phone.Phone,com_brew.BrewProtocol):
1529     "Talk to a Sanyo Sprint Phone such as SCP-4900, SCP-5300, or SCP-8100"
1530     desc="Sanyo"
1531     helpid=helpids.ID_PHONE_SANYOOTHERS
1532     imagelocations=()
1533     
1534     ringtonelocations=()
1535 
1536     builtinimages=()
1537 
1538     builtinringtones=()
1539 
1540     def __init__(self, logtarget, commport):
1541         "Call all the contructors and sets initial modes"
1542         com_phone.Phone.__init__(self, logtarget, commport)
1543         com_brew.BrewProtocol.__init__(self)
1544         SanyoPhonebook.__init__(self)
1545         self.log("Attempting to contact phone")
1546         self.mode=self.MODENONE
1547 
1548     getwallpapers=None
1549     getringtones=None
1550 
1551 

Generated by PyXR 0.9.4