implemented an installer with distribute
[stack/code/dboxswitch.git] / dboxswitch / profhandler.py
diff --git a/dboxswitch/profhandler.py b/dboxswitch/profhandler.py
new file mode 100644 (file)
index 0000000..f2be538
--- /dev/null
@@ -0,0 +1,213 @@
+# -*- 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
+import signal
+
+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 sorted([os.path.join(self.pdir, f) for f in os.listdir(self.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 addProfile(self, profileName):
+        """ Create a profile """
+
+        print("Creating a new profile")
+        if self.isValidProfileName(profileName):
+            try:
+                os.makedirs(os.path.join(self.getProfileFolder(), profileName)) 
+            except OSError,e:
+                if e.errno == errno.EEXIST:
+                    raise AppError("Profile exists.")
+                else:
+                    raise AppError(str(e))
+        else:
+            raise AppError('Profile Name not valid.\nAllowed only ascii characters.')
+        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+" deleted.")
+
+    def isCurrentProfile(self, ppath):
+        """ Returns true if the current profile path is currently activated """
+        
+        pl = platform.system()
+        if pl in ('Linux','Darwin'):
+            if os.path.exists(self.getDropboxDirectory()):
+                return True if os.readlink(self.getDropboxDirectory()) == ppath else False
+            else:
+                return False
+
+    def isValidProfileName(self, pname):
+
+        if self.reg.match(pname) is not None:
+            return True
+        else:
+            return False
+
+    def activateProfile(self, ppath):
+        pl = platform.system()
+        if ppath in self.getProfilesList():
+            self.stopDropbox()
+            try:
+                if pl in ('Linux','Darwin'):
+                    if os.path.exists(self.getDropboxDirectory()):
+                        os.unlink(self.getDropboxDirectory())
+                    os.symlink(ppath, self.getDropboxDirectory())
+                else:
+                    raise NotImplementedError, "Not implemented yet."
+            except IOError as e:
+                raise AppError('Error on activating Profile: '+ self.getBaseProfileName(ppath))
+            self.startDropbox()
+        else:
+            raise AppError("Trying to acrivate non existant profile")
+
+    def getBaseProfileName(self, ppath):
+        """ Returns the base name given a profile returned by getProfilesList """
+
+        return os.path.basename(ppath)
+
+    def getDropboxDirectory(self):
+        pl = platform.system()
+        if pl in ('Linux', 'Darwin'):
+            return os.path.join(os.path.expanduser('~'),".dropbox")
+        elif pl == 'Windows':
+            assert os.environ.has_key('APPDATA'), Exception('APPDATA env variable not found')
+            return os.path.join(os.environ['APPDATA'],'Dropbox')
+        else:
+            raise NotImplementedError, "Not implemented yet."
+
+    def stopDropbox(self):
+        """ Stop dropbox Daemon """
+        pl = platform.system()
+        if pl == 'Linux':
+            os.system("dropbox stop")
+        if pl in ('Linux','Darwin'):
+            pidfile = os.path.expanduser("~/.dropbox/dropbox.pid")                    
+            try:                                                                      
+                with open(pidfile, "r") as f:                                         
+                    pid = int(f.read())                                               
+                    os.kill(pid, signal.SIGTERM)
+            except:                                                                   
+                pass
+
+    def startDropbox(self):
+        """ Sart dropbox Daemon """
+
+        pl = platform.system()
+        if pl == 'Linux':
+            try:
+                os.system("dropbox start -i")
+            except:
+                raise AppError(u"Could not start dropbox.")
+        elif pl == 'Darwin':
+            os.system("/Applications/Dropbox.app/Contents/MacOS/Dropbox &")
+
+            
+__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()
+