1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
5 Misc functions like charset conversion, entries parsers,..
7 __author__
= 'Michal Čihař'
8 __email__
= 'michal@cihar.com'
10 Copyright © 2003 - 2010 Michal Čihař
12 This program is free software; you can redistribute it and/or modify it
13 under the terms of the GNU General Public License version 2 as published by
14 the Free Software Foundation.
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40 from Wammu
.Locales
import StrConv
43 if Wammu
.gammu_error
== None:
50 elif txt
[-8:] == 'DATETIME' or txt
== 'Date' or txt
== 'LastModified' or txt
== 'LAST_MODIFIED':
52 elif txt
[-4:] == 'DATE':
54 elif txt
in ['TEXT', 'DESCRIPTION', 'LOCATION', 'LUID'] or txt
[:4] == 'Text':
56 elif txt
== 'PHONE' or txt
[:6] == 'Number':
58 elif txt
== 'CONTACTID':
60 elif txt
== 'PRIVATE' or txt
== 'Private' or txt
== 'COMPLETED':
62 elif txt
== 'Category' or txt
== 'CATEGORY':
64 elif txt
== 'PictureID' or txt
== 'RingtoneID' or txt
== 'RingtoneFileSystemID':
71 def SearchLocation(lst
, loc
, second
= None):
73 for i
in range(len(lst
)):
75 if not lst
[i
][second
[0]] == second
[1]:
77 if type(lst
[i
]['Location']) == type(loc
):
78 if loc
== lst
[i
]['Location']:
82 if str(loc
) in lst
[i
]['Location'].split(', '):
87 def MatchesText(item
, match
, num
):
88 testkeys
= ['Value', 'Text', 'Number']
90 if type(item
) == dict:
94 if type(val
) in (str, unicode):
95 if match
.search(val
) != None:
97 elif num
is not None and type(val
) == int and num
== val
:
99 elif type(val
) == list:
100 for i
in range(len(val
)):
104 if type(val2
) in (str, unicode):
105 if match
.search(val2
) != None:
107 elif num
is not None and type(val2
) == int and num
== val2
:
110 # Ignore not found keys
115 def SearchItem(lst
, item
):
116 for i
in range(len(lst
)):
121 def GrabNumberPrefix(number
, prefixes
):
123 if l
== 0 or number
[0] != '+':
126 while not number
[:i
] in prefixes
:
132 '''Prefix for making international numbers'''
135 NumberStrip
= re
.compile('^([#*]\d+[#*])?(\\+?.*)$')
137 def NormalizeNumber(number
):
139 Attempts to create international number from anything it receives.
140 It does strip any network prefixes and attempts to properly add
141 international prefix. However this is a bit tricky, as there are
142 many ways which can break this.
144 # Strip magic prefixes (like no CLIR)
145 nbmatch
= NumberStrip
.match(number
)
146 resnumber
= nbmatch
.group(2)
147 # If we stripped whole number, return original
148 if len(resnumber
) == 0:
150 # Handle 00 prefix same as +
151 if resnumber
[0:2] == '00':
152 resnumber
= '+' + resnumber
[2:]
153 # Detect numbers with international prefix and without +
154 # This can be national number in some countries (eg. US)
155 if len(NumberPrefix
) > 0 and NumberPrefix
[0] == '+' and resnumber
[:len(NumberPrefix
) - 1] == NumberPrefix
[1:]:
156 resnumber
= '+' + resnumber
157 # Add international prefix
158 if resnumber
[0] != '+':
159 resnumber
= NumberPrefix
+ resnumber
162 def SearchNumber(lst
, number
):
163 for i
in range(len(lst
)):
164 for x
in lst
[i
]['Entries']:
165 if GetItemType(x
['Type']) == 'phone' and NormalizeNumber(number
) == NormalizeNumber(x
['Value']):
169 def GetContactLink(lst
, i
, txt
):
170 return StrConv('<a href="memory://%s/%d">%s</a> (%s)' % (lst
[i
]['MemoryType'], lst
[i
]['Location'], lst
[i
]['Name'], txt
))
172 def GetNumberLink(lst
, number
):
173 i
= SearchNumber(lst
, number
)
175 return StrConv(number
)
176 return GetContactLink(lst
, i
, number
)
178 def GetTypeString(type, value
, values
, linkphone
= True):
180 Returns string for entry in data dictionary. Formats it according to
181 knowledge of format types.
183 t
= GetItemType(type)
185 i
= SearchLocation(values
['contact']['ME'], value
)
189 return GetContactLink([] + values
['contact']['ME'], i
, str(value
))
190 elif linkphone
and t
== 'phone':
191 return StrConv(GetNumberLink([] + values
['contact']['ME'] + values
['contact']['SM'], value
))
198 return '<wxp module="Wammu.Image" class="EncodedBitmap">' + \
199 '<param name="image" value="' + \
200 base64
.b64encode(value
) + \
204 return StrConv(value
)
206 def ParseMemoryEntry(entry
, config
= None):
217 for i
in entry
['Entries']:
218 if i
['Type'] == 'Text_Name':
220 elif i
['Type'] == 'Text_FirstName':
222 if i
['Type'] == 'Text_LastName':
224 if i
['Type'] == 'Text_NickName':
225 nickname
= i
['Value']
226 if i
['Type'] == 'Text_FormalName':
227 formalname
= i
['Value']
228 if i
['Type'] == 'Date':
229 # Store date olny if it is more recent
230 # This can happen in multiple call records
234 if i
['Value'] > date
:
236 if i
['Type'] == 'Text_Company':
238 if i
['Type'] == 'Number_General':
239 number_result
= i
['Value']
240 elif i
['Type'][:7] == 'Number_':
246 format
= config
.Read('/Wammu/NameFormat')
248 if format
== 'custom':
249 name_result
= config
.Read('/Wammu/NameFormatString') % {
253 'NickName' : nickname
,
254 'FormalName' : formalname
,
262 if format
== 'auto-first-last':
263 name_result
= '%s %s' % (first
, last
)
265 name_result
= '%s, %s' % (last
, first
)
271 name_result
= nickname
272 elif formalname
!= '':
273 name_result
= formalname
277 if name_result
== '':
279 name_result
= company
282 name_result
= '%s (%s)' % (name_result
, company
)
284 if number_result
== '':
285 number_result
= number
287 entry
['Number'] = number_result
288 entry
['Name'] = name_result
289 entry
['Synced'] = False
294 def ParseTodo(entry
):
298 for i
in entry
['Entries']:
299 if i
['Type'] == 'END_DATETIME':
301 elif i
['Type'] == 'TEXT':
303 elif i
['Type'] == 'COMPLETED':
308 entry
['Completed'] = completed
311 entry
['Synced'] = False
314 def ParseCalendar(entry
):
322 for i
in entry
['Entries']:
323 if i
['Type'] == 'END_DATETIME':
324 end
= str(i
['Value'])
325 elif i
['Type'] == 'START_DATETIME':
326 start
= str(i
['Value'])
327 elif i
['Type'] == 'TONE_ALARM_DATETIME':
328 tone_alarm
= _('enabled (tone)')
329 elif i
['Type'] == 'SILENT_ALARM_DATETIME':
330 silent_alarm
= _('enabled (silent)')
331 elif i
['Type'] == 'TEXT':
333 elif i
['Type'] == 'DESCRIPTION':
334 description
= i
['Value']
335 elif i
['Type'] == 'REPEAT_MONTH':
336 recurrence
= _('yearly')
337 elif i
['Type'] == 'REPEAT_DAY':
338 recurrence
= _('monthly')
339 elif i
['Type'] == 'REPEAT_FREQUENCY':
341 recurrence
= _('daily')
342 elif i
['Value'] == 2:
343 recurrence
= _('biweekly')
344 elif (i
['Type'] == 'REPEAT_DAYOFWEEK'):
346 recurrence
= _('weekly on monday')
347 elif i
['Value'] == 2:
348 recurrence
= _('weekly on tuesday')
349 elif i
['Value'] == 3:
350 recurrence
= _('weekly on wednesday')
351 elif i
['Value'] == 4:
352 recurrence
= _('weekly on thursday')
353 elif i
['Value'] == 5:
354 recurrence
= _('weekly on friday')
355 elif i
['Value'] == 6:
356 recurrence
= _('weekly on saturday')
357 elif i
['Value'] == 7:
358 recurrence
= _('weekly on sunday')
360 if tone_alarm
is not None:
361 entry
['Alarm'] = tone_alarm
362 elif silent_alarm
is not None:
363 entry
['Alarm'] = silent_alarm
365 entry
['Alarm'] = _('disabled')
367 if recurrence
is None:
368 entry
['Recurrence'] = _('nonrecurring')
370 entry
['Recurrence'] = recurrence
373 entry
['Text'] = description
374 elif description
== '':
377 entry
['Text'] = '%s (%s)' % (text
, description
)
379 entry
['Start'] = start
381 entry
['Synced'] = False
384 def ParseMessage(msg
, parseinfo
= False):
387 msg
['Folder'] = msg
['SMS'][0]['Folder']
388 msg
['State'] = msg
['SMS'][0]['State']
389 msg
['Number'] = msg
['SMS'][0]['Number']
390 msg
['Name'] = msg
['SMS'][0]['Name']
391 msg
['DateTime'] = msg
['SMS'][0]['DateTime']
393 for i
in msg
['SMSInfo']['Entries']:
394 if i
['Buffer'] != None:
395 txt
= txt
+ i
['Buffer']
398 txt
= txt
+ i
['Text']
402 loc
= loc
+ str(i
['Location'])
409 if x
in string
.printable
:
412 msg
['Location'] = loc
413 msg
['Synced'] = False
416 def ProcessMessages(list, synced
):
421 data
= gammu
.LinkSMS(list)
425 v
= gammu
.DecodeSMS(x
)
429 ParseMessage(i
, (v
!= None))
431 if i
['State'] == 'Read':
433 elif i
['State'] == 'UnRead':
435 elif i
['State'] == 'Sent':
437 elif i
['State'] == 'UnSent':
440 return {'read':read
, 'unread':unread
, 'sent':sent
, 'unsent':unsent
}
442 def FormatError(txt
, info
):
443 if info
['Code'] == gammu
.Errors
['ERR_NOTSUPPORTED']:
444 message
= _('Your phone doesn\'t support this function.')
445 elif info
['Code'] == gammu
.Errors
['ERR_NOTIMPLEMENTED']:
446 message
= _('This function is not implemented for your phone. If you want help with implementation please contact authors.')
447 elif info
['Code'] == gammu
.Errors
['ERR_SECURITYERROR']:
448 message
= _('Your phone asks for PIN.')
449 elif info
['Code'] == gammu
.Errors
['ERR_FULL']:
450 message
= _('Memory is full, try deleting some entries.')
451 elif info
['Code'] == gammu
.Errors
['ERR_CANCELED']:
452 message
= _('Communication canceled by phone, did you press cancel on phone?')
453 elif info
['Code'] == gammu
.Errors
['ERR_EMPTY']:
454 message
= _('Empty entry received. This usually should not happen and most likely is caused by bug in phone firmware or in Gammu/Wammu.\n\nIf you miss some entry, please contact Gammu/Wammu authors.')
455 elif info
['Code'] == gammu
.Errors
['ERR_INSIDEPHONEMENU']:
456 message
= _('Please close opened menu in phone and retry, data can not be accessed while you have opened them.')
457 elif info
['Code'] == gammu
.Errors
['ERR_TIMEOUT']:
458 message
= _('Timeout while trying to communicate with phone. Maybe phone is not connected (for cable) or out of range (for Bluetooth or IrDA).')
459 elif info
['Code'] == gammu
.Errors
['ERR_DEVICENOTEXIST']:
460 message
= _('Device for communication with phone does not exist. Maybe you don\'t have phone plugged or your configuration is wrong.')
461 elif info
['Code'] == gammu
.Errors
['ERR_DEVICENOPERMISSION']:
462 message
= _('Can not access device for communication with phone.')
463 if sys
.platform
== 'linux2':
464 message
+= ' ' + _('Maybe you need to be member of some group to have acces to device.')
465 elif info
['Code'] == gammu
.Errors
['ERR_NOSIM']:
466 message
= _('Can not access SIM card. Please check whether it is properly inserted in phone and/or try to reboot the phone by removing battery.')
468 message
= '%s %s\n%s %s\n%s %d' % (_('Description:'), StrConv(info
['Text']), _('Function:'), info
['Where'], _('Error code:'), info
['Code'])
469 return StrConv(txt
+ '\n\n' + message
)
471 def FixupMaskedEdit(edit
):
472 # XXX: this is not clean way of reseting to system colour, but I don't know better.
473 bgc
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_LISTBOX
)
474 fgc
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_WINDOWTEXT
)
475 setattr(edit
, '_validBackgroundColour', bgc
)
476 setattr(edit
, '_foregroundColour', fgc
)
478 def GetWebsiteLang():
479 (loc
, charset
) = locale
.getdefaultlocale()
481 if loc
[:2].lower() == 'cs':
487 def DBUSServiceAvailable(bus
, interface
, try_start_service
=False):
492 if try_start_service
:
494 bus
.start_service_by_name(interface
)
495 except dbus
.exceptions
.DBusException
:
496 print 'Failed to start DBus service %s' % interface
497 obj
= bus
.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
498 dbus_iface
= dbus
.Interface(obj
, 'org.freedesktop.DBus')
499 avail
= dbus_iface
.ListNames()
500 return interface
in avail
503 def CheckDeviceNode(curdev
):
505 Checks whether it makes sense to perform searching on this device and
506 possibly warns user about misconfigurations.
508 Returns tuple of 4 members:
509 - error code (0 = ok, -1 = device does not exits, -2 = no permissions)
514 if sys
.platform
== 'win32':
517 if curdev
[:3] == 'COM':
519 win32file
.QueryDosDevice(curdev
)
520 return (0, '', '', '')
523 _('Device %s does not exist!') % curdev
,
524 _('Error opening device'),
525 _('Device %s does not exist!') % curdev
528 return (0, '', '', '')
529 if not os
.path
.exists(curdev
):
531 _('Device %s does not exist!') % curdev
,
532 _('Error opening device'),
533 _('Device %s does not exist!') % curdev
535 if not os
.access(curdev
, os
.R_OK
) or not os
.access(curdev
, os
.W_OK
):
536 gid
= os
.stat(curdev
).st_gid
538 group
= grp
.getgrgid(gid
)[0]
542 _('You don\'t have permissions for %s device!') % curdev
,
543 _('Error opening device'),
544 (_('You don\'t have permissions for %s device!') % curdev
) +
546 (_('Maybe you need to be member of %s group.') % group
)
548 return (0, '', '', '')
550 def CompatConfig(cfg
):
552 Adjust configuration for possible changes in Gammu history.
555 # 1.27.0 changed handling of boolean options
556 if tuple(map(int, gammu
.Version()[1].split('.'))) < (1, 27, 0):
558 cfg
['SyncTime'] = 'yes'
560 cfg
['SyncTime'] = 'no'
561 if cfg
['LockDevice']:
562 cfg
['LockDevice'] = 'yes'
564 cfg
['LockDevice'] = 'no'
566 cfg
['StartInfo'] = 'yes'
568 cfg
['StartInfo'] = 'no'
570 # Older versions did not use model auto
571 if cfg
['Model'] == 'auto':