# -*- coding: utf-8 -*-

from models import DatabaseEntry
import os
import hashlib
import re

class FileListing:
    """ Object use to list files recursively """

    def __init__ (self):
        pass

    def readdir (self, path):
        """
            List files recursively.

            path: Directory to list.
            return: List of files containned in @path and its subdirs.
        """
        ret = []

        for filename in os.listdir (path):
            abspath = os.path.join (path, filename)

            if os.path.isdir (abspath) == True:
                ret = ret + self.readdir (abspath)
            else:
                ret.append (abspath)

        return ret

class FileChecksum:
    """ Object used to get files' checksum. """

    def __init__ (self):
        pass

    def checksum (self, filelist, block_size = 512):
        """
            Get checksum of each files

            filelist: List of files to hash (can be obtained with the #FileListing object
            block_size: Files are read by block of data, this param define the size in bytes of those block (default = 512)
            return: A list of tuples (filepath, filehash)
        """
        ret = []

        for f in filelist:
            h = hashlib.sha256 ()

            try:
                fd = open (f, "rb")
            except IOError:
                print "Can't open :", f
            else:
                while True:
                    data = fd.read (block_size)

                    if not data:
                        break

                    h.update (data)

                fd.close ()
                ret.append ((f, h.hexdigest ()))

        return ret

class FileEncrypt:
    """ Object used to encrypt a list of files. """

    def __init__ (self, recipient, src, dest, nbackups = 5, extra_opts = ""):
        """
            Object's constructor

            recipient: GPG Key used to encrypt files. (need to be generated before the execution)
            src: Directory in which files are stored
            dest: Directory where to put encrypted files
            nbackups: number of copies (default = 5)
        """

        self.extra_opts = extra_opts
        self.recipient  = recipient
        self.nbackups   = nbackups
        self.src  = src
        self.dest = dest

        self.pattern = re.compile (r"(\.\d+)*.gpg")

    def _backup (self, path, iterate = 1):
        """
            Internal function which backup existant encrypted files.

            path: Path of the original file
            iterate: Internal param for the recursivity
        """

        npath = self.pattern.sub ("." + str (iterate) + ".gpg", path)

        if os.path.exists (npath) == True:
            self._backup (npath, iterate + 1)

        if iterate <= self.nbackups + 1:
            # Rename the file
            os.rename (path, npath)

            # Update the database
            try:
                dbe = DatabaseEntry.objects.get (path = path)
            except DatabaseEntry.DoesNotExist:
                pass
            else:
                dbe.path = npath
                dbe.save ()

    def encrypt (self, filelist):
        """
            Encrypt files using GnuPG

            filelist: List of files to encrypt (files stored in @self.src)
            return: List of tuples (filepath, encryptedpath)
        """

        ret = []

        for f in filelist:
            ef = f.replace (self.src, self.dest) + ".gpg"
            dirname = os.path.dirname (ef)

            if os.path.exists (dirname) == False:
                os.makedirs (dirname)

            if os.path.exists (ef) == True:
                self._backup (ef)

            os.system ("gpg " + self.extra_opts + " --output " + ef + " --encrypt --recipient " + self.recipient + " " + f)
            ret.append ((f, ef))

        return ret

    def decrypt (self, filelist):
        """
            Decrypt files using GnuPG
        """

        pass

class FileManager:
    """ Object implementing all previously created objects. """

    def __init__ (self, recipient, src, dest, nbackups = 5, extra_gpg_opts = ""):
        """
            Object's constructor

            See #FileEncrypt constructor for more details.
        """

        self.src = src
        self.fl  = FileListing ()
        self.fh  = FileChecksum ()
        self.fe  = FileEncrypt (recipient, src, dest, nbackups, extra_gpg_opts)

    def _combinedata (self, checksums, encrypted):
        """
            Combine data in one list of tuples.

            checksums: List of tuples: (path, checksum) returned by #FileChecksum.checksum
            encrypted: List of tuples: (path, encrypted_path) returned by #FileEncrypt.encrypt
            return: List of tuples: (path, checksum, encrypted_path)
        """
        ret = []

        for path,checksum in checksums:
            for path2,encrypted_path in encrypted:
                if path == path2:
                    ret.append ((path, checksum, encrypted_path))
                    break

        return ret

    def run (self):
        """
            Do: Listing, hash and encryption.
        """

        try:
            if os.path.exists (self.src) == False:
                raise self.src + ": path doesn't exists."
        except Exception as e:
            print e
        else:
            files     = self.fl.readdir (self.src)
            checksums = self.fh.checksum (files)

            to_encrypt = files

            for dbe in DatabaseEntry.objects.all ():
                # Check if file's checksum is already in the database
                for f,h in checksums:
                    # If yes, don't need to encrypt it
                    if dbe.checksum == h:
                        to_encrypt.remove (f)
                        break

            # Now, delete unused checksums from the list
            for c in checksums:
                if c[0] not in to_encrypt:
                    checksums.remove (c)

            # Encrypt only files which are not in the database
            encrypted = self.fe.encrypt (to_encrypt)

            data = self._combinedata (checksums, encrypted)

            # Put all data in the database
            for _,filehash,encryptedpath in data:
                dbe = DatabaseEntry (checksum = filehash, path = encryptedpath, sent = 0)
                dbe.save ()

# vim: tabstop=4 shiftwidth=4 expandtab