screenlets.backend

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

# Source Code for Module screenlets.backend

      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.backend (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
     15  #
     16  # INFO:
     17  # - The backend offers an abstracted way of saving a Screenlet's data
     18  #
     19  # TODO:
     20  # - add "type"-argument to save_option and read_option to be able to correctly
     21  #   set the values in GconfBackend (instead of storing only strings).
     22  #
     23
     24  import glob
     25  import os
     26  import gtk
     27  import gobject
     28  import gettext
     29  import screenlets
     30
     31  gettext.textdomain('screenlets')
     32  gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX +  '/share/locale')
     33
     34 -def _(s):
     35          return gettext.gettext(s)
     36
     37
     38  try:
     39          import gconf
     40  except:
     41          print "GConf python module not found. GConf settings backend is disabled."
     42
     43
     44 -class ScreenletsBackend(object):
     45          """The backend performs the loading/saving of the 'key=value'-strings.
     46          Extend this superclass to implement different saving-backends."""
     47
     48 -        def __init__ (self):
     49                  pass
     50
     51 -        def delete_instance (self, id):
     52                  """Delete an instance's configuration by its id."""
     53                  pass
     54
     55 -        def flush (self):
     56                  """Immediately store all values to disk (in case the backend doesn't
     57                  save in realtime anyway."""
     58                  pass
     59
     60 -        def load_option (self, id, name):
     61                  """Load one option for the instance with the given id."""
     62                  pass
     63
     64 -        def load_instance (self, id):
     65                  """Load all options for the instance with the given id."""
     66                  pass
     67
     68 -        def save_option (self, id, name, value):
     69                  """Save one option for the instance with the given id."""
     70                  pass
     71
     72
     73 -class GconfBackend (ScreenletsBackend):
     74          """Backend for storing settings in the GConf registry"""
     75
     76          gconf_dir = '/apps/screenlets/'
     77
     78 -        def __init__ (self):
     79                  ScreenletsBackend.__init__(self)
     80                  print 'GConfBackend: initializing'
     81                  self.client = gconf.client_get_default()
     82
     83 -        def delete_instance (self, id):
     84                  """Delete an instance's configuration by its id."""
     85                  os.system('gconftool-2 --recursive-unset ' + self.key + id)
     86                  return True
     87
     88 -        def flush (self):
     89                  """Immediately store all values to disk (in case the backend doesn't
     90                  save in realtime anyway."""
     91                  pass    #No need, GConf saves in realtime
     92
     93 -        def load_option (self, id, name):
     94                  """Load one option for the instance with the given id."""
     95                  return self.client.get_string(self.gconf_dir + id + '/' + name)
     96
     97 -        def load_instance (self, id):
     98                  """Load all options for the instance with the given id."""
     99                  keys = []
    100                  vals = []
    101                  for i in self.client.all_entries(self.gconf_dir + id):
    102                          keys.append(i.key.split('/')[4])
    103                          vals.append(self.client.get_string(i.key))
    104                  return dict(zip(keys, vals))
    105                  return None
    106
    107 -        def save_option (self, id, name, value):
    108                  """Save one option for the instance with the given id."""
    109                  self.client.set_string(self.gconf_dir + id + '/' + name, value)
    110                  print 'Saved option %s%s/%s = %s' % (self.gconf_dir, id, name, value)
    111
    112
    113 -class CachingBackend (ScreenletsBackend):
    114          """A backend that stores the settings in arrays and saves after a short
    115          interval to avoid overhead when multiple values are set within a short time.
    116          The data gets saved into $HOME/.config/Screenlets/<Screenletname>/, in a
    117          file for each element (named like its id with the extension '.ini')."""
    118
    119          # internals
    120          __instances = {}                # a dict with (id:dict)-entries cntaining the data
    121          __delay_time = 3000             # delay to wait before performing save
    122          __timeout = None                # the id of the timeout-function
    123          __queue = []                    # list with ids of instances that need saving
    124
    125          # attribs
    126          path = ''                               # the path to store the files
    127
    128          # Constructor
    129 -        def __init__ (self, path):
    130                  ScreenletsBackend.__init__(self)
    131                  self.path = path
    132                  self.__load_cache()
    133
    134 -        def delete_instance (self, id):
    135                  """Delete an instance from the list and from the filesystem."""
    136                  if self.__instances.has_key(id):
    137                          del self.__instances[id]
    138                          try:
    139                                  import os
    140                                  os.remove(self.path + id + '.ini')
    141                          except Exception,ex:
    142                                  print ex
    143                                  print "Temporary file didn't exist - nothing to remove."
    144                                  return False
    145                  print "CachingBackend: <#%s> removed!" % id
    146                  return True
    147
    148 -        def flush (self):
    149                  """Immediately save all pending data."""
    150                  self.__save_cache()
    151
    152 -        def save_option (self, id, name, value):
    153                  """Save option for an instance to cache and start saving-timeout
    154                  for that element (value must be of type string)."""
    155                  # create key for option, if not existent yet
    156                  if self.__instances.has_key(id) == False:
    157                          self.__instances[id] = {}
    158                  # set option in array
    159                  self.__instances[id][name] = str(value)
    160                  #print "CachingBackend.save_option: "+name+"="+self.__instances[id][name]
    161                  # if id is not already in queue, add the id to the queue
    162                  if self.__queue.count(id) == 0:
    163                          self.__queue.append(id)
    164                  # reset timeout and start new
    165                  if self.__timeout:
    166                          gobject.source_remove(self.__timeout)
    167                  self.__timeout = gobject.timeout_add(self.__delay_time,
    168                          self.__save_cache)#, id)
    169
    170 -        def load_option (self, id, name):
    171                  """TODO: Load option from the backend (returned as str)."""
    172                  return self.__instances[id][name]
    173
    174 -        def load_instance (self, id):
    175                  """Load all options for the instance with the given id."""
    176                  #print "Load element: "+id
    177                  if self.__instances.has_key(id):
    178                          return self.__instances[id]
    179                  return None
    180
    181 -        def __load_cache (self):
    182                  """Load all cached files from path."""
    183                  # perform saving
    184                  print "CachingBackend: Loading instances from cache"
    185                  # get dir content of self.path
    186                  dirname = self.path
    187                  dirlst = glob.glob(dirname + '*')
    188                  tdlen = len(dirname)
    189                  lst = []
    190                  for fname in dirlst:
    191                          dname = fname[tdlen:]
    192                          if dname.endswith('.ini'):
    193                                  id = dname[:-4]
    194                                  print "CachingBackend: Loading <%s>" % id
    195                                  #print "ID: "+id
    196                                  if self.__instances.has_key(id) == False:
    197                                          self.__instances[id] = {}
    198                                  # open file
    199                                  try:
    200                                          f = open(fname, 'r')
    201                                          lines = f.readlines()
    202                                          # read all options for this element from file
    203                                          for line in lines:
    204                                                  #print "LOAD: "+line[:-1]
    205                                                  parts = line[:-1].split('=', 1)
    206                                                  if len(parts) > 1:
    207                                                          # undocumented features to resize screenlet dynamically on first launch
    208                                                          # width, height must precede rel_x and rel_y with "_"
    209                                                          # by boamaod for Estobuntu
    210                                                          if parts[0] == 'x':
    211                                                                  if parts[1].startswith("*"): # if * is added, take distance from the opposite side
    212                                                                          parts[1] = parts[1].strip("*")
    213                                                                          add_width = 0
    214                                                                          if parts[1].startswith("_"): # if _ is added, take it to be right corner
    215                                                                                  add_width = int(float(self.__instances[id]["width"])*float(self.__instances[id]["scale"]))
    216                                                                                  print "ADD W", add_width
    217                                                                          parts[1] = str(gtk.gdk.screen_width() - int(parts[1].strip("_")) - add_width)
    218                                                                          print ">>>X", parts[1]
    219                                                          if parts[0] == 'y':
    220                                                                  if parts[1].startswith("*"): # if * is added, take distance from the opposite side
    221                                                                          parts[1] = parts[1].strip("*")
    222                                                                          add_height = 0
    223                                                                          if parts[1].startswith("_"): # if _ is added, take it to be bottom corner
    224                                                                                  add_height = int(float(self.__instances[id]["height"])*float(self.__instances[id]["scale"]))
    225                                                                                  print "ADD H", add_height
    226                                                                          parts[1] = str(gtk.gdk.screen_height() - int(parts[1].strip("_")) - add_height)
    227                                                                          print ">>>Y", parts[1]
    228                                                          if parts[0] == 'rel_x':
    229                                                                  parts[0] = 'x'
    230                                                                  add_width = 0
    231                                                                  if parts[1].startswith("_"): # if _ is added, take it to be right corner
    232                                                                          add_width = int(float(self.__instances[id]["width"])*float(self.__instances[id]["scale"]))
    233                                                                          print "ADD W", add_width
    234                                                                  parts[1] = str(int(gtk.gdk.screen_width()*float(parts[1].strip("_"))) - add_width)
    235                                                                  print ">>>X", parts[1]
    236                                                          if parts[0] == 'rel_y':
    237                                                                  parts[0] = 'y'
    238                                                                  add_height = 0
    239                                                                  if parts[1].startswith("_"): # if _ is added, take it to be bottom corner
    240                                                                          add_height = int(float(self.__instances[id]["height"])*float(self.__instances[id]["scale"]))
    241                                                                          print "ADD H", add_height
    242                                                                  parts[1] = str(int(gtk.gdk.screen_height()*float(parts[1].strip("_"))) - add_height)
    243                                                                  print ">>>Y", parts[1]
    244                                                          if parts[0] == 'rel_scale':
    245                                                                  parts[0] = 'scale'
    246                                                                  scale = float(self.__instances[id]["scale"])
    247                                                                  initial_scale = scale + float(gtk.gdk.screen_height()*gtk.gdk.screen_width())/float(parts[1])
    248                                                                  if initial_scale < 1.5:
    249                                                                          initial_scale = 1.5
    250                                                                  if initial_scale > 3:
    251                                                                          initial_scale = 3
    252                                                                  parts[1] = str(initial_scale)
    253  #                                                               parts[1] = str(gtk.gdk.screen_height()/float(parts[1]))
    254                                                                  print ">>>SCALE", parts[1]
    255                                                          if parts[0] == 'rel_font_name':
    256                                                                  parts[0] = 'font_name'
    257                                                                  print "|||", parts[1]
    258                                                                  font_parts = parts[1].rsplit(" ")
    259                                                                  parts[1]=""
    260                                                                  for fp in font_parts:
    261                                                                          if len(fp)>0 and len(fp.strip("0123456789."))==0:
    262                                                                                  parts[1]+= str( round(float(fp)*float(self.__instances[id]["scale"]), 1) ) + " "
    263                                                                          else:
    264                                                                                  parts[1]+= fp + " "
    265                                                                  parts[1] = parts[1].strip(" ")
    266                                                                  print ">>>FONT_NAME", parts[1]
    267                                                          # End of dynamic resize section
    268                                                          print "%s='%s'" % (parts[0], parts[1])
    269                                                          self.__instances[id][parts[0]] = parts[1]
    270                                          f.close()
    271                                  except Exception, ex:
    272                                          print "Error while loading options: %s" % str(ex)
    273
    274 -        def __save_cache (self):
    275                  """Save the cache (for all pending instances in queue) to self.path."""
    276                  # loop through all instances in queue:
    277                  for id in self.__queue:
    278                          # if element with id not exists, remove it and break
    279                          if self.__instances.has_key(id) == False:
    280                                  print "Queue-element <%s> not found (already removed?)!" % id
    281                                  self.__queue.remove(id)
    282                                  break
    283                          # create list with options
    284                          #print "CachingBackend: Saving <#%s> :) ..." % id
    285                          lst = []
    286                          for oname in self.__instances[id]:
    287                                  lst.append([oname, self.__instances[id][oname]])
    288                          # and save them (if any)
    289                          if len(lst) > 0:
    290                                  self.__save_settings (self.path + id + '.ini', lst)
    291                  # clear queue
    292                  self.__queue = []
    293                  # NOT continue the timeout-function (!!!!!)
    294                  return False
    295
    296 -        def __save_settings (self, filename, lst):
    297                  """ Try to save settings in a file, first save this to a temporal file avoid encodings a disk full errors """
    298                  filenametmp = filename + '.tmp'
    299                  isOk = True
    300                  newini = ''
    301                  try:
    302                          # Is posible to fail with encoding error?
    303                          for el in lst:
    304                                  newini += "%s=%s\n" % (el[0], el[1])
    305                  except:
    306                          isOk = False
    307                          print "error while convert config to string (encoding error?), I lose your last changes :'("
    308
    309                  if isOk:
    310                          # Write the new settings to a temporal file, disk full, encoding, rights may fails at this point.
    311                          try:
    312                                  open(filenametmp, 'w').write(newini)
    313                          except:
    314                                  isOk = False
    315                                  print "error while saving configuration to a temporal file %s, disk full?" % filenametmp
    316
    317                  if isOk:
    318                          # Move saved settings to definitive configuration file, disk error o incorrect rights may fails at this point.
    319                          try:
    320                                  import shutil
    321                                  shutil.move(filenametmp, filename)
    322                          except:
    323                                  print "error while moving temporal file to configuration file, %s > %s, sorry, I lose your settings. :'(" % (filenametmp, filename)
    324