diff --git a/update/update.py b/update/update.py index 0b8588f..8254f30 100644 --- a/update/update.py +++ b/update/update.py @@ -19,74 +19,29 @@ import re import sys import time from os import path +from subprocess import Popen from calculate.lib.utils.files import (process,getProgPath,STDOUT,removeDir, - processProgress) + processProgress,PercentProgress,process) -class UpdateError(Exception): - """Update Error""" - -class GitError(Exception): - """Git Error""" +class AddonError(Exception): + """ + Исключение с добавочным сообщением + """ def __init__(self, msg, addon=None): self.message = msg self.addon = addon Exception.__init__(self,msg) +class UpdateError(AddonError): + """Update Error""" + +class GitError(AddonError): + """Git Error""" + from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate setLocalTranslate('cl_update3',sys.modules[__name__]) __ = getLazyLocalTranslate(_) -class PercentProgress(processProgress): - """ - Объект выдает прогресс, ища в выводе \d+% - - Args: - part: количество прогрессов в программе - delimeter: разделители строк по умолчанию \n и \r - cachefilter: фильтр вывода программы (регулярная строка) - startpart: используется для вывода процентов прогрессбар - с определенного места (0 по умолчанию) - end: конечный прогрессбар (по умолчанию) - """ - def init(self,*args,**kwargs): - self.rePerc = re.compile("(\d+)%",re.S) - self.part = kwargs.get("part",1) - self.add_offset = 100 / self.part - self.offset = 0 + kwargs.get("startpart",0)*self.add_offset - self.is_end = kwargs.get("end",True) - self.stderr = STDOUT - self.delimeter = re.compile("[%s]"%kwargs.get("delimeter","\n\r")) - self.cachedata = re.compile(kwargs.get("cachefilter", - "((?:error|warning|fatal):.*)")) - - def processInit(self): - self.percent = 0 - self.showval = 0 - if not self.offset: - return 0 - - def processEnd(self): - if self.is_end: - self.percent = 100 - return 100 - - def processString(self,strdata): - match = self.rePerc.search(strdata) - resSearch = self.cachedata.search(strdata) - if resSearch: - self.cacheresult.append(resSearch.group(1)) - if match: - percent = int(match.group(1)) - if percent < self.percent: - self.offset = self.offset + self.add_offset - percent = percent / self.part - if percent != self.percent: - self.percent = percent - showval = min(99,self.percent + self.offset) - if showval != self.showval: - self.showval = showval - return self.showval - class Git: """ Объект для управление git репозиторием @@ -163,7 +118,10 @@ class Git: Return: Возвращает True если клонирование произведено с установкой на указанную ревизию. False если клонирование произведено с - установкой на последнюю ревизию. В остальных случаях GitError + установкой на последнюю ревизию. + Raises: + GitError: Выполнение ключевых команд выполнено с ошибками (не + удалось скачать и получить данные из удаленного репозитория) """ git_dir = self.__gitDir(rpath) error = [] @@ -357,10 +315,19 @@ class Git: to_origin: откатить все изменения до удаленного репозитория to_rev: откатить все изменения до определенной ревизии info: использовать уже полученную информация об изменения в репозитории + Return: + True - успешное выполнение + False - нет необходимости выполнять reset + Raises: + GitError: выполнение комманд reset и clean прошло с ошибкой """ git_dir = self.__gitDir(rpath) - if to_origin and not info: + if not info: info = self._getStatusInfo(rpath) + if (all(not info[x] for x in ("files","ahead","behind") if x in info) + and (not info["origin"] or + "origin/%s"%info["branch"] == info["origin"])): + return False commit = (info['origin'] if to_origin else to_rev) or "HEAD" git_reset = process(self._git,"--git-dir",git_dir,"--work-tree",rpath, "reset","--hard", commit, stderr=STDOUT) @@ -369,6 +336,7 @@ class Git: if git_reset.failed() or git_clean.failed(): raise GitError(_("Failed to clean {rpath} repository").format( rpath=rpath)) + return True def _getBranch(self,rpath): """ @@ -406,7 +374,9 @@ class Update: """ Синхронизировать репозиторий """ + dv = self.clVars git = Git() + needMeta = False if not git._checkExistsRep(rpath): if revision == "last": git._cloneRepository(url, rpath, branch, @@ -414,6 +384,7 @@ class Update: else: git._cloneRevRepository(url, rpath, branch, revision, cb_progress=cb_progress) + needMeta = True else: # если нужно обновиться до конкретной ревизии if revision != "last": @@ -426,11 +397,16 @@ class Update: repInfo = git._getStatusInfo(rpath) if repInfo['branch'] != branch: # меняем ветку + needMeta = True git._checkoutBranch(rpath,branch) if revision == "last": - git._resetRepository(rpath, to_origin=True) + if git._resetRepository(rpath, to_origin=True): + needMeta = True else: git._resetRepository(rpath, to_rev=revision) + needMeta = True + if needMeta: + dv.Set('cl_update_outdate_set','on',force=True) return True def syncRepositories(self,repname,clean_on_error=True): @@ -442,6 +418,8 @@ class Update: dv.Select(["cl_update_rep_url","cl_update_rep_path", "cl_update_rep_rev","cl_update_branch_name"], where="cl_update_rep_name",eq=repname,limit=1)) + if not url or not rpath: + raise UpdateError(_("Repositories variables is not configured")) self.addProgress() if clean_on_error: try: @@ -453,7 +431,94 @@ class Update: self.printWARNING(str(e.addon)) self.printWARNING(str(e)) self.printWARNING(_("Re-fetch {name} repository" - ).format(name=name)) + ).format(name=repname)) removeDir(rpath) self._syncRepository(name,url,rpath,revision,branch) return True + + def syncLaymanRepository(self,repname): + """ + Обновить репозиторий через layman + """ + layman = getProgPath('/usr/bin/layman') + if not layman: + raise UpdateError(_("Layman utility is not found")) + rpath = self.clVars.Select('cl_update_other_rep_path', + where='cl_update_other_rep_name',eq=repname,limit=1) + laymanname = path.basename(rpath) + if path.exists(path.join(rpath,'.git')): + self.addProgress() + p = PercentProgress(layman,"-s",laymanname,part=1,atty=True) + for perc in p.progress(): + self.setProgress(perc) + else: + p = process(layman,"-s",repname,stderr=STDOUT) + if p.failed(): + raise UpdateError(_("Failed to update repository {rname}" + ).format(rname=repname),addon=p.read()) + return True + + def regenCache(self,repname): + """ + Обновить кэш метаданных репозитория + """ + egenCache = getProgPath('/usr/bin/egencache') + if not egenCache: + raise UpdateError(_("Portage utility is not found")) + cpu_num = self.clVars.Get('hr_cpu_num') + p = process(egenCache,"--repo=%s"%repname,"--update", + "--jobs=%s"%cpu_num,stderr=STDOUT) + if p.failed(): + raise UpdateError(_("Failed to update cache of {rname} repository" + ).format(rname=repname),addon=p.read()) + return True + + def emergeMetadata(self): + """ + Выполнить egencache и emerge --metadata + """ + emerge = getProgPath("/usr/bin/emerge") + if not emerge: + raise UpdateError(_("Emerge utility is not found")) + self.addProgress() + p = PercentProgress(emerge,"--metadata",part=1,atty=True) + for perc in p.progress(): + self.setProgress(perc) + if p.failed(): + raise UpdateError(_("Failed to update metadata"),addon=p.read()) + return True + + def eixUpdate(self): + """ + Выполенине eix-update для репозиторием + + eix-update выполнятется только для тех репозиториев, которые + обновлялись, если cl_update_eixsync_force==auto, либо + все, если cl_update_eixupdate_force==force + """ + eixupdate = getProgPath("/usr/bin/eix-update") + if not eixupdate: + raise UpdateError(_("Eix utility is not found")) + self.addProgress() + excludeList = [] + if self.clVars.Get('cl_update_eixupdate_force') == 'force': + countRep = len(self.clVars.Get('cl_update_rep_name')) + else: + for rep in self.clVars.Get('cl_update_rep_name'): + # подстановка имен + mapNames = {'portage':'gentoo'} + if not rep in self.clVars.Get('cl_update_sync_rep'): + excludeList.extend(["-x",mapNames.get(rep,rep)]) + countRep = len(self.clVars.Get('cl_update_sync_rep')) + if (self.clVars.Get('cl_update_other_set') == 'on' or + self.clVars.Get('cl_update_eixupdate_force') == 'force'): + countRep += len(self.clVars.Get('update.cl_update_other_rep_name')) + else: + for rep in self.clVars.Get('update.cl_update_other_rep_name'): + excludeList.extend(['-x',rep]) + p = PercentProgress(eixupdate,"-F",*excludeList,part=countRep or 1,atty=True) + for perc in p.progress(): + self.setProgress(perc) + if p.failed(): + raise UpdateError(_("Failed to update eix cache"),addon=p.read()) + return True diff --git a/update/utils/cl_update.py b/update/utils/cl_update.py index 6dea5b1..3d1ba2d 100644 --- a/update/utils/cl_update.py +++ b/update/utils/cl_update.py @@ -15,7 +15,7 @@ # limitations under the License. import sys -from calculate.core.server.func import Action +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.install.install import (MigrationError, TemplatesError, @@ -41,15 +41,57 @@ class ClUpdateAction(Action): {'name':'sync_reps', 'foreach':'cl_update_sync_rep', 'message' : __("Syncing {eachvar} repository"), - 'method':'Update.syncRepositories(eachvar,cl_update_rep_data)', + 'method':'Update.syncRepositories(eachvar)', 'condition':lambda Get:Get('cl_update_sync_rep') }, + {'name':'sync_other_reps', + 'foreach':'cl_update_other_rep_name', + 'message' : __("Syncing {eachvar} repository"), + 'method':'Update.syncLaymanRepository(eachvar)', + 'condition':lambda Get:Get('cl_update_other_set') == 'on' + }, + {'name':'sync_reps:regen_cache', + 'foreach':'cl_update_sync_overlay_rep', + 'message' : __("Updating cache {eachvar} repository"), + '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':'sync_other_reps:regen_other_cache', + 'foreach':'cl_update_other_rep_name', + 'message' : __("Updating cache {eachvar} repository"), + 'method':'Update.regenCache(eachvar)', + 'essential':False, + }, + {'name':'emerge_metadata', + 'message' : __("Metadata trasfering"), + '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 eix cache"), + 'method':'Update.eixUpdate()', + 'condition':lambda Get:(Get('cl_update_outdate_set') == 'on' and + Get('cl_update_eixupdate_force') != 'skip' or + Get('cl_update_eixupdate_force') == 'force') + }, {'name':'dispatch', 'method':'Update.applyTemplates(install.cl_source,cl_template_clt_set,'\ 'True,None)', 'condition':lambda Get:(Get('cl_update_rev_set') == 'on' or Get('cl_rebuild_world_set') == 'on') }, + # сообщение удачного завершения при обновлении репозиториев + {'name':'success_syncrep', + 'message' : __("Synchronzation finished!"), + 'depend': (Tasks.success() & Tasks.has_any("sync_reps", + "sync_other_reps","emerge_metadata", + "eix_update")), + }, # сообщение удачного завершения при обновлении ревизии {'name':'success_rev', 'message' : __("Revision update finished!"), diff --git a/update/variables/update.py b/update/variables/update.py index c20adfb..b6db943 100644 --- a/update/variables/update.py +++ b/update/variables/update.py @@ -18,8 +18,8 @@ import os import sys import re from os import path -from calculate.lib.datavars import (Variable,VariableError,ReadonlyVariable, - ReadonlyTableVariable,TableVariable) +from calculate.lib.datavars import (Variable, VariableError, + ReadonlyVariable, ReadonlyTableVariable, TableVariable, FieldValue) from calculate.lib.utils.portage import searchProfile from calculate.lib.utils.files import readLinesFile, readFile @@ -66,7 +66,10 @@ class VariableClUpdateRevSet(Variable): opt = ["--update-rev"] untrusted = True value = "off" - check_after = ["cl_update_sync_rep"] + check_after = ["cl_update_sync_rep", + "cl_update_metadata_force", + "cl_update_other_set", + "cl_update_eixupdate_force"] def init(self): self.help = _("revision update") @@ -74,7 +77,10 @@ class VariableClUpdateRevSet(Variable): def check(self,value): if ( value == "off" and self.Get('cl_rebuild_world_set') != 'on' and - not self.Get('cl_update_sync_rep')): + not self.Get('cl_update_sync_rep') and + self.Get('cl_update_other_set') == 'off' and + self.Get('cl_update_metadata_force') != "force" and + self.Get('cl_update_eixupdate_force') != "force"): raise VariableError(_("Select at least one update action")) class VariableClUpdateRep(Variable): @@ -247,3 +253,116 @@ class VariableClUpdateSyncRep(Variable): def choice(self): return self.Get('cl_update_rep_name') + +class VariableClUpdateSyncOverlayRep(ReadonlyVariable): + """ + Обновляемые репозитории (исключая portage) + """ + type = "list" + + def get(self): + return filter(lambda x:x!="portage",self.Get('cl_update_sync_rep')) + +class VariableClUpdateOutdateSet(ReadonlyVariable): + """ + Флаг устанавливаемый в ходе обновления репозиториев, + сообщающий что хотя бы один из запланированных репозиториев + обновлен и следует обновляет различные метаданные + + Если обновляются прочие оверлеи - данные считаются что устарели + """ + type = "bool" + value = "off" + + def get(self): + return self.Get('cl_update_other_set') + +class VariableClUpdateMetadataForce(Variable): + """ + Принудительное действие с метаданными + """ + type = "choice" + value = "auto" + opt = ["--update-metadata"] + syntax = "--{choice}-update-metadata" + metavalue = "MODE" + #untrusted = True + + def init(self): + self.help = ("'force' - " + _("force update ebuilds metadata") + + ",\n'skip' - " + _("skip update ebuilds metadata") + + ",\n'auto' - " + _("update metadata if they are outdated")) + self.label = _("Update metadata") + + def choice(self): + return [("force", _("Force")), + ("skip", _("Skip")), + ("auto", _("By need"))] + +class VariableClUpdateEixupdateForce(Variable): + """ + Принудительное действие с eix-update + """ + type = "choice" + value = "auto" + opt = ["--eix-update"] + syntax = "--{choice}-eix-update" + metavalue = "MODE" + #untrusted = True + + def init(self): + self.help = ("'force' - " + _("force update eix cache") + + ",\n'skip' - " + _("skip update eix cache") + + ",\n'auto' - " + _("update eix cache if it is outdated")) + self.label = _("Update eix cache") + + def choice(self): + return [("force", _("Force")), + ("skip", _("Skip")), + ("auto", _("By need"))] + +class VariableClUpdateOtherSet(Variable): + """ + Обновить остальные оверлеи + """ + type = "bool" + value = "off" + opt = ["--update-other"] + + def init(self): + self.help = _("update other overlays") + self.label = _("Update other overlays") + +class VariableClUpdateOtherRepData(ReadonlyTableVariable): + """ + Информация о прочих репозиториях + """ + source = ['cl_update_other_rep_name', + 'cl_update_other_rep_path'] + + def generator(self): + repNames = self.Get('cl_update_rep_name') + for rpath in self.Get('cl_portdir_overlay'): + repo_file = path.join(rpath,"profiles/repo_name") + rname = readFile(repo_file).strip() or path.basename(rpath) + if not rname in repNames: + yield (rname, rpath) + + def get(self): + return list(self.generator()) + +class VariableClUpdateOtherRepName(FieldValue,ReadonlyVariable): + """ + Список имен прочих репозиториев + """ + type = "list" + source_variable = "cl_update_other_rep_data" + column = 0 + +class VariableClUpdateOtherRepPath(FieldValue,ReadonlyVariable): + """ + Список путей до прочих репозиториев + """ + type = "list" + source_variable = "cl_update_other_rep_data" + column = 1 diff --git a/update/wsdl_update.py b/update/wsdl_update.py index 8c0526f..5f014a0 100644 --- a/update/wsdl_update.py +++ b/update/wsdl_update.py @@ -62,6 +62,8 @@ class Wsdl(WsdlBase): lambda group:group(_("Update system"), normal=('cl_rebuild_world_set','cl_update_rev_set'), expert=('cl_update_sync_rep', 'cl_update_branch', + 'cl_update_metadata_force','cl_update_other_set', + 'cl_update_eixupdate_force', 'cl_templates_locate', 'cl_verbose_set','cl_dispatch_conf'), next_label=_("Update"))]},