diff --git a/bin/cl-console-gui_py3 b/bin/cl-console-gui
similarity index 100%
rename from bin/cl-console-gui_py3
rename to bin/cl-console-gui
diff --git a/bin/cl-update-checker_py3 b/bin/cl-update-checker
similarity index 94%
rename from bin/cl-update-checker_py3
rename to bin/cl-update-checker
index 64166e0..06803fa 100644
--- a/bin/cl-update-checker_py3
+++ b/bin/cl-update-checker
@@ -15,15 +15,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-#TODO: this is untested, and probably broken
+import logging
+# logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
+logger = logging.getLogger(__name__)
import os
from os import path
from PyQt5 import QtCore, QtGui, QtWidgets
import time
-from calculate.core.client.cert_info import user_can_run_update
+from calculate.consolegui.application.user_update import user_can_run_update
from calculate.lib.datavars import DataVars
import dbus
import dbus.service
@@ -131,6 +132,7 @@ class DBusChecker(dbus.service.Object):
self.parent = parent
# export this object to dbus
dbus.service.Object.__init__(self, name, session)
+ self.parent.systray.setVisible(True)
@dbus.service.method(DBUS_NAME_UPDATER, in_signature='', out_signature='')
def hide_systray(self):
@@ -159,7 +161,7 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo):
def __init__(self, bus):
super(CheckThread, self).__init__()
UpdateInfo.__init__(self)
-
+
self.bus = bus
self.wm = pyinotify.WatchManager()
@@ -170,11 +172,11 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo):
self.already_timer = QtCore.QTimer(self)
self.already_timer.timeout.connect(self.notify_step)
- self.already_timer.start(1000)
+ self.already_timer.start(1)
self.check_timer = QtCore.QTimer(self)
self.check_timer.timeout.connect(self.step)
- self.check_timer.start(self.interval * 1000)
+ self.check_timer.start(self.interval * 1)
self.systray = SysTray(self)
self.gui_runned = False
@@ -222,10 +224,8 @@ class CheckThread(QtWidgets.QMainWindow, UpdateInfo):
if __name__ == '__main__':
import sys
-
if os.fork():
sys.exit(1)
-
if not user_can_run_update(True):
sys.stderr.write(_("User can not to perform the system update") + "\n")
sys.exit(1)
@@ -236,23 +236,25 @@ if __name__ == '__main__':
sys.exit(1)
app = QtWidgets.QApplication(sys.argv)
-
for i in [0.5, 1, 2, 5]:
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
break
time.sleep(i)
-
+
# Enable glib main loop support
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
# Get the session bus
try:
bus = dbus.SessionBus()
- except dbus.exceptions.DBusException, e:
+ except dbus.exceptions.DBusException as e:
+ print(e)
sys.exit(1)
-
+
try:
remote_object = bus.get_object(DBUS_NAME_UPDATER, DBUS_APP_UPDATER)
g = dbus.Interface(remote_object, DBUS_NAME_UPDATER)
+ props = g.getProperties()
+ print(props)
sys.exit(1)
except Exception as e:
pass
diff --git a/libs_crutch/core/backup.py b/libs_crutch/core/backup.py
new file mode 100644
index 0000000..554f063
--- /dev/null
+++ b/libs_crutch/core/backup.py
@@ -0,0 +1,724 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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.
+
+
+from __future__ import absolute_import
+import sys
+import os
+import stat
+import re
+from os import path
+from calculate.core.server.core_interfaces import MethodsInterface
+from calculate.lib.utils.files import (makeDirectory, removeDir, tar_directory,
+ FilePermission, find, listDirectory,
+ FilesError, process, readFileEx,
+ readFile, pathJoin, FindFileType)
+from calculate.lib.utils.content import FileOwnersRestricted, ContentsStorage
+from calculate.lib.utils.portage import getInstalledAtom, makeCfgName
+from calculate.lib.configparser import ConfigParserCaseSens
+from calculate.lib.cl_template import Template
+from calculate.lib.utils.accounts import Passwd, Group, Shadow
+
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from .variables.action import Actions
+import tarfile
+import shutil
+import glob
+from itertools import chain
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+
+__ = getLazyLocalTranslate(_)
+
+
+class BackupError(Exception):
+ """
+ Исключение вызванное во время резервного копирования настроек
+ """
+
+
+class Backup(MethodsInterface):
+ """
+ Выполнение резервного копирования настроек
+ """
+
+ def init(self):
+ self.apply_files = set()
+ self.unlink_autorun = set()
+ self.uid_map = {}
+ self.gid_map = {}
+
+ def prepare_backup(self, dn, rootname):
+ makeDirectory(path.join(dn, rootname))
+ return True
+
+ def remove_directory(self, dn):
+ removeDir(dn)
+ return True
+
+ def backup_user_changed(self, owner, dn_root):
+ """
+ Сохранить конфигурационные файлы изменённые пользователем
+ :param owner:
+ :param dn_root:
+ :return:
+ """
+ for fn in owner.get_md5_failed(lambda x: x.startswith('/etc')):
+ self.backup_file(fn, dn_root)
+ return True
+
+ def prepare_contents(self, dn, contents_file, rootname):
+ dn_root = path.join(dn, rootname)
+ fo = FileOwnersRestricted(
+ "/", ["/%s" % x for x in find(path.join(dn, rootname),
+ fullpath=False)] + ["/etc"])
+ self.backup_user_changed(fo, dn_root)
+ cs = ContentsStorage(contents_file, fo)
+ cs.keep(dn_root, dn_root)
+ return True
+
+ def create_archive(self, dn, archfile):
+ arch_dn = path.dirname(archfile)
+ if not path.exists(arch_dn):
+ makeDirectory(arch_dn)
+ os.chmod(arch_dn, FilePermission.UserAll)
+ tar_directory(dn, archfile)
+ return True
+
+ def open_archive(self, dn, archfile):
+ makeDirectory(dn)
+ with tarfile.open(archfile, 'r:bz2') as f:
+ f.extractall(dn)
+ return True
+
+ def restore_configs(self, archfile, dn, contents_name, root_name):
+ """
+ Восстановить все файлы настроек
+ :param archfile:
+ :param dn:
+ :return:
+ """
+ dirs_data = {}
+ used_dirs = set()
+ _gid = lambda x: self.gid_map.get(x, x)
+ _uid = lambda x: self.uid_map.get(x, x)
+ with tarfile.open(archfile, 'r:bz2') as f:
+ try:
+ # исключить из переноса файлы, которые принадлежат пакетам,
+ # которые не установлены в системе
+ contents = f.extractfile(f.getmember(contents_name))
+ pkg_file = [x.split()[:3:2] for x in contents]
+ not_installed_files = [
+ x for x in pkg_file
+ if not any(getInstalledAtom(x[0].partition(":")[0]))]
+ skip_packages = sorted(list(
+ set([x[0] for x in not_installed_files])))
+ if skip_packages:
+ self.printWARNING(
+ _("Settings ignored for following packages: %s") %
+ ", ".join(x.partition(":")[0] for x in skip_packages))
+ not_installed_files = [x[1] for x in not_installed_files]
+ except KeyError:
+ raise BackupError(_("CONTENTS file not found"))
+ for ti in (x for x in f if x.name.startswith("%s/" % root_name)):
+ if ti.name[4:] in not_installed_files:
+ continue
+ if ti.issym() and not path.exists(ti.linkpath):
+ continue
+ if ti.name[5:]:
+ fn_system = path.join(dn, ti.path[5:])
+ if ti.isdir():
+ dirs_data[fn_system] = (ti.mode, _uid(ti.uid),
+ _gid(ti.gid))
+ continue
+ dirs_list = fn_system.split('/')
+ for i in range(2, len(dirs_list)):
+ used_dirs.add("/".join(dirs_list[:i]))
+ if path.lexists(fn_system):
+ stat_system = os.lstat(fn_system)
+ if ti.issym():
+ if stat.S_ISLNK(stat_system.st_mode):
+ system_link = os.readlink(fn_system)
+ if system_link == ti.linkpath:
+ continue
+ else:
+ if (stat.S_IMODE(stat_system.st_mode) == ti.mode and
+ stat_system.st_uid == _uid(ti.uid) and
+ stat_system.st_gid == _gid(ti.gid)):
+ data_system = readFile(fn_system)
+ extr_file = f.extractfile(ti)
+ if extr_file:
+ data_ti = extr_file.read()
+ if self.is_equal_files(data_system,
+ data_ti):
+ continue
+ ti.name = ti.name[5:]
+ f.extract(ti, dn)
+ os.chown(fn_system, _uid(ti.uid), _gid(ti.gid))
+ if ti.isfile() or ti.issym():
+ # если симлинк в списке предварительного удаления
+ # то исключаем его из списка изменённых файлов
+ if fn_system in self.unlink_autorun:
+ self.unlink_autorun.remove(fn_system)
+ else:
+ self.apply_files.add(fn_system)
+ # восстановление прав у каталогов, конфиги в которых должны были
+ # восстанавливаться
+ for dn_name in sorted(used_dirs):
+ if dn_name in dirs_data:
+ dn_mode, dn_uid, dn_gid = dirs_data[dn_name]
+ if path.lexists(dn_name):
+ stat_system = os.lstat(dn_name)
+ if (stat.S_IMODE(stat_system.st_mode) != dn_mode or
+ stat_system.st_uid != dn_uid or
+ stat_system.st_gid != dn_gid):
+ os.chmod(dn_name, dn_mode)
+ os.chown(dn_name, dn_uid, dn_gid)
+
+ return True
+
+ def sava_ini(self, section, key, val):
+ ini = ConfigParserCaseSens(strict=False)
+ ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8")
+ if not ini.has_section(section):
+ ini.add_section(section)
+ ini[section][key] = str(val)
+
+ ini["backup"]["version"] = self.clVars.Get('cl_ver')
+ with open(self.clVars.Get('cl_backup_ini_env'), 'w') as f:
+ ini.write(f)
+ return True
+
+ def load_ini(self, section, key):
+ ini = ConfigParserCaseSens(strict=False)
+ ini.read(self.clVars.Get('cl_backup_ini_env'), encoding="utf-8")
+ return ini.get(section, key, fallback="")
+
+ def save_initd(self, dn, root_name):
+ """
+ Сохранить список init.d
+ :param dn:
+ :param root_name:
+ :return:
+ """
+ self.sava_ini("backup", "init",
+ ','.join(listDirectory('/etc/init.d', fullPath=False)))
+ dn_root = path.join(dn, root_name)
+ for dn in ('/etc/runlevels/sysinit',
+ '/etc/runlevels/default',
+ '/etc/runlevels/boot'):
+ try:
+ dn_backup = pathJoin(dn_root, dn)
+ if not path.exists(dn_backup):
+ makeDirectory(dn_backup)
+ for fn in listDirectory(dn, fullPath=True):
+ if path.islink(fn):
+ link = os.readlink(fn)
+ symname = pathJoin(dn_root, fn)
+ if not path.lexists(symname):
+ os.symlink(link, symname)
+ except (OSError, IOError) as e:
+ raise BackupError(_("Failed to enable service at startup") +
+ (_(": %s") % (str(e))))
+ return True
+
+ def make_directory_sync(self, base_dn, dn, prefix="/"):
+ """
+ Создать директорию и сохранить права из prefix
+ :param dn:
+ :param prefix:
+ :return:
+ """
+ if not path.exists(dn):
+ self.make_directory_sync(base_dn, path.dirname(dn), prefix)
+ rel_dn = path.relpath(dn, base_dn)
+ system_dn = path.join(prefix, rel_dn)
+ system_dn_stat = os.lstat(system_dn)
+ if not makeDirectory(dn):
+ raise BackupError(_("Failed to create directory %s") % dn)
+ os.chown(dn, system_dn_stat.st_uid, system_dn_stat.st_gid)
+ os.chmod(dn, stat.S_IMODE(system_dn_stat.st_mode))
+
+ def backup_file(self, source_fn, target_dn, prefix="/"):
+ """
+ Сделать резервную копию указанного файла
+ :param source_fn:
+ :param target_dn:
+ :return:
+ """
+ target_fn = path.join(target_dn, path.relpath(source_fn, prefix))
+ source_stat = os.lstat(source_fn)
+ target_base_dn = path.dirname(target_fn)
+ self.make_directory_sync(target_dn, target_base_dn, prefix=prefix)
+ if stat.S_ISLNK(source_stat.st_mode):
+ source_link = os.readlink(source_fn)
+ os.symlink(source_link, target_fn)
+ elif stat.S_ISREG(source_stat.st_mode):
+ shutil.copy2(source_fn, target_fn)
+ os.chown(target_fn, source_stat.st_uid, source_stat.st_gid)
+ return True
+
+ def backup_marked(self, source_dn, target_dn, subdn, root_name):
+ """
+ Сохранить файлы из указанного каталога, отмеченного комментариями
+ выполнения шаблонов
+ :return:
+ """
+ source_etc_dn = path.join(source_dn, subdn)
+ root_dn = path.join(target_dn, root_name)
+ reCfg = re.compile('._cfg\d{4}_')
+ try:
+ for fn in find(source_etc_dn, filetype=FindFileType.RegularFile):
+ if (not reCfg.search(fn) and
+ " Modified Calculate" in readFileEx(fn,
+ headbyte=300)):
+ self.backup_file(fn, root_dn, prefix=source_dn)
+ except (OSError, IOError) as e:
+ raise BackupError(_("Failed to backup configuration files that "
+ "were modified by templates") +
+ (_(": %s") % (str(e))))
+ return True
+
+ def clear_autorun(self):
+ """
+ Удалить все файлы из автозапуска, которые ссылаются на файлы из списка
+ init.d
+ :return:
+ """
+ files = ["/etc/init.d/%s" % x.strip()
+ for x in self.load_ini("backup", "init").split(',')]
+ for dn in ('/etc/runlevels/sysinit',
+ '/etc/runlevels/default',
+ '/etc/runlevels/boot'):
+ for fn in listDirectory(dn, fullPath=True):
+ if path.islink(fn) and os.readlink(fn) in files:
+ os.unlink(fn)
+ self.unlink_autorun.add(fn)
+ return True
+
+ def restore_contents(self, contentsfile, dn):
+ cs = ContentsStorage(contentsfile)
+ cs.restore(dn, files=self.apply_files)
+ return True
+
+ def set_service_action(self):
+ self.clVars.Set('core.cl_backup_action', Actions.Service, force=True)
+ return True
+
+ nm_name = "NetworkManager"
+ nm_config = "/etc/NetworkManager"
+ nm_connections = path.join(nm_config, "system-connections")
+
+ def _do_service(self, service, action):
+ """
+ Выполнить действие с сервисом (restart, start, stop, zap)
+ :param service:
+ :param action:
+ :return:
+ """
+ actions = {
+ 'restart': _("Failed to restart {name} service"),
+ 'start': _("Failed to start {name} service"),
+ 'stop': _("Failed to stop {name} service"),
+ 'zap': _("Failed to zap {name} service"),
+ 'status': _("Failed to get status of {name} service")
+ }
+ try:
+ p = process(service, action)
+ if p.failed():
+ data = p.readerr().strip()
+ if ("has started, but is inactive" not in data and
+ "will start when" not in data):
+ for line in data.split('\n'):
+ self.printERROR(line)
+ raise BackupError(actions.get(action, action).format(
+ name=path.basename(service)))
+ except FilesError:
+ self.printERROR(actions.get(action, action).format(
+ name=path.basename(service)))
+ raise
+ return True
+
+ def stop_net_services(self):
+ """
+ Остановить все сетевые службы (и NM и openrc)
+ :return:
+ """
+ self._do_service("/etc/init.d/netmount", "zap")
+ for fn in chain(["/etc/init.d/%s" % self.nm_name],
+ glob.glob('/etc/init.d/net.*')):
+ if fn.endswith('.lo') or not path.exists(fn):
+ continue
+ self._do_service(fn, "stop")
+ return True
+
+ def unlink_openrc_net_services(self, files):
+ """
+ Удалить сетевые сервисы openrc сервисы openrc
+ :return:
+ """
+ for fn in glob.glob('/etc/init.d/net.*'):
+ if fn.endswith('.lo') or not path.exists(fn) or fn in files:
+ continue
+ try:
+ os.unlink(fn)
+ self.apply_files.add(fn)
+ except OSError as e:
+ self.printERROR(_("Failed to remove %s service"),
+ path.basename(fn))
+ self.printERROR(str(e))
+ return True
+
+ def is_networkmanager_backup(self, backup_path):
+ """
+ Проверить сетевой менеджер в резервной копии
+ :param backup_path:
+ :return:
+ """
+ return path.lexists(path.join(
+ backup_path, "root/etc/runlevels/default/%s" % self.nm_name))
+
+ def is_networkmanager_system(self):
+ """
+ Проверить сетевой менеджер в текущей системе
+ :return:
+ """
+ return path.lexists("/etc/runlevels/default/%s" % self.nm_name)
+
+ def restore_openrc_net_initd(self, files):
+ """
+ Восстановить сервисы net.* и запустить их
+ :return:
+ """
+ for fn in files:
+ if not path.exists(fn):
+ os.symlink("/etc/init.d/net.lo", fn)
+ self.apply_files.add(fn)
+ self._do_service(fn, "start")
+ return True
+
+ def restore_files(self, backup_path, files, notapply=False):
+ """
+ Восстановить указанные файлы из backup/root
+ :param backup_path:
+ :param files: список файлов (поддерживаются глобальные символы)
+ :return:
+ """
+ len_source_prefix = len(path.join(backup_path, "root"))
+ _gid = lambda x: self.gid_map.get(x, x)
+ _uid = lambda x: self.uid_map.get(x, x)
+ for source in chain(*[glob.glob(pathJoin(backup_path, "root", x))
+ for x in files]):
+ dest = source[len_source_prefix:]
+ if path.lexists(source):
+ dn = path.dirname(dest)
+ if not path.exists(dn):
+ makeDirectory(dn)
+ if path.lexists(dest):
+ if self.is_equal_system_backup(dest, source):
+ continue
+
+ shutil.copy2(source, dest)
+ fn_stat = os.lstat(source)
+ os.chown(dest, _uid(fn_stat.st_uid), _gid(fn_stat.st_gid))
+ if not notapply:
+ self.apply_files.add(dest)
+ return True
+
+ def clear_nm_connections(self, backup_path):
+ """
+ Удалить доступные соединения для NetworkManager
+ :return:
+ """
+ base_dir = pathJoin(backup_path, "root")
+ for fn in listDirectory(self.nm_connections, fullPath=True):
+ try:
+ if not path.exists(pathJoin(base_dir, fn)):
+ os.unlink(fn)
+ self.apply_files.add(fn)
+ except OSError as e:
+ raise BackupError(str(e))
+
+ def is_equal_files(self, text1, text2):
+ """
+ Сравнить два файла отбросив комментарии и пробельные символы в начале и
+ в конце
+ :param text1:
+ :param text2:
+ :return:
+ """
+ text1 = Template.removeComment(text1).strip()
+ text2 = Template.removeComment(text2).strip()
+ return text1 == text2
+
+ def is_equal_system_backup(self, system_fn, backup_fn):
+ """
+ Проверить одинаковый ли файл в резервной копии и системе
+ :param system_fn:
+ :param backup_fn:
+ :return:
+ """
+ _gid = lambda x: self.gid_map.get(x, x)
+ _uid = lambda x: self.uid_map.get(x, x)
+ if path.islink(system_fn) != path.islink(backup_fn):
+ return False
+ if path.islink(system_fn):
+ return os.readlink(system_fn) == os.readlink(backup_fn)
+ if path.isfile(system_fn) != path.isfile(backup_fn):
+ return False
+ data_system = readFile(system_fn)
+ data_backup = readFile(backup_fn)
+ if not self.is_equal_files(data_system, data_backup):
+ return False
+ stat_system = os.lstat(system_fn)
+ stat_backup = os.lstat(backup_fn)
+ if (stat.S_IMODE(stat_system.st_mode) !=
+ stat.S_IMODE(stat_backup.st_mode) or
+ stat_system.st_uid != _uid(stat_backup.st_uid) or
+ stat_system.st_gid != _gid(stat_backup.st_gid)):
+ return False
+ return True
+
+ def check_backup_for_network(self, backup_path, files):
+ """
+ Проверить конфигурационные файлы настройки сети на соответствие текущим
+ :param backup_path:
+ :return:
+ """
+ backup_nm = self.is_networkmanager_backup(backup_path)
+ system_nm = self.is_networkmanager_system()
+ # проверить совпадает ли сетевой менеджер
+ if backup_nm != system_nm:
+ if backup_nm and not any(
+ getInstalledAtom("net-misc/networkmanager")):
+ return True
+ return False
+ # если nm проверить совпадение system-connections
+ if backup_nm:
+ connection_files = set(path.basename(x) for x in chain(
+ glob.glob("%s/*" % self.nm_connections),
+ glob.glob("%s/*" % (pathJoin(backup_path, "root",
+ self.nm_connections)))
+ ))
+ for fn in connection_files:
+ system_fn = pathJoin(self.nm_connections, fn)
+ backup_fn = pathJoin(backup_path, "root",
+ self.nm_connections, fn)
+ if not self.is_equal_system_backup(system_fn, backup_fn):
+ return False
+ # если openrc проверить conf.d/net и соответствие net.*
+ else:
+ system_fn = "/etc/conf.d/net"
+ backup_fn = pathJoin(backup_path, "root", system_fn)
+ if not self.is_equal_system_backup(system_fn, backup_fn):
+ return False
+ system_net_set = (set(path.basename(x)
+ for x in glob.glob('/etc/init.d/net.*')) -
+ {"net.lo"})
+ backup_net_set = set(path.basename(x) for x in files)
+ if system_net_set != backup_net_set:
+ return False
+ return True
+
+ def restore_network(self, backup_path):
+ """
+ Восстановить сеть из backup
+ :param backup_path:
+ :return:
+ """
+ files = ["/etc/init.d/%s" % x.strip()
+ for x in self.load_ini("backup", "init").split(',')
+ if x.startswith("net.") and x != "net.lo"]
+ if self.check_backup_for_network(backup_path, files):
+ self.endTask("skip")
+ return True
+ self.stop_net_services()
+ self.unlink_openrc_net_services(files)
+ self.clear_nm_connections(backup_path)
+ self.restore_files(backup_path, ["/etc/conf.d/hostname",
+ "/etc/resolv.conf",
+ "/etc/hosts"])
+ self._do_service("/etc/init.d/hostname", "restart")
+ if self.is_networkmanager_backup(backup_path):
+ self.unlink_openrc_net_services([])
+ self.restore_files(backup_path, [
+ "/etc/NetworkManager/system-connections/*",
+ "/etc/NetworkManager/dispatcher.d/*",
+ ])
+ self._do_service("/etc/init.d/NetworkManager", "start")
+ else:
+ self.restore_files(backup_path, ["/etc/conf.d/net"])
+ self.restore_openrc_net_initd(files)
+ self._do_service("/etc/init.d/netmount", "start")
+ return True
+
+ def special_backup(self, backup_path):
+ """
+ Выполнить специализирование резервное копирование модулей
+ :param backup_path:
+ :return:
+ """
+ for backup_obj in self.iterate_modules():
+ backup_obj.backup(backup_path)
+ return True
+
+ def special_restore(self, backup_path):
+ """
+ Выполнить специализирование восстановление из резервной копии
+ :param backup_path:
+ :return:
+ """
+ for backup_obj in self.iterate_modules():
+ backup_obj.restore(backup_path)
+ return True
+
+ def iterate_modules(self):
+ """
+ Перебрать все модули backup
+ :return:
+ """
+ site_packages = [path.join(x, "calculate")
+ for x in sys.path
+ if (x.endswith('site-packages') and
+ x.startswith('/usr/lib'))]
+ ret_list = []
+ for module, modDir in chain(
+ *map(lambda x: map(lambda y: (path.basename(y), y),
+ listDirectory(x, True, True)),
+ site_packages)):
+ if path.exists(path.join(modDir, "backup_%s.py" % module)):
+ if not "calculate-%s" % module in ret_list:
+ ret_list.append("calculate-%s" % module)
+ cl_backup = ret_list
+ for pack in cl_backup:
+ if pack:
+ module_name = '%s.backup_%s' % (pack.replace("-", "."),
+ pack.rpartition("-")[2])
+ import importlib
+
+ try:
+ backup_module = importlib.import_module(module_name)
+ backup_obj = backup_module.Backup(self, self.clVars)
+ yield backup_obj
+ except ImportError:
+ sys.stderr.write(_("Unable to import %s") % module_name)
+
+ def display_changed_configs(self):
+ """
+ Отобразить список восстановленных файлов
+ :return:
+ """
+ t = Template(self.clVars, printWARNING=self.printWARNING,
+ printERROR=self.printERROR, printSUCCESS=self.printSUCCESS)
+ t.verboseOutput(sorted(list(self.apply_files | self.unlink_autorun)))
+ return True
+
+ def display_backup_configs(self, archfile):
+ """
+ Отобразить список помещённых в резервную копию файлов
+ :return:
+ """
+ with tarfile.open(archfile, 'r:bz2') as f:
+ self.printWARNING(_("Calculate Utilities have backuped files")
+ + _(":"))
+ for fn in sorted("/%s" % x.path.partition('/')[2] for x in
+ f.getmembers() if (not x.isdir() and (
+ x.path.startswith("root") or
+ x.path.startswith("ldap")))):
+ self.printSUCCESS(" " * 5 + fn)
+ return True
+
+ def run_openrc(self, command):
+ p = process("/sbin/openrc", "default")
+ p.success()
+ return True
+
+ passwd_fn = '/etc/passwd'
+ group_fn = '/etc/group'
+ shadow_fn = '/etc/shadow'
+
+ def save_accounts(self, backup_path):
+ accounts_path = path.join(backup_path, "accounts")
+ for source_fn in (self.passwd_fn, self.group_fn, self.shadow_fn):
+ self.backup_file(source_fn, accounts_path, prefix="/etc")
+ return True
+
+ def restore_accounts(self, backup_path):
+ accounts_path = path.join(backup_path, "accounts")
+ backup_passwd_fn = pathJoin(accounts_path,
+ path.basename(self.passwd_fn))
+ backup_group_fn = pathJoin(accounts_path, path.basename(self.group_fn))
+ backup_shadow_fn = pathJoin(accounts_path,
+ path.basename(self.shadow_fn))
+
+ if any(not path.exists(x) for x in (backup_passwd_fn,
+ backup_group_fn,
+ backup_shadow_fn)):
+ return "skip"
+ # пользователи
+ passwd = Passwd(readFile(self.passwd_fn))
+ backup_passwd = Passwd(readFile(backup_passwd_fn))
+ added_users = [x.name for x in passwd.new_users(backup_passwd)]
+ keep_users = [x.name for x in backup_passwd.new_users(passwd)]
+ if self.clVars.GetBool('cl_backup_verbose_set') and added_users:
+ self.printSUCCESS(
+ _("Restored users:") + " " + ", ".join(added_users))
+ self.uid_map = backup_passwd.get_uid_map(passwd)
+ passwd.join(backup_passwd)
+ with open(makeCfgName(self.passwd_fn), 'w') as f:
+ passwd.write(f)
+ os.chown(self.passwd_fn, 0, 0)
+ os.chmod(self.passwd_fn,
+ FilePermission.OtherRead |
+ FilePermission.GroupRead |
+ FilePermission.UserRead |
+ FilePermission.UserWrite)
+
+ # группы
+ groups = Group(readFile(self.group_fn))
+ backup_groups = Group(readFile(backup_group_fn))
+ added_groups = [x.name for x in groups.new_groups(backup_groups)]
+ if self.clVars.GetBool('cl_backup_verbose_set') and added_groups:
+ self.printSUCCESS(_("Restored groups:") + " "
+ + ", ".join(added_groups))
+ self.gid_map = backup_groups.get_gid_map(groups)
+ groups.join(backup_groups, keep_users=keep_users)
+ with open(makeCfgName(self.group_fn), 'w') as f:
+ groups.write(f)
+ os.chown(self.group_fn, 0, 0)
+ os.chmod(self.group_fn,
+ FilePermission.OtherRead |
+ FilePermission.GroupRead |
+ FilePermission.UserRead |
+ FilePermission.UserWrite)
+
+ # пароли
+ shadow = Shadow(readFile(self.shadow_fn))
+ backup_shadow = Shadow(readFile(backup_shadow_fn))
+ changed_shadow = [x.name
+ for x in shadow.changed_passwords(backup_shadow)]
+ if self.clVars.GetBool('cl_backup_verbose_set') and changed_shadow:
+ self.printSUCCESS(_("Restored user passwords:") + " "
+ + ", ".join(changed_shadow))
+ shadow.join(backup_shadow)
+ with open(makeCfgName(self.shadow_fn), 'w') as f:
+ shadow.write(f)
+ os.chown(self.shadow_fn, 0, 0)
+ os.chmod(self.shadow_fn,
+ FilePermission.UserRead |
+ FilePermission.UserWrite)
+ return True
diff --git a/libs_crutch/core/core_main.py b/libs_crutch/core/core_main.py
new file mode 100644
index 0000000..ffed785
--- /dev/null
+++ b/libs_crutch/core/core_main.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# Copyright 2012-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.
+# from server.cl_server import main
+# print(main)
+
+from __future__ import print_function
+from __future__ import absolute_import
+def core_main():
+ import sys
+
+ if hasattr(sys, "setdefaultencoding"):
+ sys.setdefaultencoding("utf-8")
+ from calculate.lib.cl_lang import setLocalTranslate
+
+ _ = lambda x: x
+ setLocalTranslate('cl_core', sys.modules[__name__])
+ from traceback import print_exc
+ from os import path
+
+ if not path.exists('/dev/urandom'):
+ sys.stderr.write("/dev/urandom not found\n")
+ sys.exit(1)
+
+ try:
+ from .server.cl_server import main
+
+ reload(sys)
+ from calculate.lib.datavars import CriticalError, DataVarsError
+
+ try:
+ sys.exit(main())
+ except (CriticalError, DataVarsError) as e:
+ sys.stderr.write("%s\n" % str(e))
+ sys.exit(1)
+ except ImportError as e:
+ print_exc()
+ cannot_import = 'cannot import name '
+ no_module = 'No module named '
+ if e.message.startswith(cannot_import):
+ print (_('Failed to import %s')
+ % e.message.rpartition(cannot_import)[2])
+ elif e.message.startswith(no_module):
+ print (_('No module named %s') %
+ e.message.rpartition(no_module)[2])
+ else:
+ print(e.message)
+ sys.exit(1)
+ except KeyboardInterrupt:
+ print()
+ print(_("Task interrupted"))
+
+
+if (__name__ == "__main__"):
+ core_main()
\ No newline at end of file
diff --git a/libs_crutch/core/result_viewer.py b/libs_crutch/core/result_viewer.py
new file mode 100644
index 0000000..3b994d5
--- /dev/null
+++ b/libs_crutch/core/result_viewer.py
@@ -0,0 +1,692 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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.
+
+from __future__ import print_function
+from itertools import cycle
+from calculate.lib.utils.colortext import get_terminal_print, Terminal, \
+ TextState, convert_xml_to_terminal, Print
+from calculate.lib.cl_progressbar import get_progress_bar
+import sys
+from calculate.lib.utils.files import getch, set_active_tty, get_active_tty
+from calculate.lib.utils.text import tableReport
+import threading
+from calculate.lib.utils.tools import classificate
+
+Colors = TextState.Colors
+from calculate.lib.cl_lang import setLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+
+
+class Spinner(threading.Thread):
+ def __init__(self, *args, **kwargs):
+ self.__halt = threading.Event()
+ self.__main_thread = threading.currentThread()
+ threading.Thread.__init__(self, *args, **kwargs)
+ self.start()
+
+ def run(self):
+ Terminal().cursor = False
+ try:
+ sys.stdout.write(" |")
+ for c in cycle('/-\|'):
+ sys.stdout.write('\b' + c)
+ sys.stdout.flush()
+ self.__halt.wait(0.2)
+ sys.stdout.flush()
+ if self.__halt.is_set():
+ sys.stdout.write('\b\b \b\b')
+ return
+ if not self.__main_thread.is_alive():
+ return
+ finally:
+ Terminal().cursor = True
+
+ def stop(self):
+ self.__halt.set()
+ self.join()
+
+
+class Table(tableReport):
+ def __init__(self, *args, **kwargs):
+ self.res = []
+ tableReport.__init__(self, *args, **kwargs)
+
+ def printFunc(self, s):
+ self.res.append(s)
+
+ def printTable(self):
+ self.setAutosize()
+ self.printReport(printRows=False)
+ return "".join(self.res)
+
+
+def printTable(data, header=None):
+ try:
+ if any(data):
+ return Table(None, header, data, colSpan=0).printTable()
+ else:
+ return ""
+ except Exception:
+ # print str(e)
+ raise
+
+
+def echo_on(f):
+ def wrapper(self, *args, **kw):
+ oldecho = self.parent.terminal_info.echo
+ self.parent.terminal_info.echo = True
+ try:
+ return f(self, *args, **kw)
+ finally:
+ self.parent.terminal_info.echo = oldecho
+
+ return wrapper
+
+
+class TaskState(object):
+ """
+ Текущее состояние вывода сообщений
+ """
+
+ def __init__(self, parent):
+ self.parent = parent
+
+ @property
+ def state(self):
+ return self.parent.task_state
+
+ def process_tags(self, s):
+ """
+ Выполнить текстовое преобразование
+ """
+ s = s or ""
+ return convert_xml_to_terminal(s).replace(" ", " ")
+
+ def display_asterisk(self, color):
+ """
+ Отобразить маркер
+ """
+ self.parent.printer(" ")
+ self.parent.printer.foreground(color).bold("*")
+ self.parent.printer(" ")
+
+ def _right_indent(self, indent, width=-1):
+ """
+ Выполнить выравнивание от правого края
+ """
+ if width > 0:
+ self.parent.printer('\r')
+ self.parent.printer.right(width - indent)
+ else:
+ self.parent.printer(" ")
+
+ def _change_asterisk(self, color, width=-1):
+ if width > 0:
+ self.parent.printer('\r')
+ self.display_asterisk(color)
+
+ def dotting(self):
+ if self.parent.spinner:
+ self.parent.spinner.stop()
+ self.parent.printer(" ...")
+ self.parent.printer.flush()
+
+ def _print_result(self, text, color):
+ width = self.parent.terminal_info.width
+ self._change_asterisk(color, width)
+ self._right_indent(len(text) + 4, width)
+ self.parent.printer.bold.foreground(TextState.Colors.BLUE)("[ ")
+ self.parent.printer.bold.foreground(color)(text)
+ self.parent.printer.bold.foreground(TextState.Colors.BLUE)(" ]")
+ self.parent.printer("\n")
+
+ def _print_ok(self):
+ self._print_result("ok", TextState.Colors.GREEN)
+
+ def _print_failed(self):
+ self._print_result("!!", TextState.Colors.RED)
+
+ def _print_skip(self):
+ self._print_result("skip", TextState.Colors.YELLOW)
+
+ def display_result(self, result):
+ func_map = {"skip": self._print_skip,
+ False: self._print_failed}
+ func_map.get(result, self._print_ok)()
+ self.parent.printer.flush()
+
+ def startTask(self, message, progress, num):
+ pass
+
+ def endTask(self, result, progress_message=None):
+ pass
+
+ def breakTask(self):
+ pass
+
+ def printMessage(self, color, message):
+ for i, line in classificate(self.process_tags(message).split('\n')):
+ self.display_asterisk(color)
+ self.parent.printer(line)
+ if not i.last:
+ self.parent.printer('\n')
+ try:
+ self.parent.printer.flush()
+ except IOError:
+ pass
+
+ def printERROR(self, message):
+ self.printMessage(Colors.RED, message)
+
+ def printSUCCESS(self, message):
+ self.printMessage(Colors.GREEN, message)
+
+ def printWARNING(self, message):
+ self.printMessage(Colors.YELLOW, message)
+
+ def startGroup(self, message):
+ self.parent.printer.foreground(Colors.WHITE)(self.process_tags(message))
+ self.parent.printer('\n')
+
+ def endGroup(self):
+ pass
+
+ def beginFrame(self, message):
+ self.parent.terminal_info.echo = False
+
+ def endFrame(self):
+ self.parent.terminal_info.echo = True
+
+ def addProgress(self, message):
+ pass
+
+ def setProgress(self, percent, short_message, long_message):
+ pass
+
+ @echo_on
+ def askConfirm(self, message, default):
+ self.parent.printer("\n")
+ while True:
+ try:
+ _print = Print(output=self.parent.printer.output)
+ if default in "yes":
+ yes_color, no_color = Colors.GREEN, Colors.LIGHT_RED
+ else:
+ yes_color, no_color = Colors.LIGHT_RED, Colors.GREEN
+ yes = _print.foreground(yes_color)("Yes")
+ no = _print.foreground(no_color)("No")
+ white_message = _print.foreground(Colors.WHITE)(message)
+ ask = raw_input(white_message + ' (%s/%s): ' % (yes, no))
+ except (EOFError, KeyboardInterrupt):
+ ask = 'no'
+ print()
+ if ask.lower() in ['n', 'no']:
+ return "no"
+ if ask.lower() in ['y', 'yes']:
+ return "yes"
+ if ask == '':
+ return default
+
+ def printPre(self, message):
+ self.parent.printer(self.process_tags(message))
+ self.parent.printer('\n')
+
+ def printDefault(self, message):
+ self.parent.printer(self.process_tags(message))
+ self.parent.printer('\n')
+
+ @echo_on
+ def askChoice(self, message, answers):
+ self.parent.printer("\n")
+ Colors = TextState.Colors
+ printer = self.parent.printer
+ _print = Print(output=printer.output)
+ # ability answer by first letter
+ firstletter = 0
+ i_value, i_comment = 0, 1
+ answerByChar = map(lambda x: x[i_value][firstletter], answers)
+
+ if filter(lambda x: answerByChar.count(x) > 1, answerByChar):
+ use_getch = False
+ sa = slice(0, None)
+ else:
+ use_getch = True
+ sa = slice(1)
+ message = _print.foreground(Colors.WHITE)(message)
+ full_message = (message +
+ ' (%s): ' % ("/".join(map(
+ lambda x: "%s[%s]" % (x[i_comment], x[i_value][sa]),
+ answers))))
+ while True:
+ CTRC_C = chr(3)
+ if use_getch:
+ printer(full_message)
+ ask = getch()
+ printer("\n")
+ if ask in (CTRC_C, ""):
+ raise KeyboardInterrupt
+ else:
+ try:
+ ask = raw_input(full_message)
+ except (EOFError, KeyboardInterrupt):
+ printer("\n")
+ raise KeyboardInterrupt
+ ask = ask.lower()
+ like_answers = filter(lambda x: x[i_value].startswith(ask),
+ answers)
+ if not like_answers:
+ self.state.printERROR(_('The answer is uncertain'))
+ continue
+ if len(like_answers) == 1:
+ return like_answers[i_value][firstletter]
+ else:
+ self.state.printERROR(_('Ambiguous answer:') +
+ ",".join(map(lambda x: x[i_comment],
+ like_answers)))
+
+ @echo_on
+ def askQuestion(self, message):
+ self.parent.printer("\n")
+ return raw_input(message + _(":"))
+
+ def askPassword(self, message, twice):
+ from calculate.lib.utils.common import getpass
+
+ old_tty = None
+ try:
+ if self.parent.terminal_info.is_boot_console():
+ old_tty = get_active_tty()
+ set_active_tty(1)
+
+ text1 = _("%s: ") % message
+ if not twice:
+ return getpass.getpass(text1)
+ text2 = _('Repeat: ')
+ pass1 = 'password'
+ pass2 = 'repeat'
+ try:
+ while pass1 != pass2:
+ pass1 = getpass.getpass(text1)
+ pass2 = getpass.getpass(text2)
+ if pass1 != pass2:
+ self.state.printERROR(_('Passwords do not match'))
+ except KeyboardInterrupt:
+ return None
+ passwd = pass1 if (pass1 and pass1 == pass2) else None
+ return passwd
+ finally:
+ if old_tty and old_tty.isdigit():
+ set_active_tty(int(old_tty))
+
+ def printTable(self, table_name, head, body):
+ self.state.printSUCCESS(message=table_name)
+ self.parent.printer(printTable(body, head))
+
+
+class CleanState(TaskState):
+ """
+ Ожидается вывод
+ """
+
+ def startTask(self, message, progress, num):
+ self.printMessage(Colors.GREEN, message)
+ self.parent.spinner = Spinner()
+ self.parent.set_state('start')
+ if progress:
+ self.parent.addProgress()
+
+ def printERROR(self, message):
+ super(CleanState, self).printERROR(message)
+ self.parent.printer('\n')
+
+ def printSUCCESS(self, message):
+ super(CleanState, self).printSUCCESS(message)
+ self.parent.printer('\n')
+
+ def printWARNING(self, message):
+ super(CleanState, self).printWARNING(message)
+ self.parent.printer('\n')
+
+
+class CleanStateNoProgress(CleanState):
+ """
+ ... без отображения прогрессов
+ """
+
+ def startTask(self, message, progress, num):
+ self.display_asterisk(Colors.GREEN)
+ self.parent.printer(message)
+ self.dotting()
+ self.parent.set_state('start')
+
+
+class StartState(TaskState):
+ """
+ Выполняется задача (отображается spinner)
+ """
+
+ def startTask(self, message, progress, num):
+ self.parent.endTask(True)
+ self.parent.startTask(message, progress, num)
+
+ def endTask(self, result, progress_message=None):
+ self.dotting()
+ self.parent.set_state('clean')
+ self.display_result(result)
+
+ def breakTask(self):
+ self.dotting()
+ self.parent.set_state('clean')
+ self.parent.printer('\n')
+
+ def printERROR(self, message):
+ self.dotting()
+ self.parent.printer('\n')
+ self.parent.set_state('clean')
+ self.state.printERROR(message)
+
+ def printSUCCESS(self, message):
+ self.dotting()
+ self.parent.set_state('breaked')
+ self.state.printSUCCESS(message)
+
+ def printWARNING(self, message):
+ self.dotting()
+ self.parent.set_state('breaked')
+ self.state.printWARNING(message)
+
+ def startGroup(self, message):
+ self.state.endTask(True)
+ self.state.startGroup(message)
+
+ def endGroup(self):
+ self.state.endTask(True)
+ self.state.endGroup()
+
+ def beginFrame(self, message):
+ self.state.endTask(True)
+ self.state.beginFrame(message)
+
+ def endFrame(self):
+ self.state.endTask(True)
+ self.state.endFrame()
+
+ def addProgress(self, message):
+ self.parent.set_state("pre-progress")
+ self.state.addProgress(message)
+
+ def printPre(self, message):
+ self.parent.endTask(True)
+ self.state.printPre(message)
+
+ def printDefault(self, message):
+ self.state.endTask(True)
+ self.state.printDefault(message)
+
+ def askChoice(self, message, answers):
+ self.breakTask()
+ return self.state.askChoice(message, answers)
+
+ def askQuestion(self, message):
+ self.breakTask()
+ return self.state.askQuestion(message)
+
+ def askPassword(self, message, twice):
+ self.breakTask()
+ return self.state.askPassword(message, twice)
+
+ def askConfirm(self, message, default):
+ self.breakTask()
+ return self.state.askConfirm(message, default)
+
+ def printTable(self, table_name, head, body):
+ self.breakTask()
+ self.state.printTable(table_name, head, body)
+
+
+class StartStateNoProgress(StartState):
+ """
+ ... без прогресса
+ """
+
+ def startTask(self, message, progress, num):
+ self.parent.endTask(True)
+ self.parent.startTask(message, progress, num)
+
+ def endTask(self, result, progress_message=None):
+ self.parent.set_state('clean')
+ self.display_result(result)
+
+ def breakTask(self):
+ self.parent.printer('\n')
+
+ def printERROR(self, message):
+ self.breakTask()
+ self.parent.set_state('clean')
+ self.state.printERROR(message)
+
+ def printSUCCESS(self, message):
+ self.breakTask()
+ self.parent.set_state('clean')
+ self.state.printSUCCESS(message)
+
+ def printWARNING(self, message):
+ self.breakTask()
+ self.parent.set_state('clean')
+ self.state.printWARNING(message)
+
+ def addProgress(self, message):
+ pass
+
+
+class BreakedState(StartState):
+ """
+ Во время выполнения задачи выведено сообщение
+ """
+
+ def stop_spinner_newline(self):
+ self.parent.spinner.stop()
+ self.parent.printer('\n')
+
+ def startTask(self, message, progress, num):
+ self.state.endTask(True)
+ self.state.startTask(message, progress, num)
+
+ def breakTask(self):
+ self.stop_spinner_newline()
+ self.parent.set_state('clean')
+
+ def endTask(self, result, progress_message=None):
+ self.breakTask()
+
+ def printERROR(self, message):
+ self.parent.endTask(True)
+ self.state.printERROR(message)
+
+ def printSUCCESS(self, message):
+ self.stop_spinner_newline()
+ TaskState.printSUCCESS(self, message)
+ self.parent.spinner = Spinner()
+
+ def printWARNING(self, message):
+ self.stop_spinner_newline()
+ TaskState.printWARNING(self, message)
+ self.parent.spinner = Spinner()
+
+
+class PreProgressState(StartState):
+ """
+ Задача запрошена как с прогрессом но проценты еще не обрабатывались
+ """
+
+ def addProgress(self, message):
+ pass
+
+ def setProgress(self, percent, short_message, long_message):
+ self.parent.set_state("progress")
+ self.dotting()
+ self.parent.printer("\n")
+ self.parent.add_progressbar()
+ self.parent.terminal_info.cursor = False
+ self.state.setProgress(percent, short_message, long_message)
+
+
+class ProgressState(StartState):
+ """
+ Отображается progressbar
+ """
+
+ def finish_and_clean(self):
+ self.parent.printer('\r')
+ self.parent.printer.flush()
+ self.parent.progress.finish()
+ self.parent.terminal_info.cursor = True
+ self.parent.set_progressbar(None)
+ self.parent.printer.up(1).clear_line("")
+ self.parent.printer.up(1)("")
+
+ def setProgress(self, percent, short_message, long_message):
+ if not 0 <= percent <= 100:
+ self.breakTask()
+ else:
+ self.parent.progress.update(percent)
+
+ def breakTask(self):
+ self.finish_and_clean()
+ self.parent.set_state('clean')
+ self.parent.printer('\n')
+
+ def endTask(self, result, progress_message=None):
+ self.finish_and_clean()
+ self.parent.set_state('clean')
+ self.display_result(result)
+
+ def printERROR(self, message):
+ self.finish_and_clean()
+ self.parent.printer.down(1)("")
+ self.parent.set_state('clean')
+ self.state.printERROR(message)
+
+ def printSUCCESS(self, message):
+ self.finish_and_clean()
+ self.parent.set_state('breaked')
+ self.state.printSUCCESS(message)
+
+ def printWARNING(self, message):
+ self.finish_and_clean()
+ self.parent.set_state('breaked')
+ self.state.printWARNING(message)
+
+
+class ResultViewer(object):
+ """
+ Просмотрщик результатов
+ """
+
+ def __init__(self):
+ self.printer = \
+ get_terminal_print(sys.stdout)
+ self.terminal_info = Terminal()
+ self.states = {'clean': CleanState(self),
+ 'breaked': BreakedState(self),
+ 'pre-progress': PreProgressState(self),
+ 'progress': ProgressState(self),
+ 'start': StartState(self)}
+ self.task_state = self.states['clean']
+ self.spinner = None
+ self.progress = None
+ self.no_questions = False
+
+ def set_no_progress(self):
+ self.states = {'clean': CleanStateNoProgress(self),
+ 'start': StartStateNoProgress(self)}
+ self.set_state('clean')
+
+ def set_no_questions(self):
+ self.no_questions = True
+
+ def set_state(self, state):
+ self.task_state = self.states[state]
+
+ def add_progressbar(self):
+ self.set_progressbar(get_progress_bar())
+
+ def set_progressbar(self, pb):
+ self.progress = pb
+
+ def endTask(self, result=None, progress_message=None):
+ self.task_state.endTask(result, progress_message)
+
+ def startTask(self, message, progress=False, num=1):
+ self.task_state.startTask(message, progress, num)
+
+ def printERROR(self, message, onlyShow=None):
+ if onlyShow != 'gui':
+ self.task_state.printERROR(message)
+
+ def printSUCCESS(self, message, onlyShow=None):
+ if onlyShow != 'gui':
+ self.task_state.printSUCCESS(message)
+
+ def printWARNING(self, message, onlyShow=None):
+ if onlyShow != 'gui':
+ self.task_state.printWARNING(message)
+
+ def startGroup(self, message):
+ self.task_state.startGroup(message)
+
+ def endGroup(self):
+ self.task_state.endGroup()
+
+ def beginFrame(self, message=None):
+ self.task_state.beginFrame(message)
+
+ def endFrame(self):
+ self.task_state.endFrame()
+
+ def addProgress(self, message=None):
+ self.task_state.addProgress(message)
+
+ def setProgress(self, percent, short_message=None, long_message=None):
+ self.task_state.setProgress(percent, short_message, long_message)
+
+ def printPre(self, message, onlyShow=None):
+ if onlyShow != 'gui':
+ self.task_state.printPre(message)
+
+ def printDefault(self, message='', onlyShow=None):
+ if onlyShow != 'gui':
+ self.task_state.printDefault(message)
+
+ def askConfirm(self, message, default="yes"):
+ if self.no_questions:
+ return default
+ return self.task_state.askConfirm(message, default)
+
+ def askChoice(self, message, answers=(("yes", "Yes"), ("no", "No"))):
+ return self.task_state.askChoice(message, answers)
+
+ def askPassword(self, message, twice=False):
+ return self.task_state.askPassword(message, twice)
+
+ def askQuestion(self, message):
+ return self.task_state.askQuestion(message)
+
+ def printTable(self, table_name, head, body, fields=None,
+ onClick=None, addAction=None, step=None, records=None):
+ self.task_state.printTable(table_name, head, body)
diff --git a/libs_crutch/core/result_viewer_gui.py b/libs_crutch/core/result_viewer_gui.py
new file mode 100644
index 0000000..e88067c
--- /dev/null
+++ b/libs_crutch/core/result_viewer_gui.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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.
+
+from __future__ import absolute_import
+from calculate.lib.cl_progressbar import get_progress_bar, get_message_box
+from calculate.lib.utils.colortext import TextState
+import sys
+from .result_viewer import PreProgressState, ProgressState
+
+Colors = TextState.Colors
+from calculate.lib.cl_lang import setLocalTranslate
+
+setLocalTranslate('cl_core3', sys.modules[__name__])
+
+
+class PreProgressStateGui(PreProgressState):
+ """
+ Задача запрошена как с прогрессом но проценты еще не обрабатывались
+ """
+
+ def addProgress(self, message):
+ self.dotting()
+ self.parent.printer("\n")
+ self.parent.add_progressbar()
+ self.parent.set_state("progress")
+
+
+class ProgressStateGui(ProgressState):
+ """
+ Отображение для gui прогресса
+ """
+
+ def finish_and_clean(self):
+ self.parent.progress.finish()
+ self.parent.printer.up(1)("")
+ self.parent.set_progressbar(None)
+
+
+class ResultViewerDecorator(object):
+ def __init__(self, rv):
+ self.rv = rv
+ for v in self.rv.states.values():
+ v.parent = self
+
+ def __getattr__(self, item):
+ return getattr(self.rv, item)
+
+
+class ProgressGui(ResultViewerDecorator):
+ """
+ Отображение прогресса в Qt диалогах
+ """
+
+ def __init__(self, rv):
+ super(ProgressGui, self).__init__(rv)
+ self.rv.states['pre-progress'] = PreProgressStateGui(self)
+ self.rv.states['progress'] = ProgressStateGui(self)
+ self.progress_title = ""
+
+ def add_progressbar(self):
+ self.set_progressbar(get_progress_bar("gui", self.progress_title))
+
+ def startTask(self, message, progress=False, num=1):
+ self.rv.startTask(message, progress, num)
+ self.progress_title = message
+
+
+class ErrorGui(ResultViewerDecorator):
+ """
+ Отображение ошибок через gui
+ """
+
+ def __init__(self, rv):
+ super(ErrorGui, self).__init__(rv)
+ self.messages = []
+
+ def show_messages(self):
+ get_message_box().critical("\n".join(self.messages).decode('utf-8'))
+
+ def printERROR(self, message, onlyShow=None):
+ self.rv.printERROR(message, onlyShow)
+ if onlyShow != 'gui':
+ if message:
+ self.messages.append(message)
+
+ def endFrame(self):
+ self.rv.task_state.endFrame()
+ if self.messages:
+ self.show_messages()
+
+
+class WarningGui(ResultViewerDecorator):
+ """
+ Отображение предупреждений через gui
+ """
+
+ def __init__(self, rv):
+ super(WarningGui, self).__init__(rv)
+ self.warnings = []
+
+ def show_messages(self):
+ get_message_box().warning("\n".join(self.warnings).decode('utf-8'))
+
+ def printWARNING(self, message, onlyShow=None):
+ self.rv.printWARNING(message, onlyShow)
+ if onlyShow != 'gui':
+ if message:
+ self.warnings.append(message)
+
+ def endFrame(self):
+ self.rv.task_state.endFrame()
+ if not self.messages and self.warnings:
+ self.show_messages()
+ elif self.messages:
+ self.messages.extend(self.warnings)
+ self.rv.show_messages()
diff --git a/libs_crutch/core/set_vars.py b/libs_crutch/core/set_vars.py
new file mode 100644
index 0000000..b2ccbe9
--- /dev/null
+++ b/libs_crutch/core/set_vars.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+import re
+from calculate.core.server.core_interfaces import MethodsInterface
+
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+VARIABLE, MODE, LOCATION, VALUE = 0, 1, 2, 3
+
+
+class Variables(MethodsInterface):
+ def writeVariables(self, vardata):
+ """
+ Write variable to env files, or delete from env files
+ """
+ dv = self.clVars
+ data = filter(lambda x: x[LOCATION] or
+ not x[LOCATION] and
+ dv.isFromIni(x[VARIABLE]),
+ vardata)
+ if data:
+ head = [_("Variable"), _("Mode"), _("Location"), _("Value")]
+ self.printTable(_("List of variables"), head, data)
+ for varname, mode, location, value in data:
+ if location:
+ value = dv.unserialize(dv.getInfo(varname).type, str(value))
+ section, op, varname = varname.rpartition('.')
+ if not location:
+ for env_location in dv.Get('main.cl_env_location'):
+ if not dv.Delete(varname, env_location, header=section):
+ self.printWARNING(
+ _("Failed to delete variable {var} from "
+ "{location}").format(
+ var=varname, location=env_location))
+ else:
+ if varname in dv.iniCache:
+ oldValue = dv.unserialize(
+ dv.getInfo(varname).type,
+ str(dv.iniCache[varname]['value']))
+ else:
+ oldValue = None
+ if value != oldValue:
+ dv.Write(varname, value, location=location,
+ header=section)
+ else:
+ self.printSUCCESS("Nothing to set")
+ return True
+
+ def showVariables(self, showVal, filterVal, vardata):
+ """
+ Show variables by cl_variable_filter
+ """
+ dv = self.clVars
+ removeQuotes = lambda x: x if x != "''" else ""
+ reIndex = re.compile("((?:\w+\.)?(\w+))(?:\[(\d+)\])")
+ if showVal:
+ index = reIndex.search(showVal)
+ if index:
+ varname = index.group(1)
+ index = int(index.group(3))
+ else:
+ varname = showVal
+ prevVal = str(dv.Select('cl_variable_value',
+ where='cl_variable_fullname',
+ eq=varname, limit=1))
+ if index is not None:
+ typeVar = dv.getInfo(varname).type
+ val = dv.unserialize(typeVar, prevVal)
+ if index < len(val):
+ self.printDefault(removeQuotes(val[index]))
+ else:
+ self.printDefault("")
+ else:
+ self.printDefault(removeQuotes(prevVal))
+ return True
+ filter_names = {'all': None,
+ 'userset': lambda x: x[LOCATION],
+ 'writable': lambda x: x[MODE].startswith("w"),
+ 'system': lambda x: x[LOCATION] == "system",
+ 'local': lambda x: x[LOCATION] == "local",
+ 'remote': lambda x: x[LOCATION] == "remote"}
+ filterFunc = filter_names.get(filterVal,
+ lambda x: filterVal in x[VARIABLE])
+ body = filter(filterFunc, vardata)
+ dv.close()
+ if body:
+ head = [_("Variable"), _("Mode"),
+ _("Location"), _("Value")]
+ self.printTable(_("List of variables"), head, body)
+ return True
+ else:
+ self.printWARNING(_("No such variables"))
+ return True
diff --git a/libs_crutch/core/setup_cache.py b/libs_crutch/core/setup_cache.py
new file mode 100644
index 0000000..6b95a64
--- /dev/null
+++ b/libs_crutch/core/setup_cache.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2015-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 re
+import os
+from os import path
+from itertools import chain, groupby
+import time
+
+from calculate.lib.datavars import DataVars
+from calculate.lib.cl_template import templateFunction
+from calculate.lib.utils.files import readFile, writeFile
+
+get_pkgname_by_filename = templateFunction.get_pkgname_by_filename
+
+
+class Cache(object):
+ reMerge = re.compile("(merge|mergepkg)\(([-\w/]*)(?:\[[^\]]\])?\)[-!=<>]")
+ rePatch = re.compile("^#\s*Calculate.*ac_install_patch==on")
+
+ PATCH_TYPE = "patch"
+ MERGE_TYPE = "merge"
+ DIRECTORY_TEMPLATE = ".calculate_directory"
+ CLT_SUFFIX = ".clt"
+
+ def __init__(self, dv=None):
+ if dv is None:
+ dv = DataVars()
+ dv.importData()
+ dv.flIniFile()
+ self.dv = dv
+ self.base_dn = "/var/lib/calculate/calculate-core/cache"
+ self.fn_mtime = path.join(self.base_dn, "merge.mtime")
+ self.fn_patch = path.join(self.base_dn, "merge-patch.list")
+ self.fn_setup = path.join(self.base_dn, "merge-setup.list")
+
+ def search_merge(self, dn):
+ """
+ Сканировать директорию с шаблонами
+ """
+ patch_dirs = []
+ for root, dirs, files in os.walk(dn):
+ for fn in (path.join(root, x) for x in files):
+ data = readFile(fn)
+ if self.rePatch.search(data):
+ if path.basename(fn) == self.DIRECTORY_TEMPLATE:
+ patch_dirs.append(path.dirname(fn))
+ patch_template = True
+ else:
+ if any(fn.startswith(x) for x in patch_dirs):
+ patch_template = True
+ else:
+ patch_template = False
+ for fname, pkg in self.reMerge.findall(data):
+ pkg = pkg or get_pkgname_by_filename(fn)
+ yield (self.PATCH_TYPE if patch_template
+ else self.MERGE_TYPE, pkg)
+
+ def search_merge_clt(self, dn):
+ """
+ Сканировать clt шаблоны
+ """
+ for root, dirs, files in os.walk(dn):
+ for fn in (path.join(root, x) for x in files
+ if x.endswith(self.CLT_SUFFIX)):
+ data = readFile(fn)
+ for fname, pkg in self.reMerge.findall(data):
+ pkg = pkg or get_pkgname_by_filename(fn)
+ yield (self.MERGE_TYPE, pkg)
+
+ @staticmethod
+ def check_new_that(mtime_fn, dirs, fn_filter=None):
+ """
+ Проверить появились ли новые файлы после последней проверки
+ """
+ if not path.exists(mtime_fn):
+ return True
+ check_mtime = os.stat(mtime_fn).st_mtime
+ for dn in dirs:
+ for root, dirs, files in os.walk(dn):
+ for fn in (path.join(root, x) for x in files
+ if fn_filter is None or fn_filter(x)):
+ if os.stat(fn).st_mtime > check_mtime:
+ return True
+ return False
+
+ def update(self, force=False):
+ template_path = self.dv.Get('main.cl_template_path')
+ if (force or
+ self.check_new_that(
+ self.fn_mtime, ['/etc'],
+ fn_filter=lambda x: x.endswith(self.CLT_SUFFIX)) or
+ self.check_new_that(self.fn_mtime, template_path)):
+ all_packages = chain(self.search_merge_clt('/etc'),
+ *[self.search_merge(x) for x in template_path])
+ for _type, pkgs in groupby(sorted(all_packages), lambda x: x[0]):
+ list_packages = sorted(set(y for x, y in pkgs))
+ if _type == self.MERGE_TYPE:
+ with writeFile(self.fn_setup) as f:
+ f.write("\n".join(list_packages))
+ if _type == self.PATCH_TYPE:
+ with writeFile(self.fn_patch) as f:
+ f.write("\n".join(list_packages))
+ with writeFile(self.fn_mtime) as f:
+ f.write(str(time.time()))
diff --git a/libs_crutch/core/setup_package.py b/libs_crutch/core/setup_package.py
new file mode 100644
index 0000000..5a28078
--- /dev/null
+++ b/libs_crutch/core/setup_package.py
@@ -0,0 +1,414 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+import os
+from os import path
+
+from calculate.core.datavars import DataVars
+from calculate.core.server.core_interfaces import MethodsInterface
+from calculate.core.server.gen_pid import get_pid_info, ProcessMode
+from calculate.lib.datavars import Variable
+from calculate.lib.cl_log import log
+from calculate.lib.utils.common import getPasswdUsers
+from calculate.lib.utils.portage import isPkgInstalled, reVerSplitToPV
+from calculate.lib.utils.colortext import get_color_print
+import pwd
+import glob
+import calculate.lib.cl_template as cl_template
+
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+
+__ = getLazyLocalTranslate(_)
+
+
+class SetupPackageError(Exception):
+ """
+ Исключение вызванное во время настройки пакета
+ """
+
+
+class UpdateLogger(MethodsInterface):
+ """
+ Логгер для обновления настроек системы
+ """
+
+ logger = log("apply-templates",
+ filename="/var/log/calculate/update_config.log",
+ formatter="%(asctime)s - %(levelname)s - %(message)s")
+
+ def ERROR(self, *arg, **argv):
+ """
+ Вывести ошибку в лог и на экран
+ """
+ self.logger.error(arg[0])
+ self.printERROR(*arg, **argv)
+
+ def SUCCESS(self, *arg, **argv):
+ """
+ Вывести сообщение в лог и на экран
+ """
+ self.logger.info(arg[0])
+ self.printSUCCESS(*arg, **argv)
+
+ def WARNING(self, *arg, **argv):
+ """
+ Вывести предупреждение в лог и на экран
+ """
+ self.logger.warn(arg[0])
+ self.printWARNING(*arg, **argv)
+
+
+class ChainProgressTemplate(cl_template.ProgressTemplate):
+ """
+ Наложение шаблонов с определением перенастройки зависимых пакетов
+ """
+
+ def __init__(self, startTask, endTask, *args, **kwargs):
+ self.startTask = startTask
+ self.endTask = endTask
+ cl_template.ProgressTemplate.__init__(self, *args, **kwargs)
+
+ def changeMergePackage(self, packages):
+ """
+ Изменился настраиваемый пакет (по зависимостям)
+ """
+ self.endTask()
+ packages = filter(isPkgInstalled,
+ packages)
+ self.startTask(_("Configuring dependencies: %s") %
+ ",".join(packages))
+ return True
+
+
+class StubVariable(Variable):
+ """
+ Переменная-заглушка используется при обновлении настроек пакета
+ в emerge. Если переменная не найдена, то будет возвращена пустая
+ строка.
+ """
+ value = ""
+
+
+class UpdateConfigs(UpdateLogger):
+ """
+ Обновить настройки пакета в пользовательских профилях
+ """
+
+ def init(self):
+ self.color_print = get_color_print()
+
+ def getXUsers(self):
+ """
+ Получить пользователей в X сессии
+ """
+ return list(
+ self.clVars.Get('desktop.cl_desktop_online_user')) + ["root"]
+
+ def getConfiguredPasswdUsers(self):
+ """
+ Получить пользоватлей, которые есть в /etc/passwd (UID>=1000)
+ и при этом у них есть настройка профиля (.calculate/ini.env)
+ """
+ user, dn = 0, 1
+ iniEnv = ".calculate/ini.env"
+ return map(lambda x: x[user],
+ filter(lambda x: path.exists(path.join(x[dn], iniEnv)),
+ map(lambda x: (x, pwd.getpwnam(x).pw_dir),
+ getPasswdUsers())))
+
+ def _setClMergePkg(self, clVars, category, nameProgram, slot=None):
+ """
+ Установить переменную cl_merge_pkg в зависимости от category и
+ nameProgram
+ """
+ # выбрана перенастройка всех пакетов, установленных в системе
+ if nameProgram == "all":
+ clVars.Set("cl_merge_pkg",
+ map(lambda x: "{CATEGORY}/{PN}".format(**x),
+ filter(None,
+ map(reVerSplitToPV,
+ glob.glob('/var/db/pkg/*/*')))),
+ True)
+ else:
+ if slot:
+ clVars.Set("cl_merge_pkg", ["%s/%s:%s" % (category,
+ nameProgram, slot)], True)
+ else:
+ clVars.Set("cl_merge_pkg", ["%s/%s" % (category, nameProgram)],
+ True)
+ clVars.Set("cl_merge_set", "on", True)
+
+ def updateDesktopConfig(self, nameProgram, version, slot, category,
+ configPath,
+ rootSet, verbose, dispatchConf, templates_locate,
+ ebuildPhase, useClt, arch_machine):
+ """
+ Настроить пакеты в профилях пользователей
+ """
+ # настраиватся будут пользователи из активных X сессии
+ # и сконфигурированные
+ xUsers = filter(lambda x: not "(unknown)" in x,
+ self.getXUsers())
+ if not xUsers:
+ self.logger.info(_("Package %s") % nameProgram)
+ self.logger.warn(_("X session users not found"))
+ return True
+ self.logger.info(_("Package %s") % nameProgram)
+ self.logger.info(_("Updating user configuration files"))
+ firstValue = True
+
+ clVars = DataVars()
+ try:
+ clVars.importData()
+ clVars.flIniFile()
+ setupable_users = set(xUsers + self.getConfiguredPasswdUsers())
+ for userName in list(setupable_users):
+ clVars.Set("cl_root_path", '/', True)
+ clVars.Set("ur_login", userName, True)
+ clVars.Set("cl_action", "desktop", True)
+ clVars.Set("cl_verbose_set", verbose, True)
+ clVars.Set("cl_protect_use_set", "off", True)
+ clVars.Set("cl_template_path_use", templates_locate, True)
+ clVars.Set("install.os_install_arch_machine", arch_machine,
+ True)
+
+ self._setClMergePkg(clVars, category, nameProgram)
+
+ clTempl = ChainProgressTemplate(self.startTask,
+ self.endTask,
+ self.setProgress,
+ clVars, cltObj=False,
+ printSUCCESS=self.printSUCCESS,
+ printERROR=self.printERROR,
+ askConfirm=self.askConfirm,
+ printWARNING=self.printWARNING,
+ printWarning=False)
+ clTempl.onFirstValue = lambda *args: \
+ self.startTask(
+ _("User configuring the {nameProgram} package by "
+ "Calculate Utilities").format(
+ nameProgram=nameProgram))
+ clTempl.firstValue = firstValue
+ clTempl.applyTemplates()
+ firstValue = clTempl.firstValue
+ nofastlogin_users = set(getPasswdUsers()) - setupable_users
+ fastlogin_path = self.clVars.Get(
+ 'desktop.cl_desktop_fastlogin_path')
+ for user in nofastlogin_users:
+ fastlogin_user = path.join(fastlogin_path, user)
+ if path.exists(fastlogin_user):
+ try:
+ os.unlink(fastlogin_user)
+ except OSError:
+ pass
+ finally:
+ clVars.close()
+ self.endTask()
+ return True
+
+ def updateSystemConfig(self, nameProgram, version, slot, category,
+ configPath,
+ rootSet, verbose, dispatchConf, templates_locate,
+ ebuildPhase, useClt, arch_machine):
+ """
+ Обновить конфигурационные файлы системы
+ """
+ self.logger.info(_("Package %s") % nameProgram)
+ self.logger.info(_("Updating system cofiguration files"))
+ if not os.path.exists(configPath):
+ self.ERROR(_("Path '%s' does not exist") % configPath)
+ return False
+
+ clVars = DataVars()
+ try:
+ clVars.importData()
+ clVars.flIniFile()
+ clVars.Set("cl_root_path", configPath, True)
+
+ # если конфигурирование пакета происходит не в корне
+ if rootSet:
+ # остальные пакеты настраиваются в корень
+ clVars.Set("cl_root_path_next", '/', True)
+
+ if self.clVars.Get('core.cl_core_pkg_slot_opt') != "all":
+ self._setClMergePkg(clVars, category, nameProgram, slot)
+ else:
+ self._setClMergePkg(clVars, category, nameProgram)
+ clVars.Set("cl_action", 'merge', True)
+ clVars.Set("cl_verbose_set", verbose, True)
+ clVars.Set("cl_dispatch_conf", dispatchConf, True)
+ clVars.Set("cl_template_path_use", templates_locate, True)
+ clVars.Set("core.cl_core_pkg_slot", slot, True)
+ clVars.Set("install.os_install_arch_machine", arch_machine, True)
+ useClt = useClt in (True, "on")
+
+ dictVer = {slot: version}
+ cl_template.templateFunction.installProg.update(
+ {"%s/%s" % (category, nameProgram): dictVer,
+ "%s" % nameProgram: dictVer})
+
+ # используем объект шаблонов
+ # с clt шаблонами, clt фильтром, без использования postDispatchConf
+ clTempl = ChainProgressTemplate(
+ self.startTask,
+ self.endTask,
+ self.setProgress,
+ clVars, cltObj=useClt,
+ cltFilter=True,
+ printSUCCESS=self.printSUCCESS,
+ printERROR=self.printERROR,
+ printWARNING=self.printWARNING,
+ askConfirm=self.askConfirm,
+ dispatchConf=self.dispatchConf
+ if not ebuildPhase and self.isInteractive() else None,
+ printWarning=False)
+ # выводим сообщение о настройке пакета только если действительно
+ # менялись файлы
+ clTempl.onFirstValue = lambda *args: \
+ self.startTask(_("System configuring for {nameProgram} "
+ "package by Calculate Utilities").format(
+ nameProgram=nameProgram))
+ clTempl.applyTemplates()
+ finally:
+ clVars.close()
+ self.endTask()
+ return True
+
+ def patchPackage(self, configPath, nameProgram, arch_machine):
+ """
+ Наложить патчи на пакет
+ """
+ self.clVars.Set("cl_root_path", configPath, True)
+ self.clVars.Set("install.os_install_arch_machine", arch_machine, True)
+ clTempl = ChainProgressTemplate(self.startTask,
+ self.endTask,
+ self.setProgress,
+ self.clVars, cltObj=False,
+ printSUCCESS=self.printSUCCESS,
+ printERROR=self.printERROR,
+ askConfirm=self.askConfirm,
+ printWARNING=self.printWARNING,
+ printWarning=False)
+ clTempl.onFirstValue = lambda *args: self.startTask(
+ _("Using patches for the {nameProgram} package by "
+ "Calculate Utilities").format(
+ nameProgram=nameProgram),
+ progress=True)
+ clTempl.applyTemplates()
+ return True
+
+ def checkRunning(self):
+ """
+ Проверить наличие запущенных процессов в cl-core
+ """
+ from calculate.core.server.loaded_methods import LoadedMethods
+
+ cur_pid = os.getpid()
+ pid_list = [pid for pid in get_pid_info(self.clVars)
+ if (pid.get("mode", '') == ProcessMode.CoreDaemon and
+ pid.get("os_pid", '') != cur_pid)]
+ if pid_list:
+ _print = self.color_print
+ method_names = {value[0]: value[2] for key, value in
+ LoadedMethods.conMethods.items()}
+ self.printSUCCESS(
+ _("Calculate core is executing the following tasks"))
+ mult = _print.bold("*")
+ for pid in pid_list:
+ name = pid['name']
+ method_name = method_names.get(name, name)
+ self.printDefault(
+ " {mult} {title} ({name})".format(mult=mult,
+ title=method_name,
+ name=name))
+ answer = self.askConfirm(
+ _("Would you like to terminate these tasks?"), "no")
+ if answer == "no":
+ raise KeyboardInterrupt
+ return True
+
+ def restartService(self, service_name):
+ """
+ Перезапустить указанный сервис
+ """
+ import time
+
+ time.sleep(1)
+ os.system('/etc/init.d/%s restart &>/dev/null &' % service_name)
+ return True
+
+ def processConfig(self, nameProgram, version, slot, category,
+ verbose, dispatchConf, templates_locate,
+ ebuildPhase, useClt, arch_machine):
+ """
+ Обновить конфигурационные файлы системы
+ """
+ self.logger.info(_("Package %s") % nameProgram)
+ self.logger.info(_("Updating system cofiguration files"))
+
+ clVars = DataVars()
+ try:
+ clVars.importData()
+ clVars.flIniFile()
+ clVars.Set("cl_root_path", "/", True)
+
+ if self.clVars.Get('core.cl_core_pkg_slot_opt') != "all":
+ self._setClMergePkg(clVars, category, nameProgram, slot)
+ else:
+ self._setClMergePkg(clVars, category, nameProgram)
+ clVars.Set("cl_action", 'config', True)
+ clVars.Set("cl_verbose_set", verbose, True)
+ clVars.Set("cl_dispatch_conf", dispatchConf, True)
+ clVars.Set("cl_template_path_use", templates_locate, True)
+ clVars.Set("core.cl_core_pkg_slot", slot, True)
+ useClt = False
+
+ #dictVer = {slot: version}
+ #cl_template.templateFunction.installProg.update(
+ # {"%s/%s" % (category, nameProgram): dictVer,
+ # "%s" % nameProgram: dictVer})
+
+ # используем объект шаблонов
+ # с clt шаблонами, clt фильтром, без использования postDispatchConf
+ clTempl = ChainProgressTemplate(
+ self.startTask,
+ self.endTask,
+ self.setProgress,
+ clVars, cltObj=useClt,
+ cltFilter=True,
+ printSUCCESS=self.printSUCCESS,
+ printERROR=self.printERROR,
+ printWARNING=self.printWARNING,
+ askConfirm=self.askConfirm,
+ dispatchConf=self.dispatchConf
+ if not ebuildPhase and self.isInteractive() else None,
+ printWarning=False)
+ # выводим сообщение о настройке пакета только если действительно
+ # менялись файлы
+ clTempl.onFirstValue = lambda *args: \
+ self.startTask(_("System configuring for {nameProgram} "
+ "package by Calculate Utilities").format(
+ nameProgram=nameProgram))
+ clTempl.applyTemplates()
+ finally:
+ clVars.close()
+ self.endTask()
+ return True
diff --git a/libs_crutch/core/utils/cl_backup.py b/libs_crutch/core/utils/cl_backup.py
new file mode 100644
index 0000000..e795fb6
--- /dev/null
+++ b/libs_crutch/core/utils/cl_backup.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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 sys
+from calculate.core.server.func import Action, Tasks
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+from calculate.core.backup import BackupError
+from calculate.lib.cl_template import TemplatesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClBackupAction(Action):
+ """
+ Создание резервной копии настроек
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (BackupError, FilesError, DataVarsError, VariableError,
+ TemplatesError)
+ successMessage = __("Backup successfully completed!")
+ failedMessage = __("Failed to perform backup!")
+ interruptMessage = __("Backup manually interrupted")
+
+ tasks = [
+ {'name': 'prepare_dir',
+ 'method': 'Backup.prepare_backup(core.cl_backup_path,'
+ 'core.cl_backup_root_name)'
+ },
+ {'name': 'backup_marked',
+ 'message': __("Backing up files configured by templates"),
+ 'method': 'Backup.backup_marked("/",core.cl_backup_path,'
+ '"etc",core.cl_backup_root_name)'
+ },
+ {'name': 'templates',
+ 'message': __("Templates preparing for backup"),
+ 'method': 'Backup.applyTemplates(install.cl_source,'
+ 'False,True,None,True,True)',
+ },
+ {'name': 'special_backup',
+ 'method': 'Backup.special_backup(core.cl_backup_path)'
+ },
+ {'name': 'accounts_backup',
+ 'message': __("Backing up accounts info"),
+ 'method': 'Backup.save_accounts(core.cl_backup_path)'
+ },
+ {'name': 'prepare_content',
+ 'message': __("Calculating checksums"),
+ 'method': 'Backup.prepare_contents(core.cl_backup_path,'
+ 'core.cl_backup_file_contents,core.cl_backup_root_name)',
+ },
+ {'name': 'save_initd',
+ 'method': 'Backup.save_initd(core.cl_backup_path,'
+ 'core.cl_backup_root_name)',
+ },
+ {'name': 'pack_backup',
+ 'message': __("Packing backup"),
+ 'method': 'Backup.create_archive(core.cl_backup_path,'
+ 'core.cl_backup_file)'
+ },
+ {'name': 'remove_dir',
+ 'message': __("Clearing temporary files"),
+ 'method': 'Backup.remove_directory(core.cl_backup_path)',
+ 'depend': Tasks.success_one_of("prepare_dir")
+ },
+ {'name': 'display_verbose',
+ 'method': 'Backup.display_backup_configs(core.cl_backup_file)',
+ 'condition': lambda Get: Get('core.cl_backup_verbose_set') == 'on'
+ },
+ {'name': 'display_arch',
+ 'message': __("Archive created: {core.cl_backup_file}")
+ }
+ ]
+
diff --git a/libs_crutch/core/utils/cl_backup_restore.py b/libs_crutch/core/utils/cl_backup_restore.py
new file mode 100644
index 0000000..16e62b0
--- /dev/null
+++ b/libs_crutch/core/utils/cl_backup_restore.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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 sys
+from calculate.core.server.func import Action, Tasks
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+from calculate.core.backup import BackupError
+from calculate.lib.cl_template import TemplatesError
+from tarfile import ReadError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClBackupRestoreAction(Action):
+ """
+ Восстановление настроек из резервной копии
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (BackupError, FilesError, DataVarsError, VariableError,
+ TemplatesError, ReadError)
+ successMessage = __("Files successfully restored from backup!")
+ failedMessage = __("Failed to restore from backup!")
+ interruptMessage = __("Restoration from backup manually interrupted")
+
+ restore_tasks = []
+
+ tasks = [
+ {'name': 'unpack_backup',
+ 'message': __("Unpacking backup"),
+ 'method': 'Backup.open_archive(core.cl_backup_path,'
+ 'core.cl_backup_file)'
+ },
+ {'name': 'restore_accounts',
+ 'message': __("Restoring user accounts"),
+ 'method': 'Backup.restore_accounts(core.cl_backup_path)'
+ },
+ {'name': 'restore_network',
+ 'message': __("Restoring network"),
+ 'method': 'Backup.restore_network(core.cl_backup_path)'
+ },
+ {'name': 'special_restore',
+ 'method': 'Backup.special_restore(core.cl_backup_path)'
+ },
+ {'name': 'templates',
+ 'message': __("Restoring services"),
+ 'method': 'Backup.applyTemplates(install.cl_source,'
+ 'False,True,None,True,True)',
+ },
+ {'name': 'clear_autorun',
+ 'method': 'Backup.clear_autorun()',
+ },
+ {'name': 'restore_configs',
+ 'message': __("Unpacking configuration files"),
+ 'method': 'Backup.restore_configs(core.cl_backup_file,"/",'
+ 'core.cl_backup_contents_name,core.cl_backup_root_name)'
+ },
+ {'name': 'restore_content',
+ 'message': __("Restoring file owners"),
+ 'method': 'Backup.restore_contents(core.cl_backup_file_contents,'
+ '"/")',
+ },
+ {'name': 'display_verbose',
+ 'method': 'Backup.display_changed_configs()',
+ 'condition': lambda Get: Get('core.cl_backup_verbose_set') == 'on'
+ },
+ {'name': 'set_service_mode',
+ 'method': 'Backup.set_service_action()',
+ },
+ {'name': 'templates_service',
+ 'message': __("Configuration after restoring from backup"),
+ 'method': 'Backup.applyTemplates(install.cl_source,'
+ 'False,True,None,True,True)',
+ },
+ {'name': 'remove_dir',
+ 'method': 'Backup.remove_directory(core.cl_backup_path)',
+ 'depend': Tasks.success_one_of("unpack_backup")
+ },
+ {'name': 'dispatch_conf',
+ 'message': __("Updating configuration files"),
+ 'method': 'Backup.dispatchConf()',
+ 'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
+ },
+ {'name': 'openrc_default',
+ 'message': __("Running stopped services"),
+ 'method': 'Backup.run_openrc("default")',
+ }
+ ]
diff --git a/libs_crutch/core/utils/cl_config.py b/libs_crutch/core/utils/cl_config.py
new file mode 100644
index 0000000..5150bcb
--- /dev/null
+++ b/libs_crutch/core/utils/cl_config.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+from calculate.lib.cl_template import TemplatesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClConfigAction(Action):
+ """
+ Действие настройка пакета для пользователя и системы
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, TemplatesError, VariableError, DataVarsError)
+ templateTaskMessage = __("The system is being configured")
+ successMessage = None
+ failedMessage = __("Failed to configure the system!")
+ interruptMessage = __("Configuration manually interrupted")
+
+ tasks = [
+ {'name': 'process_config',
+ # наложить шаблоны настройки пакета
+ 'method': 'UpdateConfigs.processConfig(cl_core_pkg_name,'
+ 'cl_core_pkg_version,cl_core_pkg_slot,'
+ 'cl_core_pkg_category,cl_verbose_set,cl_dispatch_conf,'
+ 'cl_template_path_use,cl_ebuild_phase,'
+ 'cl_template_clt_set,cl_core_arch_machine)',
+ },
+ ]
diff --git a/libs_crutch/core/utils/cl_core_custom.py b/libs_crutch/core/utils/cl_core_custom.py
new file mode 100644
index 0000000..6a63c50
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_custom.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2013-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.datavars import VariableError
+from calculate.lib.cl_template import TemplatesError
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreCustomAction(Action):
+ """
+ Действие для настройки параметров видео
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, TemplatesError, VariableError)
+
+ successMessage = __("Action successfully completed!")
+ failedMessage = __("Failed to perform action!")
+ interruptMessage = __("Action manually interrupted")
+
+ def __init__(self):
+ # список задач для действия
+ self.tasks = [
+ {'name': 'set_vars',
+ 'method': (
+ 'UpdateConfigs.setVariable("install.os_install_arch_machine",'
+ 'cl_core_arch_machine, True)')
+ },
+ {'name': 'apply_templates',
+ # наложить шаблоны на текущий дистрибутив, включая clt шаблоны
+ # без использования фильтров по clt шаблонам
+ 'method': 'UpdateConfigs.applyTemplates(None,False,'
+ 'None,None)',
+ },
+ {'name': 'failed_action',
+ 'error': __("Action {ac_custom_name} not found"),
+ 'condition': lambda Get: not filter(
+ lambda x: (x and x[0] == 'ac_custom_name' and
+ x[1] == Get('ac_custom_name')),
+ Get('cl_used_action'))
+ }
+ ]
+
+ Action.__init__(self)
diff --git a/libs_crutch/core/utils/cl_core_dispatch.py b/libs_crutch/core/utils/cl_core_dispatch.py
new file mode 100644
index 0000000..60e9e36
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_dispatch.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreDispatchAction(Action):
+ """
+ Действие обновление конфигурационных файлов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+
+ successMessage = __("Dispatch complete!")
+ failedMessage = __("Failed to dispatch configuration files!")
+ interruptMessage = __("Dispatching manually interrupted")
+
+ # список задач для действия
+ tasks = [
+ {'name': 'dispatch',
+ 'method': 'UpdateConfigs.dispatchConf()',
+ }]
diff --git a/libs_crutch/core/utils/cl_core_group.py b/libs_crutch/core/utils/cl_core_group.py
new file mode 100644
index 0000000..c008191
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_group.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreGroupShow(Action):
+ """
+ Отображение групп
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Viewing manually interrupted")
+
+ tasks = [
+ {'name': 'view_group',
+ 'method': 'Groups.show_groups_meth(cl_page_count,cl_page_offset)'
+ }]
+
+
+class ClCoreGroupMod(Action):
+ """
+ Изменение группы
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = __("Group {cl_core_group} changed")
+ failedMessage = __("Failed to change {cl_core_group} group")
+ interruptMessage = __("Modifying manually interrupted")
+
+ tasks = [
+ {'name': 'mod_group',
+ 'method': (
+ 'Groups.change_group_meth(cl_core_group,cl_core_group_rights,'
+ 'cl_core_group_rights_path)')
+ }]
+
+
+class ClCoreGroupAdd(Action):
+ """
+ Добавление группы
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = __("Group {cl_core_group} added")
+ failedMessage = __("Failed to add {cl_core_group} group")
+ interruptMessage = __("Adding manually interrupted")
+
+ tasks = [
+ {'name': 'add_group',
+ 'method': 'Groups.add_group_meth(cl_core_group,cl_core_group_rights,'
+ 'cl_core_group_rights_path)'
+ }]
+
+
+class ClCoreGroupDel(Action):
+ """
+ Удаление группы
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = __("Group {cl_core_group} deleted")
+ failedMessage = __("Failed to delete {cl_core_group} group")
+ interruptMessage = __("Deleting manually interrupted")
+
+ tasks = [
+ {'name': 'del_group',
+ 'method': 'Groups.del_group_meth(cl_core_group,'
+ 'cl_core_group_rights_path)'
+ }]
diff --git a/libs_crutch/core/utils/cl_core_patch.py b/libs_crutch/core/utils/cl_core_patch.py
new file mode 100644
index 0000000..2dbe807
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_patch.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+from calculate.lib.cl_template import TemplatesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCorePatchAction(Action):
+ """
+ Действие наложния патчей
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, TemplatesError, VariableError, DataVarsError)
+ templateTaskMessage = __("Appling patches to package sources")
+ successMessage = None
+ failedMessage = __("Failed to patch package sources!")
+ interruptMessage = __("Patching manually interrupted")
+
+ tasks = [
+ {'name': 'patch_package',
+ # наложить патчи для пакета
+ 'method': 'UpdateConfigs.patchPackage(cl_core_pkg_path,'
+ 'cl_core_pkg_name,cl_core_arch_machine)'
+ }
+ ]
diff --git a/libs_crutch/core/utils/cl_core_request.py b/libs_crutch/core/utils/cl_core_request.py
new file mode 100644
index 0000000..a7f7487
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_request.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreRequestShow(Action):
+ """
+ Отображение запросов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Viewing manually interrupted")
+
+ tasks = [
+ {'name': 'view_req',
+ 'method': 'Request.show_request_meth(cl_page_count,cl_page_offset)'
+ }]
+
+
+class ClCoreRequestConfirm(Action):
+ """
+ Подтверждение запроса
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = __("Certificate {cl_req_crt_path} is signed")
+ failedMessage = __("Failed to sign {cl_req_csr_path} request")
+ interruptMessage = __("Signing manually interrupted")
+
+ tasks = [
+ {'name': 'mod_req',
+ 'method': 'Request.confirm_request_meth(cl_core_client_certs_path,'
+ 'cl_core_cert_path,cl_req_csr_path,cl_req_crt_path,'
+ 'cl_req_group)'
+ }]
+
+
+class ClCoreRequestDel(Action):
+ """
+ Удаление запроса
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = None
+ failedMessage = __("Failed to delete the request with ID={cl_req_id}")
+ interruptMessage = __("Deleting manually interrupted")
+
+ tasks = [
+ {'name': 'del_req',
+ 'method': 'Request.del_request_meth(cl_core_database,cl_req_csr_path,'
+ 'cl_req_crt_path,cl_req_id)'
+ }]
diff --git a/libs_crutch/core/utils/cl_core_restart.py b/libs_crutch/core/utils/cl_core_restart.py
new file mode 100644
index 0000000..e620312
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_restart.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2013-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.datavars import VariableError
+from calculate.lib.cl_template import TemplatesError
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreRestartAction(Action):
+ """
+ Действие для настройки параметров видео
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, TemplatesError, VariableError)
+
+ successMessage = __("Action successfully completed!")
+ failedMessage = __("Failed to perform action!")
+ interruptMessage = __("Action manually interrupted")
+
+ def __init__(self):
+ # список задач для действия
+ self.tasks = [
+ {'name': 'check_running',
+ # проверить запущенные процессы
+ 'method': 'UpdateConfigs.checkRunning()',
+ },
+ {'name': 'restart',
+ # перезапустить calculate-core
+ 'message': _("Restarting calculate-core"),
+ 'method': 'UpdateConfigs.restartService("calculate-core")',
+ }]
+
+ Action.__init__(self)
diff --git a/libs_crutch/core/utils/cl_core_setup.py b/libs_crutch/core/utils/cl_core_setup.py
new file mode 100644
index 0000000..119b022
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_setup.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+from calculate.lib.cl_template import TemplatesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreSetupAction(Action):
+ """
+ Действие настройка пакета для пользователя и системы
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, TemplatesError, VariableError, DataVarsError)
+ templateTaskMessage = __("The system is being configured")
+ successMessage = None
+ failedMessage = __("Failed to configure the system!")
+ interruptMessage = __("Configuration manually interrupted")
+
+ tasks = [
+ {'name': 'system_rm_package',
+ # наложить шаблоны удаления пакета
+ 'method': 'UpdateConfigs.updateSystemConfig(cl_core_pkg_name,'
+ '"",cl_core_pkg_slot,cl_core_pkg_category,cl_core_pkg_path,'
+ 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,'
+ 'cl_template_path_use,cl_ebuild_phase,'
+ 'cl_template_clt_set,cl_core_arch_machine)',
+ 'condition': lambda dv: (
+ dv.Get('cl_core_pkg_system_set') == 'on' and
+ dv.Get('cl_ebuild_phase') in ('prerm', 'postrm'))
+ },
+ {'name': 'system_setup_package',
+ # наложить шаблоны настройки пакета
+ 'method': 'UpdateConfigs.updateSystemConfig(cl_core_pkg_name,'
+ 'cl_core_pkg_version,cl_core_pkg_slot,'
+ 'cl_core_pkg_category,cl_core_pkg_path,'
+ 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,'
+ 'cl_template_path_use,cl_ebuild_phase,'
+ 'cl_template_clt_set,cl_core_arch_machine)',
+ 'condition': lambda dv: (
+ dv.Get('cl_core_pkg_system_set') == 'on' and
+ dv.Get('cl_ebuild_phase') not in ('prerm', 'postrm'))
+ },
+ {'name': 'user_setup_package',
+ # наложить шаблоны настройки пакета
+ 'method': 'UpdateConfigs.updateDesktopConfig(cl_core_pkg_name,'
+ 'cl_core_pkg_version,cl_core_pkg_slot,'
+ 'cl_core_pkg_category,cl_core_pkg_path,'
+ 'cl_core_pkg_root_set,cl_verbose_set,cl_dispatch_conf,'
+ 'cl_template_path_use,cl_ebuild_phase,'
+ 'cl_template_clt_set,cl_core_arch_machine)',
+ 'condition': lambda dv: (
+ dv.Get('cl_core_pkg_desktop_set') == 'on' and
+ dv.isModuleInstalled('desktop') and
+ dv.Get('cl_ebuild_phase') not in ("preinst", "prerm"))
+ }
+ ]
diff --git a/libs_crutch/core/utils/cl_core_variables.py b/libs_crutch/core/utils/cl_core_variables.py
new file mode 100644
index 0000000..019fc82
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_variables.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.lib.datavars import VariableError, DataVarsError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreVariables(Action):
+ """
+ Отображение сертификатов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, DataVarsError, VariableError)
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Modification of variables manually interrupted")
+
+ tasks = [
+ {'name': 'write_vars',
+ # записать переменные
+ 'method': 'Variables.writeVariables(cl_variable_data)'
+ }
+ ]
+
+
+class ClCoreVariablesShow(Action):
+ """
+ Отображение сертификатов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, DataVarsError, VariableError)
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Viewing manually interrupted")
+
+ tasks = [
+ {'name': 'view_vars',
+ # отобразить переменные
+ 'method': 'Variables.showVariables(cl_variable_show,'
+ 'cl_variable_filter,cl_variable_data)'
+ }
+ ]
diff --git a/libs_crutch/core/utils/cl_core_view_cert.py b/libs_crutch/core/utils/cl_core_view_cert.py
new file mode 100644
index 0000000..6237aea
--- /dev/null
+++ b/libs_crutch/core/utils/cl_core_view_cert.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClCoreViewCert(Action):
+ """
+ Отображение сертификатов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,)
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Viewing manually interrupted")
+
+ tasks = [
+ {'name': 'view_cert',
+ # наложить патчи для пакета
+ 'method': 'Certificate.show_certs_meth(cl_page_count,cl_page_offset)'
+ }
+ ]
diff --git a/libs_crutch/core/wsdl_core.py b/libs_crutch/core/wsdl_core.py
new file mode 100644
index 0000000..1cb13af
--- /dev/null
+++ b/libs_crutch/core/wsdl_core.py
@@ -0,0 +1,795 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011-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.
+
+from __future__ import absolute_import
+import sys
+
+from calculate.lib.datavars import VariableError, DataVarsError
+
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_core3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+from . import setup_package
+from . import server.certificate as certificate
+from . import server.groups as groups
+from . import server.request as request
+from . import set_vars
+from .backup import Backup, BackupError
+from calculate.core.utils.cl_backup import ClBackupAction
+from calculate.core.utils.cl_backup_restore import ClBackupRestoreAction
+from calculate.core.server.func import WsdlBase
+from calculate.core.utils.cl_core_setup import ClCoreSetupAction
+from calculate.core.utils.cl_core_patch import ClCorePatchAction
+from calculate.core.utils.cl_config import ClConfigAction
+from calculate.core.utils.cl_core_dispatch import ClCoreDispatchAction
+from calculate.core.utils.cl_core_view_cert import ClCoreViewCert
+from calculate.core.utils.cl_core_group import (
+ ClCoreGroupShow, ClCoreGroupMod, ClCoreGroupAdd, ClCoreGroupDel)
+from calculate.core.utils.cl_core_request import (
+ ClCoreRequestShow, ClCoreRequestConfirm, ClCoreRequestDel)
+from calculate.core.utils.cl_core_variables import (ClCoreVariables,
+ ClCoreVariablesShow)
+from calculate.core.utils.cl_core_custom import ClCoreCustomAction
+from calculate.core.utils.cl_core_restart import ClCoreRestartAction
+from calculate.core.variables.action import Actions
+
+
+class Wsdl(WsdlBase):
+ methods = [
+ #
+ # Настройка пакета во время установки (cl-core-setup)
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_setup",
+ # категория метода
+ 'category': __('Configuration'),
+ # заголовок метода
+ 'title': __("Configure a Package"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-setup,'
+ 'preferences-desktop-default-applications',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-setup',
+ # права для запуска метода
+ 'rights': ['setup_package'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClCoreSetupAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError,
+ setup_package.SetupPackageError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'merge', 'cl_verbose_set': "on"},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Configure a package"),
+ normal=('cl_core_pkg_name',),
+ expert=('cl_core_pkg_category',
+ 'cl_core_pkg_version_opt',
+ 'cl_core_pkg_slot_opt',
+ 'cl_core_pkg_path',
+ 'cl_core_arch_machine',
+ 'cl_templates_locate',
+ 'cl_core_pkg_system_set',
+ 'cl_core_pkg_desktop_set',
+ 'cl_core_pkg_root_set',
+ 'cl_verbose_set',
+ 'cl_dispatch_conf'),
+ next_label=_("Run"))]},
+ #
+ # Патч исходников пакета (cl-core-patch)
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_patch",
+ # категория метода
+ 'category': __('Configuration'),
+ # заголовок метода
+ 'title': __("Patch"),
+ # иконка для графической консоли
+ 'image': None,
+ # метода нет в графической консоли
+ 'gui': False,
+ # консольная команда
+ 'command': 'cl-core-patch',
+ # права для запуска метода
+ 'rights': ['configure'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClCorePatchAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError,
+ setup_package.SetupPackageError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'patch',
+ 'cl_protect_use_set!': 'off'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Configure a package"),
+ normal=('cl_core_pkg_name',),
+ expert=('cl_core_pkg_category',
+ 'cl_core_pkg_version',
+ 'cl_core_pkg_slot',
+ 'cl_core_pkg_path',
+ 'cl_core_arch_machine',
+ 'cl_templates_locate',
+ 'cl_verbose_set'),
+ next_label=_("Run"))]},
+ #
+ # Обновление конфигурационных файлов (cl-dispatch-conf)
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_dispatch",
+ # категория метода
+ 'category': __('Update '),
+ # заголовок метода
+ 'title': __("Update Settings"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-dispatch,edit-find-replace,computer',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-dispatch-conf',
+ # права для запуска метода
+ 'rights': ['configure'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClCoreDispatchAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError,
+ setup_package.SetupPackageError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'dispatch'},
+ # описание груп (список лямбда функций)
+ 'groups': []},
+ #
+ # Отобразить сертификаты
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_view_cert",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Show Certificates"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-view-cert,certificate-server,'
+ 'application-certificate',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-view-cert',
+ # права для запуска метода
+ 'rights': ['certificates'],
+ # объект содержащий модули для действия
+ 'logic': {'Certificate': certificate.Certificate},
+ # описание действия
+ 'action': ClCoreViewCert,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {
+ 'cl_page_max!': lambda dv: len(dv.Get('cl_list_cert_id'))},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Certificates"),
+ normal=('cl_page_count', 'cl_page_offset'),
+ next_label=_("Next"))]},
+ #
+ # Отобразить детали сертификата
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_detail_view_cert",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Certificate Details"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # права для запуска метода
+ 'rights': ['certificates'],
+ # объект содержащий модули для действия
+ 'logic': {},
+ # описание действия
+ 'action': None,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Certificate details"),
+ normal=('cl_cert_id', 'cl_cert_groups',
+ 'cl_cert_perms'),
+ custom_buttons=[('but0', _("Back"),
+ "core_view_cert",
+ "button")])]},
+ #
+ # Группы
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_group_show",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Show Groups"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-group-show,user-group-properties,'
+ 'view-certificate-import,application-certificate',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-group-show',
+ # права для запуска метода
+ 'rights': ['core_group'],
+ # объект содержащий модули для действия
+ 'logic': {'Groups': groups.Groups},
+ # описание действия
+ 'action': ClCoreGroupShow,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {
+ 'cl_page_max!': lambda dv: len(dv.Choice('cl_core_group'))},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Groups"),
+ normal=('cl_page_count', 'cl_page_offset'),
+ next_label=_("Next"))]},
+ #
+ # Отобразить детали группы
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_detail_group",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Group Details"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # права для запуска метода
+ 'rights': ['core_group'],
+ # объект содержащий модули для действия
+ 'logic': {},
+ # описание действия
+ 'action': None,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Group details"),
+ normal=(
+ 'cl_core_group',
+ 'cl_core_group_rights'),
+ custom_buttons=[('but0', _("Back"),
+ "core_group_show",
+ "button"),
+ ('but1', _("Change"),
+ "core_group_mod",
+ "button"),
+ ('but2', _("Delete"),
+ "core_group_del",
+ "button")])]},
+ #
+ # Изменить группу
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_group_mod",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Modify Group"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-groupmod',
+ # права для запуска метода
+ 'rights': ['core_group'],
+ # объект содержащий модули для действия
+ 'logic': {'Groups': groups.Groups},
+ # описание действия
+ 'action': ClCoreGroupMod,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'modify'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Modify group"),
+ normal=(
+ 'cl_core_group',
+ 'cl_core_group_rights'),
+ next_label=_("Done"),
+ custom_buttons=[('but2', _("Confirm"),
+ 'core_change_group',
+ "button")])]},
+ #
+ # Добавить группу
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_group_add",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Add a Group"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-groupadd',
+ # права для запуска метода
+ 'rights': ['core_group'],
+ # объект содержащий модули для действия
+ 'logic': {'Groups': groups.Groups},
+ # описание действия
+ 'action': ClCoreGroupAdd,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'add'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Add a group"),
+ normal=(
+ 'cl_core_group',
+ 'cl_core_group_rights'),
+ next_label=_("Add"))]},
+ #
+ # Удалить группу
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_group_del",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Delete the Group"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-groupdel',
+ # права для запуска метода
+ 'rights': ['core_group'],
+ # объект содержащий модули для действия
+ 'logic': {'Groups': groups.Groups},
+ # описание действия
+ 'action': ClCoreGroupDel,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'delete'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Delete the group"),
+ normal=('cl_core_group',),
+ next_label=_("Delete"))]},
+ #
+ # Запрос на сертификат
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_request_show",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Show Requests"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-request-show,view-certificate-import,'
+ 'application-certificate',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-request-show',
+ # права для запуска метода
+ 'rights': ['request'],
+ # объект содержащий модули для действия
+ 'logic': {'Request': request.Request},
+ # описание действия
+ 'action': ClCoreRequestShow,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {
+ 'cl_page_max!': lambda dv: len(dv.Get('cl_list_req_id'))},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Show requests"),
+ normal=('cl_page_count', 'cl_page_offset'),
+ next_label=_("Next"))]},
+ #
+ # Отобразить детали запроса
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_detail_request",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Request Details"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # права для запуска метода
+ 'rights': ['request'],
+ # объект содержащий модули для действия
+ 'logic': {},
+ # описание действия
+ 'action': None,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Group details"),
+ normal=('cl_req_id', 'cl_req_user_name',
+ 'cl_req_ip',
+ 'cl_req_mac', 'cl_req_date',
+ 'cl_req_location',
+ 'cl_req_group'),
+ custom_buttons=[('but0', _("Back"),
+ "core_request_show",
+ "button"),
+ ('but1', _("Confirm"),
+ "core_request_confirm",
+ "button"),
+ ('but2', _("Delete"),
+ "core_request_del",
+ "button")])]},
+ #
+ # Удалить запрос
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_request_del",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Delete the Request"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-request-del',
+ # права для запуска метода
+ 'rights': ['request'],
+ # объект содержащий модули для действия
+ 'logic': {'Request': request.Request},
+ # описание действия
+ 'action': ClCoreRequestDel,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'delete'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Delete the request"),
+ normal=('cl_req_id',),
+ next_label=_("Delete"))]},
+ #
+ # Подтвердить запрос
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_request_confirm",
+ # категория метода
+ # 'category':__('Utilities'),
+ # заголовок метода
+ 'title': __("Confirm the Request"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-request-confirm',
+ # права для запуска метода
+ 'rights': ['request'],
+ # объект содержащий модули для действия
+ 'logic': {'Request': request.Request},
+ # описание действия
+ 'action': ClCoreRequestConfirm,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'confirm'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Delete the request"),
+ normal=('cl_req_id', 'cl_req_group'),
+ next_label=_("Delete"))]},
+ #
+ # установить переменные
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_variables",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Setup Variables"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-variables,applications-versioncontrol,'
+ 'text-x-preview,text-x-makefile',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-variables',
+ # права для запуска метода
+ 'rights': ['setup_variables'],
+ # объект содержащий модули для действия
+ 'logic': {'Variables': set_vars.Variables},
+ # описание действия
+ 'action': ClCoreVariables,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Setup variables"),
+ normal=('cl_variable_data',),
+ next_label=_("Save"))]},
+ #
+ # отобразить переменные
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_variables_show",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("View Variables"),
+ # иконка для графической консоли
+ 'image': None,
+ # метод в графической консоли
+ 'gui': False,
+ # консольная команда
+ 'command': 'cl-core-variables-show',
+ # права для запуска метода
+ 'rights': ['configure'],
+ # объект содержащий модули для действия
+ 'logic': {'Variables': set_vars.Variables},
+ # описание действия
+ 'action': ClCoreVariablesShow,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Setup variables"),
+ normal=(
+ 'cl_variable_filter',
+ 'cl_variable_show'),
+ next_label=_("Show"))]},
+ #
+ # Выполнить настройку пакета (cl-config)
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_config",
+ # категория метода
+ 'category': __('Configuration'),
+ # заголовок метода
+ 'title': __("Config"),
+ # иконка для графической консоли
+ 'image': None,
+ # метода нет в графической консоли
+ 'gui': False,
+ # консольная команда
+ 'command': 'cl-config',
+ # права для запуска метода
+ 'rights': ['configure'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClConfigAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError,
+ setup_package.SetupPackageError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'config', 'cl_verbose_set': "on"},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Configure a package"),
+ normal=('cl_core_pkg_name',),
+ expert=('cl_core_pkg_category',
+ 'cl_core_pkg_version_opt',
+ 'cl_core_pkg_slot_opt',
+ 'cl_templates_locate',
+ 'cl_verbose_set'),
+ next_label=_("Run"))]},
+ #
+ # отобразить переменные
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_custom",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Custom Action"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-custom,gnome-desktop-config,desktop-config',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-custom',
+ # права для запуска метода
+ 'rights': ['custom_configure'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClCoreCustomAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_verbose_set': "on", 'cl_human_edit_set': "on"},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Custom action"),
+ normal=(
+ 'ac_custom_name', 'cl_human_edit_set',
+ 'cl_verbose_set'),
+ expert=(
+ 'ur_core_login', 'cl_core_arch_machine',
+ 'cl_templates_locate',
+ 'cl_dispatch_conf'),
+ next_label=_("Run"))]},
+ #
+ # перезапустить сервис calculate core
+ #
+ {
+ # идентификатор метода
+ 'method_name': "core_restart",
+ # категория метода
+ 'category': __('Utilities'),
+ # заголовок метода
+ 'title': __("Restart calculate-core"),
+ # иконка для графической консоли
+ 'image': 'calculate-core-restart,view-refresh',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-core-restart',
+ # права для запуска метода
+ 'rights': ['core_restart'],
+ # объект содержащий модули для действия
+ 'logic': {'UpdateConfigs': setup_package.UpdateConfigs},
+ # описание действия
+ 'action': ClCoreRestartAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'restart'},
+ # описание груп (список лямбда функций)
+ 'groups': []},
+ #
+ # создание резервной копии настроек
+ #
+ {
+ # идентификатор метода
+ 'method_name': "backup",
+ # категория метода
+ 'category':__('Backup'),
+ # заголовок метода
+ 'title': __("Backup"),
+ # иконка для графической консоли
+ 'image': 'calculate-backup',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-backup',
+ # права для запуска метода
+ 'rights': ['backup'],
+ # объект содержащий модули для действия
+ 'logic': {'Backup': Backup},
+ # описание действия
+ 'action': ClBackupAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (BackupError, VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': Actions.Backup},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("System backup"),
+ normal=('cl_backup_verbose_set',),
+ next_label=_("Run"))]},
+ #
+ # восстановление настроек из резервной копии
+ #
+ {
+ # идентификатор метода
+ 'method_name': "backup_restore",
+ # категория метода
+ 'category':__('Backup'),
+ # заголовок метода
+ 'title': __("Restore"),
+ # иконка для графической консоли
+ 'image': 'calculate-backup-restore',
+ # метод в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-backup-restore',
+ # права для запуска метода
+ 'rights': ['backup'],
+ # объект содержащий модули для действия
+ 'logic': {'Backup': Backup},
+ # описание действия
+ 'action': ClBackupRestoreAction,
+ # объект переменных
+ 'datavars': "core",
+ 'native_error': (BackupError, VariableError, DataVarsError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': Actions.BackupRestore,
+ 'core.cl_backup_action': Actions.Restore},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("System restore"),
+ normal=('cl_backup_verbose_set',),
+ brief=('cl_backup_file', 'cl_backup_time'),
+ next_label=_("Run"))],
+ 'brief': {'next': __("Run"),
+ 'name': __("System restore")}},
+ ]
diff --git a/libs_crutch/lib/datavars.py b/libs_crutch/lib/datavars.py
index dcb53a6..4a18988 100644
--- a/libs_crutch/lib/datavars.py
+++ b/libs_crutch/lib/datavars.py
@@ -1051,6 +1051,7 @@ class SimpleDataVars(object):
return getList()(value)
if "table" in varType:
return map(getList(':'), value.split(','))
+ value = str(value, "UTF-8")
return fixEmpty(value).strip("'").strip('"')
def ZipVars(self, *argvVarNames, **kw):
diff --git a/libs_crutch/lib/utils/binhosts.py b/libs_crutch/lib/utils/binhosts.py
index c126f2f..06f28b8 100644
--- a/libs_crutch/lib/utils/binhosts.py
+++ b/libs_crutch/lib/utils/binhosts.py
@@ -15,7 +15,7 @@
# limitations under the License.
import sys
-import urllib2
+import urllib.request as urllib2
import re
import time
import os
diff --git a/libs_crutch/update/emerge_parser.py b/libs_crutch/update/emerge_parser.py
new file mode 100644
index 0000000..7033f5f
--- /dev/null
+++ b/libs_crutch/update/emerge_parser.py
@@ -0,0 +1,853 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2015-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 hashlib
+
+import os
+from os import path
+
+import re
+import sys
+from calculate.lib.utils.colortext.palette import TextState
+from calculate.lib.utils.tools import ignore
+from calculate.lib.utils.git import Git, GitError
+from calculate.lib.utils.portage import (EmergePackage, PackageList,
+ EmergeUpdateInfo,
+ EmergeRemoveInfo)
+
+Colors = TextState.Colors
+import pexpect
+from calculate.lib.utils.files import (getProgPath, readLinesFile,
+ listDirectory,
+ writeFile, readFile)
+from calculate.lib.utils.colortext.output import XmlOutput
+from calculate.lib.utils.colortext.converter import (ConsoleCodes256Converter,
+ XmlConverter)
+from calculate.lib.cl_log import log
+
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _
+
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+linux_term_env = {'TERM': 'linux'}
+
+class EmergeError(Exception):
+ """
+ Ошибка при сборке пакетов
+ """
+
+
+class EmergeNeedRootError(EmergeError):
+ pass
+
+
+class CommandExecutor(object):
+ """
+ Запуск программы для объекта Emerge
+ """
+ logfile = '/var/log/calculate/lastcommand.log'
+
+ def __init__(self, cmd, params, env=None, cwd=None, logfile=None):
+ self.cwd = cwd
+ self.env = env or dict(os.environ)
+ self.env.update({'EINFO_QUIET': 'NO'})
+ self.env.update(linux_term_env)
+ self.cmd = cmd
+ self.params = params
+ self.child = None
+ if logfile:
+ self.logfile = logfile
+
+ def get_command(self):
+ return [self.cmd] + list(self.params)
+
+ def execute(self):
+ if self.child is None:
+ command_data = self.get_command()
+ self.child = pexpect.spawn(command_data[0], command_data[1:],
+ logfile=open(self.logfile, 'w'),
+ env=self.env, cwd=self.cwd, timeout=None)
+ return self.child
+
+ def close(self):
+ if self.child is not None:
+ self.child.close()
+ self.child = None
+
+ def success(self):
+ if self.child:
+ self.child.read()
+ if self.child.isalive():
+ self.child.wait()
+ return self.child.exitstatus == 0
+ return False
+
+ def failed(self):
+ return not self.success()
+
+ def send(self, s):
+ if self.child:
+ self.child.send(s)
+
+
+class EmergeCommand(CommandExecutor):
+ """
+ Запуск emerge для последующего анализирования
+ """
+ # параметры по умолчанию
+ default_params = ["-av", "--color=y", "--nospinner"]
+ emerge_cmd = getProgPath("/usr/bin/emerge")
+
+ def __init__(self, packages, extra_params=None, env=None, cwd=None,
+ logfile=None, emerge_default_opts=None, env_update=None,
+ use=""):
+ extra_params = extra_params or []
+ if env is None:
+ if emerge_default_opts is None:
+ env = {'CLEAN_DELAY': '0'}
+ else:
+ env = {
+ 'CLEAN_DELAY': '0',
+ 'EMERGE_DEFAULT_OPTS': re.sub(
+ r'(?:^|\s)(--columns)(?=\s|$)', '',
+ emerge_default_opts)
+ }
+ if use:
+ env["USE"] = use
+ env.update(os.environ)
+ if env_update is not None:
+ env.update(env_update)
+
+ params = self.default_params + extra_params + packages
+ super(EmergeCommand, self).__init__(self.emerge_cmd, params=params,
+ env=env, cwd=cwd, logfile=logfile)
+
+
+def Chroot(chroot_path, obj):
+ """
+ Преобразовать команду (экземпляр объекта) в chroot
+ :param obj: экземпляр команды
+ :param chroot_path: путь для chroot
+ :return:
+ """
+ old_get_command = obj.get_command
+
+ def get_command():
+ chrootCmd = '/usr/bin/chroot'
+ bashCmd = '/bin/bash'
+ bash_command = (
+ "env-update &>/dev/null;"
+ "source /etc/profile &>/dev/null;"
+ "{cmd}".format(cmd=" ".join(old_get_command())))
+ return [chrootCmd, chroot_path, bashCmd, "-c", bash_command]
+ obj.get_command = get_command
+ return obj
+
+
+def Linux32(obj):
+ """
+ Преобразовать команду (экземпляр объекта) в вызов под linux32
+ :param obj: экземпляр команды
+ :return:
+ """
+ old_get_command = obj.get_command
+
+ def get_command():
+ return ["/usr/bin/linux32"] + old_get_command()
+ obj.get_command = get_command
+ return obj
+
+
+class InfoBlockInterface(object):
+ """
+ Интерфейс для информационного блока
+ """
+ action = None
+ token = None
+ result = None
+ text_converter = ConsoleCodes256Converter(XmlOutput())
+
+ def get_block(self, child):
+ pass
+
+ def add_element(self, element):
+ """
+ :type element: InfoBlockInterface
+ """
+ pass
+
+
+class EmergeInformationBlock(InfoBlockInterface):
+ _color_block = "(?:\033\[[^m]+?m)?"
+ _new_line = "(?:\r*\n)"
+ end_token = ["\n"]
+ re_block = None
+ re_match_type = type(re.match("", ""))
+ re_type = type(re.compile(""))
+
+ def __init__(self, parent):
+ """
+ :type parent: InfoBlockInterface
+ """
+ self.result = None
+ self.text_converter = parent.text_converter
+ self.parent = parent
+ self.parent.add_element(self)
+ self.children = []
+
+ def add_element(self, element):
+ """
+ :type element: InfoBlockInterface
+ """
+ self.children.append(element)
+
+ def __str__(self):
+ if type(self.result) == self.re_match_type:
+ return self.result.group()
+ else:
+ return self.result or ""
+
+ def __nonzero__(self):
+ return bool(self.result)
+
+ def __len__(self):
+ if self.result is None:
+ return 0
+ else:
+ return len(self.result)
+
+ def __contains__(self, item):
+ if self.result is None:
+ return False
+ else:
+ return item in str(self)
+
+ def _get_text(self, result):
+ """
+ Получить результат из регулярки и преобразовать его через self.converter
+ """
+ if result:
+ return self.text_converter.transform(result.rstrip())
+ return ""
+
+ def get_block(self, child):
+ try:
+ token = child.match
+ if type(self.end_token) == self.re_type:
+ child.expect(self.end_token)
+ match = child.match.group()
+ else:
+ child.expect_exact(self.end_token)
+ match = child.match
+ self.get_data(self.re_block.search(
+ token + child.before + match))
+ except pexpect.EOF:
+ child.buffer = "".join(
+ [x for x in (child.before, child.after, child.buffer)
+ if type(x) == str])
+
+ def get_data(self, match):
+ self.result = self._get_text(match.group(1))
+
+
+class InstallPackagesBlock(EmergeInformationBlock):
+ """
+ Блок emerge содержащий список пакетов для установки
+ """
+ list = PackageList([])
+ remove_list = PackageList([])
+ block_packages = False
+ _new_line = EmergeInformationBlock._new_line
+ _color_block = EmergeInformationBlock._color_block
+ token = "\n["
+ end_token = ["\r\n\r", "\n\n"]
+
+ re_block = re.compile(r"((?:^\[.*?{nl})+)".format(nl=_new_line),
+ re.MULTILINE)
+ re_blocks = re.compile(r"\[{c}blocks{c} {c}b".format(c=_color_block))
+
+ def get_data(self, match):
+ super(InstallPackagesBlock, self).get_data(match)
+ list_block = XmlConverter().transform(self.result).split('\n')
+ self.list = PackageList(map(EmergeUpdateInfo, list_block))
+ self.remove_list = PackageList(map(EmergeRemoveInfo, list_block))
+ self.block_packages = any(self.re_blocks.search(x) for x in list_block)
+
+
+class UninstallPackagesBlock(EmergeInformationBlock):
+ """
+ Блок emerge содержащий список удаляемых пакетов
+ """
+ list = PackageList([])
+ verbose_result = ""
+ _new_line = EmergeInformationBlock._new_line
+ _color_block = EmergeInformationBlock._color_block
+ token = ["Calculating removal order",
+ "These are the packages that would be unmerged"]
+ end_token = re.compile("All selected packages:.*\n")
+ re_block = re.compile(
+ r"(?:{token}).*?{nl}(.*){nl}All selected packages: (.*?){nl}".
+ format(token="|".join(token),
+ nl=_new_line, c=_color_block), re.DOTALL)
+
+ def get_data(self, match):
+ re_clean = re.compile(
+ "^.*?({token}).*?{c}{nl}".format(token="|".join(self.token),
+ nl=self._new_line,
+ c=self._color_block), re.DOTALL)
+ verbose_result = re_clean.sub("", match.group(1))
+ self.verbose_result = self._get_text(verbose_result)
+ self.result = self._get_text(match.group(2))
+ list_block = XmlConverter().transform(self.result).split()
+ self.list = PackageList(map(EmergePackage, list_block))
+
+
+class GroupEmergeInformationBlock(EmergeInformationBlock):
+ """
+ Группа блоков
+ """
+ def get_block(self, child):
+ self.children_get_block(child)
+ self.result = True
+
+ def children_get_block(self, child):
+ for block in self.children:
+ block.get_block(child)
+
+ def children_action(self, child):
+ for block in (x for x in self.children if x.result and x.action):
+ if block.action(child) is False:
+ return False
+
+ def action(self, child):
+ self.children_action(child)
+ return False
+
+
+class FinishEmergeGroup(GroupEmergeInformationBlock):
+ """
+ Блок завершения команды
+ """
+ token = pexpect.EOF
+ # регуляреное выражение, определяющее содержит ли блок
+ # сообщения об ошибках
+ re_failed = re.compile(
+ r"Fetch instructions for \S+:|"
+ r"The following.*are necessary to proceed|"
+ r"!!! Multiple package .* slot have been pulled|"
+ r"no ebuilds to satisfy|"
+ r"Dependencies could not be completely resolved due to",
+ re.MULTILINE)
+
+ def get_block(self, child):
+ if child.isalive():
+ child.wait()
+ if child.exitstatus != 0 or self.re_failed.search(child.before):
+ self.children_get_block(child)
+ else:
+ self.result = True
+
+
+class PrepareErrorBlock(EmergeInformationBlock):
+ """
+ Блок информации с ошибками при получении списка устанавливаемых пакетов
+ """
+ token = None
+
+ re_drop = re.compile("news items need reading|"
+ "Use eselect news|"
+ "Calculating dependencies|"
+ "to read news items|"
+ "Local copy of remote index is up-to-date|"
+ "These are the packages that would be merged|"
+ "Process finished with exit code")
+ re_multi_empty_line = re.compile("(?:
){3,}", re.DOTALL)
+ re_strip_br = re.compile("^(?:
)+|(?:
)+$", re.DOTALL)
+
+ def remove_needless_data(self, data):
+ return "\n".join([x for x in data.split('\n')
+ if not self.re_drop.search(x)])
+
+ def strip_br(self, data):
+ return self.re_strip_br.sub(
+ "",
+ self.re_multi_empty_line.sub("
", data))
+
+ def get_block(self, child):
+ self.result = self.strip_br(
+ self._get_text(self.remove_needless_data(child.before)))
+
+ def action(self, child):
+ raise EmergeError(_("Emerge failed"))
+
+
+class DownloadSizeBlock(EmergeInformationBlock):
+ """
+ Размер скачиваемых обновлений
+ """
+ token = "Size of downloads:"
+
+ re_block = re.compile(r"Size of downloads:\s(\S+\s\S+)")
+
+ def __str__(self):
+ if self.result:
+ return self.result
+ else:
+ return "0 kB"
+
+class SkippedPackagesBlock(EmergeInformationBlock):
+ """
+ Размер скачиваемых обновлений
+ """
+ token = "The following update has been skipped"
+ end_token = ["For more information, see the MASKED"]
+
+ re_block = re.compile(
+ r"(The following update has.*?)(?=For more information)", re.S)
+
+ def __str__(self):
+ if self.result:
+ return self.result
+ else:
+ return ""
+
+class QuestionGroup(GroupEmergeInformationBlock):
+ """
+ Группа блоков разбора вопросов от emerge
+ """
+ token = "Would you like"
+ end_token = ["]", "\n"]
+ _color_block = EmergeInformationBlock._color_block
+ re_block = re.compile(
+ "(Would you.*)\[{c}Yes{c}/{c}No{c}".format(c=_color_block))
+
+ def get_block(self, child):
+ try:
+ before = child.before
+ token = child.match
+ if type(self.end_token) == self.re_type:
+ child.expect(self.end_token)
+ match = child.match.group()
+ else:
+ child.expect_exact(self.end_token)
+ match = child.match
+ data = token + child.before + match
+ child.before = before
+ for block in self.children:
+ child.match = re.search(block.token, data)
+ block.get_block(child)
+ if block.result:
+ break
+ except pexpect.EOF:
+ child.buffer = "".join(
+ [x for x in (child.before, child.after, child.buffer)
+ if type(x) == str])
+
+class QuestionChangeConfigBlock(GroupEmergeInformationBlock):
+ """
+ Вопрос об изменении конфигурационных файлов
+ """
+ token = "Would you like to add these changes to your config files"
+
+ def get_block(self, child):
+ if child.match:
+ self.result = self.token
+ self.children_get_block(child)
+
+ def action(self, child):
+ if self.result:
+ child.send("no\n")
+ if child.isalive():
+ child.wait()
+ self.children_action(child)
+
+
+class QuestionBlock(EmergeInformationBlock):
+ """
+ Блок вопроса
+ """
+ default_answer = "yes"
+ token = "Would you"
+
+ def get_block(self, child):
+ if child.match:
+ self.result = self.token
+
+ def action(self, child):
+ if self.result:
+ child.send("%s\n" % self.default_answer)
+ return False
+
+
+class NeedRootBlock(EmergeInformationBlock):
+ """
+ Пользователь не явеляется root
+ """
+ token = "This action requires superuser access"
+
+ def get_data(self, child):
+ self.result = True
+
+ def action(self, child):
+ raise EmergeNeedRootError(_("This action requires superuser access"))
+
+
+class NotifierInformationBlock(EmergeInformationBlock):
+ """
+ Информационный блок поддерживающий observing
+ """
+ def __init__(self, parent):
+ super(NotifierInformationBlock, self).__init__(parent)
+ self.observers = []
+
+ def get_data(self, match):
+ self.result = match
+
+ def add_observer(self, f):
+ self.observers.append(f)
+
+ def clear_observers(self):
+ self.observers = []
+
+ def remove_observer(self, f):
+ if f in self.observers:
+ self.observers.remove(f)
+
+ def notify(self, observer, groups):
+ observer(groups)
+
+ def action(self, child):
+ if self.result and self.observers:
+ groups = self.result.groups()
+ for observer in self.observers:
+ self.notify(observer, groups)
+
+
+class EmergingPackage(NotifierInformationBlock):
+ """
+ Запуск устанавливаемого пакета
+
+ ObserverFunc: (package, num=number, max_num=number, binary=binary)
+ """
+ _color_block = EmergeInformationBlock._color_block
+ token = ">>> Emerging "
+ re_block = re.compile(
+ "Emerging (binary )?\({c}(\d+){c} "
+ "of {c}(\d+){c}\) {c}([^\s\033]+){c}".format(c=_color_block))
+
+ def notify(self, observer, groups):
+ observer(EmergePackage(groups[3]), num=groups[1], max_num=groups[2],
+ binary=bool(groups[0]))
+
+
+class UnemergingPackage(NotifierInformationBlock):
+ """
+ Запуск устанавливаемого пакета
+
+ ObserverFunc: (package, num=number, max_num=number)
+ """
+ _color_block = EmergeInformationBlock._color_block
+ token = ">>> Unmerging"
+ re_block = re.compile(
+ r"Unmerging (?:\({c}(\d+){c} "
+ r"of {c}(\d+){c}\) )?(\S+)\.\.\.".format(c=_color_block))
+
+ def notify(self, observer, groups):
+ observer(EmergePackage(groups[2]), num=groups[0], max_num=groups[1])
+
+
+class FetchingTarball(NotifierInformationBlock):
+ """
+ Происходит скачивание архивов
+ """
+ token = "Saving to:"
+ re_block = re.compile("Saving to:\s*[‘'](\S+)?['’]")
+
+ def notify(self, observer, groups):
+ observer(groups[0])
+
+
+class InstallingPackage(NotifierInformationBlock):
+ """
+ Запуск устанавливаемого пакета
+
+ ObserverFunc: (package, binary=binary)
+ """
+ _color_block = EmergeInformationBlock._color_block
+ binary = None
+
+ token = ">>> Installing "
+ re_block = re.compile(
+ "Installing \({c}(\d+){c} "
+ "of {c}(\d+){c}\) {c}([^\s\033]+){c}".format(c=_color_block))
+
+ def notify(self, observer, groups):
+ strpkg = str(EmergePackage(groups[2]))
+ binary = bool(self.binary and strpkg in self.binary)
+ observer(EmergePackage(groups[2]), binary=binary)
+
+ def mark_binary(self, package):
+ if self.binary is None:
+ self.binary = []
+ self.binary.append(str(package))
+
+
+class EmergeingErrorBlock(EmergeInformationBlock):
+ """
+ Блок содержит информацию об ошибке во время сборки пакета
+ """
+ token = ["* ERROR: ", " * \033[39;49;00mERROR: "]
+ end_token = "Working directory:"
+ re_block = re.compile("ERROR: (\S*) failed \([^)]+\).*?"
+ "The complete build log is located at '([^']+)",
+ re.DOTALL)
+ package = ""
+
+ def get_data(self, match):
+ self.result = self._get_text(match.group(2).rstrip())
+ self.package = match.group(1)
+
+ @property
+ def log(self):
+ return self.text_converter.transform(readFile(self.result))
+
+ def action(self, child):
+ raise EmergeError(_("Failed to emerge %s") % self.package)
+
+
+class RevdepPercentBlock(NotifierInformationBlock):
+ """
+ Блок определния статуса revdep-rebuild
+ """
+ token = "Collecting system binaries"
+ end_token = [re.compile("Assigning files to packages|"
+ "All prepared. Starting rebuild")]
+ re_block = re.compile("\[\s(\d+)%\s\]")
+ action = None
+
+ def notify(self, observer, groups):
+ percent = int(groups[0])
+ observer(percent)
+
+ def get_block(self, child):
+ expect_result = [self.re_block]+self.end_token
+ try:
+ while True:
+ index = child.expect(expect_result)
+ if index == 0:
+ for observer in self.observers:
+ self.notify(observer, child.match.groups())
+ else:
+ self.result = child.match
+ break
+ except pexpect.EOF:
+ self.result = ""
+
+class EmergeParser(InfoBlockInterface):
+ """
+ Парсер вывода emerge
+ """
+
+ def __init__(self, command, run=False):
+ self.command = command
+ self.elements = {}
+
+ self.install_packages = InstallPackagesBlock(self)
+ self.uninstall_packages = UninstallPackagesBlock(self)
+ self.question_group = QuestionGroup(self)
+ self.change_config_question = QuestionChangeConfigBlock(
+ self.question_group)
+ self.question = QuestionBlock(self.question_group)
+ self.finish_block = FinishEmergeGroup(self)
+ self.need_root = NeedRootBlock(self)
+ self.prepare_error = PrepareErrorBlock(self.finish_block)
+ self.change_config_question.add_element(self.prepare_error)
+ self.download_size = DownloadSizeBlock(self)
+ self.skipped_packages = SkippedPackagesBlock(self)
+ self.emerging_error = EmergeingErrorBlock(self)
+
+ self.installing = InstallingPackage(self)
+ self.uninstalling = UnemergingPackage(self)
+ self.emerging = EmergingPackage(self)
+ self.fetching = FetchingTarball(self)
+
+ self.emerging.add_observer(self.mark_binary)
+ self.emerging.add_observer(self.skip_fetching)
+ if run:
+ self.run()
+
+ def mark_binary(self, package, binary=False, **kw):
+ if binary:
+ self.installing.mark_binary(package)
+
+ def skip_fetching(self, *argv, **kw):
+ self.fetching.action = lambda child: None
+
+ def add_element(self, element):
+ """
+ :type element: InfoBlockInterface
+ """
+ if element.token:
+ if type(element.token) == list:
+ for token in element.token:
+ self.elements[token] = element
+ else:
+ self.elements[element.token] = element
+
+ def run(self):
+ """
+ Запустить команду
+ """
+ child = self.command.execute()
+
+ while True:
+ index = child.expect_exact(self.elements.keys())
+ element = self.elements.values()[index]
+ element.get_block(child)
+ if element.action:
+ if element.action(child) is False:
+ break
+
+ def close(self):
+ self.command.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *exc_info):
+ self.close()
+
+
+class MtimeCheckvalue(object):
+ def __init__(self, *fname):
+ self.fname = fname
+
+ def value_func(self, fn):
+ return str(int(os.stat(fn).st_mtime))
+
+ def get_check_values(self, file_list):
+ for fn in file_list:
+ if path.exists(fn) and not path.isdir(fn):
+ yield fn, self.value_func(fn)
+ else:
+ for k, v in self.get_check_values(
+ listDirectory(fn, fullPath=True)):
+ yield k, v
+
+ def checkvalues(self):
+ return self.get_check_values(self.fname)
+
+
+class Md5Checkvalue(MtimeCheckvalue):
+ def value_func(self, fn):
+ return hashlib.md5(readFile(fn)).hexdigest()
+
+
+class GitCheckvalue(object):
+ def __init__(self, git, rpath):
+ self.rpath = rpath
+ self.git = git
+
+ def checkvalues(self):
+ with ignore(GitError):
+ if self.git.is_git(self.rpath):
+ yield self.rpath, self.git.getCurrentCommit(self.rpath)
+
+
+class EmergeCache(object):
+ """
+ Кэш пакетов
+ """
+ cache_file = '/var/lib/calculate/calculate-update/world.cache'
+ # список файлов проверяемый по mtime на изменения
+ check_list = [MtimeCheckvalue('/etc/make.conf',
+ '/etc/portage',
+ '/etc/make.profile'),
+ Md5Checkvalue('/var/lib/portage/world',
+ '/var/lib/portage/world_sets')]
+ logger = log("emerge-cache",
+ filename="/var/log/calculate/emerge-cache.log",
+ formatter="%(asctime)s - %(levelname)s - %(message)s")
+
+ def __init__(self):
+ self.files_control_values = {}
+ self.pkg_list = PackageList([])
+
+ def set_cache(self, package_list):
+ """
+ Установить в кэш список пакетов
+ """
+ with writeFile(self.cache_file) as f:
+ for fn, val in self.get_control_values().items():
+ f.write("{fn}={val}\n".format(fn=fn, val=val))
+ f.write('\n')
+ for pkg in package_list:
+ f.write("%s\n" % str(pkg))
+ self.logger.info("Setting cache (%d packages)" % len(package_list))
+
+ def drop_cache(self, reason=None):
+ if path.exists(self.cache_file):
+ with ignore(OSError):
+ os.unlink(self.cache_file)
+ self.logger.info("Droping cache. Reason: %s" % reason)
+ else:
+ self.logger.info("Droping empty cache. Reason: %s" % reason)
+
+ def get_cached_package_list(self):
+ self.read_cache()
+ if not path.exists(self.cache_file):
+ self.logger.info("Requesting empty cache")
+ if self.check_actuality():
+ return self.pkg_list
+ return None
+
+ def check_actuality(self):
+ """
+ Кэш считается актуальным если ни один из файлов не менялся
+ """
+ if self.get_control_values() == self.files_control_values:
+ self.logger.info(
+ "Getting actuality cache (%d packages)" % len(self.pkg_list))
+ return True
+ else:
+ reason = "Unknown"
+ for k, v in self.get_control_values().items():
+ if k in self.files_control_values:
+ if v != self.files_control_values[k]:
+ reason = "%s was modified" % k
+ else:
+ reason = "Checksum of file %s is not exist" % k
+ self.logger.info("Failed to get cache. Reason: %s" % reason)
+ return False
+
+ def read_cache(self):
+ self.files_control_values = {}
+ cache_file_lines = readLinesFile(self.cache_file)
+ for line in cache_file_lines:
+ if "=" not in line:
+ break
+ k, v = line.split('=')
+ self.files_control_values[k] = v.strip()
+ self.pkg_list = PackageList(cache_file_lines)
+
+ def get_control_values(self):
+ def generate():
+ for obj in self.check_list:
+ for check_value in obj.checkvalues():
+ yield check_value
+
+ return dict(generate())
diff --git a/libs_crutch/update/profile.py b/libs_crutch/update/profile.py
new file mode 100644
index 0000000..b0f028f
--- /dev/null
+++ b/libs_crutch/update/profile.py
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2014-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 sys
+import os
+from os import path
+import shutil
+from calculate.lib.utils.files import (listDirectory, readFile, readLinesFile,
+ makeDirectory, removeDir)
+from calculate.lib.utils.git import Git
+from .update import UpdateError
+
+from calculate.lib.cl_lang import setLocalTranslate, _
+setLocalTranslate('cl_update3', sys.modules[__name__])
+
+DEFAULT_BRANCH = Git.Reference.Master
+
+class RepositoryStorageInterface(object):
+ def __iter__(self):
+ raise StopIteration
+
+ def get_profiles(self, url, branch=DEFAULT_BRANCH):
+ return []
+
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ return None
+
+class RepositoryStorage(RepositoryStorageInterface):
+ directory = '/tmp'
+
+ def __init__(self, git, directory):
+ self.directory = directory
+ self.git = git
+ makeDirectory(directory)
+
+ def __iter__(self):
+ for dn in listDirectory(self.directory, onlyDir=True, fullPath=True):
+ if self.git.is_git(dn):
+ yield ProfileRepository(self.git, path.basename(dn), self)
+
+ def get_profiles(self, url, branch=DEFAULT_BRANCH):
+ return []
+
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ return None
+
+
+class ProfileStorage(RepositoryStorage):
+ def get_profiles(self, url, branch=DEFAULT_BRANCH):
+ rep = self.get_repository(url, branch)
+ if rep:
+ return rep.get_profiles()
+ return None
+
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ return None
+
+
+class LocalStorage(ProfileStorage):
+ """
+ Локальное хранилище репозиториев, при запросе по урлу смотрит, доступные
+ репозитории если находит подходящий - возвращает его профили
+ """
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ for rep in self:
+ if rep.is_like(url, branch):
+ return rep
+
+class CacheStorage(ProfileStorage):
+ """
+ Хранилище репозиториев, при запросе по урлу смотрит, доступные
+ репозитории если находит подходящий - возвращает его профили,
+ если не находит - скачивает и возвращает профили
+ """
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ for rep in self:
+ if rep.is_like(url, branch):
+ return rep
+ else:
+ return ProfileRepository.clone(self.git, url, self,
+ branch or DEFAULT_BRANCH)
+
+class RepositoryStorageSet(RepositoryStorageInterface):
+ """
+ Набор хранилищ репозиториев
+ """
+ def __init__(self, *storages):
+ self.storages = storages
+
+ def get_profiles(self, url, branch=DEFAULT_BRANCH):
+ """
+ Получить профили из указанного репозитория
+ """
+ for storage in self.storages:
+ profiles = storage.get_profiles(url, branch)
+ if profiles is not None:
+ return profiles
+ return None
+
+ def __iter__(self):
+ for storage in self.storages:
+ for rep in storage:
+ yield rep
+
+ def get_repository(self, url, branch=DEFAULT_BRANCH):
+ """
+ Получить репозиторий по параметрам
+ """
+ for rep in self:
+ if rep.is_like(url, branch):
+ return rep
+ for storage in self.storages:
+ rep = storage.get_repository(url, branch)
+ if rep:
+ return rep
+ return None
+
+ def is_local(self, url, branch=DEFAULT_BRANCH):
+ """
+ Проверить является ли репозиторий с указанными параметрами
+ локальным
+ """
+ rep = self.get_repository(url, branch)
+ if rep and isinstance(rep.storage, LocalStorage):
+ return True
+ return False
+
+ def __repr__(self):
+ return "Repository set"
+
+class Profile(object):
+ """
+ Профиль репозитория
+ """
+ available_arch = ["amd64", "x86"]
+
+ def __init__(self, repository, profile, arch):
+ self.repository = repository
+ self.profile = profile
+ self.arch = arch
+
+ @property
+ def path(self):
+ return path.join(self.repository.directory,"profiles", self.profile)
+
+ @classmethod
+ def from_string(cls, repository, s):
+ parts = filter(None, s.split())
+ if len(parts) == 3 and parts[0] in cls.available_arch:
+ return Profile(repository, parts[1], parts[0])
+ return None
+
+ def __repr__(self):
+ return "" % (self.arch,
+ self.repository.repo_name,
+ self.profile,
+ self.repository.directory)
+
+
+class ProfileRepository(object):
+ """
+ Репозиторий либо скачивается, либо берется из кэша
+ """
+ def __init__(self, git, name, storage):
+ self._storage = storage
+ self.name = name
+ self.git = git
+
+ @property
+ def storage(self):
+ return self._storage
+
+ @storage.setter
+ def storage(self, storage):
+ if storage.directory != self._storage.directory:
+ newpath = path.join(storage.directory, self.name)
+ if self.directory == newpath:
+ return
+ try:
+ if path.exists(newpath):
+ removeDir(newpath)
+ shutil.move(self.directory, newpath)
+ self._storage = storage
+ except OSError as e:
+ raise UpdateError(_("Failed to move the profile: %s") %
+ str(e))
+
+ @classmethod
+ def clone(cls, git, url, storage, branch=DEFAULT_BRANCH):
+ name = path.basename(url)
+ if name.endswith(".git"):
+ name = name[:-4]
+ rpath = path.join(storage.directory, name)
+ if path.exists(rpath):
+ removeDir(rpath)
+ if git.is_private_url(url):
+ try:
+ makeDirectory(rpath)
+ os.chmod(rpath, 0o700)
+ except OSError:
+ pass
+ git.cloneRepository(url, rpath, branch)
+ pr = cls(git, name, storage)
+ repo_name = pr.repo_name
+ if name != repo_name:
+ rpath_new = path.join(storage.directory, repo_name)
+ if path.exists(rpath_new):
+ removeDir(rpath_new)
+ shutil.move(rpath, rpath_new)
+ pr = cls(git, repo_name, storage)
+ return pr
+
+ @property
+ def repo_name(self):
+ return readFile(path.join(self.directory,
+ "profiles/repo_name")).strip()
+
+ def is_like(self, url, branch=DEFAULT_BRANCH):
+ if self.url == url and (branch is None or self.branch == branch):
+ return True
+ return False
+
+ @property
+ def directory(self):
+ """
+ Получить локальную директорию на данные репозитория
+ """
+ return path.join(self.storage.directory, self.name)
+
+ @property
+ def url(self):
+ return self.git.get_url(self.directory, "origin")
+
+ @property
+ def branch(self):
+ return self.git.getBranch(self.directory)
+
+ def sync(self):
+ """
+ Синхронизировать репозиторий
+ """
+ if not self.git.pullRepository(self.directory, quiet_error=True):
+ self.git.resetRepository(self.directory, to_origin=True)
+ self.git.pullRepository(self.directory, quiet_error=True)
+
+ def get_profiles(self):
+ """
+ Получить список профилей репозитория
+ """
+ profiles_desc = path.join(self.directory, "profiles/profiles.desc")
+ return filter(None, (Profile.from_string(self, line)
+ for line in readLinesFile(profiles_desc)))
+
+ def __repr__(self):
+ return "" % (self.directory, self.url)
diff --git a/libs_crutch/update/update.py b/libs_crutch/update/update.py
new file mode 100644
index 0000000..3f474d4
--- /dev/null
+++ b/libs_crutch/update/update.py
@@ -0,0 +1,1764 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2014-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.
+from functools import wraps
+import random
+
+import sys
+from os import path
+import os
+import time
+from calculate.core.server.gen_pid import search_worked_process
+from calculate.core.setup_cache import Cache as SetupCache
+from calculate.core.server.func import MethodsInterface
+from calculate.lib.cl_template import SystemIni, LayeredIni
+from calculate.lib.datavars import DataVarsError, VariableError, Variable
+
+from calculate.lib.utils.tools import AddonError
+from calculate.lib.utils.colortext.palette import TextState
+from calculate.lib.utils.colortext import get_color_print
+from .emerge_parser import RevdepPercentBlock
+from .datavars import DataVarsUpdate
+from .update_info import UpdateInfo
+from calculate.lib.utils.binhosts import (Binhosts, BinhostSignError,
+ BinhostError, PackagesIndex, DAYS)
+from calculate.lib.utils.gpg import GPG, GPGError
+from calculate.lib.utils.common import get_fastlogin_domain_path
+from calculate.lib.cl_log import log
+import hashlib
+import re
+import shutil
+from collections import MutableSet
+from contextlib import contextmanager
+import tempfile
+from fnmatch import fnmatch
+
+from calculate.lib.utils.vardb import Vardb
+
+from calculate.lib.utils.git import Git, GitError, MTimeKeeper, NotGitError
+from calculate.lib.utils.portage import (ReposConf, EmergeLog,
+ EmergeLogNamedTask,
+ VDB_PATH,
+ PackageInformation,
+ PortageState,
+ get_packages_files_directory,
+ get_manifest_files_directory,
+ get_remove_list)
+from calculate.lib.utils.text import _u8, _u
+
+Colors = TextState.Colors
+from calculate.lib.utils.files import (getProgPath, STDOUT, removeDir,
+ getParentPids,
+ PercentProgress, process, getRunCommands,
+ readFile, listDirectory, pathJoin,
+ find, FindFileType,quite_unlink,
+ writeFile, makeDirectory)
+from . import emerge_parser
+import logging
+from .emerge_parser import (EmergeParser, EmergeCommand, EmergeError,
+ EmergeCache, Chroot)
+
+from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate,
+ RegexpLocalization, _)
+
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class UpdateError(AddonError):
+ """Update Error"""
+
+
+class OverlayOwnCache(MutableSet):
+ """
+ Сет оверлеев с интегрированным кэшем
+ """
+
+ def __init__(self, dv=None):
+ self.dv = dv
+
+ def __get_overlays(self):
+ own_cache_value = SystemIni(self.dv).getVar('system', 'own_cache') or ""
+ return [x.strip() for x in own_cache_value.split(',') if x.strip()]
+
+ def __write_overlays(self, overlays):
+ if not overlays:
+ SystemIni(self.dv).delVar('system', 'own_cache')
+ else:
+ SystemIni(self.dv).setVar('system',
+ {'own_cache': ",".join(overlays)})
+
+ def __contains__(self, item):
+ return item in self.__get_overlays()
+
+ def __iter__(self):
+ return iter(self.__get_overlays())
+
+ def __len__(self):
+ return len(self.__get_overlays())
+
+ def __append_value(self, overlays, value):
+ if value not in overlays:
+ overlays.append(value)
+ self.__write_overlays(overlays)
+
+ def add(self, value):
+ overlays = self.__get_overlays()
+ self.__append_value(overlays, value)
+
+ def discard(self, value):
+ overlays = self.__get_overlays()
+ if value in overlays:
+ overlays.remove(value)
+ self.__write_overlays(overlays)
+
+
+def variable_module(var_env):
+ def variable_module_decor(f):
+ @wraps(f)
+ def wrapper(self, *args, **kw):
+ old_env = self.clVars.defaultModule
+ try:
+ self.clVars.defaultModule = var_env
+ return f(self, *args, **kw)
+ finally:
+ self.clVars.defaultModule = old_env
+
+ return wrapper
+
+ return variable_module_decor
+
+
+class Update(MethodsInterface):
+ """Основной объект для выполнения действий связанных с обновлением системы
+
+ """
+
+ def init(self):
+ commandLog = path.join(self.clVars.Get('core.cl_log_path'),
+ 'lastcommand.log')
+ emerge_parser.CommandExecutor.logfile = commandLog
+ self.color_print = get_color_print()
+ self.emerge_cache = EmergeCache()
+ if self.clVars.Get('cl_env_debug_set') == 'off':
+ EmergeCache.logger.logger.setLevel(logging.WARNING)
+ self.emerge_cache.check_list = (
+ self.emerge_cache.check_list +
+ map(lambda x:emerge_parser.GitCheckvalue(x, self.getGit()),
+ self.clVars.Get('update.cl_update_rep_path')))
+ self.update_map = {}
+ self.refresh_binhost = False
+ self.pkgnum = None
+ self.pkgnummax = None
+ self.gpgdata_md5 = []
+ self.gpg_changed = False
+
+ def get_prog_path(self, program_name):
+ return getProgPath(program_name)
+
+ def getGit(self):
+ return self.clVars.Get('cl_update_git')
+
+ @contextmanager
+ def private_repo(self, rpath, url):
+ if Git.is_private_url(url):
+ try:
+ if not path.exists(rpath):
+ makeDirectory(rpath)
+ os.chmod(rpath, 0o700)
+ yield
+ finally:
+ try:
+ for dn in (Git._gitDir(rpath), path.join(rpath, "profiles/templates")):
+ if path.exists(dn):
+ os.chmod(dn, 0o700)
+ for fn in find(path.join(rpath, "profiles"), True, FindFileType.RegularFile,
+ True, None, downfilter=lambda x: not x.endswith("/templates")):
+ if fn.endswith("calculate.env") or fn.endswith("ini.env"):
+ os.chmod(fn, 0o600)
+ if path.exists(rpath):
+ os.chmod(rpath, 0o755)
+ except OSError:
+ pass
+ else:
+ yield
+
+ def _syncRepository(self, name, url, rpath, revision,
+ cb_progress=None, clean=False, notask=False):
+ """
+ Синхронизировать репозитори
+ """
+ dv = self.clVars
+ git = self.getGit()
+ info_outdated = False
+ old_dir = "%s.old" % git._gitDir(rpath)
+ if path.exists(old_dir):
+ clean = True
+ try:
+ self.stash_cache(rpath, name)
+ if not git.checkExistsRep(rpath):
+ if not notask:
+ self.startTask(_("Syncing the {rep} repository").format(
+ rep=name.capitalize()))
+ self.addProgress()
+ with self.private_repo(rpath, url):
+ git.cloneTagRepository(url, rpath, revision,
+ cb_progress=cb_progress)
+ info_outdated = True
+ else:
+ cr = ""
+ try:
+ need_update = False
+ tag_cr = git.getCommit(rpath, revision)
+ cr = git.getCurrentCommit(rpath)
+ ref_type = git.reference_type(rpath, revision)
+ if tag_cr != cr or ref_type == Git.Reference.Branch:
+ need_update = True
+ elif clean:
+ status = git.getStatusInfo(rpath)
+ if not status or status['files']:
+ need_update = True
+ except GitError as e:
+ need_update = True
+ if need_update:
+ if not notask:
+ self.startTask(_("Syncing the {rep} repository").format(
+ rep=name.capitalize()))
+ self.addProgress()
+ with self.private_repo(rpath, url):
+ git.updateTagRepository(url, rpath, revision,
+ cb_progress=cb_progress,
+ clean=clean)
+ new_cr = git.getCurrentCommit(rpath)
+ if new_cr != cr:
+ info_outdated = True
+ if info_outdated:
+ self.raiseOutdate()
+ dv.Set('cl_update_outdate_set', 'on', force=True)
+ finally:
+ self.unstash_cache(rpath, name)
+ # TODO: debug1
+ #dv.Set('cl_update_outdate_set', 'on', force=True)
+ return True
+
+ def raiseOutdate(self):
+ self.clVars.Set('cl_update_outdate_set', 'on', force=True)
+
+ def setAutocheckParams(self, status, interval, update_other, cleanpkg):
+ """
+ Настроить параметры автопроверки обновлений
+ """
+ onoff = lambda x: "on" if x else "off"
+ self.clVars.Write('cl_update_autocheck_set', onoff(status), True)
+ self.clVars.Write('cl_update_autocheck_interval', interval, True)
+ self.clVars.Write('cl_update_other_set', onoff(update_other), True)
+ self.clVars.Write('cl_update_cleanpkg_set', onoff(cleanpkg), True)
+ if not status:
+ UpdateInfo.set_update_ready(False)
+ return True
+
+ def checkSchedule(self, interval, status):
+ """
+ Проверить по расписанию необходимость запуска команды
+ """
+ if not status:
+ self.printWARNING(_("Updates autocheck is not enabled"))
+ return False
+ last_check = SystemIni(self.clVars).getVar('system', 'last_check') or ""
+ re_interval = re.compile("^(\d+)\s*(hours?|days?|weeks?)?", re.I)
+ interval_match = re_interval.search(interval)
+ MINUTE = 60
+ HOUR = MINUTE * 60
+ DAY = HOUR * 24
+ WEEK = DAY * 7
+ if interval_match:
+ if interval_match.group(2):
+ suffix_map = {'h': HOUR, 'd': DAY, 'w': WEEK}
+ k = suffix_map.get(interval_match.group(2).lower()[0], HOUR)
+ else:
+ k = HOUR
+ est = int(interval_match.group(1)) * k
+ else:
+ est = 3 * HOUR
+ if last_check:
+ if last_check.isdigit():
+ if (time.time() - int(last_check)) < (est - 10 * MINUTE):
+ self.printWARNING(_("Please wait for the update time"))
+ return False
+ self.mark_schedule()
+ return True
+
+ def checkRun(self, wait_update):
+ """
+ Проверить повторный запуск
+ """
+ update_running = lambda: any(os.getpid() != x
+ for x in
+ search_worked_process('update', dv))
+ dv = self.clVars
+ if update_running():
+ if not wait_update:
+ raise UpdateError(_("Update is already running. "
+ "Try to run later."))
+ else:
+ self.startTask(_("Waiting for another update to be complete"))
+
+ while update_running():
+ self.pauseProcess()
+ while update_running():
+ time.sleep(0.3)
+ self.resumeProcess()
+ time.sleep(random.random() * 3)
+ self.endTask()
+
+ if self.clVars.Get('cl_chroot_status') == 'off':
+ parents_pids = set(getParentPids(os.getpid()))
+ emerge_running = lambda : (
+ any((fnmatch(x[1], "*python-exec/*/emerge*") and
+ x[0] not in parents_pids)
+ for x in getRunCommands(True, withpid=True)))
+ if emerge_running():
+ if not wait_update:
+ raise UpdateError(_("Emerge is running. "
+ "Try to run later."))
+ else:
+ self.startTask(_("Waiting for emerge to be complete"))
+ while emerge_running():
+ time.sleep(1)
+ self.endTask()
+ return True
+
+ @variable_module("update")
+ def trimRepositories(self, repname):
+ """
+ Синхронизировать репозитории
+ """
+ dv = self.clVars
+ rpath = \
+ dv.select('cl_update_rep_path', cl_update_rep_name=repname, limit=1)
+ git = self.getGit()
+ self.addProgress()
+ git.trimRepository(rpath, cb_progress=self.setProgress)
+ return True
+
+ def migrateLayman(self, reposdir, laymandir, repname, rpath_orig):
+ if rpath_orig.startswith("/var/db/repos/"):
+ dn_name = os.path.basename(rpath_orig)
+ repos_fullname = pathJoin(reposdir, dn_name)
+ layman_fullname = pathJoin(laymandir, dn_name)
+ if (not os.path.islink(layman_fullname) and
+ os.path.isdir(layman_fullname) and
+ not os.path.exists(repos_fullname)):
+ self.startTask(_("Move {repname} from {laymandir} to {reposdir}").format(
+ repname=repname.capitalize(),
+ laymandir="/var/lib/layman",
+ reposdir="/var/db/repos"))
+ symlink_target = os.path.relpath(repos_fullname, laymandir)
+ if not os.path.exists(reposdir):
+ makeDirectory(reposdir)
+ os.rename(layman_fullname, repos_fullname)
+ os.symlink(symlink_target, layman_fullname)
+ #print "MYDEBUG", reposdir, laymandir, rpath_orig
+ self.endTask(True)
+ return True
+
+ @variable_module("update")
+ def syncRepositories(self, repname, fallback_sync=False,
+ clean_on_error=True):
+ """
+ Синхронизировать репозитории
+ """
+ dv = self.clVars
+ check_status = dv.GetBool('update.cl_update_check_rep_set')
+ url, rpath, revision = (
+ dv.Select(["cl_update_rep_url", "cl_update_rep_path",
+ "cl_update_rep_rev"],
+ where="cl_update_rep_name", eq=repname, limit=1))
+
+ if not url or not rpath:
+ raise UpdateError(_("Configuration variables for repositories "
+ "are not setup"))
+ git = self.getGit()
+ if not git.checkUrl(url):
+ raise UpdateError(_("Git %s is unavailable") % url)
+ chroot_path = path.normpath(self.clVars.Get('cl_chroot_path'))
+ if chroot_path == '/':
+ rpath_orig = rpath
+ else:
+ rpath_orig = rpath[len(chroot_path):]
+
+ mtime = MTimeKeeper(path.join(rpath, "profiles/updates"))
+ mtime.save()
+ self.migrateLayman(dv.Get('cl_update_repos_storage'),
+ dv.Get('cl_update_layman_storage'), repname, rpath_orig)
+ try:
+ if clean_on_error:
+ try:
+ repconf = ReposConf(dv.Get('cl_update_reposconf'),
+ dv.Get('cl_update_reposconf_dir'),
+ prefix=chroot_path)
+ repconf.add(repname, url, rpath_orig)
+ if not self._syncRepository(repname, url, rpath, revision,
+ cb_progress=self.setProgress,
+ clean=check_status,
+ notask=fallback_sync):
+ return "skip"
+ return True
+ except GitError as e:
+ if not isinstance(e, NotGitError):
+ if e.addon:
+ self.printWARNING(str(e.addon))
+ self.printWARNING(str(e))
+ self.endTask(False)
+ self.startTask(
+ _("Re-fetching the {name} repository").format(
+ name=repname))
+ self.addProgress()
+ rpath_new = "%s_new" % rpath
+ try:
+ self._syncRepository(repname, url, rpath_new, revision,
+ cb_progress=self.setProgress,
+ clean=check_status,
+ notask=fallback_sync)
+ removeDir(rpath)
+ shutil.move(rpath_new, rpath)
+ except OSError as e:
+ raise UpdateError(_("Failed to modify the "
+ "{repname} repository").format(
+ repname=repname) + _(": ") + str(e))
+ finally:
+ if path.exists(rpath_new):
+ removeDir(rpath_new)
+ else:
+ if not self._syncRepository(repname, url, rpath, revision,
+ clean=check_status):
+ return "skip"
+
+ repconf = ReposConf(dv.Get('cl_update_reposconf'),
+ dv.Get('cl_update_reposconf_dir'),
+ prefix=chroot_path)
+ repconf.add(repname, url, rpath_orig)
+ finally:
+ mtime.restore()
+ return True
+
+ metadata_cache_names = ("metadata/md5-cache", "metadata/cache")
+
+ def stash_cache(self, rpath, name):
+ """
+ Спрятать кэш
+ """
+ if name in ("portage", "gentoo"):
+ return
+ if not name in OverlayOwnCache(self.clVars):
+ for cachename in self.metadata_cache_names:
+ cachedir = path.join(rpath, cachename)
+ if path.exists(cachedir):
+ try:
+ cachedir_s = path.join(path.dirname(rpath),
+ path.basename(
+ cachename) + ".stash")
+ if path.exists(cachedir_s):
+ removeDir(cachedir_s)
+ shutil.move(cachedir, cachedir_s)
+ except BaseException as e:
+ pass
+
+ def unstash_cache(self, rpath, name):
+ """
+ Извлеч кэш
+ """
+ if name in ("portage", "gentoo"):
+ return
+ cachenames = self.metadata_cache_names
+ if not name in OverlayOwnCache(self.clVars):
+ if any(path.exists(path.join(rpath, x)) for x in cachenames):
+ for cachename in cachenames:
+ cachedir_s = path.join(path.dirname(rpath),
+ path.basename(cachename) + ".stash")
+ if path.exists(cachedir_s):
+ try:
+ removeDir(cachedir_s)
+ except BaseException as e:
+ pass
+ OverlayOwnCache(self.clVars).add(name)
+ else:
+ for cachename in cachenames:
+ cachedir = path.join(rpath, cachename)
+ cachedir_s = path.join(path.dirname(rpath),
+ path.basename(cachename) + ".stash")
+ if path.exists(cachedir_s):
+ try:
+ shutil.move(cachedir_s, cachedir)
+ except BaseException as e:
+ pass
+ else:
+ if all(not path.exists(path.join(rpath, x)) for x in cachenames):
+ OverlayOwnCache(self.clVars).discard(name)
+
+ def syncOtherRepository(self, repname):
+ """
+ Обновить репозиторий через emerge --sync
+ """
+ emerge = self.get_prog_path('/usr/bin/emerge')
+ if not emerge:
+ raise UpdateError(_("The Emerge is not found"))
+
+ rpath = self.clVars.Select('cl_update_other_rep_path',
+ where='cl_update_other_rep_name', eq=repname,
+ limit=1)
+ repdirname = path.basename(rpath)
+ self.stash_cache(rpath, repdirname)
+ try:
+ if Git.is_git(rpath):
+ self.addProgress()
+ p = PercentProgress(emerge, "--sync", repname, part=1, atty=True)
+ for perc in p.progress():
+ self.setProgress(perc)
+ else:
+ p = process(emerge, "--sync", repname, stderr=STDOUT)
+ if p.failed():
+ raise UpdateError(
+ _("Failed to update the {rname} repository").format(
+ rname=repname),
+ addon=p.read())
+ finally:
+ self.unstash_cache(rpath, repdirname)
+ return True
+
+ def _regenCache_process(self, progname, repname, cpu_num):
+ return process(progname, "--repo=%s" % repname, "--update",
+ "--jobs=%s" % cpu_num, stderr=STDOUT)
+
+ def regenCache(self, repname):
+ """
+ Обновить кэш метаданных репозитория
+ """
+ egenCache = self.get_prog_path('/usr/bin/egencache')
+ if not egenCache:
+ raise UpdateError(_("The Portage tool is not found"))
+ if repname in self.clVars.Get('cl_update_rep_name'):
+ path_rep = self.clVars.Select('cl_update_rep_path',
+ where='cl_update_rep_name',
+ eq=repname, limit=1)
+ repo_name = readFile(
+ path.join(path_rep, "profiles/repo_name")).strip()
+ if repo_name != repname:
+ self.printWARNING(
+ _("Repository '{repo_name}' called '{repname}'"
+ " in cl_update_rep_name").format(
+ repo_name=repo_name, repname=repname))
+ raise UpdateError(_("Failed to update the cache of the {rname} "
+ "repository").format(rname=repname))
+ cpu_num = self.clVars.Get('hr_cpu_num')
+ if repname in OverlayOwnCache(self.clVars):
+ self.printWARNING(
+ _("Repository %s has its own cache") % repname.capitalize())
+ else:
+ self.startTask(_("Updating the %s repository cache") %
+ repname.capitalize())
+ p = self._regenCache_process(egenCache, repname, cpu_num)
+ if p.failed():
+ raise UpdateError(_("Failed to update the cache of the {rname} "
+ "repository").format(rname=repname),
+ addon=p.read())
+ return True
+
+ def emergeMetadata(self):
+ """
+ Выполнить egencache и emerge --metadata
+ """
+ emerge = self.get_prog_path("/usr/bin/emerge")
+ if not emerge:
+ raise UpdateError(_("The Emerge tool is not found"))
+ self.addProgress()
+ p = PercentProgress(emerge, "--ask=n", "--metadata", part=1, atty=True)
+ for perc in p.progress():
+ self.setProgress(perc)
+ if p.failed():
+ data = p.read()
+ with open('/var/log/calculate/failed-metadata-%d.log' % time.time(),
+ 'w') as f:
+ f.write(data + p.alldata)
+ raise UpdateError(_("Failed to update metadata"), addon=data)
+ return True
+
+ def _eixUpdateCommand(self, eix_cmd, countRep):
+ return PercentProgress(eix_cmd, "-F", part=countRep or 1, atty=True)
+
+ def _copyPreviousEix(self):
+ chroot_path = self.clVars.Get("cl_chroot_path")
+ portage_eix = pathJoin(chroot_path, "var/cache/eix/portage.eix")
+ previous_eix = pathJoin(chroot_path, "var/cache/eix/previous.eix")
+ if path.exists(portage_eix):
+ if path.exists(previous_eix):
+ os.unlink(previous_eix)
+ try:
+ with writeFile(previous_eix) as f:
+ f.write(readFile(portage_eix))
+ portage_eix_stat = os.stat(portage_eix)
+ os.chmod(previous_eix, portage_eix_stat.st_mode)
+ os.chown(previous_eix, portage_eix_stat.st_uid,
+ portage_eix_stat.st_gid)
+ except (OSError,IOError):
+ self.printWARNING(_("Failed to create previous eix"))
+ return True
+
+
+ def eixUpdate(self, repositories):
+ """
+ Выполенине eix-update для репозиторием
+
+ eix-update выполнятется только для тех репозиториев, которые
+ обновлялись, если cl_update_eixsync_force==auto, либо
+ все, если cl_update_eixupdate_force==force
+ """
+ self._copyPreviousEix()
+
+ eixupdate = self.get_prog_path("/usr/bin/eix-update")
+ if not eixupdate:
+ raise UpdateError(_("The Eix tool is not found"))
+ self.addProgress()
+ countRep = len(repositories)
+ p = self._eixUpdateCommand(eixupdate, countRep)
+ for perc in p.progress():
+ self.setProgress(perc)
+ if p.failed():
+ raise UpdateError(_("Failed to update eix cache"), addon=p.read())
+ return True
+
+ def is_binary_pkg(self, pkg, binary=None):
+ """
+ Является ли пакет бинарным
+ """
+ if binary:
+ return True
+ if 'PN' in pkg and pkg['PN'].endswith('-bin'):
+ return True
+ if binary is not None:
+ return binary
+ if "binary" in pkg and pkg['binary']:
+ return True
+ return False
+
+ def _printEmergePackage(self, pkg, binary=False, num=1, max_num=1):
+ """
+ Вывод сообщения сборки пакета
+ """
+ self.endTask()
+ _print = self.color_print
+ if self.pkgnum is not None:
+ num = self.pkgnum
+ if self.pkgnummax is not None:
+ max_num = self.pkgnummax
+ one = _print("{0}", num)
+ two = _print("{0}", max_num)
+ part = _("({current} of {maximum})").format(current=one, maximum=two)
+ _print = _print.foreground(Colors.DEFAULT)
+ if self.is_binary_pkg(pkg, binary):
+ _colorprint = _print.foreground(Colors.PURPLE)
+ else:
+ _colorprint = _print.foreground(Colors.GREEN)
+
+ PackageInformation.add_info(pkg)
+ name = ""
+ if pkg.info['DESCRIPTION']:
+ name = _(pkg.info['DESCRIPTION'])
+ name = name[:1].upper() + name[1:]
+ if not name.strip():
+ name = str(pkg)
+
+ self.printSUCCESS(
+ _u(_("{part} {package}")).format(part=_u(part), package=_u(name)))
+ self.startTask(
+ _u(_("Emerging {package}")).format(package=_u(_colorprint(str(pkg)))))
+
+ def _printInstallPackage(self, pkg, binary=False):
+ """
+ Вывод сообщения установки пакета
+ """
+ self.endTask()
+ _print = self.color_print
+ if self.is_binary_pkg(pkg, binary):
+ _print = _print.foreground(Colors.PURPLE)
+ else:
+ _print = _print.foreground(Colors.GREEN)
+ pkg_key = "{CATEGORY}/{PF}".format(**pkg)
+ if pkg_key in self.update_map:
+ self.startTask(_("Installing {pkg} [{oldver}]").format(
+ pkg=_print(str(pkg)), oldver=self.update_map[pkg_key]))
+ self.update_map.pop(pkg_key)
+ else:
+ self.startTask(_("Installing %s") % (_print(str(pkg))))
+
+ def _printFetching(self, fn):
+ """
+ Вывод сообщения о скачивании
+ """
+ self.endTask()
+ self.startTask(_("Fetching binary packages"))
+
+ def _printUninstallPackage(self, pkg, num=1, max_num=1):
+ """
+ Вывод сообщения удаления пакета
+ """
+ self.endTask()
+ _print = self.color_print
+ if num and max_num:
+ one = _print("{0}", num)
+ two = _print("{0}", max_num)
+ part = _(" ({current} of {maximum})").format(current=one,
+ maximum=two)
+ else:
+ part = ""
+ _print = _print.foreground(Colors.LIGHT_RED)
+
+ self.startTask(
+ _("Unmerging{part} {package}").format(part=part,
+ package=_print(str(pkg))))
+
+ def emergelike(self, cmd, *params):
+ """
+ Запуск команды, которая подразумевает выполнение emerge
+ """
+ cmd_path = self.get_prog_path(cmd)
+ if not cmd_path:
+ raise UpdateError(_("Failed to find the %s command") % cmd)
+ with EmergeParser(
+ emerge_parser.CommandExecutor(cmd_path, params)) as emerge:
+ self._startEmerging(emerge)
+ return True
+
+ def revdep_rebuild(self, cmd, *params):
+ """
+ Запуск revdep-rebulid
+ """
+ cmd_path = self.get_prog_path(cmd)
+ if not cmd_path:
+ raise UpdateError(_("Failed to find the %s command") % cmd)
+ with EmergeParser(
+ emerge_parser.CommandExecutor(cmd_path, params)) as emerge:
+ revdep = RevdepPercentBlock(emerge)
+ self.addProgress()
+ revdep.add_observer(self.setProgress)
+ revdep.action = lambda x: (
+ self.endTask(), self.startTask(_("Assigning files to packages"))
+ if "Assign" in revdep else None)
+ self._startEmerging(emerge)
+ return True
+
+ def _display_pretty_package_list(self, pkglist, remove_list=False):
+ """
+ Отобразить список пакетов в "удобочитаемом" виде
+ """
+ _print = self.color_print
+ ebuild_color = TextState.Colors.GREEN
+ binary_color = TextState.Colors.PURPLE
+ remove_color = TextState.Colors.LIGHT_RED
+ flag_map = {"updating":
+ _print.foreground(TextState.Colors.LIGHT_CYAN)("U"),
+ "reinstall":
+ _print.foreground(TextState.Colors.YELLOW)("rR"),
+ "new":
+ _print.foreground(TextState.Colors.LIGHT_GREEN)("N"),
+ "newslot":
+ _print.foreground(TextState.Colors.LIGHT_GREEN)("NS"),
+ "downgrading": (
+ _print.foreground(TextState.Colors.LIGHT_CYAN)("U") +
+ _print.foreground(TextState.Colors.LIGHT_BLUE)("D"))}
+ for pkg in sorted([PackageInformation.add_info(x) for x in
+ pkglist],
+ key=lambda y: y['CATEGORY/PN']):
+ install_flag = ""
+ if remove_list:
+ pkgcolor = _print.foreground(remove_color)
+ else:
+ for flag in flag_map:
+ if pkg[flag]:
+ install_flag = "(%s) " % flag_map[flag]
+ break
+ if self.is_binary_pkg(pkg):
+ pkgcolor = _print.foreground(binary_color)
+ else:
+ pkgcolor = _print.foreground(ebuild_color)
+
+ if pkg.info['DESCRIPTION']:
+ fullname = "%s " % _(pkg.info['DESCRIPTION'])
+ fullname = fullname[:1].upper() + fullname[1:]
+ else:
+ fullname = ""
+ shortname = pkgcolor("%s-%s" % (pkg["CATEGORY/PN"], pkg["PVR"]))
+ if "SIZE" in pkg and pkg['SIZE'] and pkg["SIZE"] != "0 kB":
+ size = " (%s)" % pkg["SIZE"]
+ else:
+ size = ""
+ mult = _print.bold("*")
+ self.printDefault(
+ _u8(" {mult} {fullname}{flag}{shortname}{size}").format(
+ mult=_u8(mult), fullname=_u8(fullname), shortname=_u8(shortname),
+ size=_u8(size),
+ flag=_u8(install_flag)))
+
+ def _display_install_package(self, emerge, emergelike=False):
+ """
+ Отобразить список устанавливаемых пакетов
+ """
+ # подробный список пакетов
+ _print = self.color_print
+ if emergelike:
+ self.printPre(str(emerge.install_packages))
+ else:
+ pkglist = emerge.install_packages.list
+ self.printSUCCESS(_print(
+ _("Listing packages for installation")))
+ self._display_pretty_package_list(pkglist)
+ if emerge.install_packages.remove_list:
+ self.printSUCCESS(_print(
+ _("Listing packages for removal")))
+ self._display_pretty_package_list(
+ emerge.install_packages.remove_list, remove_list=True)
+ if len(emerge.install_packages.list) > 0:
+ install_mess = (_("{count} packages will be installed").format(
+ count=len(emerge.install_packages.list)) + ", ")
+ else:
+ install_mess = ""
+ if str(emerge.download_size) != "0 kB":
+ self.printSUCCESS(_("{install}{size} will be downloaded").format(
+ install=install_mess,
+ size=str(emerge.download_size)))
+
+ def _display_remove_list(self, emerge):
+ """
+ Отобразить список удаляемых пакетов
+ """
+ # подробный список пакетов
+ if self.clVars.Get('update.cl_update_emergelist_set') == 'on':
+ self.printPre(self._emerge_translate(
+ emerge.uninstall_packages.verbose_result))
+ else:
+ _print = self.color_print
+ pkglist = emerge.uninstall_packages.list
+ self.printSUCCESS(_print.bold(
+ _("Listing packages for removal")))
+ self._display_pretty_package_list(pkglist, remove_list=True)
+
+ def mark_schedule(self):
+ """
+ Установить отметку о запуске запланированной проверки
+ """
+ SystemIni(self.clVars).setVar('system', {'last_check': str(int(time.time()))})
+
+ def get_default_emerge_opts(self, depclean=False):
+ bdeps_val = self.clVars.Get('cl_update_with_bdeps_set')
+ if bdeps_val == "auto":
+ bdeps = " --with-bdeps-auto=y"
+ elif bdeps_val == "on":
+ bdeps = " --with-bdeps=y"
+ else:
+ bdeps = " --with-bdeps=n"
+ return self.clVars.Get('cl_emerge_default_opts') + bdeps
+
+ def premerge(self, param, *packages):
+ """
+ Вывести информацию об обновлении
+ """
+ deo = self.get_default_emerge_opts()
+ param = [param, "-pv"]
+
+ with EmergeParser(EmergeCommand(list(packages), emerge_default_opts=deo,
+ extra_params=param)) as emerge:
+ try:
+ emerge.run()
+ if not emerge.install_packages.list:
+ self.printSUCCESS(_("The system is up to date"))
+ self.set_need_update(False)
+ return True
+ emergelike = self.clVars.Get('cl_update_emergelist_set') == 'on'
+ self._display_install_package(emerge, emergelike)
+ if str(emerge.skipped_packages):
+ self._display_error(emerge.skipped_packages)
+ except EmergeError:
+ self.set_need_update(False)
+ self._display_install_package(emerge, emergelike=True)
+ self._display_error(emerge.prepare_error)
+ raise
+ if self.clVars.Get('cl_update_pretend_set') == 'on':
+ # установить кэш: есть обновления
+ self.set_need_update()
+ return True
+ self.set_need_update(False)
+ answer = self.askConfirm(
+ _("Would you like to merge these packages?"), "yes")
+ if answer == "no":
+ raise KeyboardInterrupt
+ return "yes"
+ return True
+
+ def set_need_update(self, val=True):
+ """
+ Установить флаг: есть обновления
+ """
+ if self.clVars.Get('update.cl_update_autocheck_set') == 'off':
+ val = False
+ UpdateInfo.set_update_ready(val)
+ return True
+
+ def _emerge_translate(self, s):
+ """
+ Перевести текст из emerge
+ """
+ return RegexpLocalization('cl_emerge').translate(str(s))
+
+ def setUpToDateCache(self):
+ """
+ Установить кэш - "нет пакетов для обновления"
+ """
+ #self.updateCache(PackageList([]))
+ self.set_need_update(False)
+ return True
+
+ def _startEmerging(self, emerge):
+ """
+ Настроить и выполнить emerge
+ """
+ if emerge.install_packages and emerge.install_packages.list:
+ for pkg in emerge.install_packages.list:
+ rv = pkg.get('REPLACING_VERSIONS', '')
+ if rv:
+ self.update_map["{CATEGORY}/{PF}".format(**pkg)] = \
+ rv.partition(":")[0]
+ emerge.command.send("yes\n")
+ emerge.emerging.add_observer(self._printEmergePackage)
+ emerge.installing.add_observer(self._printInstallPackage)
+ emerge.uninstalling.add_observer(self._printUninstallPackage)
+ emerge.fetching.add_observer(self._printFetching)
+
+ def cancel_observing_fetch(fn):
+ emerge.fetching.clear_observers()
+
+ emerge.fetching.add_observer(cancel_observing_fetch)
+ try:
+ emerge.run()
+ except EmergeError:
+ if emerge.emerging_error:
+ self._display_error(emerge.emerging_error.log)
+ else:
+ self._display_error(emerge.prepare_error)
+ raise
+
+ def _display_error(self, error):
+ lines_num = int(self.clVars.Get('update.cl_update_lines_limit'))
+ error = "
".join(str(error).split('
')[-lines_num:])
+ self.printPre(self._emerge_translate(error))
+
+ def emerge(self, use, param, *packages):
+ """
+ Выполнить сборку пакета
+ """
+ deo = self.get_default_emerge_opts()
+ if not packages:
+ packages = [param]
+ extra_params = None
+ else:
+ extra_params = [param]
+
+ command = EmergeCommand(list(packages), emerge_default_opts=deo,
+ extra_params=extra_params,
+ use=use)
+ if self.clVars.Get('cl_chroot_path') != '/':
+ command = Chroot(self.clVars.Get('cl_chroot_path'), command)
+
+ with EmergeParser(command) as emerge:
+ try:
+ emerge.question.action = lambda x: False
+ emerge.run()
+ if not emerge.install_packages.list:
+ return True
+ except EmergeError:
+ self._display_error(emerge.prepare_error)
+ raise
+ self._startEmerging(emerge)
+ return True
+
+ def emerge_ask(self, pretend, *params):
+ """
+ Вывести информацию об обновлении
+ """
+ deo = self.get_default_emerge_opts()
+ param = [x for x in params if x.startswith("-")]
+ packages = [x for x in params if not x.startswith("-")]
+ command = EmergeCommand(list(packages), emerge_default_opts=deo,
+ extra_params=param)
+ with EmergeParser(command) as emerge:
+ try:
+ emerge.question.action = lambda x: False
+ emerge.run()
+ if emerge.install_packages.list:
+ emergelike = self.clVars.Get(
+ 'update.cl_update_emergelist_set') == 'on'
+ self._display_install_package(emerge, emergelike)
+ if emerge.skipped_packages:
+ self._display_error(emerge.skipped_packages)
+ if not pretend:
+ answer = self.askConfirm(
+ _("Would you like to merge these packages?"), "yes")
+ if answer == "no":
+ emerge.command.send("no\n")
+ raise KeyboardInterrupt
+ else:
+ return True
+ else:
+ self.set_need_update(False)
+ self.printSUCCESS(_("The system is up to date"))
+ except EmergeError:
+ self.set_need_update(False)
+ self._display_install_package(emerge, emergelike=True)
+ self._display_error(emerge.prepare_error)
+ raise
+ self._startEmerging(emerge)
+ return True
+
+ def depclean(self):
+ """
+ Выполнить очистку системы от лишних пакетов
+ """
+ deo = self.get_default_emerge_opts(depclean=True)
+ emerge = None
+ try:
+ emerge = EmergeParser(EmergeCommand(["--depclean",
+ "--dynamic-deps=n"],
+ emerge_default_opts=deo))
+ outdated_kernel = False
+ try:
+ emerge.question.action = lambda x: False
+ emerge.run()
+ if not emerge.uninstall_packages.list:
+ UpdateInfo(self.clVars).outdated_kernel = False
+ return True
+ kernel_pkg = self.clVars.Get('cl_update_kernel_pkg')
+ if any(("%s-%s" % (x['CATEGORY/PN'], x['PVR'])) == kernel_pkg
+ for x in emerge.uninstall_packages.list):
+ pkglist = [
+ "=%s-%s" % (x['CATEGORY/PN'], x['PVR']) for x in
+ emerge.uninstall_packages.list
+ if ("%s-%s" % (x['CATEGORY/PN'],
+ x['PVR'])) != kernel_pkg]
+ emerge.command.send('n\n')
+ emerge.close()
+ emerge = None
+ if not pkglist:
+ UpdateInfo(self.clVars).outdated_kernel = True
+ return True
+ emerge = EmergeParser(
+ EmergeCommand(pkglist,
+ extra_params=["--unmerge", '--ask=y'],
+ emerge_default_opts=deo))
+ emerge.question.action = lambda x: False
+ emerge.run()
+ outdated_kernel = True
+ else:
+ outdated_kernel = False
+ self._display_remove_list(emerge)
+ except EmergeError:
+ self._display_error(emerge.prepare_error)
+ raise
+ if (self.askConfirm(
+ _("Would you like to unmerge these unused packages "
+ "(recommended)?")) != 'yes'):
+ return True
+ UpdateInfo(self.clVars).outdated_kernel = outdated_kernel
+ self._startEmerging(emerge)
+ finally:
+ if emerge:
+ emerge.close()
+ return True
+
+ def update_task(self, task_name):
+ """
+ Декоратор для добавления меток запуска и останова задачи
+ """
+
+ def decor(f):
+ def wrapper(*args, **kwargs):
+ logger = EmergeLog(EmergeLogNamedTask(task_name))
+ logger.mark_begin_task()
+ ret = f(*args, **kwargs)
+ if ret:
+ logger.mark_end_task()
+ return ret
+
+ return wrapper
+
+ return decor
+
+ def migrateCacheRepository(self, url, branch, storage):
+ """
+ Перенести репозиторий из кэша в локальный
+ """
+ rep = storage.get_repository(url, branch)
+ if rep:
+ rep.storage = storage.storages[0]
+ self.clVars.Invalidate('cl_update_profile_storage')
+ return True
+
+ def reconfigureProfileVars(self, profile_dv, chroot):
+ """
+ Синхронизировать репозитории
+ Настройка переменных для выполнения синхронизации репозиториев
+ """
+ dv = self.clVars
+
+ try:
+ if not profile_dv:
+ raise UpdateError(
+ _("Failed to use the new profile. Try again."))
+ for var_name in ('cl_update_rep_path',
+ 'cl_update_rep_url',
+ 'cl_update_rep_name',
+ 'cl_update_branch',
+ 'cl_update_binhost_list',
+ 'cl_update_binhost_unstable_list',
+ 'cl_update_binhost_stable_set',
+ 'cl_update_binhost_stable_opt_set',
+ 'cl_update_branch_name',
+ 'cl_profile_system',
+ 'cl_update_rep'):
+ dv.Set(var_name, profile_dv.Get(var_name), force=True)
+ dv.Set('cl_chroot_path', chroot, force=True)
+ except DataVarsError as e:
+ error = UpdateError(_("Wrong profile"))
+ error.addon = e
+ raise error
+ return True
+
+ def setProfile(self, profile_shortname):
+ profile = self.clVars.Select('cl_update_profile_path',
+ where='cl_update_profile_shortname',
+ eq=profile_shortname, limit=1)
+ if not profile:
+ raise UpdateError(_("Failed to determine profile %s") %
+ self.clVars.Get('cl_update_profile_system'))
+ profile_path = path.relpath(profile, '/etc/portage')
+ try:
+ profile_file = '/etc/portage/make.profile'
+ if not path.exists(
+ path.join(path.dirname(profile_file), profile_path)):
+ raise UpdateError(
+ _("Failed to set the profile: %s") % _("Profile not found"))
+ for rm_fn in filter(path.lexists,
+ ('/etc/make.profile',
+ '/etc/portage/make.profile')):
+ os.unlink(rm_fn)
+ os.symlink(profile_path, profile_file)
+ except (OSError, IOError) as e:
+ raise UpdateError(_("Failed to set the profile: %s") % str(e))
+ return True
+
+ def applyProfileTemplates(self, useClt=None, cltFilter=False,
+ useDispatch=True, action="merge"):
+ """
+ Наложить шаблоны из профиля
+ """
+ from calculate.lib.cl_template import TemplatesError, ProgressTemplate
+
+ dv = DataVarsUpdate()
+ try:
+ dv.importUpdate()
+ dv.flIniFile()
+ dv.Set('cl_action', action, force=True)
+ try:
+ dv.Set('cl_templates_locate',
+ self.clVars.Get('cl_update_templates_locate'))
+ except VariableError:
+ self.printERROR(_("Failed to apply profiles templates"))
+ return True
+ dv.Set("cl_chroot_path", '/', True)
+ dv.Set("cl_root_path", '/', True)
+ for copyvar in ("cl_dispatch_conf", "cl_verbose_set",
+ "update.cl_update_world"):
+ dv.Set(copyvar, self.clVars.Get(copyvar), True)
+ # определение каталогов содержащих шаблоны
+ useClt = useClt in ("on", True)
+ self.addProgress()
+ nullProgress = lambda *args, **kw: None
+ dispatch = self.dispatchConf if useDispatch else None
+ clTempl = ProgressTemplate(nullProgress, dv, cltObj=useClt,
+ cltFilter=cltFilter,
+ printSUCCESS=self.printSUCCESS,
+ printWARNING=self.printWARNING,
+ askConfirm=self.askConfirm,
+ dispatchConf=dispatch,
+ printERROR=self.printERROR)
+ try:
+ clTempl.applyTemplates()
+ if clTempl.hasError():
+ if clTempl.getError():
+ raise TemplatesError(clTempl.getError())
+ finally:
+ if clTempl:
+ if clTempl.cltObj:
+ clTempl.cltObj.closeFiles()
+ clTempl.closeFiles()
+ finally:
+ dv.close()
+ return True
+
+ def cleanpkg(self):
+ """
+ Очистить PKGDIR и DISTFILES в текущей системе
+ """
+ portdirs = ([self.clVars.Get('cl_portdir')] +
+ self.clVars.Get('cl_portdir_overlay'))
+ pkgfiles = get_packages_files_directory(*portdirs)
+ distdirfiles = get_manifest_files_directory(*portdirs)
+ distdir = self.clVars.Get('install.cl_distfiles_path')
+ pkgdir = self.clVars.Get('cl_pkgdir')
+
+ logger = log("update_cleanpkg.log",
+ filename="/var/log/calculate/update_cleanpkg.log",
+ formatter="%(asctime)s - %(clean)s - %(message)s")
+
+ return self._cleanpkg(
+ distdir, pkgdir, distdirfiles, pkgfiles, logger)
+
+ def _update_binhost_packages(self):
+ os.system('/usr/sbin/emaint binhost -f &>/dev/null')
+
+ def _cleanpkg(self, distdir, pkgdir, distdirfiles, pkgfiles, logger):
+ """
+ Общий алгоритм очистки distfiles и pkgdir от устаревших пакетов
+ """
+ skip_files = ["/metadata.dtd", "/Packages"]
+ try:
+ if self.clVars.Get('client.os_remote_auth'):
+ skip_files += ['portage_lockfile']
+ except DataVarsError:
+ pass
+
+ for cleantype, filelist in (
+ ("packages",
+ get_remove_list(pkgdir, list(pkgfiles), depth=4)),
+ ("distfiles",
+ get_remove_list(distdir, list(distdirfiles), depth=1))):
+ removelist = []
+ for fn in filelist:
+ try:
+ if not any(fn.endswith(x) for x in skip_files):
+ os.unlink(fn)
+ removelist.append(path.basename(fn))
+ except OSError:
+ pass
+ removelist_str = ",".join(removelist)
+ if removelist_str:
+ logger.info(removelist_str, extra={'clean': cleantype})
+ if cleantype == "packages":
+ try:
+ self._update_binhost_packages()
+ for dn in listDirectory(pkgdir, fullPath=True):
+ if path.isdir(dn) and not listDirectory(dn):
+ os.rmdir(dn)
+ except OSError:
+ pass
+ return True
+
+ def updateSetupCache(self):
+ cache = SetupCache(self.clVars)
+ cache.update(force=True)
+ return True
+
+ def get_bin_cache_filename(self):
+ return pathJoin(self.clVars.Get('cl_chroot_path'),
+ LayeredIni.IniPath.Grp)
+
+ def update_local_info_binhost(self, write_binhost=True):
+ """
+ Проверить, что доступен хотя бы один из binhost'ов
+ :return:
+ """
+ hosts = self.clVars.Get("update.cl_update_binhost_host")
+ datas = self.clVars.Get("update.cl_update_binhost_revisions")
+ if not hosts:
+ self.delete_binhost()
+ raise UpdateError(_("Failed to find the server with "
+ "appropriate updates"))
+ else:
+ with writeFile(self.get_bin_cache_filename()) as f:
+ f.write(datas[0].strip()+"\n")
+ if write_binhost:
+ if hosts[0] != self.clVars.Get('update.cl_update_binhost'):
+ self.refresh_binhost = True
+ self.clVars.Set('cl_update_package_cache_set', 'on')
+ self.clVars.Write('cl_update_binhost', hosts[0], location="system")
+ new_ts = self.clVars.Get("update.cl_update_binhost_timestamp")
+ if new_ts:
+ new_ts = new_ts[0]
+ if new_ts.isdigit():
+ ini = SystemIni(self.clVars)
+ ini.setVar('system', {'last_update': new_ts})
+ if self.is_update_action(self.clVars.Get("cl_action")):
+ value = self.clVars.GetBool('update.cl_update_binhost_stable_set')
+ new_value = self.clVars.GetBool('update.cl_update_binhost_stable_opt_set')
+ if value != new_value:
+ self.clVars.Write(
+ 'cl_update_binhost_stable_set',
+ self.clVars.Get('update.cl_update_binhost_stable_opt_set'),
+ location="system")
+ return True
+
+ def is_update_action(self, action):
+ return action == 'sync'
+
+ def modify_binary_depends(self, bdeps, prefix="/"):
+ """
+ Включить/выключить сборочные зависимости для бинарных пакетов
+ """
+ hide = bdeps == "auto"
+ self.startTask(_("Updating binary build dependences"))
+ vardb = Vardb(pathJoin(prefix,VDB_PATH))
+ if hide:
+ vardb.hide_binary_depends()
+ else:
+ vardb.unhide_binary_depends()
+ self.endTask()
+ return True
+
+ def save_with_bdeps(self):
+ oldval = self.clVars.Get('cl_update_with_bdeps_set')
+ newval = self.clVars.Get('cl_update_with_bdeps_opt_set')
+ if oldval != newval:
+ self.modify_binary_depends(newval)
+ self.clVars.Write('cl_update_with_bdeps_set', newval,
+ location="system")
+ self.clVars.Set('cl_update_force_depclean_set', 'on')
+ return True
+
+ def message_binhost_changed(self):
+ if self.refresh_binhost:
+ self.printWARNING(_("Update server was changed to %s") %
+ self.clVars.Get('update.cl_update_binhost'))
+ self.clVars.Set("update.cl_update_package_cache_set",
+ Variable.On, force=True)
+ else:
+ self.printSUCCESS(_("Update server %s") %
+ self.clVars.Get('update.cl_update_binhost'))
+ return True
+
+ def delete_binhost(self):
+ self.clVars.Delete('cl_update_binhost', location="system")
+ try:
+ bin_cache_fn = self.get_bin_cache_filename()
+ if path.exists(bin_cache_fn):
+ os.unlink(bin_cache_fn)
+ except OSError:
+ raise UpdateError(
+ _("Failed to remove cached ini.env of binary repository"))
+ try:
+ for varname in ('update.cl_update_package_cache',
+ 'update.cl_update_package_cache_sign'):
+ fn = self.clVars.Get(varname)
+ if path.exists(fn):
+ os.unlink(fn)
+ except OSError:
+ raise UpdateError(
+ _("Failed to remove cached Package index"))
+ # удалить binhost
+ binhost_fn = self.inchroot(
+ self.clVars.Get("update.cl_update_portage_binhost_path"))
+ if path.exists(binhost_fn):
+ os.unlink(binhost_fn)
+ return False
+
+ def update_binhost_list(self, dv=None):
+ """
+ Обновить список binhost'ов после обновления до master веток
+ :return:
+ """
+ changes = False
+ try:
+ if dv is None:
+ dv = DataVarsUpdate()
+ dv.importUpdate()
+ dv.flIniFile()
+ for varname in ('update.cl_update_binhost_list',
+ 'update.cl_update_binhost_unstable_list',
+ 'update.cl_update_binhost_timestamp_path',
+ 'update.cl_update_binhost_revision_path'):
+ new_value = dv.Get(varname)
+ old_value = self.clVars.Get(varname)
+ if new_value != old_value:
+ changes = True
+ self.clVars.Set(varname, new_value, force=True)
+ except DataVarsError as e:
+ raise UpdateError(_("Failed to get values for binhost search"))
+
+ if not changes:
+ return False
+ self.create_binhost_data()
+ return True
+
+ def drop_binhosts(self, dv):
+ """
+ Обновление до master веток
+ """
+ branch = dv.Get('update.cl_update_branch')
+ revs = [
+ branch for x in dv.Get('update.cl_update_rep_name')
+ ]
+ dv.Set('update.cl_update_branch_name', revs)
+ dv.Invalidate('update.cl_update_rep_rev')
+ return True
+
+ def inchroot(self, fn):
+ return pathJoin(self.clVars.Get("cl_chroot_path"), fn)
+
+ def prepare_gpg(self):
+ """
+ Получить объект для проверки подписи, либо получить заглушку
+ """
+ gpg_force = self.clVars.Get('cl_update_gpg_force')
+ gpg_keys = [self.inchroot(x)
+ for x in self.clVars.Get('cl_update_gpg_keys')]
+ if gpg_force == "skip":
+ return True
+ gpgtmpdir = "/var/calculate/tmp/update"
+ if not path.exists(gpgtmpdir):
+ makeDirectory(gpgtmpdir)
+ gpg = GPG(tempfile.mkdtemp(dir=gpgtmpdir,
+ prefix="gpg-"))
+ for keyfn in gpg_keys:
+ if path.exists(keyfn):
+ try:
+ key = readFile(keyfn)
+ gpg.import_key(key)
+ except GPGError as e:
+ self.printWARNING(_("Failed to load public keys from '%s' "
+ "for signature checking") % keyfn)
+ if not gpg.count_public():
+ if gpg_force == "force":
+ raise UpdateError(_("Public keys for Packages signature checking not found"))
+ else:
+ return True
+ self.clVars.Set('update.cl_update_gpg', gpg, force=True)
+ return True
+
+ def download_packages(self, url_binhost, packages_fn, packages_sign_fn, gpg):
+ quite_unlink(packages_fn)
+ orig_packages = Binhosts.fetch_packages(url_binhost)
+ try:
+ with writeFile(packages_fn) as f:
+ pi = PackagesIndex(orig_packages)
+ pi["TTL"] = str(30 * DAYS)
+ pi["DOWNLOAD_TIMESTAMP"] = str(int(time.time()))
+ pi.write(f)
+ except (OSError, IOError):
+ raise UpdateError(_("Failed to save Packages"))
+ self.endTask(True)
+ self.startTask(_("Check packages index signature"))
+ if not gpg:
+ self.endTask("skip")
+ self.clVars.Set('cl_update_package_cache_set', Variable.Off, force=True)
+ return True
+ try:
+ Binhosts.check_packages_signature(
+ url_binhost, orig_packages, gpg)
+ with writeFile(packages_sign_fn) as f:
+ f.write(Binhosts.fetch_packages_sign(url_binhost))
+ except BinhostSignError:
+ for fn in (packages_fn, packages_sign_fn):
+ if path.exists(fn):
+ try:
+ os.unlink(fn)
+ except OSError:
+ pass
+ self.clVars.Set("update.cl_update_bad_sign_set", Variable.On)
+ self.clVars.Set('update.cl_update_binhost_recheck_set', Variable.On)
+ self.clVars.Set('cl_update_package_cache_set', Variable.Off, force=True)
+ raise
+ return True
+
+ class Reason(object):
+ Success = 0
+ BadSign = 1
+ Outdated = 2
+ Skip = 3
+ Updating = 4
+ WrongBinhost = 5
+ BadEnv = 6
+ EnvNotFound = 7
+ SkipSlower = 8
+ UnknownError = 9
+
+
+ @staticmethod
+ def humanReadable(reason):
+ return {
+ Update.Reason.WrongBinhost: "FAILED (Wrong binhost)",
+ Update.Reason.Outdated: "OUTDATED",
+ Update.Reason.Updating: "UPDATING",
+ Update.Reason.BadEnv: "FAILED (Bad env)",
+ Update.Reason.EnvNotFound: "FAILED (Env not found)",
+ Update.Reason.UnknownError: "FAILED (Unknown error)",
+ Update.Reason.BadSign: "FAILED (Bad sign)",
+ Update.Reason.Skip: "SKIP",
+ Update.Reason.SkipSlower: "",
+ Update.Reason.Success: ""
+ }.get(reason,reason)
+
+ def _get_binhost_logger(self):
+ return log("binhost-scan.log",
+ filename=pathJoin(
+ self.clVars.Get('cl_chroot_path'),
+ "/var/log/calculate/binhost-scan.log"),
+ formatter="%(message)s")
+
+ def get_arch_machine(self):
+ return self.clVars.Get('os_arch_machine')
+
+ @variable_module("update")
+ def create_binhost_data(self):
+ dv = self.clVars
+ last_ts = dv.Get('cl_update_last_timestamp')
+ if dv.GetBool('cl_update_binhost_stable_opt_set'):
+ binhost_list = dv.Get('cl_update_binhost_list')
+ else:
+ binhost_list = dv.Get('cl_update_binhost_unstable_list')
+ self.binhosts_data = Binhosts(
+ # значение малозначимо, поэтому берётся из собирающей системы
+ dv.GetInteger('cl_update_binhost_timeout'),
+ dv.Get('cl_update_binhost_revision_path'),
+ dv.Get('cl_update_binhost_timestamp_path'),
+ last_ts, binhost_list,
+ self.get_arch_machine(),
+ gpg=dv.Get('update.cl_update_gpg'))
+ return True
+
+ @variable_module("update")
+ def _search_best_binhost(self, binhosts_data, stabilization):
+ if not self.clVars.Get('cl_ebuild_phase'):
+ logger = self._get_binhost_logger()
+ if logger:
+ logger.info(
+ "Started scan on: {date}, current timestamp: {ts}".format(
+ date=time.ctime(), ts=binhosts_data.last_ts))
+ retval = []
+ skip_check_status = False
+ actual_reason = self.Reason.UnknownError
+ for binhost in sorted(binhosts_data.get_binhosts(), reverse=True):
+ host = binhost.host
+ if not binhost.valid:
+ reason = self.Reason.WrongBinhost
+ elif binhost.outdated:
+ reason = self.Reason.Outdated
+ elif not skip_check_status:
+ status = binhost.status
+ if status is not binhosts_data.BinhostStatus.Success:
+ errors = {
+ binhosts_data.BinhostStatus.Updating: self.Reason.Updating,
+ binhosts_data.BinhostStatus.BadEnv: self.Reason.BadEnv,
+ binhosts_data.BinhostStatus.EnvNotFound: self.Reason.EnvNotFound
+ }
+ reason = errors.get(status, self.Reason.UnknownError)
+ elif binhost.bad_sign:
+ reason = self.Reason.BadSign
+ else:
+ # SUCCESS
+ if not binhost.downgraded or stabilization:
+ host = "-> %s" % host
+ reason = self.Reason.Success
+ else:
+ reason = self.Reason.Skip
+ elif binhost.downgraded:
+ reason = self.Reason.Skip
+ else:
+ reason = self.Reason.SkipSlower
+
+ if reason == self.Reason.Success:
+ retval.append([binhost.host, binhost.data,
+ str(binhost.timestamp),
+ str(binhost.duration)])
+ skip_check_status = True
+ if reason < actual_reason:
+ actual_reason = reason
+ logger.info("{host:<60} {speed:<7} {timestamp:<10} {reason}".format(
+ host=host, speed=float(binhost.duration) / 1000.0,
+ timestamp=binhost.timestamp,
+ reason=Update.Reason.humanReadable(reason)))
+ if not retval:
+ if actual_reason is self.Reason.BadSign:
+ raise UpdateError(_("Failed to find the reliable server with appropriate updates"))
+ elif actual_reason in (self.Reason.Outdated,
+ self.Reason.Skip,
+ self.Reason.Updating):
+ raise UpdateError(_("Failed to find the server with appropriate updates"))
+ else:
+ raise UpdateError(_("Failed to find the working server with updates"))
+ return retval
+
+ def check_current_binhost(self, binhost_url):
+ """
+ Проверка текущего сервера обновлений на валидность
+ """
+ if not binhost_url in self.binhosts_data.binhost_list:
+ raise UpdateError(_("Current binhost is absent in list of update servers"))
+ binhost = self.binhosts_data.get_binhost(binhost_url)
+
+ if binhost.valid and not binhost.outdated and not binhost.downgraded:
+ if binhost.status == self.binhosts_data.BinhostStatus.Success:
+ self.clVars.Set('update.cl_update_binhost_data',
+ [[binhost.host, binhost.data,
+ str(binhost.timestamp),
+ str(binhost.duration)]],
+ force=True)
+ self.endTask()
+ else:
+ if not binhost.valid:
+ raise UpdateError(
+ _("Current binhost {} is not valid").format(binhost_url))
+ elif binhost.outdated:
+ raise UpdateError(
+ _("Current binhost {} is outdated").format(binhost_url))
+ elif binhost.downgraded:
+ raise UpdateError(
+ _("Binary packages on the current binhost {} "
+ "are older than local").format(binhost_url))
+ if self.binhosts_data.gpg:
+ packages_fn = self.clVars.Get('update.cl_update_package_cache')
+ packages_sign_fn = self.clVars.Get('update.cl_update_package_cache_sign')
+ if path.exists(packages_fn) and path.exists(packages_sign_fn):
+ packages_sign = readFile(packages_sign_fn)
+ pi = PackagesIndex(readFile(packages_fn))
+ pi.clean()
+ try:
+ Binhosts.check_packages_signature(
+ None, pi.get_value(), self.binhosts_data.gpg,
+ sign=packages_sign)
+ return True
+ except BinhostSignError:
+ for fn in (packages_fn, packages_sign_fn):
+ if path.exists(fn):
+ try:
+ os.unlink(fn)
+ except OSError:
+ pass
+ if binhost.bad_sign:
+ raise UpdateError(
+ _("Current binhost {} has wrong signature").format(
+ binhost_url))
+ return True
+
+ @variable_module("update")
+ def detect_best_binhost(self):
+ # выполняется переход с серверов unstable обновлней на stable
+ # в этом случае не важно, что бинари могут старее текущих
+ if (self.clVars.GetBool('cl_update_binhost_stable_opt_set') and
+ not self.clVars.GetBool('cl_update_binhost_stable_set')):
+ stabilization = True
+ else:
+ stabilization = False
+
+ self.startTask(_("Searching new binhost"))
+ retval = self._search_best_binhost(self.binhosts_data, stabilization)
+
+ self.clVars.Set('update.cl_update_binhost_data',
+ retval or Variable.EmptyTable, force=True)
+
+ self.endTask()
+ return True
+
+ def update_rep_list(self):
+ """
+ Обновить список доступных репозиториев
+ :param builder_path:
+ :return:
+ """
+ cmd = "/usr/bin/eselect"
+ cmd_path = self.get_prog_path(cmd)
+ if not cmd_path:
+ raise UpdateError(_("Failed to find the %s command") % cmd)
+ repsync = emerge_parser.CommandExecutor(cmd_path, ["repository", "list"])
+ repsync.execute()
+ return repsync.success()
+
+ def rename_custom_files(self):
+ """
+ Переименовать все custom файлы: keywords, use, sets и т.д. в связи
+ с изменением профиля
+ """
+ newdv = self.clVars.Get("cl_update_profile_datavars")
+ cur_short = self.clVars.Get("os_linux_shortname").lower()
+ new_short = newdv["os_linux_shortname"].lower()
+ if cur_short != new_short:
+ for fn in ("/etc/portage/package.keywords/custom.{}",
+ "/etc/portage/package.use/custom.{}",
+ "/etc/portage/package.mask/custom.{}",
+ "/etc/portage/package.unmask/custom.{}",
+ "/etc/portage/sets/custom.{}",
+ "/etc/portage/make.conf/custom.{}"):
+ fn_source = fn.format(cur_short)
+ fn_target = fn.format(new_short)
+ try:
+ if path.exists(fn_source) and not path.exists(fn_target):
+ os.rename(fn_source, fn_target)
+ except (OSError, IOError) as e:
+ self.printWARNING(str(e))
+ world_sets_fn = "/var/lib/portage/world_sets"
+ if path.exists(world_sets_fn):
+ worlds_sets = readFile(world_sets_fn)
+ new_worlds_sets = re.sub("^@custom.{}$".format(cur_short),
+ "@custom.{}".format(new_short),
+ worlds_sets, flags=re.M)
+ try:
+ with open(world_sets_fn, 'w') as fd:
+ fd.write(new_worlds_sets)
+ except IOError as e:
+ self.printWARNING(str(e))
+ return True
+
+ def save_portage_state_hash(self):
+ """
+ Сохранить состояние
+ """
+ ini = SystemIni(self.clVars)
+ ps = PortageState()
+ new_portage_state_hash = ps.get_state()
+ ini.setVar('system', {'portage_hash': new_portage_state_hash})
+ return True
+
+ def drop_portage_state_hash(self):
+ """
+ Сбросить состояние
+ """
+ ini = SystemIni(self.clVars)
+ ini.delVar('system', 'portage_hash')
+ return True
+
+ def update_fastlogin_domain_path(self):
+ try:
+ if not self.clVars.Get('client.cl_remote_host'):
+ return True
+ except:
+ return True
+ fn = '/var/lib/calculate/calculate-desktop/fastlogin-domain'
+ try:
+ with writeFile(fn) as f:
+ f.write("{}\n".format(
+ "\n".join(get_fastlogin_domain_path(self.clVars))))
+ except Exception:
+ if os.path.exists(fn):
+ try:
+ os.unlink(fn)
+ except Exception:
+ pass
+ return True
diff --git a/libs_crutch/update/update_info.py b/libs_crutch/update/update_info.py
index 40c0a47..7b22157 100644
--- a/libs_crutch/update/update_info.py
+++ b/libs_crutch/update/update_info.py
@@ -16,50 +16,13 @@
import os
from os import path
-# from itertools import ifilter
from calculate.core.datavars import DataVarsCore
-# from calculate.core.server.gen_pid import search_worked_process
+from calculate.core.server.gen_pid import search_worked_process
from calculate.lib.cl_template import SystemIni
from calculate.lib.utils.content import getCfgFiles
from calculate.lib.utils.files import getRunCommands, readFile, writeFile
-# from core:
-#######################################
-class ProcessStatus(object):
- SuccessFinished = 0
- Worked = 1
- FailedFinished = 2
- NotFound = 3
- Paused = 4
-
-def search_worked_process(method_name, clVars,
- statuses=(ProcessStatus.Worked,)):
- """
- Найти все работающие процессы
-
- Возвращает список процессов со статусом Worked и существующем системным
- процессом
- """
-
- def generator():
- pids = clVars.Get('core.cl_core_pids_path')
- for pidfile in listDirectory(pids, fullPath=True):
- try:
- status = pickle.load(open(pidfile))
- if ((method_name is None or status['name'] == method_name) and
- status['status'] in statuses):
- pid_path = path.join("/proc", str(status['os_pid']))
- if path.exists(pid_path):
- cmdline = readFile(path.join(pid_path, "cmdline"))
- if cmdline and any(x in cmdline
- for x in get_symlink_commands()):
- yield status['os_pid']
- except (socket.error, ValueError, KeyError, EOFError, OSError):
- pass
-
- return list(generator())
-###################################
class UpdateInfo(object):
"""
Информационный объект о процессе обновления
diff --git a/libs_crutch/update/update_tasks.py b/libs_crutch/update/update_tasks.py
new file mode 100644
index 0000000..ffaf0e9
--- /dev/null
+++ b/libs_crutch/update/update_tasks.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2015-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.
+
+class EmergeMark:
+ Schedule = "schedule"
+ Premerge = "check updates"
+ PythonUpdater = "update python modules"
+ PerlCleaner = "update perl modules"
+ KernelModules = "update kernel modules"
+ Depclean = "depclean"
+ XorgModules = "update xorg modules"
+ PreservedLibs = "update preserved libs"
+ RevdepRebuild = "revdep rebuild"
+ Prelink = "prelink"
+ Automagic = "check for auto depends"
diff --git a/libs_crutch/update/utils/__init__.py b/libs_crutch/update/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/libs_crutch/update/utils/cl_setup_update.py b/libs_crutch/update/utils/cl_setup_update.py
new file mode 100644
index 0000000..c51fefa
--- /dev/null
+++ b/libs_crutch/update/utils/cl_setup_update.py
@@ -0,0 +1,47 @@
+# -*- 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 sys
+from calculate.core.server.func import Action
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.utils.files import FilesError
+from calculate.update.update import UpdateError
+from calculate.lib.utils.git import GitError
+
+_ = lambda x: x
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClSetupUpdateAction(Action):
+ """
+ Действие для настройки параметров автопроверки обновлений
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, UpdateError, GitError)
+
+ successMessage = __("Updates autocheck configured!")
+ failedMessage = __("Failed to configure the updates autocheck procedure!")
+ interruptMessage = __("Configuration manually interrupted")
+
+ # список задач для действия
+ tasks = [
+ {'name': 'set_variables',
+ 'method': 'Update.setAutocheckParams(cl_update_autocheck_set,'
+ 'cl_update_autocheck_interval,'
+ 'cl_update_other_set,'
+ 'cl_update_cleanpkg_set)'}
+ ]
diff --git a/libs_crutch/update/utils/cl_update.py b/libs_crutch/update/utils/cl_update.py
new file mode 100644
index 0000000..f139015
--- /dev/null
+++ b/libs_crutch/update/utils/cl_update.py
@@ -0,0 +1,457 @@
+# -*- 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 sys
+from calculate.core.server.func import Action, Tasks, AllTasks
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.cl_template import TemplatesError
+from calculate.lib.utils.binhosts import BinhostError
+from calculate.lib.utils.files import FilesError, readFile
+from calculate.update.update import UpdateError
+from calculate.update.emerge_parser import EmergeError
+from calculate.lib.utils.git import GitError
+from calculate.lib.utils.portage import (EmergeLog, isPkgInstalled,
+ EmergeLogNamedTask, PackageList)
+from calculate.update.update_tasks import EmergeMark
+
+_ = lambda x: x
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+def get_synchronization_tasks(object_name):
+ Object = lambda s: "%s.%s"%(object_name, s)
+ return [
+
+ {'name': 'reps_synchronization',
+ 'group': __("Repositories synchronization"),
+ 'tasks': [
+ # создать объект проверки PGP
+ {'name': 'prepare_gpg',
+ 'method': Object("prepare_gpg()"),
+ },
+ # создать объект хранилище серверов обновлений
+ {'name': 'create_binhost_data',
+ 'method': Object('create_binhost_data()')
+ },
+ # проверить валиден ли текущий хост
+ {'name': 'check_current_binhost',
+ 'message': __("Checking current binhost"),
+ 'essential': False,
+ 'method': Object('check_current_binhost(update.cl_update_binhost)'),
+ 'condition': lambda GetBool, Get: (
+ not GetBool('update.cl_update_binhost_recheck_set') and
+ Get('update.cl_update_sync_rep') and
+ Get('update.cl_update_binhost'))
+ },
+ {'name': 'not_use_search:failed_base_binhost',
+ 'error': __("Failed to use base binhost"),
+ 'method': Object("delete_binhost()"),
+ 'depend': AllTasks.failed_all("check_current_binhost")
+ },
+ {'name': 'group_find_binhost',
+ 'group': '',
+ 'while': (~AllTasks.has_any("detect_best_binhost") &
+ ((AllTasks.failed_all("update_packages_cache")
+ & ~AllTasks.has_any("not_use_search")) |
+ ~AllTasks.has_any("sync_reps"))) & Tasks.success(),
+ 'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and
+ Get('update.cl_update_sync_rep')),
+ 'tasks': [
+ # найти лучший сервер обновлений
+ {'name': 'detect_best_binhost',
+ 'method': Object('detect_best_binhost()'),
+ 'essential': False,
+ 'depend': (Tasks.success() & ~AllTasks.has_any("not_use_search") &
+ (~AllTasks.success_one_of("check_current_binhost") |
+ AllTasks.success_all("sync_reps"))),
+ },
+ # запасная синхронизация, в ходе которой ветки обновляются до
+ # master
+ {'name': 'sync_reps_fallback',
+ 'foreach': 'update.cl_update_sync_rep',
+ 'message':
+ __("Fallback syncing the {eachvar:capitalize} repository"),
+ 'method': Object('syncRepositories(eachvar,True)'),
+ 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
+ },
+ # обновление переменных информации из binhost
+ {'name': 'sync_reps_fallback:update_binhost_list',
+ 'method': Object('update_binhost_list()'),
+ 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
+ },
+ # найти лучший сервер обновлений
+ {'name': 'sync_reps_fallback:detect_best_binhost',
+ 'method': Object('detect_best_binhost()'),
+ 'depend': Tasks.success() & AllTasks.failed_one_of("detect_best_binhost"),
+ },
+ {'name': 'sync_reps',
+ 'foreach': 'update.cl_update_sync_rep',
+ 'message': __("Checking {eachvar:capitalize} updates"),
+ 'method': Object('syncRepositories(eachvar)'),
+ 'condition': lambda Get: Get('update.cl_update_sync_rep'),
+ 'depend': Tasks.success() & ~AllTasks.success_all("update_packages_cache")
+ },
+ {'name': 'sync_reps:update_local_info_binhost',
+ 'method': Object('update_local_info_binhost()'),
+ },
+ {'name': 'sync_reps:update_binhost_list',
+ 'essential': False,
+ 'method': Object('update_binhost_list()'),
+ 'condition': lambda GetBool: GetBool('update.cl_update_outdate_set')
+ },
+ {'name': 'sync_reps:update_packages_cache',
+ 'message': __("Update packages index"),
+ 'method': Object('download_packages(update.cl_update_portage_binhost,'
+ 'update.cl_update_package_cache,update.cl_update_package_cache_sign,'
+ 'update.cl_update_gpg)'),
+ 'essential': False,
+ 'condition': lambda Get, GetBool: (
+ Get('update.cl_update_package_cache') and (
+ Get('update.cl_update_outdate_set') == 'on' or
+ Get('update.cl_update_package_cache_set') == 'on'))
+ },
+ ],
+ },
+ {'name': 'no_server',
+ 'error': __("Failed to find the binary updates server"),
+ 'method': Object("delete_binhost()"),
+ # method: который должен удалить текущую информацию о сервере обновлений
+ 'depend': (~Tasks.has_any("failed_base_binhost") & (Tasks.failed() |
+ Tasks.success() & AllTasks.failed_one_of("update_packages_cache"))),
+ 'condition': lambda GetBool, Get: (GetBool('update.cl_update_usetag_set') and
+ Get('update.cl_update_sync_rep')),
+ },
+ {'name': 'sync_reps',
+ 'foreach': 'update.cl_update_sync_rep',
+ 'message': __("Checking {eachvar:capitalize} updates"),
+ 'method': Object('syncRepositories(eachvar)'),
+ 'condition': lambda Get, GetBool: (Get('update.cl_update_sync_rep') and
+ not GetBool('update.cl_update_usetag_set')),
+ },
+ {'name': 'update_rep_list',
+ 'message': __("Repository cache update"),
+ 'method': Object('update_rep_list()'),
+ 'condition': lambda Get: (isPkgInstalled(
+ "app-eselect/eselect-repository", prefix=Get('cl_chroot_path')) and
+ Get('cl_chroot_path') != "/"),
+ 'essential': False,
+ },
+ {'name': 'sync_other_reps',
+ 'foreach': 'update.cl_update_other_rep_name',
+ 'message': __("Syncing the {eachvar:capitalize} repository"),
+ 'method': Object('syncOtherRepository(eachvar)'),
+ 'condition': lambda GetBool: GetBool('update.cl_update_other_set')
+ },
+ {'name': 'trim_reps',
+ 'foreach': 'update.cl_update_sync_rep',
+ 'message': __("Cleaning the history of the "
+ "{eachvar:capitalize} repository"),
+ 'method': Object('trimRepositories(eachvar)'),
+ 'condition': lambda Get: (Get('update.cl_update_sync_rep') and
+ Get('update.cl_update_onedepth_set') == 'on')
+ },
+ {'name': 'sync_reps:regen_cache',
+ 'foreach': 'update.cl_update_sync_overlay_rep',
+ 'essential': False,
+ 'method': Object('regenCache(eachvar)'),
+ 'condition': (
+ lambda Get: (Get('update.cl_update_outdate_set') == 'on' and
+ Get('update.cl_update_egencache_force') != 'skip' or
+ Get('update.cl_update_egencache_force') == 'force'))
+ },
+ {'name': 'sync_other_reps:regen_other_cache',
+ 'foreach': 'update.cl_update_other_rep_name',
+ 'method': Object('regenCache(eachvar)'),
+ 'essential': False,
+ },
+ {'name': 'eix_update',
+ 'message': __("Updating the eix cache for "
+ "{update.cl_update_eix_repositories}"),
+ 'method': Object('eixUpdate(cl_repository_name)'),
+ 'condition': (
+ lambda Get: (Get('update.cl_update_outdate_set') == 'on' and
+ Get('update.cl_update_eixupdate_force') != 'skip' or
+ Get('update.cl_update_eixupdate_force') == 'force'))
+ },
+ {'name': 'update_setup_cache',
+ 'message': __("Updating the cache of configurable packages"),
+ 'method': Object('updateSetupCache()'),
+ 'essential': False,
+ 'condition': lambda Get: Get('update.cl_update_outdate_set') == 'on'
+ },
+ {'name': 'sync_reps:cleanpkg',
+ 'message': __("Removing obsolete distfiles and binary packages"),
+ 'method': Object('cleanpkg()'),
+ 'condition': (
+ lambda Get: Get('update.cl_update_cleanpkg_set') == 'on' and
+ Get('update.cl_update_outdate_set') == 'on'),
+ 'essential': False
+ },
+ # сообщение удачного завершения при обновлении репозиториев
+ {'name': 'success_syncrep',
+ 'message': __("Synchronization finished"),
+ 'depend': (Tasks.success() & Tasks.has_any("sync_reps",
+ "sync_other_reps",
+ "emerge_metadata",
+ "eix_update")),
+ }
+ ]
+ },
+ ]
+
+class UpdateConditions(object):
+ @staticmethod
+ def was_installed(pkg, task_name):
+ def func():
+ task = EmergeLog(EmergeLogNamedTask(task_name))
+ return bool(PackageList(task.list)[pkg])
+ return func
+
+ @staticmethod
+ def need_depclean(pkg, task_name):
+ def func(Get):
+ task = EmergeLog(EmergeLogNamedTask(task_name))
+ return (bool(PackageList(task.list)[pkg])
+ or Get('cl_update_force_depclean_set') == 'on'
+ or Get('cl_update_outdated_kernel_set') == 'on')
+ return func
+
+ @staticmethod
+ def force_preserved(Get):
+ pfile = "/var/lib/portage/preserved_libs_registry"
+ content = readFile(pfile).strip()
+ if not content or content[1:-1].strip() == '':
+ return False
+ else:
+ return True
+
+class ClUpdateAction(Action):
+ """
+ Действие обновление конфигурационных файлов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError, UpdateError,
+ TemplatesError, BinhostError,
+ GitError, EmergeError)
+
+ successMessage = None
+ failedMessage = None
+ interruptMessage = __("Update manually interrupted")
+
+ emerge_tasks = [
+ {'name': 'save_bdeps_val',
+ 'method': 'Update.save_with_bdeps()',
+ 'essential': False
+ },
+ {'name': 'update_fastlogin_domain',
+ 'method': "Update.update_fastlogin_domain_path()"
+ },
+ {'name': 'drop_portage_hash_on_sync',
+ 'method': 'Update.drop_portage_state_hash()',
+ 'condition': lambda Get: Get('cl_update_outdate_set') == 'on'
+ },
+ {'name': 'premerge_group',
+ 'group': __("Checking for updates"),
+ 'tasks': [
+ {'name': 'premerge',
+ 'message': __("Calculating dependencies"),
+ 'method': 'Update.premerge("-uDN","@world")',
+ 'condition': lambda Get: (
+ Get('cl_update_sync_only_set') == 'off' and
+ Get('cl_update_pretend_set') == 'on') and \
+ (Get('cl_update_world') != "update" or
+ Get('cl_update_outdate_set') == 'on' or
+ Get('cl_update_settings_changes_set') == 'on' or
+ Get('cl_update_binhost_recheck_set') == 'on' or
+ Get('cl_update_force_fix_set') == 'on' or
+ Get('update.cl_update_package_cache_set') == 'on')
+ }],
+ },
+ {'name': 'update',
+ 'condition': lambda Get:Get('cl_update_pretend_set') == 'off' and \
+ (Get('cl_update_world') != "update" or
+ Get('cl_update_outdate_set') == 'on' or
+ Get('cl_update_settings_changes_set') == 'on' or
+ Get('cl_update_binhost_recheck_set') == 'on' or
+ Get('cl_update_force_fix_set') == 'on' or
+ Get('cl_update_available_set') == 'on' or
+ Get('update.cl_update_package_cache_set') == 'on')
+ },
+ {'name': 'update_other',
+ 'condition': lambda Get: ( Get('cl_update_pretend_set') == 'off' and
+ Get('cl_update_sync_only_set') == 'off')
+ },
+ {'name': 'update:update_world',
+ 'group': __("Updating packages"),
+ 'tasks': [
+ {'name': 'update_world',
+ 'message': __("Calculating dependencies"),
+ 'method': 'Update.emerge_ask(cl_update_pretend_set,'
+ '"-uDN","@world")',
+ }
+ ],
+ 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off'
+ },
+ {'name': 'update_other:update_perl',
+ 'group': __("Updating Perl modules"),
+ 'tasks': [
+ {'name': 'update_other:perl_cleaner',
+ 'message': __('Find & rebuild packages and Perl header files '
+ 'broken due to a perl upgrade'),
+ 'method': 'Update.emergelike("perl-cleaner", "all")',
+ 'condition': UpdateConditions.was_installed(
+ 'dev-lang/perl$', EmergeMark.PerlCleaner),
+ 'decoration': 'Update.update_task("%s")' % EmergeMark.PerlCleaner
+ },
+ ]
+ },
+ {'name': 'update_other:depclean',
+ 'group': __("Cleaning the system from needless packages"),
+ 'tasks': [
+ {'name': 'update_other:update_depclean',
+ 'message': __("Calculating dependencies"),
+ 'method': 'Update.depclean()',
+ 'condition': UpdateConditions.need_depclean(
+ '.*', EmergeMark.Depclean),
+ 'decoration': 'Update.update_task("%s")' % EmergeMark.Depclean
+ },
+ ]
+ },
+ {'name': 'update_other:update_modules',
+ 'group': __("Rebuilding dependent modules"),
+ 'tasks': [
+ {'name': 'update_other:module_rebuild',
+ 'message': __('Updating Kernel modules'),
+ 'method': 'Update.emerge("","@module-rebuild")',
+ 'condition': UpdateConditions.was_installed(
+ 'sys-kernel/.*source', EmergeMark.KernelModules),
+ 'decoration': 'Update.update_task("%s")' %
+ EmergeMark.KernelModules
+ },
+ {'name': 'update_other:x11_module_rebuild',
+ 'message': __('Updating X.Org server modules'),
+ 'method': 'Update.emerge("","@x11-module-rebuild")',
+ 'condition': UpdateConditions.was_installed(
+ 'x11-base/xorg-server', EmergeMark.XorgModules),
+ 'decoration': 'Update.update_task("%s")' %
+ EmergeMark.XorgModules
+ },
+ {'name': 'update_other:preserved_rebuild',
+ 'message': __('Updating preserved libraries'),
+ 'method': 'Update.emerge("","@preserved-rebuild")',
+ 'condition': lambda Get: (UpdateConditions.was_installed(
+ '.*', EmergeMark.PreservedLibs)() or
+ UpdateConditions.force_preserved(Get)),
+ 'decoration': 'Update.update_task("%s")' %
+ EmergeMark.PreservedLibs
+ },
+ {'name': 'update_other:revdev_rebuild',
+ 'message': __('Checking reverse dependencies'),
+ 'method': 'Update.revdep_rebuild("revdep-rebuild")',
+ 'condition': lambda Get: (Get(
+ 'cl_update_revdep_rebuild_set') == 'on' and
+ UpdateConditions.was_installed(
+ '.*', EmergeMark.RevdepRebuild)()),
+ 'decoration': 'Update.update_task("%s")' %
+ EmergeMark.RevdepRebuild
+ },
+ {'name': 'update_other:dispatch_conf_end',
+ 'message': __("Updating configuration files"),
+ 'method': 'Update.dispatchConf()',
+ 'condition': lambda Get: (Get('cl_dispatch_conf') != 'skip' and
+ Get('cl_update_pretend_set') == 'off')
+ },
+ ]
+ },
+ {'name': 'set_upto_date_cache',
+ 'method': 'Update.setUpToDateCache()',
+ 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off' and \
+ Get('cl_update_pretend_set') == 'off'
+ }
+ ]
+
+ # список задач для действия
+ tasks = [
+ {'name': 'check_schedule',
+ 'method': 'Update.checkSchedule(cl_update_autocheck_interval,'
+ 'cl_update_autocheck_set)',
+ 'condition': lambda Get: (
+ Get('cl_update_autocheck_schedule_set') == 'on'),
+ },
+ {'name': 'check_run',
+ 'method': 'Update.checkRun(cl_update_wait_another_set)'
+ },
+ ] + get_synchronization_tasks("Update") + [
+ {'name': 'system_configuration',
+ 'group': __("System configuration"),
+ 'tasks': [
+ {'name': 'binhost_changed',
+ 'method': 'Update.message_binhost_changed()'
+ },
+ {'name': 'revision',
+ 'message': __("Fixing the settings"),
+ 'method': 'Update.applyTemplates(install.cl_source,'
+ 'cl_template_clt_set,True,None,False)',
+ 'condition': lambda Get, GetBool: (Get('cl_templates_locate') and
+ (Get('cl_update_world') != "update" or
+ GetBool('cl_update_outdate_set') or
+ GetBool('cl_update_binhost_recheck_set') or
+ GetBool('cl_update_force_fix_set') or
+ GetBool('update.cl_update_package_cache_set')))
+ },
+ {'name': 'dispatch_conf',
+ 'message': __("Updating configuration files"),
+ 'method': 'Update.dispatchConf()',
+ 'condition': lambda Get, GetBool: (Get('cl_dispatch_conf') != 'skip' and
+ Get('cl_update_pretend_set') == 'off' and
+ (GetBool('cl_update_outdate_set') or
+ GetBool('cl_update_binhost_recheck_set') or
+ GetBool('cl_update_force_fix_set') or
+ GetBool('update.cl_update_package_cache_set')))
+ },
+ ]
+ }
+ ] + emerge_tasks + [
+ {'name': 'failed',
+ 'error': __("Update failed"),
+ 'depend': (Tasks.failed() & Tasks.hasnot("interrupt") &
+ (Tasks.hasnot("check_schedule") |
+ Tasks.success_all("check_schedule")))},
+ {'name': 'failed',
+ 'depend': Tasks.failed_all("check_schedule")
+ },
+ # сообщение удачного завершения при обновлении ревизии
+ {'name': 'update:save_portage_hash',
+ 'method': 'Update.save_portage_state_hash()',
+ 'condition': lambda Get: Get('cl_update_sync_only_set') == 'off'
+ },
+ {'name': 'drop_portage_hash',
+ 'method': 'Update.drop_portage_state_hash()',
+ 'depend': Tasks.failed()
+ },
+ # сообщение удачного завершения при обновлении ревизии
+ {'name': 'success_rev',
+ 'message': __("System update finished!"),
+ 'condition': lambda Get: (Get('cl_update_rev_set') == 'on' and
+ Get('cl_update_pretend_set') == 'off')
+ },
+ # сообщение удачного завершения при пересоздании world
+ {'name': 'success_world',
+ 'message': __("World rebuild finished!"),
+ 'condition': lambda Get: Get('cl_rebuild_world_set') == 'on'
+ },
+ ]
diff --git a/libs_crutch/update/utils/cl_update_profile.py b/libs_crutch/update/utils/cl_update_profile.py
new file mode 100644
index 0000000..6a9ae9f
--- /dev/null
+++ b/libs_crutch/update/utils/cl_update_profile.py
@@ -0,0 +1,147 @@
+# -*- 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 sys
+from calculate.core.server.func import Action, Tasks
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+from calculate.lib.cl_template import TemplatesError
+from calculate.lib.utils.binhosts import BinhostError
+from calculate.lib.utils.files import FilesError
+from calculate.update.update import UpdateError
+from calculate.lib.utils.git import GitError
+
+_ = lambda x: x
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class ClUpdateProfileAction(Action):
+ """
+ Действие обновление конфигурационных файлов
+ """
+ # ошибки, которые отображаются без подробностей
+ native_error = (FilesError,
+ TemplatesError, BinhostError,
+ UpdateError, GitError)
+
+ successMessage = __("The profile was successfully updated")
+ failedMessage = __("Failed to update the profile")
+ interruptMessage = __("Profile update manually interrupted")
+
+ # список задач для действия
+ tasks = [
+ {'name': 'migrate_repository',
+ 'method': 'Update.migrateCacheRepository('
+ 'cl_update_profile_url,cl_update_profile_branch,'
+ 'cl_update_profile_storage)',
+ 'message': __("Repository transfer"),
+ 'condition': lambda Get: not (
+ Get('cl_update_profile_storage').is_local(
+ Get('cl_update_profile_url'),
+ Get('cl_update_profile_branch')))
+ },
+ {'name': 'reconfigure_vars1',
+ 'method': 'Update.invalidateVariables("cl_update_profile_storage")',
+ 'depend': Tasks.has('migrate_repository')
+ },
+ {'name': 'check_datavars',
+ 'error': _("Profile not found in master"),
+ 'condition': lambda Get: not Get('update.cl_update_profile_datavars')
+ },
+ {'name': 'drop_binhosts',
+ 'method': 'Update.drop_binhosts(update.cl_update_profile_datavars)'
+ },
+ {'name': 'reconfigure_vars',
+ 'method': 'Update.reconfigureProfileVars(cl_update_profile_datavars,'
+ 'cl_chroot_path)'
+ },
+ {'name': 'reps_synchronization',
+ 'group': __("Repositories synchronization"),
+ 'tasks': [
+ {'name': 'sync_reps',
+ 'foreach': 'cl_update_profile_sync_rep',
+ 'message': __("Syncing the {eachvar:capitalize} repository"),
+ 'method': 'Update.syncRepositories(eachvar)',
+ },
+ {'name': 'sync_reps:regen_cache',
+ 'foreach': 'cl_update_sync_overlay_rep',
+ 'message': __("Updating the {eachvar:capitalize} repository cache"),
+ 'essential': False,
+ 'method': 'Update.regenCache(eachvar)',
+ 'condition': (
+ lambda Get: (Get('cl_update_outdate_set') == 'on' and
+ Get('cl_update_metadata_force') != 'skip' or
+ Get('cl_update_metadata_force') == 'force'))
+ },
+ {'name': 'emerge_metadata',
+ 'message': __("Metadata transfer"),
+ 'method': 'Update.emergeMetadata()',
+ 'condition': (
+ lambda Get: (Get('cl_update_outdate_set') == 'on' and
+ Get('cl_update_metadata_force') != 'skip' or
+ Get('cl_update_metadata_force') == 'force'))
+ },
+ {'name': 'eix_update',
+ 'message': __("Updating the eix cache for "
+ "{cl_update_eix_repositories}"),
+ 'method': 'Update.eixUpdate(cl_repository_name)',
+ 'condition': (
+ lambda Get: (Get('cl_update_outdate_set') == 'on' and
+ Get('cl_update_eixupdate_force') != 'skip' or
+ Get('cl_update_eixupdate_force') == 'force'))
+ },
+ # сообщение удачного завершения при обновлении репозиториев
+ {'name': 'success_syncrep',
+ 'message': __("Synchronization finished"),
+ 'depend': (Tasks.success() & Tasks.has_any("sync_reps",
+ "sync_other_reps",
+ "emerge_metadata",
+ "eix_update")),
+ }
+ ]
+ },
+ {'name': 'reps_synchronization',
+ 'group': __("Setting up the profile"),
+ 'tasks': [
+ {'name': 'set_profile',
+ 'message': __("Switching to profile {cl_update_profile_system}"),
+ 'method': 'Update.setProfile(cl_update_profile_system)'
+ },
+ {'name': 'rename_custom',
+ 'method': 'Update.rename_custom_files()',
+ },
+ {'name': 'revision',
+ 'message': __("Fixing the settings"),
+ 'method': 'Update.applyProfileTemplates(cl_template_clt_set,'
+ 'True,False,"update_profile")',
+ 'condition': lambda Get: Get('cl_templates_locate')
+ },
+ {'name': 'reconfigure',
+ 'message': __("The system is being configured"),
+ 'method': 'Update.applyProfileTemplates(cl_template_clt_set,'
+ 'True,False,"merge")',
+ 'condition': lambda Get: (
+ Get('cl_update_templates_locate') and
+ Get('cl_update_skip_setup_set') == 'off')
+ },
+ {'name': 'dispatch_conf',
+ 'message': __("Updating configuration files"),
+ 'method': 'Update.dispatchConf()',
+ 'condition': lambda Get: Get('cl_dispatch_conf') != 'skip'
+ },
+ ]
+ }
+ ]
diff --git a/libs_crutch/update/variables/__init__.py b/libs_crutch/update/variables/__init__.py
index 0581c8b..e074ab8 100644
--- a/libs_crutch/update/variables/__init__.py
+++ b/libs_crutch/update/variables/__init__.py
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import action
-import update
+from . import action
+from . import update
section = "update"
diff --git a/libs_crutch/update/variables/update.py b/libs_crutch/update/variables/update.py
index 2f7ace8..41a3559 100644
--- a/libs_crutch/update/variables/update.py
+++ b/libs_crutch/update/variables/update.py
@@ -42,7 +42,7 @@ from calculate.lib.variables import env
from calculate.lib.variables import system as lib_system
from calculate.update.update_info import UpdateInfo
from itertools import chain
-from urlparse import urlparse
+from urllib.parse import urlparse
import io
import time
diff --git a/libs_crutch/update/wsdl_update.py b/libs_crutch/update/wsdl_update.py
new file mode 100644
index 0000000..e39dfa7
--- /dev/null
+++ b/libs_crutch/update/wsdl_update.py
@@ -0,0 +1,183 @@
+# -*- 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 sys
+
+from calculate.lib.datavars import VariableError,DataVarsError
+
+from calculate.core.server.func import WsdlBase
+from calculate.install.install import InstallError
+from .update import Update, UpdateError
+from calculate.lib.utils.git import GitError
+from utils.cl_update import ClUpdateAction
+from utils.cl_update_profile import ClUpdateProfileAction
+from utils.cl_setup_update import ClSetupUpdateAction
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate, _
+
+setLocalTranslate('cl_update3', sys.modules[__name__])
+__ = getLazyLocalTranslate(_)
+
+
+class Wsdl(WsdlBase):
+ methods = [
+ #
+ # Обновить текущую конфигурацию системы (world,ревизия)
+ #
+ {
+ # идентификатор метода
+ 'method_name': "update",
+ # категория метода
+ 'category': __('Update '),
+ # заголовок метода
+ 'title': __("Update the System"),
+ # иконка для графической консоли
+ 'image': 'calculate-update',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-update',
+ # права для запуска метода
+ 'rights': ['update'],
+ # объект содержащий модули для действия
+ 'logic': {'Update': Update},
+ # описание действия
+ 'action': ClUpdateAction,
+ # объект переменных
+ 'datavars': "update",
+ 'native_error': (VariableError, DataVarsError,
+ InstallError, UpdateError, GitError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'sync'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Update the system"),
+ normal=(
+ 'cl_update_binhost_stable_opt_set',
+ 'cl_update_binhost_recheck_set',
+ 'cl_update_with_bdeps_opt_set'
+ ),
+ expert=(
+ 'cl_update_sync_only_set',
+ 'cl_update_other_set',
+ 'cl_update_pretend_set',
+ 'cl_update_sync_rep',
+ 'cl_update_emergelist_set',
+ 'cl_update_check_rep_set',
+ 'cl_update_force_fix_set',
+ 'cl_update_world',
+ 'cl_update_gpg_force',
+ 'cl_update_egencache_force',
+ 'cl_update_eixupdate_force',
+ 'cl_update_revdep_rebuild_set',
+ 'cl_update_wait_another_set',
+ 'cl_update_autocheck_schedule_set',
+ 'cl_update_onedepth_set',
+ 'cl_update_cleanpkg_set',
+ 'cl_update_branch_data',
+ 'cl_templates_locate',
+ 'cl_verbose_set', 'cl_dispatch_conf'),
+ next_label=_("Run"))]},
+ #
+ # Сменить профиль
+ #
+ {
+ # идентификатор метода
+ 'method_name': "update_profile",
+ # категория метода
+ 'category': __('Update '),
+ # заголовок метода
+ 'title': __("Change the Profile"),
+ # иконка для графической консоли
+ 'image': 'calculate-update-profile,'
+ 'notification-display-brightness-full,gtk-dialog-info,'
+ 'help-hint',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-update-profile',
+ # права для запуска метода
+ 'rights': ['change_profile'],
+ # объект содержащий модули для действия
+ 'logic': {'Update': Update},
+ # описание действия
+ 'action': ClUpdateProfileAction,
+ # объект переменных
+ 'datavars': "update",
+ 'native_error': (VariableError, DataVarsError,
+ InstallError, UpdateError, GitError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'update_profile'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Repository"),
+ brief=('cl_update_profile_repo_name',),
+ hide=('cl_update_profile_url',
+ 'cl_update_profile_sync_set'),
+ normal=('cl_update_profile_url',),
+ expert=('cl_update_profile_sync_set',)),
+ lambda group: group(_("Profile"),
+ normal=('cl_update_profile_system',
+ 'cl_update_world'),
+ expert=('cl_update_skip_setup_set',
+ 'cl_update_templates_locate',
+ 'cl_verbose_set',
+ 'cl_dispatch_conf'),
+ hide=('cl_update_templates_locate',
+ 'cl_verbose_set',
+ 'cl_dispatch_conf'),
+ brief=('cl_update_profile_system',
+ 'cl_update_profile_linux_fullname',
+ 'cl_update_profile_depend_data')
+ )],
+ 'brief': {'next': __("Run"),
+ 'name': __("Set the profile")}},
+ #
+ # Настроить автопроверку обновлений
+ #
+ {
+ # идентификатор метода
+ 'method_name': "setup_update",
+ # категория метода
+ 'category': __('Configuration'),
+ # заголовок метода
+ 'title': __("Update Check"),
+ # иконка для графической консоли
+ 'image': 'calculate-setup-update,software-properties,preferences-desktop',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-setup-update',
+ # права для запуска метода
+ 'rights': ['setup_update'],
+ # объект содержащий модули для действия
+ 'logic': {'Update': Update},
+ # описание действия
+ 'action': ClSetupUpdateAction,
+ # объект переменных
+ 'datavars': "update",
+ 'native_error': (VariableError, DataVarsError,
+ InstallError, UpdateError, GitError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'merge'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("Updates autocheck settings"),
+ normal=('cl_update_autocheck_set',
+ 'cl_update_autocheck_interval',
+ 'cl_update_cleanpkg_set',
+ 'cl_update_other_set'),
+ next_label=_("Save"))]},
+ ]
diff --git a/pym/consolegui/application/more.py b/pym/consolegui/application/more.py
index f243e12..c08cd9b 100644
--- a/pym/consolegui/application/more.py
+++ b/pym/consolegui/application/more.py
@@ -24,7 +24,8 @@ import sys
from calculate.consolegui import qt
# from calculate.core.client.function import create_obj
-from .utils import create_obj
+# from .utils import create_obj
+from . import utils
from calculate.lib.utils.files import readLinesFile, listDirectory, readFile, \
getProgPath, process, STDOUT
from calculate.lib.utils.colortext.palette import (
@@ -2500,14 +2501,12 @@ def icon_visible (mainwgt, name, action):
if hasattr (mainwgt.topmenu, name):
getattr (mainwgt.topmenu, name).setVisible(action)
-# from calculate.core.client.function import get_ip_mac_type
-from .utils import get_ip_mac_type
from calculate.lib.utils import ip as ip_mod
def get_ip_mac():
for Interfaces in ip_mod.getInterfaces():
try:
- ip, mac, client_type = get_ip_mac_type('gui')
+ ip, mac, client_type = utils.get_ip_mac_type('gui')
return (ip, mac)
except:
pass
@@ -2733,7 +2732,7 @@ class ImageLabel(qt.QLabel):
self.setFixedHeight(sizes[1])
def get_view_params(client, method, step = None, expert = None, brief = None):
- view_params = create_obj(client, method)
+ view_params = utils.create_obj(client, method)
view_params.step = step
view_params.expert = expert
view_params.brief = brief
diff --git a/pym/consolegui/application/user_update.py b/pym/consolegui/application/user_update.py
new file mode 100644
index 0000000..7168961
--- /dev/null
+++ b/pym/consolegui/application/user_update.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2012-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 logging
+import sys
+from os import path
+import calculate.contrib
+from suds.transport import TransportError
+from suds import WebFault
+from .client_class import Client_suds
+import urllib.request as urllib2
+
+from calculate.core.datavars import DataVarsCore
+from .client_class import HTTPSClientCertTransport
+from calculate.lib.utils.dbus_tools import get_dbus_hostname
+from functools import partial
+from M2Crypto import X509
+from .utils import clear
+
+_ = lambda x: x
+from calculate.lib.cl_lang import setLocalTranslate
+setLocalTranslate('cl_core3', sys.modules[__name__])
+
+def get_cert_groups(cert_file):
+ """
+ Получить строку с группами сертификата
+ :param cert_file: файл сертификата
+ """
+ try:
+ cert = X509.load_cert(cert_file)
+ val = cert.get_ext('nsComment').get_value()
+ if val.startswith('group:'):
+ return val[6:]
+ return ""
+ except (IOError, LookupError):
+ return ""
+
+
+def get_certifactions_for_host(host, use_dbus=False):
+ """
+ Получить список сертификатов подходящих для host
+ :param host:
+ :return: список полных путей файлов сертификатов
+ """
+ log = logging.getLogger("certification")
+ cl_vars_core = DataVarsCore()
+ cl_vars_core.importCore()
+ cl_vars_core.flIniFile()
+
+ home_path = cl_vars_core.Get('ur_home_path')
+
+ port = cl_vars_core.GetInteger('core.cl_core_port')
+ path_to_cert = cl_vars_core.Get('core.cl_client_cert_dir')
+ path_to_cert = path_to_cert.replace("~", home_path)
+
+ url = "https://%s:%d/?wsdl" % (host, port)
+
+ clear()
+ if not use_dbus:
+ try:
+ client = Client_suds(url, transport=HTTPSClientCertTransport(
+ None, None, path_to_cert, None))
+ client.wsdl.services[0].setlocation(url)
+ server_host_name = client.service.get_server_host_name()
+ del client
+ except (urllib2.URLError, TransportError) as e:
+ log.debug(_('Failed to connect', ) + _(': ') + str(e))
+ return []
+ except KeyboardInterrupt:
+ log.debug(_("Manually interrupted"))
+ return []
+ else:
+ server_host_name = get_dbus_hostname() or "localhost"
+ try:
+ import glob
+
+ all_cert_list = glob.glob(os.path.join(path_to_cert, '*.crt'))
+ fit_cert_list = []
+ for client_cert_path in all_cert_list:
+ client_cert = client_cert_path.replace(path_to_cert, '')
+ client_cert_name = client_cert.replace('.crt', '')
+ if server_host_name.endswith(client_cert_name):
+ fit_cert_list.append(client_cert_name)
+ fit_cert_list.sort(key=len)
+ mkfullpath = partial(path.join, path_to_cert)
+ return map(mkfullpath, map(lambda x: "%s.crt" % x, fit_cert_list))
+ # ----------------------------------------------------
+ except WebFault as f:
+ log.debug(_("Exception: %s") % f)
+ log.debug(f.fault)
+ except TransportError as te:
+ log.debug(_("Exception: %s") % te)
+ except KeyboardInterrupt:
+ log.debug(_("Manually interrupted"))
+ except Exception as e:
+ log.debug(str(e))
+ return []
+
+
+def user_can_run_update(use_dbus):
+ """
+ Текущий пользователь может запускать обновление системы
+ """
+ for cert in get_certifactions_for_host("localhost", use_dbus=use_dbus):
+ groups = get_cert_groups(cert)
+ if any(x in groups for x in ("all", "update")):
+ return True
+ return False
+
diff --git a/pym/consolegui/application/utils.py b/pym/consolegui/application/utils.py
index 1d5f18e..3309fb1 100644
--- a/pym/consolegui/application/utils.py
+++ b/pym/consolegui/application/utils.py
@@ -14,15 +14,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
import os
+
import sys
import calculate.contrib
from suds import MethodNotFound
+
from calculate.lib.utils import ip as ip
_ = lambda x: x
from calculate.lib.cl_lang import setLocalTranslate
-
setLocalTranslate('cl_core3', sys.modules[__name__])
def getIpLocal():
@@ -146,3 +148,4 @@ class switch(object):
return True
else:
return False
+