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

# Copyright 2010-2016 Mir Calculate. 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
import stat
import re
import sys

import subprocess
import ldap
import time

import getpass
from os import path

from calculate.lib.cl_ini_parser import iniParser
from calculate.lib.cl_print import color_print
from calculate.lib.utils.ip import Pinger, IPError
from calculate.lib.utils.files import (getModeFile, removeDir,
                                       isMount, pathJoin, tarLinks,
                                       listDirectory, process, find, STDOUT,
                                       sambaPasswordCheck, checkUtils)
from calculate.lib.utils.common import cmpVersion
from calculate.desktop._cl_keys import getKey, clearKey
from calculate.lib.convertenv import convertEnv
from calculate.lib.encrypt import encrypt
from client_cache import userCache
from shutil import copy2
from socket import gethostbyname
import tarfile
from calculate.desktop.desktop import Desktop

from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate

_ = lambda x: x
setLocalTranslate('cl_client3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)


class ClientError(Exception):
    pass


class RsyncProgressBar:
    """
    Rsync with using setProgress
    """
    # get number of send file from info rsync stream
    senderre = re.compile("\[sender\] i=(\d+) ", re.S)
    # get number of receivce file from info rsync rtream
    receiverre = re.compile("recv_generator\(.+,([0-9]+)\)", re.S)
    pipe = None
    maximum = 1
    copyStarting = False

    def __init__(self, title, secondtitle, rsyncstr, parent, maximum=1,
                 rsyncver="3.0.9"):
        self.title = title
        self.secondtitle = secondtitle
        self.maximum = maximum
        self.rsyncstr = rsyncstr
        self.parent = parent
        self.value = 0
        self.errors = []
        if cmpVersion(rsyncver, "3.1.0") >= 0:
            self.extraopts = " --msgs2stderr"
            self.use_stderr = True
        else:
            self.extraopts = ""
            self.use_stderr = False

    def getFilesNum(self):
        """
        Get files count created by generator
        """
        if self.pipe:
            return self.value

    def getExitCode(self):
        """
        Get rsync exitcode
        """
        if self.pipe:
            return self.pipe.wait()
        return 255

    def getErrMessage(self):
        """
        Rsync error message 
        """
        if self.pipe:
            if self.use_stderr:
                return "".join(self.errors)
            else:
                return self.pipe.stderr.read()
        return _("RsyncProgressBar: Wrong pipe")

    def runsilent(self):
        """
        Run rsync without progressbar
        """
        self.pipe = subprocess.Popen(self.rsyncstr + self.extraopts,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     close_fds=True, shell=True)
        while True:
            if self.use_stderr:
                s = self.pipe.stderr.readline()
            else:
                s = self.pipe.stdout.readline()
            if len(s) == 0:
                break
            q = self.receiverre.search(s)
            if q:
                self.value = int(q.groups()[0])

    def run(self):
        """
        Run rsync with progressbar
        """
        self.parent.startTask(self.title, progress=True)
        self.pipe = subprocess.Popen(self.rsyncstr + self.extraopts,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     close_fds=True, shell=True)
        oldpercent = 0
        while True:
            if self.use_stderr:
                s = self.pipe.stderr.readline()
            else:
                s = self.pipe.stdout.readline()
            if len(s) == 0:
                break
            if s.startswith("rsync:"):
                self.errors.append(s)
            q = self.receiverre.search(s)
            if q:
                if not self.copyStarting:
                    self.parent.endTask()
                    self.parent.startTask(self.secondtitle, progress=True)
                    # self.parent.setProgress(0)
                    self.copyStarting = True
                self.value = int(q.groups()[0])
                if self.maximum:
                    newvalue = int(100 * self.value / self.maximum)
                    newvalue = min(newvalue, 99)
                    if newvalue > oldpercent:
                        self.parent.setProgress(newvalue)
                        oldpercent = newvalue
        self.parent.setProgress(100)

    def close(self):
        self.parent.endTask()


class commandServer(color_print):
    """Server command object"""

    def setServerCommand(self, command, varsCommand, fileConfig,
                         uid=None, gid=None):
        """Set server command"""
        pathConfig = os.path.split(fileConfig)[0]
        # create user config directory
        if not os.path.exists(pathConfig):
            os.makedirs(pathConfig)
            if not uid is None and not gid is None:
                os.chown(pathConfig, uid, gid)
        objConfig = iniParser(fileConfig)
        varsRun = {"run": "on"}
        varsRun.update(varsCommand)
        if not objConfig.setVar(["command"] + command, varsRun):
            raise ClientError(_("Failed to write variables in file %s") \
                              % fileConfig)
        if not uid is None and not gid is None:
            os.chown(fileConfig, uid, gid)
        return True

    def checkUserPwdLDAP(self, server, userDN, password):
        """Проверка пароля Unix пользователя на сервере"""
        ldapInit = ldap.initialize("ldap://%s" % server)
        errMessage = ""
        try:
            ldapInit.bind_s(userDN, password)
        except ldap.INVALID_CREDENTIALS:
            errMessage = _("Wrong password")
            return False, errMessage
        except ldap.LDAPError, e:
            errMessage = e[0]['desc']
            return False, errMessage
        return True, errMessage

    def getUserPwd(self, options, optDialog, optStdIn, pwDialog=False):
        """Получить пароль у пользователя

        options   - полученные опции командной строки
        optDialog - опция командной строки для вывода диалога для получения
                    пароля
        optStdIn  - опция командной строки для получения пароля из
                    стандартного ввода (stdin)
        pwDialog  - структура для вывода приглашения в режиме диалога
        """
        if optStdIn and options.has_key(optStdIn):
            pwdA = sys.stdin.readline().rstrip()
            pwdB = sys.stdin.readline().rstrip()
        elif optDialog and options.has_key(optDialog):
            if not pwDialog:
                pwDialog = [_("New password"),
                            _("Retype the new password")]
            pwdA = getpass.getpass(pwDialog[0] + _(":"))
            pwdB = getpass.getpass(pwDialog[1] + _(":"))
        else:
            return ""
        if not pwdA or not (pwdA == pwdB):
            self.printERROR(_("ERROR") + _(": ") + \
                            _("passwords do not match"))
            return False
        userPwd = pwdA
        return userPwd


class Client(commandServer, encrypt, Desktop):
    """
    Клиентский модуль работы с доменом
    """

    # user calculate config directory
    pathConfig = ".calculate"
    # client soft config file
    configFileSoft = os.path.join(pathConfig, "ini.env")
    # client config file
    configFileDesktop = os.path.join(pathConfig, "desktop.env")
    # client config for server
    configFileServer = os.path.join(pathConfig, "server.env")
    # file list of user profile
    listTemplFile = os.path.join(pathConfig, "files.txt")

    # files which not cleaning from user home directory
    skipHomeFile = ["Home", "Disks", "Share", "FTP", configFileDesktop]

    # option dict of services from /var/calculate/remote/server.env
    optionsInfo = {}

    # convertation object from old remote env
    convObj = None
    # private user files
    privateFiles = [configFileServer]
    # private user directories
    privateDirs = []

    def removeVars(self):
        """
        Remove domain variables. Useing on remove package
        and undomain action
        """
        self.clVars.Delete("cl_remote_host", "local")
        self.clVars.Delete("cl_remote_pw", "local")
        self.clVars.Delete("os_remote_auth")
        self.clVars.Set("cl_remote_host", "", True)
        self.clVars.Set("cl_remote_pw", "", True)
        self.clVars.Set("os_remote_auth", "", True)
        return True

    def mountSambaRes(self, host, userName, userPwd, res, rpath, uid=None,
                      gid=None):
        """
        Подключить samba ресурс
        """
        mountCmd = checkUtils('/bin/mount')
        if cmpVersion(self.clVars.Get('cl_cifs_ver'), "2.05") < 0:
            nomapposix = ""
        else:
            nomapposix = ",nomapposix"
        if not uid is None:
            # mount by uid
            p = process(mountCmd, "-t", "cifs", "-o",
                        "cache=loose,user=%s,uid=%d,gid=%d,noperm%s" % (
                            userName, uid, gid, nomapposix),
                        "//%s/%s" % (host, res), rpath,
                        envdict={"PASSWD": userPwd}, stderr=STDOUT)
            return p.success()
        else:
            p = process(mountCmd, "-t", "cifs", "-o",
                        "cache=loose,user=%s%s" % (userName, nomapposix),
                        "//%s/%s" % (host, res), rpath,
                        envdict={"PASSWD": userPwd}, stderr=STDOUT)
            return p.success()

    def mountSleepRes(self, host, userName, userPwd, res, rpath, uid=None,
                      gid=None):
        """
        Подключить пользовательский ресурс
        """
        for waittime in [0.5, 2, 5]:
            if not self.mountSambaRes(host, userName, userPwd, res, rpath,
                                      uid, gid):
                # check on mount path
                if isMount(rpath):
                    if not self.umountSleepPath(rpath):
                        return False
                # wait
                time.sleep(waittime)
            else:
                return True
        return False

    def syncUser(self, uid, gid, userHome, sync, remoteProfile,
                 host="default", skipList=()):
        """
        Синхронизация профиля пользователя
        """
        execStr = ""
        skipPaths = self.clVars.Get("cl_sync_skip_path")
        if not skipPaths:
            self.printERROR(
                _("Variable 'cl_sync_skip_path' empty") % userHome)
            return False
        deletePaths = self.clVars.Get("cl_sync_del_path")
        if not deletePaths:
            deletePaths = []
        excludePaths = " ".join(map(
            lambda x: '--exclude="/%s"' % x.replace('"', "").replace(
                "'", ""),
            skipPaths + deletePaths + list(skipList)))
        if sync == "login":
            if os.path.exists(userHome) and \
                    os.path.exists(remoteProfile):
                filterPath = " ".join(map(
                    lambda x: '--filter="P /%s"' % x.replace(
                        '"', "").replace("'", ""),
                    skipPaths))
                execStr = ('/usr/bin/rsync --delete-excluded --delete %s %s '
                           '-rlptgo -x -v -v -v  %s/ %s/'
                           % (excludePaths, filterPath,
                              remoteProfile, userHome))
        elif sync == "logout":
            if os.path.exists(userHome) and os.listdir(userHome) and \
                    os.path.exists(remoteProfile):
                execStr = ('/usr/bin/rsync --delete-excluded --delete %s '
                           '-rlpt -x -v -v -v %s/ %s/' % (
                               excludePaths, userHome, remoteProfile))
        else:
            raise ClientError(
                _("Method syncUser: wrong option sync=%s") % str(sync))
        if execStr:
            host = "<i>" + host + "</i>"
            if sync == "login":
                rsync = RsyncProgressBar(
                    _("Fetching the file list from %s") % host,
                    _("Fetching the user profile from %s") % host,
                    execStr, self, rsyncver=self.clVars.Get('cl_rsync_ver'))
            else:
                rsync = RsyncProgressBar(
                    _("Sending the file list to %s") % host,
                    _("Uploading the user profile to %s") % host,
                    execStr, self, rsyncver=self.clVars.Get('cl_rsync_ver'))
            pathConfig = os.path.join(remoteProfile,
                                      self.pathConfig)
            # remove old ini file
            prevIniFile = os.path.join(remoteProfile, ".calculate.ini")
            if os.path.exists(prevIniFile):
                os.remove(prevIniFile)
            # create home directory
            if not os.path.exists(pathConfig):
                self.createUserDirectory(pathConfig, uid, gid)
            numfiles = 0
            configFileName = os.path.join(remoteProfile, self.configFileDesktop)
            if sync == "login":
                # get rsync files
                try:
                    numfiles = iniParser(
                        configFileName).getVar('rsync', 'files')
                    if numfiles is False:
                        if os.path.exists(configFileName):
                            os.remove(configFileName)
                        numfiles = 0
                    else:
                        numfiles = int(numfiles)
                except Exception:
                    numfiles = 0
            rsync.maximum = numfiles
            if sync == "login":
                rsync.run()
            if sync == "logout":
                rsync.run()
                try:
                    if iniParser(configFileName).setVar(
                            'rsync', {'files': rsync.getFilesNum()}):
                        os.chmod(configFileName, 0600)
                        os.chown(configFileName, uid, gid)
                except Exception:
                    pass
            rsync.close()
            try:
                if iniParser(configFileName).setVar(
                        'rsync', {'exitcode': rsync.getExitCode()}):
                    os.chmod(configFileName, 0600)
                    os.chown(configFileName, uid, gid)
            except Exception:
                pass
            if rsync.getExitCode() != 0:
                self.printERROR(rsync.getErrMessage())
                self.printERROR(_("Failed to execute rsync") + " " + \
                                str(sync))
                return False
        # change permissions
        changeDirs = [userHome, remoteProfile]
        for changeDir in filter(path.exists, changeDirs):
            # get directory permissions
            mode = getModeFile(changeDir, mode="mode")
            # if permission wrong( not 0700) then change it
            if mode != 0700:
                os.chmod(changeDir, 0700)
        return True

    def clearHomeDir(self, homeDir):
        """
        Очистить домашнюю директорию
        """
        rmFiles = list(set(os.listdir(homeDir)) - \
                       set(self.skipHomeFile))
        for rmFile in rmFiles:
            delFile = os.path.join(homeDir, rmFile)
            if os.path.islink(delFile):
                os.unlink(delFile)
            elif os.path.isfile(delFile):
                os.remove(delFile)
            elif os.path.isdir(delFile):
                if not removeDir(delFile):
                    return False
            elif stat.S_ISSOCK(os.stat(delFile)[stat.ST_MODE]):
                os.remove(delFile)
        self.printSUCCESS(_("Cleaning the local user profile"))
        return True

    def syncLoginProfile(self, host, uid, gid, homeDir, resourceName,
                         profileName, clearHomeDir=True):
        """
        Получить профиль пользователя из домена
        """
        homeProfile = path.join(
            self.clVars.Select('cl_client_user_mount_path',
                               where='cl_client_user_mount_name',
                               eq=resourceName, limit=1),
            profileName)
        # if currect server has any files then sync it
        if any(x not in ('.calculate',) for x in listDirectory(homeProfile)):
            if not self.syncUser(uid, gid, homeDir, "login",
                                 homeProfile, host=host):
                return False
        # remove home directory on workstation
        else:
            if clearHomeDir:
                # clean home directory
                if not self.clearHomeDir(homeDir):
                    return False
        return True

    def syncLogoutProfile(self, host, uid, gid, homeDir, resourceName,
                          profileName, skipList):
        """
        Закачать профиль пользователя в домен
        """
        homeProfile = path.join(
            self.clVars.Select('cl_client_user_mount_path',
                               where='cl_client_user_mount_name',
                               eq=resourceName, limit=1),
            profileName)
        # if currect server has any files then sync it
        if listDirectory(homeDir):
            if not path.exists(homeProfile):
                self.createUserDirectory(homeProfile, uid, gid)
            if not self.syncUser(uid, gid, homeDir, "logout",
                                 homeProfile, host=host, skipList=skipList):
                return False
        return True

    def strftime(self, timeobj, dateformat="%Y-%m-%d %H:%M:%S"):
        return time.strftime(dateformat, timeobj)

    def packRemote(self, resourceName, localTime, currentTime, profileName,
                   uid, gid):
        """
        Запаковать удаленный профиль
        """
        remoteProfilePath = path.join(
            self.clVars.Select('cl_client_user_mount_path',
                               where='cl_client_user_mount_name',
                               eq=resourceName,
                               limit=1), profileName)
        fileServer = path.join(remoteProfilePath,
                               Client.configFileServer)
        if not localTime:
            return False
        varsConfig = {"arch_date": self.strftime(localTime),
                      "curr_time": currentTime}

        self.setServerCommand(["pack"], varsConfig, fileServer)
        fileDesktop = path.join(remoteProfilePath,
                                Client.configFileDesktop)
        self.setVarToConfig(uid, gid, "main", {"status_sync": "success"},
                            fileDesktop)
        return self.umountRemoteUserRes(False, resourceName)

    def waitingArchFile(self, packTime, profileName, resourceName):
        """
        Ожидание архива профиля от домена
        """
        arch_fn_success, arch_fn_process = self._getArchNames(
            packTime, profileName, resourceName)

        def arch_exists():
            return any(os.path.exists(x) for x in (arch_fn_process,
                                                   arch_fn_success))

        for sleeptime in [0.1, 0.2, 0.5]:
            # archive in packing process found
            if arch_exists():
                break
            time.sleep(sleeptime)
        else:
            return False

        # wait finish packing
        if arch_exists():
            while arch_exists():
                for waittime in [0.5, 1, 2]:
                    if path.exists(arch_fn_success):
                        return True
                    try:
                        startSize = os.stat(arch_fn_process).st_size
                        time.sleep(waittime)
                        if startSize != os.stat(arch_fn_process).st_size:
                            break
                    except OSError:
                        if path.exists(arch_fn_success):
                            return True
                else:
                    return False
        return False

    def unpackArch(self, homeDir, packTime, profileName, resourceName):
        """
        Распаковать архив с профилем
        """

        def fileReader(fileName, stdin, maxSize):
            """
            Прочитать файл по блокам
            """
            self.addProgress()
            fd = os.open(fileName, os.O_RDONLY)
            currSize = 0
            # default buffer size
            buffSize = 131072
            dataBlock = os.read(fd, buffSize)
            while dataBlock:
                currSize += len(dataBlock)
                self.setProgress(currSize * 100 / maxSize)
                stdin.write(dataBlock)
                dataBlock = os.read(fd, buffSize)
            stdin.close()
            os.close(fd)

        archFile, drop = self._getArchNames(packTime, profileName, resourceName)
        archiveSize = os.stat(archFile).st_size
        # os.system('tar tf %s'%archFile)
        execStr = "tar -C '%s' -xzf -" % homeDir
        # запустить процесс распаковки
        pipe = subprocess.Popen(execStr,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                close_fds=True,
                                env=os.environ, shell=True)
        try:
            # прочитать в конвеер
            fileReader(archFile, pipe.stdin, archiveSize)
            self.endTask()
        finally:
            ret = pipe.wait()
            pipe.stdout.close()
            pipe.stderr.close()
        if ret:
            return False
        removedFileList = path.join(
            self.clVars.Select('cl_client_user_mount_path',
                               where='cl_client_user_mount_name',
                               eq=resourceName, limit=1), profileName,
            self.listTemplFile)
        return self.removeFilesInProfile(homeDir, removedFileList)

    def cleanArchs(self, packTime, profileName, resourceName):
        """
        Удалить архивы использованные для синхронизации пользовательского
        профиля
        """
        for rmFile in filter(lambda x: x and path.exists(x),
                             self._getArchNames(packTime, profileName,
                                                resourceName)):
            os.remove(rmFile)
        return True

    def _getArchNames(self, packTime, profileName, resourceName):
        """
        Получить имена файлов архивов по времени, профилю и ресурсу
        """
        remoteProfile = self.clVars.Select('cl_client_user_mount_path',
                                           where='cl_client_user_mount_name',
                                           eq=resourceName, limit=1)
        extSuccess = "gz"
        extProcess = "process"
        packTime = packTime.replace(".", "_")
        archPathTmp = os.path.join(remoteProfile, "profile.%s.%s.tar"
                                   % (profileName, packTime))
        # result archive file name
        archPathSuccess = "%s.%s" % (archPathTmp, extSuccess)
        # archive in pack process
        archPathProcess = "%s.%s" % (archPathTmp, extProcess)
        return archPathSuccess, archPathProcess

    def setVarToConfig(self, uid, gid, nameSection, varsDict, configFileName):
        """Записывает переменную в файл конфигурации"""
        # Создаем директорию для конфигурационных файлов
        pathConfig = os.path.split(configFileName)[0]
        if not os.path.exists(pathConfig):
            self.createUserDirectory(pathConfig, uid, gid)
        try:
            if iniParser(configFileName).setVar(nameSection, varsDict):
                os.chmod(configFileName, 0600)
                os.chown(configFileName, uid, gid)
        except Exception:
            return False
        return True

    def createUserDirectory(self, userdir, uid, gid):
        """
        Создать директорию с правами пользователя
        """
        if not os.path.exists(userdir):
            try:
                os.mkdir(userdir)
                os.chown(userdir, uid, gid)
                os.chmod(userdir, 0700)
            except OSError:
                raise ClientError(_("Failed to create the directory") + ". " +
                                  _("Permission denied: '%s'") % userdir)
        return True

    def mountUserDomainRes(self, userName, userPwd, uid, gid, *resources):
        """
        Mount all local samba resource
        """
        for rpath, res, host in \
                self.clVars.Select(['cl_client_user_mount_path',
                                    'cl_client_user_mount_resource',
                                    'cl_client_user_mount_host'],
                                   where='cl_client_user_mount_name',
                                   _in=resources):
            if not rpath:
                continue
            self.createUserDirectory(rpath, uid, gid)
            if isMount(rpath):
                continue
            else:
                if not self.mountSleepRes(host, userName, userPwd,
                                          res, rpath, uid, gid):
                    raise ClientError(
                        _("Failed to mount the Samba volume [%s]") % res)
        else:
            return True

    def setSyncStatus(self, userDir, uid, gid, status):
        """
        Установить статус синхронизации
        """
        # сохранение текущего статуса перед изменением
        self.clVars.Get('cl_client_sync_status')
        fileDesktop = path.join(userDir, Client.configFileDesktop)
        return self.setVarToConfig(uid, gid, "main",
                                   {"status_sync": status}, fileDesktop)

    def tarSymLinks(self, uid, gid, userHome, delPath, skipPath):
        """
        Создать tar архив с симлинками
        """
        linkArch = pathJoin(userHome, ".calculate/links.tar.bz2")
        try:
            tarLinks(userHome, linkArch, skip=delPath + skipPath)
            if path.exists(linkArch):
                os.chown(linkArch, uid, gid)
        except Exception:
            return False
        return True

    def unpackLinks(self, userHome):
        """
        Распаковать архив с симлинками
        """
        linksArch = path.join(userHome, ".calculate/links.tar.bz2")
        try:
            if os.path.exists(linksArch):
                tf = tarfile.open(linksArch)
                tf.extractall(userHome)
                tf.close()
        except Exception:
            return False
        return True

    def moveHomeDir(self, userHome, movedDir, resourceName, skipPaths):
        """
        Переместить непрофильные файлы из корня домашней директории
        в Home/Moved
        """
        desktopDir = "Desktop"
        resourcePath = self.clVars.Select('cl_client_user_mount_path',
                                          where='cl_client_user_mount_name',
                                          eq=resourceName,
                                          limit=1)
        if not isMount(resourcePath):
            raise ClientError(_("Unable to mount %s") % resourcePath)
        movedPath = path.join(resourcePath, movedDir)
        movedLink = path.join(userHome, movedDir)
        if not skipPaths:
            skipPaths = ['Disks', 'Share', 'Home', 'Moved', 'FTP', 'Desktop']
        filesAndDir = filter(lambda x: not (
            path.islink(x) or
            x[len(userHome) + 1:].startswith('.') or
            x[len(userHome) + 1:] in skipPaths),
                             listDirectory(userHome, fullPath=True))
        filesDir = []
        for fd in filesAndDir:
            if os.path.islink(fd):
                os.unlink(fd)
            else:
                filesDir.append(fd)
        # find files in Desktop
        pathDesktop = path.join(userHome, desktopDir)
        filesDirDesk = filter(lambda x: (not path.islink(x) and
                                         not path.split(x)[1].startswith(
                                             '.') and
                                         not x.rpartition('.')[2] == 'desktop'),
                              listDirectory(pathDesktop, fullPath=True))
        filesDir += filesDirDesk
        if not filesDir or not path.exists(resourcePath):
            # remove empty Moved folder
            if path.exists(movedPath) and not listDirectory(movedPath):
                os.rmdir(movedPath)
            # remove link to Moved in home directory
            if path.islink(movedLink) and not path.exists(movedPath):
                os.unlink(movedLink)
            return True
        if not path.exists(movedPath):
            os.mkdir(movedPath)
        directFile = path.join(movedPath, ".directory")
        if not path.exists(directFile):
            with open(directFile, 'w') as f:
                f.write("[Desktop Entry]\nIcon=folder-development")
        if not path.exists(movedLink):
            os.symlink(movedPath[len(userHome) + 1:], movedLink)
        cpCmd, rmCmd = checkUtils('/bin/cp', '/bin/rm')
        for fd in filesDir:
            if process(cpCmd, '-xr', fd, movedPath).failed():
                self.printERROR(_("Failed to copy {ffrom} to {fto}")
                                .format(ffrom=fd, fto=movedPath))
                return False
            if process(rmCmd, '-rf', '--one-file-system', fd).failed():
                self.printERROR(_("Failed to remove " % fd))
                return False
        return True

    def removeNoiseFiles(self, userHome):
        """
        Remove files which hinder dm
        """
        noiseFiles = []
        for nsFile in noiseFiles:
            rmFile = path.join(userHome, nsFile)
            if os.path.exists(rmFile):
                os.remove(rmFile)
        return True

    def getPrivateFiles(self, userHome):
        """
        Get all private files relative user home directory
        """
        privateFiles = []
        for privateHomeDir in self.privateDirs:
            privateDir = os.path.join(userHome, privateHomeDir)
            if os.path.isdir(privateDir):
                # .ssh files relative user home directory
                privateFiles += map(lambda x: os.path.join(privateHomeDir, x),
                                    filter(lambda x: \
                                               os.path.isfile(
                                                   os.path.join(privateDir, x)),
                                           os.listdir(privateDir)))
        return self.privateFiles + privateFiles

    def removePrivateFiles(self, userHome):
        """
        Remove private files
        """
        privateFiles = self.getPrivateFiles(userHome)
        for prFile in privateFiles:
            rmFile = os.path.join(userHome, prFile)
            if os.path.exists(rmFile):
                os.remove(rmFile)
        return True

    def copyPrivateFiles(self, serverProfileDir, userHomeDir):
        """
        Copy privates files from server to home directory
        """
        privateFiles = self.getPrivateFiles(serverProfileDir)
        for prFile in privateFiles:
            src = os.path.join(serverProfileDir, prFile)
            dst = os.path.join(userHomeDir, prFile)
            if os.path.exists(src):
                dstPath = os.path.dirname(dst)
                if not os.path.exists(dstPath):
                    listElPath = []
                    for el in filter(lambda x: x,
                                     dstPath.partition(userHomeDir)[2].split(
                                         "/")):
                        listElPath.append(el)
                        joinPath = "/".join(listElPath)
                        dPath = os.path.join(userHomeDir, joinPath)
                        if not os.path.exists(dPath):
                            sPath = os.path.join(serverProfileDir, joinPath)
                            sMode, sUid, sGid = getModeFile(sPath)
                            os.mkdir(dPath, sMode)
                            os.chown(dPath, sUid, sGid)
                copy2(src, dst)
                sUid, sGid = getModeFile(src, mode="owner")
                os.chown(dst, sUid, sGid)
        return True

    def getListFilesProfile(self, homeDir):
        """
        Generation file list in user home directory, exclude mount dirs
        """
        lHomeDir = len(homeDir) + 1
        return map(lambda x: x[lHomeDir:],
                   find(homeDir, onefilesystem=True))

    def removeFilesInProfile(self, homeDir, pathListFile):
        """
        Удалить файлы которые отсутствуют в пользовательском профиле
        """
        # read file list from config
        try:
            filesProfileTxt = open(pathListFile).read()
        except IOError:
            return False
        listFilesProfile = filter(lambda x: x.strip(),
                                  filesProfileTxt.split("\n"))
        filesProfile = set(listFilesProfile)
        # get files in home directory
        listFilesHome = self.getListFilesProfile(homeDir)
        if listFilesHome is False:
            return False
        filesHome = set(listFilesHome)
        filesRemove = list(filesHome - filesProfile)
        try:
            for rmFile in sorted(filesRemove, reverse=True):
                rmPath = os.path.join(homeDir, rmFile)
                if path.ismount(rmPath):
                    continue
                if os.path.islink(rmPath):
                    os.unlink(rmPath)
                elif os.path.isfile(rmPath):
                    os.remove(rmPath)
                elif os.path.isdir(rmPath):
                    os.rmdir(rmPath)
                else:
                    os.remove(rmPath)
        except Exception:
            return False
        return True

    def getRunCommandsWithEnv(self):
        """List run program"""

        def getCmd(procNum):
            cmdLineFile = '/proc/%s/cmdline' % procNum
            environFile = '/proc/%s/environ' % procNum
            try:
                if os.path.exists(cmdLineFile) and \
                        os.path.exists(environFile):
                    return (open(cmdLineFile, 'r').read().strip(),
                            open(environFile, 'r').read().strip())
            except (OSError, IOError):
                pass
            return "", ""

        if not os.access('/proc', os.R_OK):
            return []
        return map(getCmd,
                   filter(lambda x: x.isdigit(),
                          os.listdir('/proc')))

    def clearUserKey(self, userName):
        """
        Clear user key from kernel
        """
        # find user key in kernel and check on relogout
        if getKey(userName) and not \
                filter(lambda x: "xdm/xdm\x00--login" in x[0] and
                                ("USER=%s" % userName) in x[1],
                       self.getRunCommandsWithEnv()):
            # clean
            ret = clearKey(userName)
            if ret == 0:
                return True
            else:
                self.printERROR(_("Failed to clear the kernel key for user %s") \
                                % userName)
                return False
        return True

    def setLogoutDate(self, homeDir, uid, gid):
        """
        Установить дату выхода из сеанса
        """
        configFileName = path.join(homeDir, self.configFileDesktop)
        currentDateStr = self.strftime(time.localtime())
        return self.setVarToConfig(uid, gid, "main",
                                   {"date_logout": currentDateStr},
                                   configFileName)

    def umountRemoteUserRes(self, removeEmpty, *resourceNames):
        """
        Отключить пользовательский ресурс
        """
        retRes = True
        for resourceName in resourceNames:
            resourcePath = self.clVars.Select('cl_client_user_mount_path',
                                              where='cl_client_user_mount_name',
                                              eq=resourceName,
                                              limit=1)
            if resourcePath:
                result = self.umountSleepPath(resourcePath)
                retRes &= result
                if (result and removeEmpty and path.exists(resourcePath) and
                        not listDirectory(resourcePath)):
                    os.rmdir(resourcePath)
        return retRes

    def umountUserRes(self, umountPaths):
        """
        Отключить пользовательские ресурсы
        """
        for umountPath in umountPaths:
            if not self.umountSleepPath(umountPath):
                return False
        self.umountRemoteUserRes(True,
                                 *self.clVars.Get('cl_client_user_mount_name'))
        return True

    def getDefaultRunlevelDaemons(self):
        """
        Получить все службы из default уровня загрузки
        """
        rcUpdateCmd = checkUtils("/sbin/rc-update")
        p = process(rcUpdateCmd, "show")
        if p.success():
            # получить список названии служб default уровня
            return map(lambda x: x[0].strip(),
                       # [\s*<название_службы>\s*,\s*default\s*]
                       filter(lambda x: len(x) > 1 and "default" in x[1],
                              # [\s*<название_службы>\s*,\s*уровень запуска\s*]
                              map(lambda x: x.split('|'),
                                  # \s*<название_службы>\s*|
                                  # \s*<уроверь запуска>?\s*
                                  p.readlines())))
        else:
            raise ClientError(_("ERROR") + _(": ") + p.read())

    def delDaemonAutostart(self, daemon):
        """
        Удалить службу из автозапуска
        """
        rcUpdateCmd = checkUtils('/sbin/rc-update')
        defaultDaemons = self.getDefaultRunlevelDaemons()
        if daemon in defaultDaemons:
            p = process(rcUpdateCmd, 'del', daemon, 'default')
            if p.success():
                return True
            else:
                self.printERROR(_("ERROR") + _(": ") + p.read())
                self.printERROR(_("Failed to delete from the default runlevel"))
                return False
        return True

    def addDaemonAutostart(self, daemon):
        """
        Добавить службу в автозагрузку
        """
        rcUpdateCmd = checkUtils('/sbin/rc-update')
        if daemon in self.getDefaultRunlevelDaemons():
            return True
        p = process(rcUpdateCmd, 'add', daemon, 'default')
        if p.success():
            return True
        else:
            self.printERROR(_("ERROR") + _(": ") + p.read())
            self.printERROR(_("Failed to add to the default runlevel"))
            return False

    def getInfoService(self, service, option, envFile=False):
        """
        Получить параметр севирса из удаленного env
        """
        if not envFile:
            if self.convObj is None:
                # file /var/calculate/remote/server.env
                envFile = self.clVars.Get("cl_env_server_path")
                if os.access(envFile, os.R_OK):
                    self.convObj = False
                elif os.access("/var/calculate/remote/calculate.env", os.R_OK):
                    self.convObj = convertEnv()
            if self.convObj:
                return self.convObj.getVar(service, option)
        if not self.optionsInfo:
            if not envFile:
                # file /var/calculate/remote/server.env
                envFile = self.clVars.Get("cl_env_server_path")
            optInfo = self.clVars.getRemoteInfo(envFile)
            if optInfo is False:
                return False
            if optInfo:
                self.optionsInfo = optInfo
        value = ''
        if service in self.optionsInfo and option in self.optionsInfo[service]:
            value = self.optionsInfo[service][option]
        return value

    def checkDomainServer(self, domainName, netDomain):
        """
        Проверить указанный адрес на то, что он может быть доменом
        """
        # get domain name
        if "." in domainName:
            domain = domainName
        else:
            import socket

            domain = "%s.%s" % (domainName, netDomain)
            try:
                gethostbyname(domain)
            except socket.gaierror as e:
                domain = domainName
        # check domain by ping
        for i in range(0, 1):
            try:
                Pinger().ping(domain, 1000, 16)
                break
            except IPError as e:
                pass
        else:
            self.printERROR(_("Server %s does not respond") % domain)
            return False
        reFoundHostSamba = re.compile("Server=\[Samba.+\]")
        smbClientCmd = checkUtils('/usr/bin/smbclient')
        p = process(smbClientCmd, "-N", "-L", domain, stderr=STDOUT)
        if p.success() and any(reFoundHostSamba.search(x) for x in p):
            return True
        else:
            self.printERROR(_("Samba server not found in %s") % domain)
            return False

    def getDomainPassword(self, domain):
        """
        Получить пароль для ввода в домен
        """

        def passwdQueue():
            remotePw = self.clVars.Get('cl_remote_pw')
            if remotePw:
                yield remotePw
            if remotePw:
                self.printERROR(_("Wrong password"))
            yield self.askPassword(
                _("Domain password for the desktop"), False)
            self.printERROR(_("Wrong password"))

        for pwdRemote in passwdQueue():
            pwdRemote = pwdRemote or ""
            if sambaPasswordCheck("client", pwdRemote, domain, "remote"):
                self.clVars.Set('cl_remote_pw', pwdRemote)
                return True
        return False

    def writeClientVars(self, domain, currentVersion, pwdRemote):
        """
        Записать переменные переменные клиента
        """
        # write domain to config
        self.clVars.Write("os_remote_auth", domain, True)
        # write current program version
        self.clVars.Write("cl_remote_host", domain, True, "local")
        self.clVars.Write("cl_remote_pw", pwdRemote, True, "local")
        return True

    def checkDomainInfo(self, domain):
        """
        Проверить наличие настроек на сервере для конфигурации LDAP
        """
        servDn = self.getInfoService("ldap", "services_dn")
        unixDn = self.getInfoService("unix", "dn")
        bindDn = self.getInfoService("unix", "bind_dn")
        bindPw = self.getInfoService("unix", "bind_pw")
        # check info from server
        if not (servDn and unixDn and bindDn and bindPw):
            raise ClientError(_("Info not found on the server") + _(": ") +
                              _(
                                  "services DN or unix DN or bind DN or bind password"))
        self.clVars.Set("os_remote_auth", domain)
        return True

    def applyClientTemplates(self, hostAuth=""):
        """
        Применить шаблоны клиента

        Установка события домен/не домен по удаленному хосту
        Установка переменной os_remote_auth, необходимой для корректного
        выполнения шаблонов
        Выполнение шаблонов
        Установка необходимых переменных в calculate.env
        """
        if hostAuth:
            # add to domain
            self.clVars.Set("cl_action", "domain", True)
        else:
            # del from domain
            self.clVars.Set("cl_action", "undomain", True)
        self.clVars.Set("os_remote_auth", hostAuth)
        # apply system templates
        self.applyTemplates(None, False)
        if hostAuth:
            self.printSUCCESS(_("The workstation was configured for work "
                                "in the domain"))
            self.clVars.Write("os_remote_auth", hostAuth, True)
        else:
            self.printSUCCESS(_("The workstation was configured for work "
                                "outside the domain"))
            self.clVars.Delete("os_remote_auth")
        return True

    def cDelLdapSysUsersAndSyncCache(self):
        """
        Удалить LDAP пользователей из системы (/etc/passwd и т.д.) и
        синхронизировать кэш
        """
        cacheObj = userCache(self)
        if not cacheObj.deleteCacheUsersFromSystem():
            return False
        if not cacheObj.syncCacheToLdap():
            return False
        return True

    def cAddCacheUsersFromSystem(self):
        """
        Добавить кэш пользователей из системы
        """
        cacheObj = userCache(self)
        if not cacheObj.addCacheUsersFromSystem():
            return False
        return True

    def cAddUserToCache(self, userName, userPwd):
        """
        Добавить пользователя в кэш
        """
        cacheObj = userCache(self)
        pwdHash = self.getHashPasswd(userPwd, "shadow_ssha256")
        if not cacheObj.addUserToCache(userName, pwdHash):
            return False
        return True

    def cDelLdapSysUsersAndClearCache(self):
        """
        Удалить LDAP пользователей из системы и очистить кэш
        """
        cacheObj = userCache(self)
        if not cacheObj.deleteCacheUsersFromSystem():
            return False
        if not cacheObj.clearCache():
            return False
        return True

    def mountRemoteRes(self, pwdRemote, pathRemote, *domains):
        """
        Подключить удаленный ресурс remote
        """
        # первый домен из списка
        domain = reduce(lambda x, y: x if x else y, domains, "")
        foundMountRemote = isMount(pathRemote)
        if foundMountRemote:
            self.printWARNING(_("Samba volume [%s] mounted") % "remote")
            return True
        else:
            if not (domain and pwdRemote):
                self.printERROR(_("Variable not found") +
                                _(": ") + "cl_remote_pw")
                return False
            if not os.path.exists(pathRemote):
                os.makedirs(pathRemote)
            if not self.mountSambaRes(domain, "client", pwdRemote,
                                      "remote", pathRemote):
                self.printERROR(
                    _("Failed to mount the Samba volume [%s]") % "remote")
                return False
            self.printSUCCESS(_("Samba volume [%s] mounted") % "remote")
            return True

    def clientPasswd(self, userLogin, uid, gid, homeDir, password, curPassword):
        """
        Изменить пароль пользователя. Пароль пользователя меняется на сервере
        после выхода из сеанса
        """
        varsConfig = {"unix_hash": self.getHashPasswd(password, "ssha"),
                      "samba_lm_hash": self.getHashPasswd(password, "lm"),
                      "samba_nt_hash": self.getHashPasswd(password, "nt"),
                      "samba_nt_hash_old": self.getHashPasswd(curPassword,
                                                              "nt")}
        if filter(lambda x: not x, varsConfig.values()):
            return False
        # ~/.calculate/server.env
        fileConfig = os.path.join(homeDir, self.configFileServer)
        try:
            res = self.setServerCommand(["passwd_samba"], varsConfig,
                                        fileConfig,
                                        uid, gid)
        except OSError as e:
            if e.errno == 13:
                self.printERROR(_("Permission denied"))
                return False
            return False
        return res
