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

Source Code for Module Gnumed.pycommon.gmNetworkTools

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