initial draft app implementation code
authorstack <stack@inventati.org>
Sun, 8 Jul 2012 00:17:21 +0000 (02:17 +0200)
committerstack <stack@inventati.org>
Sun, 8 Jul 2012 00:17:21 +0000 (02:17 +0200)
.gitignore [new file with mode: 0644]
COSE [new file with mode: 0644]
apperror.py [new file with mode: 0755]
gui.py [new file with mode: 0755]
main.py [new file with mode: 0755]
profhandler.py [new file with mode: 0755]
settings.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b948985
--- /dev/null
@@ -0,0 +1,2 @@
+*.swp
+*.pyc
diff --git a/COSE b/COSE
new file mode 100644 (file)
index 0000000..47cd8fb
--- /dev/null
+++ b/COSE
@@ -0,0 +1,16 @@
+ * systray
+        tasto sinistro systrai elenca o dice crea nuovo
+            click su profilo per switchare, stoppando e riavviando dropbox
+            ogni volta
+        tasto destro dice gestisci profili o esci
+ * gestire profili
+    - listare profili in base a dir
+    - creare dir profili e linkarla alla cosa
+    - switch profili
+ * inizializzazione applicazione se non trova la dir dove infila i profili
+
+
+Directory del profilo con nome dell'account
+    * validazione nome account che deve essere ([0-9]|[a-zA-Z_-])+
+
+Implementare custom exceptions per pigliarle dentro l'applicazione e mostrare l'errore in un messagebox
diff --git a/apperror.py b/apperror.py
new file mode 100755 (executable)
index 0000000..348826e
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Dboxswitch dropbox profile switcher
+
+license: Modified BSD License
+
+Copyright (c) 2012,  <stack@inventati.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+import sys       
+
+class AppError(Exception):
+    """ Custom exception that prints on stderr and raise the error to be catched for example in the Gui """
+    def __init__(self, value):
+        print >> sys.stderr, value
+        self.value = value
+
+    def __str__(self):
+        return repr(self.value)
diff --git a/gui.py b/gui.py
new file mode 100755 (executable)
index 0000000..c9bd3d5
--- /dev/null
+++ b/gui.py
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Dboxswitch dropbox profile switcher
+
+license: Modified BSD License
+
+Copyright (c) 2012,  <stack@inventati.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+import sys
+import os
+from PyQt4 import QtGui
+
+from apperror import AppError
+
+class Gui(QtGui.QDialog):
+    def __init__(self, prManager):
+        self.app = QtGui.QApplication(sys.argv)
+    
+        #check if system tray is avaiable on the system
+        if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
+            QtGui.QMessageBox.critical(None, "Systray",
+                    "I couldn't detect any system tray on this system.")
+            sys.exit(1)
+        
+        QtGui.QApplication.setQuitOnLastWindowClosed(False)
+    
+        super(Gui, self).__init__()
+
+        self.createActions()
+        self.createTrayIcon()
+
+        self.trayIcon.show()
+
+        self.setWindowTitle("Profile manager / Dboxswitch - dropbox profile switcher")
+        self.resize(400, 300)
+
+        self.profileManager = prManager
+
+
+
+    def main(self):
+        sys.exit(self.app.exec_())
+
+    def closeEvent(self, event):
+        if self.trayIcon.isVisible():
+            self.hide()
+            event.ignore()
+
+    def createActions(self):
+        self.manageprofiles = QtGui.QAction("Manage &Profiles", self,
+                triggered=self.hide)
+        self.quitAction = QtGui.QAction("&Quit", self,
+                triggered=QtGui.qApp.quit)
+
+    def createTrayIcon(self):
+         self.trayIconMenu = QtGui.QMenu(self)
+         self.trayIconMenu.addAction(self.manageprofiles)
+         self.trayIconMenu.addSeparator()
+         self.trayIconMenu.addAction(self.quitAction)
+
+         self.trayIcon = QtGui.QSystemTrayIcon(self)
+         self.trayIcon.setContextMenu(self.trayIconMenu)
diff --git a/main.py b/main.py
new file mode 100755 (executable)
index 0000000..2c187cb
--- /dev/null
+++ b/main.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Dboxswitch dropbox profile switcher
+
+license: Modified BSD License
+
+Copyright (c) 2012,  <stack@inventati.org> 
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+from profhandler import ProfHandler
+from gui import Gui
+from PyQt4 import QtGui
+
+from settings import appconf
+
+if __name__ == '__main__':
+    #Application init
+    prManager = ProfHandler()
+
+    window = Gui(prManager)
+    window.main()
diff --git a/profhandler.py b/profhandler.py
new file mode 100755 (executable)
index 0000000..2e1a30d
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Dboxswitch dropbox profile switcher
+
+license: Modified BSD License
+
+Copyright (c) 2012,  <stack@inventati.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+import platform
+import re
+import shutil
+import os
+import errno
+
+from apperror import AppError
+from settings import appconf
+
+class ProfHandler():
+
+    def __init__(self):
+
+        #create profile directory if not exists
+        try:
+            os.makedirs(self.getProfileFolder())
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise
+
+        #compile regular expression for validating profile names
+        self.reg = re.compile("[a-zA-Z0-9_-]+")
+
+        #patch symlink on windows
+        if platform.system() is 'Windows':
+            os.symlink = winsymlink
+
+    def getProfilesList(self):
+        """ Generate and returns the profiles 
+            it assumes that self.pdir is defined """
+        #this is generated every time to handle the case of the user renaming the directories by hand
+        return [os.path.join(self.pdir, f) for f in os.listdir(pdir)]
+
+    def getProfileFolder(self):
+        """ Generates, in a os dependant way, the local folder where all profiles are stored """
+        try:
+            #directory path is cached
+            return self.pdir
+        except AttributeError:
+            pl = platform.system()
+            if pl == "Linux":
+                try:
+                    from xdg.BaseDirectory import xdf_data_home
+                    self.pdir = os.path.join(xdg_data_home, appconf.appname)
+                except:
+                    self.pdir = os.path.join(os.path.expanduser('~'),".local/share",appconf.appname)
+            elif pl == 'Windows':
+                self.pdir = os.path.join(os.getenv("APPDATA"), appconf.appname)
+            elif pl == 'Darwin':
+                self.pdir =  os.path.join(os.path.expanduser('~'),"."+appconf.appname)
+            elif pl == None:
+                raise AppError('Operative system NOT supported.')
+
+            return self.pdir
+
+    def newProfile(self, profileName):
+        """ Create a profile """
+
+        print("Creating a new profile")
+        if self.isValidProfileName(profileName):
+            os.makedirs(os.path.join(self.getProfileFolder(), profileName)) 
+        else:
+            raise AppError('Profile Name not valid')
+        print("Profile "+profileName+" created.")
+
+    def delProfile(self, profileName):
+        """ Delete a profile """
+
+        print("Deleting profile")
+        if self.isValidProfileName(profileName):
+            try:
+                #recursively delete the profile directory
+                shutil.rmtree(os.path.join(self.pdir, profileName))
+            except:
+                raise AppError('Profile Name does not exists')
+        else:
+            raise AppError('Profile Name not valid')
+        print("Profile "+profileName+" created.")
+
+    def isValidProfileName(self, pname):
+        if self.reg.match(pname) is not None:
+            return True
+        else:
+            return False
+
+    def activateProfile(self, pname):
+        if pname in self.getProfilesList():
+            self.stopDropbox()
+            try:
+                with open(os.path.join(self.getProfileFolder(), pname)) as pdir:
+                    os.unlink(self.getDropboxDirectory())
+                    os.symlink(os.path.join(self.getProfileFolder(), pname), self.getDropboxDirectory())
+            except IOError as e:
+                raise AppError('Error on activating Profile: '+pname)
+            self.startDropbox()
+        else:
+            raise AppError("Trying to acrivate non existant profile")
+
+    def getDropboxDirectory(self):
+        pl = platform.system()
+        if pl == 'Linux':
+            return os.path.join(os.path.expanduser('~'),".dropbox")
+        elif pl == 'Windows':
+            raise NotImplementedError, "Not implemented yet."
+        elif pl == 'Darwin':
+            raise NotImplementedError, "Not implemented yet."
+
+            
+__CSL = None
+def winsymlink(source, link_name):
+    '''symlink(source, link_name)
+       Creates a symbolic link pointing to source named link_name.
+        Used to patch the nonexistant version on windows for python 2.6'''
+    global __CSL
+    if __CSL is None:
+        import ctypes
+        csl = ctypes.windll.kernel32.CreateSymbolicLinkW
+        csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
+        csl.restype = ctypes.c_ubyte
+        __CSL = csl
+    flags = 0
+    if source is not None and os.path.isdir(source):
+        flags = 1
+    if __CSL(link_name, source, flags) == 0:
+        raise ctypes.WinError()
+
diff --git a/settings.py b/settings.py
new file mode 100644 (file)
index 0000000..7c82d1e
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/python
+
+class Settings():
+    def __init__(self):
+        self.appname = "dboxswitch"
+
+global conf
+appconf = Settings()