# -*- mode: python; coding: utf-8 -*- import sys, thread, threading, time, re import traceback, gc import logging import os import weakref import shelve import gobject import gtk import gtk.glade import config DEBUG5 = 5 #main_ident = None main_thead = None def set_ident(): #global main_ident global main_thread #main_ident = thread.get_ident() main_thread = threading.currentThread() #print main_ident #print main_thread def list_merge(*args): return list(set(reduce(list.__add__, args, list()))) # Classes for composite inspectors class Record: "Records ad hoc" def __init__(self, **entries): self.__dict__.update(entries) def __getattr__(self, attr): if not self.__dict__.has_key(attr): raise AttributeError, attr return self.__dict__.get(attr, None) def __setattr__(self, attr, value): self.__dict__[attr] = value def __repr__(self): retval = '' for i in self.__dict__.keys(): retval += "%s:'%s', " % (i, self.__dict__[i]) return retval FS_URI_CO = re.compile('(?:(.*?)(?::(.*))?@)?(.*?)(/.*)') class URIParsingException(Exception): pass def parse_uri(text_uri): uri = text_uri.strip() if not uri: raise URIParsingException if not '://' in uri: logging.warning("Default method is: %s" % config.DEFAULT_METHOD) return URI("%s://%s" % (config.DEFAULT_METHOD, uri), config.DEFAULT_METHOD, uri) return URI(text_uri, *text_uri.split('://')) def parse_fs_uri(uri): # / # /path # method:/// # method:///path # method://host [COMPROBAR] # method://host/path # method://user@host/path # method://user:passwd@host/path if uri.kind == 'fs': return if not '/' in uri.path: uri.path += '/' logging.debug('parsing: %s' % uri.path) uri_mo = FS_URI_CO.match(uri.path) if uri_mo == None: raise URIParsingException(str(uri)) uri.user, uri.passwd, uri.host, uri.path = uri_mo.groups() uri.kind = 'fs' class URI(Record): def __init__(self, text_uri, method, path, user=None, passwd=None, host=None, port=None): Record.__init__(self, method=method, path=path, user=user, passwd=passwd, host=host, port=port) self.text_uri = text_uri self.kind = None def __to_str(self): retval = self.method + '://' if self.user: retval += self.user if self.passwd: retval += ':' + self.passwd retval += '@' if self.host: retval += self.host return retval + self.path def __str__(self): return self.text_uri def __eq__(self, other): return str(self) == str(other) def show(self): print 'URI:', str(self) print 'method:', self.method print 'user: ', self.user print 'passwd:', self.passwd print 'host: ', self.host print 'port: ', self.port print 'path: ', self.path # implicit function decorator (use as @gtksafe) def gtksafe(fn): def decorated(*args): #print threading.currentThread() is main_thread #print thread.get_ident(), main_ident if threading.currentThread() is main_thread: return fn(*args) #print 'enter:', fn.__name__ gtk.gdk.threads_enter() #print 'ENTER:', fn.__name__ try: return fn(*args) finally: gtk.gdk.flush() #print 'leave:', fn.__name__ gtk.gdk.threads_leave() #print 'LEAVE:', fn.__name__ return decorated def gtkexec(fn, args=()): if threading.currentThread().getName() == 'MainThread': # or isinstance(current, threading._DummyThread): return fn(*args) gtk.gdk.threads_enter() try: return fn(*args) finally: gtk.gdk.flush() gtk.gdk.threads_leave() def locked2(lock): def wrap(fn): def decorated(*args): lock.acquire() try: return fn(*args) finally: lock.release() return decorated return wrap def locked(lock): def wrap(fn): def decorated(*args): return fn(*args) return decorated return wrap class TPListStore(gtk.ListStore): class ItemAlreadyExists(Exception): pass def __init__(self): gtk.ListStore.__init__(self, object) self.__model = {} self.lock = threading.Lock() #def __getattribute__(self, key): # print 'TPListStore:', key # return gtk.ListStore.__getattribute__(self, key) def append(self, val): raise NotImplementedError def remove(self, iter): raise NotImplementedError def __setitem__(self, key, val): raise NotImplementedError def __iter__(self): raise NotImplementedError def append_item(self, item): @locked(self.lock) @gtksafe def func(): if item.key in self.__model.keys(): raise self.ItemAlreadyExists(item.key) titer = gtk.ListStore.append(self, [item]) self.__model[item.key] = titer return titer return func() def clear(self): @locked(self.lock) @gtksafe def func(): self.__model.clear() retval = gtk.ListStore.clear(self) return retval return func() def keys(self): return self.__model.keys()[:] @gtksafe def items(self): return [gtk.ListStore.__getitem__(self, iter)[0] for iter in self.__model.values()] # debería tener @gtksave, pero no es reentrante def __getitem__(self, index): # index may be treeiter or path return gtk.ListStore.__getitem__(self, index) def get_item_bykey(self, key): @locked(self.lock) @gtksafe def func(): if not key in self.__model.keys(): raise KeyError(key) return gtk.ListStore.__getitem__(self, self.__model[key])[0] return func() def get_item_bypath(self, path): @locked(self.lock) @gtksafe def func(): return gtk.ListStore.__getitem__(self, path)[0] return func() def __del_item(self, key): try: titer = self.__model[key] retval = gtk.ListStore.remove(self, titer) self.__model.pop(key) return retval except KeyError: self.logger.warning("Object '%s' isn't in the store" % key) def del_item_bykey(self, key): @locked(self.lock) @gtksafe def func(): return self.__del_item(key) return func() def del_items_bykey(self, keys): @locked(self.lock) @gtksafe def func(): for k in keys: self.__del_item(k) return func() def delete_items(self, items): for i in items: try: self.store.del_item_bykey(i.key) except KeyError: self.logger.warning("Object '%s' isn't in the internal model" % i.key) return False def item_changed(self, key): @locked(self.lock) @gtksafe def func(): try: titer = self.__model[key] except KeyError: return None path = self.get_path(titer) return gtk.ListStore.row_changed(self, path, titer) return func() #@gtksafe #def call_with_item(self, key, callback, args=[]): # callback(self.__get_item_bykey(key), *args) def __repr__(self): return str(self.__model.keys()) #class TPListStore2(gtk.ListStore): # # class ItemAlreadyExists(Exception): pass # # def __init__(self): # gtk.ListStore.__init__(self, object) # self.__model = {} # self.lock = threading.Lock() # # def remove(self, iter): # raise NotImplementedError # # def __setitem__(self, key, val): # raise NotImplementedError # # def append_item(self, item): # if item.key in self.__model.keys(): # raise self.ItemAlreadyExists(item.key) # # self.lock.acquire() # titer = gtkexec(gtk.ListStore.append, (self, [item])) # self.__model[item.key] = titer # self.lock.release() # return titer # # def clear(self): # self.lock.acquire() # self.__model.clear() # retval = gtkexec(gtk.ListStore.clear, (self,)) # self.lock.release() # return retval # # def keys(self): # return self.__model.keys() # # def get_item_bykey(self, key): # if not key in self.__model.keys(): raise KeyError(key) # return gtk.ListStore.__getitem__(self, self.__model[key])[0] # # def get_item_bypath(self, path): # return gtk.ListStore.__getitem__(self, path)[0] # # def del_item_bykey(self, key): # titer = self.__model[key] # self.lock.acquire() # retval = gtkexec(gtk.ListStore.remove, (self, titer)) # self.__model.pop(key) # self.lock.release() # return retval # # def item_changed(self, key): # try: # titer = self.__model[key] # except KeyError: # return None # # self.lock.acquire() # path = self.get_path(titer) # retval = gtkexec(gtk.ListStore.row_changed, (self, path, titer)) # self.lock.release() # return retval # # def __repr__(self): # return str(self.__model.keys()) class ThreadFunc(threading.Thread): '''Para ejecutar una función en otro hilo y se necesite poder indicarle que debe terminar. En otro caso usar thread.start_new_thread''' def __init__(self, function, args=(), kargs={}, start=True): threading.Thread.__init__(self, name=function.__name__) self.function = function self.args = args self.kargs = kargs self.active = threading.Event() self.active.set() if start: self.start() def cancel(self): print '*' * 20 logging.debug("ThreadFunc.cancel '%s'" % self.function.__name__) self.active.clear() def run(self): self.function(*self.args, **self.kargs) class ThreadTimeout(threading.Thread): "Based on threding.Timer class" def __init__(self, interval, function, args=[], at_init=False): threading.Thread.__init__(self, name=function.__name__) self.interval = interval # secs self.function = function self.args = args self.at_init = at_init self.finished = threading.Event() self.play = threading.Event() self.start() def __del__(self): self.cancel() def cancel(self): "terminates the thread" logging.debug("ThreadTimeout.cancel '%s'" % self.function.__name__) self.play.set() self.finished.set() def pause(self): print self.play.clear() def play(self): print 'play' self.play.set() def run(self): if self.at_init: if not self.function(*self.args): return while 1: self.finished.wait(self.interval) if self.finished.isSet(): break if not self.function(*self.args): break # Old Event pool #class EventPoolOld(threading.Thread): # # class Task: # def __init__(self, interval, func, args=[]): # self.interval = interval # self.function = func # self.args = args # self.diff = interval # # def __repr__(self): # return "[%s] i:%s, d:%s" % (self.function.__name__, # self.interval, self.diff) # # def __init__(self, name=None): # threading.Thread.__init__(self, name=name) # self.lock = threading.Lock() # self.finished = threading.Event() # self.tasks = [] # self.cancelled = [] # self.reseted = [] # # def add_func(self, interval, function, *args): # self.lock.acquire() # retval = self.Task(interval, function, args) # self.__add_task(retval) # self.lock.release() # return retval # # def cancel(self, task=None): # if isinstance(task, self.Task): # self.cancelled.append(task) # if isinstance(task, list): # for t in task: # self.cancel(t) # elif task == None: # self.finished.set() # # # # def reset(self, task): # self.reseted.append(task) # # def __cancel_task(self, task): # if task not in self.tasks: return # i = self.tasks.index(task) # if len(self.tasks) > i+1: # self.tasks[i+1].diff += task.diff # self.tasks.remove(task) # # # def __add_task(self, new_task): # total = 0 # for i,t in enumerate(self.tasks): # if total + t.diff > new_task.interval: # new_task.diff = new_task.interval - total # t.diff -= new_task.diff # self.tasks.insert(i, new_task) # return # # total += t.diff # # new_task.diff = new_task.interval - total # self.tasks.append(new_task) # # # def __pending(self): # for t in self.cancelled: # logging.debug('EventPool.Deactivating %s' % t.function.__name__) # self.__cancel_task(t) # # for t in self.reseted: # logging.debug('EventPool.Reseting %s' % t.function.__name__) # self.__cancel_task(t) # self.__add_task(t) # # self.cancelled = [] # self.reseted = [] # # # def run(self): # while not self.finished.isSet(): # #print self.tasks # if self.tasks: # self.lock.acquire() # # while self.tasks and self.tasks[0].diff <= 0: # task = self.tasks[0] # del self.tasks[0] # if task.function(*task.args): # self.__add_task(task) # # if self.tasks: # self.tasks[0].diff -= 1 # # self.__pending() # self.lock.release() # # time.sleep(1) # # for t in self.tasks: # logging.debug("EventPool: Deactivated '%s'" % t.function.__name__) # logging.debug('EventPool destroyed') class GobjectEventPool(threading.Thread): class Task: def __init__(self, ctx, interval, func, args=[]): self.ctx = ctx self.interval = interval self.function = func self.args = args self.diff = interval self.source_id = None def __repr__(self): return "%s.%s i:%s, d:%s" % (self.ctx.name, self.function.__name__, self.interval, self.diff) class Context: def __init__(self, poll, persistent, name=''): self.pool = poll self.persistent = persistent self.name = name self.tasks = [] self.reset = self.add def add_func(self, interval, func, *args): retval = GobjectEventPool.Task(self, interval, func, args) self.add(retval) return retval def new_task(self, interval, function, *args): return GobjectEventPool.Task(self, interval, function, args) def add(self, task): if task not in self.tasks: self.tasks.append(task) self.pool.add_task(task) return task def cancel(self, task=None): if isinstance(task, GobjectEventPool.Task): try: self.tasks.remove(task) except ValueError: logging.error('GobjectEventPool.cancel: Task not exist %s', repr(task)) return self.pool.cancel_task_now(task) return if task == None: task = self.tasks[:] if isinstance(task, list): for t in task: self.cancel(t) def __repr__(self): return repr(self.tasks) def __init__(self, persistent=True, name=None): threading.Thread.__init__(self, name=name) #self.persistent = persistent self.name = name #self.lock = threading.RLock() #self.finished = threading.Event() self.__contexts = [] self.__tasks = [] self.__added = [] def new_context(self, persistent=True, name=''): #impedir si el hilo está en marcha ctx = GobjectEventPool.Context(self, persistent, name) self.__contexts.append(ctx) return ctx def add_task(self, task): if task.source_id: self.cancel_task_now(task) task.source_id = gobject.timeout_add_seconds(task.interval, task.function, *task.args) def cancel_task_now(self, task): logging.log(DEBUG5, "EventPool:cancel: '%s'" % task) gobject.source_remove(task.source_id) task.source_id = None def close(self): logging.warning("GobjectEventPool:close: '%s'" % self.name) #self.finished.set() def run(self): pass class EventPool(threading.Thread): class Task: def __init__(self, ctx, interval, func, args=[]): self.ctx = ctx self.interval = interval self.function = func self.args = args self.diff = interval def __repr__(self): return "%s.%s i:%s, d:%s" % (self.ctx.name, self.function.__name__, self.interval, self.diff) class Context: def __init__(self, poll, persistent, name=''): self.pool = poll self.persistent = persistent self.name = name self.tasks = [] self.reset = self.add def add_func(self, interval, func, *args): retval = EventPool.Task(self, interval, func, args) self.add(retval) return retval def new_task(self, interval, function, *args): return EventPool.Task(self, interval, function, args) def add(self, task): if task not in self.tasks: self.tasks.append(task) self.pool.add_task(task) return task def cancel(self, task=None): if isinstance(task, EventPool.Task): try: self.tasks.remove(task) except ValueError: logging.error('EventPool.cancel: Task not exist %s', repr(task)) return self.pool.cancel_task_now(task) return if task == None: task = self.tasks[:] if isinstance(task, list): for t in task: self.cancel(t) #def reset(self, task): # if not task in self.tasks: # self.pool.add_task(task) # else: # self.pool.reset_task(task) def __repr__(self): return repr(self.tasks) def __init__(self, persistent=True, name=None): threading.Thread.__init__(self, name=name) self.persistent = persistent self.name = name self.lock = threading.RLock() self.finished = threading.Event() self.__contexts = [] self.__tasks = [] self.__added = [] def new_context(self, persistent=True, name=''): #impedir si el hilo está en marcha ctx = EventPool.Context(self, persistent, name) self.__contexts.append(ctx) return ctx #def remove_context(self): def add_task(self, task): self.__added.append(task) def cancel_task_now(self, task): self.lock.acquire() if task in self.__added: # si no ha llegado a añadirse a __tasks self.__added.remove(task) else: self.__cancel_task(task) self.lock.release() def close(self): logging.warning("EventPool:close: '%s'" % self.name) self.finished.set() def __add_task(self, new_task): total = 0 for i,t in enumerate(self.__tasks): if total + t.diff > new_task.interval: new_task.diff = new_task.interval - total t.diff -= new_task.diff self.__tasks.insert(i, new_task) return total += t.diff new_task.diff = new_task.interval - total self.__tasks.append(new_task) def __cancel_task(self, task): logging.log(DEBUG5, "EventPool:cancel: '%s'" % task) if task not in self.__tasks: logging.warning("EventPool:cancel: '%s' missing" % task) return i = self.__tasks.index(task) if len(self.__tasks) > i+1: self.__tasks[i+1].diff += task.diff self.__tasks.remove(task) def __pending(self): for t in self.__added: if t in self.__tasks: logging.log(DEBUG5, "EventPool:reset: '%s'", t) self.__cancel_task(t) logging.log(DEBUG5, "EventPool:add: '%s'" % t) self.__add_task(t) self.__added = [] # clear empty __contexts for c in self.__contexts: if not c.persistent and not c.tasks: logging.log(DEBUG5, "EventPool:remove context: '%s'", c) self.__contexts.remove(c) if not self.persistent and not self.__contexts: logging.log(DEBUG5, "EventPool:finish") self.finished.set() def run(self): while not self.finished.isSet(): #print 'Contexts:', self.__contexts #print 'tasks:', self.__tasks #print '--' self.lock.acquire() self.__pending() while self.__tasks and self.__tasks[0].diff <= 0: task = self.__tasks[0] self.__tasks.remove(task) result = task.function(*task.args) logging.log(DEBUG5, "EventPool.exec: '%s', retval:%s", task, result) if result: self.__add_task(task) else: try: task.ctx.tasks.remove(task) except ValueError: logging.error("EventPool.remove: '%s' not in context %s", task, task.ctx) if self.__tasks: self.__tasks[0].diff -= 1 self.lock.release() time.sleep(1) for t in self.__tasks: logging.log(DEBUG5, "EventPool: Deactivated '%s'" % t) logging.log(DEBUG5, 'EventPool destroyed') # file-like object for looging output in a textview class TextAreaStream(file): def __init__(self, textview): self.textview = textview self.buffer = gtk.TextBuffer() self.textview.set_buffer(self.buffer) def write(self, msg): gobject.idle_add(self.buffer.insert_at_cursor, msg) gobject.idle_add(self.textview.scroll_mark_onscreen, self.buffer.get_insert()) def flush(self): pass class MItem: def __init__(self, **entries): self.title = '' self.render = 'text' self.id = None #FIMXE: lo requieren las vistas, pero deberían inferirlo ellas self.attr = None for k in ['_text', '_markup', '_active']: if entries.has_key(k): self.attr = entries.get(k) break self.__dict__.update(entries) self.props = {} self.static = {} for key,val in entries.iteritems(): if key.startswith('__'): self.static[key[2:]] = val elif key.startswith('_'): self.props[key[1:]] = val if not self.props and self.title: self.props['text'] = self.title # print self.props # print self.static # print #for k,v in self.props.items(): # if not isinstance(v, str): # self.static[k] = v # del self.props[k] #if not self.props: #if not self.attr and self.title: self.attr = self.title #if self.attr: self.props[self.prop] = self.attr #print self.props def __str__(self): return "" %\ (self.title, self.props, self.static) # Singleton pattern class Singleton(type): def __init__(cls, name, bases, dct): cls.__instance = None type.__init__(cls, name, bases, dct) # cls.lock = threading.Lock() def __call__(cls, *args, **kw): # cls.lock.acquire() if cls.__instance is None: cls.__instance = type.__call__(cls, *args,**kw) # cls.__instance.ref = 0 # cls.__instance.ref += 1 # cls.lock.release() return weakref.proxy(cls.__instance) # return cls.__instance def loaded(cls): return cls.__instance != None class Observable(object): "Multi-observable for multi topic observers" class NotSuchTopic(Exception): pass class NotSuchObserver(Exception): pass class TopicAlreadyExists(Exception): pass def __init__(self, topics=[]): self._observers = {None:[]} assert isinstance(topics, list) for i in topics: self.add_topic(i) def attach(self, ob, topic=None): try: self._observers[topic].append(ob) except KeyError: raise self.NotSuchTopic(topic) def detach(self, ob, topic=None): try: self._observers[topic].remove(ob) except KeyError: raise self.NotSuchTopic(topic) except ValueError: raise self.NotSuchObserver(ob) def notify(self, val=None, topic=None): # assert threading.currentThread() == main_thread, "Observable.notify() is forbidden from this thread" if topic not in self._observers.keys(): raise self.NotSuchTopic(topic) for ob in self._observers[topic]: gobject.idle_add(ob, val) def add_topic(self, name=None): if name in self._observers.keys(): raise self.TopicAlreadyExists(name) self._observers[name] = [] class SyncObservable(Observable): def notify(self, val=None, topic=None): if topic not in self._observers.keys(): raise self.NotSuchTopic(topic) for ob in self._observers[topic]: ob(val) # Access to glade objects transparently class GladeWrapper: def __init__(self, glade_file, root_widget=None): self.__glade = gtk.glade.XML(glade_file, root_widget) self.__glade.signal_autoconnect(self) def get_widget(self, name): return self.__glade.get_widget(name) def __getattr__(self, name): try: return self.__dict__[name] except KeyError, e: if name.startswith("wg_"): try: ret = self.__glade.get_widget(name[3:]) except AttributeError: raise AttributeError("You must call GladeWrapper.__init__() in your derived class") if ret: self.__dict__[name] = ret return ret raise AttributeError, "%s: %s" % (str(self.__class__), e) # FIXME: THIS IS ONLY A TEST class Reference: __ims = 0 @classmethod def ref(cls): cls.__ims += 1 @classmethod def unref(cls): cls.__ims -= 1 return cls.__ims == 0 def filename_cmp(item1, item2, attr): if item1.key == '..': return -2 if item2.key == '..': return 2 if not (hasattr(item1, "is_dir") ^ hasattr(item2, "is_dir")): return cmp(getattr(item1, attr), getattr(item2, attr)) if hasattr(item1, "is_dir"): return -2 return 2 def filename_order_cmp(holder, model, iter1, iter2, data): attr, rmodel = data col_id = rmodel.get_sort_column_id() if col_id[1] == gtk.SORT_ASCENDING: order = 1 elif col_id[1] == gtk.SORT_DESCENDING: order = -1 else: return 0 retval = filename_cmp(model[iter1][0], model[iter2][0], attr) if (retval * retval) == 4: retval *= order return retval def partial(func, *args, **keywords): "patial() implementation for python.2,4" def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc # Las claves se almacenan en una lista para tenerlas en el mismo # orden en que se insertaron class SortedDict(dict): def __init__(self, other={}): self.__keylist = [] self.update(other) def __getitem__(self, key): return dict.__getitem__(self, key) def __setitem__(self, key, value): dict.__setitem__(self, key, value) if key in self.__keylist: self.__keylist.remove(key) self.__keylist.append(key) def update(self, other): for k,v in other.items(): self[k] = v def keys(self): return self.__keylist def values(self): return [self[k] for k in self.__keylist] def items(self): return [(k, self[k]) for k in self.__keylist] def __iter__(self): return self.__keylist.__iter__() def clear(self): self.__keylist = [] dict.clear(self) # Cuando se referencia una clave que no existe, se crea como una lista # vacia class ListDict(SortedDict): def __getitem__(self, key): if not key in dict.keys(self): self[key] = [] return SortedDict.__getitem__(self, key) def excepthook_logger(type, value, tb): logging.critical('Unhandled Exception\n' + ''.join(traceback.format_exception(type, value, tb))) def format_ex(exc_info=[]): try: if exc_info: type, value, tb = exc_info else: type, value, tb = sys.exc_info() info = traceback.extract_tb(tb) filename, lineno, function, text = info[-1] # last line only return "'%s', line %d [%s]\n%s (in %s)" %\ (filename, lineno, type.__name__, str(value), function) finally: type = value = tb = None # clean up def format_tb(): retval = '' exc, val, tb = sys.exc_info() for frame in traceback.format_tb(tb): retval += frame return retval def print_tb(): exc, val, tb = sys.exc_info() for frame in traceback.format_tb(tb): for line in frame.split('\n'): if line: logging.debug('| ' + line) # debugging class This: def __init__(self): try: raise SystemError except SystemError: exc, val, tb = sys.exc_info() self.f_code = tb.tb_frame.f_back.f_code del exc, val, tb self.name = self.f_code.co_name # class to notify error on gui class Issue: # importance level of the issue DEBUG, INFO, WARNING, ERROR, CRITICAL = range(5) def __init__(self, title, details='', uri='', level=DEBUG, debug=''): self.title = title self.details = details self.uri = uri self.level = level self.debug = debug @classmethod def from_exc(cls, e, uri=''): details = e.args[0] if len(e.args) > 0 else None return Issue(e.__class__.__name__, details, uri=uri, level=Issue.ERROR, debug=format_tb()) @classmethod def from_message(cls, msg, details=None, uri=''): return Issue(msg, details, uri=uri, level=Issue.INFO) # FIXME: ¿Habría que hacer from_warning...? def cache(fname, build, adaptor=None, timeout=None, force=False): '''Gestiona una cache(diccionario) automáticamente build: la función que construye el diccionario adaptor: una función para convertir los datos leidos de la caché timeout: un tiempo (en segundos) a partir del cual se regenará la caché force: un booleano que indica si se debe regenerar sample: skin_map = cache('loquesea', build_skin_map, util.ListDict, __need_rebuild_skins()) ''' # Ejemplo: # skin_map = util.cache(SKIN_MAP, # build = build_skin_map, # adaptor = util.ListDict, # timeout = 100, # force = __need_rebuild_skins()) import stat, time reload = force if not force: if not os.path.exists(fname): reload = True elif timeout: if time.time() - os.stat(fname)[stat.ST_MTIME] > timeout: reload = True stored = shelve.open(fname, protocol=2, writeback=True) if not reload: #print stored.values()[0][0].name retval = dict(stored) if adaptor: retval = adaptor(retval) else: retval = build() #print retval.values()[0][0].modulo stored.clear() stored.update(retval) #print '::', stored.values()[0][0].modulo stored.close() return retval