# -*- coding: utf-8 -*-
###########################################################################
# Eole NG - 2009
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
###########################################################################
"""
    librairie pour la gestion des partages de Scribe
"""
from os.path import join, isdir
from os import chmod, system
from shutil import rmtree
from time import sleep
from ldap import MOD_REPLACE

from fichier import models
from scribe.eoleldap import LdapEntry
from scribe.errors import LdapExistingShare
from scribe.eolead import group_test
from scribe.ldapconf import SMB_SERVEUR, SHARE_DN, SHARE_FILTER, \
GROUP_PATH, CLASS_PATH, OPT_PATH, LDAP_MODE
from scribe import eoletools as tool

# partages spéciaux de Scribe
EOLE_SHARES = ['professeurs', 'icones$', 'groupes', 'commun', 'perso']


#______________________________________________________________________________
# fonctions diverses

def test_drive(drive):
    """
    vérifie si la lettre de lecteur proposée est valide
    """
    if not drive.endswith(":"):
        drive = '%s:' % drive
    if len(drive) != 2:
        raise NameError("le nom de lecteur %s est invalide" % drive)
    return drive.upper()

def set_dt_acl(filepath, data_dir, work_dir, groupname):
    """
        Mise en place des acls pour un partage sur un modèle données/travail
    """
    tool.set_acl(filepath, 'professeurs', 'r-x', recursive=False)
    tool.set_acl(filepath, groupname, 'r-x')
    tool.set_acl(data_dir, 'professeurs', 'rwx')
    tool.set_acl(data_dir, groupname, 'r-x')
    tool.set_acl(work_dir, 'professeurs', 'rwx')
    tool.set_acl(work_dir, groupname, 'rwx')
    tool.set_default_acl(filepath)
    tool.set_default_acl(data_dir)
    tool.set_default_acl(work_dir)

#______________________________________________________________________________
# OBJET Partage

class Share(LdapEntry):
    """
        Classe de gestion de partages
        :method add: connexion à l'annuaire avant ajout
        :method _add: connexion déjà initialisée
    """

#______________________________________________________________________________
# méthodes pour l'ajout

    def add(self, sharename, groupname, _type, model=None, filepath=None, drive=None, sync=True):
        """
            créer un partage samba associé à un groupe
            :sharename: nom du partage
            :groupname: nom du groupe
            :model : modèle de partage à appliquer
            :filepath: chemin alternatif (GROUP_PATH/groupname par défaut)
            :drive: lettre de lecteur associée
            :_type: Types de partages: - ro(read-only)
                                       - rw(lecture-écriture)
                                       - dt (modèles données/travail)
                                       - classe (modèle classe + équipe pédagogique)
                                       - option (ressemble à classe)
        """
        self.ldap_admin.connect()
        self._add(sharename, groupname, _type, model, filepath, drive, sync)
        self.ldap_admin.close()

    def _add(self, sharename, groupname, _type, model=None, filepath=None, drive=None, sync=False, etab=None, mkdir=True):
        """
            ajout d'un partage samba associé à un groupe donné
            dans le cas d'une connexion déjà initialisée (utilisez add sinon)
        """
        if self._is_share(sharename):
            raise LdapExistingShare("Le partage %s existe déjà" % sharename)
        if filepath is None:
            filepath = join(GROUP_PATH, sharename)
        self._add_ldap(sharename, groupname, model, filepath, drive, etab)
        if mkdir:
            self._make_dirs(groupname, filepath, _type)
        # FIXME
        #for user in get_members(groupname):
        #    gen_ftpdir(user)
        if sync:
            self._synchronize(restart=True)

    def _add_ldap(self, sharename, groupname, model, filepath, drive, etab=None):
        """
            Ajout de l'entrée ldap pour le partage 'sharename'
        """
        if not model:
            model = 'standard'
        share_dn = SHARE_DN % dict(share=sharename, etab=etab)
        datas = [("objectClass", "sambaFileShare"),
        ( "cn", "smb://%s/%s" % (SMB_SERVEUR, sharename)),
        ("sambaShareName", sharename),
        ("description", sharename),
        ("sambaShareGroup", groupname),
        ("sambaFilePath", filepath),
        ("sambaShareURI", "\\\\%s\\%s" % (SMB_SERVEUR, sharename)),
        ("sambaShareModel", model),
        ]
        if drive is not None:
            if not drive.endswith(":"):
                drive += ':'
            if len(drive) != 2:
                return False
            datas.append(("sambaShareDrive", drive))
        self.ldap_admin._add(share_dn, datas)

    def _make_dirs(self, groupname, filepath, _type):
        """
            Création des répertoires pour les partages
        """
        for num in range(50):
            exist = group_test(groupname)
            if exist:
                break
            sleep(2)
        tool.add_dir(filepath)
        chmod(filepath, 0o700)
        system("setfacl -Rbk %s" % filepath)
        if _type == 'ro':
            tool.set_acl(filepath, 'professeurs', 'rwx', recursive=True)
            tool.set_acl(filepath, 'administratifs', 'rwx', recursive=True)
            tool.set_acl(filepath, groupname, 'r-x', recursive=True)
            tool.set_default_acl(filepath)
        elif _type == 'rw':
            tool.set_acl(filepath, groupname, 'rwx', recursive=True)
            tool.set_default_acl(filepath)
        elif _type in ['dt', 'classe', 'option']:
            data_dir = join(filepath, 'donnees')
            work_dir = join(filepath, 'travail')
            tool.add_dir(data_dir)
            tool.add_dir(work_dir)
            set_dt_acl(filepath, data_dir, work_dir, groupname)
            if _type in ['classe', 'option']:
                if _type == 'classe':
                    link_path = CLASS_PATH
                else:
                    link_path = OPT_PATH
                c_dir = join(link_path, groupname)
                tool.add_dir(c_dir)
                chmod(c_dir, 0o700)
                system("setfacl -Rbk %s" % c_dir)
                tool.set_acl(c_dir, 'professeurs', 'r-x', recursive=False)
                p_dir = join(GROUP_PATH, 'profs-%s' % groupname)
                tool.add_dir(p_dir)
                # lien vers la classe dans l'équipe
                tool.add_symlink(filepath, join(p_dir, 'classe'))
                #system("ln -nsf $GROUPES/$CLASSE /$GROUPES/profs-$CLASSE/classe
                # lien vers le répertoire contenant les élèves
                tool.add_symlink(c_dir, join(p_dir, 'eleves'))
                #ln -nsf $home_path/classes/$CLASSE /$GROUPES/profs-$CLASSE/eleves

#______________________________________________________________________________
# méthodes pour la lecture

    def get_attr(self, sharename, attr):
        """
            Connecte à l'annuaire et renvoie la valeur de
            l'attribut attr pour sharename
        """
        self.ldap_admin.connect()
        res = self._get_attr(sharename, attr)
        self.ldap_admin.close()
        return res

    def get_attrs(self, sharename, attrs):
        """
            Connecte à l'annuaire et renvoie la valeur des attributs attrs
            pour le partage 'sharename' attrs : ["attr1","attr2"] ou "attr1"
        """
        self.ldap_admin.connect()
        res = self._get_attrs(sharename, attrs)
        self.ldap_admin.close()
        return res

    def _get_attrs(self, sharename, attrs):
        """
            renvoie la valeur des attributs attrs pour le partage 'sharename'
            attrs : ["attr1","attr2"] ou "attr1"
        """
        return self.ldap_admin._search_one("(&%s(sambaShareName=%s))" % (
                                SHARE_FILTER, sharename), attrs)

    def _get_attr(self, sharename, attr):
        """
            renvoie la valeur d'un attribut
        """
        return self._get_attrs(sharename, [attr]).get(attr, [])

    def get_shares(self):
        """
            renvoie les partages
        """
        self.ldap_admin.connect()
        res = self._get_shares()
        self.ldap_admin.close()
        return res

    def _get_shares(self):
        """
            renvoie les partages
        """
        #conserve la correspondance nom/dn (pour le support des etablissements)
        self.tmp_shares = {}
        shares = {}
        res = self.ldap_admin._search(SHARE_FILTER,
                ['sambaShareName', 'sambaShareDrive'])
        for share in res:
            name = share[1]['sambaShareName'][0]
            drive = share[1].get('sambaShareDrive', [''])[0]
            self.tmp_shares[name] = share[0]
            shares[name] = drive
        return shares

    def get_shares_data(self):
        """
            renvoie tous les partages et toutes leurs propriétés
        """
        self.ldap_admin.connect()
        res = self._get_shares_data()
        self.ldap_admin.close()
        return res

    def _get_shares_data(self):
        """
            renvoie tous les partages et toutes leurs propriétés
        """
        return self.ldap_admin._search(SHARE_FILTER)

    def _get_letter(self, sharename):
        """
            renvoie la lettre associée à un partage ou ""
        """
        drive = self._get_attr(sharename, 'sambaShareDrive')
        if drive == []:
            return ""
        return drive[0]

    def get_reserved_letters(self):
        """
            renvoie les lettres de lecteur réservées
        """
        self.ldap_admin.connect()
        res = self._get_reserved_letters()
        self.ldap_admin.close()
        return res

    def _get_reserved_letters(self):
        """
            renvoie la liste des lettres déjà réservées
            {'partage':'lettre'}
        """
        lettre = {'perso':'U:'}
        res = self.ldap_admin._search("(&%s(sambaShareDrive=*))" % SHARE_FILTER,
               ['sambaShareName', 'sambaShareDrive'])
        for share in res:
            name = share[1]['sambaShareName'][0]
            drive = share[1]['sambaShareDrive'][0]
            lettre[name] = drive
        return lettre

    def _get_share(self, drive):
        """
            retourne le partage associé à "drive" si existant, "" sinon
        """
        res = self.ldap_admin._search_one("(&%s(sambaShareDrive=%s))" % \
                (SHARE_FILTER, drive), ['sambaShareName', 'sambaShareDrive'])
        if 'sambaShareName' in res:
            return res['sambaShareName']
        return ""

#______________________________________________________________________________
# méthodes pour la modification

    def _set_attr(self, sharename, attribut, value):
        """
            met à jour un attribut d'un partage
        """
        #si tmp_shares est chargé (si _get_shares a été appelé)
        if hasattr(self, 'tmp_shares') and sharename in self.tmp_shares:
            share_dn = self.tmp_shares[sharename]
        else:
            share_dn = SHARE_DN % dict(share=sharename)
        data = [((MOD_REPLACE, attribut, value))]
        self.ldap_admin._modify(share_dn, data)

    def _set_share_drive(self, sharename, drive=None):
        """
            affecte une lettre de lecteur à un partage
        """
        if sharename in EOLE_SHARES:
            raise Exception("le partage %s n'est pas modifiable" % sharename)
        if not drive:
            drive = []
        else:
            drive = self._test_drive(drive)
        self._set_attr(sharename, 'sambaShareDrive', drive)

#______________________________________________________________________________
# méthodes pour la suppression

    def delete(self, sharename, rmdir=False, sync=True):
        """
            suppression d'un partage (mode déconnecté)
        """
        self.ldap_admin.connect()
        self._delete(sharename, rmdir, sync)
        self.ldap_admin.close()

    def _delete(self, sharename, rmdir=False, sync=False):
        """
            suppression d'un partage (mode connecté)
        """
        self._delete_dir(sharename, rmdir)
        self._delete_ldap(sharename)
        if sync:
            self.synchronize(restart=True)

    def _delete_dir(self, sharename, rmdir):
        """
            suppression du répertoire d'un partage
        """
        # suppression des liens symboliques des .ftp (#2146)
        system("find /home/?/*/.ftp -type l -name %s -delete" % sharename)
        filepath = self._get_attr(sharename, 'sambaFilePath')
        if filepath and isdir(filepath[0]):
            if rmdir:
                rmtree(filepath[0])
            else:
                tool.move_share_datas(filepath[0])

    def _delete_ldap(self, sharename):
        """
            suppression de l'entrée ldap d'un partage
        """
        share_dn = SHARE_DN % dict(share=sharename)
        self.ldap_admin._delete(share_dn)

    def delete_classe_share(self, classname, rmdir=False, sync=False):
        """
            suppression d'un partage classe (mode déconnecté)
        """
        self.ldap_admin.connect()
        self._delete_classe_share(classname, rmdir, sync)
        self.ldap_admin.close()

    def _delete_classe_share(self, classname, rmdir=False, sync=False):
        """
            suppression d'un partage classe (mode connecté)
        """
        self._delete(classname, rmdir, sync)
        if rmdir:
            cl_dir = join(CLASS_PATH, classname)
            if isdir(cl_dir):
                rmtree(cl_dir)
            eq_dir = join(GROUP_PATH, 'profs-%s' % classname)
            if isdir(eq_dir):
                rmtree(eq_dir)

    def _delete_option_share(self, optname, rmdir=False, sync=False):
        """
            suppression d'un partage option (mode connecté)
        """
        self._delete(optname, rmdir, sync)
        # les ln sont toujours virés
        opt_links = join(OPT_PATH, optname)
        if isdir(opt_links):
            rmtree(opt_links)
        if rmdir:
            opt_dir = join(GROUP_PATH, optname)
            if isdir(opt_dir):
                rmtree(opt_dir)
            eq_dir = join(GROUP_PATH, 'profs-%s' % optname)
            if isdir(eq_dir):
                rmtree(eq_dir)

#______________________________________________________________________________
# autres méthodes

    def _test_drive(self, drive):
        """
            vérifie si la lettre de lecteur est valide et disponible
        """
        drive = test_drive(drive)
        for share, letter in list(self._get_reserved_letters().items()):
            if drive == letter:
                raise Exception("Le lecteur %s est déjà réservé pour %s" % \
                        (drive, share))
        return drive

    def synchronize(self, restart=True):
        """
            ajout des partages dans le fichier smb.conf
            (avec connexion ldap)
        """
        self.ldap_admin.connect()
        self._synchronize(restart)
        self.ldap_admin.close()

    def _synchronize(self, restart=False):
        """
            ajout des partages dans le fichier smb.conf
        """
        shares = []
        result = self._get_shares_data()
        for share in result:
            dico_share = {}
            dico_share['name'] = share[1]['sambaShareName'][0]
            dico_share['path'] = share[1]['sambaFilePath'][0]
            dico_share['group'] = share[1]['sambaShareGroup'][0]
            dico_share['desc'] = share[1]['description'][0]
            try:
                dico_share['model'] = share[1]['sambaShareModel'][0]
            except:
                pass
            shares.append(dico_share)
        if LDAP_MODE == 'ad':
            restart = False
        models.synchronize(shares, restart)

        ## FIXME :
        #    # création du répertoire du partage si nécessaire
        #    shared_path = join(CONTAINER_PATH_FICHIER, dico_share['path'].lstrip().lstrip('/'))
        #    if not dico_share['path'].startswith('%') and not isdir(shared_path):
        #        makedirs(shared_path)
        #        chmod(shared_path, 0700)
        #        tool.set_acl(dico_share['path'], dico_share['group'])
        #        tool.set_default_acl(dico_share['path'])
        #    # génération des entrées partages pour le smb.conf
        #    conf_file = join(CONF_DIR, '%s.conf' % dico_share['name'])
        #    if isfile(conf_file):
        #        print "partage personnalisé : %s" % dico_share['name']
        #        fic = open(conf_file)
        #        smb.append(fic.read())
        #        fic.close()
        #    else:
        #        smb.append(share_tmpl % dico_share)
