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

# Copyright 2008-2012 Calculate Ltd. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
from os import path
import subprocess
from subprocess import Popen,PIPE,STDOUT
import stat
from shutil import copytree, rmtree
from calculate.lib import cl_overriding
import re
import sys
from itertools import *
import tarfile

try:
    from magic import open as type_file, MAGIC_NONE as MAGIC_NONE
except ImportError:
    try:
        from magic import open as type_file, NONE as MAGIC_NONE
    except:
        type_file = None
        MAGIC_NONE = None

from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_lib3',sys.modules[__name__])

class process:
    """Execute system command by Popen
    
    Examples:
    
    execute program and get result:
    if process("/bin/gzip","/boot/somefile").success():
        print "Gzip success"

    unzip and process unzip data by cpio (list files):
    processGzip = process("/bin/gzip","-dc","/boot/initrd")
    processCpio = process("/bin/cpio","-tf",stdin=processGzip)
    filelist = processCpio.readlines()

    execute command and send data:
    processGrub = process("/sbin/grub")
    processGrub.write("root (hd0,0)\n")
    processGrub.write("setup (hd0)\n")
    processGrub.write("quit\n")
    isok = processGrub.success()

    union stdout and stderr:
    process("/bin/ls","/",stderr=STDOUT)
    
    result to stdout:
    process("/bin/ls",stdout=None)

    get data from keyboard:
    process("/bin/cat",stdin=None)
    """
    def __init__(self,command,*params,**kwarg):
        if not "stdin" in kwarg:
            stdin=self._defaultStdin
        else:
            if kwarg["stdin"] == None:
                stdin = self._keyboardStdin
            else:
                stdin=kwarg["stdin"].getStdout
        self.stdout = kwarg.get("stdout",PIPE)
        self.envdict = kwarg.get("envdict",{})
        if "lang" in kwarg:
            self.envdict["LANG"] = kwarg.get('lang')
        self.langc = "langc" in kwarg

        self.stderr = kwarg.get("stderr",PIPE)
        self.command = [command] + list(params)
        self.stdin = stdin
        self.iter = None
        self.pipe = None
        self.cacheresult = None
        self.cacheerr = None
        self.communicated = False

    def _open(self):
        """Open pipe if it not open"""
        if not self.pipe:
            self.pipe = Popen(self.command,
                              stdout=self.stdout,
                              stdin=self.stdin(),
                              stderr=self.stderr,
                              env=self.envdict)

    def _defaultStdin(self):
        """Return default stdin"""
        return PIPE

    def _keyboardStdin(self):
        """Return keyboard stdin"""
        return None

    def getStdout(self):
        """Get current stdout"""
        self.close()
        self._open()
        return self.pipe.stdout

    def write(self,data):
        """Write to process stdin"""
        self._open()
        self.pipe.stdin.write(data)
        self.pipe.stdin.flush()

    def close(self):
        """Close stdin"""
        if self.pipe:
            self.pipe.stdin.close()

    def readerr(self):
        self.read()
        return self.cacheerr

    def readByLine(self):
        
        try:
            self._open()
            if self.cacheresult is None:
                self.cacheresult = []
                s = self.pipe.stdout.readline()
                while s:
                    yield s
                    s = self.pipe.stdout.readline()
                if self.pipe.stderr:
                    self.cacheerr = self.pipe.stderr.read()
                else:
                    self.cacheerr = ""

        except KeyboardInterrupt:
            self.kill()
            raise KeyboardInterrupt
        finally:
            self.cacheresult = "\n".join(self.cacheresult)

    def read(self):
        """Read all data"""
        try:
            self._open()
            if self.cacheresult is None:
                self.cacheresult,self.cacheerr = self.pipe.communicate()
            return self.cacheresult
        except KeyboardInterrupt:
            self.kill()
            raise KeyboardInterrupt

    def readlines(self):
        """Read lines"""
        return self.read().split('\n')

    def __iter__(self):
        """Get iterator"""
        if not self.iter:
            self.iter = iter(self.readlines())
        return self.iter

    def kill(self):
        """Kill this process"""
        if self.pipe:
            self.pipe.kill()

    def next(self):
        """Next string from stdout"""
        return self.__iter__().next()

    def returncode(self):
        """Get return code"""
        self.read()
        return self.pipe.returncode

    def success(self):
        """Success or not"""
        return self.returncode() == 0

    def failed(self):
        """Failed or not"""
        return self.returncode() != 0

def getModeFile(nameFile, mode="all"):
    """Выдает информацию о файле
    mode=="all"
        права файла, владелец, группа файла
    mode=="mode"
        права файла
    mode=="owner"
        владелец, группа файла
    """
    fst = os.lstat(nameFile)
    if mode == "all":
        return (stat.S_IMODE(fst.st_mode), fst.st_uid, fst.st_gid)
    if mode == "mode":
        return stat.S_IMODE(fst.st_mode)
    if mode == "owner":
        return fst.st_uid, fst.st_gid

def chownR(directory, uid, gid):
    """Recusive chown"""
    def chownPaths(rootPath, listPath, uid, gid):
        for chPath in listPath:
            chownPath = path.join(rootPath, chPath)
            statInfo = os.lstat(chownPath)[stat.ST_MODE]
            if stat.S_ISLNK(statInfo):
                os.lchown(chownPath, uid, gid)
            else:
                os.chown(chownPath, uid, gid)

    for root, dirs, files in os.walk(directory):
        # меняем владельца директории
        os.chown(root, uid, gid)
        # Меняем владельца директорий
        chownPaths(root, dirs, uid, gid)
        # Меняем владельца файлов
        chownPaths(root, files, uid, gid)
    return True

class proxy_type_file:
    def __init__(self,flags):
        self.flags = flags

    def load(self):
        pass

    def setflags(self,flags):
        self.flags = flags

    def close(self):
        pass

    def file(self,filename):
        if path.exists(filename):
            if self.flags == 0x410:
                processFile = process("file","-bi",filename)
                if processFile.success():
                    return processFile.read().rstrip()
            else:
                processFile = process("file","-bz",filename)
                if processFile.success():
                    return processFile.read().rstrip()
        return None

class typeFile:
    """Получение типа файла"""

    def __init__(self, magic=0x410):
        if type_file is None:
            self.magicObject = proxy_type_file(MAGIC_NONE)
        else:
            self.magicObject = type_file(MAGIC_NONE)
        self.magicObject.load()
        self.magicObject.setflags(magic)

    def __del__(self):
        """Закрываем magic"""
        self.magicObject.close()

    def getMType(self, filename):
        """Информация о типе файла"""
        return self.magicObject.file(filename)

    def isBinary(self, filename):
        """является ли файл бинарным"""
        mime = self.getMType(filename)
        # В случае ошибки
        if mime.count("`"):
            return mime
        elif mime.count("binary"):
            return True
        return False

class scanDirectory:
    """Класс для cканирования директории"""

    def processingFile(self, pathname, prefix):
        """Обработка в случае файла"""
        return True

    def processingDirectory(self, pathname, prefix):
        """Обработка в случае директории если возвращаем None то пропуск дир."""
        return True

    def scanningDirectory(self, scanDir, skipFile=[], skipDir=[],
                          prefix=None, flagDir=False):
        """Сканирование и обработка шаблонов в директории scanDir"""
        ret = True
        if not prefix:
            prefix = path.join(scanDir,"")[:-1]
        if not flagDir:
            # проверка корневой директории
            retDir = self.processingDirectory(scanDir, scanDir)
            if retDir is None:
                return None
            elif retDir is False:
                return False
        if flagDir or stat.S_ISDIR(os.lstat(scanDir)[stat.ST_MODE]):
            for absPath in sorted(listDirectory(scanDir,fullPath=True)):
                relPath = absPath.split(prefix)[1]
                stInfo = os.lstat(absPath)
                statInfo = stInfo[stat.ST_MODE]
                if stat.S_ISREG(statInfo):
                    # Обработка файла
                    if relPath in skipFile:
                        continue
                    if not self.processingFile(absPath, prefix):
                        return False
                elif stat.S_ISDIR(statInfo):
                    # Обработка директории
                    if relPath in skipDir:
                        continue
                    retDir = self.processingDirectory(absPath, prefix)
                    if retDir is None:
                        continue
                    elif retDir is False:
                        return False
                    ret = self.scanningDirectory(absPath, skipFile,
                                                 skipDir, prefix, True)
                    if ret is False:
                        return False
        return ret

class processProgress(process):
    """
    Generator like process progress
    """
    def __init__(self,command,*params,**kwarg):
        process.__init__(self,command,*params,**kwarg)
        self.readsize = kwarg.get("readsize",10)
        self.init(**kwarg)

    def init(self,*args,**kwarg):
        pass

    def processInit(self):
        pass

    def processEnd(self):
        pass

    def processString(self,strdata):
        self.cacheresult.append(strdata)
        return strdata

    def processBuffer(self,buf):
        pass

    def progress(self):
        try:
            res = self.processInit()
            if res != None: yield res
            self._open()
            if self.cacheresult is None:
                self.cacheresult = []
                self.buf = ""
                part = self.pipe.stdout.read(1)
                while part:
                    if self.buf:
                        self.buf += part
                    else:
                        self.buf = part
                    res = self.processBuffer(part)
                    if res:
                        yield res
                    while "\n" in self.buf:
                        strdata, op, self.buf = self.buf.partition('\n')
                        res = self.processString(strdata)
                        if res:
                            yield res
                    part = self.pipe.stdout.read(self.readsize)
            self.pipe.wait()
            self.pipe.poll()
            res = self.processEnd()
            if res != None: yield res
        except KeyboardInterrupt:
            self.pipe.kill()
            raise
        finally:
            self.cacheresult = "\n".join(self.cacheresult)

def countFiles(dirpath,onefilesystem=True):
    """
    Count files in specified dirpath
    """
    num = 1
    for dirpath,dirnames,filenames in os.walk(dirpath):
        num += len(set(dirnames) | set(filenames))
        if onefilesystem:
            mountDirs = filter(lambda x:path.ismount(path.join(dirpath,x)),
                        dirnames)
            for dirname in mountDirs:
                dirnames.remove(dirname)
    return num

def getFilesCount(directory):
    """Alias for compatibility"""
    return countFiles(directory,onefilesystem=False)


def runOsCommand(cmd,in_str=None, env_dict=None):
    """Run system command

    Параметры:
    cmd            внешняя программа
    in_str         данные передаваемые программе на страндартный вход.
    env_dict       словарь переменных окружения
    Возвращает (код возврата, список stdout+stderr)
    """
    pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            env=env_dict,
                            close_fds=True,
                            shell=True)
    fout, fin, ferr = (pipe.stdout, pipe.stdin, pipe.stderr)
    # если есть данные на вход, передать их
    if in_str:
        fin.write(in_str)
    fin.close()
    res = map(lambda x: x.rstrip(), fout.readlines())
    fout.close()
    res += map(lambda x: x.rstrip(), ferr.readlines())
    ferr.close()
    # Код возврата
    retcode = pipe.wait()
    return retcode, res

def copyDir(srcDir, destDir):
    """Копируем директорию srcDir в destDir

    При копировании сохраняются владелец, группа, права
    """
    def ignoreFile(pathname, names):
        """Игнорирование сокетов при копировании"""
        ignore = []
        for name in names:
            if stat.S_ISSOCK(os.lstat(path.join(pathname, name))[stat.ST_MODE]):
                ignore.append(name)
        return ignore

    copytree(srcDir, destDir, ignore=ignoreFile)
    return True

def removeDir(rmDir):
        """Рекурсивное удаление директории"""
        rmtree(rmDir)
        return True

def getRunCommands():
    """List run program"""
    def getCmd(procNum):
        cmdLineFile = '/proc/%s/cmdline'%procNum
        try:
            if path.exists(cmdLineFile):
                return open(cmdLineFile,'r').read().strip()
        except:
            pass
        return ""
    if not os.access('/proc',os.R_OK):
        return []
    return map(getCmd,
           filter(lambda x:x.isdigit(),
           listDirectory('/proc')))


def isMount(pathname):
    """В случае монтирования директории выдает другой примонтированный путь"""
    findPath = [path.abspath(pathname)]
    if findPath[0].startswith('/dev'):
        info = device.getUdevDeviceInfo(name=findPath[0])
        if 'DM_VG_NAME' in info and 'DM_LV_NAME' in info:
            lvmDeviceName = \
                '/dev/mapper/{vg}-{lv}'.format(vg=info['DM_VG_NAME'],
                                               lv=info['DM_LV_NAME'])
            findPath.append(lvmDeviceName)
    mtabFile = '/etc/mtab'
    if not os.access(mtabFile,os.R_OK):
        return ""
    return filter(lambda x: x!=findPath,
           reduce(lambda x,y: y,
           ifilter(lambda x: any(p in x for p in findPath),
           imap(lambda x: [x[0], x[1]],
           imap(lambda x: x.split(" "),
           open(mtabFile)))), [""]))[0]

def commonPath(*paths):
    """Return common path from list of paths"""
    paths = map(lambda x:path.normpath(x).split('/'),paths)
    res = map(lambda x:x[0],
        filter(lambda x:filter(lambda y:x[0]==y,x[1:]),zip(*paths)))
    return "/".join(res)

def childMounts(pathname):
    """Get all mount points which contain path"""
    if pathname != "none":
        absPath = path.abspath(pathname)
    else:
        absPath = pathname
    mtabFile = '/etc/mtab'
    if not os.access(mtabFile,os.R_OK):
        return ""
    return reduce(lambda x,y: x + [y],
                  filter(lambda x: commonPath(absPath,x[0])==absPath or \
                                   commonPath(absPath,x[1])==absPath,
                  map(lambda x: [x[0], x[1]],
                  map(lambda x: x.split(" "),
                  open(mtabFile)))),
                  [])


def pathJoin(*paths):
    """Складывает пути, в отличии от os.path.join, складывает абсолютные пути"""
    if len(paths)==1:
        return paths[0]
    return reduce(path.join,
                  filter(lambda x:x and x != "/",
                  map(lambda x: x.startswith("/") and x[1:] or x,
                  paths[1:])),paths[0])

def checkDigestFile(digestfile):
    """Check digest by digestfile"""
    reEntry = re.compile(r"# (\S+) HASH\n(\S+)  (\S+)",re.S)
    result = []
    for alg,hashdata,filename in \
        reEntry.findall(open(digestfile,'r').read()):
        if hasattr(hashlib,alg.lower()):
            hashobj = getattr(hashlib,alg.lower())
            filename = path.join(path.dirname(digestfile),filename)
            if os.path.exists(filename):
                digest = hashobj(open(filename,'r').read())
                result.append((alg,
                    digest.hexdigest().upper() == hashdata.upper()))
    return result

def listDirectory(directory,fullPath=False,onlyDir=False):
    """Get files from directory, if it exists"""
    if not path.exists(directory):
        return []
    try:
        if fullPath:
            if onlyDir:
                return filter(lambda x:path.isdir(x),
                       map(lambda x:path.join(directory,x),
                       os.listdir(directory)))
            else:
                return map(lambda x:path.join(directory,x),
                       os.listdir(directory))
        else:
            if onlyDir:
                return filter(lambda x:path.isdir(path.join(directory,x)),
                       os.listdir(directory))
            else:
                return os.listdir(directory)
    except OSError:
        pass
    return []

def makeDirectory(pathname):
    """Make directory and parent.

    Return False on error"""
    try:
        parent = path.split(path.normpath(pathname))[0]
        if not path.exists(parent):
            makeDirectory(parent)
        else:
            if path.exists(pathname):
                return True
        os.mkdir(pathname)
        return True
    except Exception, e:
        return False

def readLinesFile(filename):
    """Read file by line"""
    try:
        if path.exists(filename):
            for line in open(filename,'r'):
                yield line.rstrip('\n')
    except (OSError,IOError):
        pass
    finally:
        raise StopIteration

def readFile(filename):
    """Read whole file or return empty string"""
    try:
        if path.exists(filename):
            return open(filename,'r').read()
    except (OSError,IOError):
        pass
    return ""

import common

class getProgPath:
    """Get full path of program or False"""
    cache = {}

    def __call__(self,progname):
        baseprogname = path.basename(progname)
        if baseprogname in self.cache:
            return self.cache[baseprogname]
        env = {"LANG":"C"}
        env.update(os.environ.items() + [("PATH",common.getpathenv())] +\
                   env.items())
        res = runOsCommand("which %s"%baseprogname,env_dict=env)
        if path.isabs(progname) and path.exists(progname):
            self.cache[baseprogname] = progname
            return self.cache[baseprogname]
        elif res[0] == 0:
            self.cache[baseprogname] = res[1][0].strip()
            return self.cache[baseprogname]
        else:
            return False

getProgPath = getProgPath()

def checkUtils(*utils):
    """Check utils, exit if it not found and return fullpath"""
    retval = []
    for util in utils:
        utilPath = getProgPath(util)
        if not utilPath:
            raise Exception(_("Command not found '%s'")%
                                     path.basename(util))
        retval.append(utilPath)
    if len(retval) == 1:
        return retval[0]
    else:
        return retval

def getCmdLineParam(paramName):
    """Get value of param /proc/cmdline. If param not found then empty.
    """
    cmdLine = '/proc/cmdline'
    paramName = "%s="%paramName
    params = \
        map(lambda x:x.partition('=')[2],
        filter(lambda x:x.startswith(paramName),
        readFile(cmdLine).split(' ')))
    if params:
        return params[-1]
    else:
        return ""

from device import getUUIDDict

def isFstabMount(pathname,mapDevUuid={},listFstab=[]):
    """Get mount point or device from fstab"""
    def removeQuotes(s):
        return s.replace('"','').replace("'","")
    if pathname == "swap":
        absPath = "swap"
    else:
        absPath = path.abspath(pathname)
    if not mapDevUuid:
        mapDevUuid.update(getUUIDDict())
    # convert fstab to
    # [['/dev/sda3', '/', '', 'reiserfs', 'noatime', '', '', '0', '2\n'],
    # ['/dev/sda5', '/var/calculate', 'reiserfs', 'noatime', '0', '0\n']]
    #if not listFstab:
    if not listFstab:
        listFstab.extend(
                map(lambda x: [mapDevUuid.get(removeQuotes(x[0]),x[0]),
                          x[1] if x[2] != "swap" else "swap"],
                filter(lambda x: len(x) >= 4,
                map(lambda x: filter(lambda x: x,
                              x.replace('\t',' ').split(' ')),
                filter(lambda x: not x.startswith('#') and x.strip(),
                open("/etc/fstab"))))))
    # get mount point or device or dir
    return filter(lambda x: x!=absPath,
           reduce(lambda x,y: y,
           filter(lambda x: absPath in x and x[1] != "none",
           listFstab),[""]))[0]

class SingletonParam(type):
    def __init__(cls, name, bases, dict):
        super(SingletonParam, cls).__init__(name, bases, dict)
        cls.instance = {}

    def __call__(cls,*args,**kw):
        keyarg = args[0] if args else ""
        if not keyarg in cls.instance:
            cls.instance[keyarg] = \
                super(SingletonParam, cls).__call__(*args, **kw)
        return cls.instance[keyarg]

import device

class FStab(object):
    """Data reader for fstab"""
    __metaclass__ = SingletonParam
    fstab_file = '/etc/fstab'
    NAME, DIR, TYPE, OPTS, FREQ, PASSNO = range(0,6)

    def __init__(self,fstab_file=None):
        if fstab_file:
            self.fstab_file = fstab_file
        self.cache = []
        self.rotateCache = []
        self.dictUUID = getUUIDDict()
        self.rebuildCache()

    def rebuildCache(self):
        """Rebuild cache from fstab file"""
        self.cache = \
            map(lambda x:map(lambda y:y.strip(),x.split()),
            filter(lambda x:x and not x.lstrip().startswith("#"),
            open(self.fstab_file,'r').read().split('\n')))
        for data in self.cache:
            convertDev = lambda x: path.realpath(x) if x.startswith('/') else x
            data[0] = device.getUdevDeviceInfo(
                       name=convertDev(self.dictUUID.get(data[0],data[0]))
                      ).get('DEVNAME',data[0])
            data[1] = data[1] if data[2] != "swap" else "swap"
        self.rotateCache = zip(*self.cache)

    def getBy(self,what=DIR,where=NAME,_in=None,eq=None,
              noteq=None,allentry=False):
        """Get data from fstab"""
        if not eq is None:
            filterfunc = lambda x: x[where] == eq
        elif not _in is None:
            filterfunc = lambda x: _in in x[where]
        else:
            filterfunc = lambda x: x[where] != noteq
        res = map(lambda x:x[what],filter(filterfunc,self.cache))
        if allentry:
            return res
        else:
            return "" if not res else res[-1]

    def getFields(self,*fields):
        """Get all data by specifie fields"""
        return zip(*reduce(lambda x,y:x+[self.rotateCache[y]],fields,[]))

    def isExists(self,what=DIR,eq=None,noteq=None):
        """Field with condition exist in fstab"""
        if not eq is None:
            filterfunc = lambda x: x[what] == eq
        else:
            filterfunc = lambda x: x[what] != noteq
        return bool(filter(filterfunc,self.cache))

def tarLinks(rootpath,archpath,skip=[]):
    """Add symbolic links to archive file"""
    links = []
    if skip:
        reSkip = re.compile("|".join(map(lambda x:x.replace("*",".*"),
            skip))).search
    else:
        reSkip = lambda x:False
    lenprefix = len(path.normpath(rootpath))+1
    if path.exists(archpath):
        os.unlink(archpath)
    # create arch
    tar = tarfile.open(archpath,"w:bz2")
    # find links
    for root, dirs, files in os.walk(rootpath):
        for link in  filter(os.path.islink,
                     map(lambda x:path.join(root,x),
                     dirs+files)):
            # try add link
            try:
                if not reSkip(link):
                    ti = tar.gettarinfo(link)
                    ti.name = link[lenprefix:]
                    tar.addfile(ti)
                    links.append(link)
            except OSError:
                pass
        # discard mounted paths
        removeDirs = map(lambda x:x[0],
                     filter(lambda x:path.islink(x[1]) or path.ismount(x[1]),
                     map(lambda x:(x,path.join(root,x)),
                     dirs)))
        map(lambda x:dirs.remove(x),
        removeDirs)
    tar.close()
    return links
