conduit r1690 - in trunk: . conduit conduit/dataproviders conduit/datatypes conduit/gtkui conduit/hildonui conduit/modules conduit/modules/GoogleModule conduit/platform conduit/utils data help help/C/figures help/es po scripts test/python-tests



Author: jstowers
Date: Fri Aug 29 23:37:30 2008
New Revision: 1690
URL: http://svn.gnome.org/viewvc/conduit?rev=1690&view=rev

Log:
Merge from trunk

Added:
   trunk/conduit/Knowledge.py
   trunk/conduit/gtkui/MsgArea.py
   trunk/test/python-tests/TestDataProviderGoogleCalendar.py
      - copied, changed from r1689, /trunk/test/python-tests/TestDataProviderGoogle.py
   trunk/test/python-tests/TestDataProviderYoutube.py
Removed:
   trunk/test/python-tests/TestDataProviderGoogle.py
Modified:
   trunk/   (props changed)
   trunk/ChangeLog
   trunk/NEWS
   trunk/conduit/Conduit.py
   trunk/conduit/Makefile.am
   trunk/conduit/Module.py
   trunk/conduit/ModuleWrapper.py
   trunk/conduit/Settings.py
   trunk/conduit/SyncSet.py
   trunk/conduit/Synchronization.py
   trunk/conduit/Vfs.py
   trunk/conduit/Web.py
   trunk/conduit/__init__.py
   trunk/conduit/dataproviders/Image.py
   trunk/conduit/datatypes/Photo.py
   trunk/conduit/gtkui/Canvas.py
   trunk/conduit/gtkui/Makefile.am
   trunk/conduit/gtkui/UI.py
   trunk/conduit/hildonui/Canvas.py
   trunk/conduit/modules/GoogleModule/GoogleModule.py
   trunk/conduit/modules/TestModule.py
   trunk/conduit/platform/WebBrowserWebkit.py
   trunk/conduit/utils/__init__.py
   trunk/configure.ac
   trunk/data/conduit.glade
   trunk/help/C/figures/conduit-dp.png   (contents, props changed)
   trunk/help/C/figures/conduit-login.png   (contents, props changed)
   trunk/help/Makefile.am
   trunk/help/es/es.po
   trunk/po/ChangeLog
   trunk/po/gl.po
   trunk/scripts/release.sh
   trunk/test/python-tests/TestCoreSettings.py
   trunk/test/python-tests/TestDataProviderFacebook.py
   trunk/test/python-tests/TestSyncTomboyiPod.py
   trunk/test/python-tests/common.py

Modified: trunk/NEWS
==============================================================================
--- trunk/NEWS	(original)
+++ trunk/NEWS	Fri Aug 29 23:37:30 2008
@@ -1,3 +1,6 @@
+NEW in 0.3.14:
+==============
+
 NEW in 0.3.13:
 ==============
 * Much inproved RSS feed enclosure support, thanks to the use of
@@ -7,6 +10,34 @@
 * Conflict inprovements. Duplicate conflicts should no longer be shown
   in the UI.
 * The UI should will now reuse your theme colors
+* Finer borders and smaller rectangles to match closer with GNOME style
+* Usage hints are now displayed at the bottom of the window, indicating steps
+  that should be taken after one adds items to the Canvas
+* A number of example Conduits can be created from the File->Examples menu
+
+* Fixed #516646, Welcome message remains on canvas after dataprovider added (John Stowers)
+* Fixed #517877, Scrolling in Canvas Pane not working correctly (John Stowers)
+* Fixed #525259, the canvas pane become a mess for remote startup (John Stowers)
+* Fixed #528221, Evo to iPod synchronisation fails (John Carr)
+* Fixed #530768, update conflicts after a new sync (John Stowers)
+* Fixed #543366, Replacement of modified items could be more efficient (Manuel J. Garrido)
+* Fixed #543534, The "Conduit Manual" window crashes. (John Stowers)
+* Fixed #543685, Feedmodule should use feedparser (John Stowers)
+* Fixed #543738, Patch: Add data-type for bookmarks. (Andrew Stormont)
+* Fixed #544712, "Add directory" button throws error when adding "Files" source (John Stowers)
+* Fixed #545129, GoogleContact : Crash to push a contact with not email (Roumano)
+* Fixed #545728, Picasa doesn't delete photos in one way syncs (Manuel J. Garrido)
+* Fixed #509702, conduit segfaults when quitting (John Stowers)
+
+Translations:
+* Updated gl: Ignacio Casal Quinteiro
+* Updated es: Jorge Gonzalez  <jorgegonz svn gnome org>
+* Updated ar: Djihed Afifi <djihed gmail com>
+* Updated pt_BR: Leonardo Ferreira Fontenelle
+* Updated pt: Duarte Loreto
+
+Help Manual Translations:
+* Updated es: Jorge Gonzalez, Jorge Gonzalez  <jorgegonz svn gnome org>
 
 NEW in 0.3.12:
 ==============
@@ -213,3 +244,5 @@
 None
 
 None
+
+None

Modified: trunk/conduit/Conduit.py
==============================================================================
--- trunk/conduit/Conduit.py	(original)
+++ trunk/conduit/Conduit.py	Fri Aug 29 23:37:30 2008
@@ -176,6 +176,16 @@
         """
         return self.syncManager.sync_in_progress(self)
         
+    def can_sync(self):
+        """
+        Returns True if this conduit can be synchronized. It must have a
+        source and a sync, that are not pending
+        """
+        return  self.datasource != None \
+                and len(self.datasinks) > 0 \
+                and not self.datasource.is_pending() \
+                and not self.datasinks[0].is_pending()
+        
     def get_dataproviders_by_key(self, key):
         """
         Use list comprehension to return all dp's with a given key

Added: trunk/conduit/Knowledge.py
==============================================================================
--- (empty file)
+++ trunk/conduit/Knowledge.py	Fri Aug 29 23:37:30 2008
@@ -0,0 +1,28 @@
+HINT_BLANK_CANVAS           = -100
+HINT_ADD_DATAPROVIDER       = -101
+HINT_RIGHT_CLICK_CONFIGURE  = -102
+
+HINT_TEXT = {
+    HINT_BLANK_CANVAS:(             "What Do You Want to Synchronize?",
+                                    "Drag and Drop a Data Provider on the Canvas",
+                                    True),
+    HINT_ADD_DATAPROVIDER:(         "Synchronization Group Created",
+                                    "Add Another Data Provider to the Group to Synchronize it",
+                                    False),
+    HINT_RIGHT_CLICK_CONFIGURE:(    "You Are Now Ready to Synchronize",
+                                    "Right Click on Group for Options",
+                                    False)
+}
+
+PRECONFIGIRED_CONDUITS = {
+    #source,sinc                            #comment                        
+        #twoway
+    ("FolderTwoWay","FolderTwoWay"):(       "Synchronize Two Folders",      
+        True    ),
+    ("FolderTwoWay","BoxDotNetTwoWay"):(    "Backup Folder to Box.net",       
+        False   ),
+    ("FSpotDbusTwoWay","FlickrTwoWay"):(    "Synchronize Tagged F-Spot Photos to Flickr",       
+        False   )
+}
+
+

Modified: trunk/conduit/Makefile.am
==============================================================================
--- trunk/conduit/Makefile.am	(original)
+++ trunk/conduit/Makefile.am	Fri Aug 29 23:37:30 2008
@@ -16,6 +16,7 @@
 	Exceptions.py \
 	Globals.py \
 	__init__.py \
+	Knowledge.py \
 	Logging.py \
 	Main.py \
 	MappingDB.py \

Modified: trunk/conduit/Module.py
==============================================================================
--- trunk/conduit/Module.py	(original)
+++ trunk/conduit/Module.py	Fri Aug 29 23:37:30 2008
@@ -14,6 +14,7 @@
 
 import conduit.dataproviders
 import conduit.ModuleWrapper as ModuleWrapper
+import conduit.Knowledge as Knowledge
 import conduit.Vfs as Vfs
 
 class ModuleManager(gobject.GObject):
@@ -271,6 +272,23 @@
         for i in self.moduleWrappers.values():
             if i.module_type == type_filter:
                 i.instantiate_module()
+                
+    def list_preconfigured_conduits(self):
+        #strip the keys back to the classnames, because the preconfigured dps
+        #are described in terms of classes, not instances (keys)
+        names = {}
+        for key in self.moduleWrappers:
+            names[key.split(":")[0]] = key
+            
+        #for a preconfigured conduit to be available, both the 
+        #source and sink must be loaded
+        found = []
+        for (source,sink),(comment,twoway) in Knowledge.PRECONFIGIRED_CONDUITS.items():
+            if source in names and sink in names:
+                #return key,key,desc,two-way
+                found.append( (names[source],names[sink],comment,twoway) )
+
+        return found
 
     def quit(self):
         for dpf in self.dataproviderFactories:

Modified: trunk/conduit/ModuleWrapper.py
==============================================================================
--- trunk/conduit/ModuleWrapper.py	(original)
+++ trunk/conduit/ModuleWrapper.py	Fri Aug 29 23:37:30 2008
@@ -256,6 +256,9 @@
         
     def instantiate_module(self):
         self.module = self.klass(*self.initargs)
+        
+    def is_pending(self):
+        return self.module == None
 
 class PendingDataproviderWrapper(ModuleWrapper):
     def __init__(self, key):

Modified: trunk/conduit/Settings.py
==============================================================================
--- trunk/conduit/Settings.py	(original)
+++ trunk/conduit/Settings.py	Fri Aug 29 23:37:30 2008
@@ -79,7 +79,7 @@
         'gui_initial_canvas_height' :   450,            #Reduce to ~300 for eepc, etc
         'gui_initial_canvas_width'  :   450,            #Reduce for eepc, etc
         'gui_use_rgba_colormap'     :   False,          #Seems to corrupt gtkmozembed on some systems
-        'web_login_browser'         :   "gtkmozembed"   #When loggin into websites use: "system","gtkmozembed","webkit","gtkhtml"
+        'gui_show_hints'            :   True,           #Show message area hints in the Conduit GUI
     }
         
     def __init__(self, **kwargs):

Modified: trunk/conduit/SyncSet.py
==============================================================================
--- trunk/conduit/SyncSet.py	(original)
+++ trunk/conduit/SyncSet.py	Fri Aug 29 23:37:30 2008
@@ -52,18 +52,20 @@
             if dp.module:
                 dp.module.uninitialize()
                 
-    def _restore_dataprovider(self, cond, wrapperKey, dpName, dpxml, trySourceFirst):
+    def _restore_dataprovider(self, cond, wrapperKey, dpName="", dpxml="", trySourceFirst=True):
         """
         Adds the dataprovider back onto the canvas at the specifed
         location and configures it with the given settings
         """
         log.debug("Restoring %s to (source=%s)" % (wrapperKey,trySourceFirst))
         wrapper = self.moduleManager.get_module_wrapper_with_instance(wrapperKey)
-        wrapper.set_name(dpName)
+        if dpName:
+            wrapper.set_name(dpName)
         if wrapper is not None:
-            for i in dpxml.childNodes:
-                if i.nodeType == i.ELEMENT_NODE and i.localName == "configuration":
-                    wrapper.set_configuration_xml(xmltext=i.toxml())
+            if dpxml:
+                for i in dpxml.childNodes:
+                    if i.nodeType == i.ELEMENT_NODE and i.localName == "configuration":
+                        wrapper.set_configuration_xml(xmltext=i.toxml())
         cond.add_dataprovider(wrapper, trySourceFirst)
 
     def on_dataprovider_available_unavailable(self, loader, dpw):
@@ -88,6 +90,14 @@
         from the main loop on an idle handler
         """
         gobject.idle_add(gobject.GObject.emit,self,*args)
+        
+    def create_preconfigured_conduit(self, sourceKey, sinkKey, twoway):
+        cond = Conduit.Conduit(self.syncManager)
+        self.add_conduit(cond)
+        if twoway == True:
+            cond.enable_two_way_sync()
+        self._restore_dataprovider(cond, sourceKey, trySourceFirst=True)
+        self._restore_dataprovider(cond, sinkKey, trySourceFirst=False)
 
     def add_conduit(self, cond):
         self.conduits.append(cond)

Modified: trunk/conduit/Synchronization.py
==============================================================================
--- trunk/conduit/Synchronization.py	(original)
+++ trunk/conduit/Synchronization.py	Fri Aug 29 23:37:30 2008
@@ -4,7 +4,7 @@
 Copyright: John Stowers, 2006
 License: GPLv2
 """
-
+import thread
 import traceback
 import threading
 import logging
@@ -208,7 +208,8 @@
 
     def __init__(self):
         threading.Thread.__init__(self)
-
+        log.debug("Created thread %s (thread: %s)" % (self,thread.get_ident()))
+        
         #Python threads are not cancellable. Hopefully this will be fixed
         #in Python 3000
         self.cancelled = False
@@ -620,6 +621,7 @@
         exception if the synchronisation state machine does not complete, in
         some way, without success.
         """
+        log.debug("Started thread %s (thread: %s)" % (self,thread.get_ident()))
         try:
             log.debug("Sync %s beginning. Slow: %s, Twoway: %s" % (
                                     self,
@@ -809,6 +811,7 @@
         steps, setting its status at the appropriate time and performing
         nicely in the case of errors. 
         """
+        log.debug("Started thread %s (thread: %s)" % (self,thread.get_ident()))
         try:
             log.debug("Refresh %s beginning" % self)
             self.cond.emit("sync-started")
@@ -854,6 +857,7 @@
         self.setName("%s functions" % len(self.functions))
 
     def run(self):
+        log.debug("Started thread %s (thread: %s)" % (self,thread.get_ident()))
         try:
             #FIXME: Set the status text on the dataprovider
             for f in self.functions:

Modified: trunk/conduit/Vfs.py
==============================================================================
--- trunk/conduit/Vfs.py	(original)
+++ trunk/conduit/Vfs.py	Fri Aug 29 23:37:30 2008
@@ -13,11 +13,25 @@
 #
 # URI Functions
 #
+def _ensure_type(arg):
+    """
+    Ensures that arg is str or unicode, returns it as str.
+    
+    Gnomevfs does not seem to play well with unicode, kill it, and this
+    could probbably be done better with a decorator
+    """
+    if type(arg) == str:
+        return arg
+    elif type(arg) == unicode:
+        return str(arg)
+    else:
+        raise Exception("URIs must be str or unicode (was %s)" % type(arg))
+
 def uri_is_valid(uri):
     """
     (weakly) checks if a uri is valid by looking for a scheme seperator
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return uri[0] != "/" and uri.find("://") != -1
     
 def uri_join(first, *rest):
@@ -51,7 +65,7 @@
     """
     Opens a gnomevfs or xdg compatible uri.
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     APP = "xdg-open"
     os.spawnlp(os.P_NOWAIT, APP, APP, uri)
     
@@ -60,14 +74,14 @@
     @returns: The local path (/foo/bar) for the given URI. Throws a 
     RuntimeError (wtf??) if the uri is not a local one    
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return gnomevfs.get_local_path_from_uri(uri)
     
 def uri_get_volume_root_uri(uri):
     """
     @returns: The root path of the volume at the given uri, or None
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     try:
         path = uri_to_local_path(uri)
         return VolumeMonitor().volume_get_root_uri(path)
@@ -79,7 +93,7 @@
     @returns: True if the specified uri is on a removable volume, like a USB key
     or removable/mountable disk.
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     scheme = gnomevfs.get_uri_scheme(uri)
     if scheme == "file":
         #FIXME: Unfortunately this approach actually works better than gnomevfs
@@ -98,7 +112,7 @@
     @returns: The filesystem that uri is stored on or None if it cannot
     be determined
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     scheme = gnomevfs.get_uri_scheme(uri)
     if scheme == "file":
         try:
@@ -117,7 +131,7 @@
     Standardizes the format of the uri
     @param uri:an absolute or relative stringified uri. It might have scheme.
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return gnomevfs.make_uri_canonical(uri)
     
 def uri_escape(uri):
@@ -126,7 +140,7 @@
     paths or host names.
     (so '/', '&', '=', ':' and '@' will not be escaped by this function)
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     #FIXME: This function lies, it escapes @
     #return gnomevfs.escape_host_and_path_string(uri)
     import urllib
@@ -136,7 +150,7 @@
     """
     Replace "%xx" escapes by their single-character equivalent.
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     import urllib
     return urllib.unquote(uri)
     
@@ -144,7 +158,7 @@
     """
     Returns the protocol (file, smb, etc) for a URI
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     if uri.rfind("://")==-1:
         return ""
     protocol = uri[:uri.index("://")+3]
@@ -155,14 +169,14 @@
     Method to return the filename of a file. Could use GnomeVFS for this
     is it wasnt so slow
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return uri.split(os.sep)[-1]
 
 def uri_get_filename_and_extension(uri):
     """
     Returns filename,file_extension
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return os.path.splitext(uri_get_filename(uri))
     
 def uri_sanitize_for_filesystem(uri, filesystem=None):
@@ -170,7 +184,7 @@
     Removes illegal characters in uri that cannot be stored on the 
     given filesystem - particuarly fat and ntfs types
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     import string
     
     ILLEGAL_CHARS = {
@@ -199,7 +213,7 @@
     """
     @returns: True if the uri is a folder and not a file
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     info = gnomevfs.get_file_info(uri)
     return info.type == gnomevfs.FILE_TYPE_DIRECTORY
     
@@ -207,14 +221,14 @@
     """
     Formats the uri so it can be displayed to the user (strips passwords, etc)
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     return gnomevfs.format_uri_for_display(uri)
     
 def uri_exists(uri):
     """
     @returns: True if the uri exists
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     try:
         return gnomevfs.exists(gnomevfs.URI(uri)) == 1
     except Exception, err:
@@ -226,7 +240,7 @@
     Makes a directory with the default permissions. Does not catch any
     error
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     gnomevfs.make_directory(
             uri,
             gnomevfs.PERM_USER_ALL | gnomevfs.PERM_GROUP_READ | gnomevfs.PERM_GROUP_EXEC | gnomevfs.PERM_OTHER_READ | gnomevfs.PERM_OTHER_EXEC
@@ -240,7 +254,7 @@
     @param uri: A directory that does not exist
     @type uri: str
     """
-    assert type(uri) == str
+    uri = _ensure_type(uri)
     exists = False
     dirs = []
 

Modified: trunk/conduit/Web.py
==============================================================================
--- trunk/conduit/Web.py	(original)
+++ trunk/conduit/Web.py	Fri Aug 29 23:37:30 2008
@@ -27,7 +27,7 @@
         self.pages = {}
         self.finished = {}
 
-        log.debug("Created Conduit login window")
+        log.debug("Created login window (thread: %s)" % thread.get_ident())
 
     def _on_window_closed(self, *args):
         for url in self.pages.keys():
@@ -38,10 +38,10 @@
         self._delete_page(url)
             
     def _on_open_uri(self, *args):
-        log.debug("LINK CLICKED (thread: %s)" % thread.get_ident())
+        log.debug("Link clicked (thread: %s)" % thread.get_ident())
 
     def _delete_page(self, url):
-        log.debug("DELETE PAGE (thread: %s)" % thread.get_ident())
+        log.debug("Delete page (thread: %s)" % thread.get_ident())
         #get the original objects
         browser = self.pages[url]
         browserWidget = browser.widget()
@@ -50,20 +50,26 @@
         #remove the page and any refs
         idx = self.notebook.page_num(browserWidget)
         self.notebook.remove_page(idx)
+        browserWidget.destroy()
         del(self.pages[url])
 
         if self.notebook.get_n_pages() == 0:
-            self.window.hide_all()
+            self.window.hide()
 
         #notify 
         self.finished[url] = True
 
-    def _create_page(self, name, url, browserImplKlass):
-        log.debug("CREATE PAGE: %s (thread: %s)" % (url,thread.get_ident()))
+    def _create_page(self, name, url, browserName):
+        log.debug("Create page: %s (thread: %s)" % (url,thread.get_ident()))
         if url in self.pages:
             return False
 
         import gtk
+        if browserName == "gtkmozembed":
+            import conduit.platform.WebBrowserMozilla as WebBrowserImpl
+        elif browserName == "webkit":
+            import conduit.platform.WebBrowserWebkit as WebBrowserImpl
+
         #lazy init to save a bit of time
         if self.window == None:
             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
@@ -78,9 +84,9 @@
         self.window.show_all()
 
         #create object and connect signals
-        browser = browserImplKlass()
+        browser = WebBrowserImpl.WebBrowserImpl()
         browser.connect("open_uri",self._on_open_uri)
-        
+
         #create the tab label
         tab_button = gtk.Button()
         tab_button.connect('clicked', self._on_tab_close_clicked, url)
@@ -99,15 +105,16 @@
 
         #add to notebook
         browserWidget = browser.widget()
+        browserWidget.show_now()
         self.notebook.append_page(child=browserWidget, tab_label=tab_box)
+        self.notebook.show_all()
         self.pages[url] = browser
 
-        browserWidget.show_now()
         browser.load_url(url)
         return False
 
     def _raise_page(self, url):
-        log.debug("RAISE PAGE (thread: %s)" % thread.get_ident())
+        log.debug("Raise page (thread: %s)" % thread.get_ident())
         self.window.show_all()
 
         #get the original objects
@@ -123,12 +130,12 @@
         return False
 
     def wait_for_login(self, name, url, **kwargs):
-        log.debug("LOGIN (thread: %s)" % thread.get_ident())
+        log.debug("Wait for login (thread: %s)" % thread.get_ident())
     
         if url in self.pages:
             gobject.idle_add(self._raise_page, url)
         else:
-            gobject.idle_add(self._create_page, name, url, kwargs["browserImplKlass"])
+            gobject.idle_add(self._create_page, name, url, kwargs["browserName"])
             self.finished[url] = False
 
         while not self.finished[url] and not conduit.GLOBALS.cancelled:
@@ -136,7 +143,7 @@
             #and gtk.main needs to iterate
             time.sleep(0.1)
 
-        log.debug("FINISHED LOGIN (thread: %s)" % thread.get_ident())
+        log.debug("Finished login (thread: %s)" % thread.get_ident())
 
         #call the test function
         testFunc = kwargs.get("login_function",None)
@@ -151,23 +158,19 @@
     either the system browser, or conduits own one.
     """
     def __init__(self, name, url, **kwargs):
-        browser = kwargs.get("browser",conduit.GLOBALS.settings.get("web_login_browser"))
-        log.info("Logging in using browser: %s" % browser)
+        browser = kwargs.get("browser",conduit.BROWSER_IMPL)
+        log.info("Logging in using browser: %s (thread: %s)" % (browser,thread.get_ident()))
 
         #instantiate the browser
         if browser == "system":
             login = WebBrowserSystem.WebBrowserImpl()
         else:
             try:
-                if browser == "gtkmozembed":
-                    from conduit.platform.WebBrowserMozilla import WebBrowserImpl
-                elif browser == "webkit":
-                    from conduit.platform.WebBrowserWebkit import WebBrowserImpl
-                else:
+                if browser not in ("gtkmozembed","webkit"):
                     log.warn("Unknown browser type")
                     return
 
-                kwargs["browserImplKlass"] = WebBrowserImpl
+                kwargs["browserName"] = browser
                 login = LoginWindow()
 
             except ImportError:

Modified: trunk/conduit/__init__.py
==============================================================================
--- trunk/conduit/__init__.py	(original)
+++ trunk/conduit/__init__.py	Fri Aug 29 23:37:30 2008
@@ -47,13 +47,14 @@
     if not PYTHONDIR in sys.path:
         sys.path.insert(0, PYTHONDIR)
 else:
-    VERSION =                   "0.3.13"
+    VERSION =                   "0.3.14"
     LOCALE_DIR =                os.path.join(DIRECTORY, "po")
     SHARED_DATA_DIR =           os.path.join(DIRECTORY, "data")
     GLADE_FILE =                os.path.join(DIRECTORY, "data","conduit.glade")
     SHARED_MODULE_DIR =         os.path.join(DIRECTORY, "conduit", "modules")
-    SETTINGS_IMPL =             "GConf"
-    FILE_IMPL =                 "GnomeVfs"
+    FILE_IMPL =                 "GnomeVfs"      #{GnomeVfs, GIO}
+    BROWSER_IMPL =              "gtkmozembed"   #{gtkmozembed, webkit, system}
+    SETTINGS_IMPL =             "GConf"         #{GConf,Python}
 
 import Globals
 GLOBALS = Globals.Globals()

Modified: trunk/conduit/dataproviders/Image.py
==============================================================================
--- trunk/conduit/dataproviders/Image.py	(original)
+++ trunk/conduit/dataproviders/Image.py	Fri Aug 29 23:37:30 2008
@@ -5,6 +5,7 @@
 import conduit.Exceptions as Exceptions
 import conduit.datatypes.File as File
 import conduit.dataproviders.DataProvider as DataProvider
+from conduit.datatypes import Rid
 
 class UploadInfo:
     """
@@ -86,7 +87,7 @@
         """
         Replace a photo with a new version
         """
-        return id
+        return Rid(uid=id)
         
     def _get_photo_formats (self):
         """
@@ -172,4 +173,45 @@
         DataProvider.DataSource.__init__(self)
         ImageSink.__init__(self)
 
+    def put(self, photo, overwrite, LUID=None):
+        """
+        Accepts a vfs file. Must be made local.
+        I also store a md5 of the photos uri to check for duplicates
+        """
+        DataProvider.DataSink.put(self, photo, overwrite, LUID)
+
+        originalName = photo.get_filename()
+        #Gets the local URI (/foo/bar). If this is a remote file then
+        #it is first transferred to the local filesystem
+        photoURI = photo.get_local_uri()
+        mimeType = photo.get_mimetype()
+        tags = photo.get_tags ()
+        caption = photo.get_caption()
+
+        uploadInfo = UploadInfo(photoURI, mimeType, originalName, tags, caption)
+
+        if overwrite and LUID:
+            rid = self._replace_photo(LUID, uploadInfo)
+        else:
+            if LUID and self._get_photo_info(LUID):
+                remotePhoto = self.get(LUID)
+                comp = photo.compare(remotePhoto, False)
+                log.debug("Compared %s with %s. Result = %s" % 
+                          (photo.get_filename(),remotePhoto.get_filename(),comp))
+
+                if LUID != None and comp == conduit.datatypes.COMPARISON_NEWER:
+                    rid = self._replace_photo(LUID, uploadInfo)
+                elif comp == conduit.datatypes.COMPARISON_EQUAL:                    
+                    rid = remotePhoto.get_rid()
+                else:
+                    raise Exceptions.SynchronizeConflictError(comp, photo, remotePhoto)
+            else:
+                log.debug("Uploading Photo URI = %s, Mimetype = %s, Original Name = %s" %
+                          (photoURI, mimeType, originalName))
+                rid = self._upload_photo (uploadInfo)
+
+        if not rid:
+            raise Exceptions.SyncronizeError("Error putting/updating photo")
+        else:
+            return rid
 

Modified: trunk/conduit/datatypes/Photo.py
==============================================================================
--- trunk/conduit/datatypes/Photo.py	(original)
+++ trunk/conduit/datatypes/Photo.py	Fri Aug 29 23:37:30 2008
@@ -2,6 +2,8 @@
 
 import conduit.datatypes.File as File
 import conduit.utils as Utils
+import logging
+log = logging.getLogger("datatypes.Photo")
 
 PRESET_ENCODINGS = {
     "jpeg":{'formats':'image/jpeg','default-format':'image/jpeg'},
@@ -29,6 +31,27 @@
         self.pb = None
         self._caption = None
 
+    def compare(self, B, sizeOnly=False):
+        if sizeOnly:
+            return File.File.compare(self, B, True)
+
+        meTime = self.get_mtime()
+        bTime = B.get_mtime()
+        log.debug("Comparing %s (MTIME: %s) with %s (MTIME: %s)" % (self.URI, meTime, B.URI, bTime))
+        if meTime and bTime and (meTime != bTime):
+            if meTime > bTime:    #Am I newer than B
+                return conduit.datatypes.COMPARISON_NEWER        
+            else:
+                return conduit.datatypes.COMPARISON_OLDER
+
+        meHash = self.get_hash()
+        bHash = B.get_hash()
+        log.debug("Comparing %s (HASH: %s) with %s (HASH: %s)" % (self.URI, meHash, B.URI, bHash))
+        if (meHash == bHash):
+            return conduit.datatypes.COMPARISON_EQUAL
+        else:
+            return conduit.datatypes.COMPARISON_UNKNOWN
+
     def get_photo_pixbuf(self):
         """
         Defer actually getting the pixbuf till as

Modified: trunk/conduit/gtkui/Canvas.py
==============================================================================
--- trunk/conduit/gtkui/Canvas.py	(original)
+++ trunk/conduit/gtkui/Canvas.py	Fri Aug 29 23:37:30 2008
@@ -18,6 +18,7 @@
 
 import conduit.utils as Utils
 import conduit.Conduit as Conduit
+import conduit.Knowledge as Knowledge
 import conduit.gtkui.Tree
 import conduit.gtkui.Util as GtkUtil
 
@@ -25,24 +26,6 @@
 
 class _StyleMixin:
 
-    STYLES = (
-        "fg",
-        "bg",
-        "light",
-        "dark",
-        "mid",
-        "text",
-        "base",
-        "text_aa"
-        )
-    STYLE_STATES = (
-        "normal",
-        "active",
-        "prelight",
-        "selected",
-        "insensitive"
-        )
-        
     def _get_colors_and_state(self, styleName, stateName):
         style = self.get_gtk_style()
         if style:
@@ -164,7 +147,7 @@
     ]
 
     WELCOME_MESSAGE = _("Drag a Data Provider here to continue")
-    def __init__(self, parentWindow, typeConverter, syncManager, dataproviderMenu, conduitMenu):
+    def __init__(self, parentWindow, typeConverter, syncManager, dataproviderMenu, conduitMenu, msg):
         """
         Draws an empty canvas of the appropriate size
         """
@@ -183,6 +166,7 @@
         self.sync_manager = syncManager
         self.typeConverter = typeConverter
         self.parentWindow = parentWindow
+        self.msg = msg
 
         self._setup_popup_menus(dataproviderMenu, conduitMenu)
 
@@ -211,9 +195,46 @@
         
         #Show a friendly welcome message on the canvas the first time the
         #application is launched
-        self.welcomeMessage = None
-        self._show_welcome_message()
+        self.welcome = None
+        self._maybe_show_welcome()
+        
+    def _do_hint(self, msgarea, respid):
+        if respid == Knowledge.HINT_BLANK_CANVAS:
+            new = conduit.GLOBALS.moduleManager.get_module_wrapper_with_instance("FolderTwoWay")
+            self.add_dataprovider_to_canvas(
+                                "FolderTwoWay",
+                                new,
+                                1,1
+                                )
+
+    def _make_hint(self, hint, timeout=4):
+        if Knowledge.HINT_TEXT[hint][2]:
+            buttons = [("Show me",hint)]
+        else:
+            buttons = []
+        h = self.msg.new_from_text_and_icon(
+                            gtk.STOCK_INFO,
+                            Knowledge.HINT_TEXT[hint][0],
+                            Knowledge.HINT_TEXT[hint][1],
+                            buttons=buttons,
+                            timeout=timeout)
+        h.connect("response", self._do_hint)
+        h.show_all()    
         
+    def _show_hint(self, conduitCanvasItem, dataproviderCanvasItem, newItem):
+        if not self.msg:
+            return
+            
+        if not conduit.GLOBALS.settings.get("gui_show_hints"):
+            return
+            
+        if newItem == conduitCanvasItem:
+            self._make_hint(Knowledge.HINT_ADD_DATAPROVIDER)
+        elif newItem == dataproviderCanvasItem:
+            #check if we have a source and a sink
+            if conduitCanvasItem.model.can_sync():
+                self._make_hint(Knowledge.HINT_RIGHT_CLICK_CONFIGURE)
+            
     def _update_for_theme(self, *args):
         if not self.get_gtk_style() or self._changing_style:
             return
@@ -223,8 +244,8 @@
                 "background_color_rgb",
                 self.get_style_color_int_rgb("bg","normal")
                 )
-        if self.welcomeMessage:
-            self.welcomeMessage.set_property(
+        if self.welcome:
+            self.welcome.set_property(
                 "fill_color_rgba",
                 self.get_style_color_int_rgba("text","normal")
                 )
@@ -268,41 +289,41 @@
         conduitPopupXML.signal_autoconnect(self)
         dataproviderPopupXML.signal_autoconnect(self)
 
-    def _show_welcome_message(self):
-        """
-        Adds a friendly welcome message to the canvas.
+    def _delete_welcome(self):
+        idx = self.root.find_child(self.welcome)
+        if idx != -1:
+            self.root.remove_child(idx)
+        self.welcome = None
         
-        Does so only if there are no conduits, otherwise it would just
-        get in the way.
-        """
-        if self.welcomeMessage == None:
-            c_x,c_y,c_w,c_h = self.get_bounds()
-            self.welcomeMessage = goocanvas.Text(  
-                                    x=c_w/2, 
-                                    y=c_w/3, 
-                                    width=3*c_w/5, 
-                                    text=self.WELCOME_MESSAGE, 
-                                    anchor=gtk.ANCHOR_CENTER,
-                                    alignment=pango.ALIGN_CENTER,
-                                    font="Sans 10",
-                                    fill_color_rgba=self.get_style_color_int_rgba("text","normal"),
-                                    )
+    def _create_welcome(self):
+        c_x,c_y,c_w,c_h = self.get_bounds()
+        self.welcome = ConduitCanvasItem(
+                                parent=self.root, 
+                                model=None,
+                                width=c_w
+                                )
 
-        idx = self.root.find_child(self.welcomeMessage)
+    def _maybe_show_welcome(self):
+        """
+        Adds a friendly welcome to the canvas. Only does so only if 
+        there are no conduits, otherwise it would just get in the way.
+        """
         if self.model == None or (self.model != None and self.model.num_conduits() == 0):
-            if idx == -1:
-                self.root.add_child(self.welcomeMessage,-1)
-        else:
-            if idx != -1:
-                self.root.remove_child(idx)
-                self.welcomeMessage = None
+            if self.welcome == None:
+                self._create_welcome()
+            if self.msg and conduit.GLOBALS.settings.get("gui_show_hints"):
+                self._make_hint(Knowledge.HINT_BLANK_CANVAS, timeout=0)
+
+        elif self.welcome:
+            self._delete_welcome()
 
     def _get_child_conduit_canvas_items(self):
         items = []
         for i in range(0, self.root.get_n_children()):
             condItem = self.root.get_child(i)
             if isinstance(condItem, ConduitCanvasItem):
-                items.append(condItem)
+                if condItem != self.welcome:
+                    items.append(condItem)
         return items
 
     def _get_child_dataprovider_canvas_items(self):
@@ -442,7 +463,7 @@
                     log.warn("Error finding item")
         self._remove_overlap()
 
-        self._show_welcome_message()
+        self._maybe_show_welcome()
         c_x,c_y,c_w,c_h = self.get_bounds()
         self.set_bounds(
                     0,
@@ -478,7 +499,7 @@
         conduitAdded.connect("dataprovider-added", self.on_dataprovider_added, conduitCanvasItem)
         conduitAdded.connect("dataprovider-removed", self.on_dataprovider_removed, conduitCanvasItem)
 
-        self._show_welcome_message()
+        self._maybe_show_welcome()
         self.set_bounds(
                     0,
                     0,
@@ -486,6 +507,8 @@
                     self._get_minimum_canvas_size()
                     )
 
+        self._show_hint(conduitCanvasItem, None, conduitCanvasItem)
+
     def on_dataprovider_removed(self, sender, dataproviderRemoved, conduitCanvasItem):
         for item in self._get_child_dataprovider_canvas_items():
             if item.model == dataproviderRemoved:
@@ -509,6 +532,8 @@
         item.connect('button-press-event', self._on_dataprovider_button_press)
         conduitCanvasItem.add_dataprovider_canvas_item(item)
         self._remove_overlap()
+        
+        self._show_hint(conduitCanvasItem, item, item)
 
     def get_sync_set(self):
         return self.model
@@ -616,6 +641,7 @@
         @type y: C{int}
         @returns: The conduit that the dataprovider was added to
         """
+        parent = None
         existing = self.get_item_at(x,y,False)
         c_x,c_y,c_w,c_h = self.get_bounds()
 
@@ -624,19 +650,29 @@
             trySourceFirst = True
         else:
             trySourceFirst = False
-
-        if existing == None:
-            cond = Conduit.Conduit(self.sync_manager)
-            cond.add_dataprovider(dataproviderWrapper, trySourceFirst)
-            self.model.add_conduit(cond)
-
-        else:
+            
+        #recurse up the canvas objects to determine if we have been dropped
+        #inside an existing conduit
+        if existing:
             parent = existing.get_parent()
-            while parent != None and not isinstance(parent, ConduitCanvasItem):
+            while parent != None and not parent == self.welcome and not isinstance(parent, ConduitCanvasItem):
                 parent = parent.get_parent()
+
+        #if we were dropped on the welcome message we first remove that
+        if parent and parent == self.welcome:
+            self._delete_welcome()
+            #ensure a new conduit is created
+            parent = None
+
+        if parent != None:
+            #we were dropped on an existing conduit            
+            parent.model.add_dataprovider(dataproviderWrapper, trySourceFirst)
+            return
             
-            if parent != None:
-                parent.model.add_dataprovider(dataproviderWrapper, trySourceFirst)
+        #create a new conduit
+        cond = Conduit.Conduit(self.sync_manager)
+        cond.add_dataprovider(dataproviderWrapper, trySourceFirst)
+        self.model.add_conduit(cond)
 
     def clear_canvas(self):
         self.model.clear()
@@ -644,7 +680,7 @@
 class DataProviderCanvasItem(_CanvasItem):
 
     WIDGET_WIDTH = 130
-    WIDGET_HEIGHT = 60
+    WIDGET_HEIGHT = 50
     IMAGE_TO_TEXT_PADDING = 5
     PENDING_MESSAGE = "Pending"
     MAX_TEXT_LENGTH = 10
@@ -803,16 +839,17 @@
 
     DIVIDER = False
     FLAT_BOX = True
-    WIDGET_HEIGHT = 100
+    WIDGET_HEIGHT = 63.0
     SIDE_PADDING = 10.0
     LINE_WIDTH = 2.0
 
     def __init__(self, parent, model, width):
         _CanvasItem.__init__(self, parent, model)
 
-        self.model.connect("parameters-changed", self._on_conduit_parameters_changed)
-        self.model.connect("dataprovider-changed", self._on_conduit_dataprovider_changed)
-        self.model.connect("sync-progress", self._on_conduit_progress)
+        if self.model:
+            self.model.connect("parameters-changed", self._on_conduit_parameters_changed)
+            self.model.connect("dataprovider-changed", self._on_conduit_dataprovider_changed)
+            self.model.connect("sync-progress", self._on_conduit_progress)
 
         self.sourceItem = None
         self.sinkDpItems = []
@@ -1151,7 +1188,7 @@
     CONNECTOR_YOFFSET = 20
     CONNECTOR_TEXT_XPADDING = 5
     CONNECTOR_TEXT_YPADDING = 10
-    LINE_WIDTH = 5.0
+    LINE_WIDTH = 4.0
 
     def __init__(self, parent, fromX, fromY, toX, toY, twoway, conversionExists):
         _CanvasItem.__init__(self, parent, None)
@@ -1173,7 +1210,7 @@
                                     line_width=0.0,
                                     **self.get_style_properties("left_end_round")
                                     )
-        points = goocanvas.Points([(self.fromX-6, self.fromY), (self.fromX-7, self.fromY)])
+        points = goocanvas.Points([(self.fromX+3, self.fromY), (self.fromX-5, self.fromY)])
         self.left_end_arrow = goocanvas.Polyline(
                             points=points,
                             line_width=5,
@@ -1186,7 +1223,7 @@
 
         
 
-        points = goocanvas.Points([(self.toX-1, self.toY), (self.toX, self.toY)])
+        points = goocanvas.Points([(self.toX-3, self.toY), (self.toX+3, self.toY)])
         self.right_end = goocanvas.Polyline(
                             points=points,
                             line_width=5,
@@ -1209,7 +1246,7 @@
 
     def _draw_arrow_ends(self):
         #Always draw the right arrow end for the correct width
-        points = goocanvas.Points([(self.toX-1, self.toY), (self.toX, self.toY)])
+        points = goocanvas.Points([(self.toX-3, self.toY), (self.toX+3, self.toY)])
         self.right_end.set_property("points",points)
         #selectively add or remove a rounded left or right arrow
         #remove both

Modified: trunk/conduit/gtkui/Makefile.am
==============================================================================
--- trunk/conduit/gtkui/Makefile.am	(original)
+++ trunk/conduit/gtkui/Makefile.am	Fri Aug 29 23:37:30 2008
@@ -4,6 +4,7 @@
 	ConflictResolver.py \
 	Database.py \
 	__init__.py \
+	MsgArea.py \
 	SimpleConfigurator.py \
 	Tree.py \
 	Util.py \

Added: trunk/conduit/gtkui/MsgArea.py
==============================================================================
--- (empty file)
+++ trunk/conduit/gtkui/MsgArea.py	Fri Aug 29 23:37:30 2008
@@ -0,0 +1,249 @@
+# This file is part of the Hotwire Shell user interface.
+#   
+# Copyright (C) 2007,2008 Colin Walters <walters verbum org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import os, sys, re, logging, string
+
+import gtk, gobject, pango
+
+#from hotssh.hotlib.logutil import log_except
+
+#_logger = logging.getLogger("hotwire.ui.MsgArea")
+
+# This file is a Python translation of gedit/gedit/gedit-message-area.c
+
+class MsgArea(gtk.HBox):
+    __gsignals__ = {
+        "response" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)),                    
+        "close" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
+    }
+
+    def __init__(self, buttons, **kwargs):
+        super(MsgArea, self).__init__(**kwargs)
+        
+        self.__contents = None
+        self.__changing_style = False
+        
+        self.__main_hbox = gtk.HBox(False, 16) # FIXME: use style properties
+        self.__main_hbox.show()
+        self.__main_hbox.set_border_width(8) # FIXME: use style properties
+
+        self.__action_area = gtk.HBox(True, 4); # FIXME: use style properties
+        self.__action_area.show()
+        
+        #Pack the buttons into a VBox so they remain the same height and do 
+        #not expand to be the same size as the Primary + Secondary text
+        vb = gtk.VBox()
+        vb.pack_end(self.__action_area, False, False)
+        
+        self.__main_hbox.pack_end (vb, False, False, 0)
+
+        self.pack_start(self.__main_hbox, True, True, 0)
+
+        self.set_app_paintable(True)
+
+        self.connect("expose-event", self.__paint)
+
+        # Note that we connect to style-set on one of the internal
+        # widgets, not on the message area itself, since gtk does
+        # not deliver any further style-set signals for a widget on
+        # which the style has been forced with gtk_widget_set_style()
+        self.__main_hbox.connect("style-set", self.__on_style_set)
+        
+        self.add_buttons(buttons)       
+
+    def __get_response_data(self, w, create):
+        d = w.get_data('hotwire-msg-area-data')
+        if (d is None) and create:
+            d = {'respid': None}
+            w.set_data('hotwire-msg-area-data', d)
+        return d
+    
+    def __find_button(self, respid):
+        children = self.__actionarea.get_children()
+        for child in children:
+            rd = self.__get_response_data(child, False)
+            if rd is not None and rd['respid'] == respid:
+                return child
+    
+    def __close(self):
+        cancel = self.__find_button(gtk.RESPONSE_CANCEL)
+        if cancel is None: 
+            return
+        self.response(gtk.RESPONSE_CANCEL)
+        
+    def __paint(self, w, event):
+        gtk.Style.paint_flat_box(w.style,
+                                 w.window,
+                                 gtk.STATE_NORMAL,
+                                 gtk.SHADOW_OUT,
+                                 None,
+                                 w,
+                                 "tooltip",
+                                 w.allocation.x + 1,
+                                 w.allocation.y + 1,
+                                 w.allocation.width - 2,
+                                 w.allocation.height - 2)
+    
+        return False
+    
+    def __on_style_set(self, w, style):
+        if self.__changing_style:
+            return
+        # This is a hack needed to use the tooltip background color
+        window = gtk.Window(gtk.WINDOW_POPUP);
+        window.set_name("gtk-tooltip")
+        window.ensure_style()
+        style = window.get_style()
+
+        self.__changing_style = True
+        self.set_style(style)
+        self.__changing_style = False
+
+        window.destroy()
+
+        self.queue_draw()
+        
+    def __get_response_for_widget(self, w):
+        rd = self.__get_response_data(w, False)
+        if rd is None:
+            return gtk.RESPONSE_NONE
+        return rd['respid']
+    
+    def __on_action_widget_activated(self, w):
+        response_id = self.__get_response_for_widget(w)
+        self.response(response_id)
+        
+    def add_action_widget(self, child, respid):
+        rd = self.__get_response_data(child, True)
+        rd['respid'] = respid
+        if not isinstance(child, gtk.Button):
+            raise ValueError("Can only pack buttons as action widgets")
+        child.connect('clicked', self.__on_action_widget_activated)
+        if respid != gtk.RESPONSE_HELP:
+            self.__action_area.pack_start(child, False, False, 0)
+        else:
+            self.__action_area.pack_end(child, False, False, 0)
+            
+    def set_contents(self, contents):
+        self.__contents = contents
+        self.__main_hbox.pack_start(contents, True, True, 0)
+        
+        
+    def add_button(self, btext, respid):
+        button = gtk.Button(stock=btext)
+        button.set_focus_on_click(False)
+        button.set_flags(gtk.CAN_DEFAULT)
+        button.show()
+        self.add_action_widget(button, respid)
+        return button
+    
+    def add_buttons(self, args):
+        #_logger.debug("init buttons: %r", args)
+        for (btext, respid) in args:
+            self.add_button(btext, respid)
+    
+    def set_response_sensitive(self, respid, setting):
+        for child in self.__action_area.get_children():
+            rd = self.__get_response_data(child, False)
+            if rd is not None and rd['respid'] == respid:
+                child.set_sensitive(setting)
+                break
+            
+    def set_default_response(self, respid):
+        for child in self.__action_area.get_children():
+            rd = self.__get_response_data(child, False)
+            if rd is not None and rd['respid'] == respid:
+                child.grab_default()
+                break        
+            
+    def response(self, respid):
+        self.emit('response', respid)
+        
+    def add_stock_button_with_text(self, text, stockid, respid):
+        b = gtk.Button(label=text)
+        b.set_focus_on_click(False)
+        img = gtk.Image()
+        img.set_from_stock(stockid, gtk.ICON_SIZE_BUTTON)
+        b.set_image(img)
+        b.show_all()
+        self.add_action_widget(b, respid)
+        return b
+
+    def set_text_and_icon(self, stockid, primary_text, secondary_text=None):
+        hbox_content = gtk.HBox(False, 8)
+        hbox_content.show()
+
+        image = gtk.Image()
+        image.set_from_stock(stockid, gtk.ICON_SIZE_BUTTON)
+        image.show()
+        hbox_content.pack_start(image, False, False, 0)
+        image.set_alignment(0.5, 0.5)
+    
+        vbox = gtk.VBox(False, 6)
+        vbox.show()
+        hbox_content.pack_start (vbox, True, True, 0)
+    
+        primary_markup = "<b>%s</b>" % (primary_text,)
+        primary_label = gtk.Label(primary_markup)
+        primary_label.show()
+        vbox.pack_start(primary_label, True, True, 0)
+        primary_label.set_use_markup(True)
+        primary_label.set_line_wrap(True)
+        primary_label.set_alignment(0, 0.5)
+        primary_label.set_flags(gtk.CAN_FOCUS)
+        primary_label.set_selectable(True)
+    
+        if secondary_text:
+            secondary_markup = "<small>%s</small>" % (secondary_text,)
+            secondary_label = gtk.Label(secondary_markup)
+            secondary_label.show()
+            vbox.pack_start(secondary_label, True, True, 0)
+            secondary_label.set_flags(gtk.CAN_FOCUS)
+            secondary_label.set_use_markup(True)
+            secondary_label.set_line_wrap(True)
+            secondary_label.set_selectable(True)
+            secondary_label.set_alignment(0, 0.5)
+    
+        self.set_contents(hbox_content)
+
+class MsgAreaController(gtk.HBox):
+    def __init__(self):
+        super(MsgAreaController, self).__init__()
+        
+        self.__msgarea = None
+        
+    def _timeout(self, msgarea):
+        if msgarea == self.__msgarea:
+            self.clear()
+        
+    def clear(self):
+        if self.__msgarea is not None:
+            self.remove(self.__msgarea)
+            self.__msgarea.destroy()
+            self.__msgarea = None
+        
+    def new_from_text_and_icon(self, stockid, primary, secondary=None, buttons=[], timeout=0):
+        self.clear()
+        msgarea = self.__msgarea = MsgArea(buttons)
+        msgarea.set_text_and_icon(stockid, primary, secondary)
+        self.pack_start(msgarea, expand=True)
+        
+        if timeout:
+            gobject.timeout_add(timeout*1000, self._timeout, msgarea)
+            
+        return msgarea

Modified: trunk/conduit/gtkui/UI.py
==============================================================================
--- trunk/conduit/gtkui/UI.py	(original)
+++ trunk/conduit/gtkui/UI.py	Fri Aug 29 23:37:30 2008
@@ -6,6 +6,7 @@
 Copyright: John Stowers, 2006
 License: GPLv2
 """
+import thread
 import gobject
 import gtk, gtk.glade
 import gnome.ui
@@ -20,6 +21,7 @@
 import conduit.Web as Web
 import conduit.Conduit as Conduit
 import conduit.gtkui.Canvas as Canvas
+import conduit.gtkui.MsgArea as MsgArea
 import conduit.gtkui.Tree as Tree
 import conduit.gtkui.ConflictResolver as ConflictResolver
 import conduit.gtkui.Database as Database
@@ -38,7 +40,35 @@
 for module in gtk.glade, gettext:
     module.bindtextdomain('conduit', conduit.LOCALE_DIR)
     module.textdomain('conduit')
-    
+
+class _PreconfiguredConduitMenu(gtk.Menu):
+    def __init__(self):
+        gtk.Menu.__init__(self)
+#        self._items = {}
+#        conduit.GLOBALS.moduleManager.connect("dataprovider-available", self._dp_added)
+#        conduit.GLOBALS.moduleManager.connect("dataprovider-unavailable", self._dp_removed)
+
+        for sok,sik,desc,w in conduit.GLOBALS.moduleManager.list_preconfigured_conduits():
+            item = gtk.MenuItem(desc)
+            item.connect("activate", self._create, sok, sik, w)
+            item.show()
+            self.append(item)
+
+    def set_sync_set(self, syncSet):    
+        self.syncSet = syncSet  
+        
+    def _create(self, menu, sok, sik, w):
+        self.syncSet.create_preconfigured_conduit(sok,sik,w)
+        
+    def _dp_added(self, manager, dpw):
+        item = gtk.MenuItem(dpw.get_key())
+        self._items[dpw] = item
+        self.append(item)
+        item.show()
+        
+    def _dp_removed(self, manager, dpw):
+        self.remove(self._items[dpw])
+
 class MainWindow:
     """
     The main conduit window.
@@ -104,14 +134,17 @@
         #Configure canvas
         self.canvasSW = self.widgets.get_widget("canvasScrolledWindow")
         self.hpane = self.widgets.get_widget("hpaned1")
-
+        
         #start up the canvas
+        msg = MsgArea.MsgAreaController()
+        self.widgets.get_widget("mainVbox").pack_start(msg, False, False)
         self.canvas = Canvas.Canvas(
                         parentWindow=self.mainWindow,
                         typeConverter=self.type_converter,
                         syncManager=self.sync_manager,
                         dataproviderMenu=gtk.glade.XML(self.gladeFile, "DataProviderMenu"),
-                        conduitMenu=gtk.glade.XML(self.gladeFile, "ConduitMenu")
+                        conduitMenu=gtk.glade.XML(self.gladeFile, "ConduitMenu"),
+                        msg=msg
                         )
         self.canvasSW.add(self.canvas)
         self.canvas.connect('drag-drop', self.drop_cb)
@@ -125,13 +158,18 @@
 
         #Set up the expander used for resolving sync conflicts
         self.conflictResolver = ConflictResolver.ConflictResolver(self.widgets)
-
-        #final GUI setup
-        self.cancelSyncButton = self.widgets.get_widget('cancel')
-        self.hpane.set_position(conduit.GLOBALS.settings.get("gui_hpane_postion"))
-        self.dataproviderTreeView.set_expand_rows()
-        self.window_state = 0
         
+        #add the preconfigured Conduit menu
+        if conduit.GLOBALS.settings.get("gui_show_hints"):
+            self.preconfiguredConduitsMenu = _PreconfiguredConduitMenu()
+            item = gtk.ImageMenuItem("Examples")
+            item.set_image(
+                    gtk.image_new_from_stock(gtk.STOCK_OPEN,gtk.ICON_SIZE_MENU))
+            item.set_submenu(self.preconfiguredConduitsMenu)
+            self.widgets.get_widget("file_menu").insert(item, 3)
+        else:
+            self.preconfiguredConduitsMenu = None
+
         #if running a development version, add some developer specific links
         #to the help menu
         if conduit.IS_DEVELOPMENT_VERSION:
@@ -152,7 +190,13 @@
                                 gtk.ICON_SIZE_MENU))
                 item.connect("activate",self.on_developer_menu_item_clicked,name,url)
                 developersMenu.append(item)
-                
+
+        #final GUI setup
+        self.cancelSyncButton = self.widgets.get_widget('cancel')
+        self.hpane.set_position(conduit.GLOBALS.settings.get("gui_hpane_postion"))
+        self.dataproviderTreeView.set_expand_rows()
+        self.window_state = 0                
+        log.info("Main window constructed  (thread: %s)" % thread.get_ident())
                 
     def on_developer_menu_item_clicked(self, menuitem, name, url):
         threading.Thread(
@@ -170,6 +214,8 @@
         self.syncSet = syncSet
         self.syncSet.connect("conduit-added", self.on_conduit_added)
         self.canvas.set_sync_set(syncSet)
+        if self.preconfiguredConduitsMenu:
+            self.preconfiguredConduitsMenu.set_sync_set(syncSet)
 
     def present(self):
         """
@@ -310,18 +356,19 @@
         status_icon_check.set_active(conduit.GLOBALS.settings.get("show_status_icon")) 
         minimize_to_tray_check = tree.get_widget("minimize_to_tray_check")
         minimize_to_tray_check.set_active(conduit.GLOBALS.settings.get("gui_minimize_to_tray")) 
-        web_browser_check = tree.get_widget("web_check")
-        web_browser_check.set_active(conduit.GLOBALS.settings.get("web_login_browser") != "system")
+        show_hints_check = tree.get_widget("show_hints_check")
+        show_hints_check.set_active(conduit.GLOBALS.settings.get("gui_show_hints"))
+
 
         #restore the current policy
         for policyName in Conduit.CONFLICT_POLICY_NAMES:
             currentValue = conduit.GLOBALS.settings.get("default_policy_%s" % policyName)
             for policyValue in Conduit.CONFLICT_POLICY_VALUES:
-                widgetName = "%s_%s" % (policyName,policyValue)
-                widget = tree.get_widget(widgetName)
+                name = "%s_%s" % (policyName,policyValue)
+                widget = tree.get_widget(name+"_radio")
                 widget.set_image(
                         gtk.image_new_from_icon_name(
-                                Conduit.CONFLICT_POLICY_VALUE_ICONS[widgetName],
+                                Conduit.CONFLICT_POLICY_VALUE_ICONS[name],
                                 gtk.ICON_SIZE_MENU))
                 if currentValue == policyValue:
                     widget.set_active(True)
@@ -345,15 +392,12 @@
             conduit.GLOBALS.settings.set("save_on_exit", save_settings_check.get_active())
             conduit.GLOBALS.settings.set("show_status_icon", status_icon_check.get_active())
             conduit.GLOBALS.settings.set("gui_minimize_to_tray", minimize_to_tray_check.get_active())
-            if web_browser_check.get_active():
-                conduit.GLOBALS.settings.set("web_login_browser", DEFAULT_CONDUIT_BROWSER)
-            else:
-                conduit.GLOBALS.settings.set("web_login_browser", "system")
+            conduit.GLOBALS.settings.set("gui_show_hints", show_hints_check.get_active())
             #save the current policy
             for policyName in Conduit.CONFLICT_POLICY_NAMES:
                 for policyValue in Conduit.CONFLICT_POLICY_VALUES:
-                    widgetName = "%s_%s" % (policyName,policyValue)
-                    if tree.get_widget(widgetName).get_active() == True:
+                    name = "%s_%s" % (policyName,policyValue)
+                    if tree.get_widget(name+"_radio").get_active() == True:
                         conduit.GLOBALS.settings.set(
                                 "default_policy_%s" % policyName,
                                 policyValue)
@@ -373,7 +417,7 @@
         dialog.set_transient_for(self.mainWindow)
         dialog.run()
         dialog.destroy()
-
+        
     def on_help(self, widget):
         """
         Display help
@@ -478,7 +522,7 @@
                             self.mainWindow.get_size())
         conduit.GLOBALS.settings.set(
                             "gui_expanded_rows",
-                            self.dataproviderTreeView.get_expanded_rows())        
+                            self.dataproviderTreeView.get_expanded_rows())
 
 class SplashScreen:
     """

Modified: trunk/conduit/hildonui/Canvas.py
==============================================================================
--- trunk/conduit/hildonui/Canvas.py	(original)
+++ trunk/conduit/hildonui/Canvas.py	Fri Aug 29 23:37:30 2008
@@ -34,8 +34,9 @@
         #setup the canvas
         conduit.gtkui.Canvas.Canvas.__init__(self,
                                 parentWindow,typeConverter,syncManager,
-                                #menus are set in _setup_popup_menus
-                                None,None)
+                                None,None,  #menus are set in _setup_popup_menus
+                                None        #no message hints in hildon
+                                )
         self.position = -1
         
     def _update_for_theme(self, *args):
@@ -47,6 +48,20 @@
         # conduit context menu
         self.conduitMenu = ConduitMenu(self)
         
+    def _create_welcome(self):
+        c_x,c_y,c_w,c_h = self.get_bounds()
+        self.welcome = goocanvas.Text(  
+                            x=c_w/2, 
+                            y=c_w/3, 
+                            width=3*c_w/5, 
+                            text=self.WELCOME_MESSAGE, 
+                            anchor=gtk.ANCHOR_CENTER,
+                            alignment=pango.ALIGN_CENTER,
+                            font="Sans 10",
+                            fill_color="black",
+                            )
+        self.root.add_child(self.welcome,-1)
+        
     def _on_conduit_button_press(self, view, target, event):        
         log.debug("Clicked View: %s" % view.model)
 

Modified: trunk/conduit/modules/GoogleModule/GoogleModule.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/GoogleModule.py	(original)
+++ trunk/conduit/modules/GoogleModule/GoogleModule.py	Fri Aug 29 23:37:30 2008
@@ -36,7 +36,7 @@
     import gdata.youtube.service
 
     MODULES = {
-        "GoogleCalendarTwoWay" : { "type": "dataprovider" },
+#        "GoogleCalendarTwoWay" : { "type": "dataprovider" },
         "PicasaTwoWay" :         { "type": "dataprovider" },
         "YouTubeTwoWay" :        { "type": "dataprovider" },    
         "ContactsTwoWay" :       { "type": "dataprovider" },
@@ -650,6 +650,8 @@
         if not self.loggedIn:
             raise Exceptions.RefreshError("Could not log in")
         self._get_album()
+        if self.galbum:
+            self._get_photos()
 
     def get_all (self):
         Image.ImageTwoWay.get_all(self)
@@ -1341,7 +1343,7 @@
     """
     _name_ = _("YouTube")
     _description_ = _("Sync data from YouTube")
-    _category_ = conduit.dataproviders.CATEGORY_MISC
+    _category_ = conduit.dataproviders.CATEGORY_MEDIA
     _module_type_ = "twoway"
     _in_type_ = "file/video"
     _out_type_ = "file/video"

Modified: trunk/conduit/modules/TestModule.py
==============================================================================
--- trunk/conduit/modules/TestModule.py	(original)
+++ trunk/conduit/modules/TestModule.py	Fri Aug 29 23:37:30 2008
@@ -588,7 +588,7 @@
     def __init__(self, *args):
         TestTwoWay.__init__(self)
         self.url = "http://www.google.com";
-        self.browser = conduit.GLOBALS.settings.get("web_login_browser")
+        self.browser = conduit.BROWSER_IMPL
 
     def configure(self, window):
         import gtk

Modified: trunk/conduit/platform/WebBrowserWebkit.py
==============================================================================
--- trunk/conduit/platform/WebBrowserWebkit.py	(original)
+++ trunk/conduit/platform/WebBrowserWebkit.py	Fri Aug 29 23:37:30 2008
@@ -1,13 +1,17 @@
+import gtk
 import webkit
 import conduit.platform
 
 class WebBrowserImpl(conduit.platform.WebBrowser):
     def __init__(self):
         conduit.platform.WebBrowser.__init__(self)
+        self.sw = gtk.ScrolledWindow()
+        self.sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
         self.webView = webkit.WebView()
+        self.sw.add(self.webView)
 
     def widget(self):
-        return self.webView
+        return self.sw
  
     def load_url(self,url):
         self.webView.open(url)

Modified: trunk/conduit/utils/__init__.py
==============================================================================
--- trunk/conduit/utils/__init__.py	(original)
+++ trunk/conduit/utils/__init__.py	Fri Aug 29 23:37:30 2008
@@ -170,13 +170,15 @@
     Sets the dialog to display the busy cursor
     """
     import gtk.gdk
-    dlg.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+    if dlg and dlg.window:
+        dlg.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
     
 def dialog_reset_cursor(dlg):
     """
     Resets the dialog to display the plain Gtk cursor
     """
-    dlg.window.set_cursor(None)
+    if dlg and dlg.window:
+        dlg.window.set_cursor(None)
 
 def md5_string(string):
     """

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Fri Aug 29 23:37:30 2008
@@ -1,4 +1,4 @@
-AC_INIT([conduit], [0.3.13],[john stowers gmail com],[conduit])
+AC_INIT([conduit], [0.3.14],[john stowers gmail com],[conduit])
 AM_INIT_AUTOMAKE([1.9])
 AC_CONFIG_SRCDIR(conduit/__init__.py)
 AC_CONFIG_MACRO_DIR([m4])

Modified: trunk/data/conduit.glade
==============================================================================
--- trunk/data/conduit.glade	(original)
+++ trunk/data/conduit.glade	Fri Aug 29 23:37:30 2008
@@ -17,12 +17,12 @@
           <widget class="GtkMenuBar" id="menubar1">
             <property name="visible">True</property>
             <child>
-              <widget class="GtkMenuItem" id="file_menu">
+              <widget class="GtkMenuItem" id="file_menu_item">
                 <property name="visible">True</property>
                 <property name="label" translatable="yes">_File</property>
                 <property name="use_underline">True</property>
                 <child>
-                  <widget class="GtkMenu" id="file1_menu">
+                  <widget class="GtkMenu" id="file_menu">
                     <child>
                       <widget class="GtkImageMenuItem" id="save1">
                         <property name="visible">True</property>
@@ -85,7 +85,7 @@
               </widget>
             </child>
             <child>
-              <widget class="GtkMenuItem" id="edit_menu">
+              <widget class="GtkMenuItem" id="edit_menu_item">
                 <property name="visible">True</property>
                 <property name="label" translatable="yes">_Edit</property>
                 <property name="use_underline">True</property>
@@ -125,7 +125,7 @@
               </widget>
             </child>
             <child>
-              <widget class="GtkMenuItem" id="help_menu">
+              <widget class="GtkMenuItem" id="help_menu_item">
                 <property name="visible">True</property>
                 <property name="label" translatable="yes">_Help</property>
                 <property name="use_underline">True</property>
@@ -209,13 +209,18 @@
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <child>
-                  <widget class="GtkScrolledWindow" id="canvasScrolledWindow">
+                  <widget class="GtkVBox" id="mainVbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
-                    <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
                     <child>
-                      <placeholder/>
+                      <widget class="GtkScrolledWindow" id="canvasScrolledWindow">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+                        <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </widget>
                     </child>
                   </widget>
                   <packing>
@@ -787,11 +792,10 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkCheckButton" id="web_check">
+                              <widget class="GtkCheckButton" id="show_hints_check">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
-                                <property name="label" translatable="yes">Use built in Web browser</property>
-                                <property name="use_underline">True</property>
+                                <property name="label" translatable="yes">Show Hints</property>
                                 <property name="response_id">0</property>
                                 <property name="draw_indicator">True</property>
                               </widget>
@@ -843,7 +847,7 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="deleted_ask">
+                              <widget class="GtkRadioButton" id="deleted_ask_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Ask me what to do</property>
@@ -858,14 +862,14 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="deleted_replace">
+                              <widget class="GtkRadioButton" id="deleted_replace_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Delete from the corresponding sink</property>
                                 <property name="use_underline">True</property>
                                 <property name="response_id">0</property>
                                 <property name="draw_indicator">True</property>
-                                <property name="group">deleted_ask</property>
+                                <property name="group">deleted_ask_radio</property>
                               </widget>
                               <packing>
                                 <property name="expand">False</property>
@@ -874,14 +878,14 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="deleted_skip">
+                              <widget class="GtkRadioButton" id="deleted_skip_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Skip</property>
                                 <property name="use_underline">True</property>
                                 <property name="response_id">0</property>
                                 <property name="draw_indicator">True</property>
-                                <property name="group">deleted_ask</property>
+                                <property name="group">deleted_ask_radio</property>
                               </widget>
                               <packing>
                                 <property name="expand">False</property>
@@ -902,7 +906,7 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="conflict_ask">
+                              <widget class="GtkRadioButton" id="conflict_ask_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Ask me what to do</property>
@@ -917,14 +921,14 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="conflict_replace">
+                              <widget class="GtkRadioButton" id="conflict_replace_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Replace the older item</property>
                                 <property name="use_underline">True</property>
                                 <property name="response_id">0</property>
                                 <property name="draw_indicator">True</property>
-                                <property name="group">conflict_ask</property>
+                                <property name="group">conflict_ask_radio</property>
                               </widget>
                               <packing>
                                 <property name="expand">False</property>
@@ -933,14 +937,14 @@
                               </packing>
                             </child>
                             <child>
-                              <widget class="GtkRadioButton" id="conflict_skip">
+                              <widget class="GtkRadioButton" id="conflict_skip_radio">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="label" translatable="yes">Skip</property>
                                 <property name="use_underline">True</property>
                                 <property name="response_id">0</property>
                                 <property name="draw_indicator">True</property>
-                                <property name="group">conflict_ask</property>
+                                <property name="group">conflict_ask_radio</property>
                               </widget>
                               <packing>
                                 <property name="expand">False</property>

Modified: trunk/help/C/figures/conduit-dp.png
==============================================================================
Binary files. No diff available.

Modified: trunk/help/C/figures/conduit-login.png
==============================================================================
Binary files. No diff available.

Modified: trunk/help/Makefile.am
==============================================================================
--- trunk/help/Makefile.am	(original)
+++ trunk/help/Makefile.am	Fri Aug 29 23:37:30 2008
@@ -11,7 +11,7 @@
     figures/conduit-gui-parts.png \
     figures/conduit-dp.png \
     figures/conduit-folder-configuration.png \
-    figures/conduit-pc1.png \
-    figures/conduit-pc2.png \
-    figures/conduit-sync-pc1.png \
-    figures/conduit-sync-pc2.png
+    figures/network-pc1.png \
+    figures/network-pc2.png \
+    figures/network-sync-pc1.png \
+    figures/network-sync-pc2.png

Modified: trunk/scripts/release.sh
==============================================================================
--- trunk/scripts/release.sh	(original)
+++ trunk/scripts/release.sh	Fri Aug 29 23:37:30 2008
@@ -6,9 +6,9 @@
 fi
 
 ./scripts/maintainer.py \
-    --revision=0.3.12 \
+    --revision=0.3.13 \
     --package-name=Conduit \
-    --package-version=0.3.13 \
+    --package-version=0.3.14 \
     --package-module=conduit \
     --release-note-template=scripts/release-template.txt \
     $*

Modified: trunk/test/python-tests/TestCoreSettings.py
==============================================================================
--- trunk/test/python-tests/TestCoreSettings.py	(original)
+++ trunk/test/python-tests/TestCoreSettings.py	Fri Aug 29 23:37:30 2008
@@ -10,7 +10,7 @@
         'gui_expanded_rows'         :   [],
         'gui_hpane_postion'         :   250,
         'gui_minimize_to_tray'      :   False,
-        'web_login_browser'         :   "system"
+        'default_policy_conflict'   :   "ask"
 }
 
 for impl in ("GConf", "Python"):

Modified: trunk/test/python-tests/TestDataProviderFacebook.py
==============================================================================
--- trunk/test/python-tests/TestDataProviderFacebook.py	(original)
+++ trunk/test/python-tests/TestDataProviderFacebook.py	Fri Aug 29 23:37:30 2008
@@ -20,7 +20,7 @@
 albums = facebook._get_albums()
 ok("Got %d albums" % len(albums), len(albums) > 0)
 
-name, aid = albums[0]
+aid = albums['Conduit Photos']
 photos = facebook._get_photos(int(aid))
 ok("Got %d photos" % len(photos), len(photos) > 0)
 

Copied: trunk/test/python-tests/TestDataProviderGoogleCalendar.py (from r1689, /trunk/test/python-tests/TestDataProviderGoogle.py)
==============================================================================
--- /trunk/test/python-tests/TestDataProviderGoogle.py	(original)
+++ trunk/test/python-tests/TestDataProviderGoogleCalendar.py	Fri Aug 29 23:37:30 2008
@@ -40,7 +40,6 @@
 hour=random.randint(12,23)
 ics="""BEGIN:VCALENDAR
 VERSION:2.0
-PRODID:-//PYVOBJECT//NONSGML Version 1//EN
 BEGIN:VEVENT
 DTSTART:2008%(month)02d%(day)02dT%(hour)02d0000Z
 DTEND:2008%(month)02d%(day)02dT%(end)02d0000Z
@@ -56,30 +55,9 @@
 test.do_dataprovider_tests(
         supportsGet=True,
         supportsDelete=False,
-        safeLUID=SAFE_EVENT_UID,
+        safeLUID=None,
         data=event,
         name="event"
         )
-
-#-------------------------------------------------------------------------------
-# Youtube
-#-------------------------------------------------------------------------------
-#Now a very simple youtube test...
-test = SimpleTest(sourceName="YouTubeSource")
-config = {
-    "max_downloads" :   MAX_YOUTUBE_VIDEOS
-}
-test.configure(source=config)
-youtube = test.get_source().module
-
-try:
-    youtube.refresh()
-    ok("Refresh youtube", True)
-except Exception, err:
-    ok("Refresh youtube (%s)" % err, False) 
-
-videos = youtube.get_all()
-num = len(videos)
-ok("Got %s videos" % num, num == MAX_YOUTUBE_VIDEOS)
-
 finished()
+

Added: trunk/test/python-tests/TestDataProviderYoutube.py
==============================================================================
--- (empty file)
+++ trunk/test/python-tests/TestDataProviderYoutube.py	Fri Aug 29 23:37:30 2008
@@ -0,0 +1,37 @@
+#common sets up the conduit environment
+from common import *
+
+import conduit.datatypes.Event as Event
+import conduit.utils as Utils
+
+import random
+
+SAFE_CALENDAR_NAME="Conduit Project"
+SAFE_EVENT_UID="2bh7mbagsc880g64qaps06tbp4 google com"
+MAX_YOUTUBE_VIDEOS=5
+
+if not is_online():
+    skip()
+
+#-------------------------------------------------------------------------------
+# Youtube
+#-------------------------------------------------------------------------------
+#Now a very simple youtube test...
+test = SimpleTest(sourceName="YouTubeSource")
+config = {
+    "max_downloads" :   MAX_YOUTUBE_VIDEOS
+}
+test.configure(source=config)
+youtube = test.get_source().module
+
+try:
+    youtube.refresh()
+    ok("Refresh youtube", True)
+except Exception, err:
+    ok("Refresh youtube (%s)" % err, False) 
+
+videos = youtube.get_all()
+num = len(videos)
+ok("Got %s videos" % num, num == MAX_YOUTUBE_VIDEOS)
+
+finished()

Modified: trunk/test/python-tests/TestSyncTomboyiPod.py
==============================================================================
--- trunk/test/python-tests/TestSyncTomboyiPod.py	(original)
+++ trunk/test/python-tests/TestSyncTomboyiPod.py	Fri Aug 29 23:37:30 2008
@@ -9,7 +9,7 @@
 import conduit.Synchronization as Synchronization
 
 from conduit.datatypes import COMPARISON_EQUAL
-from conduit.modules import iPodModule
+from conduit.modules.iPodModule import iPodModule
 
 #setup the test
 test = SimpleSyncTest()

Modified: trunk/test/python-tests/common.py
==============================================================================
--- trunk/test/python-tests/common.py	(original)
+++ trunk/test/python-tests/common.py	Fri Aug 29 23:37:30 2008
@@ -37,12 +37,10 @@
 conduit.IS_DEVELOPMENT_VERSION =    True
 conduit.SHARED_DATA_DIR =           os.path.join(base_path,"data")
 conduit.SHARED_MODULE_DIR =         os.path.join(base_path,"conduit","modules")
+conduit.FILE_IMPL =                 "GnomeVfs"  
+conduit.BROWSER_IMPL =              "system"
 conduit.SETTINGS_IMPL =             "GConf"
-
-# override some conduit settings. 
-# without a gobject main loop the gtkmozembed browser hangs
-conduit.GLOBALS.settings = Settings.Settings()
-conduit.GLOBALS.settings.set_overrides(web_login_browser="system")
+conduit.GLOBALS.settings =          Settings.Settings()
 
 def is_online():
     try:    
@@ -468,7 +466,7 @@
                 info = self.sink.module._get_photo_info(safePhotoLUID)
                 ok("Got photo info", info != None)
                 url = self.sink.module._get_raw_photo_url(info)
-                ok("Got photo url (%s)" % url, url != None and Vfs.uri_exists(url))
+                ok("Got photo url (%s)" % url, url != None and Vfs.uri_exists(str(url)))
             except Exception, err:
                 traceback.print_exc()
                 ok("Got photo info/url (%s)" % err, False)



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