# -*- 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'):
                    dbdir = self.getDropboxDirectory()
                    if os.path.exists(dbdir):
                        os.unlink(dbdir)
                    os.symlink(ppath, dbdir)
                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'):
            basepath = os.path.join(os.path.expanduser("~"), ".dropbox")
            for path in [os.path.join(basepath, "instance1"), basepath]:
                if os.path.exists(path) and os.path.islink(path):
                    return path 
            raise NotImplementedError("Path not found " + basepath + "[instance1]")
        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()

