Index: test/python-tests/common.py
===================================================================
--- test/python-tests/common.py	(revision 1364)
+++ test/python-tests/common.py	(working copy)
@@ -1,3 +1,4 @@
+#coding: utf-8
 import sys
 import cgitb
 import os
@@ -6,6 +7,7 @@
 import datetime
 import traceback
 import ConfigParser
+import random
 
 # make sure we have conduit folder in path!
 my_path = os.path.dirname(__file__)
@@ -136,7 +138,12 @@
                 data[k] = v
                 
     return data
-    
+
+def get_unicode_character():
+    #Convenience function to get a unicode character.
+    UNICODE_CHARS = ('Ä','á','æ','é','Ë','Ü')
+    return UNICODE_CHARS[random.randint(0,len(UNICODE_CHARS)-1)]
+
 #Functions to construct new types
 def new_file(filename):
     if filename == None:
Index: test/python-tests/TestSyncFolderFolder.py
===================================================================
--- test/python-tests/TestSyncFolderFolder.py	(revision 1364)
+++ test/python-tests/TestSyncFolderFolder.py	(working copy)
@@ -7,7 +7,6 @@
 
 import os.path
 import traceback
-import random
 import time
 
 #Repeat syncs a few times to check for duplicate mapping bugs, etc
@@ -36,7 +35,8 @@
 
 FILES = []
 for i in range(0, NUM_FILES):
-    name = Utils.random_string()
+    #test unicode
+    name = "%s UNICODE %s" % (Utils.random_string(), get_unicode_character())
     contents = Utils.random_string()
     f = File.TempFile(contents)
     f.force_new_filename(name)
Index: conduit/Vfs.py
===================================================================
--- conduit/Vfs.py	(revision 1364)
+++ conduit/Vfs.py	(working copy)
@@ -121,7 +121,7 @@
     try:
         return gnomevfs.exists(gnomevfs.URI(uri)) == 1
     except Exception, err:
-        print err
+        log.warn("Error checking if location exists")
         return False
         
 def uri_make_directory_and_parents(uri):
Index: conduit/datatypes/File.py
===================================================================
--- conduit/datatypes/File.py	(revision 1364)
+++ conduit/datatypes/File.py	(working copy)
@@ -38,9 +38,6 @@
         #optional args
         self.basePath = kwargs.get("basepath","")
         self.group = kwargs.get("group","")
-        
-        #calculate the relative path
-        self.relpath = self._get_text_uri().replace(self.basePath,"")
 
         #instance
         self.fileInfo = None
@@ -170,7 +167,6 @@
         self.URI = f.URI
         self.basePath = f.basePath
         self.group = f.group
-        self.relpath = f.relpath
         self.fileInfo = f.fileInfo
         self.fileExists = f.fileExists
         self.triedOpen = f.triedOpen
@@ -422,6 +418,12 @@
             return u
         else:
             return self.to_tempfile()
+            
+    def get_relative_uri(self):
+        """
+        @returns: The files URI relative to its basepath
+        """
+        return self._get_text_uri().replace(self.basePath,"")
 
     def compare(self, B, sizeOnly=False, existOnly=False):
         """
@@ -486,7 +488,6 @@
         data = DataType.DataType.__getstate__(self)
         data['basePath'] = self.basePath
         data['group'] = self.group
-        data['relpath'] = self.relpath
         data['filename'] = self.get_filename()
         data['filemtime'] = self.get_mtime()
 
@@ -503,7 +504,6 @@
         self.URI = gnomevfs.URI(name)
         self.basePath = data['basePath']
         self.group = data['group']
-        self.relpath = data['relpath']
         self._defer_rename(data['filename'])
         self._defer_new_mtime(data['filemtime'])
 
Index: conduit/dataproviders/File.py
===================================================================
--- conduit/dataproviders/File.py	(revision 1364)
+++ conduit/dataproviders/File.py	(working copy)
@@ -215,14 +215,15 @@
             newURI = self.folder+os.sep+vfsFile.get_filename()
         else:
             #Look for corresponding groups
+            relpath = vfsFile.get_relative_uri()
             if self.folderGroupName == vfsFile.group:
                 log.debug("FolderTwoWay: Found corresponding group")
                 #put in the folder
-                newURI = self.folder+vfsFile.relpath
+                newURI = self.folder+relpath
             else:
                 log.debug("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+os.sep+os.path.join(vfsFile.group+vfsFile.relpath)
+                newURI = self.folder+os.sep+os.path.join(vfsFile.group+relpath)
 
         destFile = File.File(URI=newURI)
         comp = vfsFile.compare(
Index: conduit/dataproviders/VolumeFactory.py
===================================================================
--- conduit/dataproviders/VolumeFactory.py	(revision 1364)
+++ conduit/dataproviders/VolumeFactory.py	(working copy)
@@ -47,20 +47,6 @@
                 self.item_removed(device_udi)
         return False
 
-    def probe(self):
-        """
-        Called after VolumeFactory is initialised to detect already connected volumes
-        """
-        for volume in self.vol_monitor.get_mounted_volumes():
-            device_udi = volume.get_hal_udi()
-            if device_udi != None:
-                props = self._get_properties(device_udi)
-                if self.is_interesting(device_udi, props):
-                    mount, label = self._get_device_info(props)
-                    kwargs = { "mount": mount, "label": label }
-                    print device_udi, kwargs
-                    self.item_added(device_udi, **kwargs)
-
     def _get_properties(self, device_udi):
         try:
             device_dbus_obj = self.bus.get_object("org.freedesktop.Hal" ,device_udi)
@@ -82,6 +68,19 @@
             label = ""
 
         return (mount, label)
+        
+    def probe(self):
+        """
+        Called after VolumeFactory is initialised to detect already connected volumes
+        """
+        for volume in self.vol_monitor.get_mounted_volumes():
+            device_udi = volume.get_hal_udi()
+            if device_udi != None:
+                props = self._get_properties(device_udi)
+                if self.is_interesting(device_udi, props):
+                    mount, label = self._get_device_info(props)
+                    kwargs = { "mount": mount, "label": label }
+                    self.item_added(device_udi, **kwargs)
 
     def get_args(self, udi, **kwargs):
         """ VolumeFactory passes mount point and udi to dataproviders """
Index: conduit/dataproviders/DataProvider.py
===================================================================
--- conduit/dataproviders/DataProvider.py	(revision 1364)
+++ conduit/dataproviders/DataProvider.py	(working copy)
@@ -49,7 +49,7 @@
     _out_type_ = ""
     _in_type_ = ""
     
-    def __init__(self):
+    def __init__(self, *args):
         """
         All sync functionality should be provided by derived classes
         """
@@ -209,10 +209,7 @@
     def configure(self, window):
         """
         Show a configuration box for configuring the dataprovider instance.
-        This call may block
-        
-        @param window: The parent window (to show a modal diaconduit.log)
-        @type window: {gtk.Window}
+        @param window: The parent gtk.Window (to show a modal dialog)
         """
         log.debug("configure() not overridden by derived class %s" % self._name_)
 
@@ -485,19 +482,18 @@
     dynamic dataproviders become available at runtime.
     """
     __gsignals__ = {
-        "dataprovider-available" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+        "dataprovider-added" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
             gobject.TYPE_PYOBJECT,      #Wrapper
-            gobject.TYPE_PYOBJECT]),    #Class
-        "dataprovider-unavailable" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
-            gobject.TYPE_STRING])     #Unique key
+            gobject.TYPE_PYOBJECT,      #Class
+            gobject.TYPE_BOOLEAN]),     #Allow Use of Existing Classes
+        "dataprovider-removed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING])       #Unique key
     }
 
     def __init__(self, **kwargs):
         gobject.GObject.__init__(self)
 
-    def emit_added(self, klass, initargs=(), category=None):
-        if category == None:
-            category = getattr(klass, "_category_", conduit.dataproviders.CATEGORY_TEST)
+    def emit_added(self, klass, initargs, category, allowDuplicates=False):
         dpw = ModuleWrapper.ModuleWrapper(   
                     name=getattr(klass, "_name_", ""),
                     description=getattr(klass, "_description_", ""),
@@ -512,13 +508,13 @@
                     module=None,
                     enabled=True
                     )
-        log.debug("DataProviderFactory %s: Emitting dataprovider-available for %s" % (self, dpw.get_key()))
-        self.emit("dataprovider-available", dpw, klass)
+        log.debug("DataProviderFactory %s: Emitting dataprovider-added for %s" % (self, dpw.get_key()))
+        self.emit("dataprovider-added", dpw, klass, allowDuplicates)
         return dpw.get_key()
 
     def emit_removed(self, key):
-        log.debug("DataProviderFactory %s: Emitting dataprovider-unavailable for %s" % (self, key))
-        self.emit("dataprovider-unavailable", key)
+        log.debug("DataProviderFactory %s: Emitting dataprovider-removed for %s" % (self, key))
+        self.emit("dataprovider-removed", key)
 
     def probe(self):
         pass
Index: conduit/modules/FileModule/FileModule.py
===================================================================
--- conduit/modules/FileModule/FileModule.py	(revision 1364)
+++ conduit/modules/FileModule/FileModule.py	(working copy)
@@ -5,15 +5,17 @@
 
 import conduit
 import conduit.dataproviders.DataProvider as DataProvider
+import conduit.dataproviders.DataProviderCategory as DataProviderCategory
 import conduit.dataproviders.File as FileDataProvider
+import conduit.dataproviders.VolumeFactory as VolumeFactory
 import conduit.dataproviders.AutoSync as AutoSync
 import conduit.Utils as Utils
 import conduit.Vfs as Vfs
 
 MODULES = {
-	"FileSource" :      { "type": "dataprovider" },
-	"FolderTwoWay" :    { "type": "dataprovider" },
-#    "USBFactory" :      { "type": "dataprovider-factory" }
+	"FileSource" :              { "type": "dataprovider" },
+	"FolderTwoWay" :            { "type": "dataprovider" },
+    "RemovableDeviceFactory" :  { "type": "dataprovider-factory" }
 }
 
 class FileSource(FileDataProvider.FileSource):
@@ -66,11 +68,12 @@
     DEFAULT_COMPARE_IGNORE_MTIME = False
 
     def __init__(self, *args):
+        print "#"*100,self.DEFAULT_FOLDER
         FileDataProvider.FolderTwoWay.__init__(self,
-                FolderTwoWay.DEFAULT_FOLDER,
-                FolderTwoWay.DEFAULT_GROUP,
-                FolderTwoWay.DEFAULT_HIDDEN,
-                FolderTwoWay.DEFAULT_COMPARE_IGNORE_MTIME
+                self.DEFAULT_FOLDER,
+                self.DEFAULT_GROUP,
+                self.DEFAULT_HIDDEN,
+                self.DEFAULT_COMPARE_IGNORE_MTIME
                 )
         AutoSync.AutoSync.__init__(self)
         self._monitor_folder_id = None
@@ -79,7 +82,7 @@
         if self._monitor_folder_id != None:
             Vfs.monitor_cancel(self._monitor_folder_id)
             self._monitor_folder_id = None
-
+            
     def configure(self, window):
         Utils.dataprovider_add_dir_to_path(__file__, "")
         import FileConfiguration
@@ -88,10 +91,10 @@
         self._monitor_folder()
         
     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.compareIgnoreMtime = config.get("compareIgnoreMtime", FolderTwoWay.DEFAULT_COMPARE_IGNORE_MTIME)
+        self.folder = config.get("folder", self.DEFAULT_FOLDER)
+        self.folderGroupName = config.get("folderGroupName", self.DEFAULT_GROUP)
+        self.includeHidden = config.get("includeHidden", self.DEFAULT_HIDDEN)
+        self.compareIgnoreMtime = config.get("compareIgnoreMtime", self.DEFAULT_COMPARE_IGNORE_MTIME)
         self._monitor_folder()
 
     def get_configuration(self):
@@ -128,46 +131,50 @@
         elif event == Vfs.MONITOR_EVENT_DELETED:
             self.handle_deleted(event_uri)
 
-class USBFactory(DataProvider.DataProviderFactory):
+class RemovableDeviceFactory(VolumeFactory.VolumeFactory):
+
     def __init__(self, **kwargs):
-        DataProvider.DataProviderFactory.__init__(self, **kwargs)
+        VolumeFactory.VolumeFactory.__init__(self, **kwargs)
+        self.foo = {}
 
-        if kwargs.has_key("hal"):
-            self.hal = kwargs["hal"]
-            self.hal.connect("usb-added", self._usb_added)
-            self.hal.connect("usb-removed", self._usb_removed)
+    def _make_class(self, folder, name):
+        klass = type(
+                "FolderTwoWay",
+                (FolderTwoWay,),
+                {"DEFAULT_FOLDER":folder,"DEFAULT_GROUP":name}
+                )
+        print "*"*100,klass.DEFAULT_FOLDER
+        return klass
 
-        self.usb = {}
-
-    def probe(self):
+    def emit_added(self, klass, initargs, category, allowDuplicates=False):
         """
-        Probe for USB Keys that are already attached
+        Override emit_added to allow duplictes.
         """
-        for device_type, udi, mount, name in self.hal.get_all_usb_keys():
-            self._usb_added(None, udi, mount, name)
+        VolumeFactory.VolumeFactory.emit_added(self, klass, initargs, category, allowDuplicates=True)
 
-    def _usb_added(self, hal, udi, mount, name):
-        """
-        New USB key has been discovered
-        """
-        cat = DataProvider.DataProviderCategory(
-                    name,
-                    "drive-removable-media",
-                    mount)
+    def is_interesting(self, udi, props):
+        if props.has_key("info.parent") and props.has_key("info.parent") != "":
+            prop2 = self._get_properties(props["info.parent"])
+            if prop2.has_key("storage.removable") and prop2["storage.removable"] == True:
+                mount,label = self._get_device_info(props)
+                #check for the presence of mount/.conduit/group file(s)
+                #which describe the groups, and their names, on the volume
+                path = Vfs.uri_join(mount,".conduit")
+                log.debug("------------------Checking for %s" % path)
+                if Vfs.uri_exists(path):
+                    self.foo[udi] = [self._make_class("/tmp",i) for i in range(2)]
+                return True
+        return False
+    
+    def get_category(self, udi, **kwargs):
+        return DataProviderCategory.DataProviderCategory(
+                    kwargs['label'],
+                    "multimedia-player-ipod-video-white",
+                    udi)
 
-        keys = []
-        for klass in [FileSource]:
-            key = self.emit_added(
-                           klass,            # Dataprovider class
-                           (mount,udi,),     # Init args
-                           cat)              # Category..
-            keys.append(key)
+    def get_dataproviders(self, udi, **kwargs):
+         return self.foo[udi]
+         
+    def get_args(self, udi, **kwargs):
+        return ()
 
-        self.usb[udi] = keys
-
-    def _usb_removed(self, hal, udi, mount, name):
-        for key in self.usb[udi]:
-            self.emit_removed(key)
-
-        del self.usb[udi]
-
Index: conduit/modules/NetworkModule/Client.py
===================================================================
--- conduit/modules/NetworkModule/Client.py	(revision 1364)
+++ conduit/modules/NetworkModule/Client.py	(working copy)
@@ -121,10 +121,10 @@
 
         # Register the new dataprovider with Conduit
         key = self.emit_added(
-                                  newdp, 
-                                  (), #No init args, these are encoded as class params
-                                  self.categories[newdp.hostUrl]
-                             )
+                          klass=newdp, 
+                          initargs=(), #No init args, these are encoded as class params
+                          category=self.categories[newdp.hostUrl]
+                         )
 
         # Record the key so we can unregister the dp later (if needed)
         self.dataproviders[hostUrl][newdp.uid] = key
Index: conduit/Module.py
===================================================================
--- conduit/Module.py	(revision 1364)
+++ conduit/Module.py	(working copy)
@@ -59,25 +59,21 @@
         #scan all dirs for files in the right format (*Module/*Module.py)
         self.filelist = self._build_filelist_from_directories(dirs)
 
-    def _on_dynamic_dataprovider_available(self, monitor, dpw, klass):
+    def _on_dynamic_dataprovider_added(self, monitor, dpw, klass, allowDuplicates):
         """
         Store the ipod so it can be retrieved later by the treeview/model
         emit a signal so it is added to the GUI
         """
         log.info("Dynamic dataprovider (%s) available by %s" % (dpw, monitor))
-        self._append_module(dpw, klass)
+        self._append_module(dpw, klass, allowDuplicates)
 
-    def _on_dynamic_dataprovider_unavailable(self, monitor, key):
+    def _on_dynamic_dataprovider_removed(self, monitor, key):
         log.info("Dynamic dataprovider (%s) unavailable by %s" % (key, monitor))
         self._remove_module(key)
 
     def _emit_available(self, dataproviderWrapper):
         if dataproviderWrapper.module_type in ["source", "sink", "twoway"]:
             self.emit("dataprovider-available", dataproviderWrapper)
-        else:
-            #Dont emit a signal when a datatype of converter is loaded as I dont
-            #think signal emission is useful in that case
-            pass
 
     def _emit_unavailable(self, dataproviderWrapper):
         if dataproviderWrapper.module_type in ["source", "sink", "twoway"]:
@@ -116,19 +112,12 @@
         return res            
        
     def _is_module(self, filename):
-        """
-        Tests whether the filename has the appropriate extension.
-        """
-        endswith = "Module.py"
-        isModule = filename[-len(endswith):] == endswith
-        return isModule
+        return filename.endswith("Module.py")
 
     def _is_module_dir(self, dirname):
-        endswith = "Module"
-        isModuleDir = dirname[-len(endswith):] == endswith
-        return isModuleDir
+        return dirname.endswith("Module")
         
-    def _append_module(self, wrapper, klass):
+    def _append_module(self, wrapper, klass, allowDuplicates):
         """
         Checks if the given module (checks by classname) is already loaded
         into the modulelist array, if not it is added to that array
@@ -137,14 +126,14 @@
         @type module: L{conduit.ModuleManager.ModuleWrapper}
         """
         #Check if the class is unique
-        classname = klass.__name__
+        classname = wrapper.classname
         if classname not in self.classRegistry:
             self.classRegistry[classname] = klass
         else:
-            log.warn("Class named %s allready loaded" % (classname))
+            log.info("Class named %s allready loaded" % (classname))
         #Check if the wrapper is unique
         key = wrapper.get_key()
-        if key not in self.moduleWrappers:
+        if (allowDuplicates == True) or (key not in self.moduleWrappers):
             self.moduleWrappers[key] = wrapper
             #Emit a signal because this wrapper is new
             self._emit_available(wrapper)
@@ -189,7 +178,23 @@
                     log.warn("Class %s in file %s does define a %s attribute. Skipping." % (modules, filename, i))
                     raise Exception
         return mods
-        
+
+    def _build_modulewrapper(self, klass, moduleType, filename):
+        mod_wrapper = ModuleWrapper(   
+                                name=getattr(klass, "_name_", ""),
+                                description=getattr(klass, "_description_", ""),
+                                icon_name=getattr(klass, "_icon_", ""),
+                                module_type=getattr(klass, "_module_type_", moduleType),
+                                category=getattr(klass, "_category_", CATEGORY_TEST),
+                                in_type=getattr(klass, "_in_type_", ""),
+                                out_type=getattr(klass, "_out_type_", ""),
+                                filename=filename,
+                                classname=klass.__name__,
+                                initargs=(),
+                                enabled=True
+                                )
+        return mod_wrapper
+
     def _load_modules_in_file(self, filename):
         """
         Loads all modules in the given file
@@ -200,21 +205,12 @@
                 try:
                     klass = getattr(mod, modules)
                     if infos["type"] == "dataprovider" or infos["type"] == "converter":
-                        mod_wrapper = ModuleWrapper(   
-                                            name=getattr(klass, "_name_", ""),
-                                            description=getattr(klass, "_description_", ""),
-                                            icon_name=getattr(klass, "_icon_", ""),
-                                            module_type=getattr(klass, "_module_type_", infos["type"]),
-                                            category=getattr(klass, "_category_", CATEGORY_TEST),
-                                            in_type=getattr(klass, "_in_type_", ""),
-                                            out_type=getattr(klass, "_out_type_", ""),
-                                            filename=filename,
-                                            classname=klass.__name__,
-                                            initargs=(),
-                                            enabled=True
-                                            )
-                        #Save the module (signal is emitted _append_module
-                        self._append_module(mod_wrapper, klass)
+                        #Save the module (signal is emitted in _append_module)
+                        self._append_module(
+                                self._build_modulewrapper(klass, infos["type"], filename),
+                                klass,
+                                False
+                                )
                     elif infos["type"] == "dataprovider-factory":
                         # build a dict of kwargs to pass to factories
                         kwargs = {
@@ -224,11 +220,11 @@
                         instance = klass(**kwargs)
                         self.dataproviderFactories.append(instance)
                     else:
-                        log.warn("Class %s is an unknown type: %s" % (klass.__name__, infos["type"]))
+                        log.warn("Class is an unknown type: %s" % klass)
                 except AttributeError:
-                    log.warn("Could not find module %s in %s\n%s" % (modules,filename,traceback.format_exc()))
+                    log.warn("Could not find module %s in %s" % (modules,filename))
         except Exception, e:
-            log.warn("Error loading the file: %s.\n%s" % (filename, traceback.format_exc()))
+            log.warn("Error loading the file: %s\n%s" % (filename, traceback.format_exc()))
             self.invalidFiles.append(os.path.basename(filename))
 
     def _instantiate_class(self, classname, initargs):
@@ -249,8 +245,8 @@
             self._load_modules_in_file(f)
 
         for i in self.dataproviderFactories:
-            i.connect("dataprovider-unavailable", self._on_dynamic_dataprovider_unavailable)
-            i.connect("dataprovider-available", self._on_dynamic_dataprovider_available)
+            i.connect("dataprovider-removed", self._on_dynamic_dataprovider_removed)
+            i.connect("dataprovider-added", self._on_dynamic_dataprovider_added)
             i.probe()
 
         self.emit('all-modules-loaded')
@@ -280,9 +276,7 @@
         if wrapperKey in self.moduleWrappers:
             #Get the existing wrapper
             m = self.moduleWrappers[wrapperKey]
-            #Get its construction args
-            classname = m.classname
-            initargs = m.initargs
+            #Make a copy of it, containing an instantiated module
             mod_wrapper = ModuleWrapper(  
                                 name=m.name, 
                                 description=m.description,
@@ -292,9 +286,9 @@
                                 in_type=m.in_type,
                                 out_type=m.out_type,
                                 filename=m.filename,
-                                classname=classname,
-                                initargs=initargs,
-                                module=self._instantiate_class(classname, initargs),
+                                classname=m.classname,
+                                initargs=m.initargs,
+                                module=self._instantiate_class(m.classname, m.initargs),
                                 enabled=True
                                 )
         else:
