Initial N800 backup support



Hi again,

Here is my initial patch to add N800 backup support to Conduit. It's
quite "hacky" and it isn't finished yet, but the sync N800 -> PC folder
works. 

The folder created in the PC has an strange name (self.folderGroupName +
"file:") and I don't know how to fix this. Besides, the PC folder ->
N800 sync doesn't work. 

Any help on the folder name fixing would be appreciated :-)

-- 
Jaime Frutos Morales
Computer Science Engineer
Blog: http://textoplano.livejournal.com
Index: conduit/dataproviders/N800Module.py
===================================================================
--- conduit/dataproviders/N800Module.py	(revisi�0)
+++ conduit/dataproviders/N800Module.py	(revisi�0)
@@ -0,0 +1,426 @@
+"""
+Provides a number of dataproviders which are associated with
+a N800 device.
+
+Copyright: Jaime Frutos Morales , 2007
+License: GPLv2
+"""
+import os
+import os.path
+import gtk
+import gobject
+import gnomevfs
+import traceback
+import threading
+import time
+from gettext import gettext as _
+
+import conduit
+from conduit import log,logd,logw
+import conduit.DataProvider as DataProvider
+import conduit.Module as Module
+from conduit.datatypes import DataType
+from conduit.datatypes import File
+import conduit.datatypes.Text as Text
+import conduit.Exceptions as Exceptions
+import conduit.Utils as Utils
+import conduit.Settings as Settings
+import conduit.DB as DB
+
+MODULES = {
+    "N800Factory" : { "type": "dataprovider-factory" },
+}
+
+
+class N800Factory(Module.DataProviderFactory):
+    def __init__(self, **kwargs):
+        Module.DataProviderFactory.__init__(self, **kwargs)
+
+        if kwargs.has_key("hal"):
+            self.hal = kwargs["hal"]
+            self.hal.connect("n800-added", self._n800_added)
+            self.hal.connect("n800-removed", self._n800_removed)
+
+        self.n800s = {}
+
+    def probe(self):
+        """ Probe for N800 devices that are already attached """
+        for device_type, udi, mount, name in self.hal.get_all_n800s():
+            self._n800_added(None, udi, mount, name)
+
+    def _n800_added(self, hal, udi, mount, name):
+        """ New N800 has been discovered """
+        cat = DataProvider.DataProviderCategory(
+                    "Nokia N800",
+                    "n800",
+                    mount)
+
+        keys = []
+        for klass in [N800BackupTwoWay]:
+            key = self.emit_added(
+                           klass,            # Dataprovider class
+                           (mount,udi,),     # Init args
+                           cat)              # Category..
+            keys.append(key)
+
+        self.n800s[udi] = keys
+
+    def _n800_removed(self, hal, udi, mount, name):
+        for key in self.n800s[udi]:
+            self.emit_removed(key)
+
+        del self.n800s[udi]
+
+TYPE_FILE = 0
+TYPE_FOLDER = 1
+TYPE_EMPTY_FOLDER = 2
+TYPE_SINGLE_FILE = 3
+
+#Indexes of data in the list store
+URI_IDX = 0                     #URI of the file/folder
+TYPE_IDX = 1                    #TYPE_FILE/FOLDER/etc
+CONTAINS_NUM_ITEMS_IDX = 2      #(folder only) How many items in the folder
+SCAN_COMPLETE_IDX = 3           #(folder only) HAs the folder been recursively scanned
+GROUP_NAME_IDX = 4              #(folder only) The visible identifier for the folder
+CONTAINS_ITEMS_IDX = 5          #(folder only) All the items contained within the folder
+
+CONFIG_FILE_NAME = ".conduit.conf"
+
+def _save_config_file_for_dir(uri, groupName):
+    tempFile = Utils.new_tempfile(groupName)
+    tempFile.force_new_filename(CONFIG_FILE_NAME)
+    tempFile.transfer(uri, True)
+
+def _get_config_file_for_dir(uri):
+    config = os.path.join(uri,CONFIG_FILE_NAME)
+    return gnomevfs.read_entire_file(config)
+
+class _FolderScanner(threading.Thread, gobject.GObject):
+    __gsignals__ =  { 
+                    "scan-progress": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+                        gobject.TYPE_INT]),
+                    "scan-completed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
+                    }
+
+    def __init__(self, baseURI, includeHidden):
+        threading.Thread.__init__(self)
+        gobject.GObject.__init__(self)
+        self.baseURI = baseURI
+        self.includeHidden = includeHidden
+        self.dirs = [baseURI]
+        self.cancelled = False
+        self.URIs = []
+        self.setName("FolderScanner Thread: %s" % baseURI)
+
+    def run(self):
+        delta = 0
+        
+        startTime = time.time()
+        t = 1
+        last_estimated = estimated = 0 
+        while len(self.dirs)>0:
+            if self.cancelled:
+                return
+            dir = self.dirs.pop(0)
+            try:hdir = gnomevfs.DirectoryHandle(gnomevfs.URI(dir))
+            except: 
+                logw("Folder %s Not found" % dir)
+                continue
+            try: fileinfo = hdir.next()
+            except StopIteration: continue;
+            while fileinfo:
+                if fileinfo.name in [".","..",CONFIG_FILE_NAME]: 
+                        pass
+                else:
+                    if fileinfo.type == gnomevfs.FILE_TYPE_DIRECTORY:
+                        #Include hidden directories
+                        if fileinfo.name[0] != "." or self.includeHidden:
+                            self.dirs.append(dir+"/"+fileinfo.name)
+                            t += 1
+                    elif fileinfo.type == gnomevfs.FILE_TYPE_REGULAR:
+                        try:
+                            uri = gnomevfs.make_uri_canonical(dir+"/"+fileinfo.name)
+                            #Include hidden files
+                            if fileinfo.name[0] != "." or self.includeHidden:
+                                self.URIs.append(uri)
+                        except UnicodeDecodeError:
+                            raise "UnicodeDecodeError",uri
+                    else:
+                        logd("Unsupported file type: %s (%s)" % (fileinfo.name, fileinfo.type))
+                try: fileinfo = hdir.next()
+                except StopIteration: break;
+            #Calculate the estimated complete percentags
+            estimated = 1.0-float(len(self.dirs))/float(t)
+            estimated *= 100
+            #Enly emit progress signals every 10% (+/- 1%) change to save CPU
+            if delta+10 - estimated <= 1:
+                logd("Folder scan %s%% complete" % estimated)
+                self.emit("scan-progress", len(self.URIs))
+                delta += 10
+            last_estimated = estimated
+
+        i = 0
+        total = len(self.URIs)
+        endTime = time.time()
+        logd("%s files loaded in %s seconds" % (total, (endTime - startTime)))
+        self.emit("scan-completed")
+
+    def cancel(self):
+        self.cancelled = True
+
+    def get_uris(self):
+        return self.URIs
+
+
+class _ScannerThreadManager:
+    MAX_CONCURRENT_SCAN_THREADS = 2
+    def __init__(self):
+        self.scanThreads = {}
+        self.pendingScanThreadsURIs = []
+
+    def make_thread(self, folderURI, includeHidden, progressCb, completedCb, rowref):
+        running = len(self.scanThreads) - len(self.pendingScanThreadsURIs)
+
+        if folderURI not in self.scanThreads:
+            thread = _FolderScanner(folderURI, includeHidden)
+            thread.connect("scan-progress",progressCb, rowref)
+            thread.connect("scan-completed",completedCb, rowref)
+            thread.connect("scan-completed", self._register_thread_completed, folderURI)
+            self.scanThreads[folderURI] = thread
+            if running < _ScannerThreadManager.MAX_CONCURRENT_SCAN_THREADS:
+                logd("Starting thread %s" % folderURI)
+                self.scanThreads[folderURI].start()
+            else:
+                self.pendingScanThreadsURIs.append(folderURI)
+
+    def _register_thread_completed(self, sender, folderURI):
+        #delete the old thread
+        del(self.scanThreads[folderURI])
+        running = len(self.scanThreads) - len(self.pendingScanThreadsURIs)
+
+        logd("Thread %s completed. %s running, %s pending" % (folderURI, running, len(self.pendingScanThreadsURIs)))
+
+        if running < _ScannerThreadManager.MAX_CONCURRENT_SCAN_THREADS:
+            try:
+                uri = self.pendingScanThreadsURIs.pop()
+                logd("Starting pending thread %s" % uri)
+                self.scanThreads[uri].start()
+            except IndexError: pass
+
+    def join_all_threads(self):
+        joinedThreads = 0
+        while(joinedThreads < len(self.scanThreads)):
+            for thread in self.scanThreads.values():
+                try:
+                    thread.join()
+                    joinedThreads += 1
+                except AssertionError: 
+                    #deal with not started threads
+                    time.sleep(1)
+
+    def cancel_all_threads(self):
+        for thread in self.scanThreads.values():
+            if thread.isAlive():
+                logd("Cancelling thread %s" % thread)
+                thread.cancel()
+            thread.join() #May block
+
+
+class FolderTwoWay(DataProvider.TwoWay):
+    """
+    TwoWay dataprovider for synchronizing a folder
+    """
+
+    _name_ = _("Folder")
+    _description_ = _("Synchronize folders")
+    _category_ = DataProvider.CATEGORY_FILES
+    _module_type_ = "twoway"
+    _in_type_ = "file"
+    _out_type_ = "file"
+    _icon_ = "folder"
+
+    DEFAULT_FOLDER = os.path.expanduser("~")
+    DEFAULT_GROUP = "Home"
+    DEFAULT_HIDDEN = False
+
+    def __init__(self, *args):
+        DataProvider.TwoWay.__init__(self)
+        self.need_configuration(True)
+
+        self.folder = FolderTwoWay.DEFAULT_FOLDER
+        self.folderGroupName = FolderTwoWay.DEFAULT_GROUP
+        self.includeHidden = FolderTwoWay.DEFAULT_HIDDEN
+        self.files = []
+
+        self._monitor_folder_id = None
+
+    def __del__(self):
+        if self._monitor_folder_id != None:
+            gnomevfs.monitor_cancel(self._monitor_folder_id)
+            self._monitor_folder_id = None
+
+    def initialize(self):
+        return True
+
+    def configure(self, window):
+        pass
+    def refresh(self):
+        DataProvider.TwoWay.refresh(self)
+        #scan the folder 
+        scanThread = _FolderScanner(self.folder, self.includeHidden)
+        scanThread.start()
+        scanThread.join()
+
+        self.files = scanThread.get_uris()
+        
+
+    def put(self, vfsFile, overwrite, LUID=None):
+        """
+        Puts vfsFile at the correct location. There are two scenarios
+        1) File came from a foreign DP like tomboy
+        2) File came from another file dp
+
+        Behaviour:
+        1) The foreign DP should have encoded enough information (such as
+        the filename) so that we can go ahead and put the file in the dir
+        2) First we see if the file has a group attribute. If so, and the
+        group matches the groupName here then we put the files into the 
+        directory. If not we put the file in the orphan dir. We try and 
+        retain the relative path for the files in the specifed group 
+        and recreate that in the group dir
+        """
+        DataProvider.TwoWay.put(self, vfsFile, overwrite, LUID)
+        newURI = ""
+        if LUID != None:
+            newURI = LUID
+        elif vfsFile.basePath == "":
+            #came from another type of dataprovider such as tomboy
+            #where relative path makes no sense. Could also come from
+            #the FileSource dp when the user has selected a single file
+            logd("FolderTwoWay: No basepath. Going to empty dir")
+            newURI = self.folder+"/"+vfsFile.get_filename()
+        else:
+            pathFromBase = vfsFile._get_text_uri().replace(vfsFile.basePath,"")
+            #Look for corresponding groups
+            if self.folderGroupName == vfsFile.group:
+                logd("FolderTwoWay: Found corresponding group")
+                #put in the folder
+                newURI = self.folder+pathFromBase
+            else:
+                logd("FolderTwoWay: Recreating group %s --- %s --- %s" % (vfsFile._get_text_uri(),vfsFile.basePath,vfsFile.group))
+                #unknown. Store in the dir but recreate the group
+                newURI = self.folder+"/"+vfsFile.group+pathFromBase
+
+        destFile = File.File(URI=newURI)
+        comp = vfsFile.compare(destFile)
+        if overwrite or comp == DataType.COMPARISON_NEWER:
+            vfsFile.transfer(newURI, True)
+
+        return gnomevfs.make_uri_canonical(newURI)
+
+    def delete(self, LUID):
+        f = File.File(URI=LUID)
+        if f.exists():
+            f.delete()
+                
+    def get(self, uid):
+        DataProvider.TwoWay.get(self, uid)
+        f = File.File(
+                    URI=uid,
+                    basepath=self.folder,
+                    group=self.folderGroupName
+                    )
+        f.set_open_URI(uid)
+        f.set_UID(uid)
+        return f
+
+    def get_all(self):
+        DataProvider.TwoWay.get_all(self)
+        return self.files
+
+    def finish(self):
+        DataProvider.TwoWay.finish(self)
+        self.files = []
+
+    def set_configuration(self, config):
+        self.folder = config.get("folder", FolderTwoWay.DEFAULT_FOLDER)
+        self.folderGroupName = config.get("folderGroupName", FolderTwoWay.DEFAULT_GROUP)
+        self.includeHidden = config.get("includeHidden", FolderTwoWay.DEFAULT_HIDDEN)
+
+        self.set_configured(True)
+        self._monitor_folder()
+
+    def get_configuration(self):
+        _save_config_file_for_dir(self.folder, self.folderGroupName)
+        return {
+            "folder" : self.folder,
+            "folderGroupName" : self.folderGroupName,
+            "includeHidden" : self.includeHidden
+            }
+
+    def get_UID(self):
+        return "%s:%s" % (self.folder, self.folderGroupName)
+
+    def _on_scan_folder_progress(self, folderScanner, numItems, rowref):
+        path = self.items.get_path(rowref)
+        self.items[path][CONTAINS_NUM_ITEMS_IDX] = numItems
+
+    def _on_scan_folder_completed(self, folderScanner, rowref):
+        logd("Folder scan complete %s" % folderScanner)
+        path = self.items.get_path(rowref)
+        self.items[path][SCAN_COMPLETE_IDX] = True
+        self.items[path][CONTAINS_ITEMS_IDX] = folderScanner.get_uris()
+        #If the user has not yet given the folder a descriptive name then
+        #check of the folder contains a .conduit file in which that name is 
+        #stored (i.e. the case when the user starts the sync from a
+        #saved configuration)
+        if self.items[path][GROUP_NAME_IDX] == "":
+            try:
+                configString = _get_config_file_for_dir(folderScanner.baseURI)
+                self.items[path][GROUP_NAME_IDX] = configString
+            except gnomevfs.NotFoundError: pass
+
+    def _monitor_folder(self):
+        if self._monitor_folder_id != None:
+            gnomevfs.monitor_cancel(self._monitor_folder_id)
+            self._monitor_folder_id = None
+
+        try:
+            self._monitor_folder_id = gnomevfs.monitor_add(self.folder, gnomevfs.MONITOR_DIRECTORY, self._monitor_folder_cb)
+        except gnomevfs.NotSupportedError:
+            # silently fail if we are looking at a folder that doesn't support directory monitoring
+            pass
+
+    def _monitor_folder_cb(self, monitor_uri, event_uri, event, data=None):
+        if event in (gnomevfs.MONITOR_EVENT_CREATED, gnomevfs.MONITOR_EVENT_CHANGED, gnomevfs.MONITOR_EVENT_DELETED):
+            self.emit_change_detected()
+
+
+class N800BackupTwoWay(FolderTwoWay):
+
+    _name_ = _("Backup files")
+    _description_ = _("Synchronize your N800 Backup files")
+
+    def __init__(self, *args):
+        FolderTwoWay.__init__(self, *args)
+
+        self.mountPoint = args[0]
+        self.folder = os.path.join(self.mountPoint, 'backups')
+        self.folderGroupName = "N800-b"
+        self.includeHidden = True
+        # Check whether the folder exists and create it
+        if not os.path.exists(self.folder):
+            os.mkdir(self.folder)
+        self.need_configuration(False)
+        self.set_configured(True)
+
+    def set_configuration(self, config):
+        pass
+
+    def get_configuration(self):
+        pass
+
+    def configure(self, window):
+        pass
+
+
Index: conduit/dataproviders/Makefile.am
===================================================================
--- conduit/dataproviders/Makefile.am	(revisi�726)
+++ conduit/dataproviders/Makefile.am	(copia de trabajo)
@@ -16,7 +16,8 @@
 	__init__.py \
 	iPodModule.py \
 	TomboyModule.py \
-	ConverterModule.py
+	ConverterModule.py \
+    N800Module.py
 
 clean-local:
 	rm -rf *.pyc *.pyo


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]