screenlets.session

Package screenlets :: Module session |
[hide private]
[frames] | no frames]

# Source Code for Module screenlets.session

      1  # This program is free software: you can redistribute it and/or modify
      2  # it under the terms of the GNU General Public License as published by
      3  # the Free Software Foundation, either version 3 of the License, or
      4  # (at your option) any later version.
      5  #
      6  # This program is distributed in the hope that it will be useful,
      7  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      8  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      9  # GNU General Public License for more details.
     10  #
     11  # You should have received a copy of the GNU General Public License
     12  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     13
     14  #  screenlets.session (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
     15  #
     16  # INFO:
     17  # This module contains the ScreenletSession-class which handles the lower-level
     18  # things like startup, multiple instances and sessions. It should also become
     19  # the interface for load/save operations. The ScreenletSession is further
     20  # responsible for handling command-line args to the Screenlet and should maybe
     21  # offer some convenient way of setting Screenlet-options via commandline (so
     22  # one can do "NotesScreenlet --theme_name=green --scale=0.5" and launch the
     23  # Note with the given theme and scale)..
     24  #
     25  #
     26  # INFO:
     27  # - When a screenlet gets launched:
     28  #   - the first instance of a screenlet creates the Session-object (within the
     29  #     __main__-code)
     30  #   - the session object investigates the config-dir for the given Screenlet
     31  #     and restores available instances
     32  #   - else (if no instance was found) it simply creates a new instance of the
     33  #     given screenlet and runs its mainloop
     34  # - the --session argument allows setting the name of the session that will be
     35  #   used by the Screenlet (to allow multiple configs for one Screenlet)
     36  #
     37  # TODO:
     38  # - set attributes via commandline??
     39  #
     40
     41  import os
     42  import glob
     43  import random
     44  from xdg import BaseDirectory
     45
     46  import backend                  # import screenlets.backend module
     47  import services
     48  import utils
     49
     50  import dbus     # TEMPORARY!! only needed for workaround
     51  from stat import S_IRWXU, S_IRWXG, S_IRWXO
     52  import gettext
     53  import screenlets
     54  gettext.textdomain('screenlets')
     55  gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX +  '/share/locale')
     56
     57 -def _(s):
     58          return gettext.gettext(s)
     59
     60
     61  # temporary path for saving files for opened screenlets
     62  TMP_DIR         = '/tmp/screenlets'
     63  TMP_FILE        = 'screenlets.' + os.environ['USER'] + '.running'
     64
     65
     66 -class ScreenletSession (object):
     67          """The ScreenletSession manages instances of a Screenlet and handles
     68          saving/restoring options. Each Screenlet contains a reference to its
     69          session. Multiple instances of the same Screenlet share the same
     70          session-object."""
     71
     72          # constructor
     73 -        def __init__ (self, screenlet_classobj, backend_type='caching', name='default'):
     74                  object.__init__(self)
     75                  # check type
     76                  if not screenlet_classobj.__name__.endswith('Screenlet'):
     77                          # TODO: also check for correct type (Screenlet-subclass)!!
     78                          raise Exception("ScreenletSession.__init__ has to be called with a valid Screenlet-classobject as first argument!")
     79                  # init props
     80                  self.name               = name
     81                  self.screenlet  = screenlet_classobj
     82                  self.instances  = []
     83                  self.tempfile   = TMP_DIR + '/' + TMP_FILE
     84                  # check sys.args for "--session"-argument and override name, if set
     85                  self.__parse_commandline()
     86                  # set session path (and create dir-tree if not existent)
     87                  p = screenlet_classobj.__name__[:-9] + '/' + self.name + '/'
     88                  self.path = BaseDirectory.load_first_config('screenlets/' + p)
     89                  if self.path == None:
     90                          self.path = BaseDirectory.save_config_path('screenlets/' + p)
     91                  if self.path == None: self.path = os.path.join(screenlets.DIR_CONFIG,p)
     92                  if self.path:
     93                          if backend_type == 'caching':
     94                                  self.backend = backend.CachingBackend(path=self.path)
     95                          elif backend_type == 'gconf':
     96                                  self.backend = backend.GconfBackend()
     97                  else:
     98                          # no config-dir? use dummy-backend and note about problem
     99                          self.backend = backend.ScreenletsBackend()
    100                          print "Unable to init backend - settings will not be saved!"
    101                  # WORKAROUND: connect to daemon (ideally the daemon should watch the
    102                  #             tmpfile for changes!!)
    103
    104
    105                  # NOTE: daemon will be started by bus.get_object anyway!!!
    106                  #check for daemon
    107                  #proc = os.popen("""ps axo "%p,%a" | grep "python.*screenlets-daemon.py" | grep -v grep|cut -d',' -f1""").read()
    108
    109                  #procs = proc.split('\n')
    110                  #if len(procs) <= 1:
    111                  #       os.system('python -u ' + screenlets.INSTALL_PREFIX + '/share/screenlets-manager/screenlets-daemon.py &')
    112                  #       print 'No Daemon, Launching Daemon'
    113                  self.connect_daemon()
    114
    115 -        def connect_daemon (self):
    116                  """Connect to org.screenlets.ScreenletsDaemon."""
    117                  self.daemon_iface = None
    118                  bus = dbus.SessionBus()
    119                  if bus:
    120                          try:
    121                                  proxy_obj = bus.get_object(screenlets.DAEMON_BUS, screenlets.DAEMON_PATH)
    122                                  if proxy_obj:
    123                                          self.daemon_iface = dbus.Interface(proxy_obj, screenlets.DAEMON_IFACE)
    124                          except Exception, ex:
    125                                  print "Error in screenlets.session.connect_daemon: %s" % ex
    126
    127 -        def create_instance (self, id=None, **keyword_args):
    128                  """Create a new instance with ID 'id' and add it to this session. The
    129                  function returns either the new Screenlet-instance or None."""
    130                  # if id is none or already exists
    131                  if id==None or id=='' or self.get_instance_by_id(id) != None:
    132                          print "ID is unset or already in use - creating new one!"
    133                          id = self.__get_next_id()
    134                          dirlst = glob.glob(self.path + '*')
    135                          tdlen = len(self.path)
    136                          for filename in dirlst:
    137                                  filename = filename[tdlen:]             # strip path from filename
    138                                  print 'Loaded config from: %s' % filename
    139                                  if filename.endswith(id + '.ini'):
    140                                  # create new instance
    141                                          sl = self.create_instance(id=filename[:-4], enable_saving=False)
    142                                          if sl:
    143                                                  # set options for the screenlet
    144                                                  print "Set options in %s" % sl.__name__
    145                                                  #self.__restore_options_from_file (sl, self.path + filename)
    146                                                  self.__restore_options_from_backend(sl, self.path+filename)
    147                                                  sl.enable_saving(True)
    148                                                  # and call init handler
    149                                                  sl.finish_loading()
    150                                                  return sl
    151                  sl = self.screenlet(id=id, session=self, **keyword_args)
    152                  if sl:
    153                          self.instances.append(sl)               # add screenlet to session
    154                          # and cause initial save to store INI-file in session dir
    155                          sl.x = sl.x
    156                          return sl
    157                  return None
    158
    159 -        def delete_instance (self, id):
    160                  """Delete the given instance with ID 'id' and remove its session file.
    161                  When the last instance within the session is removed, the session dir
    162                  is completely removed."""
    163                  sl = self.get_instance_by_id(id)
    164                  if sl:
    165                          # remove instance from session
    166                          self.instances.remove(sl)
    167                          # remove session file
    168                          try:
    169                                  self.backend.delete_instance(id)
    170                          except Exception:
    171                                  print "Failed to remove INI-file for instance (not critical)."
    172                          # if this was the last instance
    173                          if len(self.instances) == 0:
    174                                  # maybe show confirmation popup?
    175                                  print "Removing last instance from session"
    176                                  # TODO: remove whole session directory
    177                                  print "TODO: remove self.path: %s" % self.path
    178                                  try:
    179                                          os.rmdir(self.path)
    180                                  except:
    181                                          print "Failed to remove session dir '%s' - not empty?" % self.name
    182                                  # ...
    183                                  # quit gtk on closing screenlet
    184                                  sl.quit_on_close = True
    185                          else:
    186                                  print "Removing instance from session but staying alive"
    187                                  sl.quit_on_close = False
    188                          # delete screenlet instance
    189                          sl.close()
    190                          del sl
    191                          return True
    192                  return False
    193
    194 -        def get_instance_by_id (self, id):
    195                  """Return the instance with the given id from within this session."""
    196                  for inst in self.instances:
    197                          if inst.id == id:
    198                                  return inst
    199                  return None
    200
    201 -        def quit_instance (self, id):
    202                  """quit the given instance with ID 'id'"""
    203
    204                  sl = self.get_instance_by_id(id)
    205                  if sl:
    206                          print self.instances
    207                          # remove instance from session
    208
    209
    210                          if len(self.instances) == 1:
    211                                  sl.quit_on_close = True
    212                          else:
    213                                  print "Removing instance from session but staying alive"
    214                                  sl.quit_on_close = False
    215                          self.backend.flush()
    216                          sl.close()
    217                          self.instances.remove(sl)
    218
    219                          # remove session file
    220                          return True
    221                  return False
    222
    223
    224 -        def start (self):
    225                  """Start a new session (or restore an existing session) for the
    226                  current Screenlet-class. Creates a new instance when none is found.
    227                  Returns True if everything worked well, else False."""
    228                  # check for a running instance first and use dbus-call to add
    229                  # a new instance in that case
    230                  #sln = self.screenlet.get_short_name()
    231                  sln = self.screenlet.__name__[:-9]
    232                  running = utils.list_running_screenlets()
    233                  if running and running.count(self.screenlet.__name__) > 0:
    234                  #if services.service_is_running(sln):
    235                          print "Found a running session of %s, adding new instance by service." % sln
    236                          srvc = services.get_service_by_name(sln)
    237                          if srvc:
    238                                  print "Adding new instance through: %s" % str(srvc)
    239                                  srvc.add('')
    240                                  return False
    241                  # ok, we have a new session running - indicate that to the system
    242                  self.__register_screenlet()
    243                  # check for existing entries in the session with the given name
    244                  print "Loading instances in: %s" % self.path
    245                  if self.__load_instances():
    246                          # restored existing entries?
    247                          print "Restored instances from session '%s' ..." % self.name
    248                          # call mainloop of first instance (starts application)
    249                          #self.instances[0].main()
    250                          self.__run_session(self.instances[0])
    251                  else:
    252                          # create new first instance
    253                          print 'No instance(s) found in session-path, creating new one.'
    254                          sl = self.screenlet(session=self, id=self.__get_next_id())
    255                          if sl:
    256                                  # add screenlet to session
    257                                  self.instances.append(sl)
    258                                  # now cause a save of the options to initially create the
    259                                  # INI-file for this instance
    260                                  self.backend.save_option(sl.id, 'x', sl.x)
    261                                  # call on_init-handler
    262                                  sl.finish_loading()
    263                                  # call mainloop and give control to Screenlet
    264                                  #sl.main()
    265                                  self.__run_session(sl)
    266                          else:
    267                                  print 'Failed creating instance of: %s' % self.classobj.__name__
    268                                  # remove us from the running screenlets
    269                                  self.__unregister_screenlet()
    270                                  return False
    271                  # all went well
    272                  return True
    273
    274 -        def __register_screenlet (self):
    275                  """Create new entry for this session in the global TMP_FILE."""
    276
    277                  # if tempfile not exists, create it
    278                  if  not self.__create_tempdir():
    279                          return False # error already returned
    280
    281                  # if screenlet not already added
    282                  running = utils.list_running_screenlets()
    283                  if running == None : running = []
    284                  if running.count(self.screenlet.__name__) == 0:
    285                  # open temp file for appending data
    286                          try:
    287                                  f = open(self.tempfile, 'a') # No need to create a empty file , append will do just fine
    288                          except IOError, e:
    289                                  print "Unable to open %s" % self.tempfile
    290                                  return False
    291                          else:
    292                                  print "Creating new entry for %s in %s" % (self.screenlet.__name__, self.tempfile)
    293                                  f.write(self.screenlet.__name__ + '\n')
    294                                  f.close()
    295                  else: print "Screenlet has already been added to %s" % self.tempfile
    296                  # WORKAROUND: for now we manually add this to the daemon,
    297                  #             ideally the daemon should watch the tmpdir for changes
    298                  if self.daemon_iface:
    299                          self.daemon_iface.register_screenlet(self.screenlet.__name__)
    300
    301 -        def __create_tempdir (self):
    302                  """Create the global temporary file for saving screenlets. The file is
    303                  used for indicating which screnlets are currently running."""
    304
    305                  # check for existence of TMP_DIR and create it if missing
    306                  if not os.path.isdir(TMP_DIR):
    307                          try:
    308                                  if os.path.exists(TMP_DIR):
    309                                          # something exists, but is not a directory
    310                                          os.remove(TMP_DIR)
    311
    312                                  print "No global tempdir found, creating new one."
    313                                  os.mkdir(TMP_DIR)
    314                                  # make the tmp directory accessible for all users
    315
    316                                  os.chmod(TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO)
    317                                  print 'Temp directory %s created.' % TMP_DIR
    318                          except OSError, e:
    319                                  print 'Error: Unable to create temp directory %s - screenlets-manager will not work properly.' % TMP_DIR
    320                                  print "Error was: %s"%e
    321                                  return False
    322                  return True
    323
    324
    325 -        def __unregister_screenlet (self, name=None):
    326                  """Delete this session's entry from the gloabl tempfile (and delete the
    327                  entire file if no more running screenlets are set."""
    328                  if not name:
    329                          name = self.screenlet.__name__
    330                  # WORKAROUND: for now we manually unregister from the daemon,
    331                  #             ideally the daemon should watch the tmpfile for changes
    332                  if self.daemon_iface:
    333                          try:
    334                                  self.daemon_iface.unregister_screenlet(name)
    335                          except Exception, ex:
    336                                  print "Failed to unregister from daemon: %s" % ex
    337                  # /WORKAROUND
    338                  # get running screenlets
    339                  running = utils.list_running_screenlets()
    340                  if running and len(running) > 0:
    341                          pass#print "Removing entry for %s from global tempfile %s" % (name, self.tempfile)
    342                          try:
    343                                  running.remove(name)
    344                          except:
    345                                  # not found, so ok
    346                                  print "Entry not found. Will (obviously) not be removed."
    347                                  return True
    348                          # still running screenlets?
    349                          if running and len(running) > 0:
    350                                  # re-save new list of running screenlets
    351                                  f = open(self.tempfile, 'w')
    352                                  if f:
    353                                          for r in running:
    354                                                  f.write(r + '\n')
    355                                          f.close()
    356                                          return True
    357                                  else:
    358                                          print "Error global tempfile not found. Some error before?"
    359                                  return False
    360                          else:
    361                                  print 'No more screenlets running.'
    362                                  self.__delete_tempfile(name)
    363                  else:
    364                          print 'No screenlets running?'
    365                          return False
    366
    367 -        def __delete_tempfile (self, name=None):
    368                  """Delete the tempfile for this session."""
    369                  if self.tempfile and os.path.isfile(self.tempfile):
    370                          print "Deleting global tempfile %s" % self.tempfile
    371                          try:
    372                                  os.remove(self.tempfile)
    373                                  return True
    374                          except:
    375                                  print "Error: Failed to delete global tempfile"
    376                                  return False
    377
    378 -        def __get_next_id (self):
    379                  """Get the next ID for an instance of the assigned Screenlet."""
    380                  num = 1
    381                  sln = self.screenlet.__name__[:-9]
    382                  id = sln + str(num)
    383                  while self.get_instance_by_id(id) != None:
    384                          id = sln + str(num)
    385                          num += 1
    386                  return id
    387
    388 -        def __load_instances (self):
    389                  """Check for existing instances in the current session, create them
    390                  and store them into self.instances if any are found. Returns True if
    391                  at least one instance was found, else False."""
    392                  dirlst = glob.glob(self.path + '*')
    393                  tdlen = len(self.path)
    394                  for filename in dirlst:
    395                          filename = filename[tdlen:]             # strip path from filename
    396                          print 'Loaded config from: %s' % filename
    397                          if filename.endswith('.ini'):
    398                                  # create new instance
    399                                  sl = self.create_instance(id=filename[:-4], enable_saving=False)
    400                                  if sl:
    401                                          # set options for the screenlet
    402                                          print "Set options in %s" % sl.__name__
    403                                          #self.__restore_options_from_file (sl, self.path + filename)
    404                                          self.__restore_options_from_backend(sl, self.path+filename)
    405                                          sl.enable_saving(True)
    406                                          # and call init handler
    407                                          sl.finish_loading()
    408                                  else:
    409                                          print "Failed to create instance of '%s'!" % filename[:-4]
    410                  # if instances were found, return True, else False
    411                  if len(self.instances) > 0:
    412                          return True
    413                  return False
    414
    415          # replacement for above function
    416 -        def __restore_options_from_backend (self, screenlet, filename):
    417                  """Restore and apply a screenlet's options from the backend."""
    418                  # disable the canvas-updates in the screenlet
    419                  screenlet.disable_updates = True
    420                  # get options for SL from backend
    421                  opts = self.backend.load_instance(screenlet.id)
    422                  if opts:
    423                          for o in opts:
    424                                  # get the attribute's Option-object from Screenlet
    425                                  opt = screenlet.get_option_by_name(o)
    426                                  # NOTE: set attribute in Screenlet by calling the
    427                                  # on_import-function for the Option (to import
    428                                  # the value as the required type)
    429                                  if opt:
    430                                          setattr(screenlet, opt.name, opt.on_import(opts[o]))
    431                  # re-enable updates and call redraw/reshape
    432                  screenlet.disable_updates = False
    433                  screenlet.redraw_canvas()
    434                  screenlet.update_shape()
    435
    436 -        def __run_session (self, main_instance):
    437                  """Run the session by calling the main handler of the given Screenlet-
    438                  instance. Handles sigkill (?) and keyboard interrupts."""
    439                  # add sigkill-handler
    440                  import signal
    441                  def on_kill(*args):
    442                          #print "Screenlet has been killed. TODO: make this an event"
    443                          pass
    444                  signal.signal(signal.SIGTERM, on_kill)
    445                  # set name of tempfile for later (else its missing after kill)
    446                  tempfile = self.screenlet.__name__
    447                  # start
    448                  try:
    449                          # start mainloop of screenlet
    450                          main_instance.main()
    451                  except KeyboardInterrupt:
    452                          # notify when daemon is closed
    453                          self.backend.flush()
    454                          print "Screenlet '%s' has been interrupted by keyboard. TODO: make this an event" % self.screenlet.__name__
    455                  except Exception, ex:
    456                          print "Exception in ScreenletSession: " + ex
    457                  # finally delete the tempfile
    458                  self.__unregister_screenlet(name=tempfile)
    459
    460 -        def __parse_commandline (self):
    461                  """Check commandline args for "--session" argument and set session
    462                  name if found. Runs only once during __init__.
    463                  TODO: handle more arguments and maybe allow setting options by
    464                  commandline"""
    465                  import sys
    466                  for arg in sys.argv[1:]:
    467                          # name of session?
    468                          if arg.startswith('--session=') and len(arg)>10:
    469                                  self.name = arg[10:]
    470
    471
    472
    473 -def create_session (classobj, backend='caching', threading=False):
    474          """A very simple utility-function to easily create/start a new session."""
    475
    476          if threading:
    477                  import gtk
    478                  gtk.gdk.threads_init()
    479          session = ScreenletSession(classobj, backend_type=backend)
    480          session.start()
    481