home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / serpentine / operations.py < prev    next >
Encoding:
Python Source  |  2006-08-23  |  13.1 KB  |  431 lines

  1. # Copyright (C) 2004 Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  2. #
  3. # This library is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU Library General Public
  5. # License as published by the Free Software Foundation; either
  6. # version 2 of the License, or (at your option) any later version.
  7. #
  8. # This library is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11. # Library General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Library General Public
  14. # License along with this library; if not, write to the
  15. # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  16. # Boston, MA 02111-1307, USA.
  17. #
  18. # Authors: Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  19.  
  20. """
  21. Contains three groups of classes. Events, listeners and operations.
  22.  
  23. The Operation is used in the OperationsQueue, which is an Operation itself, to
  24. process other operations in a sequenced way. They use gobject's main loop.
  25. """
  26.  
  27. import gobject
  28. #TODO: listeners must be grouped by class
  29. #
  30.  
  31. class OperationListener (object):
  32.     def on_finished (self, event):
  33.         pass
  34.  
  35. SUCCESSFUL = 0
  36. ABORTED = 1
  37. ERROR = 2
  38.  
  39. class Event (object):
  40.     def __init__ (self, source):
  41.         self.source = source
  42.     
  43. class FinishedEvent (Event):
  44.     def __init__ (self, source, id, error = None):
  45.         Event.__init__ (self, source)
  46.         self.id = id
  47.         self.error = error
  48.  
  49. class Listenable (object):
  50.     def __init__ (self):
  51.         self.__listeners = []
  52.         
  53.     listeners = property (\
  54.             fget = lambda self: self.__listeners,
  55.             doc  = "The list of available listeners.")
  56.  
  57. class Operation (Listenable):
  58.     """
  59.     An operation is run assynchrounously. It is a Listenable and as such
  60.     classes which extend this one should call the event on_finished of it's
  61.     listeners when this operation is finished.
  62.     """
  63.     
  64.     title = None
  65.     description = None
  66.     
  67.     can_start = property (lambda self: not self.running, doc = "Checks if the operation can start. By default you can start an operation when it's not running.")
  68.     can_stop = property (lambda self: self.running, doc = "Checks if this operation can stop. By default you can stop operations that are running.")
  69.     running = property (doc = "Tests if the operation is running.")
  70.  
  71.     def start (self):
  72.         pass
  73.     
  74.     def stop (self):
  75.         pass
  76.  
  77.     def _notify (self, method_name, *args, **kw):
  78.         for l in self.listeners:
  79.             meth = getattr (l, method_name, None)
  80.             if meth:
  81.                 meth (*args, **kw)
  82.  
  83.     def _send_finished_event (self, status, error=None, source=None):
  84.         """
  85.         Broadcasts to all listeners the finished event. Simplifies the 
  86.         task of creating the event and iterating over listeners.
  87.         """
  88.         
  89.         if source is None:
  90.             source = self
  91.             
  92.         e = FinishedEvent (source, status, error)
  93.  
  94.         for l in self.listeners:
  95.             if hasattr (l, "on_finished"):
  96.                 l.on_finished (e)
  97.                 
  98.     def _propagate (self, evt, source = None):
  99.         self._send_finished_event (evt.id, evt.error, source)
  100.  
  101.  
  102.  
  103. class FailledOperation(Operation):
  104.     def __init__(self, error=None, source=None):
  105.         if source is None:
  106.             source = self
  107.             
  108.         self.error = error
  109.         self.source = source
  110.         
  111.         super(FailledOperation, self).__init__()
  112.     
  113.     def start(self):
  114.         self._send_finished_event(ERROR, self.error, self.source)
  115.         
  116.     
  117.     can_start = property(lambda: True)
  118.     can_stop = property (lambda: False)
  119.     running = property (lambda: False)
  120.  
  121.  
  122.  
  123. def operation_factory(func):
  124.     """
  125.     This decorator protects operation factories (functions wich return
  126.     operators by wrapping a try catch, if the function, by any chance, raises
  127.     an exception a FailledOperation is returned instead.
  128.     """
  129.     
  130.     def wrapper(*args, **kwargs):
  131.         try:
  132.             return func(*args, **kwargs)
  133.         except Exception, err:
  134.             return FailledOperation(error=err)
  135.  
  136.     wrapper.func_name = func.func_name
  137.     return wrapper
  138.     
  139.     
  140. class CallableOperation (Operation):
  141.     """Simple operations that takes a callable object (ie: function) and creates
  142.     an non-measurable Operation."""
  143.     
  144.     can_start = property (lambda: True)
  145.     can_stop = property (lambda: False)
  146.     running = property (lambda: False)
  147.     
  148.     def __init__ (self, callable):
  149.         self.callable = callable
  150.         Operation.__init__ (self)
  151.     
  152.     def start (self):
  153.         self.callable ()
  154.         self._send_finished_event (SUCCESSFUL)
  155.  
  156. try:
  157.     import subprocess
  158.     import os
  159.     
  160.     class SubprocessOperation (Operation):    
  161.         def __init__ (self, *args, **kwargs):
  162.             super (SubprocessOperation, self).__init__ ()
  163.             self.args = args
  164.             self.kwargs = kwargs
  165.         
  166.         pid = property (lambda self: self.__pid)
  167.         
  168.         can_start = property (lambda self: self.pid is not None)
  169.     
  170.         can_stop = property (lambda self: self.pid is not None)
  171.     
  172.         running = property (lambda self: self.pid is not None)
  173.         
  174.         def start (self):
  175.             try:
  176.                 proc = subprocess.Popen (*self.args, **self.kwargs)
  177.                 self.__pid = proc.pid
  178.                 self.__id = gobject.child_watch_add (self.pid, self.__on_finish)
  179.             except Exception, e:
  180.                 print "Error:", e
  181.         
  182.         def stop (self):
  183.             try:
  184.                 os.kill (self.pid, 9)
  185.             except OSError:
  186.                 pass
  187.         
  188.         def __on_finish (self, pid, status):
  189.             if status == 0:
  190.                 status = SUCCESSFUL
  191.             else:
  192.                 status = ERROR
  193.                 
  194.             self._send_finished_event (status)
  195.             self.__pid = None
  196. except ImportError:
  197.     pass        
  198.     
  199. class MeasurableOperation (Operation):
  200.     progress = property (doc = "Returns the operation's progress.")
  201.  
  202. class OperationsQueueListener (OperationListener):
  203.     def before_operation_starts (self, event, operation):
  204.         pass
  205.     
  206. class OperationsQueue (MeasurableOperation, OperationListener):
  207.     """
  208.     Operation Queuees allow a user to enqueue a number of operations and run
  209.     them sequentially.
  210.     If one of the operations is aborted or has an error the whole queue is 
  211.     aborted or returns an error too. The error returned is the same returned
  212.     by the problematic operation. All the elements remaining on the queue are
  213.     removed.
  214.     """
  215.     
  216.     def __init__ (self, operations = None):
  217.         Operation.__init__ (self)
  218.         
  219.         if operations is None:
  220.             operations = []
  221.             
  222.         self.__operations = operations
  223.         self.__done = 0
  224.         self.__curr_oper = None
  225.         self.__progress = 0.0
  226.         self.__total = 0
  227.         self.__abort_on_failure = True
  228.     
  229.     def __is_running (self):
  230.         return self.__curr_oper != None
  231.         
  232.     running = property (__is_running)
  233.     
  234.     def __get_progress (self):
  235.         if not self.running:
  236.             # Return 1 if there are no operations pending
  237.             # and we are not running and there are operations done
  238.             # else return 0
  239.             if len(self.__operations) == 0 and self.__done:
  240.                 return 1.0
  241.             else:
  242.                 return 0.0
  243.         # All that were done, the ones remaining plus the current working operation
  244.         total = self.__total
  245.         partial = 0.0
  246.         if isinstance(self.__curr_oper, MeasurableOperation):
  247.             partial = self.__curr_oper.progress
  248.             assert partial is not None, (self.__curr_oper, self.__curr_oper.progress)
  249.         return (self.__done + partial) / total
  250.         
  251.     progress = property (__get_progress)
  252.     
  253.     def __set_abort_on_failure (self, val):
  254.         assert isinstance (val, bool)
  255.         self.__abort_on_failure = val
  256.     
  257.     abort_on_failure = property (lambda self: self.__abort_on_failure,
  258.                                  __set_abort_on_failure,
  259.                                  doc = "If one operation stops abort progress and propagate event.")
  260.         
  261.     def start (self):
  262.         """
  263.         Starts all the operations on queue, sequentially.
  264.         """
  265.         assert not self.running, self.__curr_oper
  266.         self.__done = 0
  267.         self.__total = len (self.__operations)
  268.         self.__progress = 0.0
  269.         self.__started = True
  270.         gobject.idle_add (self.__start_next)
  271.     
  272.     def append (self, oper):
  273.         self.__operations.append (oper)
  274.     
  275.     def insert (self, index, oper):
  276.         self.__operations.insert (index, oper)
  277.         
  278.     # Private methods:
  279.     def __start_next (self):
  280.         if len (self.__operations):
  281.             oper = self.__operations[0]
  282.             del self.__operations[0]
  283.             e = Event (self)
  284.             for l in self.listeners:
  285.                 if hasattr (l, "before_operation_starts"):
  286.                     l.before_operation_starts (e, oper)
  287.                 
  288.             oper.listeners.append (self)
  289.             self.__curr_oper = oper
  290.             oper.start()
  291.             
  292.         else:
  293.             self.__started = False
  294.             e = FinishedEvent (self, SUCCESSFUL)
  295.             for l in self.listeners:
  296.                 if hasattr (l, "on_finished"):
  297.                     l.on_finished (e)
  298.     
  299.     
  300.     
  301.     def on_finished (self, evt):
  302.         assert isinstance (evt, FinishedEvent), evt
  303.         # Remove the listener connection
  304.         evt.source.listeners.remove (self)
  305.         # One more done
  306.         self.__done += 1
  307.         self.__curr_oper = None
  308.         
  309.         # Abort on not success
  310.         if self.abort_on_failure and evt.id != SUCCESSFUL:
  311.             # Clear remaining operations
  312.             self.__operations = []
  313.             evt.source = self
  314.             for l in self.listeners:
  315.                 l.on_finished (evt)
  316.         else:
  317.             # Start next operation
  318.             self.__start_next()
  319.     
  320.     can_stop = property (lambda self: self.running and self.__curr_oper.can_stop)
  321.     
  322.     def stop (self):
  323.         assert self.can_stop, "Check if the operation can be stopped first."
  324.         self.__curr_oper.stop ()
  325.     
  326.     __len__ = lambda self: len (self.__operations)
  327.     
  328.     def __repr__ (self):
  329.         return "{%s: %s}" % (
  330.             super(OperationsQueue, self).__repr__(),
  331.             self.__operations.__repr__()
  332.         )
  333.  
  334. class SyncListener (OperationListener):
  335.     def __init__ (self, mainloop):
  336.         self.mainloop = mainloop
  337.         
  338.     def on_finished (self, event):
  339.         self.result = event
  340.         self.mainloop.quit ()
  341.         
  342. def syncOperation (oper):
  343.     """
  344.     This function can run an operation synchronously and returns the event
  345.     object. This only affects GObject related operations.
  346.     """
  347.     
  348.     mainloop = gobject.MainLoop ()
  349.     
  350.     listener = SyncListener (mainloop)
  351.     oper.listeners.append (listener)
  352.     oper.start ()
  353.     mainloop.run ()
  354.     return listener.result
  355.  
  356. def syncableMethod(kwarg="sync", default_value=False):
  357.     """
  358.     This is a decorator that accepts a keyword argument (defaults to 'sync')
  359.     with a kwarg default value (defaults to `False`).
  360.     When you call the method you can use the extra keyword argument to
  361.     specify if the method call is going to be sync or async.
  362.     
  363.     The decorated method should be one that returns an operation.
  364.     """
  365.     def decorator(func):
  366.         def wrapper(*args, **kwargs):
  367.             is_sync = kwargs.get(kwarg, default_value)
  368.             if is_sync:
  369.                 del kwargs[kwarg]
  370.                 return syncOperation(func(*args, **kwargs))
  371.             else:
  372.                 return func(*args, **kwargs)
  373.         return wrapper
  374.         
  375.     return decorator
  376.  
  377. sync = syncableMethod(default_value=True)
  378. async = syncableMethod()
  379.  
  380. class MapFunctor (object):
  381.     def __init__ (self, funcs):
  382.         self.__funcs = funcs
  383.         
  384.     def __call__ (self, *args, **keyws):
  385.         r = []
  386.         for f in self.__funcs:
  387.             r.append (f(*args, **keyws))
  388.         return tuple (r)
  389.  
  390. class MapProxy (object):
  391.     """
  392.     This class acts as a hub or a proxy for calling methods on multiple objects.
  393.     The method called from an instance of this class will be transparently
  394.     called in all elements contained in this instance. The added elements is of
  395.     a dictionary type and can be accessed by the __getitem__ and __setitem__ of
  396.     this instance.
  397.     """
  398.     def __init__ (self, elements):
  399.         self.__elements = elements
  400.     
  401.     def __getattr__ (self, attr):
  402.         funcs = []
  403.         for key in self.__elements:
  404.             funcs.append (getattr (self.__elements[key], attr))
  405.         
  406.         return MapFunctor (funcs)
  407.     
  408.     def __getitem__ (self, key):
  409.         return self.__elements[key]
  410.     
  411.     def __setitem__ (self, key, value):
  412.         self.__elements[key] = value
  413.     
  414.     def __delitem__ (self, key):
  415.         del self.__elements[key]
  416.     
  417.     def has_key (self, key):
  418.         return self.__elements.has_key (key)
  419.  
  420. if __name__ == '__main__':
  421.     import sys, gtk
  422.     class Listener:
  423.         def on_finished (self, evt):
  424.             gtk.main_quit ()
  425.             
  426.     oper = SubprocessOperation (sys.argv[1:])
  427.     oper.listeners.append (Listener())
  428.     oper.start ()
  429.     gtk.main ()
  430.     
  431.