Package Gnumed :: Package proxiedpyjamas :: Module gmWebGuiServer |
[frames] | no frames]

# Source Code for Module Gnumed.proxiedpyjamas.gmWebGuiServer

      1  #!/usr/bin/env python
      2
      3  __doc__ = """GNUmed web user interface server launcher.
      4  """
      5  #==========================================================
      6  __version__ = "$Revision: 0.1 $"
      7  __author__  = "S. Hilbert <Sebastian.Hilbert@gmx.net>"
      8  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      9
     10  # stdlib
     11  import re, sys, time, os, cPickle, zlib, locale, os.path
     12  import datetime as pyDT, shutil, logging, urllib2
     13
     14  # json-rpc
     15  from jsonserver import SimpleForkingJSONRPCServer, CloseConnection
     16
     17  # GNUmed libs
     18  from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks
     19  from Gnumed.pycommon import gmLoginInfo, gmBackendListener, gmTools, gmCfg2
     20  from Gnumed.pycommon import gmCfg2, gmI18N, gmDispatcher, gmBusinessDBObject
     21  from Gnumed.pycommon.gmBusinessDBObject import jsonclasshintify
     22  from Gnumed.pycommon import gmPG2
     23  from Gnumed.business import gmDocuments
     24  from Gnumed.business import gmPerson
     25  from Gnumed.business import gmStaff
     26  from Gnumed.business import gmProviderInbox
     27  from Gnumed.business import gmPersonSearch
     28
     29
     30  #try:
     31  #   _('dummy-no-need-to-translate-but-make-epydoc-happy')
     32  #except NameError:
     33  #   _ = lambda x:x
     34
     35  _cfg = gmCfg2.gmCfgData()
     36  _provider = None
     37  _scripting_listener = None
     38
     39  _log = logging.getLogger('gm.main')
     40  _log.info(__version__)
     41  _log.info('web GUI framework')
     42
     43  #================================================================
     44  # convenience functions
     45  #----------------------------------------------------------------
     46 -def connect_to_database(login_info=None, max_attempts=3, expected_version=None, require_version=True):
     47      """Display the login dialog and try to log into the backend.
     48
     49      - up to max_attempts times
     50      - returns True/False
     51      """
     52      from Gnumed.pycommon import gmPG2
     53      # force programmer to set a valid expected_version
     54      expected_hash = gmPG2.known_schema_hashes[expected_version]
     55      client_version = _cfg.get(option = u'client_version')
     56      global current_db_name
     57      current_db_name = u'gnumed_v%s' % expected_version
     58
     59      attempt = 0
     60
     61      while attempt < max_attempts:
     62
     63          _log.debug('login attempt %s of %s', (attempt+1), max_attempts)
     64
     65          connected = False
     66
     67          login = login_info
     68          if login is None:
     69              _log.info("did not provide a login information")
     70
     71          # try getting a connection to verify the DSN works
     72          dsn = gmPG2.make_psycopg2_dsn (
     73              database = login.database,
     74              host = login.host,
     75              port = login.port,
     76              user = login.user,
     77              password = login.password
     78          )
     79          try:
     80              #conn = gmPG2.get_raw_connection(dsn = dsn, verbose = True, readonly = True)
     81              conn = gmPG2.get_raw_connection(dsn = dsn, verbose = True, readonly = True)
     82              connected = True
     83
     84          except gmPG2.cAuthenticationError, e:
     85              attempt += 1
     86              _log.error(u"login attempt failed: %s", e)
     87              if attempt < max_attempts:
     88                  if (u'host=127.0.0.1' in (u'%s' % e)) or (u'host=' not in (u'%s' % e)):
     89                      msg = _(
     90                          'Unable to connect to database:\n\n'
     91                          '%s\n\n'
     92                          "Are you sure you have got a local database installed ?\n"
     93                          '\n'
     94                          "Please retry with proper credentials or cancel.\n"
     95                          '\n'
     96                          'You may also need to check the PostgreSQL client\n'
     97                          'authentication configuration in pg_hba.conf. For\n'
     98                          'details see:\n'
     99                          '\n'
    100                          'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    101                      )
    102                  else:
    103                      msg = _(
    104                          "Unable to connect to database:\n\n"
    105                          "%s\n\n"
    106                          "Please retry with proper credentials or cancel.\n"
    107                          "\n"
    108                          'You may also need to check the PostgreSQL client\n'
    109                          'authentication configuration in pg_hba.conf. For\n'
    110                          'details see:\n'
    111                          '\n'
    112                          'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    113                      )
    114                  msg = msg % e
    115                  msg = re.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg)
    116                  gmGuiHelpers.gm_show_error (
    117                      msg,
    118                      _('Connecting to backend')
    119                  )
    120              del e
    121              continue
    122
    123          except gmPG2.dbapi.OperationalError, e:
    124              _log.error(u"login attempt failed: %s", e)
    125              msg = _(
    126                  "Unable to connect to database:\n\n"
    127                  "%s\n\n"
    128                  "Please retry another backend / user / password combination !\n"
    129              ) % gmPG2.extract_msg_from_pg_exception(e)
    130              msg = re.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg)
    131              gmGuiHelpers.gm_show_error (
    132                  msg,
    133                  _('Connecting to backend')
    134              )
    135              del e
    136              continue
    137
    138          # connect was successful
    139          gmPG2.set_default_login(login = login)
    140          #gmPG2.set_default_client_encoding(encoding = dlg.panel.backend_profile.encoding)
    141
    142  #       compatible = gmPG2.database_schema_compatible(version = expected_version)
    143  #       if compatible or not require_version:
    144              #dlg.panel.save_state()
    145  #           continue
    146
    147  #       if not compatible:
    148  #           connected_db_version = gmPG2.get_schema_version()
    149  #           msg = msg_generic % (
    150  #               client_version,
    151  #               connected_db_version,
    152  #               expected_version,
    153  #               gmTools.coalesce(login.host, '<localhost>'),
    154  #               login.database,
    155  #               login.user
    156  #           )
    157
    158  #           if require_version:
    159              #   gmGuiHelpers.gm_show_error(msg + msg_fail, _('Verifying database version'))
    160  #               pass
    161              #gmGuiHelpers.gm_show_info(msg + msg_override, _('Verifying database version'))
    162
    163  #       # FIXME: make configurable
    164  #       max_skew = 1        # minutes
    165  #       if _cfg.get(option = 'debug'):
    166  #           max_skew = 10
    167  #       if not gmPG2.sanity_check_time_skew(tolerance = (max_skew * 60)):
    168  #           if _cfg.get(option = 'debug'):
    169  #               gmGuiHelpers.gm_show_warning(msg_time_skew_warn % max_skew, _('Verifying database settings'))
    170  #           else:
    171  #               gmGuiHelpers.gm_show_error(msg_time_skew_fail % max_skew, _('Verifying database settings'))
    172  #               continue
    173
    174  #       sanity_level, message = gmPG2.sanity_check_database_settings()
    175  #       if sanity_level != 0:
    176  #           gmGuiHelpers.gm_show_error((msg_insanity % message), _('Verifying database settings'))
    177  #           if sanity_level == 2:
    178  #               continue
    179
    180  #       gmExceptionHandlingWidgets.set_is_public_database(login.public_db)
    181  #       gmExceptionHandlingWidgets.set_helpdesk(login.helpdesk)
    182
    183          listener = gmBackendListener.gmBackendListener(conn = conn)
    184          break
    185
    186      #dlg.Destroy()
    187
    188      return connected
    189
    190  #----------------------------------------------------------------------------
    191  #internal helper functions
    192  #----------------------------------------------------
    193 -def __get_backend_profiles():
    194          """Get server profiles from the configuration files.
    195
    196          1) from system-wide file
    197          2) from user file
    198
    199          Profiles in the user file which have the same name
    200          as a profile in the system file will override the
    201          system file.
    202          """
    203          # find active profiles
    204          src_order = [
    205              (u'explicit', u'extend'),
    206              (u'system', u'extend'),
    207              (u'user', u'extend'),
    208              (u'workbase', u'extend')
    209          ]
    210
    211          profile_names = gmTools.coalesce (
    212              _cfg.get(group = u'backend', option = u'profiles', source_order = src_order),
    213              []
    214          )
    215
    216          # find data for active profiles
    217          src_order = [
    218              (u'explicit', u'return'),
    219              (u'workbase', u'return'),
    220              (u'user', u'return'),
    221              (u'system', u'return')
    222          ]
    223
    224          profiles = {}
    225
    226          for profile_name in profile_names:
    227              # FIXME: once the profile has been found always use the corresponding source !
    228              # FIXME: maybe not or else we cannot override parts of the profile
    229              profile = cBackendProfile()
    230              profile_section = 'profile %s' % profile_name
    231
    232              profile.name = profile_name
    233              profile.host = gmTools.coalesce(_cfg.get(profile_section, u'host', src_order), u'').strip()
    234              port = gmTools.coalesce(_cfg.get(profile_section, u'port', src_order), 5432)
    235              try:
    236                  profile.port = int(port)
    237                  if profile.port < 1024:
    238                      raise ValueError('refusing to use priviledged port (< 1024)')
    239              except ValueError:
    240                  _log.warning('invalid port definition: [%s], skipping profile [%s]', port, profile_name)
    241                  continue
    242              profile.database = gmTools.coalesce(_cfg.get(profile_section, u'database', src_order), u'').strip()
    243              if profile.database == u'':
    244                  _log.warning('database name not specified, skipping profile [%s]', profile_name)
    245                  continue
    246              profile.encoding = gmTools.coalesce(_cfg.get(profile_section, u'encoding', src_order), u'UTF8')
    247              profile.public_db = bool(_cfg.get(profile_section, u'public/open access', src_order))
    248              profile.helpdesk = _cfg.get(profile_section, u'help desk', src_order)
    249
    250              label = u'%s (%s@%s)' % (profile_name, profile.database, profile.host)
    251              profiles[label] = profile
    252
    253          # sort out profiles with incompatible database versions if not --debug
    254          # NOTE: this essentially hardcodes the database name in production ...
    255          if not (_cfg.get(option = 'debug') or current_db_name.endswith('_devel')):
    256              profiles2remove = []
    257              for label in profiles:
    258                  if profiles[label].database != current_db_name:
    259                      profiles2remove.append(label)
    260              for label in profiles2remove:
    261                  del profiles[label]
    262
    263          if len(profiles) == 0:
    264              host = u'publicdb.gnumed.de'
    265              label = u'public GNUmed database (%s@%s)' % (current_db_name, host)
    266              profiles[label] = cBackendProfile()
    267              profiles[label].name = label
    268              profiles[label].host = host
    269              profiles[label].port = 5432
    270              profiles[label].database = current_db_name
    271              profiles[label].encoding = u'UTF8'
    272              profiles[label].public_db = True
    273              profiles[label].helpdesk = u'http://wiki.gnumed.de'
    274
    275          return profiles
    276
    277  # ------------------------------------------------------------
    278 -def GetLoginInfo(username=None, password=None, backend=None ):
    279
    280      # username is provided through the web interface
    281      # password is provided
    282      # we need the profile
    283
    284      """convenience function for compatibility with gmLoginInfo.LoginInfo"""
    285      from Gnumed.pycommon import gmLoginInfo
    286      #if not self.cancelled:
    287          # FIXME: do not assume conf file is latin1 !
    288          #profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('latin1').strip()]
    289      #self.__backend_profiles = self.__get_backend_profiles()
    290      __backend_profiles = __get_backend_profiles()
    291      profile = __backend_profiles[backend.encode('utf8').strip()]
    292
    293      _log.debug(u'backend profile "%s" selected', profile.name)
    294      _log.debug(u' details: <%s> on %s@%s:%s (%s, %s)',
    295          username,
    296          profile.database,
    297          profile.host,
    298          profile.port,
    299          profile.encoding,
    300          gmTools.bool2subst(profile.public_db, u'public', u'private')
    301          )
    302      #_log.debug(u' helpdesk: "%s"', profile.helpdesk)
    303      login = gmLoginInfo.LoginInfo (
    304          user = username,
    305          password = password,
    306          host = profile.host,
    307          database = profile.database,
    308          port = profile.port
    309          )
    310      #login.public_db = profile.public_db
    311      #login.helpdesk = profile.helpdesk
    312      return login
    313
    314  #----------------------------------------------
    315 -def _signal_debugging_monitor(*args, **kwargs):
    316          try:
    317              kwargs['originated_in_database']
    318              print '==> got notification from database "%s":' % kwargs['signal']
    319          except KeyError:
    320              print '==> received signal from client: "%s"' % kwargs['signal']
    321
    322          del kwargs['signal']
    323          for key in kwargs.keys():
    324              print '    [%s]: %s' % (key, kwargs[key])
    325
    326  #================================================================
    327 -class cBackendProfile:
    328      pass
    329
    330  #================================================================
    331
    332
    333  PYJSDIR = sys._getframe().f_code.co_filename
    334  PYJSDIR = os.path.split(os.path.dirname(PYJSDIR))[0]
    335  PYJSDIR = os.path.join(PYJSDIR, 'pyjamas')
    336
    337  DEFAULT_BACKEND = "GNUmed database on this machine (Linux/Mac) (gnumed_v17@)"
    338
    339 -class HTTPServer(SimpleForkingJSONRPCServer):
    340      '''An application instance containing any number of streams. Except for constructor all methods are generators.'''
    341      count = 0
    342 -    def __init__(self):
    343          SimpleForkingJSONRPCServer.__init__(self, ("localhost", 60001))
    344
    345          self.register_function(self.echo)
    346          self.register_function(self.login)
    347          self.register_function(self.logout)
    348          self.register_function(self.search_patient)
    349          self.register_function(self.get_provider_inbox_data)
    350          self.register_function(self.get_patient_messages)
    351          self.register_function(self.get_doc_types)
    352          self.register_function(self.get_documents)
    353          self.register_function(self.get_schema_version)
    354          self.register_function(self.doSomething)
    355
    356 -    def echo(self, text):
    357          return text
    358 -    def reverse(self, text):
    359          return text[::-1]
    360 -    def uppercase(self, text):
    361          return text.upper()
    362 -    def lowercase(self,text):
    363          return text.lower()
    364
    365 -    def login(self, username=None, password=None, backend=None):
    366          from Gnumed.pycommon import gmPG2
    367          if backend is None:
    368              backend = DEFAULT_BACKEND
    369          login_info = GetLoginInfo(username, password, backend)
    370          override = _cfg.get(option = '--override-schema-check',
    371                              source_order = [('cli', 'return')])
    372          cb = _cfg.get(option = 'client_branch')
    373          expected_version = gmPG2.map_client_branch2required_db_version[cb]
    374          connected = connect_to_database (
    375                  login_info,
    376                  expected_version = expected_version,
    377                  require_version = not override
    378              )
    379          return connected
    380
    381 -    def logout(self):
    382          """ return value is in the exception
    383          """
    384          raise CloseConnection(True)
    385
    386 -    def search_patient(self, search_term):
    387
    388          self.__person_searcher = gmPersonSearch.cPatientSearcher_SQL()
    389          # get list of matching ids
    390          idents = self.__person_searcher.get_identities(search_term)
    391
    392          if idents is None:
    393              idents = []
    394
    395          _log.info("%s matching person(s) found", len(idents))
    396
    397          # only one matching identity
    398          if len(idents) == 1:
    399              self.person = idents[0]
    400              return jsonclasshintify(self.person)
    401
    402          # ambiguous - return available choices, to be able to choose from them.
    403          self.person = None
    404          return jsonclasshintify(idents)
    405
    406 -    def get_patient_messages(self, pk_patient):
    407          messages = gmProviderInbox.get_inbox_messages(pk_patient=pk_patient)
    408          return jsonclasshintify(messages)
    409
    410 -    def get_provider_inbox_data(self):
    411          self.provider = gmStaff.gmCurrentProvider(provider=gmStaff.cStaff())
    412          inbox = gmProviderInbox.cProviderInbox()
    413          self.__msgs = inbox.messages
    414          return jsonclasshintify(inbox.messages)
    415
    416 -    def get_schema_version(self):
    417          return gmPG2.get_schema_version()
    418
    419 -    def get_documents(self, key):
    420          doc_folder = gmDocuments.cDocumentFolder(aPKey=key)
    421          return jsonclasshintify(doc_folder.get_documents())
    422
    423 -    def get_doc_types(self):
    424          return jsonclasshintify(gmDocuments.get_document_types())
    425
    426 -    def doSomething(self):
    427          msg = 'schema version is:' + gmPG2.get_schema_version() +'\n\n'
    428          msg2 =''
    429          for item in gmDocuments.get_document_types():
    430              msg2 = msg2 +'\n' + str(item)
    431          msg = msg + msg2
    432          return "<pre>%s</pre>" % msg
    433
    434
    435  #==========================================================
    436  # main - launch the GNUmed web client
    437  #----------------------------------------------------------
    438
    439 -def main():
    440
    441      if _cfg.get(option = 'debug'):
    442          gmDispatcher.connect(receiver = _signal_debugging_monitor)
    443          _log.debug('gmDispatcher signal monitor activated')
    444
    445      server = HTTPServer()
    446      server.serve_forever()
    447