#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
from os import remove, chmod, makedirs
from os.path import exists, basename, isfile, isdir, join
from traceback import print_exc
from copy import copy

# monkey patch bug in python 2.6 and lower
# see http://bugs.python.org/issue6122
# http://bugs.python.org/issue1236
# http://bugs.python.org/issue1731717
if sys.version_info < (2, 7) and os.name != "nt":
    from subprocess import Popen
    import errno

    def _eintr_retry_call(func, *args):
        while True:
            try:
                return func(*args)
            except OSError, e:
                if e.errno == errno.EINTR:
                    continue
                raise

    # unsued timeout option for older python version
    def wait(self, timeout=0):
        """Wait for child process to terminate.  Returns returncode
        attribute."""
        if self.returncode is None:
            try:
                pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
            except OSError, e:
                if e.errno != errno.ECHILD:
                    raise
                    # This happens if SIGCLD is set to be ignored or waiting
                # for child processes has otherwise been disabled for our
                # process.  This child is dead, we can't get the status.
                sts = 0
            self._handle_exitstatus(sts)
        return self.returncode

    Popen.wait = wait

if os.name != "nt":
    from os import chown
    from pwd import getpwnam
    from grp import getgrnam

from module.utils import save_join, fs_encode
from module.plugins.Hook import Hook, threaded, Expose
from module.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword

class ExtractArchive(Hook):
    """
    Provides: unrarFinished (folder, filename)
    """
    __name__ = "ExtractArchive"
    __version__ = "0.12"
    __description__ = "Extract different kind of archives"
    __config__ = [("activated", "bool", "Activated", True),
        ("fullpath", "bool", "Extract full path", True),
        ("overwrite", "bool", "Overwrite files", True),
        ("passwordfile", "file", "password file", "unrar_passwords.txt"),
        ("deletearchive", "bool", "Delete archives when done", False),
        ("subfolder", "bool", "Create subfolder for each package", False),
        ("destination", "folder", "Extract files to", ""),
        ("recursive", "bool", "Extract archives in archvies", True),
        ("queue", "bool", "Wait for all downloads to be finished", True),
        ("renice", "int", "CPU Priority", 0), ]
    __author_name__ = ("pyload Team")
    __author_mail__ = ("admin<at>pyload.org")

    event_list = ["allDownloadsProcessed"]

    def setup(self):
        self.plugins = []
        self.passwords = []
        names = []

        for p in ("UnRar", "UnZip"):
            try:
                module = self.core.pluginManager.loadModule("internal", p)
                klass = getattr(module, p)
                if klass.checkDeps():
                    names.append(p)
                    self.plugins.append(klass)

            except OSError, e:
                if e.errno == 2:
                    self.logInfo(_("No %s installed") % p)
                else:
                    self.logWarning(_("Could not activate %s") % p, str(e))
                    if self.core.debug:
                        print_exc()

            except Exception, e:
                self.logWarning(_("Could not activate %s") % p, str(e))
                if self.core.debug:
                    print_exc()

        if names:
            self.logInfo(_("Activated") + " " + " ".join(names))
        else:
            self.logInfo(_("No Extract plugins activated"))

        # queue with package ids
        self.queue = []

    @Expose
    def extractPackage(self, id):
        """ Extract package with given id"""
        self.manager.startThread(self.extract, [id])

    def packageFinished(self, pypack):
        if self.getConfig("queue"):
            self.logInfo(_("Package %s queued for later extracting") % pypack.name)
            self.queue.append(pypack.id)
        else:
            self.manager.startThread(self.extract, [pypack.id])


    @threaded
    def allDownloadsProcessed(self, thread):
        local = copy(self.queue)
        del self.queue[:]
        self.extract(local, thread)


    def extract(self, ids, thread=None):
        # reload from txt file
        self.reloadPasswords()

        # dl folder
        dl = self.config['general']['download_folder']

        extracted = []

        #iterate packages -> plugins -> targets
        for pid in ids:
            p = self.core.files.getPackage(pid)
            self.logInfo(_("Check package %s") % p.name)
            if not p: continue

            # determine output folder
            out = save_join(dl, p.folder, "")
            # force trailing slash

            if self.getConfig("destination") and self.getConfig("destination").lower() != "none":

                out = save_join(dl, p.folder, self.getConfig("destination"), "")
                #relative to package folder if destination is relative, otherwise absolute path overwrites them

                if self.getConf("subfolder"):
                    out = join(out, fs_encode(p.folder))

                if not exists(out):
                    makedirs(out)

            files_ids = [(save_join(dl, p.folder, x["name"]), x["id"]) for x in p.getChildren().itervalues()]
            matched = False

            # check as long there are unseen files
            while files_ids:
                new_files_ids = []

                for plugin in self.plugins:
                    targets = plugin.getTargets(files_ids)
                    if targets:
                        self.logDebug("Targets for %s: %s" % (plugin.__name__, targets))
                        matched = True
                    for target, fid in targets:
                        if target in extracted:
                            self.logDebug(basename(target), "skipped")
                            continue
                        extracted.append(target) #prevent extracting same file twice

                        klass = plugin(self, target, out, self.getConfig("fullpath"), self.getConfig("overwrite"),
                            self.getConfig("renice"))
                        klass.init()

                        self.logInfo(basename(target), _("Extract to %s") % out)
                        new_files = self.startExtracting(klass, fid, p.password.strip().splitlines(), thread)
                        self.logDebug("Extracted: %s" % new_files)
                        self.setPermissions(new_files)

                        for file in new_files:
                            if not exists(file):
                                self.logDebug("new file %s does not exists" % file)
                                continue
                            if self.getConfig("recursive") and isfile(file):
                                new_files_ids.append((file, fid)) #append as new target

                files_ids = new_files_ids # also check extracted files

            if not matched: self.logInfo(_("No files found to extract"))
	


    def startExtracting(self, plugin, fid, passwords, thread):
        pyfile = self.core.files.getFile(fid)
        if not pyfile: return []

        pyfile.setCustomStatus(_("extracting"))
        thread.addActive(pyfile) #keep this file until everything is done

        try:
            progress = lambda x: pyfile.setProgress(x)
            success = False

            if not plugin.checkArchive():
                plugin.extract(progress)
                success = True
            else:
                self.logInfo(basename(plugin.file), _("Password protected"))
                self.logDebug("Passwords: %s" % str(passwords))

                pwlist = copy(self.getPasswords())
                #remove already supplied pws from list (only local)
                for pw in passwords:
                    if pw in pwlist: pwlist.remove(pw)

                for pw in passwords + pwlist:
                    try:
                        self.logDebug("Try password: %s" % pw)
                        if plugin.checkPassword(pw):
                            plugin.extract(progress, pw)
                            self.addPassword(pw)
                            success = True
                            break
                    except WrongPassword:
                        self.logDebug("Password was wrong")

            if not success:
                self.logError(basename(plugin.file), _("Wrong password"))
                return []

            if self.core.debug:
                self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles()))

            if self.getConfig("deletearchive"):
                files = plugin.getDeleteFiles()
                self.logInfo(_("Deleting %s files") % len(files))
                for f in files:
                    if exists(f): remove(f)
                    else: self.logDebug("%s does not exists" % f)

            self.logInfo(basename(plugin.file), _("Extracting finished"))
            self.manager.dispatchEvent("unrarFinished", plugin.out, plugin.file)

            return plugin.getExtractedFiles()


        except ArchiveError, e:
            self.logError(basename(plugin.file), _("Archive Error"), str(e))
        except CRCError:
            self.logError(basename(plugin.file), _("CRC Mismatch"))
        except Exception, e:
            if self.core.debug:
                print_exc()
            self.logError(basename(plugin.file), _("Unknown Error"), str(e))

        return []

    @Expose
    def getPasswords(self):
        """ List of saved passwords """
        return self.passwords


    def reloadPasswords(self):
        pwfile = self.getConfig("passwordfile")
        if not exists(pwfile):
            open(pwfile, "wb").close()

        passwords = []
        f = open(pwfile, "rb")
        for pw in f.read().splitlines():
            passwords.append(pw)
        f.close()

        self.passwords = passwords


    @Expose
    def addPassword(self, pw):
        """  Adds a password to saved list"""
        pwfile = self.getConfig("passwordfile")

        if pw in self.passwords: self.passwords.remove(pw)
        self.passwords.insert(0, pw)

        f = open(pwfile, "wb")
        for pw in self.passwords:
            f.write(pw + "\n")
        f.close()

    def setPermissions(self, files):
        for f in files:
            if not exists(f): continue
            try:
                if self.core.config["permission"]["change_file"]:
                    if isfile(f):
                        chmod(f, int(self.core.config["permission"]["file"], 8))
                    elif isdir(f):
                        chmod(f, int(self.core.config["permission"]["folder"], 8))

                if self.core.config["permission"]["change_dl"] and os.name != "nt":
                    uid = getpwnam(self.config["permission"]["user"])[2]
                    gid = getgrnam(self.config["permission"]["group"])[2]
                    chown(f, uid, gid)
            except Exception, e:
                self.log.warning(_("Setting User and Group failed"), e)