PageRenderTime 55ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/util.py

https://github.com/wilane/blink-cocoa
Python | 397 lines | 385 code | 10 blank | 2 comment | 0 complexity | db6fe28d69a76a008ed54792dab53943 MD5 | raw file
  1. # Copyright (C) 2009-2011 AG Projects. See LICENSE for details.
  2. #
  3. __all__ = ['allocate_autorelease_pool', 'call_in_gui_thread', 'compare_identity_addresses', 'escape_html', 'external_url_pattern', 'format_identity_to_string', 'format_identity_to_string', 'format_size', 'format_size_rounded', 'is_sip_aor_format', 'image_file_extension_pattern', 'html2txt', 'normalize_sip_uri_for_outgoing_session',
  4. 'run_in_gui_thread', 'sipuri_components_from_string', 'strip_addressbook_special_characters', 'sip_prefix_pattern', 'video_file_extension_pattern', 'translate_alpha2digit',
  5. 'AccountInfo', 'DictDiffer']
  6. import re
  7. import shlex
  8. from application.python.decorator import decorator, preserve_signature
  9. from AppKit import NSApp, NSRunAlertPanel
  10. from Foundation import NSAutoreleasePool, NSThread
  11. from sipsimple.account import Account, BonjourAccount
  12. from sipsimple.core import SIPURI, FrozenSIPURI, SIPCoreError
  13. video_file_extension_pattern = re.compile("\.(mp4|mpeg4|mov|avi)$", re.I)
  14. image_file_extension_pattern = re.compile("\.(png|tiff|jpg|jpeg|gif)$", re.I)
  15. sip_prefix_pattern = re.compile("^(sip:|sips:)")
  16. external_url_pattern = re.compile("^(tel:|//|mailto:|xmpp:|callto://|callto:)")
  17. _pstn_addressbook_chars = "(\(\s?0\s?\)|[-() \/\.])"
  18. _pstn_addressbook_chars_substract_regexp = re.compile(_pstn_addressbook_chars)
  19. _pstn_match_regexp = re.compile("^\+?([0-9]|%s)+$" % _pstn_addressbook_chars)
  20. _pstn_plus_regexp = re.compile("^\+")
  21. def strip_addressbook_special_characters(contact):
  22. return _pstn_addressbook_chars_substract_regexp.sub("", contact)
  23. def show_error_panel(message):
  24. message = re.sub("%", "%%", message)
  25. NSRunAlertPanel("Error", message, "OK", None, None)
  26. def normalize_sip_uri_for_outgoing_session(target_uri, account):
  27. def format_uri(uri, default_domain, idd_prefix = None, prefix = None):
  28. if default_domain is not None:
  29. if "@" not in uri:
  30. if _pstn_match_regexp.match(uri):
  31. username = strip_addressbook_special_characters(uri)
  32. if idd_prefix:
  33. username = _pstn_plus_regexp.sub(idd_prefix, username)
  34. if prefix:
  35. username = prefix + username
  36. else:
  37. username = uri
  38. uri = "%s@%s" % (username, default_domain)
  39. elif "." not in uri.split("@", 1)[1]:
  40. uri += "." + default_domain
  41. if not uri.startswith("sip:") and not uri.startswith("sips:"):
  42. uri = "sip:%s" % uri
  43. return uri
  44. try:
  45. target_uri = str(target_uri)
  46. except:
  47. show_error_panel("SIP address must not contain unicode characters (%s)" % target_uri)
  48. return None
  49. if '@' not in target_uri and isinstance(account, BonjourAccount):
  50. show_error_panel("SIP address must contain host in bonjour mode (%s)" % target_uri)
  51. return None
  52. target_uri = format_uri(target_uri, account.id.domain if not isinstance(account, BonjourAccount) else None, account.pstn.idd_prefix if not isinstance(account, BonjourAccount) else None, account.pstn.prefix if not isinstance(account, BonjourAccount) else None)
  53. try:
  54. target_uri = SIPURI.parse(target_uri)
  55. except SIPCoreError:
  56. show_error_panel('Illegal SIP URI: %s' % target_uri)
  57. return None
  58. return target_uri
  59. def format_identity_to_string(identity, check_contact=False, format='AOR'):
  60. """
  61. Takes a SIPURI, Account, FromHeader, ToHeader, CPIMIdentity object and
  62. returns either an AOR (user@domain), compact (username of phone number) or full (Display Name <user@domain>)
  63. """
  64. port = 5060
  65. transport = 'udp'
  66. if isinstance(identity, (SIPURI, FrozenSIPURI)):
  67. if format == 'AOR':
  68. return u"%s@%s" % (identity.user, identity.host)
  69. user = identity.user
  70. host = identity.host
  71. display_name = None
  72. uri = sip_prefix_pattern.sub("", str(identity))
  73. contact = NSApp.delegate().contactsWindowController.getContactMatchingURI(uri) if check_contact else None
  74. if identity.port is not None and identity.port != 5060:
  75. port = identity.port
  76. if identity.transport != 'udp':
  77. transport = identity.transport
  78. else:
  79. if format == 'AOR':
  80. return u"%s@%s" % (identity.uri.user, identity.uri.host)
  81. user = identity.uri.user
  82. host = identity.uri.host
  83. if identity.uri.port is not None and identity.uri.port != 5060:
  84. port = identity.uri.port
  85. if identity.uri.transport != 'udp':
  86. transport = identity.uri.transport
  87. display_name = identity.display_name
  88. uri = sip_prefix_pattern.sub("", str(identity.uri))
  89. contact = NSApp.delegate().contactsWindowController.getContactMatchingURI(uri) if check_contact else None
  90. if port == 5060 and transport == 'udp':
  91. address = u"%s@%s" % (user, host)
  92. elif transport == 'udp':
  93. address = u"%s@%s:%d" % (user, host, port)
  94. else:
  95. address = u"%s@%s:%d;transport=%s" % (user, host, port, transport)
  96. match = re.match(r'^(?P<number>\+[1-9][0-9]\d{5,15})@(\d{1,3}\.){3}\d{1,3}$', address)
  97. if contact:
  98. if format == 'compact':
  99. if display_name == user or not display_name:
  100. return contact.name
  101. else:
  102. return display_name
  103. else:
  104. if display_name == user or not display_name:
  105. return "%s <%s>" % (contact.name, address)
  106. else:
  107. return "%s <%s>" % (display_name, address)
  108. elif match is not None:
  109. if format == 'compact':
  110. return match.group('number')
  111. else:
  112. return "%s <%s>" % (display_name, match.group('number')) if display_name else match.group('number')
  113. elif display_name:
  114. if format == 'compact':
  115. return display_name
  116. else:
  117. return "%s <%s>" % (display_name, address)
  118. else:
  119. return address
  120. def sipuri_components_from_string(text):
  121. """
  122. Takes a SIP URI in text format and returns formatted strings with various sub-parts
  123. """
  124. display_name = ""
  125. address = ""
  126. full_uri = ""
  127. fancy_uri = ""
  128. # the shlex module doesn't support unicode
  129. uri = text.encode('utf8') if isinstance(text, unicode) else text
  130. toks = shlex.split(uri)
  131. if len(toks) == 2:
  132. display_name = toks[0]
  133. address = toks[1]
  134. elif len(toks) == 1:
  135. address = toks[0]
  136. elif len(toks) > 2:
  137. j = 0
  138. while (j < len(toks) -1):
  139. display_name = '%s %s' % (display_name, toks[j])
  140. j = j + 1
  141. display_name = display_name.strip()
  142. address = toks[-1]
  143. else:
  144. address = uri
  145. address = address.strip("<>")
  146. if display_name:
  147. full_uri = '%s <%s>' % (display_name, address)
  148. else:
  149. full_uri = address
  150. match_number_ip = re.match(r'^(?P<number>\+?[0-9]\d{5,15})@(\d{1,3}\.){3}\d{1,3}$', address)
  151. match_number = re.match(r'^(?P<number>(00|\+)[1-9]\d{4,14})@', address)
  152. match = match_number_ip or match_number
  153. if match is not None:
  154. address = match.group('number')
  155. if display_name and display_name != match.group('number'):
  156. fancy_uri = '%s <%s>' % (display_name, match.group('number'))
  157. else:
  158. fancy_uri = match.group('number')
  159. elif display_name:
  160. fancy_uri = '%s <%s>' % (display_name, address)
  161. else:
  162. fancy_uri = address
  163. if isinstance(text, unicode):
  164. return address.decode('utf8'), display_name.decode('utf8'), full_uri.decode('utf8'), fancy_uri.decode('utf8')
  165. else:
  166. return address, display_name, full_uri, fancy_uri
  167. def is_sip_aor_format(uri):
  168. """
  169. Check if the given URI is a full SIP URI with username and host.
  170. """
  171. if isinstance(uri, (SIPURI, FrozenSIPURI)):
  172. return uri.user is not None and uri.host is not None
  173. else:
  174. if not (uri.startswith('sip:') or uri.startswith('sips:')):
  175. uri = "sip:%s" % uri
  176. try:
  177. sip_uri = SIPURI.parse(str(uri))
  178. except:
  179. return False
  180. else:
  181. return sip_uri.user is not None and sip_uri.host is not None
  182. def format_size(s, minsize=0, bits=False):
  183. if bits:
  184. # used for network speed
  185. s = s * 8;
  186. if max(s,minsize) < 1024:
  187. return "%s bit"%s
  188. elif max(s,minsize) < 1024*1024:
  189. return "%.01f Kbit"%(s/1024.0)
  190. elif max(s,minsize) < 1024*1024*1024:
  191. return "%.02f Mbit"%(s/(1024.0*1024.0))
  192. else:
  193. return "%.04f Gbit"%(s/(1024.0*1024.0*1024.0))
  194. else:
  195. if max(s,minsize) < 1024:
  196. return "%s B"%s
  197. elif max(s,minsize) < 1024*1024:
  198. return "%.01f KB"%(s/1024.0)
  199. elif max(s,minsize) < 1024*1024*1024:
  200. return "%.02f MB"%(s/(1024.0*1024.0))
  201. else:
  202. return "%.04f GB"%(s/(1024.0*1024.0*1024.0))
  203. def format_size_rounded(s, minsize=0, bits=False):
  204. if bits:
  205. # used for network speed
  206. s = s * 8;
  207. if max(s,minsize) < 1024:
  208. return "%s bit"%s
  209. elif max(s,minsize) < 1024*1024:
  210. return "%.00f Kbit"%(s/1024.0)
  211. elif max(s,minsize) < 1024*1024*1024:
  212. return "%.01f Mbit"%(s/(1024.0*1024.0))
  213. else:
  214. return "%.01f Gbit"%(s/(1024.0*1024.0*1024.0))
  215. else:
  216. # used for file size
  217. if max(s,minsize) < 1024:
  218. return "%s B"%s
  219. elif max(s,minsize) < 1024*1024:
  220. return "%.00f KB"%(s/1024.0)
  221. elif max(s,minsize) < 1024*1024*1024:
  222. return "%.01f MB"%(s/(1024.0*1024.0))
  223. else:
  224. return "%.01f GB"%(s/(1024.0*1024.0*1024.0))
  225. def escape_html(text):
  226. text = text.replace('&', '&amp;') # Must be done first!
  227. text = text.replace('<', '&lt;')
  228. text = text.replace('>', '&gt;')
  229. text = text.replace('"', '&quot;')
  230. text = text.replace("'", '&apos;')
  231. text = text.replace(' ', '&nbsp;')
  232. text = text.replace('\\', '\\\\')
  233. text = text.replace('\r\n', '<br/>')
  234. text = text.replace('\n', '<br/>')
  235. text = text.replace('\r', '<br/>')
  236. return text
  237. def compare_identity_addresses(id1, id2):
  238. return format_identity_to_string(id1) == format_identity_to_string(id2)
  239. def html2txt(s):
  240. """Convert the html to raw txt
  241. - suppress all return
  242. - <p>, <tr> to return
  243. - <td> to tab
  244. Need the following regex:
  245. p = re.compile('(<p.*?>)|(<tr.*?>)', re.I)
  246. t = re.compile('<td.*?>', re.I)
  247. comm = re.compile('<!--.*?-->', re.M)
  248. tags = re.compile('<.*?>', re.M)
  249. """
  250. p = re.compile('(<p.*?>)|(<tr.*?>)', re.I)
  251. t = re.compile('<td.*?>', re.I)
  252. comm = re.compile('<!--.*?-->', re.M)
  253. tags = re.compile('<.*?>', re.M)
  254. s = s.replace('\n', '') # remove returns time this compare to split filter join
  255. s = p.sub('\n', s) # replace p and tr by \n
  256. s = t.sub('\t', s) # replace td by \t
  257. s = comm.sub('', s) # remove comments
  258. s = tags.sub('', s) # remove all remaining tags
  259. s = re.sub(' +', ' ', s) # remove running spaces this remove the \n and \t
  260. return s
  261. def call_in_gui_thread(func, *args, **kwargs):
  262. if NSThread.isMainThread():
  263. func(*args, **kwargs)
  264. else:
  265. NSApp.delegate().performSelectorOnMainThread_withObject_waitUntilDone_("callObject:", lambda: func(*args, **kwargs), False)
  266. @decorator
  267. def run_in_gui_thread(func):
  268. @preserve_signature(func)
  269. def wrapper(*args, **kw):
  270. if NSThread.isMainThread():
  271. func(*args, **kw)
  272. else:
  273. NSApp.delegate().performSelectorOnMainThread_withObject_waitUntilDone_("callObject:", lambda: func(*args, **kw), False)
  274. return wrapper
  275. @decorator
  276. def allocate_autorelease_pool(func):
  277. @preserve_signature(func)
  278. def wrapper(*args, **kw):
  279. pool = NSAutoreleasePool.alloc().init()
  280. func(*args, **kw)
  281. return wrapper
  282. def translate_alpha2digit(key):
  283. try:
  284. letter_map = translate_alpha2digit.letter_map
  285. except AttributeError:
  286. digit_map = {'2': 'ABC', '3': 'DEF', '4': 'GHI', '5': 'JKL', '6': 'MNO', '7': 'PQRS', '8': 'TUV', '9': 'WXYZ'}
  287. letter_map = dict((letter, digit) for digit, letter_group in digit_map.iteritems() for letter in letter_group)
  288. translate_alpha2digit.letter_map = letter_map
  289. return letter_map.get(key.upper(), key)
  290. class AccountInfo(object):
  291. def __init__(self, account):
  292. self.account = account
  293. self.registration_state = None
  294. self.failure_code = None
  295. self.failure_reason = None
  296. self.registrar = None
  297. @property
  298. def name(self):
  299. return u'Bonjour' if isinstance(self.account, BonjourAccount) else unicode(self.account.id)
  300. @property
  301. def order(self):
  302. return self.account.order
  303. def __eq__(self, other):
  304. if isinstance(other, basestring):
  305. return self.name == other
  306. elif isinstance(other, (Account, BonjourAccount)):
  307. return self.account == other
  308. elif isinstance(other, AccountInfo):
  309. return self.account == other
  310. return False
  311. def __ne__(self, other):
  312. return not self.__eq__(other)
  313. class DictDiffer(object):
  314. """
  315. Calculate the difference between two dictionaries as:
  316. (1) items added
  317. (2) items removed
  318. (3) keys same in both but changed values
  319. (4) keys same in both and unchanged values
  320. """
  321. def __init__(self, current_dict, past_dict):
  322. self.current_dict, self.past_dict = current_dict, past_dict
  323. self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys())
  324. self.intersect = self.set_current.intersection(self.set_past)
  325. def added(self):
  326. return self.set_current - self.intersect
  327. def removed(self):
  328. return self.set_past - self.intersect
  329. def changed(self):
  330. return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o])
  331. def unchanged(self):
  332. return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o])