"""transporter.py Transporter class for daemon"""


__author__ = "Wim Leers (work@wimleers.com)"
__version__ = "$Rev$"
__date__ = "$Date$"
__license__ = "GPL"


from django.core.files.storage import Storage
from django.core.files import File


# Define exceptions.
class TransporterError(Exception): pass
class InvalidSettingError(TransporterError): pass
class MissingSettingError(TransporterError): pass
class InvalidCallbackError(TransporterError): pass
class ConnectionError(TransporterError): pass
class InvalidActionError(TransporterError): pass


import threading
import Queue
import time
import os.path
from sets import Set, ImmutableSet


class Transporter(threading.Thread):
    """threaded abstraction around a Django Storage subclass"""


    ACTIONS = {
        "ADD_MODIFY" : 0x00000001,
        "DELETE"     : 0x00000002,
    }


    def __init__(self, settings, callback):
        if not callable(callback):
            raise InvalidCallbackError

        self.settings = settings
        self.storage = False
        self.ready = False
        self.lock = threading.Lock()
        self.queue = Queue.Queue()
        self.callback = callback
        self.die = False
        threading.Thread.__init__(self)


    def run(self):
        while not self.die:
            # Sleep a little bit if there's no work.
            if self.queue.qsize() == 0:
                self.ready = True
                time.sleep(0.5)
            else:
                self.ready = False

                self.lock.acquire()
                (src, dst, action) = self.queue.get_nowait()
                self.lock.release()

                try:
                    # Sync the file: either add/modify it, or delete it.
                    if action == Transporter.ADD_MODIFY:
                        # Sync the file.
                        f = File(open(src, "rb"))
                        if self.storage.exists(dst):
                            self.storage.delete(dst)
                        self.storage.save(dst, f)
                        f.close()
                        # Calculate the URL.
                        url = self.storage.url(dst)
                        url = self.alter_url(url)
                    else:
                        if self.storage.exists(dst):
                            self.storage.delete(dst)
                        url = None

                    # Call the callback function.
                    self.callback(src, dst, url, action)

                except Exception, e:
                    raise ConnectionError(e)


    def alter_url(self, url):
        """allow some classes to alter the generated URL"""
        return url


    def stop(self):
        self.lock.acquire()
        self.die = True
        self.lock.release()


    def validate_settings(self, valid_settings, required_settings, settings):
        if len(settings.difference(valid_settings)):
            raise InvalidSettingError

        if len(required_settings.difference(settings)):
            raise MissingSettingError


    def sync_file(self, src, dst=None, action=None):
        # Set the default value here because Python won't allow it sooner.
        if dst is None:
            dst = src
        if action is None:
            action = Transporter.ADD_MODIFY
        elif action not in Transporter.ACTIONS.values():
            raise InvalidActionError

        # If dst is relative to the root, strip the leading slash.
        if dst.startswith("/"):
            dst = dst[1:]

        self.lock.acquire()
        self.queue.put((src, dst, action))
        self.lock.release()


    def is_ready(self):
        return self.ready


# Make EVENTS' members directly accessible through the class dictionary.
for name, mask in Transporter.ACTIONS.iteritems():
    setattr(Transporter, name, mask)

@@ -1546,16 +1546,26 @@
 , action
+, callback
 ) = self
@@ -1584,16 +1584,16 @@
 owait()%0A
-
         
@@ -2417,17 +2417,328 @@
 unction.
-%0A
+ Use the callback function%0A                    # defined for this Transporter (self.callback), unless%0A                    # an alternative one was defined for this file (callback).%0A                    if not callback is None:%0A                        callback(src, dst, url, action)%0A                    else:%0A    
         
@@ -3400,16 +3400,31 @@
 ion=None
+, callback=None
 ):%0A     
@@ -3851,32 +3851,32 @@
 .lock.acquire()%0A
-
         self.que
@@ -3899,16 +3899,26 @@
 , action
+, callback
 ))%0A     

Allow an alternative callback for each synced file in the Transporter class.