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

# Copyright 2008-2013 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
import sys
import re
from os import path
from calculate.lib.datavars import (Variable, VariableError,
    ReadonlyVariable, ReadonlyTableVariable, TableVariable, FieldValue,
    SimpleDataVars, DataVarsError)
from calculate.lib.utils.files import readFile, \
    listDirectory, process, pathJoin
from calculate.lib.utils.git import Git

from calculate.lib.configparser import ConfigParser
from calculate.lib.cl_lang import setLocalTranslate
from calculate.lib.utils.text import simplify_profiles
from calculate.lib.utils.portage import Git, GitError, Layman
from ..profile import RepositoryStorageSet, DEFAULT_BRANCH, \
    LocalStorage, ProfileRepository, CacheStorage

from calculate.lib.variables import linux as lib_linux
from calculate.lib.variables import env
from calculate.update.update_info import UpdateInfo
import urllib2
import time

_ = lambda x:x

setLocalTranslate('cl_update3', sys.modules[__name__])

class VariableAcUpdateSync(ReadonlyVariable):
    """
    Action variable which has value "up" for package install and
    install this package
    """
    def get(self):
        action = self.Get("cl_action")
        if action in ("sync",):
            if self.Get('cl_update_world'):
                return self.Get('cl_update_world')
        return ""

class VariableClUpdateWorldDefault(Variable):
    """
    Ad-hoc
    """
    value = "update"

class VariableClUpdateWorld(Variable):
    type = "choice"
    opt = ["--world"]
    syntax = "--{choice}-world"
    metavalue = "MODE"

    def init(self):
        self.help = ("'rebuild' - " + _("rebuild the system packages list") +
                     ",\n'merge' - " + _("append the profile packages list") +
                     ",\n'update' - " + _("update the system packages list"))
        self.label = _("System packages list")

    def get(self):
        return self.Get('cl_update_world_default')

    def choice(self):
        return [("rebuild", _("Rebuild")),
                ("merge", _("Merge")),
                ("update", _("Update"))]


class VariableClRebuildWorldSet(Variable):
    """
    List of action update world, rebuild world,
    """
    type = "bool"
    opt = ["--rebuild-world"]
    untrusted = True
    value = "off"

    def init(self):
        self.help = _("Rebuild world")
        self.label = _("Rebuild world")

class VariableClUpdateRevSet(Variable):
    """
    List of action update world, rebuild world,
    """
    type = "bool"
    opt = ["--update-rev"]
    untrusted = True
    value = "on"
    check_after = ["cl_update_sync_rep",
                    "cl_update_metadata_force",
                    "cl_update_other_set",
                    "cl_update_eixupdate_force"]

    def init(self):
        self.help = _("make a revision update")
        self.label = _("Make a revision update")

class VariableClUpdateRep(Variable):
    """
    Обновлять репозитории до конкретной ревизии или до последней
    """
    type = "choice"
    value = "rev"

    def choice(self):
        return ["last", "rev"]

class VariableClUpdateRepData(ReadonlyTableVariable):
    """
    Информация о репозиториях
    """
    source = ['cl_update_rep_name',
              'cl_update_rep_url',
              'cl_update_rep_path',
              'cl_update_rep_rev']

class VariableClUpdateRepName(Variable):
    """
    Список имен используемых репозиториев
    """
    type = "list"
    value = []

class VariableClUpdateRepUrl(Variable):
    """
    Список путей до репозиториев
    """
    type = "list"
    value = []

class VariableClUpdateLaymanConfig(ReadonlyVariable):
    """
    Объект конфига layman
    """
    type = "object"

    def get(self):
        chroot = self.Get('cl_chroot_path')
        cp = ConfigParser()
        cp.read(path.join(chroot, 'etc/layman/layman.cfg'))
        return cp

class VariableClUpdateLaymanStorage(Variable):
    """
    Путь к репозиториям layman
    """
    param_name = "storage"
    fallback_value = "var/lib/layman"

    def get(self):
        cp = self.Get('cl_update_layman_config')
        res = cp.get('MAIN', self.param_name, fallback=self.fallback_value)
        return pathJoin(self.Get('cl_chroot_path'), res)

class VariableClUpdateRepPath(ReadonlyVariable):
    """
    Пути до репозиториев
    """
    type = "list"
    mapPath = {'portage': 'usr/portage',
               'gentoo': '/usr/portage'}

    def get(self):
        repPath = self.Get('cl_update_layman_storage')
        chroot_path = self.Get('cl_chroot_path')

        def generatePaths(names):
            for name in names:
                if name in self.mapPath:
                    yield path.join(chroot_path, self.mapPath[name])
                else:
                    yield path.join(repPath, name)
        return list(generatePaths(self.Get('cl_update_rep_name')))


class VariableClUpdateRepRev(Variable):
    """
    Ревизии до которых необходимо обновить репозитории
    """
    type = "list"

    def get(self):
        cp = ConfigParser()
        revisions = self.Get('update.cl_update_binhost_revisions')
        if revisions:
            cp.read_string(unicode(revisions[0]))

        branch = self.Get('cl_update_branch')

        def generateBranch():
            for repname, branchname in zip(
                    self.Get('cl_update_rep_name'),
                    self.Get('cl_update_branch_name')):
                if branchname == Git.Reference.Tag:
                    yield cp.get("vcs", repname, fallback=branch)
                else:
                    yield branchname
        return list(generateBranch())


class VariableClUpdateBranch(Variable):
    """
    Ветка на которую будет обновляться репозиторий если -getbinpkg
    """
    value = Git.Reference.Master

class VariableClUpdateBranchData(TableVariable):
    """
    Выбор веток репозиториев до которых необходимо обновиться
    """
    opt = ["--branch"]
    metavalue = 'REFS'
    untrusted = True
    source = ["cl_update_branch_rep",
              "cl_update_branch_name"]

    def init(self):
        self.help = _("set references for repository (REPOSITORY:REF)")
        self.label = _("Repositories references")

    def raiseReadonlyIndexError(self, fieldname="", variablename="", value=""):
        """
        Неизвестный оврелей
        """
        raise VariableError(_("Repository %s not found")%value)

class VariableClUpdateBranchRep(ReadonlyVariable):
    """
    Список доступных репозиторием
    """
    type = "list"

    def init(self):
        self.label = _("Repository")

    def get(self):
        return self.Get('cl_update_rep_name')

class VariableClUpdateBinhostData(ReadonlyTableVariable):
    """
    Таблица содержащая
    binhost/содержимое файла ревизий/время доступа
    """
    source = ["cl_update_binhost_host",
              "cl_update_binhost_revisions",
              "cl_update_binhost_time"]

    def check_binhost(self, binhost):
        revision_files = [path.join(binhost, x)
                          for x in self.Get('cl_update_binhost_revision_path')]
        timeout = self.GetInteger('cl_update_binhost_timeout')
        try:
            data = None
            for fn in revision_files:
                if data is None:
                    data = urllib2.urlopen(fn, timeout=timeout).read()
                elif data != urllib2.urlopen(fn, timeout=timeout).read():
                    return ""
            return data
        except urllib2.URLError:
            return ""

    re_revison = re.compile("\w+=(\w+)")

    def binhost_key(self, data):
        host, t = data
        try:
            cp = ConfigParser()
            cp.read_string(data.decode('utf-8'))
            data = sum(int(x) for x in cp['vcs'].values())
        except (TypeError, KeyError) as e:
            data = 0
        return (1 if int(time) >= 0 else 0,
                data,
                -int(time))

    def get_timestamp(self, binhost):
        DAY = 60 * 60 * 24
        timeout = self.GetInteger('cl_update_binhost_timeout')
        timestamp_file = path.join(binhost,
                                   self.Get('cl_update_binhost_timestamp_path'))
        try:
            t = time.time()
            data = urllib2.urlopen(timestamp_file,
                                   timeout=timeout).read().strip()
            if data.isdigit() and t - int(data) < 5 * DAY:
                return data, int((time.time() - t)*1000)
        except urllib2.URLError as e:
            pass
        except BaseException as e:
            pass
        return "", -1

    def get(self, hr=False):
        binhost = self.Get('cl_update_binhost')
        recheck = self.GetBool('cl_update_binhost_recheck_set')

        def generate_by_timestamp():
            for host in self.Get('cl_update_binhost_list'):
                if host:
                    ts_content, duration = self.get_timestamp(host)
                    if ts_content:
                        yield host, ts_content, str(duration)
        if not recheck and binhost:
            ts, t = self.get_timestamp(binhost)
            if ts:
                data = self.check_binhost(binhost)
                if data:
                    return [[binhost, data, str(t)]]
        for host, ts, t in sorted(generate_by_timestamp(),
                                  key=lambda x: (-int(x[1]), int(x[2]))):
            data = self.check_binhost(host)
            if data:
                return [[host, data, str(t)]]
        return [[]]


class VariableClUpdateBinhostRecheckSet(Variable):
    """
    Принудительно обновить binhost
    """
    type = "bool"
    value = "off"

    opt = ["--scan"]

    def init(self):
        self.help = _("search for the most appropriate update server")
        self.label = _("Search for the most appropriate update server")


class VariableClUpdateBinhostHost(FieldValue, ReadonlyVariable):
    """
    Список имен прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_binhost_data"
    column = 0

class VariableClUpdateBinhostRevisions(FieldValue,ReadonlyVariable):
    """
    Список имен прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_binhost_data"
    column = 1

class VariableClUpdateBinhostTime(FieldValue,ReadonlyVariable):
    """
    Список имен прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_binhost_data"
    column = 2


class VariableClUpdateBranchName(Variable):
    """
    Список доступных репозиторием
    """
    type = "choiceedit-list"

    def init(self):
        self.label = _("Reference")

    def choice(self):
        return [
            Git.Reference.Tag,
            Git.Reference.Master,
            Git.Reference.Develop,
            Git.Reference.Update]

    def get(self):
        if "getbinpkg" in self.Get('cl_features'):
            return [Git.Reference.Tag for x in self.Get('cl_update_rep_name')]
        else:
            branch = self.Get('cl_update_branch')
            return [branch for x in self.Get('cl_update_rep_name')]


class VariableClUpdateSyncRep(Variable):
    """
    Обновляемый репозиторий
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["-r", "--repositories"]
    metavalue = "REPOSITORIES"
    untrusted = True

    @property
    def rep_name(self):
        return self.Get('update.cl_update_rep_name')

    def init(self):
        self.help = _("synchronized repositories (all by default)")
        self.label = _("Synchronized repositories")

    def set(self, value):
        orderList = self.rep_name
        return sorted(value, key=lambda x:
            (orderList.index(x) if x in orderList else -1), reverse=True)

    def get(self):
        return list(reversed(self.rep_name))

    def choice(self):
        return self.rep_name

class VariableClUpdateSyncOverlayRep(ReadonlyVariable):
    """
    Обновляемые репозитории (исключая portage)
    """
    type = "list"

    def get(self):
        return filter(lambda x: x != "portage", self.Get('cl_update_sync_rep'))

class VariableClUpdateOutdateSet(ReadonlyVariable):
    """
    Флаг устанавливаемый в ходе обновления репозиториев,
    сообщающий что хотя бы один из запланированных репозиториев
    обновлен и следует обновляет различные метаданные

    Если обновляются прочие оверлеи - данные считаются что устарели
    """
    type = "bool"

    def get(self):
        if (self.Get('cl_update_other_set') == 'on' and
            self.Get('cl_update_other_rep_name')):
            return "on"
        return "off"

class VariableClUpdateMetadataForce(Variable):
    """
    Принудительное действие с метаданными
    """
    type = "choice"
    value = "auto"
    opt = ["--update-metadata"]
    syntax = "--{choice}-update-metadata"
    metavalue = "MODE"
    #untrusted = True

    def init(self):
        self.help = ("'force' - " + _("force the update ebuilds metadata") +
                     ",\n'skip' - " + _("skip the ebuild metadata update") +
                     ",\n'auto' - " + _("update metadata if it is outdated"))
        self.label = _("Update metadata")

    def choice(self):
        return [("force", _("Force")),
                ("skip", _("Skip")),
                ("auto", _("If needed"))]


class VariableClUpdateEgencacheForce(Variable):
    """
    Принудительное выполнение egencache
    """
    type = "choice"
    value = "auto"
    opt = ["--egencache"]
    syntax = "--{choice}-egencache"
    metavalue = "MODE"
    #untrusted = True

    def init(self):
        self.help = ("'force' - " + _("force the update of the overlays cache") +
                     ",\n'skip' - " + _("skip the update of the overlays cache") +
                     ",\n'auto' - " + _("update the overlays cache if outdated"))
        self.label = _("Update the overlays cache")

    def choice(self):
        return [("force", _("Force")),
                ("skip", _("Skip")),
                ("auto", _("If needed"))]


class VariableClUpdateEixupdateForce(Variable):
    """
    Принудительное действие с eix-update
    """
    type = "choice"
    value = "auto"
    opt = ["--eix-update"]
    syntax = "--{choice}-eix-update"
    metavalue = "MODE"
    #untrusted = True

    def init(self):
        self.help = ("'force' - " + _("force the eix cache update") +
                     ",\n'skip' - " + _("skip the eix cache update") +
                     ",\n'auto' - " + _("update the eix cache if it "
                                        "is outdated"))
        self.label = _("Update the eix cache")

    def choice(self):
        return [("force", _("Force")),
                ("skip", _("Skip")),
                ("auto", _("If needed"))]

class VariableClUpdateOtherSet(Variable):
    """
    Обновить остальные оверлеи
    """
    type = "bool"
    value = "off"
    opt = ["-o", "--update-other"]

    def init(self):
        self.help = _("update other overlays")
        self.label = _("Update other overlays")

class VariableClUpdateOtherRepData(ReadonlyTableVariable):
    """
    Информация о прочих репозиториях
    """
    source = ['cl_update_other_rep_name',
              'cl_update_other_rep_path']

    portdir_overlay = "main.cl_portdir_overlay"

    def generator(self):
        repNames = self.Get('update.cl_update_rep_name')
        chroot_path = self.Get('cl_chroot_path')
        layman = Layman(self.Get('update.cl_update_layman_installed'),
                        self.Get('update.cl_update_layman_make'),
                        self.Get('update.cl_update_layman_conf'),
                        prefix=chroot_path)
        layman_overlays = layman.get_installed()
        for rpath in self.Get(self.portdir_overlay):
            repo_file = path.join(rpath, "profiles/repo_name")
            rname = readFile(repo_file).strip() or path.basename(rpath)
            if rname in layman_overlays and rname not in repNames:
                yield (rname, rpath)

    def get(self):
        return list(self.generator())

class VariableClUpdateOtherRepName(FieldValue,ReadonlyVariable):
    """
    Список имен прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_other_rep_data"
    column = 0

class VariableClUpdateOtherRepPath(FieldValue,ReadonlyVariable):
    """
    Список путей до прочих репозиториев
    """
    type = "list"
    source_variable = "cl_update_other_rep_data"
    column = 1

class VariableClUpdateLaymanInstalled(VariableClUpdateLaymanStorage):
    """
    Путь до файла layman installed.xml
    """
    param_name = "installed"
    fallback_value = "var/lib/layman/installed.xml"

class VariableClUpdateLaymanMake(VariableClUpdateLaymanStorage):
    """
    Путь до файла make.conf изменяемого layman`ом
    """
    param_name = "make_conf"
    fallback_value = "var/lib/layman/make.conf"

class VariableClUpdateLaymanConf(VariableClUpdateLaymanStorage):
    """
    Путь до конфигурационного файла репозиториев для layman
    """
    param_name = "repos_conf"
    fallback_value = "etc/portage/repos.conf/layman.conf"

class VariableClUpdatePretendSet(Variable):
    """
    Запустить предварительную проверку на обновления
    """
    type = "bool"
    value = "off"
    opt = ["-p", "--pretend"]

    def init(self):
        self.label = _("Pretend a package update")
        self.help = _("instead of actually performing the update, "
                      "simply display the list of packages that "
                      "will be installed")

class VariableClUpdateSyncOnlySet(Variable):
    """
    Не выполнять установку/обновление пакетов при обновлении
    """
    type = "bool"
    value = "off"
    opt = ["-s", "--sync-only"]

    def init(self):
        self.label = _("Only synchronize repositories")
        self.help = _("do not update packages")

    def check(self, value):
        if (value == "on" and self.Get('cl_rebuild_world_set') != 'on' and
                not self.Get('cl_update_sync_rep') and
                    self.Get('cl_update_other_set') == 'off' and
                    self.Get('cl_update_rev_set') == 'off' and
                    self.Get('cl_update_metadata_force') != "force" and
                    self.Get('cl_update_eixupdate_force') != "force"):
            raise VariableError(_("Select at least one sync repository"))

class VariableClUpdateWaitAnotherSet(Variable):
    """
    Ждать завершения другого процесса обновления
    """
    type = "bool"
    value = "on"
    opt = ["--wait-another-update"]

    def init(self):
        self.label = _("Wait for another update to be complete")
        self.help = _("wait until the running update is finished")

class VariableClUpdateProfileStorage(ReadonlyVariable):
    type = "object"

    def get(self):
        return RepositoryStorageSet(
            LocalStorage('/var/lib/layman'),
            CacheStorage('/var/calculate/tmp/update'))

class VariableClUpdateRepHost(Variable):
    type = "list"
    value = ['calculate']

class VariableClUpdateRepHostUrl(Variable):
    type = "list"
    value = ['git://git.calculate.ru/calculate/%s.git']


class VariableClUpdateProfileDatavars(ReadonlyVariable):
    type = "object"
    profile = "cl_update_profile_system"
    profiles_path = "cl_update_profile_path"
    profiles_shortname = "cl_update_profile_shortname"

    def get(self):
        profile = self.Get(self.profile)
        path_profile = self.Select(self.profiles_path,
                                   where=self.profiles_shortname,
                                   eq=profile, limit=1)
        if path_profile:
            return DataVarsUpdateProfile(path_profile)
        return ""

class VariableClUpdateProfileLinuxFullname(ReadonlyVariable):
    """
    Имя системы в профиле
    """
    def init(self):
        self.label = _("Distribution name")

    datavars = "cl_update_profile_datavars"

    def get(self):
        dv = self.Get(self.datavars)
        if dv:
            try:
                subname = dv.Get('os_linux_subname')
                linuxname = dv.Get('os_linux_name')
                linuxver = dv.Get('os_linux_ver')
                if subname:
                    return "%s %s %s" % (linuxname, linuxver, subname)
                return "%s %s" %(linuxname,linuxver)
            except DataVarsError as s:
                raise VariableError("Wrong Calculate Linux profile")
        return ""

class VariableClUpdateProfileDependData(ReadonlyTableVariable):
    """
    Зависимые репозитории
    """
    source = ['cl_update_profile_depend_name',
              'cl_update_profile_depend_url']

    datavars = "cl_update_profile_datavars"

    def init(self):
        self.label = _("Used repositories")

    @staticmethod
    def url_like(url1, url2):
        match1 = VariableClUpdateProfileUrl.re_url.search(url1)
        match2 = VariableClUpdateProfileUrl.re_url.search(url2)
        if match1 and match2:
            return match1.group(2).lower() == match2.group(2).lower()
        return False


    def get(self, hr=False):
        dv = self.Get(self.datavars)
        # TODO:  неиспользуемая переменная возможно
        # испольуется для инициализации
        #url = self.Get('cl_update_profile_url').lower()
        if dv:
            return reversed(zip(dv.Get('cl_update_rep_name'),
                                dv.Get('cl_update_rep_url')))
        return ""

    setValue = Variable.setValue

class VariableClUpdateTemplatesLocate(Variable):
    """
    Выбранные типы хранилищ шаблонов
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["-T","--templates"]
    metavalue = "TEMPLATES"
    untrusted = True
    check_after = ['cl_update_profile_system']

    profile_datevars = "update.cl_update_profile_datavars"

    descriptionMap = {'overlay': _('Overlay templates'),
                      'local': _('Local templates'),
                      'calculate': _("Calculate overlay templates"),
                      'distros': _('Distribution templates'),
                      'distro': _('Distribution templates'),
                      'remote': _('Remote templates'),
                      'clt': _('clt templates')}

    def init(self):
        self.label = _("Templates location")
        self.help = _("select the location for templates %s") \
                      %",".join(self.get())

    def get(self):
        dv = self.Get(self.profile_datevars)
        if dv:
            return dv.Get('cl_template_location') + ['clt']
        else:
            return self.Get('cl_templates_locate')

    def choice(self):
        descr = lambda x: self.descriptionMap.get(x,
                                                  _("%s overlay templates" % x))
        return map(lambda x: (x, descr(x)), self.get())

class VariableClUpdateProfileDependName(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_update_profile_depend_data"
    column = 0

    def init(self):
        self.label = _("Name")

class VariableClUpdateProfileDependUrl(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_update_profile_depend_data"
    column = 1

    def init(self):
        self.label = _("URL")


class VariableClUpdateProfileRepName(ReadonlyVariable):
    type = "list"

    def get(self):
        dv = self.Get('cl_update_profile_datavars')
        if dv:
            return dv.Get('cl_update_rep_name')
        return []

class VariableClUpdateProfileSyncRep(ReadonlyVariable):
    type = "list"

    def get(self):
        return list(reversed(self.Get('cl_update_profile_rep_name')))

class VariableClUpdateProfileRepUrl(ReadonlyVariable):
    type = "list"

    def get(self):
        dv = self.Get('cl_update_profile_datavars')
        if dv:
            return dv.Get('cl_update_rep_url')
        return []

class VariableClUpdateProfileLinuxVer(ReadonlyVariable):
    """
    Имя системы в профиле
    """
    def init(self):
        self.label = _("System profile version")

    def get(self):
        dv = self.Get('cl_update_profile_datavars')
        if dv:
            return dv.Get('os_linux_ver')
        return ""

class VariableClUpdateProfileLinuxName(ReadonlyVariable):
    """
    Имя системы в профиле
    """
    def get(self):
        dv = self.Get('cl_update_profile_datavars')
        if dv:
            dv.Get('os_linux_name')
        return ""

class VariableClUpdateProfileUrl(Variable):
    """
    URL текущего репозитория
    """
    untrusted = True
    check_after = ["cl_update_profile_branch"]

    opt = ["--url"]
    metavalue = "URL"

    profile = "cl_profile_system"
    branch = "cl_update_profile_branch"
    storage = "cl_update_profile_storage"
    default_url = "git://git.calculate.ru/calculate/distros.git"
    profiles_shortname = 'cl_update_profile_shortname'

    @property
    def current_root(self):
        return '/'


    def init(self):
        self.label = _("Profile repository")
        self.help = _("set the profile repository")

    re_url = re.compile(
        r"^(?:(%s)://)?(\w[\w\./:-]+?\w)(\.git)?$" % "|".join(
            ["http", "https", "git"]))

    re_shortname = re.compile('^(?:([\w\.-]+):)?([\w\.-]+)$')

    @classmethod
    def normalize_url(cls, url):
        match = cls.re_url.match(url)
        if not match:
            raise VariableError(_("Wrong repository URL"))
        url = match.group(2)
        url = "%s://%s" % (match.group(1) or "git", url)
        url = "%s.git" % url
        return url

    def url_by_shortname(self, value):
        match = self.re_shortname.match(value)
        if not match.group(1):
            template = self.Get('update.cl_update_rep_host_url')
            if template:
                template = template[0]
        else:
            template = self.Select('update.cl_update_rep_host_url',
                                   where='update.cl_update_rep_host',
                                   eq=match.group(1), limit=1)
        if not template:
            raise VariableError(_("Failed to determine the repository host"))

        try:
            return template % match.group(2)
        except TypeError:
            raise VariableError(_("Failed to determine the repository host"))

    def set(self, value):
        if self.re_shortname.match(value):
            value = self.url_by_shortname(value)
        return self.normalize_url(value)

    def check(self, value):
        if not value:
            raise VariableError("Need to specify profile repository")
        try:
            branch = self.Get(self.branch)
            self.Get(self.storage).get_profiles(value, branch)
        except GitError as e:
            raise VariableError(str(e))
        if not self.Get(self.profiles_shortname):
            raise VariableError(_("Repository %s has no profiles")%value)

    def get(self):
        try:
            profile = self.Get(self.profile)
            if profile:
                while (profile != self.current_root and
                               ".git" not in listDirectory(profile)):
                    profile = path.dirname(profile)
                if profile == self.current_root:
                    return ""
                git = Git()
                return git.get_url(profile, "origin") or ""
        except:
            pass
        return self.default_url

class VariableClUpdateProfileRepoName(ReadonlyVariable):
    def init(self):
        self.label = _("Repository name")

    storage = "cl_update_profile_storage"
    url = "cl_update_profile_url"

    def get(self):
        rep_set = self.Get(self.storage)
        url = self.Get(self.url)
        rep = rep_set.get_repository(url, branch=None)
        if rep:
            return rep.repo_name
        return ""


class VariableClUpdateProfileBranch(Variable):
    """
    Текущий репозиторий
    """
    untrusted = True

    type = "edit"
    opt = ["--branch"]
    metavalue = "BRANCH"

    storage = "cl_update_profile_storage"
    url = "cl_update_profile_url"
    value = DEFAULT_BRANCH

    def init(self):
        self.label = _("Repository branch")
        self.help = _("set the repository branch")

    def check(self, value):
        pass
        ## TODO: проверка ветки
        #try:
        #    url = self.Get('cl_update_profile_url')
        #    self.Get('cl_update_profile_storage').get_profiles(url, value)
        #except GitError as e:
#            raise VariableError(str(e))


class VariableClProfileRepository(ReadonlyVariable):
    """
    Репозиторий из которого будут получены профили
    """
    type = "object"

    def get(self):
        try:
            profile_dn = self.Get('cl_profile_system')
            while profile_dn != '/' and ".git" not in listDirectory(profile_dn):
                profile_dn = path.dirname(profile_dn)
            if profile_dn == '/':
                return ""
            ls = LocalStorage(path.dirname(profile_dn))
            return ProfileRepository(path.basename(profile_dn), ls)
        except:
            return ""


class VariableClProfileData(ReadonlyTableVariable):
    """
    Таблица данных о текущих профилях системы
    """
    source = []
    repository = "cl_profile_repository"

    def profile_filter(self, profiles):
        arch = self.Get('os_arch_machine_gentoo')
        return [x for x in profiles if x.arch == arch]

    def get(self, hr=False):
        rep = self.Get(self.repository)
        if not rep:
            return [[]]
        profiles = rep.get_profiles()
        if not profiles:
            return [[]]
        repo_name = profiles[0].repository.repo_name
        filtered_profiles = self.profile_filter(profiles)
        full_name = [x.profile for x in filtered_profiles]
        profile_path = [x.path for x in filtered_profiles]
        profile_arch = [x.arch for x in filtered_profiles]
        short_name = simplify_profiles(full_name)
        full_name = ["%s:%s" % (repo_name, x) for x in full_name]
        return zip(full_name,
                   short_name,
                   profile_path,
                   profile_arch)

    setValue = Variable.setValue

class VariableClUpdateProfileRepository(ReadonlyVariable):
    """
    Репозиторий из которого будет извлечён список профилей
    """
    type = "object"
    url = "cl_update_profile_url"
    storage = "cl_update_profile_storage"
    branch = "cl_update_profile_branch"
    sync_set = "update.cl_update_profile_sync_set"

    def get(self):
        url = self.Get(self.url)
        if not url:
            return ""
        try:
            rep_set = self.Get(self.storage)
            branch = self.Get(self.branch)
            rep = rep_set.get_repository(url, branch)
            if rep and self.Get(self.sync_set) == 'on':
                rep.sync()
            return rep_set.get_repository(url, branch) or ""
        except GitError:
            return ""

class VariableClUpdateProfileRepositoryName(ReadonlyVariable):
    """
    Название репозитория, из которого будут извлечены профили
    """
    def get(self):
        rep = self.Get('cl_update_profile_repository')
        if rep:
            return rep.repo_name
        return ""


class VariableClUpdateProfileData(VariableClProfileData):
    source = ["cl_update_profile_fullname",
              "cl_update_profile_shortname",
              "cl_update_profile_path",
              "cl_update_profile_arch"]

    repository = "cl_update_profile_repository"


class VariableClUpdateProfileFullname(FieldValue, ReadonlyVariable):
    """
    Полное название профиля
    """
    type = "list"
    source_variable = "cl_update_profile_data"
    column = 0

class VariableClUpdateProfileShortname(FieldValue, ReadonlyVariable):
    """
    Упрощенное название профиля
    """
    type = "list"
    source_variable = "cl_update_profile_data"
    column = 1

class VariableClUpdateProfilePath(FieldValue, ReadonlyVariable):
    """
    Путь от корня до профиля
    """
    type = "list"
    source_variable = "cl_update_profile_data"
    column = 2

class VariableClUpdateProfileArch(FieldValue, ReadonlyVariable):
    """
    Архитектура профиля
    """
    type = "list"
    source_variable = "cl_update_profile_data"
    column = 3

class VariableClUpdateProfileSystem(Variable):
    """
    Профиль системы (симлинк /etc/make.profile')
    """
    type = "choice"
    opt = ["cl_update_profile_system"]
    untrusted = True
    metavalue = "PROFILE"

    profiles_path = "cl_update_profile_path"
    profiles_shortname = "cl_update_profile_shortname"
    profiles_fullname = "cl_update_profile_fullname"
    profiles_arch = "cl_update_profile_arch"
    profile = "cl_profile_system"
    url = "cl_update_profile_url"
    gentoo_arch = 'os_arch_machine_gentoo'

    def init(self):
        self.label = _("System profile")
        self.help = _("set the system profile")

    def check(self, profile):
        if not profile:
            raise VariableError(_("You must specify the profile"))
        path_profile = self.Select(self.profiles_path,
                                   where=self.profiles_shortname,
                                   eq=profile, limit=1)
        profile_fullname = self.Select(self.profiles_fullname,
                                       where=self.profiles_shortname,
                                       eq=profile, limit=1)
        if path_profile:
            dv = DataVarsUpdateProfile(path_profile)
            if ":" in profile_fullname:
                repo_name = profile_fullname.partition(":")[0]
            else:
                repo_name = ""
            try:
                if (not dv.Get('cl_update_rep_name') or
                        not dv.Get('cl_update_rep_url')):
                    raise VariableError(
                        _("Repository variables "
                          "were not configured for the profile"))
                if not dv.Get('os_linux_name'):
                    raise VariableError("")
                if repo_name not in list(dv.Get('cl_update_rep_name')):
                    raise VariableError(
                        _("Overlay %s is not specified "
                          "in cl_update_rep_name") % repo_name)
            except (DataVarsError,VariableError) as e:
                if str(e):
                    message = ". " + str(e)
                else:
                    message = ""
                raise VariableError(_("The selected profile is not Calculate")
                                    + message)
        else:
            raise VariableError(_("Wrong Calculate profile"))

    def get(self):
        try:
            profile_system = self.Get(self.profile)
            profile = self.Select(self.profiles_shortname,
                                  where=self.profiles_path,
                                  eq=profile_system, limit=1)
            if profile:
                return profile
        except VariableError:
            pass
        shortname = self.Get(self.profiles_shortname)
        if len(shortname) == 1:
            return shortname[0]
        return ""

    def choice(self):
        url = self.Get(self.url)
        if not url:
            return []
        arch = self.Get(self.gentoo_arch)
        profiles = zip(*self.Select([self.profiles_shortname,
                                     self.profiles_fullname],
                                    where=self.profiles_arch, eq=arch))
        if profiles:
            short_name, full_name = profiles
            return zip(short_name, full_name)
        return []

class DataVarsUpdateProfile(SimpleDataVars):
    """
    Упрощенная модель переменных для получения данных с удаленного профиля
    """
    source = ['cl_update_rep_name',
              'cl_update_rep_url',
              'cl_update_rep_path',
              'cl_update_rep_rev',
              'cl_update_branch_name']
    def __init__(self, profile, chroot_path='/', recheck=None):
        SimpleDataVars.__init__(
            self,
            lib_linux.VariableOsLinuxName(),
            lib_linux.VariableOsLinuxShortname(),
            lib_linux.VariableOsLinuxSubname(),
            lib_linux.VariableOsLinuxVer(),
            lib_linux.VariableClProfileSystem(),
            env.VariableClRepositoryData(),
            env.VariableClRepositoryName(),
            env.VariableClRepositoryLocation(),
            env.VariableClChrootPath(),
            env.VariableClTemplateLocation(),
            env.VariableClTemplatePath(),
            env.VariableClEmergeConfig(systemRoot=chroot_path),
            env.VariableClFeatures(),
            VariableClUpdateRepData(section="update"),
            VariableClUpdateRepPath(section="update"),
            VariableClUpdateRepRev(section="update"),
            VariableClUpdateBranch(section="update"),
            VariableClUpdateBranchName(section="update"),
            VariableClUpdateLaymanConfig(section="update"),
            VariableClUpdateLaymanStorage(section="update"),
            VariableClUpdateRepName(section="update"),
            VariableClUpdateRep(section="update"),
            VariableClUpdateRepUrl(section="update"),
            VariableClUpdateBinhost(section="update"),
            VariableClUpdateBinhostData(section="update"),
            VariableClUpdateBinhostHost(section="update"),
            VariableClUpdateBinhostRecheckSet(section="update"),
            VariableClUpdateBinhostRevisions(section="update"),
            VariableClUpdateBinhostTime(section="update"),
            VariableClUpdateBinhostTimeout(section="update"),
            VariableClUpdateBinhostTimestampPath(section="update"),
            VariableClUpdateBinhostList(section="update"),
            VariableClUpdateBinhostRevisionPath(section="update"),
        )
        self['cl_profile_system'] = profile
        self['cl_chroot_path'] = chroot_path
        if recheck is not None:
            self['cl_update_binhost_recheck_set'] = recheck
        self.flIniFileFrom(profile)

    def __repr__(self):
        return "Profile variables"

class VariableClUpdateProfileSyncSet(Variable):
    """
    Синхронизировать репозиторий перед сменой профиля
    """
    type = "bool"
    value = "off"
    opt = ["-u", "--update-cache"]

    def init(self):
        self.label = _("Update the cache")
        self.help = _("Update the cache")


class VariableClUpdateAutocheckSet(Variable):
    """
    Выполнять/невыполнять автопроверку обновлений
    """
    type = "bool"
    value = "on"
    opt = ["-a", "--autocheck"]

    def init(self):
        self.label = _("Automatically check updates")
        self.help = _("automatically check updates")


class VariableClUpdateAutocheckInterval(Variable):
    """
    Интервал выполнения проверки
    """
    type = "choiceedit"
    opt = ["-I", "--autocheck-interval"]
    metavalue = "INTERVAL"

    def init(self):
        self.label = _("Interval for the updates checking")
        self.help = _("set interval for the updates checking")

    def choice(self):
        return [["6h",_("every six hours")],
            ["12h",_("every twelve hours")],
            ["1d",_("daily")]]


class VariableClUpdateAutocheckScheduleSet(Variable):
    """
    Запустить проверку только если со времени последнего запуска прошло
    необходимое время
    """
    type = "bool"
    value = "off"
    opt = ["--schedule"]

    def init(self):
        self.label = _("Consider the autocheck schedule")
        self.help = _("consider the autocheck schedule")


class VariableClUpdateEmergelistSet(Variable):
    """
    Вывести список пакетов в формате emerge
    """
    type = "bool"
    value = "off"
    opt = ["-e","--emergelist"]

    def init(self):
        self.label = _("Emerge-like packages list")
        self.help = _("display the packages list in emerge format")


class VariableClUpdateKernelVersion(ReadonlyVariable):
    """
    Текущая версия ядра
    """
    def get(self):
        return process('/bin/uname','-r').read().strip()


class VariableClUpdateKernelSrcPath(ReadonlyVariable):
    """
    Каталог содержащий исходный код текущего ядра
    """
    def get(self):
        kernel_ver = self.Get('cl_update_kernel_version')
        for template_path in ("/lib/modules/%s/build",
                                 "/usr/src/linux-%s"):
            src_path = template_path % kernel_ver
            if path.exists(src_path):
                if path.islink(src_path):
                    return os.readlink(src_path)
                else:
                    return src_path
        else:
            return ""


class VariableClUpdateKernelPkg(ReadonlyVariable):
    """
    Пакет текущего ядра
    """
    def get(self):
        src_path = self.Get('cl_update_kernel_src_path')
        if src_path:
            qfile = process('/usr/bin/qfile', '-vC', src_path)
            if qfile.success():
                return qfile.read().partition(" ")[0]
        return ""


class VariableClUpdateLinesLimit(Variable):
    """
    Количество выводимых строк при ошибке
    """
    type = "int"
    value = "30"


class VariableClUpdateSkipSetupSet(Variable):
    """
    Пропустить выполнение cl-setup-system в cl-update-profile
    """
    type = "bool"
    value = "off"
    opt = ["--skip-setup-system"]

    def init(self):
        self.label = _("Skip the system setup")
        self.help = _("skip the system setup")


class VariableClUpdateCleanpkgSet(Variable):
    """
    Пропустить выполнение cl-setup-system в cl-update-profile
    """
    type = "bool"
    value = "off"
    opt = ["--clean-pkg"]

    def init(self):
        self.label = _("Clean obsolete programs archives")
        self.help = _("clean obsolete programs archives")


class VariableClUpdateOutdatedKernelPath(Variable):
    """
    Файл-флаг наличия устаревшего, неудаленного ядра
    """
    value = "/var/lib/calculate/calculate-update/outdated_kernel"


class VariableClUpdateSkipRbSet(Variable):
    """
    Пропусить revdep-rebuild
    """
    type = "bool"
    value = "off"

    opt = ["-R", "--skip-revdep-rebuild"]

    def init(self):
        self.label = _("Skip reverse dependencies check")
        self.help = _("skip reverse dependencies check")


class VariableClUpdateOutdatedKernelSet(ReadonlyVariable):
    """
    Есть наличие устаревшего ядра
    """
    type = "bool"

    def get(self):
        ui = UpdateInfo(self.parent)
        return "on" if ui.outdated_kernel else "off"

class VariableClUpdateBinhostList(Variable):
    """
    Список хостов с бинарными обновлениями
    """
    type = "list"
    value = ["ftp://ftp.calculate-linux.ru/pub/calculate"]

class VariableClUpdateBinhost(Variable):
    """
    Хост с бинарными обновлениями
    """
    value = ""

class VariableClUpdateBinhostRevisionPath(Variable):
    """
    Путь до revisions файлов
    """
    type = "list"
    value = [
        "grp/default/ini.env",
        "grp/kde/ini.env",
        "grp/server/ini.env",
        "grp/x/ini.env"
    ]

class VariableClUpdateBinhostTimestampPath(Variable):
    """
    Путь до файла timestamp
    """
    value = "timestamp"

class VariableClUpdateBinhostTimeout(Variable):
    """
    Таймаут на проверку одного binhost
    """
    type = "int"
    value = "5"
