conduit r1534 - in trunk: . conduit conduit/dataproviders conduit/gtkui conduit/modules conduit/modules/FileModule test/python-tests
- From: johncarr svn gnome org
- To: svn-commits-list gnome org
- Subject: conduit r1534 - in trunk: . conduit conduit/dataproviders conduit/gtkui conduit/modules conduit/modules/FileModule test/python-tests
- Date: Wed, 9 Jul 2008 16:07:11 +0000 (UTC)
Author: johncarr
Date: Wed Jul 9 16:07:11 2008
New Revision: 1534
URL: http://svn.gnome.org/viewvc/conduit?rev=1534&view=rev
Log:
Merge in branch from nzjrs
Added:
trunk/ (props changed)
- copied from r1532, /trunk/
trunk/test/python-tests/TestCoreConflict.py
Modified:
trunk/ChangeLog
trunk/conduit/Conduit.py
trunk/conduit/Conflict.py
trunk/conduit/Synchronization.py
trunk/conduit/dataproviders/DataProvider.py
trunk/conduit/dataproviders/SimpleFactory.py
trunk/conduit/dataproviders/VolumeFactory.py
trunk/conduit/gtkui/ConflictResolver.py
trunk/conduit/gtkui/UI.py
trunk/conduit/modules/FileModule/FileConfiguration.py
trunk/conduit/modules/FileModule/FileModule.py
trunk/conduit/modules/FileModule/config.glade
trunk/conduit/modules/TestModule.py
Modified: trunk/conduit/Conduit.py
==============================================================================
--- /trunk/conduit/Conduit.py (original)
+++ trunk/conduit/Conduit.py Wed Jul 9 16:07:11 2008
@@ -85,12 +85,14 @@
self.autoSyncEnabled = False
self.conflictPolicy = ""
self.deletedPolicy = ""
-
+
#set conduits to have the default conflict/deleted policy
for policyName in CONFLICT_POLICY_NAMES:
policyValue = conduit.GLOBALS.settings.get("default_policy_%s" % policyName)
self.set_policy(policyName,policyValue)
+ self._conflicts = {}
+
def _parameters_changed(self):
self.emit("parameters-changed")
@@ -297,7 +299,7 @@
)
if newDpw.module != None:
newDpw.module.connect("change-detected", self._change_detected)
-
+
self.emit("dataprovider-changed", oldDpw, newDpw)
def refresh_dataprovider(self, dp, block=False):
@@ -324,4 +326,15 @@
else:
log.info("Conduit must have a datasource and a datasink")
-
+ def emit_conflict(self, conflict):
+ hc = hash(conflict)
+ if hc not in self._conflicts:
+ self._conflicts[hc] = conflict
+ self.emit("sync-conflict", conflict)
+
+ def resolved_conflict(self, conflict):
+ try:
+ hc = hash(conflict)
+ del(self._conflicts[hc])
+ except KeyError:
+ log.warn("Unknown conflict")
Modified: trunk/conduit/Conflict.py
==============================================================================
--- /trunk/conduit/Conflict.py (original)
+++ trunk/conduit/Conflict.py Wed Jul 9 16:07:11 2008
@@ -4,6 +4,11 @@
Copyright: John Stowers, 2006
License: GPLv2
"""
+import logging
+log = logging.getLogger("Conflict")
+
+import conduit
+
#ENUM of directions when resolving a conflict
CONFLICT_ASK = 0
CONFLICT_SKIP = 1
@@ -15,7 +20,8 @@
"""
Represents a conflict
"""
- def __init__(self, sourceWrapper, sourceData, sourceDataRid, sinkWrapper, sinkData, sinkDataRid, validResolveChoices, isDeletion):
+ def __init__(self, cond, sourceWrapper, sourceData, sourceDataRid, sinkWrapper, sinkData, sinkDataRid, validResolveChoices, isDeletion):
+ self.cond = cond
self.sourceWrapper = sourceWrapper
self.sourceData = sourceData
self.sourceDataRid = sourceDataRid
@@ -25,4 +31,85 @@
self.choices = validResolveChoices
self.isDeletion = isDeletion
+ self._gen_hash()
+
+ def _gen_hash(self):
+ if self.sourceWrapper and self.sourceDataRid and self.sinkWrapper:
+ mapping = conduit.GLOBALS.mappingDB.get_mapping(
+ sourceUID=self.sourceWrapper.get_UID(),
+ dataLUID=self.sourceDataRid.get_UID(),
+ sinkUID=self.sinkWrapper.get_UID())
+
+ if mapping.oid:
+ self._hash = hash(mapping.oid)
+ return
+
+ #approximate a stable hash for the relationship that is invariant
+ #based upon the order of source and sink
+ uids = [hash(self.sourceWrapper.get_UID()),
+ hash(self.sinkWrapper.get_UID()),
+ hash(self.sourceDataRid),
+ hash(self.sinkDataRid)]
+ uids.sort()
+ self._hash = hash(tuple(uids))
+
+ def __hash__(self):
+ return self._hash
+
+ def get_partnership(self):
+ return self.sourceWrapper,self.sinkWrapper
+
+ def get_snippet(self, is_source):
+ if is_source:
+ return self.sourceData.get_snippet()
+ else:
+ return self.sinkData.get_snippet()
+
+ def get_icon(self, is_source):
+ return None
+
+ def resolve(self, direction):
+ resolve = True
+ delete = False
+
+ if direction == CONFLICT_ASK:
+ log.debug("Not resolving")
+ resolve = False
+ elif direction == CONFLICT_SKIP:
+ log.debug("Skipping conflict")
+ resolve = False
+ elif direction == CONFLICT_COPY_SOURCE_TO_SINK:
+ log.debug("Resolving source data --> sink")
+ data = self.sourceData
+ dataRid = self.sourceDataRid
+ source = self.sourceWrapper
+ sink = self.sinkWrapper
+ elif direction == CONFLICT_COPY_SINK_TO_SOURCE:
+ log.debug("Resolving source <-- sink data")
+ data = self.sinkData
+ dataRid = self.sinkDataRid
+ source = self.sinkWrapper
+ sink = self.sourceWrapper
+ elif direction == CONFLICT_DELETE:
+ log.debug("Resolving deletion --->")
+ data = self.sinkData
+ dataRid = self.sinkDataRid
+ source = self.sourceWrapper
+ sink = self.sinkWrapper
+ delete = True
+ else:
+ log.warn("Unknown resolution")
+ resolve = False
+
+ if resolve:
+ if delete:
+ log.debug("Resolving self. Deleting %s from %s" % (data, sink))
+ conduit.Synchronization.delete_data(source, sink, data.get_UID())
+ else:
+ log.debug("Resolving self. Putting %s --> %s" % (data, sink))
+ conduit.Synchronization.put_data(source, sink, data, dataRid, True)
+
+ self.cond.resolved_conflict(self)
+
+ return resolve
Modified: trunk/conduit/Synchronization.py
==============================================================================
--- /trunk/conduit/Synchronization.py (original)
+++ trunk/conduit/Synchronization.py Wed Jul 9 16:07:11 2008
@@ -391,6 +391,7 @@
sourceData = DeletedData(sourceDataLUID)
sinkData = DeletedData(sinkDataLUID)
c = Conflict(
+ self.cond, #the conduit this conflict belongs to
sourceWrapper, #datasource wrapper
sourceData, #from data
sourceData.get_rid(), #from data rid
@@ -400,7 +401,7 @@
validResolveChoices, #valid resolve choices
True #This conflict is a deletion
)
- self.cond.emit("sync-conflict", c)
+ self.cond.emit_conflict(c)
elif self.cond.get_policy("deleted") == "replace":
log.debug("Deleted Policy: Delete")
@@ -426,6 +427,7 @@
avail = (CONFLICT_SKIP,CONFLICT_COPY_SOURCE_TO_SINK)
c = Conflict(
+ self.cond,
sourceWrapper,
fromData,
fromDataRid,
@@ -435,7 +437,7 @@
avail,
False
)
- self.cond.emit("sync-conflict", c)
+ self.cond.emit_conflict(c)
elif self.cond.get_policy("conflict") == "replace":
log.debug("Conflict Policy: Replace")
Modified: trunk/conduit/dataproviders/DataProvider.py
==============================================================================
--- /trunk/conduit/dataproviders/DataProvider.py (original)
+++ trunk/conduit/dataproviders/DataProvider.py Wed Jul 9 16:07:11 2008
@@ -459,9 +459,10 @@
category=category
)
dpw.set_dnd_key(customKey)
- log.debug("DataProviderFactory %s: Emitting dataprovider-added for %s" % (self, dpw.get_dnd_key()))
+ key = dpw.get_dnd_key()
+ log.debug("DataProviderFactory %s: Emitting dataprovider-added for %s" % (self, key))
self.emit("dataprovider-added", dpw, klass)
- return dpw.get_dnd_key()
+ return key
def emit_removed(self, key):
log.debug("DataProviderFactory %s: Emitting dataprovider-removed for %s" % (self, key))
@@ -476,18 +477,22 @@
"""
pass
- def get_configuration_widget(self):
+ def setup_configuration_widget(self):
"""
If the factory needs to offer configuration options then
- it should return a gtk.widget here.
+ it should return a gtk.widget here. This widget is then packed
+ into the configuration notebook.
"""
return None
- def save_configuration(self):
+ def save_configuration(self, ok):
"""
- If the user closes the configuration panel with RESPONSE_OK (e.g.
- doesnt click cancel) then this will be called on all derived classes
+ @param ok: True if the user closed the prefs panel with OK, false if
+ they cancelled it.
"""
pass
+
+ def get_name(self):
+ return self.__class__.__name__
Modified: trunk/conduit/dataproviders/SimpleFactory.py
==============================================================================
--- /trunk/conduit/dataproviders/SimpleFactory.py (original)
+++ trunk/conduit/dataproviders/SimpleFactory.py Wed Jul 9 16:07:11 2008
@@ -15,6 +15,7 @@
self.items = {}
def item_added(self, key, **kwargs):
+ log.info("Item Added: %s" % key)
cat = self.get_category(key, **kwargs)
idxs = []
for klass in self.get_dataproviders(key, **kwargs):
@@ -27,7 +28,7 @@
if key in self.items:
for idx in self.items[key]:
self.emit_removed(idx)
- del self.items[key]
+ del(self.items[key])
def get_category(self, key, **kwargs):
""" Return a category to contain these dataproviders """
@@ -40,3 +41,6 @@
def get_args(self, key, **kwargs):
raise NotImplementedError
+ def is_interesting(self, udi, properties):
+ raise NotImplementedError
+
Modified: trunk/conduit/dataproviders/VolumeFactory.py
==============================================================================
--- /trunk/conduit/dataproviders/VolumeFactory.py (original)
+++ trunk/conduit/dataproviders/VolumeFactory.py Wed Jul 9 16:07:11 2008
@@ -38,10 +38,11 @@
return True
def _volume_unmounted_cb(self, monitor, volume):
- log.info("Volume Umounted")
+ log.info("Volume Umounted: %s" % volume.get_hal_udi())
device_udi = volume.get_hal_udi()
if device_udi :
if self.is_interesting(device_udi, self._get_properties(device_udi)):
+ log.info("Removing Volume")
self.item_removed(device_udi)
return False
@@ -84,5 +85,4 @@
""" VolumeFactory passes mount point and udi to dataproviders """
return (kwargs['mount'], udi,)
- def is_interesting(self, udi, properties):
- raise NotImplementedError
+
Modified: trunk/conduit/gtkui/ConflictResolver.py
==============================================================================
--- /trunk/conduit/gtkui/ConflictResolver.py (original)
+++ trunk/conduit/gtkui/ConflictResolver.py Wed Jul 9 16:07:11 2008
@@ -5,7 +5,6 @@
License: GPLv2
"""
import traceback
-import threading
import time
import gobject
import gtk, gtk.gdk
@@ -24,9 +23,22 @@
CONFLICT_IDX = 0 #The conflict object
DIRECTION_IDX = 1 #The current user decision re: the conflict (-->, <-- or -x-)
-class ConflictHeader(Conflict.Conflict):
+class ConflictHeader:
def __init__(self, sourceWrapper, sinkWrapper):
- Conflict.Conflict.__init__(self, sourceWrapper, None, None, sinkWrapper, None, None, (), False)
+ self.sourceWrapper = sourceWrapper
+ self.sinkWrapper = sinkWrapper
+
+ def get_snippet(self, is_source):
+ if is_source:
+ return self.sourceWrapper.name
+ else:
+ return self.sinkWrapper.name
+
+ def get_icon(self, is_source):
+ if is_source:
+ return self.sourceWrapper.get_icon()
+ else:
+ return self.sinkWrapper.get_icon()
class ConflictResolver:
"""
@@ -41,9 +53,6 @@
self.partnerships = {}
self.numConflicts = 0
- #resolve conflicts in a background thread
- self.resolveThreadManager = ConflictResolveThreadManager(3)
-
self.view = gtk.TreeView( self.model )
self._build_view()
@@ -118,37 +127,13 @@
self.view.get_selection().connect("changed", self.on_selection_changed)
def _name_data_func(self, column, cell_renderer, tree_model, rowref, is_source):
- """
- The format for displaying the data is
- uri (modified)
- snippet
- """
conflict = tree_model.get_value(rowref, CONFLICT_IDX)
- #render the headers different to the data
- if tree_model.iter_depth(rowref) == 0:
- if is_source:
- text = conflict.sourceWrapper.name
- else:
- text = conflict.sinkWrapper.name
- else:
- if is_source:
- text = conflict.sourceData.get_snippet()
- else:
- text = conflict.sinkData.get_snippet()
-
+ text = conflict.get_snippet(is_source)
cell_renderer.set_property("text", text)
def _icon_data_func(self, column, cell_renderer, tree_model, rowref, is_source):
conflict = tree_model.get_value(rowref, CONFLICT_IDX)
- #Only the headers have icons
- if tree_model.iter_depth(rowref) == 0:
- if is_source:
- icon = conflict.sourceWrapper.get_icon()
- else:
- icon = conflict.sinkWrapper.get_icon()
- else:
- icon = None
-
+ icon = conflict.get_icon(is_source)
cell_renderer.set_property("pixbuf", icon)
def _direction_data_func(self, column, cell_renderer, tree_model, rowref, user_data):
@@ -164,54 +149,22 @@
def _set_conflict_titles(self):
self.expander.set_label(_("Conflicts (%s)") % self.numConflicts)
self.standalone.set_title(_("Conflicts (%s)") % self.numConflicts)
-
- def _conflict_resolved(self, sender, rowref):
- """
- Callback when a ConflictResolveThread finishes. Deletes the
- appropriate conflict from the model. Also looks to see if there
- are any other conflicts remainng so it can set the sink status and/or
- delete the partnership
- """
- if not self.model.iter_is_valid(rowref):
- #FIXME: Need to work a way around this before resolution can be threaded
- log.warn("Iters do not persist throug signal emission!")
- return
-
- self.model.remove(rowref)
- #now look for any sync partnerships with no children
- empty = False
- for source,sink in self.partnerships:
- rowref = self.partnerships[(source,sink)]
- numChildren = self.model.iter_n_children(rowref)
- if numChildren == 0:
- empty = True
-
- #do in two loops so as to not change the dict while iterating
- if empty:
- sink.module.set_status(DataProvider.STATUS_DONE_SYNC_OK)
- del(self.partnerships[(source,sink)])
- self.model.remove(rowref)
- else:
- sink.module.set_status(DataProvider.STATUS_DONE_SYNC_CONFLICT)
- #FIXME: Do this properly with model signals and a count function
- self.numConflicts -= 1
- self._set_conflict_titles()
-
- def on_conflict(self, thread, conflict):
+ def on_conflict(self, cond, conflict):
#We start with the expander disabled. Make sure we only enable it once
if len(self.model) == 0:
self.expander.set_sensitive(True)
self.numConflicts += 1
- source = conflict.sourceWrapper
- sink = conflict.sinkWrapper
+ source,sink = conflict.get_partnership()
if (source,sink) not in self.partnerships:
#create a header row
header = ConflictHeader(source, sink)
- self.partnerships[(source,sink)] = self.model.append(None, (header, Conflict.CONFLICT_ASK) )
+ rowref = self.model.append(None, (header, Conflict.CONFLICT_ASK))
+ self.partnerships[(source,sink)] = (rowref,conflict)
- self.model.append(self.partnerships[(source,sink)], (conflict, Conflict.CONFLICT_ASK) )
+ rowref = self.partnerships[(source,sink)][0]
+ self.model.append(rowref, (conflict, Conflict.CONFLICT_ASK))
#FIXME: Do this properly with model signals and a count function
#update the expander label and the standalone window title
@@ -242,12 +195,6 @@
return True
def on_resolve_conflicts(self, sender):
- """
- According to the users selection, start backgroun threads to
- resolve the conflicts
- """
- IHaveMadeItersPersist = False
-
#save the resolved rowrefs and remove them at the end
resolved = []
@@ -259,76 +206,30 @@
direction = model[path][DIRECTION_IDX]
conflict = model[path][CONFLICT_IDX]
- #do as the user inducated with the arrow
- if direction == Conflict.CONFLICT_ASK:
- log.debug("Not resolving")
- return
- elif direction == Conflict.CONFLICT_SKIP:
- log.debug("Skipping conflict")
+ if conflict.resolve(direction):
resolved.append(rowref)
- return
- elif direction == Conflict.CONFLICT_COPY_SOURCE_TO_SINK:
- log.debug("Resolving source data --> sink")
- data = conflict.sourceData
- dataRid = conflict.sourceDataRid
- source = conflict.sourceWrapper
- sink = conflict.sinkWrapper
- elif direction == Conflict.CONFLICT_COPY_SINK_TO_SOURCE:
- log.debug("Resolving source <-- sink data")
- data = conflict.sinkData
- dataRid = conflict.sinkDataRid
- source = conflict.sinkWrapper
- sink = conflict.sourceWrapper
- elif direction == Conflict.CONFLICT_DELETE:
- log.debug("Resolving deletion --->")
- data = conflict.sinkData
- dataRid = conflict.sinkDataRid
- source = conflict.sourceWrapper
- sink = conflict.sinkWrapper
- else:
- log.warn("Unknown resolution")
-
- deleted = conflict.isDeletion
-
- #add to resolve thread
- #FIXME: Think of a way to make rowrefs persist through signals
- if IHaveMadeItersPersist:
- self.resolveThreadManager.make_thread(self._conflict_resolved,rowref,source,sink,data,dataRid,deleted)
- else:
- try:
- if deleted:
- log.debug("Resolving conflict. Deleting %s from %s" % (data, sink))
- conduit.Synchronization.delete_data(source, sink, data.get_UID())
- else:
- log.debug("Resolving conflict. Putting %s --> %s" % (data, sink))
- conduit.Synchronization.put_data(source, sink, data, dataRid, True)
-
- resolved.append(rowref)
- except Exception:
- log.warn("Could not resolve conflict\n%s" % traceback.format_exc())
self.model.foreach(_resolve_func)
for r in resolved:
self.model.remove(r)
- if not IHaveMadeItersPersist:
- #now look for any sync partnerships with no children
- empty = []
- for source,sink in self.partnerships:
- rowref = self.partnerships[(source,sink)]
- numChildren = self.model.iter_n_children(rowref)
- if numChildren == 0:
- sink.module.set_status(DataProvider.STATUS_DONE_SYNC_OK)
- empty.append( (rowref, source, sink) )
- else:
- sink.module.set_status(DataProvider.STATUS_DONE_SYNC_CONFLICT)
-
- #do in two loops so as to not change the model while iterating
- for rowref, source, sink in empty:
- self.model.remove(rowref)
- try:
- del(self.partnerships[(source,sink)])
- except KeyError: pass
+ #now look for any sync partnerships with no children
+ empty = []
+ for source,sink in self.partnerships:
+ rowref = self.partnerships[(source,sink)][0]
+ numChildren = self.model.iter_n_children(rowref)
+ if numChildren == 0:
+ sink.module.set_status(DataProvider.STATUS_DONE_SYNC_OK)
+ empty.append( (rowref, source, sink) )
+ else:
+ sink.module.set_status(DataProvider.STATUS_DONE_SYNC_CONFLICT)
+
+ #do in two loops so as to not change the model while iterating
+ for rowref, source, sink in empty:
+ self.model.remove(rowref)
+ try:
+ del(self.partnerships[(source,sink)])
+ except KeyError: pass
def on_cancel_conflicts(self, sender):
self.model.clear()
@@ -432,127 +333,3 @@
model[path][DIRECTION_IDX] = conflict.choices[curIdx+1]
return True
-
-class _ConflictResolveThread(threading.Thread, gobject.GObject):
- """
- Resolves a conflict or deletion event. If a deleted event then
- calls sink.delete, if a conflict then does put()
- """
- __gsignals__ = {
- "completed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
- }
-
- def __init__(self, *args):
- """
- Args
- - arg[0]: source
- - arg[1]: sink
- - arg[2]: data
- - arg[3]: dataRid
- - arg[4]: isDeleted
- """
- threading.Thread.__init__(self)
- gobject.GObject.__init__(self)
-
-
- self.source = args[0]
- self.sink = args[1]
- self.data = args[2]
- self.dataRid = args[3]
- self.isDeleted = args[4]
-
- self.setName("ResolveThread for sink: %s. (Delete: %s)" % (self.sink, self.isDeleted))
-
- def emit(self, *args):
- """
- Override the gobject signal emission so that all signals are emitted
- from the main loop on an idle handler
- """
- gobject.idle_add(gobject.GObject.emit,self,*args)
-
- def run(self):
- try:
- if self.isDeleted:
- log.debug("Resolving conflict. Deleting %s from %s" % (self.data, self.sink))
- conduit.Synchronization.delete_data(self.source, self.sink, self.dataRid.get_UID())
- else:
- log.debug("Resolving conflict. Putting %s --> %s" % (self.data, self.sink))
- conduit.Synchronization.put_data(self.source, self.sink, self.data, self.dataRid, True)
- except Exception:
- log.warn("Could not resolve conflict\n%s" % traceback.format_exc())
- #sink.module.set_status(DataProvider.STATUS_DONE_SYNC_ERROR)
-
- self.emit("completed")
-
-class ConflictResolveThreadManager:
- """
- Manages many resolve threads. This involves joining and cancelling
- said threads, and respecting a maximum num of concurrent threads limit
- """
- def __init__(self, maxConcurrentThreads):
- self.maxConcurrentThreads = maxConcurrentThreads
- #stores all threads, running or stopped
- self.fooThreads = {}
- #the pending thread args are used as an index for the stopped threads
- self.pendingFooThreadArgs = []
-
- def _register_thread_completed(self, thread, *args):
- """
- Decrements the count of concurrent threads and starts any
- pending threads if there is space
- """
- del(self.fooThreads[args])
- running = len(self.fooThreads) - len(self.pendingFooThreadArgs)
-
- log.debug("Thread %s completed. %s running, %s pending" % (
- thread, running, len(self.pendingFooThreadArgs)))
-
- if running < self.maxConcurrentThreads:
- try:
- args = self.pendingFooThreadArgs.pop()
- log.debug("Starting pending %s" % self.fooThreads[args])
- self.fooThreads[args].start()
- except IndexError: pass
-
- def make_thread(self, completedCb, completedCbUserData, *args):
- """
- Makes a thread with args. The thread will be started when there is
- a free slot
- """
- running = len(self.fooThreads) - len(self.pendingFooThreadArgs)
-
- if args not in self.fooThreads:
- thread = _ConflictResolveThread(*args)
- #signals run in the order connected. Connect the user one first
- #incase they wish to do something before we delete the thread
- thread.connect("completed", completedCb, completedCbUserData)
- thread.connect("completed", self._register_thread_completed, *args)
- #This is why we use args, not kwargs, because args are hashable
- self.fooThreads[args] = thread
-
- if running < self.maxConcurrentThreads:
- log.debug("Starting %s" % thread)
- self.fooThreads[args].start()
- else:
- log.debug("Queing %s" % thread)
- self.pendingFooThreadArgs.append(args)
- else:
- log.debug("Already resolving conflict")
-
- def join_all_threads(self):
- """
- Joins all threads (blocks)
-
- Unfortunately we join all the threads do it in a loop to account
- for join() a non started thread failing. To compensate I time.sleep()
- to not smoke CPU
- """
- joinedThreads = 0
- while(joinedThreads < len(self.fooThreads)):
- for thread in self.fooThreads.values():
- try:
- thread.join()
- joinedThreads += 1
- except AssertionError:
- #deal with not started threads
- time.sleep(1)
Modified: trunk/conduit/gtkui/UI.py
==============================================================================
--- /trunk/conduit/gtkui/UI.py (original)
+++ trunk/conduit/gtkui/UI.py Wed Jul 9 16:07:11 2008
@@ -233,8 +233,8 @@
#Build some liststores to display
CONVERT_FROM_MESSAGE = _("Convert from")
- CONVERT_INTO_MESSAGE = _("into")
-
+ CONVERT_INTO_MESSAGE = _("into")
+
convertables = self.type_converter.get_convertables_list()
converterListStore = gtk.ListStore( str )
for froms,tos in convertables:
@@ -250,9 +250,10 @@
#construct the dialog
tree = gtk.glade.XML(self.gladeFile, "PreferencesDialog")
+ notebook = tree.get_widget("prop_notebook")
+
#Show the DB contents to help debugging
if conduit.IS_DEVELOPMENT_VERSION:
- notebook = tree.get_widget("prop_notebook")
vbox = gtk.VBox(False,5)
#build the treeview to show all column fields. For performance
@@ -284,7 +285,6 @@
vbox.pack_start(clear, False, False)
notebook.append_page(vbox,gtk.Label('Mapping DB'))
- notebook.show_all()
converterTreeView = tree.get_widget("dataConversionsTreeView")
converterTreeView.set_model(converterListStore)
@@ -326,6 +326,20 @@
if currentValue == policyValue:
widget.set_active(True)
+ #Add configuration widgets for all factories
+ #The dataprovider factories can provide a configuration widget which is
+ #packed into the notebook
+ factoryConfigurationWidgets = []
+ for i in conduit.GLOBALS.moduleManager.dataproviderFactories:#get_modules_by_type("dataprovider-factory"):
+ widget = i.setup_configuration_widget()
+ if widget:
+ factoryConfigurationWidgets.append(widget)
+ notebook.append_page(
+ widget,
+ gtk.Label(i.get_name()))
+
+ notebook.show_all()
+
#Show the dialog
dialog = tree.get_widget("PreferencesDialog")
dialog.set_transient_for(self.mainWindow)
@@ -346,6 +360,11 @@
conduit.GLOBALS.settings.set(
"default_policy_%s" % policyName,
policyValue)
+
+ #give the dataprovider factories to ability to save themselves
+ for factory in factoryConfigurationWidgets:
+ factory.save_configuration(response == gtk.RESPONSE_OK)
+
dialog.destroy()
@@ -479,7 +498,9 @@
The splash can also be destroyed manually by the application
"""
self.wSplash = gtk.Window(gtk.WINDOW_POPUP)
+ self.wSplash.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_SPLASHSCREEN)
self.wSplash.set_decorated(False)
+
wSplashScreen = gtk.Image()
wSplashScreen.set_from_file(os.path.join(conduit.SHARED_DATA_DIR,"conduit-splash.png"))
Modified: trunk/conduit/modules/FileModule/FileConfiguration.py
==============================================================================
--- /trunk/conduit/modules/FileModule/FileConfiguration.py (original)
+++ trunk/conduit/modules/FileModule/FileConfiguration.py Wed Jul 9 16:07:11 2008
@@ -300,11 +300,10 @@
dialog.emit_stop_by_name("response")
class _FolderTwoWayConfigurator:
- def __init__(self, mainWindow, folder, folderGroupName, includeHidden, compareIgnoreMtime, followSymlinks):
+ def __init__(self, mainWindow, folder, includeHidden, compareIgnoreMtime, followSymlinks):
log.debug("Starting new folder chooser at %s" % folder)
self.folder = folder
self.includeHidden = includeHidden
- self.folderGroupName = folderGroupName
self.compareIgnoreMtime = compareIgnoreMtime
self.followSymlinks = followSymlinks
@@ -315,8 +314,6 @@
)
self.folderChooser = tree.get_widget("filechooserbutton1")
self.folderChooser.set_current_folder_uri(self.folder)
- self.folderEntry = tree.get_widget("entry1")
- self.folderEntry.set_text(self.folderGroupName)
self.hiddenCb = tree.get_widget("hidden")
self.hiddenCb.set_active(includeHidden)
self.mtimeCb = tree.get_widget("ignoreMtime")
@@ -330,37 +327,22 @@
def on_response(self, dialog, response_id):
if response_id == gtk.RESPONSE_OK:
- if self.folderEntry.get_text() == "":
- #stop this dialog from closing, and show a warning to the
- #user indicating that the folder must be named
- warning = gtk.MessageDialog(
- parent=dialog,
- flags=gtk.DIALOG_MODAL,
- type=gtk.MESSAGE_WARNING,
- buttons=gtk.BUTTONS_OK,
- message_format=_("Please Enter a Folder Name"))
- warning.format_secondary_text(_("All folders require a descriptive name. To name a folder enter its name where indicated"))
- warning.run()
- warning.destroy()
- dialog.emit_stop_by_name("response")
- else:
- self.folderGroupName = self.folderEntry.get_text()
- #I think the logic here is ugly, and rather non-symmetric.
- #Basically get_uri returns the selected folder uri, OR the current
- #(read: same as get_folder_uri) uri if none is selected.
- #The non symmetric part is that when we pre-load the filechooser
- #at time+1, we call set_current_folder_uri() #because the set_uri()
- #call doesnt make sense in a folderchooser where you want
- #to be *inside* the selected folder and not one below it.
- selected = self.folderChooser.get_uri()
- self.folder = Vfs.uri_make_canonical(selected)
- log.debug("Folderconfig returned %s (non-canonical: %s)" % (self.folder,selected))
- self.includeHidden = self.hiddenCb.get_active()
- self.compareIgnoreMtime = self.mtimeCb.get_active()
- self.followSymlinks = self.followSymlinksCb.get_active()
+ #I think the logic here is ugly, and rather non-symmetric.
+ #Basically get_uri returns the selected folder uri, OR the current
+ #(read: same as get_folder_uri) uri if none is selected.
+ #The non symmetric part is that when we pre-load the filechooser
+ #at time+1, we call set_current_folder_uri() #because the set_uri()
+ #call doesnt make sense in a folderchooser where you want
+ #to be *inside* the selected folder and not one below it.
+ selected = self.folderChooser.get_uri()
+ self.folder = Vfs.uri_make_canonical(selected)
+ log.debug("Folderconfig returned %s (non-canonical: %s)" % (self.folder,selected))
+ self.includeHidden = self.hiddenCb.get_active()
+ self.compareIgnoreMtime = self.mtimeCb.get_active()
+ self.followSymlinks = self.followSymlinksCb.get_active()
def show_dialog(self):
self.dlg.show_all()
self.dlg.run()
self.dlg.destroy()
- return self.folder, self.folderGroupName, self.includeHidden, self.compareIgnoreMtime, self.followSymlinks
+ return self.folder, self.includeHidden, self.compareIgnoreMtime, self.followSymlinks
Modified: trunk/conduit/modules/FileModule/FileModule.py
==============================================================================
--- /trunk/conduit/modules/FileModule/FileModule.py (original)
+++ trunk/conduit/modules/FileModule/FileModule.py Wed Jul 9 16:07:11 2008
@@ -13,8 +13,8 @@
import conduit.Vfs as Vfs
MODULES = {
- "FileSource" : { "type": "dataprovider" },
- "FolderTwoWay" : { "type": "dataprovider" },
+ "FileSource" : { "type": "dataprovider" },
+ "FolderTwoWay" : { "type": "dataprovider" },
"RemovableDeviceFactory" : { "type": "dataprovider-factory" }
}
@@ -89,34 +89,35 @@
def configure(self, window):
Utils.dataprovider_add_dir_to_path(__file__, "")
import FileConfiguration
- f = FileConfiguration._FolderTwoWayConfigurator(window, self.folder, self.folderGroupName,
- self.includeHidden, self.compareIgnoreMtime, self.followSymlinks)
- self.folder, self.folderGroupName, self.includeHidden, self.compareIgnoreMtime, self.followSymlinks = f.show_dialog()
+ f = FileConfiguration._FolderTwoWayConfigurator(
+ window,
+ self.folder,
+ self.includeHidden,
+ self.compareIgnoreMtime,
+ self.followSymlinks)
+ self.folder, self.includeHidden, self.compareIgnoreMtime, self.followSymlinks = f.show_dialog()
self._monitor_folder()
def set_configuration(self, config):
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.followSymlinks = config.get("followSymlinks", self.DEFAULT_FOLLOW_SYMLINKS)
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,
"compareIgnoreMtime" : self.compareIgnoreMtime,
"followSymlinks" : self.followSymlinks
}
def get_UID(self):
- return "%s:%s" % (self.folder, self.folderGroupName)
+ return self.folder
def get_name(self):
- return self.folderGroupName
+ return Vfs.uri_get_filename(self.folder)
def _monitor_folder(self):
if self._monitor_folder_id != None:
@@ -151,7 +152,6 @@
"_udi_" : udi
}
if name:
- info["DEFAULT_GROUP"] = name
info["_name_"] = name
klass = type(
@@ -167,7 +167,7 @@
the folder and the udi to allow multiple preconfigured groups per
usb key
"""
- VolumeFactory.VolumeFactory.emit_added(self,
+ return VolumeFactory.VolumeFactory.emit_added(self,
klass,
initargs,
category,
@@ -180,10 +180,18 @@
if prop2.has_key("storage.removable") and prop2["storage.removable"] == True:
mount,label = self._get_device_info(props)
log.info("Detected removable volume %s %s" % (label,mount))
+
+ #short circuit the logic here to test if this is a volume being
+ #unmounted. Its still interesting, we just dont make a
+ #klass for it
+ if udi in self._volumes and not mount:
+ log.debug("This is a FileModule being removed")
+ del(self._volumes[udi])
+ return True
+
#check for the presence of a mount/.conduit group file
#which describe the folder sync groups, and their names,
mountUri = "file://%s" % mount
-
try:
groups = FileDataProvider.read_removable_volume_group_file(mountUri)
except Exception, e:
Modified: trunk/conduit/modules/FileModule/config.glade
==============================================================================
--- /trunk/conduit/modules/FileModule/config.glade (original)
+++ trunk/conduit/modules/FileModule/config.glade Wed Jul 9 16:07:11 2008
@@ -233,28 +233,6 @@
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
- <widget class="GtkLabel" id="label82">
- <property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Folder Name:</property>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- </packing>
- </child>
- <child>
- <widget class="GtkEntry" id="entry1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
<widget class="GtkLabel" id="label83">
<property name="visible">True</property>
<property name="xalign">0</property>
@@ -263,7 +241,6 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">2</property>
</packing>
</child>
<child>
@@ -274,7 +251,7 @@
<property name="title" translatable="yes">Select A Folder</property>
</widget>
<packing>
- <property name="position">3</property>
+ <property name="position">1</property>
</packing>
</child>
<child>
@@ -343,7 +320,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="position">4</property>
+ <property name="position">2</property>
</packing>
</child>
</widget>
Modified: trunk/conduit/modules/TestModule.py
==============================================================================
--- /trunk/conduit/modules/TestModule.py (original)
+++ trunk/conduit/modules/TestModule.py Wed Jul 9 16:07:11 2008
@@ -687,9 +687,6 @@
raise Exceptions.SynchronizeConflictError(conduit.datatypes.COMPARISON_UNKNOWN, data, newData)
return newData.get_rid()
- def get_UID(self):
- return Utils.random_string()
-
class TestConverter(TypeConverter.Converter):
def __init__(self):
self.conversions = {
@@ -714,6 +711,7 @@
return t
class TestFactory(DataProvider.DataProviderFactory):
+ _configurable_ = True
def __init__(self, **kwargs):
DataProvider.DataProviderFactory.__init__(self, **kwargs)
@@ -754,6 +752,17 @@
self.emit_removed(self.key1)
return False
+ def setup_configuration_widget(self):
+ import gtk
+ vb = gtk.VBox(2)
+ vb.pack_start(gtk.Label("Hello World"))
+ self.entry = gtk.Entry()
+ vb.pack_start(self.entry)
+ return vb
+
+ def save_configuration(self, ok):
+ log.debug("OK: %s Message: %s" % (ok,self.entry.get_text()))
+
class TestFactoryRemoval(DataProvider.DataProviderFactory):
"""
Repeatedly add/remove a DP/Category to stress test framework
Added: trunk/test/python-tests/TestCoreConflict.py
==============================================================================
--- (empty file)
+++ trunk/test/python-tests/TestCoreConflict.py Wed Jul 9 16:07:11 2008
@@ -0,0 +1,41 @@
+#common sets up the conduit environment
+from common import *
+
+import conduit.Conflict as Conflict
+
+test = SimpleSyncTest()
+test.prepare(
+ test.get_dataprovider("TestSource"),
+ test.get_dataprovider("TestConflict")
+ )
+test.set_two_way_policy({"conflict":"ask","deleted":"skip"})
+config = {}
+config["numData"] = 1
+test.configure(source=config)
+test.set_two_way_sync(False)
+
+test.sync(debug=False)
+aborted,errored,conflicted = test.get_sync_result()
+ok("Conflict trapped", conflicted == True and aborted == False)
+
+c = test.conduit._conflicts
+ok("One Conflict", len(c) == 1)
+
+test.sync(debug=False)
+aborted,errored,conflicted = test.get_sync_result()
+ok("Conflict trapped again", conflicted == True and aborted == False)
+
+c = test.conduit._conflicts
+ok("Just One Conflict", len(c) == 1)
+
+#get the one conflict
+for h,conf in c.items():
+ break
+
+resolved = conf.resolve(Conflict.CONFLICT_SKIP)
+ok("Didnt resolve when resolution is skip", resolved == False and len(test.conduit._conflicts) == 1)
+
+resolved = conf.resolve(Conflict.CONFLICT_COPY_SOURCE_TO_SINK)
+ok("Resolved, source -> sink", resolved == True and len(test.conduit._conflicts) == 0)
+
+finished()
\ No newline at end of file
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]