Package Gnumed :: Package pycommon :: Module gmNetworkTools
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmNetworkTools

  1  # -*- coding: utf-8 -*- 
  2   
  3  __doc__ = """GNUmed internetworking tools.""" 
  4   
  5  #=========================================================================== 
  6  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
  7  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
  8   
  9  # std libs 
 10  import sys 
 11  import os.path 
 12  import logging 
 13  import urllib.request 
 14  import urllib.error 
 15  import zipfile 
 16  import webbrowser 
 17  import io 
 18   
 19   
 20  # GNUmed libs 
 21  if __name__ == '__main__': 
 22          sys.path.insert(0, '../../') 
 23  from Gnumed.pycommon import gmLog2 
 24  from Gnumed.pycommon import gmTools 
 25  from Gnumed.pycommon import gmShellAPI 
 26  from Gnumed.pycommon import gmCfg2 
 27  from Gnumed.pycommon import gmMimeLib 
 28   
 29   
 30  _log = logging.getLogger('gm.net') 
 31   
 32  #=========================================================================== 
 33  # browser access 
 34  #--------------------------------------------------------------------------- 
35 -def open_url_in_browser(url, new=2, autoraise=True, *args, **kwargs):
36 # url, new=0, autoraise=True 37 # new=2: open new tab if possible 38 try: 39 webbrowser.open(url, new = new, autoraise = autoraise, **kwargs) 40 except (webbrowser.Error, OSError, UnicodeEncodeError): 41 _log.exception('error calling browser with url=%s', url) 42 return False 43 return True
44 #===========================================================================
45 -def download_file(url, filename=None, suffix=None):
46 47 if filename is None: 48 filename = gmTools.get_unique_filename(prefix = 'gm-dl-', suffix = suffix) 49 _log.debug('downloading [%s] into [%s]', url, filename) 50 51 try: 52 dl_name, headers = urllib.request.urlretrieve(url, filename) 53 except (ValueError, OSError, IOError): 54 _log.exception('cannot download from [%s]', url) 55 gmLog2.log_stack_trace() 56 return None 57 58 _log.debug('%s' % headers) 59 return dl_name
60 #=========================================================================== 61 # data pack handling 62 #---------------------------------------------------------------------------
63 -def download_data_packs_list(url, filename=None):
64 return download_file(url, filename = filename, suffix = 'conf')
65 #---------------------------------------------------------------------------
66 -def download_data_pack(pack_url, filename=None, md5_url=None):
67 68 _log.debug('downloading data pack from: %s', pack_url) 69 dp_fname = download_file(pack_url, filename = filename, suffix = 'zip') 70 _log.debug('downloading MD5 from: %s', md5_url) 71 md5_fname = download_file(md5_url, filename = dp_fname + '.md5') 72 73 md5_file = io.open(md5_fname, mode = 'rt', encoding = 'utf8') 74 md5_expected = md5_file.readline().strip('\n') 75 md5_file.close() 76 _log.debug('expected MD5: %s', md5_expected) 77 md5_calculated = gmTools.file2md5(dp_fname, return_hex = True) 78 _log.debug('calculated MD5: %s', md5_calculated) 79 80 if md5_calculated != md5_expected: 81 _log.error('mismatch of expected vs calculated MD5: [%s] vs [%s]', md5_expected, md5_calculated) 82 return (False, (md5_expected, md5_calculated)) 83 84 return True, dp_fname
85 #---------------------------------------------------------------------------
86 -def unzip_data_pack(filename=None):
87 88 unzip_dir = os.path.splitext(filename)[0] 89 _log.debug('unzipping data pack into [%s]', unzip_dir) 90 gmTools.mkdir(unzip_dir) 91 try: 92 data_pack = zipfile.ZipFile(filename, 'r') 93 except (zipfile.BadZipfile): 94 _log.exception('cannot unzip data pack [%s]', filename) 95 gmLog2.log_stack_trace() 96 return None 97 98 data_pack.extractall(unzip_dir) 99 100 return unzip_dir
101 102 #---------------------------------------------------------------------------
103 -def install_data_pack(data_pack=None, conn=None):
104 from Gnumed.pycommon import gmPsql 105 psql = gmPsql.Psql(conn) 106 sql_script = os.path.join(data_pack['unzip_dir'], 'install-data-pack.sql') 107 if psql.run(sql_script) == 0: 108 curs = conn.cursor() 109 curs.execute('select gm.log_script_insertion(%(name)s, %(ver)s)', {'name': data_pack['pack_url'], 'ver': 'current'}) 110 curs.close() 111 conn.commit() 112 return True 113 114 _log.error('error installing data pack: %s', data_pack) 115 return False
116 #---------------------------------------------------------------------------
117 -def download_data_pack_old(url, target_dir=None):
118 119 if target_dir is None: 120 target_dir = gmTools.get_unique_filename(prefix = 'gm-dl-') 121 122 _log.debug('downloading [%s]', url) 123 _log.debug('unpacking into [%s]', target_dir) 124 125 gmTools.mkdir(directory = target_dir) 126 127 # FIXME: rewrite to use urllib.request.urlretrieve() and 128 129 paths = gmTools.gmPaths() 130 local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', 'gm-download_data') 131 132 candidates = ['gm-download_data', 'gm-download_data.bat', local_script, 'gm-download_data.bat'] 133 args = ' %s %s' % (url, target_dir) 134 135 success = gmShellAPI.run_first_available_in_shell ( 136 binaries = candidates, 137 args = args, 138 blocking = True, 139 run_last_one_anyway = True 140 ) 141 142 if success: 143 return True, target_dir 144 145 _log.error('download failed') 146 return False, None
147 #=========================================================================== 148 # client update handling 149 #---------------------------------------------------------------------------
150 -def compare_versions(left_version, right_version):
151 """ 152 0: left == right 153 -1: left < right 154 1: left > right 155 """ 156 _log.debug('comparing [%s] with [%s]', left_version, right_version) 157 if left_version == right_version: 158 _log.debug('same version') 159 return 0 160 161 if right_version in ['head', 'dev', 'devel']: 162 _log.debug('development code') 163 return -1 164 165 if left_version in ['head', 'dev', 'devel']: 166 _log.debug('development code') 167 return 1 168 169 left_parts = left_version.split('.') 170 right_parts = right_version.split('.') 171 172 tmp, left_major = gmTools.input2decimal('%s.%s' % (left_parts[0], left_parts[1])) 173 tmp, right_major = gmTools.input2decimal('%s.%s' % (right_parts[0], right_parts[1])) 174 175 if left_major < right_major: 176 _log.debug('left version [%s] < right version [%s]: major part', left_version, right_version) 177 return -1 178 179 if left_major > right_major: 180 _log.debug('left version [%s] > right version [%s]: major part', left_version, right_version) 181 return 1 182 183 tmp, left_part3 = gmTools.input2decimal(left_parts[2].replace('rc', '0.')) 184 tmp, right_part3 = gmTools.input2decimal(right_parts[2].replace('rc', '0.')) 185 186 if left_part3 < right_part3: 187 _log.debug('left version [%s] < right version [%s]: minor part', left_version, right_version) 188 return -1 189 190 if left_part3 > right_part3: 191 _log.debug('left version [%s] > right version [%s]: minor part', left_version, right_version) 192 return 1 193 194 return 0
195 #---------------------------------------------------------------------------
196 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
197 """Check for new releases at <url>. 198 199 Returns (bool, text). 200 True: new release available 201 False: up to date 202 None: don't know 203 """ 204 if current_version is None: 205 _log.debug('<current_version> is None, currency unknown') 206 return (None, None) 207 208 if current_version.lower() in ['git head', 'head', 'tip', 'dev', 'devel']: 209 _log.debug('[%s] always considered up to date', current_version) 210 return (False, None) 211 212 try: 213 remote_file = urllib.request.urlopen(url) 214 except (urllib.error.URLError, ValueError, OSError, IOError): 215 # IOError: socket.error 216 _log.exception("cannot retrieve version file from [%s]", url) 217 return (None, _('Cannot retrieve version information from:\n\n%s') % url) 218 219 _log.debug('retrieving version information from [%s]', url) 220 221 cfg = gmCfg2.gmCfgData() 222 try: 223 #remote_file.read().decode(resource.headers.get_content_charset()) 224 cfg.add_stream_source(source = 'gm-versions', stream = remote_file, encoding = u'utf8') 225 except (UnicodeDecodeError): 226 remote_file.close() 227 _log.exception("cannot read version file from [%s]", url) 228 return (None, _('Cannot read version information from:\n\n%s') % url) 229 230 remote_file.close() 231 232 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')]) 233 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')]) 234 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')]) 235 236 cfg.remove_source('gm-versions') 237 238 _log.info('current release: %s', current_version) 239 _log.info('current branch: %s', current_branch) 240 _log.info('latest release on current branch: %s', latest_release_on_current_branch) 241 _log.info('latest branch: %s', latest_branch) 242 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch) 243 244 # anything known ? 245 no_release_information_available = ( 246 ( 247 (latest_release_on_current_branch is None) and 248 (latest_release_on_latest_branch is None) 249 ) or ( 250 not consider_latest_branch and 251 (latest_release_on_current_branch is None) 252 ) 253 ) 254 if no_release_information_available: 255 _log.warning('no release information available') 256 msg = _('There is no version information available from:\n\n%s') % url 257 return (None, msg) 258 259 # up to date ? 260 if consider_latest_branch: 261 _log.debug('latest branch taken into account') 262 if latest_release_on_latest_branch is None: 263 if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]: 264 _log.debug('up to date: current version >= latest version on current branch and no latest branch available') 265 return (False, None) 266 else: 267 if compare_versions(latest_release_on_latest_branch, current_version) in [-1, 0]: 268 _log.debug('up to date: current version >= latest version on latest branch') 269 return (False, None) 270 else: 271 _log.debug('latest branch not taken into account') 272 if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]: 273 _log.debug('up to date: current version >= latest version on current branch') 274 return (False, None) 275 276 new_release_on_current_branch_available = ( 277 (latest_release_on_current_branch is not None) and 278 (compare_versions(latest_release_on_current_branch, current_version) == 1) 279 ) 280 _log.info('%snew release on current branch available', gmTools.bool2str(new_release_on_current_branch_available, '', 'no ')) 281 282 new_release_on_latest_branch_available = ( 283 (latest_branch is not None) 284 and 285 ( 286 (latest_branch > current_branch) or ( 287 (latest_branch == current_branch) and 288 (compare_versions(latest_release_on_latest_branch, current_version) == 1) 289 ) 290 ) 291 ) 292 _log.info('%snew release on latest branch available', gmTools.bool2str(new_release_on_latest_branch_available, '', 'no ')) 293 294 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available): 295 _log.debug('up to date: no new releases available') 296 return (False, None) 297 298 # not up to date 299 msg = _('A new version of GNUmed is available.\n\n') 300 msg += _(' Your current version: "%s"\n') % current_version 301 if consider_latest_branch: 302 if new_release_on_current_branch_available: 303 msg += '\n' 304 msg += _(' New version: "%s"') % latest_release_on_current_branch 305 msg += '\n' 306 msg += _(' - bug fixes only\n') 307 msg += _(' - database fixups may be needed\n') 308 if new_release_on_latest_branch_available: 309 if current_branch != latest_branch: 310 msg += '\n' 311 msg += _(' New version: "%s"') % latest_release_on_latest_branch 312 msg += '\n' 313 msg += _(' - bug fixes and new features\n') 314 msg += _(' - database upgrade required\n') 315 else: 316 msg += '\n' 317 msg += _(' New version: "%s"') % latest_release_on_current_branch 318 msg += '\n' 319 msg += _(' - bug fixes only\n') 320 msg += _(' - database fixups may be needed\n') 321 322 msg += '\n\n' 323 msg += _( 324 'Note, however, that this version may not yet\n' 325 'be available *pre-packaged* for your system.' 326 ) 327 328 msg += '\n\n' 329 msg += _('Details are found on <http://wiki.gnumed.de>.\n') 330 msg += '\n' 331 msg += _('Version information loaded from:\n\n %s') % url 332 333 return (True, msg)
334 335 #=========================================================================== 336 # mail handling 337 #--------------------------------------------------------------------------- 338 default_mail_sender = 'gnumed@gmx.net' 339 default_mail_receiver = 'gnumed-devel@gnu.org' 340 default_mail_server = 'mail.gmx.net' 341 342 343 #--------------------------------------------------------------------------- 344 from email.mime.text import MIMEText 345 from email.mime.multipart import MIMEMultipart 346 from email.mime.image import MIMEImage 347 from email.mime.audio import MIMEAudio 348 from email.mime.application import MIMEApplication 349
350 -def compose_email(sender=None, receiver=None, message=None, subject=None, files2attach=None):
351 # filenames unicode 352 # file content binary or utf8 353 # files2attach = [(filename, mimetype-or-None), ...] 354 355 if message is None: 356 raise ValueError('<message> is None, cannot compose email') 357 358 message = message.lstrip().lstrip('\r\n').lstrip() 359 360 if sender is None: 361 sender = default_mail_sender 362 363 if receiver is None: 364 receiver = [default_mail_receiver] 365 366 if subject is None: 367 subject = 'compose_email() test' 368 369 if files2attach is None: 370 email = MIMEText(message, 'plain', 'utf8') 371 else: 372 email = MIMEMultipart() 373 email.attach(MIMEText(message, 'plain', 'utf8')) 374 375 email['From'] = sender 376 email['To'] = ', '.join(receiver) 377 email['Subject'] = subject 378 379 if files2attach is None: 380 return email 381 382 for file2attach in files2attach: 383 filename = file2attach[0] 384 try: 385 mimetype = file2attach[1] 386 except IndexError: 387 mimetype = gmMimeLib.guess_mimetype(filename = filename) 388 # text/* 389 if mimetype.startswith('text/'): 390 txt = io.open(filename, mode = 'rt', encoding = 'utf8') 391 attachment = MIMEText(txt.read(), 'plain', 'utf8') 392 txt.close() 393 # image/* 394 elif mimetype.startswith('image/'): 395 img = io.open(filename, mode = 'rb') 396 attachment = MIMEImage(img.read()) 397 img.close() 398 # audio/* 399 elif mimetype.startswith('audio/'): 400 song = io.open(filename, mode = 'rb') 401 attachment = MIMEAudio(song.read()) 402 song.close() 403 # catch-all application/* 404 else: 405 _log.debug('attaching [%s] with type [%s]', filename, mimetype) 406 mime_subtype = mimetype.split('/', 1)[1] 407 data = io.open(filename, mode = 'rb') 408 attachment = MIMEApplication(data.read(), mime_subtype) 409 data.close() 410 411 try: 412 attachment.replace_header('Content-Disposition', 'attachment; filename="%s"' % gmTools.fname_from_path(filename)) 413 except KeyError: 414 attachment.add_header('Content-Disposition', 'attachment; filename="%s"' % gmTools.fname_from_path(filename)) 415 email.attach(attachment) 416 417 return email
418 419 #---------------------------------------------------------------------------
420 -def send_email(sender=None, receiver=None, email=None, server=None, auth=None, debug=False):
421 422 if email is None: 423 raise ValueError('<email> is None, cannot send') 424 425 if sender is None: 426 sender = default_mail_sender 427 428 if receiver is None: 429 receiver = [default_mail_receiver] 430 431 if server is None: 432 server = default_mail_server 433 434 import smtplib 435 failed = False 436 refused = [] 437 try: 438 session = smtplib.SMTP(server) 439 session.set_debuglevel(debug) 440 try: 441 session.starttls() 442 except smtplib.SMTPException: 443 _log.error('cannot enable TLS on [%s]', server) 444 session.ehlo() 445 if auth is not None: 446 session.login(auth['user'], auth['password']) 447 refused = session.sendmail(sender, receiver, email.as_string()) 448 session.quit() 449 except smtplib.SMTPException: 450 failed = True 451 _log.exception('cannot send email') 452 gmLog2.log_stack_trace() 453 454 if len(refused) > 0: 455 _log.error("refused recipients: %s" % refused) 456 457 if failed: 458 return False 459 460 return True
461 462 #---------------------------------------------------------------------------
463 -def compose_and_send_email(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, attachments=None):
464 email = compose_email ( 465 sender = sender, 466 receiver = receiver, 467 message = message, 468 subject = subject, 469 files2attach = attachments 470 ) 471 return send_email ( 472 sender = sender, 473 receiver = receiver, 474 email = email, 475 server = server, 476 auth = auth, 477 debug = debug 478 )
479 480 #=========================================================================== 481 # main 482 #--------------------------------------------------------------------------- 483 if __name__ == '__main__': 484 485 if len(sys.argv) < 2: 486 sys.exit() 487 488 if sys.argv[1] != 'test': 489 sys.exit() 490 491 #-----------------------------------------------------------------------
492 - def test_compose_email():
493 email = compose_email ( 494 message = 'compose_email() test: üü ßß', 495 files2attach = [[sys.argv[2]]] 496 ) 497 print(email.as_string()) 498 return email
499 500 #-----------------------------------------------------------------------
501 - def test_send_email():
502 email = compose_email ( 503 message = 'compose_email() test: üü ßß', 504 files2attach = [[sys.argv[2]]] 505 ) 506 print(send_email ( 507 # receiver = u'ncq@localhost', 508 email = email, 509 # server = 'localhost', 510 auth = {'user': default_mail_sender, 'password': 'gnumed-at-gmx-net'}, 511 debug = True 512 ))
513 514 #-----------------------------------------------------------------------
515 - def test_check_for_update():
516 517 test_data = [ 518 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False), 519 ('file:///home/ncq/gm-versions.txt', None, None, False), 520 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False), 521 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True), 522 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True) 523 ] 524 525 for test in test_data: 526 print("arguments:", test) 527 found, msg = check_for_update(test[0], test[1], test[2], test[3]) 528 print(msg) 529 530 return
531 #-----------------------------------------------------------------------
532 - def test_dl_data_pack():
533 #url = 'file:./x-data_pack.zip' 534 #url = 'missing-file.zip' 535 url = 'gmTools.py' 536 dl_name = download_data_pack(url) 537 print(url, "->", dl_name) 538 unzip_dir = unzip_data_pack(dl_name) 539 print("unzipped into", unzip_dir)
540 541 #-----------------------------------------------------------------------
542 - def test_browser():
543 success = open_url_in_browser(sys.argv[2]) 544 print(success) 545 open_url_in_browser(sys.argv[2], abc=222)
546 547 #----------------------------------------------------------------------- 548 #test_check_for_update() 549 #test_compose_email() 550 test_send_email() 551 #test_dl_data_pack() 552 #test_browser() 553 554 #=========================================================================== 555