0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2004 Vic Heintz <vheintz@rochester.rr.com> 0004 ### Copyright (C) 2004 Joe Pham <djpham@netzero.com> 0005 ### 0006 ### This program is free software; you can redistribute it and/or modify 0007 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0008 ### 0009 0010 ### $Id: com_samsungscha670.py 4365 2007-08-17 21:11:59Z djpham $ 0011 0012 ### The bulk of this code was borrowed from com_samsungscha650.py 0013 ### and modified so that operations work with the sch-a670. 0014 0015 """Communicate with a Samsung SCH-A670""" 0016 0017 # lib modules 0018 import re 0019 import sha 0020 import time 0021 0022 # my modules 0023 import common 0024 import commport 0025 import com_brew 0026 import com_samsung 0027 import com_phone 0028 import conversions 0029 import fileinfo 0030 import nameparser 0031 import p_samsungscha670 0032 import prototypes 0033 0034 0035 class Phone(com_samsung.Phone): 0036 "Talk to the Samsung SCH-A670 Cell Phone" 0037 0038 desc="SCH-A670" 0039 serialsname='scha670' 0040 protocolclass=p_samsungscha670 0041 parent_phone=com_samsung.Phone 0042 0043 __groups_range=xrange(5) 0044 __phone_entries_range=xrange(1,501) 0045 __pb_atpbokw_field_count=26 # This is a rename from __pb_max_entries in a650 code 0046 # which I found confusing 0047 __pb_max_speeddials=500 0048 __pb_entry=0 0049 __pb_mem_loc=1 0050 __pb_group=2 0051 __pb_ringtone=3 0052 __pb_name=4 0053 __pb_speed_dial=5 0054 __pb_home_num=7 0055 __pb_office_num=9 0056 __pb_mobile_num=11 0057 __pb_pager_num=13 0058 __pb_fax_num=15 0059 __pb_alias=17 0060 __pb_blanks=(19, 20) 0061 __pb_email=21 0062 __pb_image_assign=22 # 3, 4 or 5 depending on if picture ID assigned 0063 __pb_image_id=23 # when above is 4, has caller-id wallpaper id 0064 __pb_contact_image=24 # Path to "Picture ID" image 0065 __pb_date_time_stamp=25 0066 __pb_numbers= ({'home': __pb_home_num}, 0067 {'office': __pb_office_num}, 0068 {'cell': __pb_mobile_num}, 0069 {'pager': __pb_pager_num}, 0070 {'fax': __pb_fax_num}) 0071 __pb_max_name_len=22 0072 __pb_max_number_len=32 0073 __pb_max_emails=1 0074 __pb_max_email_chars=48 # Not currently used, but shouldn't it be? 0075 __pb_max_alias_chars=48 # Not currently used, but shouldn't it be? 0076 __wp_photo_dir="digital_cam" 0077 __wp_header_bytes=96 # Extra bytes in front of jpg 0078 __wp_ts_offset=47 # Timestamp in yyyymmddHHMMSS format starts here 0079 __wp_index_file = "nvm/nvm/brew_image" 0080 __rt_index_file = "nvm/nvm/brew_melody" 0081 __rt_dir = "brew/ringer" 0082 __wp_dir = "brew/shared" 0083 0084 # The extra "User" entries are a temporary fix to allow round trip integrity of user 0085 # assigned ringtones for caller ID. Need to read /nvm/nvm/brew_melody to get true names 0086 # of ringtones. 0087 builtinringtones=( 'Inactive', 0088 'Bell 1', 'Bell 2', 'Bell 3', 'Bell 4', 'Bell 5', 0089 'Melody 1', 'Melody 2', 'Melody 3', 'Melody 4', 'Melody 5', 0090 'Melody 6', 'Melody 7', 'Melody 8', 'Melody 9', 'Melody 10', 0091 'User 00', 'User 01', 'User 02', 'User 03', 'User 04', 'User 05', 0092 'User 06', 'User 07', 'User 08', 'User 09', 'User 10', 'User 11', 0093 'User 12', 'User 13', 'User 14', 'User 15', 'User 16', 'User 17', 0094 'User 18', 'User 19') 0095 allringtones={} 0096 0097 # 'type name', 'type index name', 'origin', 'dir path', 'max file name length', 'max file name count' 0098 __ringtone_info=('ringtone', 'ringtone-index', 'ringtone', 'brew/ringer', 19, 20) 0099 __wallpaper_info=('wallpapers', 'wallpaper-index', 'images', 'mms_image', 19, 10) 0100 ## __wallpaper_info=('wallpapers', 'wallpaper-index', 'wallpapers', 'brew/shared', 19, 10) 0101 __camerapix_info=('wallpapers', 'wallpaper-index', 'camera', 'digital_cam', 19, 40) 0102 __video0_info=('wallpapers', 'wallpaper-index', 'video', 'camcoder0', None, None) 0103 __video1_info=('wallpapers', 'wallpaper-index', 'video', 'camcoder1', None, None) 0104 0105 def __init__(self, logtarget, commport): 0106 0107 "Calls all the constructors and sets initial modes" 0108 com_samsung.Phone.__init__(self, logtarget, commport) 0109 self.mode=self.MODENONE 0110 0111 def getfundamentals(self, results): 0112 0113 """Gets information fundamental to interoperating with the phone and UI. 0114 0115 Currently this is: 0116 0117 - 'uniqueserial' a unique serial number representing the phone 0118 - 'groups' the phonebook groups 0119 - 'wallpaper-index' map index numbers to names 0120 - 'ringtone-index' map index numbers to ringtone names 0121 0122 This method is called before we read the phonebook data or before we 0123 write phonebook data. 0124 """ 0125 0126 self.setmode(self.MODEPHONEBOOK) 0127 0128 # use a hash of ESN and other stuff (being paranoid) 0129 0130 self.log("Retrieving fundamental phone information") 0131 self.log("Reading phone serial number") 0132 results['uniqueserial']=sha.new(self.get_esn()).hexdigest() 0133 0134 # now read groups 0135 0136 self.log("Reading group information") 0137 g=self.get_groups(self.__groups_range) 0138 groups={} 0139 for i, g_i in enumerate(g): 0140 if len(g_i): 0141 groups[i]={ 'name': g_i } 0142 results['groups']=groups 0143 0144 # getting rintone-index 0145 self.setmode(self.MODEBREW) 0146 rt_index=RingtoneIndex(self) 0147 results['ringtone-index']=rt_index.get() 0148 0149 # getting wallpaper-index 0150 img_index=ImageIndex(self) 0151 results['wallpaper-index']=img_index.get() 0152 0153 self.setmode(self.MODEMODEM) 0154 self.log("Fundamentals retrieved") 0155 0156 return results 0157 0158 def get_builtin_ringtone_index(self): # Joe's version 0159 r={} 0160 for k, n in enumerate(self.builtinringtones): 0161 r[k]={ 'name': n, 'origin': 'builtin' } 0162 return r 0163 0164 def ATget_builtin_ringtone_index(self): # Vic's version using AT command 0165 r={} 0166 s=self.comm.sendatcommand("#PUGSN?") 0167 for rt in s[1:]: 0168 this_r = rt.split(",") 0169 r[int(this_r[0])] = { 'name': this_r[2], 'origin': 'builtin' } 0170 self.allringtones[int(this_r[0])] = this_r[2] 0171 return r 0172 0173 def get_user_rt_index(self, r): # IDs on phone needed for caller-ID 0174 bi_cnt = len(r) 0175 rtlist = self.getfilecontents(self.__rt_index_file) 0176 offset=0 0177 while offset < (40 * 77): 0178 rtid = ord(rtlist[offset+1]) 0179 rtlen = ord(rtlist[offset+74]) 0180 rtname = rtlist[offset+23:offset+23+rtlen][len(self.__rt_dir)+1:] 0181 if rtlen > 0: 0182 r[rtid + bi_cnt] = { 'name': rtname, 'origin': 'ringtone' } 0183 self.allringtones[rtid + bi_cnt] = rtname 0184 offset+=77 0185 return r 0186 0187 def get_wallpaper_index(self): # IDs on phone needed for caller-ID 0188 wpi = {} 0189 imglist = self.getfilecontents(self.__wp_index_file) 0190 offset=0 0191 while offset < (30 * 76): 0192 imgid = ord(imglist[offset+1]) 0193 imglen = ord(imglist[offset+73]) 0194 imgname = imglist[offset+22:offset+22+imglen][len(self.__wp_dir)+1:] 0195 if imglen > 0: 0196 wpi[imgid] = { 'name': imgname, 'origin': 'wallpaper' } 0197 offset+=76 0198 return wpi 0199 0200 def _get_phonebook(self, result, show_progress=True): 0201 """Reads the phonebook data. The L{getfundamentals} information will 0202 already be in result.""" 0203 0204 self.setmode(self.MODEPHONEBOOK) 0205 c=len(self.__phone_entries_range) 0206 k=0 0207 pb_book={} 0208 for j in self.__phone_entries_range: 0209 # print "Getting entry: ", j 0210 pb_entry=self.get_phone_entry(j) 0211 if len(pb_entry)==self.__pb_atpbokw_field_count: 0212 pb_book[k]=self._extract_phone_entry(pb_entry, result) 0213 k+=1 0214 # print pb_book[k], i 0215 if show_progress: 0216 self.progress(j, c, 'Reading '+pb_entry[self.__pb_name]) 0217 else: 0218 if show_progress: 0219 self.progress(j, c, 'Blank entry: %d' % j) 0220 self.setmode(self.MODEMODEM) 0221 0222 return pb_book 0223 0224 def getphonebook(self,result): 0225 """Reads the phonebook data. The L{getfundamentals} information will 0226 already be in result.""" 0227 pb_book=self._get_phonebook(result) 0228 result['phonebook']=pb_book 0229 return pb_book 0230 0231 def _extract_phone_entry(self, entry, fundamentals): 0232 0233 res={} 0234 # serials 0235 res['serials']=[ {'sourcetype': self.serialsname, 0236 'sourceuniqueid': fundamentals['uniqueserial'], 0237 'serial1': entry[self.__pb_entry], 0238 'serial2': entry[self.__pb_mem_loc] }] 0239 # only one name 0240 res['names']=[ {'full': unicode(entry[self.__pb_name].replace('"', ''), 0241 errors='ignore') } ] 0242 if len(entry[self.__pb_alias]): 0243 res['urls']=[ {'url': entry[self.__pb_alias] } ] 0244 0245 # only one category 0246 g=fundamentals['groups'] 0247 i=int(entry[self.__pb_group]) 0248 res['categories']=[ {'category': g[i]['name'] } ] 0249 0250 # emails 0251 s=entry[self.__pb_email].replace('"', '') 0252 if len(s): 0253 res['emails']=[ { 'email': s } ] 0254 0255 # urls 0256 # private 0257 # memos 0258 0259 # wallpapers 0260 if entry[self.__pb_image_assign]=='3': 0261 # the image is 'Gallery' with the string 'digital_cam/Imagexxx' 0262 res['wallpapers']=[ { 'wallpaper': entry[self.__pb_contact_image].split( "/")[1]+".jpg", 0263 'use': 'call' } ] 0264 elif entry[self.__pb_image_assign]=='4': 0265 # the entry is an index into wallpaper-index 0266 wp_index=fundamentals.get('wallpaper-index', {}) 0267 try: 0268 res['wallpapers']=[{ 'wallpaper': wp_index[int(entry[self.__pb_image_id])]['name'], 0269 'use': 'call'} ] 0270 except: 0271 pass 0272 0273 # ringtones 0274 try: 0275 rt_index=fundamentals.get('ringtone-index', {}) 0276 res['ringtones']=[ { 'ringtone': rt_index[int(entry[self.__pb_ringtone])]['name'], 0277 'use': 'call' } ] 0278 except: 0279 res['ringtones']=[ { 'ringtone': self.builtinringtones[0], 0280 'use': 'call' } ] 0281 0282 0283 # numbers 0284 speed_dial=int(entry[self.__pb_speed_dial]) 0285 res['numbers']=[] 0286 for k, n in enumerate(self.__pb_numbers): 0287 for key in n: 0288 if len(entry[n[key]]): 0289 if speed_dial==k: 0290 res['numbers'].append({ 'number': entry[n[key]], 0291 'type': key, 0292 'speeddial': int(entry[self.__pb_mem_loc])}) 0293 else: 0294 res['numbers'].append({ 'number': entry[n[key]], 0295 'type': key }) 0296 # done 0297 return res 0298 0299 def savephonebook(self, data): 0300 "Saves out the phonebook" 0301 0302 pb_book=data['phonebook'] 0303 pb_groups=data['groups'] 0304 ringtone_index=data.get('ringtone-index', {}) 0305 self.log('Validating phonebook entries.') 0306 del_entries=[] 0307 for k in pb_book: 0308 if not self.__validate_entry(pb_book[k], pb_groups, ringtone_index): 0309 self.log('Invalid entry, entry will be not be sent.') 0310 del_entries.append(k) 0311 for k in del_entries: 0312 self.log('Deleting entry '+\ 0313 nameparser.getfullname(pb_book[k]['names'][0])) 0314 del pb_book[k] 0315 self._has_duplicate_speeddial(pb_book) 0316 self.log('All entries validated') 0317 0318 pb_locs=[False]*(len(self.__phone_entries_range)+1) 0319 pb_mem=[False]*len(pb_locs) 0320 0321 # get existing phonebook from the phone 0322 self.log("Getting current phonebook from the phone") 0323 current_pb=self._get_phonebook(data) 0324 0325 # check and adjust for speeddial changes 0326 self.log("Processing speeddial data") 0327 for k in pb_book: 0328 self._update_speeddial(pb_book[k]) 0329 0330 # check for deleted entries and delete them 0331 self.setmode(self.MODEPHONEBOOK) 0332 self.log("Processing deleted entries") 0333 0334 for k1 in current_pb: 0335 s1=current_pb[k1]['serials'][0]['serial1'] 0336 found=False 0337 for k2 in pb_book: 0338 if self._same_serial1(s1, pb_book[k2]): 0339 found=True 0340 break 0341 if found: 0342 pb_locs[int(current_pb[k1]['serials'][0]['serial1'])]=True 0343 pb_mem[int(current_pb[k1]['serials'][0]['serial2'])]=True 0344 else: 0345 self.log("Deleted item: "+\ 0346 nameparser.getfullname(current_pb[k1]['names'][0])) 0347 # delete the entries from data and the phone 0348 self.progress(0, 10, "Deleting "+\ 0349 nameparser.getfullname(\ 0350 current_pb[k1]['names'][0])) 0351 self._del_phone_entry(current_pb[k1]) 0352 mem_idx, loc_idx = self.__pb_max_speeddials, 1 0353 0354 # check for new entries & update serials 0355 self.log("Processing new & updated entries") 0356 serials_update=[] 0357 progresscur, progressmax=1,len(pb_book) 0358 for k in pb_book: 0359 if progresscur>len(self.__phone_entries_range): 0360 self.log('Max phone entries exceeded: '+str(progresscur)) 0361 break 0362 e=pb_book[k] 0363 if not self._has_serial1(e): 0364 while pb_locs[loc_idx]: 0365 loc_idx += 1 0366 pb_locs[loc_idx]=True 0367 sd=self._get_speeddial(e) 0368 if sd: 0369 mem_index=sd 0370 pb_mem[sd]=True 0371 else: 0372 while pb_mem[mem_idx]: 0373 mem_idx -= 1 0374 pb_mem[mem_idx]=True 0375 mem_index=mem_idx 0376 self._set_speeddial(e, mem_idx) 0377 s1={ 'sourcetype': self.serialsname, 0378 'sourceuniqueid': data['uniqueserial'], 0379 'serial1': `loc_idx`, 0380 'serial2': `mem_index` } 0381 e['serials'].append(s1) 0382 self.log("New entries: Name: "+\ 0383 nameparser.getfullname(e['names'][0])+", s1: "+`loc_idx`+", s2: "+`mem_index`) 0384 serials_update.append((self._bitpim_serials(e), s1)) 0385 self.progress(progresscur, progressmax, "Updating "+\ 0386 nameparser.getfullname(e['names'][0])) 0387 if not self._write_phone_entry(e, data): 0388 self.log("Failed to save entry: "+\ 0389 nameparser.getfullname(e['names'][0])) 0390 progresscur += 1 0391 0392 data["serialupdates"]=serials_update 0393 self.log("Done") 0394 self.setmode(self.MODEMODEM) 0395 return data 0396 0397 # validate a phonebook entry, return True if good, False otherwise 0398 def __validate_entry(self, pb_entry, pb_groups, ringtone_index): 0399 try: 0400 # validate name 0401 name=nameparser.getfullname(pb_entry['names'][0]).replace('"', '') 0402 if len(name)>self.__pb_max_name_len: 0403 name=name[:self.__pb_max_name_len] 0404 pb_entry['names'][0].setdefault('full', name) 0405 # validate url/alias 0406 url=pb_entry.get('urls', [{}])[0].get('url', None) 0407 if url is not None: 0408 url=re.sub('[,"]', '', url) 0409 if len(url)>self.__pb_max_alias_chars: 0410 url=url[:self.__pb_max_alias_chars] 0411 pb_entry['urls']=[ { 'url': url } ] 0412 # validate numbers 0413 has_number_or_email=False 0414 if pb_entry.has_key('numbers'): 0415 for n in pb_entry['numbers']: 0416 num=self.phonize(n['number']) 0417 if len(num)>self.__pb_max_number_len: 0418 num=num[:self.__pb_max_number_len] 0419 if num != n['number']: 0420 self.log('Updating number from '+n['number']+' to '+num) 0421 n['number']=num 0422 try: 0423 self._get_number_type(n['type']) 0424 except: 0425 self.log(n['number']+': setting type to home.') 0426 n['type']='home' 0427 has_number_or_email=True 0428 # validate emails 0429 if pb_entry.has_key('emails'): 0430 if len(pb_entry['emails'])>self.__pb_max_emails: 0431 self.log(name+': Each entry can only have %s emails. The rest will be ignored.'%str(self.__pb_max_emails)) 0432 email=pb_entry['emails'][0]['email'].replace('"', '') 0433 if len(email)>self.__pb_max_email_chars: 0434 email=email[:self.__pb_max_email_chars] 0435 if email!=pb_entry['emails'][0]['email']: 0436 pb_entry['emails'][0]['email']=email 0437 has_number_or_email=True 0438 if not has_number_or_email: 0439 self.log(name+': Entry has no numbers or emails') 0440 # return False so this entry can be deleted from the dict 0441 return False 0442 # validate groups 0443 found=False 0444 if pb_entry.has_key('categories') and len(pb_entry['categories']): 0445 pb_cat=pb_entry['categories'][0]['category'] 0446 for k in pb_groups: 0447 if pb_groups[k]['name']==pb_cat: 0448 found=True 0449 break 0450 if not found: 0451 self.log(name+': category set to '+pb_groups[0]['name']) 0452 pb_entry['categories']=[{'category': pb_groups[0]['name']}] 0453 # validate ringtones 0454 found=False 0455 if pb_entry.has_key('ringtones') and len(pb_entry['ringtones']): 0456 pb_rt=pb_entry['ringtones'][0]['ringtone'] 0457 for k, rt in ringtone_index.items(): 0458 if pb_rt==rt['name']: 0459 found=True 0460 break 0461 if not found: 0462 rt=ringtone_index[0]['name'] 0463 self.log(name+': ringtone set to '+rt) 0464 pb_entry['ringtones']=[{'ringtone': rt, 0465 'use': 'call' }] 0466 # to to: validate wallpaper 0467 0468 # everything's cool 0469 return True 0470 except: 0471 raise 0472 0473 def _has_duplicate_speeddial(self, pb_book): 0474 b=[False]*(self.__pb_max_speeddials+1) 0475 for k in pb_book: 0476 try: 0477 for k1, kk in enumerate(pb_book[k]['numbers']): 0478 sd=kk['speeddial'] 0479 if sd and b[sd]: 0480 # speed dial is in used, remove this one 0481 del pb_book[k]['numbers'][k1]['speeddial'] 0482 self.log('speeddial %d exists, deleted'%sd) 0483 else: 0484 b[sd]=True 0485 except: 0486 pass 0487 return False 0488 0489 def _update_speeddial(self, pb_entry): 0490 try: 0491 s=self._my_serials(pb_entry) 0492 s1=int(s['serial2']) 0493 sd=self._get_speeddial(pb_entry) 0494 if not sd: 0495 # speed dial not set, set it to current mem slot 0496 self._set_speeddial(pb_entry, s1) 0497 elif sd!=s1: 0498 # speed dial set to a different slot, mark it 0499 self._del_my_serials(pb_entry) 0500 except: 0501 pass 0502 0503 def _get_speeddial(self, pb_entry): 0504 n=pb_entry.get('numbers', []) 0505 for k in n: 0506 try: 0507 if k['speeddial']: 0508 return k['speeddial'] 0509 except: 0510 pass 0511 return 0 0512 0513 def _set_speeddial(self, pb_entry, sd): 0514 if not pb_entry.has_key('numbers'): 0515 # no numbers key, just return 0516 return 0517 for k in pb_entry['numbers']: 0518 if k.has_key('speeddial'): 0519 k['speeddial']=sd 0520 return 0521 pb_entry['numbers'][0]['speeddial']=sd 0522 0523 def _del_phone_entry(self, pb_entry): 0524 try: 0525 return self.save_phone_entry(self._my_serials(pb_entry)['serial1']) 0526 except: 0527 return False 0528 0529 def _same_serial1(self, s1, pb_entry): 0530 for k in pb_entry['serials']: 0531 if k['sourcetype']==self.serialsname and k.has_key('serial1'): 0532 return k['serial1']==s1 0533 return False 0534 0535 def _has_serial1(self, pb_entry): 0536 for k in pb_entry['serials']: 0537 if k['sourcetype']==self.serialsname and k.has_key('serial1'): 0538 return True 0539 return False 0540 0541 def _bitpim_serials(self, pb_entry): 0542 for k in pb_entry['serials']: 0543 if k['sourcetype']=="bitpim": 0544 return k 0545 return {} 0546 0547 def _del_my_serials(self, pb_entry): 0548 for k in range(len(pb_entry['serials'])): 0549 if pb_entry['serials'][k]['sourcetype']==self.serialsname: 0550 del pb_entry['serials'][k] 0551 return 0552 0553 def _my_serials(self, pb_entry): 0554 for k in pb_entry['serials']: 0555 if k['sourcetype']==self.serialsname: 0556 return k 0557 return {} 0558 0559 def _get_number_type(self, type): 0560 n=self.__pb_numbers 0561 for k in range(len(n)): 0562 if n[k].has_key(type): 0563 return k, n[k][type] 0564 raise common.IntegrityCheckFailed(self.desc, "Invalid Number Type") 0565 0566 def _write_phone_entry(self, pb_entry, data): 0567 0568 # setting up a list to send to the phone, all fields preset to '0' 0569 e=['0']*self.__pb_atpbokw_field_count 0570 0571 # setting the entry # and memory location # 0572 serials=self._my_serials(pb_entry) 0573 e[self.__pb_entry]=serials['serial1'] 0574 e[self.__pb_mem_loc]=serials['serial2'] 0575 0576 # groups/categories 0577 groups=data.get('groups', {}) 0578 e[self.__pb_group]='0' 0579 try: 0580 grp_name=pb_entry['categories'][0]['category'] 0581 for k, n in groups.items(): 0582 if n.get('name', None)==grp_name: 0583 e[self.__pb_group]=`k` 0584 break 0585 except: 0586 pass 0587 0588 # ringtones 0589 ringtone_index=data.get('ringtone-index', {}) 0590 e[self.__pb_ringtone]='0' 0591 try: 0592 rt=pb_entry['ringtones'][0]['ringtone'] 0593 for k, n in ringtone_index.items(): 0594 if rt==n.get('name', None): 0595 e[self.__pb_ringtone]=`k` 0596 break 0597 except: 0598 pass 0599 0600 # name & alias/url 0601 e[self.__pb_name]='"'+nameparser.getfullname(\ 0602 pb_entry['names'][0])+'"' 0603 url='' 0604 try: 0605 url=pb_entry['urls'][0]['url'] 0606 except: 0607 pass 0608 e[self.__pb_alias]=url 0609 if len(url): 0610 e[self.__pb_alias+1]='0' 0611 else: 0612 e[self.__pb_alias+1]='' 0613 0614 # numbers & speed dial 0615 # preset to empty 0616 for k in range(len(self.__pb_numbers)): 0617 for kk in self.__pb_numbers[k]: 0618 e[self.__pb_numbers[k][kk]]='' 0619 e[self.__pb_numbers[k][kk]+1]='' 0620 speed_dial='0' 0621 n=pb_entry.get('numbers', []) 0622 for k in range(len(n)): 0623 try: 0624 nk=n[k] 0625 kkk, kk=self._get_number_type(nk['type']) 0626 except: 0627 # invalid type, default to 'home' 0628 nk['type']='home' 0629 kkk, kk=0, self.__pb_home_num 0630 e[kk],e[kk+1]=self.phonize(nk['number']),'0' 0631 try: 0632 if nk['speeddial']: 0633 speed_dial=`kkk` 0634 except: 0635 pass 0636 e[self.__pb_speed_dial]=speed_dial 0637 0638 # email 0639 email='' 0640 try: 0641 email=pb_entry['emails'][0]['email'] 0642 except: 0643 pass 0644 e[self.__pb_email]='"'+email+'"' 0645 0646 # wallpaper: phonebook entry "caller id" image 0647 # Default: No image assigned 0648 e[self.__pb_image_assign]='5' 0649 e[self.__pb_image_id]='0' 0650 e[self.__pb_contact_image]='""' 0651 try: 0652 imgName = pb_entry['wallpapers'][0]['wallpaper'] 0653 image_index=data.get('wallpaper-index', {}) 0654 for k, n in image_index.items(): 0655 if imgName==n.get('name', None): 0656 if k>self.protocolclass.max_image_entries: 0657 # this is a 'Gallery' entry 0658 e[self.__pb_image_assign]='3' 0659 e[self.__pb_contact_image]='"'+self.__wp_photo_dir+'/'+imgName.split('.')[0]+'"' 0660 e[self.__pb_image_id]='0' 0661 else: 0662 # 'My Image' entry 0663 e[self.__pb_image_assign]='4' 0664 e[self.__pb_image_id]=`k` 0665 e[self.__pb_contact_image]='""' 0666 break 0667 except: 0668 pass 0669 0670 for k in self.__pb_blanks: 0671 e[k]='' 0672 0673 e[self.__pb_date_time_stamp]=self.get_time_stamp() 0674 0675 # final check to determine if this entry has changed. 0676 # if it has not then do nothing an just return 0677 ee=self.get_phone_entry(int(e[self.__pb_entry])) 0678 if len(ee)==self.__pb_atpbokw_field_count: 0679 # DSV took the " out, need to put them back in for comparison 0680 ee[self.__pb_name]='"'+ee[self.__pb_name]+'"' 0681 ee[self.__pb_email]='"'+ee[self.__pb_email]+'"' 0682 ee[self.__pb_contact_image]='"'+ee[self.__pb_contact_image]+'"' 0683 # exclude the timestamp field 0684 k=self.__pb_atpbokw_field_count-1 0685 if e[:k]==ee[:k]: 0686 return True 0687 return self.save_phone_entry('0,'+','.join(e)) 0688 0689 def getringtones(self, result): 0690 self.setmode(self.MODEBREW) 0691 m=FileEntries(self, self.__ringtone_info) 0692 rt_info=RingtoneIndex(self).get_download_info() 0693 r=m.get_media(result, rt_info) 0694 self.setmode(self.MODEMODEM) 0695 return r 0696 0697 def saveringtones(self, result, merge): 0698 self.setmode(self.MODEBREW) 0699 m=FileEntries(self, self.__ringtone_info) 0700 result['rebootphone']=1 # So we end up back in AT mode 0701 r=m.save_media(result, RingtoneIndex(self).get_download_info()) 0702 self.setmode(self.MODEMODEM) 0703 return r 0704 0705 def getwallpapers(self, result): 0706 self.setmode(self.MODEBREW) 0707 m=FileEntries(self, self.__wallpaper_info) 0708 img_info=ImageIndex(self).get_download_info() 0709 m.get_media(result, img_info) 0710 FileEntries(self, self.__video0_info).get_video(result, 'Video001.avi') 0711 r=FileEntries(self, self.__video1_info).get_video(result, 'Video002.avi') 0712 self.setmode(self.MODEMODEM) 0713 return r 0714 0715 def savewallpapers(self, result, merge): 0716 self.setmode(self.MODEBREW) 0717 m=FileEntries(self, self.__wallpaper_info) 0718 r=m.save_media(result, ImageIndex(self).get_download_info()) 0719 result['rebootphone']=1 0720 self.setmode(self.MODEMODEM) 0721 return r 0722 0723 getmemo=parent_phone._getmemo 0724 savememo=parent_phone._savememo 0725 0726 gettodo=parent_phone._gettodo 0727 savetodo=parent_phone._savetodo 0728 0729 getsms=parent_phone._getsms 0730 savesms=parent_phone._savesms 0731 0732 getphoneinfo=parent_phone._getphoneinfo 0733 0734 getmedia=None 0735 0736 class Profile(com_samsung.Profile): 0737 0738 serialsname='scha670' 0739 0740 WALLPAPER_WIDTH=128 0741 WALLPAPER_HEIGHT=128 0742 MAX_WALLPAPER_BASENAME_LENGTH=19 0743 WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789_ ." 0744 ## WALLPAPER_CONVERT_FORMAT="bmp" 0745 WALLPAPER_CONVERT_FORMAT="jpg" 0746 MAX_RINGTONE_BASENAME_LENGTH=19 0747 RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789_ ." 0748 RINGTONE_LIMITS= { 0749 'MAXSIZE': 30000 0750 } 0751 # use for auto-detection 0752 phone_manufacturer='SAMSUNG ELECTRONICS' 0753 phone_model='SCH-A670/164' 0754 0755 def __init__(self): 0756 com_samsung.Profile.__init__(self) 0757 0758 _supportedsyncs=( 0759 ('phonebook', 'read', None), # all phonebook reading 0760 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 0761 ('calendar', 'read', None), # all calendar reading 0762 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 0763 ('ringtone', 'read', None), # all ringtone reading 0764 ('ringtone', 'write', 'OVERWRITE'), 0765 ('wallpaper', 'read', None), # all wallpaper reading 0766 ('wallpaper', 'write', 'OVERWRITE'), 0767 ('memo', 'read', None), # all memo list reading DJP 0768 ('memo', 'write', 'OVERWRITE'), # all memo list writing DJP 0769 ('todo', 'read', None), # all todo list reading DJP 0770 ('todo', 'write', 'OVERWRITE'), # all todo list writing DJP 0771 ('sms', 'read', None), # all SMS list reading DJP 0772 ) 0773 0774 if __debug__: 0775 _supportedsyncs+=(('sms', 'write', 'OVERWRITE'),) 0776 0777 def convertphonebooktophone(self, helper, data): 0778 return data 0779 0780 __audio_ext={ 'MIDI': 'mid', 'PMD': 'pmd', 'QCP': 'pmd' } 0781 def QueryAudio(self, origin, currentextension, afi): 0782 # we don't modify any of these 0783 if afi.format in ("MIDI", "PMD", "QCP"): 0784 for k,n in self.RINGTONE_LIMITS.items(): 0785 setattr(afi, k, n) 0786 return currentextension, afi 0787 d=self.RINGTONE_LIMITS.copy() 0788 d['format']='QCP' 0789 return ('pmd', fileinfo.AudioFileInfo(afi, **d)) 0790 0791 imageorigins={} 0792 imageorigins.update(common.getkv(com_samsung.Profile.stockimageorigins, "images")) 0793 0794 imagetargets={} 0795 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "wallpaper", 0796 {'width': 128, 'height': 128, 'format': "PNG"})) 0797 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "fullscreen", 0798 {'width': 128, 'height': 160, 'format': "PNG"})) 0799 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "pictureid", 0800 {'width': 96, 'height': 96, 'format': "JPEG"})) 0801 0802 def GetImageOrigins(self): 0803 # Note: only return origins that you can write back to the phone 0804 return self.imageorigins 0805 0806 def GetTargetsForImageOrigin(self, origin): 0807 # right now, supporting just 'images' origin 0808 if origin=='images': 0809 return self.imagetargets 0810 0811 class FileEntries: 0812 def __init__(self, phone, info): 0813 self.__phone=phone 0814 self.__file_type, self.__index_type, self.__origin, self.__path, self.__max_file_len, self.__max_file_count=info 0815 0816 def get_media(self, result, download_info=None): 0817 self.__phone.log('Getting media for type '+self.__file_type) 0818 if download_info is None: 0819 return self.__get_media_by_dir(result) 0820 else: 0821 return self.__get_media_by_index(result, download_info) 0822 0823 def __get_media_by_dir(self, result): 0824 media=result.get(self.__file_type, {}) 0825 idx=result.get(self.__index_type, {}) 0826 file_cnt, idx_k=0, len(idx) 0827 path_len=len(self.__path)+1 0828 try: 0829 file_list=self.__phone.listfiles(self.__path) 0830 for k in file_list: 0831 try: 0832 index=k[path_len:] 0833 # print k, index 0834 media[index]=self.__phone.getfilecontents(k, True) 0835 idx[idx_k]={ 'name': index, 'origin': self.__origin } 0836 idx_k+=1 0837 file_cnt += 1 0838 except: 0839 self.__phone.log('Failed to read file '+k) 0840 except: 0841 self.__phone.log('Failed to read dir '+self.__path) 0842 result[self.__file_type]=media 0843 result[self.__index_type]=idx 0844 if file_cnt > self.__max_file_count: 0845 self.__phone.log('This phone only supports %d %s. %d %s read, weird things may happen.' % \ 0846 (self.__max_file_count, self.__file_type, 0847 file_cnt, self.__file_type)) 0848 return result 0849 0850 def __get_media_by_index(self, result, rt_info): 0851 media=result.get(self.__file_type, {}) 0852 media_index=result.get(self.__index_type, {}) 0853 mms_img_path=self.__phone.protocolclass.mms_image_path 0854 mms_img_len=len(mms_img_path) 0855 for k, m in media_index.items(): 0856 file_key=m.get('name', None) 0857 file_name=rt_info.get(file_key, None) 0858 if file_key is not None and file_name is not None: 0859 try : 0860 contents=self.__phone.getfilecontents(file_name, True) 0861 img_origin=m.get('origin', None) 0862 if img_origin=='images': 0863 if file_name[:mms_img_len]==mms_img_path: 0864 # mms image file, skip the header 0865 contents=contents[52:] 0866 elif img_origin=='camera': 0867 # Camera pix file, need to be touched up 0868 # patch code to make it work right now, need to recode 0869 m['date']=\ 0870 ( int(contents[47:51]), int(contents[51:53]), 0871 int(contents[53:55]), int(contents[55:57]), 0872 int(contents[57:59])) 0873 contents=contents[96:] 0874 if contents[-1]=='\xff': 0875 contents+='\x00' 0876 media[file_key]=contents 0877 except: 0878 self.__phone.log('Failed to read file '+file_name) 0879 if __debug__: raise 0880 result[self.__file_type]=media 0881 return result 0882 0883 def get_video(self, result, video_file_name): 0884 if not conversions.helperavailable('bmp2avi'): 0885 # helper not available , just bail 0886 self.__phone.log('Helper bmp2avi not found, cannot retrieve '+\ 0887 video_file_name) 0888 return result 0889 self.__phone.log('Getting video file '+video_file_name) 0890 media=result.get(self.__file_type, {}) 0891 idx=result.get(self.__index_type, {}) 0892 tmp_avi_name=common.gettempfilename("avi") 0893 try: 0894 file_list=self.__phone.listfiles(self.__path) 0895 except com_brew.BrewNoSuchDirectoryException: 0896 file_list={} 0897 except: 0898 file_list={} 0899 if __debug__: raise 0900 0901 if not len(file_list): 0902 # empty directory 0903 return result 0904 file_keys=file_list.keys() 0905 file_keys.sort(); 0906 for k in file_keys: 0907 try: 0908 conversions.convertjpgtoavi(self.__phone.getfilecontents(k, True)[96:], 0909 tmp_avi_name) 0910 except: 0911 self.__phone.log('Failed to read video files') 0912 if __debug__: raise 0913 # got the avi file, now prep to send it back 0914 if len(idx): 0915 idx_k=max(idx.keys())+1 0916 else: 0917 idx_k=0 0918 media[video_file_name]=open(tmp_avi_name, 'rb').read() 0919 idx[idx_k]={ 'name': video_file_name, 'origin': 'video' } 0920 result[self.__file_type]=media 0921 result[self.__index_type]=idx 0922 try: 0923 os.remove(tmp_avi_name) 0924 except: 0925 pass 0926 return result 0927 0928 __mms_max_file_name_len=35 0929 def __mms_header(self, img_name, img_type, img_data_len): 0930 if len(img_name)>=self.__mms_max_file_name_len: 0931 name=img_name[:self.__mms_max_file_name_len-1] 0932 else: 0933 name=img_name 0934 return str('\x06'+str(name)+\ 0935 '\x00'*(self.__mms_max_file_name_len-len(name))+\ 0936 common.LSBstr32(img_data_len)+chr(img_type)+\ 0937 '\x00'*11) 0938 def __mms_file_name(self): 0939 now = time.localtime(time.time()) 0940 return '%02d%02d%02d%02d%02d%02d'%((now[0]%2000,)+now[1:6]) 0941 def _to_mms_JPEG(self, img_name, img_data_len): 0942 return (self.__mms_file_name(), 0943 self.__mms_header(img_name, 0, img_data_len)) 0944 def _to_mms_BMP(self, img_name, img_data_len): 0945 return (self.__mms_file_name(), 0946 self.__mms_header(img_name, 1, img_data_len)) 0947 def _to_mms_PNG(self, img_name, img_data_len): 0948 return (self.__mms_file_name(), 0949 self.__mms_header(img_name, 2, img_data_len)) 0950 def _to_mms_GIF(self, img_name, img_data_len): 0951 return (self.__mms_file_name(), 0952 self.__mms_header(img_name, 3, img_data_len)) 0953 0954 def save_media(self, result, dl_info): 0955 self.__phone.log('Saving media for type '+self.__file_type) 0956 media, idx=result[self.__file_type], result[self.__index_type] 0957 # check for files selected for deletion 0958 media_names=[media[k]['name'] for k in media] 0959 dl_info_keys=dl_info.keys() 0960 deleted_keys=[k for k in dl_info_keys if k not in media_names] 0961 new_keys=[k for k in media if media[k]['name'] not in dl_info_keys] 0962 # deleting files 0963 campix_file_path=self.__phone.protocolclass.cam_pix_file_path 0964 campix_file_len=len(campix_file_path) 0965 for k in deleted_keys: 0966 file_name=dl_info[k] 0967 if file_name[:campix_file_len]==campix_file_path: 0968 # campera pix, do nothing 0969 continue 0970 self.__phone.log('Deleting file: '+file_name) 0971 try: 0972 self.__phone.rmfile(file_name) 0973 except: 0974 self.__phone.log('Failed to delete file: '+file_name) 0975 # writing new files 0976 # make sure dir exists to write new files 0977 if len(new_keys): 0978 try: 0979 self.__phone.mkdirs(self.__path) 0980 # yet another hack 0981 self.__phone.mkdirs('brew/shared') 0982 except: 0983 pass 0984 file_count=0 0985 for k in new_keys: 0986 n=media[k] 0987 origin=n.get('origin', None) 0988 if origin is not None and origin != self.__origin: 0989 continue 0990 if len(n['name']) > self.__max_file_len: 0991 self.__phone.log('%s %s name is too long and not sent to phone'% \ 0992 (self.__file_type, n['name'])) 0993 continue 0994 file_count+=1 0995 if file_count>self.__max_file_count: 0996 # max # of files reached, bailing out 0997 self.__phone.log('This phone only supports %d %s. Save operation stopped.'%\ 0998 (self.__max_file_count, self.__file_type)) 0999 break 1000 if self.__origin=='images': 1001 file_info=fileinfo.identify_imagestring(n['data']) 1002 if file_info.format in ('JPEG', 'GIF', 'BMP'): 1003 # jpeg/gif/bmp file/picture ID files 1004 (mms_file_name, file_hdr)=\ 1005 getattr(self, '_to_mms_'+file_info.format)( 1006 n['name'], len(n['data'])) 1007 file_name=self.__path+'/'+mms_file_name 1008 file_contents=file_hdr+n['data'] 1009 elif file_info.format=='PNG': 1010 # wallpaper files 1011 file_name='brew/shared/'+n['name'] 1012 # try to optimize png image files 1013 file_contents=conversions.convertto8bitpng_joe(n['data']) 1014 else: 1015 # unknown file type, skip it 1016 continue 1017 else: 1018 file_name=self.__path+'/'+n['name'] 1019 file_contents=n['data'] 1020 self.__phone.log('Writing file: '+file_name) 1021 try: 1022 self.__phone.writefile(file_name, file_contents) 1023 except: 1024 self.__phone.log('Failed to write file: '+file_name) 1025 media[k]['origin']=self.__origin 1026 1027 return result 1028 1029 class RingtoneIndex: 1030 __builtin_ringtones=( 'Inactive', 1031 'Bell 1', 'Bell 2', 'Bell 3', 'Bell 4', 'Bell 5', 1032 'Melody 1', 'Melody 2', 'Melody 3', 'Melody 4', 'Melody 5', 1033 'Melody 6', 'Melody 7', 'Melody 8', 'Melody 9', 'Melody 10') 1034 1035 def __init__(self, phone): 1036 self.__phone=phone 1037 1038 def get_builtin_index(self): 1039 r={} 1040 for k, n in enumerate(self.__builtin_ringtones): 1041 r[k]={ 'name': n, 'origin': 'builtin' } 1042 return r 1043 1044 def get_download_index(self): 1045 r={} 1046 try: 1047 rt_idx=self.__phone.protocolclass.ringtones() 1048 buf=prototypes.buffer(self.__phone.getfilecontents( \ 1049 self.__phone.protocolclass.ringtone_index_file_name)) 1050 rt_idx.readfrombuffer(buf, logtitle="Read ringtone download index") 1051 idx=len(self.__builtin_ringtones) 1052 l=len(self.__phone.protocolclass.ringtone_file_path)+1 1053 for i in range(self.__phone.protocolclass.max_ringtone_entries): 1054 e=rt_idx.entry[i] 1055 if e.name_len: 1056 r[idx+i]={ 'name': e.file_name[l:e.file_name_len], 1057 'origin': 'ringtone' } 1058 except: 1059 pass 1060 return r 1061 1062 def get(self): 1063 r=self.get_builtin_index() 1064 r.update(self.get_download_index()) 1065 return r 1066 1067 def get_download_info(self): 1068 r={} 1069 try: 1070 rt_idx=self.__phone.protocolclass.ringtones() 1071 buf=prototypes.buffer(self.__phone.getfilecontents( \ 1072 self.__phone.protocolclass.ringtone_index_file_name)) 1073 rt_idx.readfrombuffer(buf, logtitle="Read ringtone download index") 1074 l=len(self.__phone.protocolclass.ringtone_file_path)+1 1075 for i in range(self.__phone.protocolclass.max_ringtone_entries): 1076 e=rt_idx.entry[i] 1077 if e.name_len: 1078 r[e.file_name[l:e.file_name_len]]=e.file_name[:e.file_name_len] 1079 except: 1080 pass 1081 return r 1082 1083 class ImageIndex: 1084 1085 def __init__(self, phone): 1086 self.__phone=phone 1087 1088 def get_download_index(self): 1089 r={} 1090 try: 1091 # first, read 'My Image' index 1092 img_idx=self.__phone.protocolclass.images() 1093 buf=prototypes.buffer(self.__phone.getfilecontents( \ 1094 self.__phone.protocolclass.image_index_file_name)) 1095 img_idx.readfrombuffer(buf, logtitle="Read image download index") 1096 l=len(self.__phone.protocolclass.image_file_path)+1 1097 mms_img_path=self.__phone.protocolclass.mms_image_path 1098 mms_img_len=len(mms_img_path) 1099 for i in range(self.__phone.protocolclass.max_image_entries): 1100 e=img_idx.entry[i] 1101 if e.name_len and e.file_name_len: 1102 if e.file_name[:mms_img_len]==mms_img_path: 1103 # this is an mms_image file 1104 idx_name=e.name[:e.name_len] 1105 else: 1106 # other image files 1107 idx_name=e.file_name[l:e.file_name_len] 1108 r[i]={ 'name': idx_name, 1109 'origin': 'images' } 1110 # then read the camera pix image index ('Gallery') 1111 # starting index should be max_image_entries+1 1112 idx=self.__phone.protocolclass.max_image_entries+1 1113 try: 1114 dir_l=self.__phone.listfiles(\ 1115 self.__phone.protocolclass.cam_pix_file_path) 1116 except com_brew.BrewNoSuchDirectoryException: 1117 dir_l={} 1118 l=len(self.__phone.protocolclass.cam_pix_file_path)+1 1119 for f in dir_l: 1120 r[idx]={ 'name': f[l:]+'.jpg', 'origin': 'camera' } 1121 idx += 1 1122 except: 1123 raise 1124 return r 1125 1126 def get(self): 1127 return self.get_download_index() 1128 1129 def get_download_info(self): 1130 r={} 1131 try: 1132 # first, 'My Image' entries 1133 img_idx=self.__phone.protocolclass.images() 1134 buf=prototypes.buffer(self.__phone.getfilecontents( \ 1135 self.__phone.protocolclass.image_index_file_name)) 1136 img_idx.readfrombuffer(buf, logtitle="Read image download index") 1137 l=len(self.__phone.protocolclass.image_file_path)+1 1138 mms_img_path=self.__phone.protocolclass.mms_image_path 1139 mms_img_len=len(mms_img_path) 1140 for i in range(self.__phone.protocolclass.max_image_entries): 1141 e=img_idx.entry[i] 1142 if e.name_len and e.file_name_len: 1143 if e.file_name[:mms_img_len]==mms_img_path: 1144 idx_name=e.name[:e.name_len] 1145 else: 1146 idx_name=e.file_name[l:e.file_name_len] 1147 r[idx_name]=e.file_name[:e.file_name_len] 1148 # then 'Gallery' entries 1149 try: 1150 dir_l=self.__phone.listfiles(\ 1151 self.__phone.protocolclass.cam_pix_file_path) 1152 except com_brew.BrewNoSuchDirectoryException: 1153 dir_l={} 1154 l=len(self.__phone.protocolclass.cam_pix_file_path)+1 1155 for f in dir_l: 1156 r[f[l:]+'.jpg']=f 1157 except: 1158 pass 1159 return r 1160
Generated by PyXR 0.9.4