#!/usr/bin/env python
# -*- mode: python; coding: utf-8 -*-
#
# ressuscite.py
# -------------
#
# Copyright (C) 2008 Michel Blockelet <blockelet@crans.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.

u"""Outil pour ressusciter des objets du cimetière."""

import os, sys
import cPickle
import dialog
import time, pwd, ldap, ldap.modlist, random
from time import mktime, strptime

sys.path.append('/usr/scripts/gestion')
from ldap_crans import crans_ldap, MachineFixe, MachineWifi, Adherent, Club, date_format
from gest_crans import select, set_machine
from whos import aff
import affich_tools
import user_tests

db = crans_ldap()
isadm = user_tests.isadm()
cur_user = os.getenv('SUDO_USER') or pwd.getpwuid(os.getuid())[0]

dlg = dialog.Dialog()
dlg.setBackgroundTitle('Ressusciter une machine')

def menu_principal():
    """Menu principal de l'interface."""
    choix = ''
    while 1:
        annul, choix = dlg.menu(u"Que souhaitez vous faire ?",
                         choices=[("A",u"Ressusciter à partir d'un adhérent",u"Rechercher les anciennes machines d'un adhérent"),
                                  ("D",u"Ressusciter à partir de la date",u"Rechercher à partir de la date de suppression de la machine"),
                                  ("F",u"Ressusciter à partir d'un fichier",u"Récupérer depuis un fichier du cimetière"),
                                  ("C",u"Ressusciter un adhérent",u"Ressusciter un adhérent depuis un fichier du cimetière"),
                                  ("K",u"Ressusciter un club",u"Ressusciter un club depuis un fichier du cimetière"),
                                  ],
                         item_help=1,title=u"Ressusciter")
        if annul: break

        if choix == 'A':
            # Ressusciter à partir d'un adhérent
            # On sélectionne l'adhérent
            adh = select(db, u'Adhérent duquel rechercher les anciennes machines a', mde='ro')
            if not adh: continue

            # Est-ce que c'est bien une machine de l'adhérent ?
            def condition(machine, date):
                return (int(machine.dn.split(',', 3)[1].split('=', 2)[1])
                    == int(adh.id()) and str(machine.proprio) == ("%s %s"
                    % (adh.prenom(), adh.nom())))

            choixmachine = choixrecherche(condition)

            if choixmachine != None:
                (machine, date) = choixmachine
                adh = choixadherent(machine, adh)
                if not adh: continue
                ressuscite(adh, machine)

        elif choix == 'D':
            # Ressusciter à partir de la date
            while 1:
                debut = None
                fin = None
                annul, result = dlg.inputbox(width=54, height=10,
                                             text=u"Entrez la plage de dates de recherche au format\nJJ/MM/AAAA:JJ/MM/AAAA :\n(Une des deux dates peut ne pas être spécifiée.)\n",
                                             title=u"Sélection des dates")
                if annul: break
                try:
                    [strdebut, strfin] = result.strip().split(':', 1)
                    # On teste la validité des dates
                    # (strptime émet une erreur si le format est mauvais)
                    if len(strdebut) > 0:
                        debut = mktime(strptime(strdebut, '%d/%m/%Y'))
                    if len(strfin) > 0:
                        fin = mktime(strptime(strfin, '%d/%m/%Y')) + 24*60*60
                    break
                except:
                    dlg.msgbox(text=u"Dates invalides",title=u"Sélection des dates")
                    continue

            # On ne fait la recherche que si on a spécifié une date
            # (c'est mieux de ne pas instancier en mémoire toutes les machines)
            if debut != None or fin != None:
                def condition(machine, date):
                    ok = True
                    unixdate = mktime(strptime(date, '%Y-%m-%d-%H:%M'))
                    if debut != None: ok = (unixdate >= debut)
                    if fin != None: ok = ok and (unixdate <= fin)
                    return ok

                choixmachine = choixrecherche(condition)

                if choixmachine != None:
                    (machine, date) = choixmachine
                    adh = choixadherent(machine)
                    if not adh: continue
                    ressuscite(adh, machine)

        elif choix == 'F':
            # Ressusciter à partir d'un fichier
            fichier = None
            while 1:
                annul, result = dlg.inputbox(width=54, height=12,
                                             text=u"Vous pouvez aussi exécuter le script sur le fichier\nressuscite [le fichier]\n\nFichier : ",
                                             init=u"/home/cimetiere/Machine",
                                             title=u"Sélection du fichier")
                if annul: break
                try:
                    fichier = open(result, 'r')
                    break
                except:
                    dlg.msgbox(text=u"Fichier invalide",title=u"Sélection du fichier")
                    continue
            if fichier != None:
                machine = cPickle.load(fichier)
                adh = choixadherent(machine)
                if not adh: continue
                ressuscite(adh, machine)

        elif choix == 'C':
            # Ressusciter à partir d'un fichier
            fichier = None
            while 1:
                annul, result = dlg.inputbox(width=54, height=12,
                                             text=u"Vous pourrez peut-être aussi exécuter le script sur le fichier\nressuscite [le fichier]\n\nFichier : ",
                                             init=u"/home/cimetiere/Adherent",
                                             title=u"Sélection du fichier")
                if annul: break
                try:
                    fichier = open(result, 'r')
                    break
                except:
                    dlg.msgbox(text=u"Fichier invalide",title=u"Sélection du fichier")
                    continue
            if fichier != None:
                adh = cPickle.load(fichier)
                if not adh: continue
                ressuscite_adherent(adh)

        elif choix == 'K':
            # Ressusciter à partir d'un fichier
            fichier = None
            while 1:
                annul, result = dlg.inputbox(width=54, height=12,
                                             text=u"Vous pourrez peut-être aussi exécuter le script sur le fichier\nressuscite [le fichier]\n\nFichier : ",
                                             init=u"/home/cimetiere/Club",
                                             title=u"Sélection du fichier")
                if annul: break
                try:
                    fichier = open(result, 'r')
                    break
                except:
                    dlg.msgbox(text=u"Fichier invalide",title=u"Sélection du fichier")
                    continue
            if fichier != None:
                club = cPickle.load(fichier)
                if not club: continue
                ressuscite_club(club)

        continue


def choixrecherche(condition=None):
    """Demande les types de machines à chercher, et effectue la recherche avec
    la condition."""
    # On sélectionne le type de machine
    annul, choix = dlg.menu(u"Type de machine ?",
                         choices=[("Fixe",u"Machine fixe"),
                                  ("Wifi",u"Machine wireless"),
                                  ("Tous",u"Rechercher les deux types")],
                         title=u"Recherche de machine")
    if annul: return None

    # On cherche les machines
    machines = recherche(dlg,
                         choix == 'Fixe' or choix == 'Tous',
                         choix == 'Wifi' or choix == 'Tous',
                         condition)

    # Pas de résultat
    if not machines:
        dlg.msgbox(text=u"Aucun résultat",title=u"Recherche")
        return None

    return choixmachine(machines)


def recherche(dlg, fixe=False, wifi=False, condition=None):
    """Recherche des machines du type demandé, et satisfaisant une condition :
    la fonction condition doit être de la forme
        condition(machine, date)
    et retourner True ou False selon si l'on veut avoir la machine dans la
    liste ou non."""
    # Liste des fichiers à lire
    cimetiere = []
    if fixe: cimetiere  = map(lambda s: "/home/cimetiere/MachineFixe/" + s,
                os.listdir("/home/cimetiere/MachineFixe"))
    if wifi: cimetiere += map(lambda s: "/home/cimetiere/MachineWifi/" + s,
                os.listdir("/home/cimetiere/MachineWifi"))

    # On regarde chacun des fichiers
    valid = []
    done = 0
    dlg.gauge_start(text="Recherche en cours...")
    for fichier in cimetiere:
        try:
            machine = cPickle.load(open(fichier, 'r'))
            date = fichier[28:44]
            if condition == None or condition(machine, date):
                valid.append((machine, date))
        except:
            pass
        done += 1
        dlg.gauge_update(int(done*100/len(cimetiere)))
    dlg.gauge_stop()
    return valid


def choixadherent(machine, oldadh=None):
    """Permet de choisir l'adhérent à qui rattacher la machine."""
    if oldadh == None:
        oldadhl = db.search(machine.dn.split(',', 2)[1])['adherent']
        if not oldadhl:
            oldadhl =  db.search(machine.dn.split(',', 2)[1])['club']
    else:
        oldadhl = [oldadh]

    arg  = u'--title "" '
    arg += u'--menu "%s.' % machine.proprio

    # Est-ce que l'adhérent existe encore ?
    if (len(oldadhl) > 0 and machine.proprio == oldadhl[0].Nom()):
        annul, choix = dlg.menu(u"Le propriétaire de la machine est %s" % machine.proprio,
                            choices=[("Garder",u"Ajouter à cet adhérent"),
                                     ("Autre",u"Ajouter à un autre adhérent")],
                            title=u"Adhérent auquel rattacher la machine")
        annul = False
        choix = "Garder"
    else:
    	annul, choix = dlg.menu(u"Cet adhérent n'existe plus dans la base.",
                        choices=[("Autre",u"Ajouter à un autre adhérent")],
                        title=u"Adhérent auquel rattacher la machine")
    if not annul:
        if choix == "Garder":
            adh = oldadhl[0]
        else:
            adh = select(db, u'Adhérent auquel ajouter la machine a', mde='ro')
    else:
        adh = None
    return adh


def choixmachine(valid):
    """Choisit une machine dans une liste."""
    # Pas de machines !
    if len(valid) == 1:
        # Une seule réponse
        choix = valid[0]
    else:
        # Il faut choisir
        while 1:
            os.system('clear')
            choix = None
            print "Plusieurs réponses correspondant à votre requête ont été trouvées :"

            data = []
            i = 1
            for (machine, date) in valid:
                data.append([i, date, str(machine.__class__)[-4:], machine.nom().split('.', 1)[0], machine.mac()])
                i += 1
            print affich_tools.tableau(data,
                titre=[u'id', u'Date destruction', u'Type', u'Nom de machine', u'Adresse MAC'],
                largeur=[5, 16, 4, '*', 17],
                alignement=['d', 'c', 'c', 'c', 'c'])

            i = affich_tools.prompt(u'Votre choix ? (0 pour annuler) id =')
            try:
                i = int(i)
            except:
                print 'Choix invalide'
                continue

            if i == 0: break
            if i > len(valid):
                print 'Choix invalide'
                continue
            choix = valid[i-1]
            if choix: break

    if not choix:
        # Retour à l'interface de recherche
        return None
    return choix


def ressuscite(adh, oldmachine):
    """Ressuscite une instance de machine."""
    # On regarde quel type de machine c'était
    if str(oldmachine.__class__)[-4:] == 'Fixe':
        strmachine = u"Cette machine était une machine Fixe."
        strdef = 'Fixe'
    elif str(oldmachine.__class__)[-4:] == 'Wifi':
        strmachine = u"Cette machine était une machine Wifi."
        strdef = 'Wifi'
    else:
        strmachine = u"L'ancien type de cette machine est inconnu ..."
        strdef = ''

    # Certaines fois, on peut vouloir transformer une machine Fixe en Wifi,
    # et inversement (erreurs d'ajout ...)
    annul, choix = dlg.menu(strmachine,
                    choices=[("Fixe",u"Ressusciter en tant que machine Fixe"),
                             ("Wifi",u"Ressusciter en tant que machine Wifi")],
                    default_item=strdef,
                    title=u"Nouveau type de machine")

    if annul: return False

    if choix == 'Fixe':
        machine = MachineFixe(adh)
    elif choix == 'Wifi':
        machine = MachineWifi(adh)


    err = ""

    # On remet le nom
    try: machine.nom(str(oldmachine.nom()))
    except ValueError, c: err += c.args[0] + '\n'
    except EnvironmentError, c: err += c.args[0] + '\n'

    # On remet la MAC
    try: machine.mac(str(oldmachine.mac()))
    except ValueError, c:
        if len(c.args)>1 and c.args[1] == 1 and isadm:
            # Mac en double
            no, res = dlg.yesno(text=u"L'adresse MAC existe déjà, continuer ?", title=u"Adresse MAC")
            if not no:
                try: machine.mac(str(oldmachine.mac()), 1)
                except ValueError, c: err += c.args[0] + '\n'
                except EnvironmentError, c: err += c.args[0] + '\n'
        elif len(c.args)>1 and c.args[1] == 3 and isadm:
            # Mac douteuse
            no, res = dlg.yesno(text=u"L\'adresse MAC ne correspond à aucun constructeur, continuer ?",
                                title=u"Adresse MAC")
            if not no:
                try: machine.mac(str(oldmachine.mac()), 1)
                except ValueError, c: err += c.args[0] + '\n'
                except EnvironmentError, c: err += c.args[0] + '\n'
        else:
            err += c.args[0] + '\n'
    except EnvironmentError, c: err += c.args[0] + '\n'

    # On met une IP automatique
    try: machine.ip('<automatique>')
    except ValueError, c: err += c.args[0] + '\n'
    except EnvironmentError, c: err += c.args[0] + '\n'
    except RuntimeError, c: err += c.args[0] + '\n'

    # Si c'est une machine WiFi, on rajoute la clé WiFi
    if choix == 'Wifi':
        try: machine.ipsec(oldmachine.ipsec())
        except ValueError, c: err += c.args[0] + '\n'
        except EnvironmentError, c: err += c.args[0] + '\n'
        except RuntimeError, c: err += c.args[0] + '\n'
        except AttributeError: pass # apparemment, ce n'est pas une machine wifi

    # Des erreurs ?
    if err:
        dlg.msgbox(text=u"%s" % err,title=u"Paramètres machine")
    set_machine(machine)

    return True


def ressuscite_adherent(old):
    """Ressuscite une instance d'adhérent"""

    if not isinstance(old, Adherent):
        dlg.msgbox(text=u"Ceci n'est pas un adhérent !",
                   title=u"Mauvais type d'objet")
        return
    if not isadm:
        dlg.msgbox(text=u"Vous devez être nounou !",
                   title=u"Droits insuffisants")
        return

    # On contourne ldap_crans
    data = old._data

    # Ajout d'une entrée dans l'historique
    timestamp = time.localtime()
    hist = "%s, %s : " % (time.strftime(date_format, timestamp), cur_user)
    hist += "resurrection"
    data['historique'].append(hist)

    # Entrées à vérifier :
    #   aid, canonicalAlias, homeDirectory, mail, mailAlias, uid, uidNumber

    # On recherche les aid/uidNumber pris
    used_aid = [0]
    used_uidNumber = [0]
    for r in db.conn.search_s(Adherent.base_dn, 1, Adherent.filtre_idn):
        # r = ( dn, {} )
        d = r[1]
        if d.has_key('aid'):
            used_aid.append(int(d['aid'][0]))
        if d.has_key('uidNumber'):
            used_uidNumber.append(int(d['uidNumber'][0]))

    # Nouvelle valeur pour aid (peut mieux faire)
    aid = max(used_aid) + random.randint(1, 10)
    data['aid'] = [str(aid)]
    dn = 'aid=%d,ou=data,dc=crans,dc=org' % aid

    if 'posixAccount' in data['objectClass']:
        # L'adhérent avait un compte, il faut changer/vérifier ses attributs
        uidNumber = max(used_uidNumber) + random.randint(1, 10)
        data['uidNumber'] = [str(uidNumber)]

        # Disponibilité du login (vérification sommaire)
        login = data['uid'][0]
        if os.path.exists("/home/" + login):
            no = dlg.yesno(text=u"Le compte %s existe déjà, continuer ?" % login,
                           title=u"Compte existant")
            if no:
                return
    if True:
        # On croise les doigts
        try:
            modlist = ldap.modlist.addModlist(data)
            db.conn.add_s(dn, modlist)
            dlg.msgbox(text=u"Résurrection effectée ! Veuillez maintenant restaurer le home et les mails",
                       title=u"Fin")
            # Au cas où l'adhérent avait des droits
            db.services_to_restart('droits')
            # On notifie après une résurrection
            db.services_to_restart('mail_modif', ['uid=' + login])
        except Exception, e:
            dlg.msgbox(text=unicode(e), title=u"Erreur")

    else:
        print data
        raise ValueError("debug")

def ressuscite_club(old):
    """Ressuscite une instance d'un club"""

    if not isinstance(old, Club):
        dlg.msgbox(text=u"Ceci n'est pas un club !",
                   title=u"Mauvais type d'objet")
        return
    if not isadm:
        dlg.msgbox(text=u"Vous devez être nounou !",
                   title=u"Droits insuffisants")
        return

    # On contourne ldap_crans
    data = old._data

    # Ajout d'une entrée dans l'historique
    timestamp = time.localtime()
    hist = "%s, %s : " % (time.strftime(date_format, timestamp), cur_user)
    hist += "resurrection"
    data['historique'].append(hist)

    # Entrées à vérifier :
    #   aid, canonicalAlias, homeDirectory, mail, mailAlias, uid, uidNumber

    # On recherche les aid/uidNumber pris
    used_cid = [0]
    used_uidNumber = [0]
    for r in db.conn.search_s(Club.base_dn, 1, Club.filtre_idn):
        # r = ( dn, {} )
        d = r[1]
        if d.has_key('cid'):
            used_cid.append(int(d['cid'][0]))
        if d.has_key('uidNumber'):
            used_uidNumber.append(int(d['uidNumber'][0]))

    # Nouvelle valeur pour cid (peut mieux faire)
    cid = max(used_cid) + random.randint(1, 10)
    data['cid'] = [str(cid)]
    dn = 'cid=%d,ou=data,dc=crans,dc=org' % cid

    if 'posixAccount' in data['objectClass']:
        # L'adhérent avait un compte, il faut changer/vérifier ses attributs
        uidNumber = max(used_uidNumber) + random.randint(1, 10)
        data['uidNumber'] = [str(uidNumber)]

        # Disponibilité du login (vérification sommaire)
        login = data['uid'][0]
        if os.path.exists("/home/" + login):
            no = dlg.yesno(text=u"Le compte %s existe déjà, continuer ?" % login,
                           title=u"Compte existant")
            if no:
                return
        if True:
            # On croise les doigts
            try:
                modlist = ldap.modlist.addModlist(data)
                db.conn.add_s(dn, modlist)
                dlg.msgbox(text=u"Résurrection effectée ! Veuillez maintenant restaurer le home et les mails",
                           title=u"Fin")
                # Au cas où l'adhérent avait des droits
                db.services_to_restart('droits')
                # On notifie après une résurrection
                db.services_to_restart('mail_modif', ['uid=' + login])
            except Exception, e:
                dlg.msgbox(text=unicode(e), title=u"Erreur")

        else:
            print data
            raise ValueError("debug")

if __name__ == '__main__':
    if len(sys.argv) < 2:
        menu_principal()
        os.system("clear")
    else:
        for path in sys.argv[1:]:
                fichier = open(path, 'r')
                machine = cPickle.load(fichier)
                adh = choixadherent(machine)
                if adh:
                    ressuscite(adh, machine)
