From afc339f71fbcf98c11a63207a33828f23595e2c2 Mon Sep 17 00:00:00 2001 From: stack Date: Sun, 8 Jul 2012 02:17:21 +0200 Subject: [PATCH 1/1] initial draft app implementation code --- .gitignore | 2 + COSE | 16 +++++ apperror.py | 45 ++++++++++++++ gui.py | 88 +++++++++++++++++++++++++++ main.py | 47 +++++++++++++++ profhandler.py | 160 +++++++++++++++++++++++++++++++++++++++++++++++++ settings.py | 8 +++ 7 files changed, 366 insertions(+) create mode 100644 .gitignore create mode 100644 COSE create mode 100755 apperror.py create mode 100755 gui.py create mode 100755 main.py create mode 100755 profhandler.py create mode 100644 settings.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b948985 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +*.pyc diff --git a/COSE b/COSE new file mode 100644 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 index 0000000..348826e --- /dev/null +++ b/apperror.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +Dboxswitch dropbox profile switcher + +license: Modified BSD License + +Copyright (c) 2012, +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 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 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 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, +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 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 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 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, +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 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 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 index 0000000..2e1a30d --- /dev/null +++ b/profhandler.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +Dboxswitch dropbox profile switcher + +license: Modified BSD License + +Copyright (c) 2012, +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 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 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 index 0000000..7c82d1e --- /dev/null +++ b/settings.py @@ -0,0 +1,8 @@ +#!/bin/python + +class Settings(): + def __init__(self): + self.appname = "dboxswitch" + +global conf +appconf = Settings() -- 2.20.1