Package phones :: Module com_samsungscha670
[hide private]
[frames] | no frames]

Source Code for Module phones.com_samsungscha670

   1  ### BITPIM 
   2  ### 
   3  ### Copyright (C) 2004 Vic Heintz <vheintz@rochester.rr.com> 
   4  ### Copyright (C) 2004 Joe Pham <djpham@netzero.com> 
   5  ### 
   6  ### This program is free software; you can redistribute it and/or modify 
   7  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
   8  ### 
   9   
  10  ### $Id: com_samsungscha670.py 4365 2007-08-17 21:11:59Z djpham $ 
  11   
  12  ### The bulk of this code was borrowed from com_samsungscha650.py 
  13  ### and modified so that operations work with the sch-a670. 
  14   
  15  """Communicate with a Samsung SCH-A670""" 
  16   
  17  # lib modules 
  18  import re 
  19  import sha 
  20  import time 
  21   
  22  # my modules 
  23  import common 
  24  import commport 
  25  import com_brew 
  26  import com_samsung 
  27  import com_phone 
  28  import conversions 
  29  import fileinfo 
  30  import nameparser 
  31  import p_samsungscha670 
  32  import prototypes 
  33   
  34   
35 -class Phone(com_samsung.Phone):
36 "Talk to the Samsung SCH-A670 Cell Phone" 37 38 desc="SCH-A670" 39 serialsname='scha670' 40 protocolclass=p_samsungscha670 41 parent_phone=com_samsung.Phone 42 43 __groups_range=xrange(5) 44 __phone_entries_range=xrange(1,501) 45 __pb_atpbokw_field_count=26 # This is a rename from __pb_max_entries in a650 code 46 # which I found confusing 47 __pb_max_speeddials=500 48 __pb_entry=0 49 __pb_mem_loc=1 50 __pb_group=2 51 __pb_ringtone=3 52 __pb_name=4 53 __pb_speed_dial=5 54 __pb_home_num=7 55 __pb_office_num=9 56 __pb_mobile_num=11 57 __pb_pager_num=13 58 __pb_fax_num=15 59 __pb_alias=17 60 __pb_blanks=(19, 20) 61 __pb_email=21 62 __pb_image_assign=22 # 3, 4 or 5 depending on if picture ID assigned 63 __pb_image_id=23 # when above is 4, has caller-id wallpaper id 64 __pb_contact_image=24 # Path to "Picture ID" image 65 __pb_date_time_stamp=25 66 __pb_numbers= ({'home': __pb_home_num}, 67 {'office': __pb_office_num}, 68 {'cell': __pb_mobile_num}, 69 {'pager': __pb_pager_num}, 70 {'fax': __pb_fax_num}) 71 __pb_max_name_len=22 72 __pb_max_number_len=32 73 __pb_max_emails=1 74 __pb_max_email_chars=48 # Not currently used, but shouldn't it be? 75 __pb_max_alias_chars=48 # Not currently used, but shouldn't it be? 76 __wp_photo_dir="digital_cam" 77 __wp_header_bytes=96 # Extra bytes in front of jpg 78 __wp_ts_offset=47 # Timestamp in yyyymmddHHMMSS format starts here 79 __wp_index_file = "nvm/nvm/brew_image" 80 __rt_index_file = "nvm/nvm/brew_melody" 81 __rt_dir = "brew/ringer" 82 __wp_dir = "brew/shared" 83 84 # The extra "User" entries are a temporary fix to allow round trip integrity of user 85 # assigned ringtones for caller ID. Need to read /nvm/nvm/brew_melody to get true names 86 # of ringtones. 87 builtinringtones=( 'Inactive', 88 'Bell 1', 'Bell 2', 'Bell 3', 'Bell 4', 'Bell 5', 89 'Melody 1', 'Melody 2', 'Melody 3', 'Melody 4', 'Melody 5', 90 'Melody 6', 'Melody 7', 'Melody 8', 'Melody 9', 'Melody 10', 91 'User 00', 'User 01', 'User 02', 'User 03', 'User 04', 'User 05', 92 'User 06', 'User 07', 'User 08', 'User 09', 'User 10', 'User 11', 93 'User 12', 'User 13', 'User 14', 'User 15', 'User 16', 'User 17', 94 'User 18', 'User 19') 95 allringtones={} 96 97 # 'type name', 'type index name', 'origin', 'dir path', 'max file name length', 'max file name count' 98 __ringtone_info=('ringtone', 'ringtone-index', 'ringtone', 'brew/ringer', 19, 20) 99 __wallpaper_info=('wallpapers', 'wallpaper-index', 'images', 'mms_image', 19, 10) 100 ## __wallpaper_info=('wallpapers', 'wallpaper-index', 'wallpapers', 'brew/shared', 19, 10) 101 __camerapix_info=('wallpapers', 'wallpaper-index', 'camera', 'digital_cam', 19, 40) 102 __video0_info=('wallpapers', 'wallpaper-index', 'video', 'camcoder0', None, None) 103 __video1_info=('wallpapers', 'wallpaper-index', 'video', 'camcoder1', None, None) 104
105 - def __init__(self, logtarget, commport):
106 107 "Calls all the constructors and sets initial modes" 108 com_samsung.Phone.__init__(self, logtarget, commport) 109 self.mode=self.MODENONE
110
111 - def getfundamentals(self, results):
112 113 """Gets information fundamental to interoperating with the phone and UI. 114 115 Currently this is: 116 117 - 'uniqueserial' a unique serial number representing the phone 118 - 'groups' the phonebook groups 119 - 'wallpaper-index' map index numbers to names 120 - 'ringtone-index' map index numbers to ringtone names 121 122 This method is called before we read the phonebook data or before we 123 write phonebook data. 124 """ 125 126 self.setmode(self.MODEPHONEBOOK) 127 128 # use a hash of ESN and other stuff (being paranoid) 129 130 self.log("Retrieving fundamental phone information") 131 self.log("Reading phone serial number") 132 results['uniqueserial']=sha.new(self.get_esn()).hexdigest() 133 134 # now read groups 135 136 self.log("Reading group information") 137 g=self.get_groups(self.__groups_range) 138 groups={} 139 for i, g_i in enumerate(g): 140 if len(g_i): 141 groups[i]={ 'name': g_i } 142 results['groups']=groups 143 144 # getting rintone-index 145 self.setmode(self.MODEBREW) 146 rt_index=RingtoneIndex(self) 147 results['ringtone-index']=rt_index.get() 148 149 # getting wallpaper-index 150 img_index=ImageIndex(self) 151 results['wallpaper-index']=img_index.get() 152 153 self.setmode(self.MODEMODEM) 154 self.log("Fundamentals retrieved") 155 156 return results
157
158 - def get_builtin_ringtone_index(self): # Joe's version
159 r={} 160 for k, n in enumerate(self.builtinringtones): 161 r[k]={ 'name': n, 'origin': 'builtin' } 162 return r
163
164 - def ATget_builtin_ringtone_index(self): # Vic's version using AT command
165 r={} 166 s=self.comm.sendatcommand("#PUGSN?") 167 for rt in s[1:]: 168 this_r = rt.split(",") 169 r[int(this_r[0])] = { 'name': this_r[2], 'origin': 'builtin' } 170 self.allringtones[int(this_r[0])] = this_r[2] 171 return r 172
173 - def get_user_rt_index(self, r): # IDs on phone needed for caller-ID
174 bi_cnt = len(r) 175 rtlist = self.getfilecontents(self.__rt_index_file) 176 offset=0 177 while offset < (40 * 77): 178 rtid = ord(rtlist[offset+1]) 179 rtlen = ord(rtlist[offset+74]) 180 rtname = rtlist[offset+23:offset+23+rtlen][len(self.__rt_dir)+1:] 181 if rtlen > 0: 182 r[rtid + bi_cnt] = { 'name': rtname, 'origin': 'ringtone' } 183 self.allringtones[rtid + bi_cnt] = rtname 184 offset+=77 185 return r 186
187 - def get_wallpaper_index(self): # IDs on phone needed for caller-ID
188 wpi = {} 189 imglist = self.getfilecontents(self.__wp_index_file) 190 offset=0 191 while offset < (30 * 76): 192 imgid = ord(imglist[offset+1]) 193 imglen = ord(imglist[offset+73]) 194 imgname = imglist[offset+22:offset+22+imglen][len(self.__wp_dir)+1:] 195 if imglen > 0: 196 wpi[imgid] = { 'name': imgname, 'origin': 'wallpaper' } 197 offset+=76 198 return wpi 199
200 - def _get_phonebook(self, result, show_progress=True):
201 """Reads the phonebook data. The L{getfundamentals} information will 202 already be in result.""" 203 204 self.setmode(self.MODEPHONEBOOK) 205 c=len(self.__phone_entries_range) 206 k=0 207 pb_book={} 208 for j in self.__phone_entries_range: 209 # print "Getting entry: ", j 210 pb_entry=self.get_phone_entry(j) 211 if len(pb_entry)==self.__pb_atpbokw_field_count: 212 pb_book[k]=self._extract_phone_entry(pb_entry, result) 213 k+=1 214 # print pb_book[k], i 215 if show_progress: 216 self.progress(j, c, 'Reading '+pb_entry[self.__pb_name]) 217 else: 218 if show_progress: 219 self.progress(j, c, 'Blank entry: %d' % j) 220 self.setmode(self.MODEMODEM) 221 222 return pb_book
223
224 - def getphonebook(self,result):
225 """Reads the phonebook data. The L{getfundamentals} information will 226 already be in result.""" 227 pb_book=self._get_phonebook(result) 228 result['phonebook']=pb_book 229 return pb_book
230
231 - def _extract_phone_entry(self, entry, fundamentals):
232 233 res={} 234 # serials 235 res['serials']=[ {'sourcetype': self.serialsname, 236 'sourceuniqueid': fundamentals['uniqueserial'], 237 'serial1': entry[self.__pb_entry], 238 'serial2': entry[self.__pb_mem_loc] }] 239 # only one name 240 res['names']=[ {'full': unicode(entry[self.__pb_name].replace('"', ''), 241 errors='ignore') } ] 242 if len(entry[self.__pb_alias]): 243 res['urls']=[ {'url': entry[self.__pb_alias] } ] 244 245 # only one category 246 g=fundamentals['groups'] 247 i=int(entry[self.__pb_group]) 248 res['categories']=[ {'category': g[i]['name'] } ] 249 250 # emails 251 s=entry[self.__pb_email].replace('"', '') 252 if len(s): 253 res['emails']=[ { 'email': s } ] 254 255 # urls 256 # private 257 # memos 258 259 # wallpapers 260 if entry[self.__pb_image_assign]=='3': 261 # the image is 'Gallery' with the string 'digital_cam/Imagexxx' 262 res['wallpapers']=[ { 'wallpaper': entry[self.__pb_contact_image].split( "/")[1]+".jpg", 263 'use': 'call' } ] 264 elif entry[self.__pb_image_assign]=='4': 265 # the entry is an index into wallpaper-index 266 wp_index=fundamentals.get('wallpaper-index', {}) 267 try: 268 res['wallpapers']=[{ 'wallpaper': wp_index[int(entry[self.__pb_image_id])]['name'], 269 'use': 'call'} ] 270 except: 271 pass 272 273 # ringtones 274 try: 275 rt_index=fundamentals.get('ringtone-index', {}) 276 res['ringtones']=[ { 'ringtone': rt_index[int(entry[self.__pb_ringtone])]['name'], 277 'use': 'call' } ] 278 except: 279 res['ringtones']=[ { 'ringtone': self.builtinringtones[0], 280 'use': 'call' } ] 281 282 283 # numbers 284 speed_dial=int(entry[self.__pb_speed_dial]) 285 res['numbers']=[] 286 for k, n in enumerate(self.__pb_numbers): 287 for key in n: 288 if len(entry[n[key]]): 289 if speed_dial==k: 290 res['numbers'].append({ 'number': entry[n[key]], 291 'type': key, 292 'speeddial': int(entry[self.__pb_mem_loc])}) 293 else: 294 res['numbers'].append({ 'number': entry[n[key]], 295 'type': key }) 296 # done 297 return res
298
299 - def savephonebook(self, data):
300 "Saves out the phonebook" 301 302 pb_book=data['phonebook'] 303 pb_groups=data['groups'] 304 ringtone_index=data.get('ringtone-index', {}) 305 self.log('Validating phonebook entries.') 306 del_entries=[] 307 for k in pb_book: 308 if not self.__validate_entry(pb_book[k], pb_groups, ringtone_index): 309 self.log('Invalid entry, entry will be not be sent.') 310 del_entries.append(k) 311 for k in del_entries: 312 self.log('Deleting entry '+\ 313 nameparser.getfullname(pb_book[k]['names'][0])) 314 del pb_book[k] 315 self._has_duplicate_speeddial(pb_book) 316 self.log('All entries validated') 317 318 pb_locs=[False]*(len(self.__phone_entries_range)+1) 319 pb_mem=[False]*len(pb_locs) 320 321 # get existing phonebook from the phone 322 self.log("Getting current phonebook from the phone") 323 current_pb=self._get_phonebook(data) 324 325 # check and adjust for speeddial changes 326 self.log("Processing speeddial data") 327 for k in pb_book: 328 self._update_speeddial(pb_book[k]) 329 330 # check for deleted entries and delete them 331 self.setmode(self.MODEPHONEBOOK) 332 self.log("Processing deleted entries") 333 334 for k1 in current_pb: 335 s1=current_pb[k1]['serials'][0]['serial1'] 336 found=False 337 for k2 in pb_book: 338 if self._same_serial1(s1, pb_book[k2]): 339 found=True 340 break 341 if found: 342 pb_locs[int(current_pb[k1]['serials'][0]['serial1'])]=True 343 pb_mem[int(current_pb[k1]['serials'][0]['serial2'])]=True 344 else: 345 self.log("Deleted item: "+\ 346 nameparser.getfullname(current_pb[k1]['names'][0])) 347 # delete the entries from data and the phone 348 self.progress(0, 10, "Deleting "+\ 349 nameparser.getfullname(\ 350 current_pb[k1]['names'][0])) 351 self._del_phone_entry(current_pb[k1]) 352 mem_idx, loc_idx = self.__pb_max_speeddials, 1 353 354 # check for new entries & update serials 355 self.log("Processing new & updated entries") 356 serials_update=[] 357 progresscur, progressmax=1,len(pb_book) 358 for k in pb_book: 359 if progresscur>len(self.__phone_entries_range): 360 self.log('Max phone entries exceeded: '+str(progresscur)) 361 break 362 e=pb_book[k] 363 if not self._has_serial1(e): 364 while pb_locs[loc_idx]: 365 loc_idx += 1 366 pb_locs[loc_idx]=True 367 sd=self._get_speeddial(e) 368 if sd: 369 mem_index=sd 370 pb_mem[sd]=True 371 else: 372 while pb_mem[mem_idx]: 373 mem_idx -= 1 374 pb_mem[mem_idx]=True 375 mem_index=mem_idx 376 self._set_speeddial(e, mem_idx) 377 s1={ 'sourcetype': self.serialsname, 378 'sourceuniqueid': data['uniqueserial'], 379 'serial1': `loc_idx`, 380 'serial2': `mem_index` } 381 e['serials'].append(s1) 382 self.log("New entries: Name: "+\ 383 nameparser.getfullname(e['names'][0])+", s1: "+`loc_idx`+", s2: "+`mem_index`) 384 serials_update.append((self._bitpim_serials(e), s1)) 385 self.progress(progresscur, progressmax, "Updating "+\ 386 nameparser.getfullname(e['names'][0])) 387 if not self._write_phone_entry(e, data): 388 self.log("Failed to save entry: "+\ 389 nameparser.getfullname(e['names'][0])) 390 progresscur += 1 391 392 data["serialupdates"]=serials_update 393 self.log("Done") 394 self.setmode(self.MODEMODEM) 395 return data
396 397 # validate a phonebook entry, return True if good, False otherwise
398 - def __validate_entry(self, pb_entry, pb_groups, ringtone_index):
399 try: 400 # validate name 401 name=nameparser.getfullname(pb_entry['names'][0]).replace('"', '') 402 if len(name)>self.__pb_max_name_len: 403 name=name[:self.__pb_max_name_len] 404 pb_entry['names'][0].setdefault('full', name) 405 # validate url/alias 406 url=pb_entry.get('urls', [{}])[0].get('url', None) 407 if url is not None: 408 url=re.sub('[,"]', '', url) 409 if len(url)>self.__pb_max_alias_chars: 410 url=url[:self.__pb_max_alias_chars] 411 pb_entry['urls']=[ { 'url': url } ] 412 # validate numbers 413 has_number_or_email=False 414 if pb_entry.has_key('numbers'): 415 for n in pb_entry['numbers']: 416 num=self.phonize(n['number']) 417 if len(num)>self.__pb_max_number_len: 418 num=num[:self.__pb_max_number_len] 419 if num != n['number']: 420 self.log('Updating number from '+n['number']+' to '+num) 421 n['number']=num 422 try: 423 self._get_number_type(n['type']) 424 except: 425 self.log(n['number']+': setting type to home.') 426 n['type']='home' 427 has_number_or_email=True 428 # validate emails 429 if pb_entry.has_key('emails'): 430 if len(pb_entry['emails'])>self.__pb_max_emails: 431 self.log(name+': Each entry can only have %s emails. The rest will be ignored.'%str(self.__pb_max_emails)) 432 email=pb_entry['emails'][0]['email'].replace('"', '') 433 if len(email)>self.__pb_max_email_chars: 434 email=email[:self.__pb_max_email_chars] 435 if email!=pb_entry['emails'][0]['email']: 436 pb_entry['emails'][0]['email']=email 437 has_number_or_email=True 438 if not has_number_or_email: 439 self.log(name+': Entry has no numbers or emails') 440 # return False so this entry can be deleted from the dict 441 return False 442 # validate groups 443 found=False 444 if pb_entry.has_key('categories') and len(pb_entry['categories']): 445 pb_cat=pb_entry['categories'][0]['category'] 446 for k in pb_groups: 447 if pb_groups[k]['name']==pb_cat: 448 found=True 449 break 450 if not found: 451 self.log(name+': category set to '+pb_groups[0]['name']) 452 pb_entry['categories']=[{'category': pb_groups[0]['name']}] 453 # validate ringtones 454 found=False 455 if pb_entry.has_key('ringtones') and len(pb_entry['ringtones']): 456 pb_rt=pb_entry['ringtones'][0]['ringtone'] 457 for k, rt in ringtone_index.items(): 458 if pb_rt==rt['name']: 459 found=True 460 break 461 if not found: 462 rt=ringtone_index[0]['name'] 463 self.log(name+': ringtone set to '+rt) 464 pb_entry['ringtones']=[{'ringtone': rt, 465 'use': 'call' }] 466 # to to: validate wallpaper 467 468 # everything's cool 469 return True 470 except: 471 raise
472
473 - def _has_duplicate_speeddial(self, pb_book):
474 b=[False]*(self.__pb_max_speeddials+1) 475 for k in pb_book: 476 try: 477 for k1, kk in enumerate(pb_book[k]['numbers']): 478 sd=kk['speeddial'] 479 if sd and b[sd]: 480 # speed dial is in used, remove this one 481 del pb_book[k]['numbers'][k1]['speeddial'] 482 self.log('speeddial %d exists, deleted'%sd) 483 else: 484 b[sd]=True 485 except: 486 pass 487 return False
488
489 - def _update_speeddial(self, pb_entry):
490 try: 491 s=self._my_serials(pb_entry) 492 s1=int(s['serial2']) 493 sd=self._get_speeddial(pb_entry) 494 if not sd: 495 # speed dial not set, set it to current mem slot 496 self._set_speeddial(pb_entry, s1) 497 elif sd!=s1: 498 # speed dial set to a different slot, mark it 499 self._del_my_serials(pb_entry) 500 except: 501 pass
502
503 - def _get_speeddial(self, pb_entry):
504 n=pb_entry.get('numbers', []) 505 for k in n: 506 try: 507 if k['speeddial']: 508 return k['speeddial'] 509 except: 510 pass 511 return 0
512
513 - def _set_speeddial(self, pb_entry, sd):
514 if not pb_entry.has_key('numbers'): 515 # no numbers key, just return 516 return 517 for k in pb_entry['numbers']: 518 if k.has_key('speeddial'): 519 k['speeddial']=sd 520 return 521 pb_entry['numbers'][0]['speeddial']=sd
522
523 - def _del_phone_entry(self, pb_entry):
524 try: 525 return self.save_phone_entry(self._my_serials(pb_entry)['serial1']) 526 except: 527 return False
528
529 - def _same_serial1(self, s1, pb_entry):
530 for k in pb_entry['serials']: 531 if k['sourcetype']==self.serialsname and k.has_key('serial1'): 532 return k['serial1']==s1 533 return False
534
535 - def _has_serial1(self, pb_entry):
536 for k in pb_entry['serials']: 537 if k['sourcetype']==self.serialsname and k.has_key('serial1'): 538 return True 539 return False
540
541 - def _bitpim_serials(self, pb_entry):
542 for k in pb_entry['serials']: 543 if k['sourcetype']=="bitpim": 544 return k 545 return {}
546
547 - def _del_my_serials(self, pb_entry):
548 for k in range(len(pb_entry['serials'])): 549 if pb_entry['serials'][k]['sourcetype']==self.serialsname: 550 del pb_entry['serials'][k] 551 return
552
553 - def _my_serials(self, pb_entry):
554 for k in pb_entry['serials']: 555 if k['sourcetype']==self.serialsname: 556 return k 557 return {}
558
559 - def _get_number_type(self, type):
560 n=self.__pb_numbers 561 for k in range(len(n)): 562 if n[k].has_key(type): 563 return k, n[k][type] 564 raise common.IntegrityCheckFailed(self.desc, "Invalid Number Type")
565
566 - def _write_phone_entry(self, pb_entry, data):
567 568 # setting up a list to send to the phone, all fields preset to '0' 569 e=['0']*self.__pb_atpbokw_field_count 570 571 # setting the entry # and memory location # 572 serials=self._my_serials(pb_entry) 573 e[self.__pb_entry]=serials['serial1'] 574 e[self.__pb_mem_loc]=serials['serial2'] 575 576 # groups/categories 577 groups=data.get('groups', {}) 578 e[self.__pb_group]='0' 579 try: 580 grp_name=pb_entry['categories'][0]['category'] 581 for k, n in groups.items(): 582 if n.get('name', None)==grp_name: 583 e[self.__pb_group]=`k` 584 break 585 except: 586 pass 587 588 # ringtones 589 ringtone_index=data.get('ringtone-index', {}) 590 e[self.__pb_ringtone]='0' 591 try: 592 rt=pb_entry['ringtones'][0]['ringtone'] 593 for k, n in ringtone_index.items(): 594 if rt==n.get('name', None): 595 e[self.__pb_ringtone]=`k` 596 break 597 except: 598 pass 599 600 # name & alias/url 601 e[self.__pb_name]='"'+nameparser.getfullname(\ 602 pb_entry['names'][0])+'"' 603 url='' 604 try: 605 url=pb_entry['urls'][0]['url'] 606 except: 607 pass 608 e[self.__pb_alias]=url 609 if len(url): 610 e[self.__pb_alias+1]='0' 611 else: 612 e[self.__pb_alias+1]='' 613 614 # numbers & speed dial 615 # preset to empty 616 for k in range(len(self.__pb_numbers)): 617 for kk in self.__pb_numbers[k]: 618 e[self.__pb_numbers[k][kk]]='' 619 e[self.__pb_numbers[k][kk]+1]='' 620 speed_dial='0' 621 n=pb_entry.get('numbers', []) 622 for k in range(len(n)): 623 try: 624 nk=n[k] 625 kkk, kk=self._get_number_type(nk['type']) 626 except: 627 # invalid type, default to 'home' 628 nk['type']='home' 629 kkk, kk=0, self.__pb_home_num 630 e[kk],e[kk+1]=self.phonize(nk['number']),'0' 631 try: 632 if nk['speeddial']: 633 speed_dial=`kkk` 634 except: 635 pass 636 e[self.__pb_speed_dial]=speed_dial 637 638 # email 639 email='' 640 try: 641 email=pb_entry['emails'][0]['email'] 642 except: 643 pass 644 e[self.__pb_email]='"'+email+'"' 645 646 # wallpaper: phonebook entry "caller id" image 647 # Default: No image assigned 648 e[self.__pb_image_assign]='5' 649 e[self.__pb_image_id]='0' 650 e[self.__pb_contact_image]='""' 651 try: 652 imgName = pb_entry['wallpapers'][0]['wallpaper'] 653 image_index=data.get('wallpaper-index', {}) 654 for k, n in image_index.items(): 655 if imgName==n.get('name', None): 656 if k>self.protocolclass.max_image_entries: 657 # this is a 'Gallery' entry 658 e[self.__pb_image_assign]='3' 659 e[self.__pb_contact_image]='"'+self.__wp_photo_dir+'/'+imgName.split('.')[0]+'"' 660 e[self.__pb_image_id]='0' 661 else: 662 # 'My Image' entry 663 e[self.__pb_image_assign]='4' 664 e[self.__pb_image_id]=`k` 665 e[self.__pb_contact_image]='""' 666 break 667 except: 668 pass 669 670 for k in self.__pb_blanks: 671 e[k]='' 672 673 e[self.__pb_date_time_stamp]=self.get_time_stamp() 674 675 # final check to determine if this entry has changed. 676 # if it has not then do nothing an just return 677 ee=self.get_phone_entry(int(e[self.__pb_entry])) 678 if len(ee)==self.__pb_atpbokw_field_count: 679 # DSV took the " out, need to put them back in for comparison 680 ee[self.__pb_name]='"'+ee[self.__pb_name]+'"' 681 ee[self.__pb_email]='"'+ee[self.__pb_email]+'"' 682 ee[self.__pb_contact_image]='"'+ee[self.__pb_contact_image]+'"' 683 # exclude the timestamp field 684 k=self.__pb_atpbokw_field_count-1 685 if e[:k]==ee[:k]: 686 return True 687 return self.save_phone_entry('0,'+','.join(e))
688
689 - def getringtones(self, result):
690 self.setmode(self.MODEBREW) 691 m=FileEntries(self, self.__ringtone_info) 692 rt_info=RingtoneIndex(self).get_download_info() 693 r=m.get_media(result, rt_info) 694 self.setmode(self.MODEMODEM) 695 return r
696
697 - def saveringtones(self, result, merge):
698 self.setmode(self.MODEBREW) 699 m=FileEntries(self, self.__ringtone_info) 700 result['rebootphone']=1 # So we end up back in AT mode 701 r=m.save_media(result, RingtoneIndex(self).get_download_info()) 702 self.setmode(self.MODEMODEM) 703 return r
704
705 - def getwallpapers(self, result):
706 self.setmode(self.MODEBREW) 707 m=FileEntries(self, self.__wallpaper_info) 708 img_info=ImageIndex(self).get_download_info() 709 m.get_media(result, img_info) 710 FileEntries(self, self.__video0_info).get_video(result, 'Video001.avi') 711 r=FileEntries(self, self.__video1_info).get_video(result, 'Video002.avi') 712 self.setmode(self.MODEMODEM) 713 return r
714
715 - def savewallpapers(self, result, merge):
716 self.setmode(self.MODEBREW) 717 m=FileEntries(self, self.__wallpaper_info) 718 r=m.save_media(result, ImageIndex(self).get_download_info()) 719 result['rebootphone']=1 720 self.setmode(self.MODEMODEM) 721 return r
722 723 getmemo=parent_phone._getmemo 724 savememo=parent_phone._savememo 725 726 gettodo=parent_phone._gettodo 727 savetodo=parent_phone._savetodo 728 729 getsms=parent_phone._getsms 730 savesms=parent_phone._savesms 731 732 getphoneinfo=parent_phone._getphoneinfo 733 734 getmedia=None 735
736 -class Profile(com_samsung.Profile):
737 738 serialsname='scha670' 739 740 WALLPAPER_WIDTH=128 741 WALLPAPER_HEIGHT=128 742 MAX_WALLPAPER_BASENAME_LENGTH=19 743 WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789_ ." 744 ## WALLPAPER_CONVERT_FORMAT="bmp" 745 WALLPAPER_CONVERT_FORMAT="jpg" 746 MAX_RINGTONE_BASENAME_LENGTH=19 747 RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789_ ." 748 RINGTONE_LIMITS= { 749 'MAXSIZE': 30000 750 } 751 # use for auto-detection 752 phone_manufacturer='SAMSUNG ELECTRONICS' 753 phone_model='SCH-A670/164' 754
755 - def __init__(self):
757 758 _supportedsyncs=( 759 ('phonebook', 'read', None), # all phonebook reading 760 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 761 ('calendar', 'read', None), # all calendar reading 762 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 763 ('ringtone', 'read', None), # all ringtone reading 764 ('ringtone', 'write', 'OVERWRITE'), 765 ('wallpaper', 'read', None), # all wallpaper reading 766 ('wallpaper', 'write', 'OVERWRITE'), 767 ('memo', 'read', None), # all memo list reading DJP 768 ('memo', 'write', 'OVERWRITE'), # all memo list writing DJP 769 ('todo', 'read', None), # all todo list reading DJP 770 ('todo', 'write', 'OVERWRITE'), # all todo list writing DJP 771 ('sms', 'read', None), # all SMS list reading DJP 772 ) 773 774 if __debug__: 775 _supportedsyncs+=(('sms', 'write', 'OVERWRITE'),) 776
777 - def convertphonebooktophone(self, helper, data):
778 return data
779 780 __audio_ext={ 'MIDI': 'mid', 'PMD': 'pmd', 'QCP': 'pmd' }
781 - def QueryAudio(self, origin, currentextension, afi):
782 # we don't modify any of these 783 if afi.format in ("MIDI", "PMD", "QCP"): 784 for k,n in self.RINGTONE_LIMITS.items(): 785 setattr(afi, k, n) 786 return currentextension, afi 787 d=self.RINGTONE_LIMITS.copy() 788 d['format']='QCP' 789 return ('pmd', fileinfo.AudioFileInfo(afi, **d))
790 791 imageorigins={} 792 imageorigins.update(common.getkv(com_samsung.Profile.stockimageorigins, "images")) 793 794 imagetargets={} 795 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "wallpaper", 796 {'width': 128, 'height': 128, 'format': "PNG"})) 797 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "fullscreen", 798 {'width': 128, 'height': 160, 'format': "PNG"})) 799 imagetargets.update(common.getkv(com_samsung.Profile.stockimagetargets, "pictureid", 800 {'width': 96, 'height': 96, 'format': "JPEG"})) 801
802 - def GetImageOrigins(self):
803 # Note: only return origins that you can write back to the phone 804 return self.imageorigins
805
806 - def GetTargetsForImageOrigin(self, origin):
807 # right now, supporting just 'images' origin 808 if origin=='images': 809 return self.imagetargets
810
811 -class FileEntries:
812 - def __init__(self, phone, info):
813 self.__phone=phone 814 self.__file_type, self.__index_type, self.__origin, self.__path, self.__max_file_len, self.__max_file_count=info
815
816 - def get_media(self, result, download_info=None):
817 self.__phone.log('Getting media for type '+self.__file_type) 818 if download_info is None: 819 return self.__get_media_by_dir(result) 820 else: 821 return self.__get_media_by_index(result, download_info)
822
823 - def __get_media_by_dir(self, result):
824 media=result.get(self.__file_type, {}) 825 idx=result.get(self.__index_type, {}) 826 file_cnt, idx_k=0, len(idx) 827 path_len=len(self.__path)+1 828 try: 829 file_list=self.__phone.listfiles(self.__path) 830 for k in file_list: 831 try: 832 index=k[path_len:] 833 # print k, index 834 media[index]=self.__phone.getfilecontents(k, True) 835 idx[idx_k]={ 'name': index, 'origin': self.__origin } 836 idx_k+=1 837 file_cnt += 1 838 except: 839 self.__phone.log('Failed to read file '+k) 840 except: 841 self.__phone.log('Failed to read dir '+self.__path) 842 result[self.__file_type]=media 843 result[self.__index_type]=idx 844 if file_cnt > self.__max_file_count: 845 self.__phone.log('This phone only supports %d %s. %d %s read, weird things may happen.' % \ 846 (self.__max_file_count, self.__file_type, 847 file_cnt, self.__file_type)) 848 return result
849
850 - def __get_media_by_index(self, result, rt_info):
851 media=result.get(self.__file_type, {}) 852 media_index=result.get(self.__index_type, {}) 853 mms_img_path=self.__phone.protocolclass.mms_image_path 854 mms_img_len=len(mms_img_path) 855 for k, m in media_index.items(): 856 file_key=m.get('name', None) 857 file_name=rt_info.get(file_key, None) 858 if file_key is not None and file_name is not None: 859 try : 860 contents=self.__phone.getfilecontents(file_name, True) 861 img_origin=m.get('origin', None) 862 if img_origin=='images': 863 if file_name[:mms_img_len]==mms_img_path: 864 # mms image file, skip the header 865 contents=contents[52:] 866 elif img_origin=='camera': 867 # Camera pix file, need to be touched up 868 # patch code to make it work right now, need to recode 869 m['date']=\ 870 ( int(contents[47:51]), int(contents[51:53]), 871 int(contents[53:55]), int(contents[55:57]), 872 int(contents[57:59])) 873 contents=contents[96:] 874 if contents[-1]=='\xff': 875 contents+='\x00' 876 media[file_key]=contents 877 except: 878 self.__phone.log('Failed to read file '+file_name) 879 if __debug__: raise 880 result[self.__file_type]=media 881 return result
882
883 - def get_video(self, result, video_file_name):
884 if not conversions.helperavailable('bmp2avi'): 885 # helper not available , just bail 886 self.__phone.log('Helper bmp2avi not found, cannot retrieve '+\ 887 video_file_name) 888 return result 889 self.__phone.log('Getting video file '+video_file_name) 890 media=result.get(self.__file_type, {}) 891 idx=result.get(self.__index_type, {}) 892 tmp_avi_name=common.gettempfilename("avi") 893 try: 894 file_list=self.__phone.listfiles(self.__path) 895 except com_brew.BrewNoSuchDirectoryException: 896 file_list={} 897 except: 898 file_list={} 899 if __debug__: raise 900 901 if not len(file_list): 902 # empty directory 903 return result 904 file_keys=file_list.keys() 905 file_keys.sort(); 906 for k in file_keys: 907 try: 908 conversions.convertjpgtoavi(self.__phone.getfilecontents(k, True)[96:], 909 tmp_avi_name) 910 except: 911 self.__phone.log('Failed to read video files') 912 if __debug__: raise 913 # got the avi file, now prep to send it back 914 if len(idx): 915 idx_k=max(idx.keys())+1 916 else: 917 idx_k=0 918 media[video_file_name]=open(tmp_avi_name, 'rb').read() 919 idx[idx_k]={ 'name': video_file_name, 'origin': 'video' } 920 result[self.__file_type]=media 921 result[self.__index_type]=idx 922 try: 923 os.remove(tmp_avi_name) 924 except: 925 pass 926 return result
927 928 __mms_max_file_name_len=35
929 - def __mms_header(self, img_name, img_type, img_data_len):
930 if len(img_name)>=self.__mms_max_file_name_len: 931 name=img_name[:self.__mms_max_file_name_len-1] 932 else: 933 name=img_name 934 return str('\x06'+str(name)+\ 935 '\x00'*(self.__mms_max_file_name_len-len(name))+\ 936 common.LSBstr32(img_data_len)+chr(img_type)+\ 937 '\x00'*11)
938 - def __mms_file_name(self):
939 now = time.localtime(time.time()) 940 return '%02d%02d%02d%02d%02d%02d'%((now[0]%2000,)+now[1:6])
941 - def _to_mms_JPEG(self, img_name, img_data_len):
942 return (self.__mms_file_name(), 943 self.__mms_header(img_name, 0, img_data_len))
944 - def _to_mms_BMP(self, img_name, img_data_len):
945 return (self.__mms_file_name(), 946 self.__mms_header(img_name, 1, img_data_len))
947 - def _to_mms_PNG(self, img_name, img_data_len):
948 return (self.__mms_file_name(), 949 self.__mms_header(img_name, 2, img_data_len))
950 - def _to_mms_GIF(self, img_name, img_data_len):
951 return (self.__mms_file_name(), 952 self.__mms_header(img_name, 3, img_data_len))
953
954 - def save_media(self, result, dl_info):
955 self.__phone.log('Saving media for type '+self.__file_type) 956 media, idx=result[self.__file_type], result[self.__index_type] 957 # check for files selected for deletion 958 media_names=[media[k]['name'] for k in media] 959 dl_info_keys=dl_info.keys() 960 deleted_keys=[k for k in dl_info_keys if k not in media_names] 961 new_keys=[k for k in media if media[k]['name'] not in dl_info_keys] 962 # deleting files 963 campix_file_path=self.__phone.protocolclass.cam_pix_file_path 964 campix_file_len=len(campix_file_path) 965 for k in deleted_keys: 966 file_name=dl_info[k] 967 if file_name[:campix_file_len]==campix_file_path: 968 # campera pix, do nothing 969 continue 970 self.__phone.log('Deleting file: '+file_name) 971 try: 972 self.__phone.rmfile(file_name) 973 except: 974 self.__phone.log('Failed to delete file: '+file_name) 975 # writing new files 976 # make sure dir exists to write new files 977 if len(new_keys): 978 try: 979 self.__phone.mkdirs(self.__path) 980 # yet another hack 981 self.__phone.mkdirs('brew/shared') 982 except: 983 pass 984 file_count=0 985 for k in new_keys: 986 n=media[k] 987 origin=n.get('origin', None) 988 if origin is not None and origin != self.__origin: 989 continue 990 if len(n['name']) > self.__max_file_len: 991 self.__phone.log('%s %s name is too long and not sent to phone'% \ 992 (self.__file_type, n['name'])) 993 continue 994 file_count+=1 995 if file_count>self.__max_file_count: 996 # max # of files reached, bailing out 997 self.__phone.log('This phone only supports %d %s. Save operation stopped.'%\ 998 (self.__max_file_count, self.__file_type)) 999 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