"""
FtpCube
Copyright (C) Michael Gilfix
This file is part of FtpCube.
You should have received a file COPYING containing license terms
along with this program; if not, write to Michael Gilfix
(mgilfix@eecs.tufts.edu) for a copy.
This version of FtpCube is open source; you can redistribute it and/or
modify it under the terms listed in the file COPYING.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""
import logger
import threading
import time
import exceptions
import traceback
class DispatcherException(exceptions.Exception):
"""Base exception for the dispatching of command objects."""
def __init__(self, args=None):
self.args = args
class Dispatcher(threading.Thread):
"""Command dispatcher class.
This class dispatches control session commands in a control session thread.
Multiple commands may be queued up for dispatching within the thread. This
class is thread-safe for methods implemented in the base class. Classes
that extend this class must make sure to protect access to shared data
structures using the dispatch_lock mutex lock. It is recommended that command
processing occur within the dispatch thread as much as possible and
communication with the UI occur via the event system."""
def __init__(self, **kwargs):
"""Creates an initializes a new thread dispatcher."""
threading.Thread.__init__(self)
self.setDaemon(1)
# Try grabbing the logger argument
try:
self.logger = kwargs['logger']
except KeyError:
self.logger = None
if self.logger and not isinstance(self.logger, logger.Logger):
raise DispatcherException, _("Invalid logger: %(log)s") %{ 'log' : self.logger }
# Initialize instance variables
self.command_queue = [ ]
self.current_cmd = None
self.default_err_handler = None
# Synchronization
self.die = threading.Event()
self.outbound = threading.Event()
self.dispatch_lock = threading.Lock()
def setDefaultErrorHandler(self, handler):
"""Sets the default error handler for errors in command execution.
Error handlers set on specific command objects will override the
default error handler set by this method. This handler should be
of type ErrorHandler."""
self.dispatch_lock.acquire()
self.default_err_handler = handler
self.dispatch_lock.release()
def getDefaultErrorHandler(self):
"""Gets the default error handler."""
self.dispatch_lock.acquire()
handler = self.default_err_handler
self.dispatch_lock.release()
return handler
def resetQueue(self):
"""Resets the command queue.
This empties out (discards) the current command queue."""
self.dispatch_lock.acquire()
self.command_queue = [ ]
self.outbound.set()
self.dispatch_lock.release()
def destroy(self):
"""Destroys the dispatcher.
This halts the dispatcher thread on the next iteration of the
dispatcher loop."""
self.die.set()
self.resetQueue()
def run(self):
"""Main dispatcher loop."""
while not self.die.isSet():
cmd = None
self.dispatch_lock.acquire()
if self.command_queue:
cmd = self.command_queue.pop(0)
self.current_cmd = cmd
else:
self.outbound.clear()
self.dispatch_lock.release()
if cmd is None:
self.outbound.wait()
continue
if __debug__:
print "%s dispatching: %s" %(threading.currentThread(), cmd)
if not isinstance(cmd, DispatcherCommand):
self.log(logger.Logger.ERROR,
_("Warning: attempting to dispatch invalid command object: %(obj)s")
%{ 'obj' : cmd })
try:
# Dispatch the command object in our thread
cmd.execute(self)
except Exception, strerror:
if __debug__:
print "Dispatcher caught an error executing command: %s: %s" \
%(Exception, strerror)
if not self.die.isSet():
handler = cmd.getErrorHandler()
if isinstance(handler, ErrorHandler):
handler.handle(strerror, self)
else:
self.dispatch_lock.acquire()
default_handler = self.default_err_handler
self.dispatch_lock.release()
if isinstance(default_handler, ErrorHandler):
default_handler.handle(strerror, self)
else:
self.log(logger.Logger.ERROR, strerror)
if __debug__:
print "Terminating thread: %s" %str(self)
def dispatch(self, command):
"""Places a command into the dispatch queue for processing."""
self.dispatch_lock.acquire()
self.command_queue.append(command)
self.outbound.set()
self.dispatch_lock.release()
def getExecutingCommand(self):
"""Gets the current executing command.
This also corresponds to the last command executed if the dispatcher is not
currently busy."""
self.dispatch_lock.acquire()
cmd = self.current_cmd
self.dispatch_lock.release()
return cmd
def busy(self):
"""Indicates whether the dispatcher is currently processing a command."""
return self.outbound.isSet()
def log(self, kind, msg):
"""Logger for messages related to command execution."""
if self.logger:
self.logger.log(kind, msg)
class DispatcherCommand:
"""Base dispatcher command interface.
All dispatcher commands should implement this interface. Dispatcher commands
contain the logic that represents the command within the execute() method.
This logic executes within the thread of the dispatcher and must be aware of
dispatcher threading concerns.
Each dispatcher may have its own error handler object. The error handler
will be called by the dispatcher in the event of execution failure."""
def __init__(self):
"""Initializes a dispatcher with an empty error handler."""
self.error_handler = None
def setErrorHandler(self, handler):
"""Sets the error handler for this command object."""
self.error_handler = handler
def getErrorHandler(self):
"""Gets the error handler for this command object."""
return self.error_handler
def execute(self, dispatcher):
"""Executes the main logic of the dispatcher command."""
raise NotImplementedError, _("DispatcherCommand class must implement execute()")
class DispatchStatus:
"""Status object for obtaining status regarding command execution.
This interface provides a means for command objects to communicate with
the outside world. The use of status objects are part of the command
implementation. Long running command (like transfers) may make use of
this status interface to execute code that will update the UI accordingly
to reflect the latest command status."""
def __init__(self):
"""Creates a new dispatch status object with the optional data field
set to empty."""
self.opt_data = None
def setOptionalData(self, data):
"""Sets the optional data for this status object."""
self.opt_data = data
def getOptionalData(self):
"""Gets the optional data for this status object."""
return self.opt_data
def start(self):
"""Called to indicate the start of command execution."""
raise NotImplementedError, _("DispatchStatus class must implement start()")
def update(self, update):
"""Called to provide an update on command execution status.
The meaning of the update argument depends on implementation."""
raise NotImplementedError, _("DispatchStatus class must implement update()")
def finished(self):
"""Called to indicate that command execution has finished."""
raise NotImplementedError, _("DispatchStatus class must implement finished()")
class ErrorHandler:
"""Base error handler class."""
def __init__(self):
pass
def handle(self, error, dispatcher):
"""Main logic for handling dispatch errors.
The error argument contains a string indicating the error."""
# Do nothing by default
pass
class ErrorLogger(ErrorHandler):
"""Logging error handler class.
This class logs error strings to the supplied logger. This logger may result in
the message being logging to the command window console or to a file."""
def __init__(self, mylogger):
ErrorHandler.__init__(self)
if not isinstance(mylogger, logger.Logger):
raise TypeError, _("Unexpected logger type: %s") %repr(mylogger)
self.logger = mylogger
def handle(self, error, dispatcher):
"""Logs the error message to the initialized logger."""
self.logger.log(logger.Logger.ERROR, error)
|