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

# Source Code for Module Gnumed.pycommon.gmNetworkTools

      1  # -*- coding: utf8 -*-
      2  __doc__ = """GNUmed internetworking tools."""
      3
      4  #===========================================================================
      5  __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
      6  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      7
      8  # std libs
      9  import sys
     10  import os.path
     11  import logging
     12  import urllib2 as wget
     13  import urllib
     14  import MimeWriter
     15  import mimetypes
     16  import mimetools
     17  import StringIO
     18  import zipfile
     19  import webbrowser
     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
     30
     31  _log = logging.getLogger('gm.net')
     32
     33  #===========================================================================
     34  # browser access
     35  #---------------------------------------------------------------------------
     36 -def open_url_in_browser(url, new=2, autoraise=True, *args, **kwargs):
     37          # url, new=0, autoraise=True
     38          try:
     39                  webbrowser.open(url, new = new, autoraise = autoraise, **kwargs)
     40          except (webbrowser.Error, OSError):
     41                  _log.exception('error calling browser')
     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.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(u'%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 + u'.md5')
     72
     73          md5_file = open(md5_fname, 'rU')
     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 -def install_data_pack(data_pack=None, conn=None):
    103          from Gnumed.pycommon import gmPsql
    104          psql = gmPsql.Psql(conn)
    105          sql_script = os.path.join(data_pack['unzip_dir'], 'install-data-pack.sql')
    106          if psql.run(sql_script) == 0:
    107                  curs = conn.cursor()
    108                  curs.execute(u'select gm.log_script_insertion(%(name)s, %(ver)s)', {'name': data_pack['pack_url'], 'ver': u'current'})
    109                  curs.close()
    110                  conn.commit()
    111                  return True
    112
    113          _log.error('error installing data pack: %s', data_pack)
    114          return False
    115  #---------------------------------------------------------------------------
    116 -def download_data_pack_old(url, target_dir=None):
    117
    118          if target_dir is None:
    119                  target_dir = gmTools.get_unique_filename(prefix = 'gm-dl-')
    120
    121          _log.debug('downloading [%s]', url)
    122          _log.debug('unpacking into [%s]', target_dir)
    123
    124          gmTools.mkdir(directory = target_dir)
    125
    126          # FIXME: rewrite to use urllib.urlretrieve() and
    127
    128          paths = gmTools.gmPaths()
    129          local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', 'gm-download_data')
    130
    131          candidates = [u'gm-download_data', u'gm-download_data.bat', local_script, u'gm-download_data.bat']
    132          args = u' %s %s' % (url, target_dir)
    133
    134          success = gmShellAPI.run_first_available_in_shell (
    135                  binaries = candidates,
    136                  args = args,
    137                  blocking = True,
    138                  run_last_one_anyway = True
    139          )
    140
    141          if success:
    142                  return True, target_dir
    143
    144          _log.error('download failed')
    145          return False, None
    146  #===========================================================================
    147  # client update handling
    148  #---------------------------------------------------------------------------
    149 -def compare_versions(left_version, right_version):
    150          """
    151           0: left == right
    152          -1: left < right
    153           1: left > right
    154          """
    155          if left_version == right_version:
    156                  _log.debug('same version: [%s] = [%s]', left_version, right_version)
    157                  return 0
    158
    159          left_parts = left_version.split('.')
    160          right_parts = right_version.split('.')
    161
    162          tmp, left_major = gmTools.input2decimal(u'%s.%s' % (left_parts[0], left_parts[1]))
    163          tmp, right_major = gmTools.input2decimal(u'%s.%s' % (right_parts[0], right_parts[1]))
    164
    165          if left_major < right_major:
    166                  _log.debug('left version [%s] < right version [%s]: major part', left_version, right_version)
    167                  return -1
    168
    169          if left_major > right_major:
    170                  _log.debug('left version [%s] > right version [%s]: major part', left_version, right_version)
    171                  return 1
    172
    173          tmp, left_part3 = gmTools.input2decimal(left_parts[2].replace('rc', '0.'))
    174          tmp, right_part3 = gmTools.input2decimal(right_parts[2].replace('rc', '0.'))
    175
    176          if left_part3 < right_part3:
    177                  _log.debug('left version [%s] < right version [%s]: minor part', left_version, right_version)
    178                  return -1
    179
    180          if left_part3 > right_part3:
    181                  _log.debug('left version [%s] > right version [%s]: minor part', left_version, right_version)
    182                  return 1
    183
    184          return 0
    185  #---------------------------------------------------------------------------
    186 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
    187          """Check for new releases at <url>.
    188
    189          Returns (bool, text).
    190          True: new release available
    191          False: up to date
    192          None: don't know
    193          """
    194          if current_version == u'GIT HEAD':
    195                  _log.debug('GIT HEAD always up to date')
    196                  return (False, None)
    197
    198          try:
    199                  remote_file = wget.urlopen(url)
    200          except (wget.URLError, ValueError, OSError):
    201                  _log.exception("cannot retrieve version file from [%s]", url)
    202                  return (None, _('Cannot retrieve version information from:\n\n%s') % url)
    203
    204          _log.debug('retrieving version information from [%s]', url)
    205
    206          cfg = gmCfg2.gmCfgData()
    207          try:
    208                  cfg.add_stream_source(source = 'gm-versions', stream = remote_file)
    209          except (UnicodeDecodeError):
    210                  remote_file.close()
    211                  _log.exception("cannot read version file from [%s]", url)
    212                  return (None, _('Cannot read version information from:\n\n%s') % url)
    213
    214          remote_file.close()
    215
    216          latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')])
    217          latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')])
    218          latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')])
    219
    220          cfg.remove_source('gm-versions')
    221
    222          _log.info('current release: %s', current_version)
    223          _log.info('current branch: %s', current_branch)
    224          _log.info('latest release on current branch: %s', latest_release_on_current_branch)
    225          _log.info('latest branch: %s', latest_branch)
    226          _log.info('latest release on latest branch: %s', latest_release_on_latest_branch)
    227
    228          # anything known ?
    229          no_release_information_available = (
    230                  (
    231                          (latest_release_on_current_branch is None) and
    232                          (latest_release_on_latest_branch is None)
    233                  ) or (
    234                          not consider_latest_branch and
    235                          (latest_release_on_current_branch is None)
    236                  )
    237          )
    238          if no_release_information_available:
    239                  _log.warning('no release information available')
    240                  msg = _('There is no version information available from:\n\n%s') % url
    241                  return (None, msg)
    242
    243          # up to date ?
    244          if consider_latest_branch:
    245                  _log.debug('latest branch taken into account')
    246                  if latest_release_on_latest_branch is None:
    247                          if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]:
    248                                  _log.debug('up to date: current version >= latest version on current branch and no latest branch available')
    249                                  return (False, None)
    250                  else:
    251                          if compare_versions(latest_release_on_latest_branch, current_version) in [-1, 0]:
    252                                  _log.debug('up to date: current version >= latest version on latest branch')
    253                                  return (False, None)
    254          else:
    255                  _log.debug('latest branch not taken into account')
    256                  if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]:
    257                          _log.debug('up to date: current version >= latest version on current branch')
    258                          return (False, None)
    259
    260          new_release_on_current_branch_available = (
    261                  (latest_release_on_current_branch is not None) and
    262                  (compare_versions(latest_release_on_current_branch, current_version) == 1)
    263          )
    264          _log.info('%snew release on current branch available', gmTools.bool2str(new_release_on_current_branch_available, '', 'no '))
    265
    266          new_release_on_latest_branch_available = (
    267                  (latest_branch is not None)
    268                          and
    269                  (
    270                          (latest_branch > current_branch) or (
    271                                  (latest_branch == current_branch) and
    272                                  (compare_versions(latest_release_on_latest_branch, current_version) == 1)
    273                          )
    274                  )
    275          )
    276          _log.info('%snew release on latest branch available', gmTools.bool2str(new_release_on_latest_branch_available, '', 'no '))
    277
    278          if not (new_release_on_current_branch_available or new_release_on_latest_branch_available):
    279                  _log.debug('up to date: no new releases available')
    280                  return (False, None)
    281
    282          # not up to date
    283          msg = _('A new version of GNUmed is available.\n\n')
    284          msg += _(' Your current version: "%s"\n') % current_version
    285          if consider_latest_branch:
    286                  if new_release_on_current_branch_available:
    287                          msg += u'\n'
    288                          msg += _(' New version: "%s"') % latest_release_on_current_branch
    289                          msg += u'\n'
    290                          msg += _(' - bug fixes only\n')
    291                          msg += _(' - database fixups may be needed\n')
    292                  if new_release_on_latest_branch_available:
    293                          if current_branch != latest_branch:
    294                                  msg += u'\n'
    295                                  msg += _(' New version: "%s"') % latest_release_on_latest_branch
    296                                  msg += u'\n'
    297                                  msg += _(' - bug fixes and new features\n')
    298                                  msg += _(' - database upgrade required\n')
    299          else:
    300                  msg += u'\n'
    301                  msg += _(' New version: "%s"') % latest_release_on_current_branch
    302                  msg += u'\n'
    303                  msg += _(' - bug fixes only\n')
    304                  msg += _(' - database fixups may be needed\n')
    305
    306          msg += u'\n\n'
    307          msg += _(
    308                  'Note, however, that this version may not yet\n'
    309                  'be available *pre-packaged* for your system.'
    310          )
    311
    312          msg += u'\n\n'
    313          msg += _('Details are found on <http://wiki.gnumed.de>.\n')
    314          msg += u'\n'
    315          msg += _('Version information loaded from:\n\n %s') % url
    316
    317          return (True, msg)
    318  #===========================================================================
    319  # mail handling
    320  #---------------------------------------------------------------------------
    321  default_mail_sender = u'gnumed@gmx.net'
    322  default_mail_receiver = u'gnumed-devel@gnu.org'
    323  default_mail_server = u'mail.gmx.net'
    324
    325 -def send_mail(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, encoding='quoted-printable', attachments=None):
    326          # FIXME: How to generate and send mails: a step by step tutorial
    327          # FIXME: http://groups.google.com/group/comp.lang.python/browse_thread/thread/e0793c1007361398/
    328          # FIXME: google for aspineux blog
    329
    330          if message is None:
    331                  return False
    332
    333          message = message.lstrip().lstrip('\r\n').lstrip()
    334
    335          if sender is None:
    336                  sender = default_mail_sender
    337
    338          if receiver is None:
    339                  receiver = [default_mail_receiver]
    340
    341          if server is None:
    342                  server = default_mail_server
    343
    344          if subject is None:
    345                  subject = u'gmTools.py: send_mail() test'
    346
    347          msg = StringIO.StringIO()
    348          writer = MimeWriter.MimeWriter(msg)
    349          writer.addheader('To', u', '.join(receiver))
    350          writer.addheader('From', sender)
    351          writer.addheader('Subject', subject[:50].replace('\r', '/').replace('\n', '/'))
    352          writer.addheader('MIME-Version', '1.0')
    353
    354          writer.startmultipartbody('mixed')
    355
    356          # start with a text/plain part
    357          part = writer.nextpart()
    358          body = part.startbody('text/plain')
    359          part.flushheaders()
    360          body.write(message.encode(encoding))
    361
    362          # now add the attachments
    363          if attachments is not None:
    364                  for a in attachments:
    365                          filename = os.path.basename(a[0])
    366                          try:
    367                                  mtype = a[1]
    368                                  encoding = a[2]
    369                          except IndexError:
    370                                  mtype, encoding = mimetypes.guess_type(a[0])
    371                                  if mtype is None:
    372                                          mtype = 'application/octet-stream'
    373                                          encoding = 'base64'
    374                                  elif mtype == 'text/plain':
    375                                          encoding = 'quoted-printable'
    376                                  else:
    377                                          encoding = 'base64'
    378
    379                          part = writer.nextpart()
    380                          part.addheader('Content-Transfer-Encoding', encoding)
    381                          body = part.startbody("%s; name=%s" % (mtype, filename))
    382                          mimetools.encode(open(a[0], 'rb'), body, encoding)
    383
    384          writer.lastpart()
    385
    386          import smtplib
    387          session = smtplib.SMTP(server)
    388          session.set_debuglevel(debug)
    389          if auth is not None:
    390                  session.login(auth['user'], auth['password'])
    391          refused = session.sendmail(sender, receiver, msg.getvalue())
    392          session.quit()
    393          msg.close()
    394          if len(refused) != 0:
    395                  _log.error("refused recipients: %s" % refused)
    396                  return False
    397
    398          return True
    399  #===========================================================================
    400  # main
    401  #---------------------------------------------------------------------------
    402  if __name__ == '__main__':
    403
    404          if len(sys.argv) < 2:
    405                  sys.exit()
    406
    407          if sys.argv[1] != 'test':
    408                  sys.exit()
    409
    410          #-----------------------------------------------------------------------
    411 -        def test_send_mail():
    412                  msg = u"""
    413  To: %s
    414  From: %s
    415  Subject: gmTools test suite mail
    416
    417  This is a test mail from the gmTools.py module.
    418  """ % (default_mail_receiver, default_mail_sender)
    419                  print "mail sending succeeded:", send_mail (
    420                          receiver = [default_mail_receiver, u'karsten.hilbert@gmx.net'],
    421                          message = msg,
    422                          auth = {'user': default_mail_sender, 'password': u'gnumed-at-gmx-net'}, # u'gm/bugs/gmx'
    423                          debug = True,
    424                          attachments = [sys.argv[0]]
    425                  )
    426          #-----------------------------------------------------------------------
    427 -        def test_check_for_update():
    428
    429                  test_data = [
    430                          ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
    431                          ('file:///home/ncq/gm-versions.txt', None, None, False),
    432                          ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
    433                          ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
    434                          ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
    435                  ]
    436
    437                  for test in test_data:
    438                          print "arguments:", test
    439                          found, msg = check_for_update(test[0], test[1], test[2], test[3])
    440                          print msg
    441
    442                  return
    443          #-----------------------------------------------------------------------
    444 -        def test_dl_data_pack():
    445                  #url = 'file:./x-data_pack.zip'
    446                  #url = 'missing-file.zip'
    447                  url = 'gmTools.py'
    448                  dl_name = download_data_pack(url)
    449                  print url, "->", dl_name
    450                  unzip_dir = unzip_data_pack(dl_name)
    451                  print "unzipped into", unzip_dir
    452          #-----------------------------------------------------------------------
    453 -        def test_browser():
    454                  success = open_url_in_browser(sys.argv[2])
    455                  print success
    456                  open_url_in_browser(sys.argv[2], abc=222)
    457          #-----------------------------------------------------------------------
    458          #test_check_for_update()
    459          #test_send_mail()
    460          #test_dl_data_pack()
    461          test_browser()
    462
    463  #===========================================================================
    464