conduit r1590 - in trunk: . conduit conduit/dataproviders conduit/modules/FileModule conduit/platform conduit/utils test/python-tests test/python-tests/data



Author: jstowers
Date: Tue Jul 29 12:38:09 2008
New Revision: 1590
URL: http://svn.gnome.org/viewvc/conduit?rev=1590&view=rev

Log:
'Merge' conduit-platform branch. I use the term merge loosely because bzr has broken our repository to such and extreme degree that it has made all bzr operations impossible. Lets just copy all the files over top of the old ones. Its like 1997 all over again. No soup for you

Added:
   trunk/conduit/platform/
   trunk/conduit/platform/Makefile.am
   trunk/conduit/platform/SettingsGConf.py
   trunk/conduit/platform/SettingsPython.py
   trunk/conduit/platform/WebBrowserMozilla.py
   trunk/conduit/platform/WebBrowserSystem.py
   trunk/conduit/platform/WebBrowserWebkit.py
   trunk/conduit/platform/__init__.py
   trunk/conduit/utils/Bluetooth.py
   trunk/conduit/utils/Singleton.py
   trunk/conduit/utils/Thread.py
   trunk/test/python-tests/TestCoreUtilBluetooth.py
Modified:
   trunk/   (props changed)
   trunk/ChangeLog
   trunk/conduit/Globals.py
   trunk/conduit/Main.py
   trunk/conduit/Makefile.am
   trunk/conduit/Settings.py
   trunk/conduit/Vfs.py
   trunk/conduit/Web.py
   trunk/conduit/__init__.py
   trunk/conduit/dataproviders/VolumeFactory.py
   trunk/conduit/defs.py.in
   trunk/conduit/modules/FileModule/FileModule.py
   trunk/conduit/utils/Makefile.am
   trunk/configure.ac
   trunk/test/python-tests/TestCoreSettings.py
   trunk/test/python-tests/TestCoreUtil.py
   trunk/test/python-tests/TestCoreVfs.py
   trunk/test/python-tests/common.py
   trunk/test/python-tests/data/folder.list

Modified: trunk/conduit/Globals.py
==============================================================================
--- trunk/conduit/Globals.py	(original)
+++ trunk/conduit/Globals.py	Tue Jul 29 12:38:09 2008
@@ -1,12 +1,10 @@
 """
 Excapsulates those items global to the Conduit process
 """
-import conduit.Settings as Settings
-
-class Globals(object):
+class Globals:
     def __init__(self):
         #settings is global and initialized early
-        self.settings = Settings.Settings()
+        self.settings = None
 
         #to save resources DB, moduleManager and typeConverter are global
         self.moduleManager = None

Modified: trunk/conduit/Main.py
==============================================================================
--- trunk/conduit/Main.py	(original)
+++ trunk/conduit/Main.py	Tue Jul 29 12:38:09 2008
@@ -18,6 +18,7 @@
 from conduit.SyncSet import SyncSet
 from conduit.Synchronization import SyncManager
 from conduit.DBus import DBusInterface
+from conduit.Settings import Settings
 
 APPLICATION_DBUS_IFACE="org.conduit.Application"
 
@@ -46,6 +47,9 @@
         self.settingsFile = os.path.join(conduit.USER_DIR, "settings.xml")
         self.dbFile = os.path.join(conduit.USER_DIR, "mapping.db")
 
+        #initialize application settings
+        conduit.GLOBALS.settings = Settings(conduit.SETTINGS_IMPL)
+
         buildGUI = True
         iconify = False
         whitelist = None
@@ -279,6 +283,9 @@
         #Save the mapping DB
         conduit.GLOBALS.mappingDB.save()
         conduit.GLOBALS.mappingDB.close()
+
+        #Save the application settings
+        conduit.GLOBALS.settings.save()
         
         log.info("Main Loop Quitting")
         conduit.GLOBALS.mainloop.quit()

Modified: trunk/conduit/Makefile.am
==============================================================================
--- trunk/conduit/Makefile.am	(original)
+++ trunk/conduit/Makefile.am	Tue Jul 29 12:38:09 2008
@@ -1,4 +1,4 @@
-SUBDIRS = datatypes dataproviders modules gtkui hildonui utils
+SUBDIRS = datatypes dataproviders modules gtkui hildonui utils platform
 
 conduitbindir = $(bindir)
 conduitbin_SCRIPTS = \

Modified: trunk/conduit/Settings.py
==============================================================================
--- trunk/conduit/Settings.py	(original)
+++ trunk/conduit/Settings.py	Tue Jul 29 12:38:09 2008
@@ -6,21 +6,8 @@
 Copyright: John Stowers, 2006
 License: GPLv2
 """
-import re
-import os
 import gobject
 
-try:
-    import gconf
-except ImportError:
-    from gnome import gconf
-
-import logging
-log = logging.getLogger("Settings")
-
-#import gnomekeyring
-#import conduit
-
 #these dicts are used for mapping config setting types to type names
 #and back again (isnt python cool...)
 TYPE_TO_TYPE_NAME = {
@@ -93,135 +80,48 @@
         '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"
     }
-    CONDUIT_GCONF_DIR = "/apps/conduit/"
         
-    def __init__(self):
-        """
-        @param xmlSettingFilePath: The path to the xml file in which to store
-        the per-conduit settings
-        @type xmlSettingFilePath: C{string}
-        """
+    def __init__(self, implName):
         gobject.GObject.__init__(self)
-        self.client = gconf.client_get_default()
-        #Preload gconf directories
-        self.client.add_dir(self.CONDUIT_GCONF_DIR[:-1], gconf.CLIENT_PRELOAD_RECURSIVE)  
-        self.notifications = []
-        #also keep an internal dict of settings which have been overridden
-        #for this session
-        self.overrides = {}
+        if implName == "GConf":
+            from conduit.platform.SettingsGConf import SettingsImpl            
+        else:
+            from conduit.platform.SettingsPython import SettingsImpl
 
-    def _fix_key(self, key):
-        """
-        Appends the CONDUIT_GCONF_PREFIX to the key if needed
+        self._impl = SettingsImpl(
+                        defaults=self.DEFAULTS,
+                        changedCb=self._key_changed)
         
-        @param key: The key to check
-        @type key: C{string}
-        @returns: The fixed key
-        @rtype: C{string}
-        """
-        if not key.startswith(self.CONDUIT_GCONF_DIR):
-            return self.CONDUIT_GCONF_DIR + key
-        else:
-            return key
-            
-    def _key_changed(self, client, cnxn_id, entry, data=None):
-        """
-        Callback when a gconf key changes
-        """
-        key = self._fix_key(entry.key)
-        detailed_signal = 'changed::%s' % key
-        self.emit(detailed_signal)
+    def _key_changed(self, key):
+        self.emit('changed::%s' % key)
         
     def set_overrides(self, **overrides):
-        self.overrides = overrides
+        """
+        Sets values of settings that only exist for this setting, and are
+        never saved, nor updated.
+        """
+        self._impl.set_overrides(**overrides)
 
-    def get(self, key, vtype=None, default=None):
+    def get(self, key, **kwargs):
         """
         Returns the value of the key or the default value if the key is 
-        not yet in gconf
+        not yet stored
         """
-        #check if the setting has been overridden for this session
-        if key in self.overrides:
-            try:
-                #try and cast to correct type
-                return type(self.DEFAULTS[key])(self.overrides[key])
-            except:
-                return self.overrides[key]
+        return self._impl.get(key, **kwargs)
 
-        if key in self.DEFAULTS:
-            #function arguments override defaults
-            if default is None:
-                default = self.DEFAULTS[key]
-            if vtype is None:
-                vtype = type(default)
-
-        #for gconf refer to the full key path
-        key = self._fix_key(key)
-
-        if key not in self.notifications:
-            self.client.notify_add(key, self._key_changed)
-            self.notifications.append(key)
-        
-        value = self.client.get(key)
-        if not value:
-            self.set(key, default, vtype)
-            return default
-
-        if vtype is bool:
-            return value.get_bool()
-        elif vtype is str:
-            return value.get_string()
-        elif vtype is int:
-            return value.get_int()
-        elif vtype in [list, tuple]:
-            l = []
-            for i in value.get_list():
-                l.append(i.get_string())
-            return l
-            
-        log.warn("Unknown gconf key: %s" % key)
-        return None
-
-    def set(self, key, value, vtype=None):
+    def set(self, key, value, **kwargs):
         """
-        Sets the key value in gconf and connects adds a signal 
-        which is fired if the key changes
+        Sets the key to value.
         """
-        #overidden settings only apply for this session, and are
-        #not set
-        if key in self.overrides:
-            return True
-
-        log.debug("Settings %s -> %s" % (key, value))
-        if key in self.DEFAULTS and not vtype:
-            vtype = type(self.DEFAULTS[key])
-
-        #for gconf refer to the full key path
-        key = self._fix_key(key)
-
-        if vtype is bool:
-            self.client.set_bool(key, value)
-        elif vtype is str:
-            self.client.set_string(key, value)
-        elif vtype is int:
-            self.client.set_int(key, value)
-        elif vtype in [list, tuple]:
-            #Save every value as a string
-            strvalues = [str(i) for i in value]
-            self.client.set_list(key, gconf.VALUE_STRING, strvalues)
-        else:
-            log.warn("Unknown gconf type (k:%s v:%s t:%s)" % (key,value,vtype))
-            return False
-
-        return True
+        return self._impl.set(key, value, **kwargs)
         
     def proxy_enabled(self):
         """
         @returns: True if the user has specified a http proxy via
-        the http_proxy environment variable, or in gconf
+        the http_proxy environment variable, or in the appropriate settings
+        backend, such as gconf
         """
-        return os.environ.has_key("http_proxy") or \
-                self.client.get_bool("/system/http_proxy/use_http_proxy")
+        return self._impl.proxy_enabled()
         
     def get_proxy(self):
         """
@@ -229,39 +129,12 @@
         The http_proxy environment variable overrides the GNOME setting
         @returns: host,port,user,password
         """
-        if self.proxy_enabled():
-            #env vars have preference
-            if os.environ.has_key("http_proxy"):
-                #re taken from python boto
-                pattern = re.compile(
-                    '(?:http://)?' \
-                    '(?:(?P<user>\w+):(?P<pass>.*)@)?' \
-                    '(?P<host>[\w\-\.]+)' \
-                    '(?::(?P<port>\d+))?'
-                )
-                match = pattern.match(os.environ['http_proxy'])
-                if match:
-                    return (match.group('host'),
-                            int(match.group('port')),
-                            match.group('user'),
-                            match.group('pass'))
-            #now try gconf
-            if self.client.get_bool("/system/http_proxy/use_authentication"):
-                return (self.client.get_string("/system/http_proxy/host"),
-                        self.client.get_int("/system/http_proxy/port"),
-                        self.client.get_string("/system/http_proxy/authentication_user"),
-                        self.client.get_string("/system/http_proxy/authentication_password"))
-            else:
-                return (self.client.get_string("/system/http_proxy/host"),
-                        self.client.get_int("/system/http_proxy/port"),
-                        "",
-                        "")
+        return self._impl.get_proxy()
 
-        return ("",0,"","")
-                            
-                
-        
+    def save(self):
+        """
+        Performs any necessary tasks to ensure settings are saved between sessions
+        """
+        self._impl.save()
 
-        
-        
 

Modified: trunk/conduit/Vfs.py
==============================================================================
--- trunk/conduit/Vfs.py	(original)
+++ trunk/conduit/Vfs.py	Tue Jul 29 12:38:09 2008
@@ -1,11 +1,14 @@
 import os.path
 import logging
+import gobject
 log = logging.getLogger("Vfs")
 
 try:
     import gnomevfs
 except ImportError:
     from gnome import gnomevfs
+
+import conduit.utils.Singleton as Singleton
     
 #
 # URI Functions
@@ -14,6 +17,7 @@
     """
     (weakly) checks if a uri is valid by looking for a scheme seperator
     """
+    assert type(uri) == str
     return uri[0] != "/" and uri.find("://") != -1
     
 def uri_join(first, *rest):
@@ -21,6 +25,7 @@
     Joins multiple uri components. Performs safely if the first
     argument contains a uri scheme
     """
+    assert type(first) == str
     return os.path.join(first,*rest)
     #idx = first.rfind("://")
     #if idx == -1:
@@ -33,6 +38,8 @@
     """
     Returns the relative path fromURI --> toURI
     """
+    assert type(fromURI) == str
+    assert type(toURI) == str
     rel = toURI.replace(fromURI,"")
     #strip leading /
     if rel[0] == os.sep:
@@ -44,9 +51,7 @@
     """
     Opens a gnomevfs or xdg compatible uri.
     """
-    if uri == None:
-        log.warn("Cannot open non-existant URI")
-
+    assert type(uri) == str
     APP = "xdg-open"
     os.spawnlp(os.P_NOWAIT, APP, APP, uri)
     
@@ -55,15 +60,17 @@
     @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
     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
     try:
         path = uri_to_local_path(uri)
-        return VolumeMonitor().get_volume_for_path(path).get_activation_uri()
+        return VolumeMonitor().volume_get_root_uri(path)
     except:
         return None
     
@@ -72,15 +79,16 @@
     @returns: True if the specified uri is on a removable volume, like a USB key
     or removable/mountable disk.
     """
+    assert type(uri) == str
     scheme = gnomevfs.get_uri_scheme(uri)
     if scheme == "file":
         #FIXME: Unfortunately this approach actually works better than gnomevfs
         #return uri.startswith("file:///media/")
         try:
             path = uri_to_local_path(uri)
-            return VolumeMonitor().get_volume_for_path(path).is_user_visible()
+            return VolumeMonitor().volume_is_removable(path)
         except Exception, e:
-            log.warn("Could not determine if uri on removable volume: %s" % uri)
+            log.warn("Could not determine if uri on removable volume: %s (%s)" % (uri, e))
             return False
     return False
     
@@ -90,12 +98,12 @@
     @returns: The filesystem that uri is stored on or None if it cannot
     be determined
     """
+    assert type(uri) == str
     scheme = gnomevfs.get_uri_scheme(uri)
     if scheme == "file":
         try:
             path = uri_to_local_path(uri)
-            volume = VolumeMonitor().get_volume_for_path(path)
-            return  volume.get_filesystem_type()
+            return VolumeMonitor().volume_get_fstype(path)
         except RuntimeError:
             log.warn("Could not get local path from URI")
             return None
@@ -109,6 +117,7 @@
     Standardizes the format of the uri
     @param uri:an absolute or relative stringified uri. It might have scheme.
     """
+    assert type(uri) == str
     return gnomevfs.make_uri_canonical(uri)
     
 def uri_escape(uri):
@@ -117,6 +126,7 @@
     paths or host names.
     (so '/', '&', '=', ':' and '@' will not be escaped by this function)
     """
+    assert type(uri) == str
     #FIXME: This function lies, it escapes @
     #return gnomevfs.escape_host_and_path_string(uri)
     import urllib
@@ -126,6 +136,7 @@
     """
     Replace "%xx" escapes by their single-character equivalent.
     """
+    assert type(uri) == str
     import urllib
     return urllib.unquote(uri)
     
@@ -133,6 +144,7 @@
     """
     Returns the protocol (file, smb, etc) for a URI
     """
+    assert type(uri) == str
     if uri.rfind("://")==-1:
         return ""
     protocol = uri[:uri.index("://")+3]
@@ -143,12 +155,14 @@
     Method to return the filename of a file. Could use GnomeVFS for this
     is it wasnt so slow
     """
+    assert type(uri) == str
     return uri.split(os.sep)[-1]
 
 def uri_get_filename_and_extension(uri):
     """
     Returns filename,file_extension
     """
+    assert type(uri) == str
     return os.path.splitext(uri_get_filename(uri))
     
 def uri_sanitize_for_filesystem(uri, filesystem=None):
@@ -156,8 +170,9 @@
     Removes illegal characters in uri that cannot be stored on the 
     given filesystem - particuarly fat and ntfs types
     """
+    assert type(uri) == str
     import string
-
+    
     ILLEGAL_CHARS = {
         "vfat"  :   "\\:*?\"<>|",
         "ntfs"  :   "\\:*?\"<>|"
@@ -184,6 +199,7 @@
     """
     @returns: True if the uri is a folder and not a file
     """
+    assert type(uri) == str
     info = gnomevfs.get_file_info(uri)
     return info.type == gnomevfs.FILE_TYPE_DIRECTORY
     
@@ -191,12 +207,14 @@
     """
     Formats the uri so it can be displayed to the user (strips passwords, etc)
     """
+    assert type(uri) == str
     return gnomevfs.format_uri_for_display(uri)
     
 def uri_exists(uri):
     """
     @returns: True if the uri exists
     """
+    assert type(uri) == str
     try:
         return gnomevfs.exists(gnomevfs.URI(uri)) == 1
     except Exception, err:
@@ -208,6 +226,7 @@
     Makes a directory with the default permissions. Does not catch any
     error
     """
+    assert type(uri) == str
     gnomevfs.make_directory(
             uri,
             gnomevfs.PERM_USER_ALL | gnomevfs.PERM_GROUP_READ | gnomevfs.PERM_GROUP_EXEC | gnomevfs.PERM_OTHER_READ | gnomevfs.PERM_OTHER_EXEC
@@ -221,6 +240,7 @@
     @param uri: A directory that does not exist
     @type uri: str
     """
+    assert type(uri) == str
     exists = False
     dirs = []
 
@@ -233,32 +253,79 @@
     dirs.reverse()
     for d in dirs:
         log.debug("Making directory %s" % d)
-        uri_make_directory(d)
+        uri_make_directory(str(d))
 
-#
-# For monitoring locations
-#
-MONITOR_EVENT_CREATED =             gnomevfs.MONITOR_EVENT_CREATED
-MONITOR_EVENT_CHANGED =             gnomevfs.MONITOR_EVENT_CHANGED
-MONITOR_EVENT_DELETED =             gnomevfs.MONITOR_EVENT_DELETED
-MONITOR_EVENT_METADATA_CHANGED =    gnomevfs.MONITOR_EVENT_METADATA_CHANGED
-MONITOR_EVENT_STARTEXECUTING =      gnomevfs.MONITOR_EVENT_STARTEXECUTING
-MONITOR_EVENT_STOPEXECUTING =       gnomevfs.MONITOR_EVENT_STOPEXECUTING
-MONITOR_FILE =                      gnomevfs.MONITOR_FILE
-MONITOR_DIRECTORY =                 gnomevfs.MONITOR_DIRECTORY
+class FileMonitor(gobject.GObject):
 
-def monitor_add(folder, type, monitor_cb):
-    try:
-        return gnomevfs.monitor_add (folder, type, monitor_cb)
-    except gnomevfs.NotSupportedError:
-        # silently fail if we are looking at a folder that doesn't support directory monitoring
-        return None
+    __gsignals__ = {
+        "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_PYOBJECT,
+            gobject.TYPE_PYOBJECT,
+            gobject.TYPE_PYOBJECT])
+        }
+
+    MONITOR_EVENT_CREATED =             gnomevfs.MONITOR_EVENT_CREATED
+    MONITOR_EVENT_CHANGED =             gnomevfs.MONITOR_EVENT_CHANGED
+    MONITOR_EVENT_DELETED =             gnomevfs.MONITOR_EVENT_DELETED
+    MONITOR_EVENT_METADATA_CHANGED =    gnomevfs.MONITOR_EVENT_METADATA_CHANGED
+    MONITOR_EVENT_STARTEXECUTING =      gnomevfs.MONITOR_EVENT_STARTEXECUTING
+    MONITOR_EVENT_STOPEXECUTING =       gnomevfs.MONITOR_EVENT_STOPEXECUTING
+    MONITOR_FILE =                      gnomevfs.MONITOR_FILE
+    MONITOR_DIRECTORY =                 gnomevfs.MONITOR_DIRECTORY
+
+    def __init__(self):
+        gobject.GObject.__init__(self)
+        self._monitor_folder_id = None
+
+    def _monitor_cb(self, monitor_uri, event_uri, event):
+        self.emit("changed", monitor_uri, event_uri, event)
+
+    def add(self, folder, monitorType):
+        if self._monitor_folder_id != None:
+            gnomevfs.monitor_cancel(self._monitor_folder_id)
+            self._monitor_folder_id = None
+
+        try:
+            self._monitor_folder_id = gnomevfs.monitor_add(folder, monitorType, self._monitor_cb)   
+        except gnomevfs.NotSupportedError:
+            # silently fail if we are looking at a folder that doesn't support directory monitoring
+            self._monitor_folder_id = None
+        
+    def cancel(self):
+        if self._monitor_folder_id != None:
+            gnomevfs.monitor_cancel(self._monitor_folder_id)
+            self._monitor_folder_id = None
+
+class VolumeMonitor(Singleton.Singleton, gobject.GObject):
+
+    __gsignals__ = {
+        "volume-mounted" :      (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING]),      #udi
+        "volume-unmounted" :    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING])       #udi
+
+    }
+
+    def __init__(self):
+        gobject.GObject.__init__(self)
+        self._impl = gnomevfs.VolumeMonitor()
+        self._impl.connect("volume-mounted", self._mounted_unmounted_cb, "volume-mounted")
+        self._impl.connect("volume-unmounted", self._mounted_unmounted_cb, "volume-unmounted")
+
+    def _mounted_unmounted_cb(self, sender, volume, signalname):
+        self.emit(signalname, volume.get_hal_udi())
+
+    def get_mounted_volumes(self):
+        return [volume.get_hal_udi() for volume in self._impl.get_mounted_volumes()]
+
+    def volume_is_removable(self, path):
+        return self._impl.get_volume_for_path(path).is_user_visible()
 
-def monitor_cancel(monitor_id):
-    gnomevfs.monitor_cancel(monitor_id)
+    def volume_get_fstype(self, path):
+        return self._impl.get_volume_for_path(path).get_filesystem_type()
 
-class VolumeMonitor(gnomevfs.VolumeMonitor):
-    pass
+    def volume_get_root_uri(self, path):
+        return self._impl.get_volume_for_path(path).get_activation_uri()
 
 #
 # Scanner ThreadManager

Modified: trunk/conduit/Web.py
==============================================================================
--- trunk/conduit/Web.py	(original)
+++ trunk/conduit/Web.py	Tue Jul 29 12:38:09 2008
@@ -5,244 +5,30 @@
 import sys
 import os
 import gobject
-import webbrowser
 import time
 import thread
 import logging
 log = logging.getLogger("Web")
 
 import conduit
+import conduit.utils.Singleton as Singleton
+import conduit.platform.WebBrowserSystem as WebBrowserSystem
 
-def open_url(url):
-    log.debug("Opening %s" % url)
-    webbrowser.open(url,new=1,autoraise=True)
-    log.debug("Opened %s" % url)
-
-class _WebBrowser(gobject.GObject):
-    """
-    Basic webbrowser abstraction to provide an upgrade path
-    to webkit from gtkmozembed
-    """
-    __gsignals__ = {
-        "location_changed" : (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
-            gobject.TYPE_STRING]),      # The new location
-        "loading_started" : (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
-        "loading_finished" : (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
-        "loading_progress" : (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
-            gobject.TYPE_FLOAT]),       # -1 (unknown), 0 -> 1 (finished)
-        "status_changed" : (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
-            gobject.TYPE_STRING]),      # The status
-        "open_uri": (
-            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
-            gobject.TYPE_STRING])       # URI
-        }
-    def __init__(self, emitOnIdle=False):
-        gobject.GObject.__init__(self)
-        self.emitOnIdle = emitOnIdle
-        
-    def emit(self, *args):
-        """
-        Override the gobject signal emission so that signals
-        can be emitted from the main loop on an idle handler
-        """
-        if self.emitOnIdle == True:
-            gobject.idle_add(gobject.GObject.emit,self,*args)
-        else:
-            gobject.GObject.emit(self,*args)
-
-    def load_url(self, url):
-        raise NotImplementedError
-
-    def stop_load(self):
-        raise NotImplementedError
-
-class _WebKitBrowser(_WebBrowser):
-    """
-    Sucks less than the shitty gtkMozEmbeded interface
-    """
-
-    def __init__(self):
-        _WebBrowser.__init__(self)
-        if 'webkit' not in sys.modules:
-            import webkit
-            global webkit
-
-        self.webView = webkit.WebView()
-
-    def widget(self):
-        return self.webView
- 
-    def load_url(self,url):
-        self.webView.open(url)
-
-    def stop_load(self):
-        self.webView.stop_loading()
-
-class _MozEmbedWebBrowser(_WebBrowser):
+class LoginWindow(Singleton.Singleton):
     """
-    Wraps the GTK embeddable Mozilla in the WebBrowser interface
+    The ConduitLogin object needs to be a singleton so that we
+    only have one window with multiple tabs, and so we can guarentee
+    that it runs in the GUI thread
     """
-    
-    PROFILE = 'default'
-    
-    def __init__(self):
-        _WebBrowser.__init__(self)
-
-        #lazy import and other hoops necessary because
-        #set_profile path must be the first call to gtkmozembed
-        #after it is imported, otherwise it crashes..
-        if 'gtkmozembed' not in sys.modules:
-            import gtkmozembed
-            global gtkmozembed
-            
-            #setup the mozilla environment
-            profdir = self._get_profile_subdir()
-            self._create_prefs_js()
-            gtkmozembed.set_profile_path(profdir, self.PROFILE)
-
-        self.url_load_request = False # flag to break load_url recursion
-        self.location = ""
-
-        self.moz = gtkmozembed.MozEmbed()
-        self.moz.connect("link-message", self._signal_link_message)
-        self.moz.connect("open-uri", self._signal_open_uri)
-        self.moz.connect("location", self._signal_location)
-        self.moz.connect("progress", self._signal_progress)
-        self.moz.connect("net-start", self._signal_net_start)
-        self.moz.connect("net-stop", self._signal_net_stop)
-        
-    def _get_profile_subdir(self):
-        """
-        Some webbrowsers need a profile dir. Make it if
-        it doesnt exist
-        """
-        subdir = os.path.join(conduit.USER_DIR, 'mozilla')
-        profdir = os.path.join(subdir, self.PROFILE)
-        if not os.access(profdir, os.F_OK):
-            os.makedirs(profdir)
-        return subdir
-        
-    def _create_prefs_js(self):
-        """
-        Create the file prefs.js in the mozilla profile directory.  This
-        file does things like turn off the warning when navigating to https pages.
-        """
-        prefsContent = """\
-# Mozilla User Preferences
-user_pref("security.warn_entering_secure", false);
-user_pref("security.warn_entering_weak", false);
-user_pref("security.warn_viewing_mixed", false);
-user_pref("security.warn_leaving_secure", false);
-user_pref("security.warn_submit_insecure", false);
-user_pref("security.warn_entering_secure.show_once", false);
-user_pref("security.warn_entering_weak.show_once", false);
-user_pref("security.warn_viewing_mixed.show_once", false);
-user_pref("security.warn_leaving_secure.show_once", false);
-user_pref("security.warn_submit_insecure.show_once", false);
-user_pref("security.enable_java", false);
-user_pref("browser.xul.error_pages.enabled", false);
-user_pref("general.useragent.vendor", "%s");
-user_pref("general.useragent.vendorSub", "%s");
-user_pref("general.useragent.vendorComment", "%s");
-""" % ("Conduit",conduit.VERSION,"http://www.conduit-project.org";)
-
-        if conduit.GLOBALS.settings.proxy_enabled():
-            log.info("Setting mozilla proxy details")
-            host,port,user,password = conduit.GLOBALS.settings.get_proxy()
-            prefsContent += """\
-user_pref("network.proxy.type", 1);
-user_pref("network.proxy.http", "%s");
-user_pref("network.proxy.http_port", %d);
-user_pref("network.proxy.ssl", "%s");
-user_pref("network.proxy.ssl_port", %s);
-user_pref("network.proxy.share_proxy_settings", true);
-""" % (host,port,host,port)
-
-        prefsPath = os.path.join(self._get_profile_subdir(),self.PROFILE,'prefs.js')
-        f = open(prefsPath, "wt")
-        f.write(prefsContent)
-        f.close()
-
-    def widget(self):
-        return self.moz
-
-    def load_url(self, str):
-        self.url_load_request = True  # don't handle open-uri signal
-        self.moz.load_url(str)        # emits open-uri signal
-        self.url_load_request = False # handle open-uri again
-
-    def stop_load(self):
-        self.moz.stop_load()
-
-    def _signal_link_message(self, object):
-        self.emit("status_changed", self.moz.get_link_message())
-
-    def _signal_open_uri(self, object, uri):
-        if self.url_load_request: 
-            return False # proceed as requested
-        else:
-            return self.emit("open_uri", uri)
-        
-    def _signal_location(self, object):
-        self.location_changed(self.moz.get_location())
-
-    def location_changed(self, location):
-        self.location = location
-        self.emit("location_changed",self.location)
-
-    def _signal_progress(self, object, cur, maxim):
-        if maxim < 1:
-            self.emit("loading_progress", -1.0)
-        else:
-            self.emit("loading_progress", (cur/maxim))
-
-    def _signal_net_start(self, object):
-        self.emit("loading_started")
-
-    def _signal_net_stop(self, object):
-        self.emit("loading_finished")
-
-    def __del__(self):
-        log.warn("IF WEIRD THINGS HAPPEN ITS BECAUSE I WAS GC'd TOO EARLY")
-
-class _SystemLogin(object):
-    def __init__ (self):
-        pass
-        
-    def wait_for_login(self, name, url, **kwargs):
-        self.testFunc = kwargs.get("login_function",None)
-        self.timeout = kwargs.get("timeout",30)
-    
-        #use the system web browerser to open the url
-        log.debug("System Login for %s" % name)
-        open_url(url)
-
-        start_time = time.time()
-        while not self._is_timed_out(start_time):
-            time.sleep(kwargs.get("sleep_time",2))        
-            try:
-                if self.testFunc():
-                    return
-            except Exception, e:
-                log.debug("Login function threw an error: %s" % e)
-
-        raise Exception("Login timed out")
-
-    def _is_timed_out(self, start):
-        return int(time.time() - start) > self.timeout
 
-class _ConduitLoginSingleton(object):
     def __init__(self):
         self.window = None
         self.notebook = None
         self.pages = {}
         self.finished = {}
 
+        log.debug("Created Conduit login window")
+
     def _on_window_closed(self, *args):
         for url in self.pages.keys():
             self._delete_page(url)
@@ -251,16 +37,6 @@
     def _on_tab_close_clicked(self, button, url):
         self._delete_page(url)
             
-    def _build_browser(self, browserName):
-        if browserName == "gtkmozembed":
-            browser = _MozEmbedWebBrowser()
-        elif browserName == "webkit":
-            browser = _WebKitBrowser()
-        else:
-            raise Exception("Unknown browser: %s" % browserName)
-
-        return browser
-
     def _on_open_uri(self, *args):
         log.debug("LINK CLICKED (thread: %s)" % thread.get_ident())
 
@@ -282,7 +58,7 @@
         #notify 
         self.finished[url] = True
 
-    def _create_page(self, name, url, browserName):
+    def _create_page(self, name, url, browserImplKlass):
         log.debug("CREATE PAGE: %s (thread: %s)" % (url,thread.get_ident()))
         if url in self.pages:
             return False
@@ -302,7 +78,7 @@
         self.window.show_all()
 
         #create object and connect signals
-        browser = self._build_browser(browserName)
+        browser = browserImplKlass()
         browser.connect("open_uri",self._on_open_uri)
         
         #create the tab label
@@ -352,8 +128,7 @@
         if url in self.pages:
             gobject.idle_add(self._raise_page, url)
         else:
-            browserName = kwargs.get("browser",conduit.GLOBALS.settings.get("web_login_browser"))
-            gobject.idle_add(self._create_page, name, url, browserName)
+            gobject.idle_add(self._create_page, name, url, kwargs["browserImplKlass"])
             self.finished[url] = False
 
         while not self.finished[url] and not conduit.GLOBALS.cancelled:
@@ -370,11 +145,6 @@
         else:
             raise Exception("Login failure")
             
-#The ConduitLogin object needs to be a singleton so that we
-#only have one window with multiple tabs, and so we can guarentee
-#that it runs in the GUI thread
-_ConduitLogin = _ConduitLoginSingleton()
-
 class LoginMagic(object):
     """
     Performs all the magic to log into a website to authenticate. Uses
@@ -383,12 +153,30 @@
     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)
+
         #instantiate the browser
         if browser == "system":
-            login = _SystemLogin()
+            login = WebBrowserSystem.WebBrowserImpl()
         else:
-            login = _ConduitLogin
+            try:
+                if browser == "gtkmozembed":
+                    from conduit.platform.WebBrowserMozilla import WebBrowserImpl
+                elif browser == "webkit":
+                    from conduit.platform.WebBrowserWebkit import WebBrowserImpl
+                else:
+                    log.warn("Unknown browser type")
+                    return
+
+                kwargs["browserImplKlass"] = WebBrowserImpl
+                login = LoginWindow()
 
-        #blocks/times out until the user logs in or gives up        
-        login.wait_for_login(name, url, **kwargs)
+            except ImportError:
+                login = None
+
+        if login:
+            #blocks/times out until the user logs in or gives up        
+            login.wait_for_login(name, url, **kwargs)
+        else:
+            log.warn("Error setting up browser")
+            
 

Modified: trunk/conduit/__init__.py
==============================================================================
--- trunk/conduit/__init__.py	(original)
+++ trunk/conduit/__init__.py	Tue Jul 29 12:38:09 2008
@@ -52,6 +52,7 @@
     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"
 
 import Globals
 GLOBALS = Globals.Globals()

Modified: trunk/conduit/dataproviders/VolumeFactory.py
==============================================================================
--- trunk/conduit/dataproviders/VolumeFactory.py	(original)
+++ trunk/conduit/dataproviders/VolumeFactory.py	Tue Jul 29 12:38:09 2008
@@ -26,9 +26,8 @@
         else:
             log.warn("HAL Could not be Initialized")
 
-    def _volume_mounted_cb(self, monitor, volume):
-        log.info("Volume mounted")
-        device_udi = volume.get_hal_udi()
+    def _volume_mounted_cb(self, monitor, device_udi):
+        log.info("Volume mounted, udi: %s" % device_udi)
         if device_udi :
             props = self._get_properties(device_udi)
             if self.is_interesting(device_udi, props):
@@ -37,12 +36,10 @@
                 self.item_added(device_udi, **kwargs)
         return True
 
-    def _volume_unmounted_cb(self, monitor, volume):
-        log.info("Volume Umounted: %s" % volume.get_hal_udi())
-        device_udi = volume.get_hal_udi()
+    def _volume_unmounted_cb(self, monitor, device_udi):
+        log.info("Volume mounted, udi: %s" % device_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
 
@@ -72,8 +69,7 @@
         """
         Called after VolumeFactory is initialised to detect already connected volumes
         """
-        for volume in self.vol_monitor.get_mounted_volumes():
-            device_udi = volume.get_hal_udi()
+        for device_udi in self.vol_monitor.get_mounted_volumes():
             if device_udi != None:
                 props = self._get_properties(device_udi)
                 if self.is_interesting(device_udi, props):

Modified: trunk/conduit/defs.py.in
==============================================================================
--- trunk/conduit/defs.py.in	(original)
+++ trunk/conduit/defs.py.in	Tue Jul 29 12:38:09 2008
@@ -5,5 +5,5 @@
 LOCALE_DIR = "@LOCALEDIR@"
 SHARED_DATA_DIR = "@PKGDATADIR@"
 SHARED_MODULE_DIR = "@MODULEDIR@"
-
+SETTINGS_IMPL = "GConf"
 

Modified: trunk/conduit/modules/FileModule/FileModule.py
==============================================================================
--- trunk/conduit/modules/FileModule/FileModule.py	(original)
+++ trunk/conduit/modules/FileModule/FileModule.py	Tue Jul 29 12:38:09 2008
@@ -79,12 +79,11 @@
                 self.DEFAULT_FOLLOW_SYMLINKS
                 )
         AutoSync.AutoSync.__init__(self)
-        self._monitor_folder_id = None
+        self._monitor = Vfs.FileMonitor()
+        self._monitor.connect("changed", self._monitor_folder_cb)
 
     def __del__(self):
-        if self._monitor_folder_id != None:
-            Vfs.monitor_cancel(self._monitor_folder_id)
-            self._monitor_folder_id = None
+        self._monitor.cancel()
             
     def configure(self, window):
         Utils.dataprovider_add_dir_to_path(__file__, "")
@@ -120,22 +119,18 @@
         return Vfs.uri_get_filename(self.folder)
 
     def _monitor_folder(self):
-        if self._monitor_folder_id != None:
-            Vfs.monitor_cancel(self._monitor_folder_id)
-            self._monitor_folder_id = None
+        self._monitor.add(self.folder, self._monitor.MONITOR_DIRECTORY)
 
-        self._monitor_folder_id = Vfs.monitor_add(self.folder, Vfs.MONITOR_DIRECTORY, self._monitor_folder_cb)            
-
-    def _monitor_folder_cb(self, monitor_uri, event_uri, event, data=None):
+    def _monitor_folder_cb(self, sender, monitor_uri, event_uri, event):
         """
         Called when a file in the current folder is changed, added or deleted
         """
         # supported events = CHANGED, DELETED, CREATED
-        if event == Vfs.MONITOR_EVENT_CREATED:
+        if event == self._monitor.MONITOR_EVENT_CREATED:
             self.handle_added(event_uri)
-        elif event == Vfs.MONITOR_EVENT_CHANGED:
+        elif event == self._monitor.MONITOR_EVENT_CHANGED:
             self.handle_modified(event_uri)
-        elif event == Vfs.MONITOR_EVENT_DELETED:
+        elif event == self._monitor.MONITOR_EVENT_DELETED:
             self.handle_deleted(event_uri)
 
 class RemovableDeviceFactory(VolumeFactory.VolumeFactory):

Added: trunk/conduit/platform/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/Makefile.am	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,11 @@
+conduitdir = $(pythondir)/conduit/platform
+conduit_PYTHON = \
+	__init__.py				\
+	SettingsGConf.py		\
+	SettingsPython.py		\
+	WebBrowserWebkit.py		\
+	WebBrowserSystem.py		\
+	WebBrowserMozilla.py
+	
+clean-local:
+	rm -rf *.pyc *.pyo

Added: trunk/conduit/platform/SettingsGConf.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/SettingsGConf.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,185 @@
+import re
+import os
+
+try:
+    import gconf
+except ImportError:
+    from gnome import gconf
+
+import conduit.platform
+
+import logging
+log = logging.getLogger("Settings")
+    
+class SettingsImpl(conduit.platform.Settings):
+    """
+    Settings implementation which stores settings in GConf
+    """
+    
+    CONDUIT_GCONF_DIR = "/apps/conduit/"
+    VALID_KEY_TYPES = (bool, str, int, list, tuple)
+    
+    def __init__(self, defaults, changedCb):
+        conduit.platform.Settings.__init__(self, defaults, changedCb)
+
+        self._client = gconf.client_get_default()
+        self._client.add_dir(self.CONDUIT_GCONF_DIR[:-1], gconf.CLIENT_PRELOAD_RECURSIVE)  
+        self._notifications = []
+
+    def _fix_key(self, key):
+        """
+        Appends the CONDUIT_GCONF_PREFIX to the key if needed
+        
+        @param key: The key to check
+        @type key: C{string}
+        @returns: The fixed key
+        @rtype: C{string}
+        """
+        if not key.startswith(self.CONDUIT_GCONF_DIR):
+            return self.CONDUIT_GCONF_DIR + key
+        else:
+            return key
+            
+    def _key_changed(self, client, cnxn_id, entry, data=None):
+        """
+        Callback when a gconf key changes
+        """
+        key = self._fix_key(entry.key)
+        self._changedCb(key)
+        
+    def get(self, key, default=None):
+        """
+        Returns the value of the key or the default value if the key is 
+        not yet in gconf
+        """
+        #check if the setting has been overridden for this session
+        if key in self._overrides:
+            try:
+                #try and cast to correct type
+                return type(self._defaults[key])(self._overrides[key])
+            except:
+                return self._overrides[key]
+
+        #function arguments override defaults
+        if default == None:
+            default = self._defaults.get(key, None)
+        vtype = type(default)
+
+        #we now have a valid key and type
+        if default == None:
+            log.warn("Unknown key: %s, must specify default value" % key)
+            return None
+
+        if vtype not in self.VALID_KEY_TYPES:
+            log.warn("Invalid key type: %s" % vtype)
+            return None
+
+        #for gconf refer to the full key path
+        key = self._fix_key(key)
+
+        if key not in self._notifications:
+            self._client.notify_add(key, self._key_changed)
+            self._notifications.append(key)
+        
+        value = self._client.get(key)
+        if not value:
+            self.set(key, default)
+            return default
+
+        if vtype is bool:
+            return value.get_bool()
+        elif vtype is str:
+            return value.get_string()
+        elif vtype is int:
+            return value.get_int()
+        elif vtype in (list, tuple):
+            l = []
+            for i in value.get_list():
+                l.append(i.get_string())
+            return l
+            
+        log.warn("Unknown gconf key: %s" % key)
+        return None
+
+    def set(self, key, value):
+        """
+        Sets the key value in gconf and connects adds a signal 
+        which is fired if the key changes
+        """
+        #overidden settings only apply for this session, and are
+        #not set
+        if key in self._overrides:
+            return True
+
+        log.debug("Settings %s -> %s" % (key, value))
+        if key in self._defaults:
+            vtype = type(self._defaults[key])
+        else:
+            vtype = type(value)
+
+        if vtype not in self.VALID_KEY_TYPES:
+            log.warn("Invalid key type: %s" % vtype)
+            return False
+
+        #for gconf refer to the full key path
+        key = self._fix_key(key)
+
+        if vtype is bool:
+            self._client.set_bool(key, value)
+        elif vtype is str:
+            self._client.set_string(key, value)
+        elif vtype is int:
+            self._client.set_int(key, value)
+        elif vtype in (list, tuple):
+            #Save every value as a string
+            strvalues = [str(i) for i in value]
+            self._client.set_list(key, gconf.VALUE_STRING, strvalues)
+
+        return True
+        
+    def proxy_enabled(self):
+        """
+        @returns: True if the user has specified a http proxy via
+        the http_proxy environment variable, or in gconf
+        """
+        return os.environ.has_key("http_proxy") or \
+                self._client.get_bool("/system/http_proxy/use_http_proxy")
+        
+    def get_proxy(self):
+        """
+        Returns the details of the configured http proxy. 
+        The http_proxy environment variable overrides the GNOME setting
+        @returns: host,port,user,password
+        """
+        if self.proxy_enabled():
+            #env vars have preference
+            if os.environ.has_key("http_proxy"):
+                #re taken from python boto
+                pattern = re.compile(
+                    '(?:http://)?' \
+                    '(?:(?P<user>\w+):(?P<pass>.*)@)?' \
+                    '(?P<host>[\w\-\.]+)' \
+                    '(?::(?P<port>\d+))?'
+                )
+                match = pattern.match(os.environ['http_proxy'])
+                if match:
+                    return (match.group('host'),
+                            int(match.group('port')),
+                            match.group('user'),
+                            match.group('pass'))
+            #now try gconf
+            if self._client.get_bool("/system/http_proxy/use_authentication"):
+                return (self._client.get_string("/system/http_proxy/host"),
+                        self._client.get_int("/system/http_proxy/port"),
+                        self._client.get_string("/system/http_proxy/authentication_user"),
+                        self._client.get_string("/system/http_proxy/authentication_password"))
+            else:
+                return (self._client.get_string("/system/http_proxy/host"),
+                        self._client.get_int("/system/http_proxy/port"),
+                        "",
+                        "")
+
+        return ("",0,"","")
+
+
+

Added: trunk/conduit/platform/SettingsPython.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/SettingsPython.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,115 @@
+import os
+import re
+import ConfigParser
+
+import conduit.platform
+
+import logging
+log = logging.getLogger("Settings")
+
+class SettingsImpl(conduit.platform.Settings):
+    """
+    Settings implementation which stores settings in an ini style
+    format using python config parser library
+    """
+
+    VALID_KEY_TYPES = (bool, str, int, list, tuple)
+
+    def __init__(self, defaults, changedCb):
+        conduit.platform.Settings.__init__(self, defaults, changedCb)
+
+        self._filePath = os.path.join(conduit.USER_DIR,"settings.ini")
+
+        #convert defaults to strings
+        strDefaults = {}
+        for k,v in defaults.items():
+            strDefaults[k] = str(v)
+
+        self._config = ConfigParser.ConfigParser(defaults=strDefaults)
+        self._config.read(self._filePath)
+
+    def get(self, key, default=None):
+        #check if the setting has been overridden for this session
+        if key in self._overrides:
+            val = self._overrides[key]
+        else:
+            try:
+                val = self._config.get('DEFAULT',key)
+            except ConfigParser.NoOptionError:
+                val = default
+
+        #config parser saves everything to strings, so rely on the defaults
+        #for the type information
+        if key in self._defaults:
+            vtype = type(self._defaults[key])
+        else:
+            vtype = type(val)
+
+        if val == None:
+            log.warn("Unknown key: %s, must specify default value" % key)
+            return None
+
+        if vtype not in self.VALID_KEY_TYPES:
+            log.warn("Invalid key type: %s" % vtype)
+            return None
+
+        #convert list/tuple to list of string values
+        if vtype in (list, tuple):
+            return eval(val)
+        #cast simple types
+        else:
+            return vtype(val)
+
+    def set(self, key, value):
+        if key in self._overrides:
+            return True
+
+        if key in self._defaults:
+            vtype = type(self._defaults[key])
+        else:
+            vtype = type(value)
+
+        if vtype not in self.VALID_KEY_TYPES:
+            log.warn("Invalid key type: %s" % vtype)
+            return False
+
+        #Save every value as a string
+        self._config.set('DEFAULT',key, str(value))
+        return True
+
+    def proxy_enabled(self):
+        """
+        @returns: True if the user has specified a http proxy via
+        the http_proxy environment variable
+        """
+        return os.environ.has_key("http_proxy")
+        
+    def get_proxy(self):
+        """
+        Returns the details of the configured http proxy. 
+        The http_proxy environment variable overrides the GNOME setting
+        @returns: host,port,user,password
+        """
+        if self.proxy_enabled():
+            #env vars have preference
+            if os.environ.has_key("http_proxy"):
+                #re taken from python boto
+                pattern = re.compile(
+                    '(?:http://)?' \
+                    '(?:(?P<user>\w+):(?P<pass>.*)@)?' \
+                    '(?P<host>[\w\-\.]+)' \
+                    '(?::(?P<port>\d+))?'
+                )
+                match = pattern.match(os.environ['http_proxy'])
+                if match:
+                    return (match.group('host'),
+                            int(match.group('port')),
+                            match.group('user'),
+                            match.group('pass'))
+        return ("",0,"","")
+
+    def save(self):
+        fp = open(self._filePath, 'w')
+        self._config.write(fp)
+        fp.close()
+

Added: trunk/conduit/platform/WebBrowserMozilla.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/WebBrowserMozilla.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,139 @@
+import os.path
+import gtkmozembed
+
+import logging
+log = logging.getLogger("WebBrowser")
+
+import conduit.platform
+import conduit.utils.Singleton as Singleton
+
+class _MozConfig(Singleton.Singleton):
+    """
+    A Singleton whose only responsibilty is to configure gtkmozembed to
+    use the correct profile path. Gtkmozembed only allows its profile
+    path to be set once
+    """
+
+    DEFAULT_PROFILE = 'default'
+
+    def __init__(self, **kwargs):
+        self._profile = kwargs.get('profile', self.DEFAULT_PROFILE)
+        self._profileDir = kwargs.get('profileDir', self._get_profile_subdir())
+
+        log.info("Configuring Mozilla profile dir")
+
+        self._create_prefs_js()
+        gtkmozembed.set_profile_path(self._profileDir, self._profile)
+
+    def _get_profile_subdir(self):
+        """
+        Some webbrowsers need a profile dir. Make it if
+        it doesnt exist
+        """
+        subdir = os.path.join(conduit.USER_DIR, 'mozilla')
+        profdir = os.path.join(subdir, self._profile)
+        if not os.access(profdir, os.F_OK):
+            os.makedirs(profdir)
+        return subdir
+
+    def _create_prefs_js(self):
+        """
+        Create the file prefs.js in the mozilla profile directory.  This
+        file does things like turn off the warning when navigating to https pages.
+        """
+        prefsContent = """\
+# Mozilla User Preferences
+user_pref("security.warn_entering_secure", false);
+user_pref("security.warn_entering_weak", false);
+user_pref("security.warn_viewing_mixed", false);
+user_pref("security.warn_leaving_secure", false);
+user_pref("security.warn_submit_insecure", false);
+user_pref("security.warn_entering_secure.show_once", false);
+user_pref("security.warn_entering_weak.show_once", false);
+user_pref("security.warn_viewing_mixed.show_once", false);
+user_pref("security.warn_leaving_secure.show_once", false);
+user_pref("security.warn_submit_insecure.show_once", false);
+user_pref("security.enable_java", false);
+user_pref("browser.xul.error_pages.enabled", false);
+user_pref("general.useragent.vendor", "%s");
+user_pref("general.useragent.vendorSub", "%s");
+user_pref("general.useragent.vendorComment", "%s");
+""" % ("Conduit",conduit.VERSION,"http://www.conduit-project.org";)
+
+        if conduit.GLOBALS.settings.proxy_enabled():
+            log.info("Setting mozilla proxy details")
+            host,port,user,password = conduit.GLOBALS.settings.get_proxy()
+            prefsContent += """\
+user_pref("network.proxy.type", 1);
+user_pref("network.proxy.http", "%s");
+user_pref("network.proxy.http_port", %d);
+user_pref("network.proxy.ssl", "%s");
+user_pref("network.proxy.ssl_port", %s);
+user_pref("network.proxy.share_proxy_settings", true);
+""" % (host,port,host,port)
+
+        prefsPath = os.path.join(self._profileDir,self._profile,'prefs.js')
+        f = open(prefsPath, "wt")
+        f.write(prefsContent)
+        f.close()
+
+class WebBrowserImpl(conduit.platform.WebBrowser):
+    """
+    Wraps the GTK embeddable Mozilla in the WebBrowser interface
+    """
+    def __init__(self, **kwargs):
+        conduit.platform.WebBrowser.__init__(self)
+
+        #lazy import and other hoops necessary because
+        self._mozconfig = _MozConfig(**kwargs)
+
+        self.url_load_request = False # flag to break load_url recursion
+        self.location = ""
+
+        self.moz = gtkmozembed.MozEmbed()
+        self.moz.connect("link-message", self._signal_link_message)
+        self.moz.connect("open-uri", self._signal_open_uri)
+        self.moz.connect("location", self._signal_location)
+        self.moz.connect("progress", self._signal_progress)
+        self.moz.connect("net-start", self._signal_net_start)
+        self.moz.connect("net-stop", self._signal_net_stop)
+        
+    def widget(self):
+        return self.moz
+
+    def load_url(self, str):
+        self.url_load_request = True  # don't handle open-uri signal
+        self.moz.load_url(str)        # emits open-uri signal
+        self.url_load_request = False # handle open-uri again
+
+    def stop_load(self):
+        self.moz.stop_load()
+
+    def _signal_link_message(self, object):
+        self.emit("status_changed", self.moz.get_link_message())
+
+    def _signal_open_uri(self, object, uri):
+        if self.url_load_request: 
+            return False # proceed as requested
+        else:
+            return self.emit("open_uri", uri)
+        
+    def _signal_location(self, object):
+        self.location_changed(self.moz.get_location())
+
+    def location_changed(self, location):
+        self.location = location
+        self.emit("location_changed",self.location)
+
+    def _signal_progress(self, object, cur, maxim):
+        if maxim < 1:
+            self.emit("loading_progress", -1.0)
+        else:
+            self.emit("loading_progress", (cur/maxim))
+
+    def _signal_net_start(self, object):
+        self.emit("loading_started")
+
+    def _signal_net_stop(self, object):
+        self.emit("loading_finished")
+

Added: trunk/conduit/platform/WebBrowserSystem.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/WebBrowserSystem.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,34 @@
+import time
+import webbrowser
+import logging
+log = logging.getLogger("WebBrowser")
+
+import conduit.platform
+
+class WebBrowserImpl(conduit.platform.WebBrowser):
+    def __init__(self, **kwargs):
+        conduit.platform.WebBrowser.__init__(self)
+
+    def wait_for_login(self, name, url, **kwargs):
+        self.testFunc = kwargs.get("login_function",None)
+        self.timeout = kwargs.get("timeout",30)
+    
+        #use the system web browerser to open the url
+        log.debug("System Login for %s" % name)
+        webbrowser.open(url,new=1,autoraise=True)
+
+        start_time = time.time()
+        while not self._is_timed_out(start_time):
+            time.sleep(kwargs.get("sleep_time",2))        
+            try:
+                if self.testFunc():
+                    return
+            except Exception, e:
+                log.debug("Login function threw an error: %s" % e)
+
+        raise Exception("Login timed out")
+
+    def _is_timed_out(self, start):
+        return int(time.time() - start) > self.timeout
+
+

Added: trunk/conduit/platform/WebBrowserWebkit.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/WebBrowserWebkit.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,17 @@
+import webkit
+import conduit.platform
+
+class WebBrowserImpl(conduit.platform.WebBrowser):
+    def __init__(self):
+        conduit.platform.WebBrowser.__init__(self)
+        self.webView = webkit.WebView()
+
+    def widget(self):
+        return self.webView
+ 
+    def load_url(self,url):
+        self.webView.open(url)
+
+    def stop_load(self):
+        self.webView.stop_loading()
+

Added: trunk/conduit/platform/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/platform/__init__.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,70 @@
+import gobject
+
+class Settings:
+
+    def __init__(self, defaults, changedCb):
+        self._defaults = defaults
+        self._changedCb = changedCb
+        self._overrides = {}
+
+    def get(self, key, **kwargs):
+        return None
+
+    def set(self, key, val, **kwargs):
+        return False
+        
+    def set_overrides(self, **overrides):
+        self._overrides = overrides
+        
+    def proxy_enabled(self):
+        return False
+        
+    def get_proxy(self):
+        return ("",0,"","")
+
+    def save(self):
+        pass
+    
+class WebBrowser(gobject.GObject):
+    """
+    Basic webbrowser abstraction to provide an upgrade path
+    to webkit from gtkmozembed
+    """
+    __gsignals__ = {
+        "location_changed" : (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING]),      # The new location
+        "loading_started" : (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
+        "loading_finished" : (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
+        "loading_progress" : (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_FLOAT]),       # -1 (unknown), 0 -> 1 (finished)
+        "status_changed" : (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING]),      # The status
+        "open_uri": (
+            gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+            gobject.TYPE_STRING])       # URI
+        }
+    def __init__(self, emitOnIdle=False):
+        gobject.GObject.__init__(self)
+        self.emitOnIdle = emitOnIdle
+        
+    def emit(self, *args):
+        """
+        Override the gobject signal emission so that signals
+        can be emitted from the main loop on an idle handler
+        """
+        if self.emitOnIdle == True:
+            gobject.idle_add(gobject.GObject.emit,self,*args)
+        else:
+            gobject.GObject.emit(self,*args)
+
+    def load_url(self, url):
+        raise NotImplementedError
+
+    def stop_load(self):
+        raise NotImplementedError
+

Added: trunk/conduit/utils/Bluetooth.py
==============================================================================
--- (empty file)
+++ trunk/conduit/utils/Bluetooth.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,83 @@
+import threading
+import logging
+log = logging.getLogger("utils.Bluetooth")
+
+try:
+    import bluetooth
+    BLUETOOTH_AVAILABLE = True
+
+    class _DeviceDiscovererFilter(bluetooth.DeviceDiscoverer):
+        def __init__(self):
+            bluetooth.DeviceDiscoverer.__init__(self)
+            self._found = {}
+
+        def device_discovered(self, address, device_class, name):
+            if not name:
+                name = bluetooth.lookup_name(address)
+            log.debug("Bluetooth Device Discovered: %s %s" % (name,address))
+            self._found[address] = (name, device_class)
+
+        def inquiry_complete(self):
+            log.debug("Bluetooth Search Complete")
+
+        def get_devices(self):
+            return self._found
+
+except:
+    BLUETOOTH_AVAILABLE = False
+
+    class _DeviceDiscovererFilter:
+        def get_devices(self):
+            return {}
+
+import conduit.utils.Thread as Thread
+import conduit.utils.Singleton as Singleton
+
+def is_computer_class(device_class):
+    major_class = ( device_class & 0xf00 ) >> 8
+    return major_class == 1
+
+def is_phone_class(device_class):
+    major_class = ( device_class & 0xf00 ) >> 8
+    return major_class == 2
+
+class BluetoothSearcher(Singleton.Singleton, Thread.PauseCancelThread):
+    def __init__(self):
+        Thread.PauseCancelThread.__init__(self)
+        self._disc = _DeviceDiscovererFilter()
+        self._cbs = {}
+        self._cblock = threading.Lock()
+        
+        self.setDaemon(False)
+        self.start()
+
+    def watch_for_devices(self, cb, class_check_func=is_phone_class):
+        self._cblock.acquire()
+        if cb not in self._cbs:
+            self._cbs[cb] = class_check_func
+        self._cblock.release()
+
+    def call_callbacks(self, address, name, device_class):
+        self._cblock.acquire()
+        #check if any devices are the class
+        for cb, class_check_func in self._cbs.items():
+            if class_check_func(device_class):
+                #call the registered callback
+                cb(address, name)
+        self._cblock.release()
+
+    def get_devices(self):
+        return self._disc.get_devices()
+
+    def run(self):
+        while self.is_cancelled() == False:
+            log.debug("Scanning..")
+
+            self._disc.find_devices()
+            self._disc.process_inquiry()
+            devices = self._disc.get_devices()
+            for address in devices:
+                self.call_callbacks(address, devices[address][0], devices[address][1])
+
+            self.pause()
+

Modified: trunk/conduit/utils/Makefile.am
==============================================================================
--- trunk/conduit/utils/Makefile.am	(original)
+++ trunk/conduit/utils/Makefile.am	Tue Jul 29 12:38:09 2008
@@ -1,8 +1,9 @@
 conduitdir = $(pythondir)/conduit/utils
 conduit_PYTHON = \
-	__init__.py \
-	Memstats.py \
-	CommandLineConverter.py
+	__init__.py 				\
+	Memstats.py 				\
+	CommandLineConverter.py		\
+	Singleton.py
 
 clean-local:
 	rm -rf *.pyc *.pyo

Added: trunk/conduit/utils/Singleton.py
==============================================================================
--- (empty file)
+++ trunk/conduit/utils/Singleton.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- coding: UTF8 -*-
+#
+#  GObjectSingleton.py
+#  Copyright (c) 2006 INdT (Instituto Nokia de Tecnologia)
+#  Author: Eduardo de Barros Lima <eduardo lima indt org br>
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public License as
+#  published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+#  USA
+
+import gobject
+
+class GObjectSingleton(gobject.GObjectMeta):
+
+    def __init__(cls, name, base, dict):
+        gobject.GObjectMeta.__init__(cls, name, base, dict)
+        cls.__instance = None
+        cls.__copy__ = lambda self: self
+        cls.__deepcopy__ = lambda self, memo=None: self
+
+    def __call__(cls, *args, **kwargs):
+        if not cls.__instance:
+            cls.__instance = super(GObjectSingleton, cls).__call__(*args, **kwargs)
+        return cls.__instance
+
+class Singleton:
+    """
+    A model that implements the Singleton pattern.
+    """
+
+    __metaclass__ = GObjectSingleton
+
+    pass
+

Added: trunk/conduit/utils/Thread.py
==============================================================================
--- (empty file)
+++ trunk/conduit/utils/Thread.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,26 @@
+import time
+import threading
+import conduit
+
+class PauseCancelThread(threading.Thread):
+    SLEEP_TIME = 20
+    SLEEP = 0.1
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self._cancelled = False
+
+    def run(self):
+        raise NotImplementedError
+
+    def pause(self):
+        i = 0
+        while ( i < (self.SLEEP_TIME/self.SLEEP) ) and ( self.is_cancelled() == False ):
+            time.sleep(self.SLEEP)
+            i += 1
+
+    def is_cancelled(self):
+        return conduit.GLOBALS.cancelled or self._cancelled
+
+    def cancel(self):
+        self._cancelled = True
+

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Tue Jul 29 12:38:09 2008
@@ -133,6 +133,7 @@
 conduit/dataproviders/Makefile
 conduit/gtkui/Makefile
 conduit/hildonui/Makefile
+conduit/platform/Makefile
 conduit/modules/Makefile
 conduit/modules/BackpackModule/Makefile
 conduit/modules/BackpackModule/backpack/Makefile

Modified: trunk/test/python-tests/TestCoreSettings.py
==============================================================================
--- trunk/test/python-tests/TestCoreSettings.py	(original)
+++ trunk/test/python-tests/TestCoreSettings.py	Tue Jul 29 12:38:09 2008
@@ -5,8 +5,6 @@
 
 import conduit.Settings as Settings
 
-s = Settings.Settings()
-
 #Value not important. Needed to compare TYPES
 SETTINGS = {
         'gui_expanded_rows'         :   [],
@@ -15,27 +13,41 @@
         'web_login_browser'         :   "system"
 }
 
-for k,v in SETTINGS.items():
-    val = s.get(k)
-    ok("Settings returned correct type (%s) for %s" % (type(val),k), type(val) == type(v))
-    i = s.set(k,val)
-    ok("Save setting %s OK" % k, i)
-
-#Override defaults
-val = s.get("foo",vtype=str,default="bar")
-ok("Defaults function params override defaults", type(val) == str and val == "bar")
-
-#test error paths
-i = s.set("foo",lambda x: x)
-ok("Unknown types not saved", i == False)
-
-#test error paths
-i = s.get("foo")
-ok("Unknown keys not fetched", i == None)
-
-#Test proxy
-os.environ['http_proxy'] = "http://foo:bar 132 181 1 1:8080"
-ok("Detect proxy", s.proxy_enabled())
-ok("Parse environment variables proxy", s.get_proxy() == ('132.181.1.1', 8080, 'foo', 'bar'))
+for impl in ("GConf", "Python"):
+    ok("--- TESTING SETTINGS IMPL: %s" % impl, True)
+
+    s = Settings.Settings(implName=impl)
+
+    for k,v in SETTINGS.items():
+        val = s.get(k)
+        ok("Settings returned correct type (%s) for %s" % (type(val),k), type(val) == type(v))
+        i = s.set(k,val)
+        ok("Save setting %s OK" % k, i)
+
+    #Override defaults
+    val = s.get("foo",default="bar")
+    ok("Defaults function params override defaults", type(val) == str and val == "bar")
+
+    #test error paths
+    i = s.set("foo",lambda x: x)
+    ok("Unknown types not saved", i == False)
+
+    i = s.get("foo")
+    ok("Unknown keys not fetched", i == None)
+
+    i = s.get("foo", default=lambda x: x)
+    ok("Unknown keys with invalid defaults not fetched", i == None)
+
+    #Test proxy
+    os.environ['http_proxy'] = "http://foo:bar 132 181 1 1:8080"
+    ok("Detect proxy", s.proxy_enabled())
+    ok("Parse environment variables proxy", s.get_proxy() == ('132.181.1.1', 8080, 'foo', 'bar'))
+
+    #Test overridden settings are not set
+    s.set_overrides(cheese="swiss")
+    orig = s.get('cheese')
+    setOK = s.set('cheese', 'colby')
+    new = s.get('cheese')
+    ok("Overridden settings not saved", setOK == True and orig == new and new == "swiss")
 
 finished()

Modified: trunk/test/python-tests/TestCoreUtil.py
==============================================================================
--- trunk/test/python-tests/TestCoreUtil.py	(original)
+++ trunk/test/python-tests/TestCoreUtil.py	Tue Jul 29 12:38:09 2008
@@ -3,7 +3,9 @@
 import conduit.utils as Utils
 import conduit.utils.Memstats as Memstats
 import conduit.utils.CommandLineConverter as CommandLineConverter
+import conduit.utils.Singleton as Singleton
 
+import random
 import datetime
 import os.path
 import sys
@@ -74,4 +76,16 @@
 info = Utils.get_module_information(sys, 'version_info')
 ok("System Information: %s" % info, len(info) > 0)
 
+class A(Singleton.Singleton):
+    def __init__(self):
+        Singleton.Singleton.__init__(self)
+        self.i = random.random()
+    def num(self):
+        return self.i
+
+a1 = A()
+a2 = A()
+
+ok("Singleton OK", a1 == a2 and a1.num() == a2.num())
+
 finished()

Added: trunk/test/python-tests/TestCoreUtilBluetooth.py
==============================================================================
--- (empty file)
+++ trunk/test/python-tests/TestCoreUtilBluetooth.py	Tue Jul 29 12:38:09 2008
@@ -0,0 +1,35 @@
+#common sets up the conduit environment
+from common import *
+
+import conduit.utils.Bluetooth as Bluetooth
+
+#test the bluetooth searching singleton
+def found_phone(address, name):
+    pass
+
+def found_pc(address, name):
+    pass
+
+a = Bluetooth.BluetoothSearcher()
+b = Bluetooth.BluetoothSearcher()
+
+ok("Bluetooth searcher is singleton", a != None and a == b)
+
+a.watch_for_devices(found_phone)
+b.watch_for_devices(found_phone)
+ok("Registered found_phone function", len(a._cbs) == 1)
+
+b.watch_for_devices(found_pc, class_check_func=Bluetooth.is_computer_class)
+ok("Registered found_pc function", len(a._cbs) == 2)
+
+wait_seconds(2)
+ok("Bluetooth search thread started", a.isAlive())
+
+try:
+    a.cancel()
+    a.join(a.SLEEP_TIME)
+    ok("Cancelled scan (found %d devices)" % len(a.get_devices()), True)
+except Exception:
+    ok("Cancelled scan", False)
+
+finished()

Modified: trunk/test/python-tests/TestCoreVfs.py
==============================================================================
--- trunk/test/python-tests/TestCoreVfs.py	(original)
+++ trunk/test/python-tests/TestCoreVfs.py	Tue Jul 29 12:38:09 2008
@@ -16,7 +16,9 @@
 ok("Get filename (%s,%s)" % (name,ext), name == "bar" and ext == ".ext")
 ok("file:///home exists", Vfs.uri_exists("file:///home") == True)
 ok("/home exists", Vfs.uri_exists("/home") == True)
+ok("/home is folder", Vfs.uri_is_folder("/home") == True)
 ok("/foo/bar does not exist", Vfs.uri_exists("/foo/bar") == False)
+ok("format uri", Vfs.uri_format_for_display("file:///foo") == "/foo")
 
 tmpdiruri = Utils.new_tempdir()
 newtmpdiruri = Vfs.uri_join(tmpdiruri, "foo", "bar", "baz")
@@ -52,7 +54,7 @@
 
 removableUri = get_external_resources('folder')['removable-volume']
 ok("Removable volume detected removable", Vfs.uri_is_on_removable_volume(removableUri))
-ok("Removable volume calculate root path", Vfs.uri_get_volume_root_uri(removableUri) == "file:///media/media")
+ok("Removable volume calculate root path", Vfs.uri_get_volume_root_uri(removableUri).startswith("file:///media/"))
 
 URIS_TO_JOIN = (
     (   ("file:///foo/bar","gax","ssss"),   

Modified: trunk/test/python-tests/common.py
==============================================================================
--- trunk/test/python-tests/common.py	(original)
+++ trunk/test/python-tests/common.py	Tue Jul 29 12:38:09 2008
@@ -14,7 +14,7 @@
 base_path = os.path.abspath(os.path.join(my_path, '..', '..'))
 sys.path.insert(0, base_path)
 
-# import main conduit module and datatypes
+# import main conduit modules
 import conduit
 import conduit.Logging as Logging
 import conduit.utils as Utils
@@ -26,6 +26,9 @@
 import conduit.Conduit as Conduit
 import conduit.SyncSet as SyncSet
 import conduit.MappingDB as MappingDB
+import conduit.Settings as Settings
+
+#import conduit datatypes
 from conduit.datatypes import File, Note, Setting, Contact, Email, Text, Video, Photo, Audio, Event, Bookmark
 from conduit.modules import TestModule
 
@@ -34,9 +37,11 @@
 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.SETTINGS_IMPL =             "GConf"
 
 # override some conduit settings. 
 # without a gobject main loop the gtkmozembed browser hangs
+conduit.GLOBALS.settings = Settings.Settings(conduit.SETTINGS_IMPL)
 conduit.GLOBALS.settings.set_overrides(web_login_browser="system")
 
 def is_online():

Modified: trunk/test/python-tests/data/folder.list
==============================================================================
--- trunk/test/python-tests/data/folder.list	(original)
+++ trunk/test/python-tests/data/folder.list	Tue Jul 29 12:38:09 2008
@@ -12,4 +12,7 @@
 removable-volume=file:///media/media/MusicToSort/Kora
 ntfs-volume=file:///media/windows
 
+[john nzjrs-uni]
+removable-volume=file:///media/sda2/dell
+ntfs-volume=file:///media/sda2
 



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