Update locales.
[wammu.git] / Wammu / Utils.py
blobd2715866e66509fa022e85e274656634d6178405
1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
3 '''
4 Wammu - Phone manager
5 Misc functions like charset conversion, entries parsers,..
6 '''
7 __author__ = 'Michal Čihař'
8 __email__ = 'michal@cihar.com'
9 __license__ = '''
10 Copyright © 2003 - 2008 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
19 more details.
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
24 '''
26 import codecs
27 import locale
28 import sys
29 import re
30 import wx
31 import string
32 import os
33 try:
34 import grp
35 HAVE_GRP = True
36 except ImportError:
37 HAVE_GRP = False
38 import Wammu.Locales
39 from Wammu.Locales import StrConv
41 import Wammu
42 if Wammu.gammu_error == None:
43 import gammu
46 def GetItemType(txt):
47 if txt == '':
48 return None
49 elif txt[-8:] == 'DATETIME' or txt == 'Date' or txt == 'LastModified' or txt == 'LAST_MODIFIED':
50 return 'datetime'
51 elif txt[-4:] == 'DATE':
52 return 'date'
53 elif txt in ['TEXT', 'DESCRIPTION', 'LOCATION', 'LUID'] or txt[:4] == 'Text':
54 return 'text'
55 elif txt == 'PHONE' or txt[:6] == 'Number':
56 return 'phone'
57 elif txt == 'CONTACTID':
58 return 'contact'
59 elif txt == 'PRIVATE' or txt == 'Private' or txt == 'COMPLETED':
60 return 'bool'
61 elif txt == 'Category' or txt == 'CATEGORY':
62 return 'category'
63 elif txt == 'PictureID' or txt == 'RingtoneID' or txt == 'RingtoneFileSystemID':
64 return 'id'
65 else:
66 return 'number'
68 def SearchLocation(lst, loc, second = None):
69 result = -1
70 for i in range(len(lst)):
71 if second != None:
72 if not lst[i][second[0]] == second[1]:
73 continue
74 if type(lst[i]['Location']) == type(loc):
75 if loc == lst[i]['Location']:
76 result = i
77 break
78 else:
79 if str(loc) in lst[i]['Location'].split(', '):
80 result = i
81 break
82 return result
84 def MatchesText(item, match, num):
85 testkeys = ['Value', 'Text', 'Number']
86 for x in item:
87 if type(item) == dict:
88 val = item[x]
89 else:
90 val = x
91 if type(val) in (str, unicode):
92 if match.search(val) != None:
93 return True
94 elif num is not None and type(val) == int and num == val:
95 return True
96 elif type(val) == list:
97 for i in range(len(val)):
98 for key in testkeys:
99 try:
100 val2 = val[i][key]
101 if type(val2) in (str, unicode):
102 if match.search(val2) != None:
103 return True
104 elif num is not None and type(val2) == int and num == val2:
105 return True
106 except KeyError:
107 # Ignore not found keys
108 pass
109 return False
112 def SearchItem(lst, item):
113 for i in range(len(lst)):
114 if item == lst[i]:
115 return i
116 return -1
118 def GrabNumberPrefix(number, prefixes):
119 l = len(number)
120 if l == 0 or number[0] != '+':
121 return None
122 i = 2
123 while not number[:i] in prefixes:
124 i += 1
125 if i > l:
126 return None
127 return number[:i]
129 '''Prefix for making international numbers'''
130 NumberPrefix = ''
132 NumberStrip = re.compile('^([#*]\d+[#*])?(\\+?.*)$')
134 def NormalizeNumber(number):
136 Attempts to create international number from anything it receives.
137 It does strip any network prefixes and attempts to properly add
138 international prefix. However this is a bit tricky, as there are
139 many ways which can break this.
141 # Strip magic prefixes (like no CLIR)
142 nbmatch = NumberStrip.match(number)
143 resnumber = nbmatch.group(2)
144 # If we stripped whole number, return original
145 if len(resnumber) == 0:
146 return number
147 # Handle 00 prefix same as +
148 if resnumber[0:2] == '00':
149 resnumber = '+' + resnumber[2:]
150 # Detect numbers with international prefix and without +
151 # This can be national number in some countries (eg. US)
152 if NumberPrefix[0] == '+' and len(NumberPrefix) > 0 and resnumber[:len(NumberPrefix) - 1] == NumberPrefix[1:]:
153 resnumber = '+' + resnumber
154 # Add international prefix
155 if resnumber[0] != '+':
156 resnumber = NumberPrefix + resnumber
157 return resnumber
159 def SearchNumber(lst, number):
160 for i in range(len(lst)):
161 for x in lst[i]['Entries']:
162 if GetItemType(x['Type']) == 'phone' and NormalizeNumber(number) == NormalizeNumber(x['Value']):
163 return i
164 return -1
166 def GetContactLink(lst, i, txt):
167 return StrConv('<a href="memory://%s/%d">%s</a> (%s)' % (lst[i]['MemoryType'], lst[i]['Location'], lst[i]['Name'], txt))
169 def GetNumberLink(lst, number):
170 i = SearchNumber(lst, number)
171 if i == -1:
172 return StrConv(number)
173 return GetContactLink(lst, i, number)
175 def GetTypeString(type, value, values, linkphone = True):
177 Returns string for entry in data dictionary. Formats it according to
178 knowledge of format types.
180 t = GetItemType(type)
181 if t == 'contact':
182 i = SearchLocation(values['contact']['ME'], value)
183 if i == -1:
184 return '%d' % value
185 else:
186 return GetContactLink([] + values['contact']['ME'], i, str(value))
187 elif linkphone and t == 'phone':
188 return StrConv(GetNumberLink([] + values['contact']['ME'] + values['contact']['SM'], value))
189 elif t == 'id':
190 v = hex(value)
191 if v[-1] == 'L':
192 v = v[:-1]
193 return v
194 else:
195 return StrConv(value)
197 def ParseMemoryEntry(entry, config = None):
198 first = ''
199 last = ''
200 name = ''
201 nickname = ''
202 formalname = ''
203 company = ''
204 number = ''
205 number_result = ''
206 name_result = ''
207 date = None
208 for i in entry['Entries']:
209 if i['Type'] == 'Text_Name':
210 name = i['Value']
211 elif i['Type'] == 'Text_FirstName':
212 first = i['Value']
213 if i['Type'] == 'Text_LastName':
214 last = i['Value']
215 if i['Type'] == 'Text_NickName':
216 nickname = i['Value']
217 if i['Type'] == 'Text_FormalName':
218 formalname = i['Value']
219 if i['Type'] == 'Date':
220 # Store date olny if it is more recent
221 # This can happen in multiple call records
222 if date is None:
223 date = i['Value']
224 else:
225 if i['Value'] > date:
226 date = i['Value']
227 if i['Type'] == 'Text_Company':
228 company = i['Value']
229 if i['Type'] == 'Number_General':
230 number_result = i['Value']
231 elif i['Type'][:7] == 'Number_':
232 number = i['Value']
234 if config is None:
235 format = 'auto'
236 else:
237 format = config.Read('/Wammu/NameFormat')
239 if format == 'custom':
240 name_result = config.Read('/Wammu/NameFormatString') % {
241 'Name' : name,
242 'FirstName' : first,
243 'LastName' : last,
244 'NickName' : nickname,
245 'FormalName' : formalname,
246 'Company' : company,
248 else:
249 if name != '':
250 name_result = name
251 elif first != '':
252 if last != '':
253 if format == 'auto-first-last':
254 name_result = '%s %s' % (first, last)
255 else:
256 name_result = '%s, %s' % (last, first)
257 else:
258 name_result = first
259 elif last != '':
260 name_result = last
261 elif nickname != '':
262 name_result = nickname
263 elif formalname != '':
264 name_result = formalname
265 else:
266 name_result = ''
268 if name_result == '':
269 if company != '':
270 name_result = company
271 else:
272 if company != '':
273 name_result = '%s (%s)' % (name_result, company)
275 if number_result == '':
276 number_result = number
278 entry['Number'] = number_result
279 entry['Name'] = name_result
280 entry['Synced'] = False
281 entry['Date'] = date
283 return entry
285 def ParseTodo(entry):
286 dt = ''
287 text = ''
288 completed = ''
289 for i in entry['Entries']:
290 if i['Type'] == 'END_DATETIME':
291 dt = str(i['Value'])
292 elif i['Type'] == 'TEXT':
293 text = i['Value']
294 elif i['Type'] == 'COMPLETED':
295 if i['Value']:
296 completed = _('Yes')
297 else:
298 completed = _('No')
299 entry['Completed'] = completed
300 entry['Text'] = text
301 entry['Date'] = dt
302 entry['Synced'] = False
303 return entry
305 def ParseCalendar(entry):
306 start = ''
307 end = ''
308 text = ''
309 description = ''
310 tone_alarm = None
311 silent_alarm = None
312 recurrence = None
313 for i in entry['Entries']:
314 if i['Type'] == 'END_DATETIME':
315 end = str(i['Value'])
316 elif i['Type'] == 'START_DATETIME':
317 start = str(i['Value'])
318 elif i['Type'] == 'TONE_ALARM_DATETIME':
319 tone_alarm = _('enabled (tone)')
320 elif i['Type'] == 'SILENT_ALARM_DATETIME':
321 silent_alarm = _('enabled (silent)')
322 elif i['Type'] == 'TEXT':
323 text = i['Value']
324 elif i['Type'] == 'DESCRIPTION':
325 description = i['Value']
326 elif i['Type'] == 'REPEAT_MONTH':
327 recurrence = _('yearly')
328 elif i['Type'] == 'REPEAT_DAY':
329 recurrence = _('monthly')
330 elif i['Type'] == 'REPEAT_FREQUENCY':
331 if i['Value'] == 1:
332 recurrence = _('daily')
333 elif i['Value'] == 2:
334 recurrence = _('biweekly')
335 elif (i['Type'] == 'REPEAT_DAYOFWEEK'):
336 if i['Value'] == 1:
337 recurrence = _('weekly on monday')
338 elif i['Value'] == 2:
339 recurrence = _('weekly on tuesday')
340 elif i['Value'] == 3:
341 recurrence = _('weekly on wednesday')
342 elif i['Value'] == 4:
343 recurrence = _('weekly on thursday')
344 elif i['Value'] == 5:
345 recurrence = _('weekly on friday')
346 elif i['Value'] == 6:
347 recurrence = _('weekly on saturday')
348 elif i['Value'] == 7:
349 recurrence = _('weekly on sunday')
351 if tone_alarm is not None:
352 entry['Alarm'] = tone_alarm
353 elif silent_alarm is not None:
354 entry['Alarm'] = silent_alarm
355 else:
356 entry['Alarm'] = _('disabled')
358 if recurrence is None:
359 entry['Recurrence'] = _('nonrecurring')
360 else:
361 entry['Recurrence'] = recurrence
363 if text == '':
364 entry['Text'] = description
365 elif description == '':
366 entry['Text'] = text
367 else:
368 entry['Text'] = '%s (%s)' % (text, description)
370 entry['Start'] = start
371 entry['End'] = end
372 entry['Synced'] = False
373 return entry
375 def ParseMessage(msg, parseinfo = False):
376 txt = ''
377 loc = ''
378 msg['Folder'] = msg['SMS'][0]['Folder']
379 msg['State'] = msg['SMS'][0]['State']
380 msg['Number'] = msg['SMS'][0]['Number']
381 msg['Name'] = msg['SMS'][0]['Name']
382 msg['DateTime'] = msg['SMS'][0]['DateTime']
383 if parseinfo:
384 for i in msg['SMSInfo']['Entries']:
385 if i['Buffer'] != None:
386 txt = txt + i['Buffer']
387 else:
388 for i in msg['SMS']:
389 txt = txt + i['Text']
390 for i in msg['SMS']:
391 if loc != '':
392 loc = loc + ', '
393 loc = loc + str(i['Location'])
394 try:
395 tmp = StrConv(txt)
396 msg['Text'] = txt
397 except:
398 s2 = ''
399 for x in txt:
400 if x in string.printable:
401 s2 += x
402 msg['Text'] = s2
403 msg['Location'] = loc
404 msg['Synced'] = False
405 return msg
407 def ProcessMessages(list, synced):
408 read = []
409 unread = []
410 sent = []
411 unsent = []
412 data = gammu.LinkSMS(list)
414 for x in data:
415 i = {}
416 v = gammu.DecodeSMS(x)
417 i['SMS'] = x
418 if v != None:
419 i['SMSInfo'] = v
420 ParseMessage(i, (v != None))
421 i['Synced'] = synced
422 if i['State'] == 'Read':
423 read.append(i)
424 elif i['State'] == 'UnRead':
425 unread.append(i)
426 elif i['State'] == 'Sent':
427 sent.append(i)
428 elif i['State'] == 'UnSent':
429 unsent.append(i)
431 return {'read':read, 'unread':unread, 'sent':sent, 'unsent':unsent}
433 def FormatError(txt, info):
434 if info['Code'] == gammu.Errors['ERR_NOTSUPPORTED']:
435 message = _('Your phone doesn\'t support this function.')
436 elif info['Code'] == gammu.Errors['ERR_NOTIMPLEMENTED']:
437 message = _('This function is not implemented for your phone. If you want help with implementation please contact authors.')
438 elif info['Code'] == gammu.Errors['ERR_SECURITYERROR']:
439 message = _('Your phone asks for PIN.')
440 elif info['Code'] == gammu.Errors['ERR_FULL']:
441 message = _('Memory is full, try deleting some entries.')
442 elif info['Code'] == gammu.Errors['ERR_CANCELED']:
443 message = _('Communication canceled by phone, did you press cancel on phone?')
444 elif info['Code'] == gammu.Errors['ERR_EMPTY']:
445 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.')
446 elif info['Code'] == gammu.Errors['ERR_INSIDEPHONEMENU']:
447 message = _('Please close opened menu in phone and retry, data can not be accessed while you have opened them.')
448 elif info['Code'] == gammu.Errors['ERR_TIMEOUT']:
449 message = _('Timeout while trying to communicate with phone. Maybe phone is not connected (for cable) or out of range (for bluetooth or IrDA).')
450 elif info['Code'] == gammu.Errors['ERR_DEVICENOTEXIST']:
451 message = _('Device for communication with phone does not exist. Maybe you don\'t have phone plugged or your configuration is wrong.')
452 elif info['Code'] == gammu.Errors['ERR_DEVICENOPERMISSION']:
453 message = _('Can not access device for communication with phone.')
454 if sys.platform == 'linux2':
455 message += ' ' + _('Maybe you need to be member of some group to have acces to device.')
456 else:
457 message = '%s %s\n%s %s\n%s %d' % (_('Description:'), StrConv(info['Text']), _('Function:'), info['Where'], _('Error code:'), info['Code'])
458 return StrConv(txt + '\n\n' + message)
460 def FixupMaskedEdit(edit):
461 # XXX: this is not clean way of reseting to system colour, but I don't know better.
462 bgc = wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)
463 fgc = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
464 setattr(edit, '_validBackgroundColour', bgc)
465 setattr(edit, '_foregroundColour', fgc)
467 def GetWebsiteLang():
468 (loc, charset) = locale.getdefaultlocale()
469 try:
470 if loc[:2].lower() == 'cs':
471 return 'cz.'
472 return ''
473 except TypeError:
474 return ''
476 def DBUSServiceAvailable(bus, interface, try_start_service=False):
477 try:
478 import dbus
479 except ImportError:
480 return False
481 if try_start_service:
482 try:
483 bus.start_service_by_name(interface)
484 except dbus.exceptions.DBusException:
485 print 'Failed to start DBus service %s' % interface
486 obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
487 dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
488 avail = dbus_iface.ListNames()
489 return interface in avail
492 def CheckDeviceNode(curdev):
494 Checks whether it makes sense to perform searching on this device and
495 possibly warns user about misconfigurations.
497 Returns tuple of 4 members:
498 - error code (0 = ok, -1 = device does not exits, -2 = no permissions)
499 - log text
500 - error dialog title
501 - error dialog text
503 if sys.platform == 'win32':
504 try:
505 import win32file
506 if curdev[:3] == 'COM':
507 try:
508 win32file.QueryDosDevice(curdev)
509 return (0, '', '', '')
510 except:
511 return (-1,
512 _('Device %s does not exist!') % curdev,
513 _('Error opening device'),
514 _('Device %s does not exist!') % curdev
516 except ImportError:
517 return (0, '', '', '')
518 if not os.path.exists(curdev):
519 return (-1,
520 _('Device %s does not exist!') % curdev,
521 _('Error opening device'),
522 _('Device %s does not exist!') % curdev
524 if not os.access(curdev, os.R_OK) or not os.access(curdev, os.W_OK):
525 gid = os.stat(curdev).st_gid
526 if HAVE_GRP:
527 group = grp.getgrgid(gid)[0]
528 else:
529 group = str(gid)
530 return (-2,
531 _('You don\'t have permissions for %s device!') % curdev,
532 _('Error opening device'),
533 (_('You don\'t have permissions for %s device!') % curdev) +
534 ' ' +
535 (_('Maybe you need to be member of %s group.') % group)
537 return (0, '', '', '')