You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1324 lines
53 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from functools import wraps
  16. import random
  17. import sys
  18. from os import path
  19. import os
  20. import time
  21. from calculate.core.server.gen_pid import search_worked_process
  22. from calculate.core.setup_cache import Cache as SetupCache
  23. from calculate.core.server.func import MethodsInterface
  24. from calculate.lib.cl_template import SystemIni, LayeredIni
  25. from calculate.lib.datavars import DataVarsError, VariableError
  26. from calculate.lib.utils.tools import AddonError
  27. from calculate.lib.utils.colortext.palette import TextState
  28. from calculate.lib.utils.colortext import get_color_print
  29. from calculate.update.emerge_parser import RevdepPercentBlock
  30. from calculate.update.datavars import DataVarsUpdate
  31. from calculate.update.update_info import UpdateInfo
  32. from calculate.lib.utils.binhosts import fetch_packages
  33. from calculate.lib.cl_log import log
  34. import re
  35. import shutil
  36. from collections import MutableSet
  37. from contextlib import contextmanager
  38. from calculate.lib.utils.git import Git, GitError, MTimeKeeper, NotGitError
  39. from calculate.lib.utils.portage import (Layman, EmergeLog,
  40. EmergeLogNamedTask,
  41. PackageInformation,
  42. get_packages_files_directory,
  43. get_manifest_files_directory,
  44. get_remove_list)
  45. from calculate.lib.utils.text import _u8
  46. Colors = TextState.Colors
  47. from calculate.lib.utils.files import (getProgPath, STDOUT, removeDir,
  48. PercentProgress, process, getRunCommands,
  49. readFile, listDirectory, pathJoin,
  50. find, FindFileType,
  51. writeFile, makeDirectory)
  52. import emerge_parser
  53. import logging
  54. from emerge_parser import (EmergeParser, EmergeCommand, EmergeError,
  55. EmergeCache, Chroot)
  56. from calculate.lib.cl_lang import (setLocalTranslate, getLazyLocalTranslate,
  57. RegexpLocalization, _)
  58. setLocalTranslate('cl_update3', sys.modules[__name__])
  59. __ = getLazyLocalTranslate(_)
  60. class UpdateError(AddonError):
  61. """Update Error"""
  62. class OverlayOwnCache(MutableSet):
  63. """
  64. Сет оверлеев с интегрированным кэшем
  65. """
  66. def __init__(self, dv=None):
  67. self.dv = dv
  68. def __get_overlays(self):
  69. own_cache_value = SystemIni(self.dv).getVar('system', 'own_cache') or ""
  70. return [x.strip() for x in own_cache_value.split(',') if x.strip()]
  71. def __write_overlays(self, overlays):
  72. if not overlays:
  73. SystemIni(self.dv).delVar('system', 'own_cache')
  74. else:
  75. SystemIni(self.dv).setVar('system',
  76. {'own_cache': ",".join(overlays)})
  77. def __contains__(self, item):
  78. return item in self.__get_overlays()
  79. def __iter__(self):
  80. return iter(self.__get_overlays())
  81. def __len__(self):
  82. return len(self.__get_overlays())
  83. def __append_value(self, overlays, value):
  84. if value not in overlays:
  85. overlays.append(value)
  86. self.__write_overlays(overlays)
  87. def add(self, value):
  88. overlays = self.__get_overlays()
  89. self.__append_value(overlays, value)
  90. def discard(self, value):
  91. overlays = self.__get_overlays()
  92. if value in overlays:
  93. overlays.remove(value)
  94. self.__write_overlays(overlays)
  95. def variable_module(var_env):
  96. def variable_module_decor(f):
  97. @wraps(f)
  98. def wrapper(self, *args, **kw):
  99. old_env = self.clVars.defaultModule
  100. try:
  101. self.clVars.defaultModule = var_env
  102. return f(self, *args, **kw)
  103. finally:
  104. self.clVars.defaultModule = old_env
  105. return wrapper
  106. return variable_module_decor
  107. class Update(MethodsInterface):
  108. """Основной объект для выполнения действий связанных с обновлением системы
  109. """
  110. def init(self):
  111. commandLog = path.join(self.clVars.Get('core.cl_log_path'),
  112. 'lastcommand.log')
  113. emerge_parser.CommandExecutor.logfile = commandLog
  114. self.color_print = get_color_print()
  115. self.emerge_cache = EmergeCache()
  116. if self.clVars.Get('cl_env_debug_set') == 'off':
  117. EmergeCache.logger.logger.setLevel(logging.WARNING)
  118. self.emerge_cache.check_list = (
  119. self.emerge_cache.check_list +
  120. map(lambda x:emerge_parser.GitCheckvalue(x, self.getGit()),
  121. self.clVars.Get('update.cl_update_rep_path')))
  122. self.update_map = {}
  123. self.refresh_binhost = False
  124. self.pkgnum = None
  125. self.pkgnummax = None
  126. def get_prog_path(self, program_name):
  127. return getProgPath(program_name)
  128. def getGit(self):
  129. return self.clVars.Get('cl_update_git')
  130. @contextmanager
  131. def private_repo(self, rpath, url):
  132. if Git.is_private_url(url):
  133. try:
  134. if not path.exists(rpath):
  135. makeDirectory(rpath)
  136. os.chmod(rpath, 0700)
  137. yield
  138. finally:
  139. try:
  140. for dn in (Git._gitDir(rpath), path.join(rpath, "profiles/templates")):
  141. if path.exists(dn):
  142. os.chmod(dn, 0700)
  143. for fn in find(path.join(rpath, "profiles"), True, FindFileType.RegularFile,
  144. True, None, downfilter=lambda x: not x.endswith("/templates")):
  145. if fn.endswith("calculate.env") or fn.endswith("ini.env"):
  146. os.chmod(fn, 0600)
  147. if path.exists(rpath):
  148. os.chmod(rpath, 0755)
  149. except OSError:
  150. pass
  151. else:
  152. yield
  153. def _syncRepository(self, name, url, rpath, revision,
  154. cb_progress=None, clean=False, notask=False):
  155. """
  156. Синхронизировать репозитори
  157. """
  158. dv = self.clVars
  159. git = self.getGit()
  160. info_outdated = False
  161. old_dir = "%s.old" % git._gitDir(rpath)
  162. if path.exists(old_dir):
  163. clean = True
  164. try:
  165. self.stash_cache(rpath, name)
  166. if not git.checkExistsRep(rpath):
  167. if not notask:
  168. self.startTask(_("Syncing the {rep} repository").format(
  169. rep=name.capitalize()))
  170. self.addProgress()
  171. with self.private_repo(rpath, url):
  172. git.cloneTagRepository(url, rpath, revision,
  173. cb_progress=cb_progress)
  174. info_outdated = True
  175. else:
  176. cr = ""
  177. try:
  178. need_update = False
  179. tag_cr = git.getCommit(rpath, revision)
  180. cr = git.getCurrentCommit(rpath)
  181. ref_type = git.reference_type(rpath, revision)
  182. if tag_cr != cr or ref_type == Git.Reference.Branch:
  183. need_update = True
  184. elif clean:
  185. status = git.getStatusInfo(rpath)
  186. if not status or status['files']:
  187. need_update = True
  188. except GitError:
  189. need_update = True
  190. if need_update:
  191. if not notask:
  192. self.startTask(_("Syncing the {rep} repository").format(
  193. rep=name.capitalize()))
  194. self.addProgress()
  195. with self.private_repo(rpath, url):
  196. git.updateTagRepository(url, rpath, revision,
  197. cb_progress=cb_progress,
  198. clean=clean)
  199. new_cr = git.getCurrentCommit(rpath)
  200. if new_cr != cr:
  201. info_outdated = True
  202. if info_outdated:
  203. self.raiseOutdate()
  204. dv.Set('cl_update_outdate_set', 'on', force=True)
  205. finally:
  206. self.unstash_cache(rpath, name)
  207. return True
  208. def raiseOutdate(self):
  209. self.clVars.Set('cl_update_outdate_set', 'on', force=True)
  210. def setAutocheckParams(self, status, interval, update_other, cleanpkg):
  211. """
  212. Настроить параметры автопроверки обновлений
  213. """
  214. onoff = lambda x: "on" if x else "off"
  215. self.clVars.Write('cl_update_autocheck_set', onoff(status), True)
  216. self.clVars.Write('cl_update_autocheck_interval', interval, True)
  217. self.clVars.Write('cl_update_other_set', onoff(update_other), True)
  218. self.clVars.Write('cl_update_cleanpkg_set', onoff(cleanpkg), True)
  219. if not status:
  220. UpdateInfo.set_update_ready(False)
  221. return True
  222. def checkSchedule(self, interval, status):
  223. """
  224. Проверить по расписанию необходимость запуска команды
  225. """
  226. if not status:
  227. self.printWARNING(_("Updates autocheck is not enabled"))
  228. return False
  229. last_check = SystemIni(self.clVars).getVar('system', 'last_check') or ""
  230. re_interval = re.compile("^(\d+)\s*(hours?|days?|weeks?)?", re.I)
  231. interval_match = re_interval.search(interval)
  232. MINUTE = 60
  233. HOUR = MINUTE * 60
  234. DAY = HOUR * 24
  235. WEEK = DAY * 7
  236. if interval_match:
  237. if interval_match.group(2):
  238. suffix_map = {'h': HOUR, 'd': DAY, 'w': WEEK}
  239. k = suffix_map.get(interval_match.group(2).lower()[0], HOUR)
  240. else:
  241. k = HOUR
  242. est = int(interval_match.group(1)) * k
  243. else:
  244. est = 3 * HOUR
  245. if last_check:
  246. if last_check.isdigit():
  247. if (time.time() - int(last_check)) < (est - 10 * MINUTE):
  248. self.printWARNING(_("Please wait for the update time"))
  249. return False
  250. self.mark_schedule()
  251. return True
  252. def checkRun(self, wait_update):
  253. """
  254. Проверить повторный запуск
  255. """
  256. update_running = lambda: any(os.getpid() != x
  257. for x in
  258. search_worked_process('update', dv))
  259. dv = self.clVars
  260. if update_running():
  261. if not wait_update:
  262. raise UpdateError(_("Update is already running. "
  263. "Try to run later."))
  264. else:
  265. self.startTask(_("Waiting for another update to be complete"))
  266. while update_running():
  267. self.pauseProcess()
  268. while update_running():
  269. time.sleep(0.3)
  270. self.resumeProcess()
  271. time.sleep(random.random() * 3)
  272. self.endTask()
  273. if self.clVars.Get('cl_chroot_status') == 'off':
  274. emerge_running = lambda: any("/usr/bin/emerge" in x
  275. for x in getRunCommands(True))
  276. if emerge_running():
  277. if not wait_update:
  278. raise UpdateError(_("Emerge is running. "
  279. "Try to run later."))
  280. else:
  281. self.startTask(_("Waiting for emerge to be complete"))
  282. while emerge_running():
  283. time.sleep(1)
  284. self.endTask()
  285. return True
  286. @variable_module("update")
  287. def trimRepositories(self, repname):
  288. """
  289. Синхронизировать репозитории
  290. """
  291. dv = self.clVars
  292. rpath = \
  293. dv.select('cl_update_rep_path', cl_update_rep_name=repname, limit=1)
  294. git = self.getGit()
  295. self.addProgress()
  296. git.trimRepository(rpath, cb_progress=self.setProgress)
  297. return True
  298. @variable_module("update")
  299. def syncRepositories(self, repname, fallback_sync=False,
  300. clean_on_error=True):
  301. """
  302. Синхронизировать репозитории
  303. """
  304. dv = self.clVars
  305. check_status = dv.GetBool('update.cl_update_check_rep_set')
  306. url, rpath, revision = (
  307. dv.Select(["cl_update_rep_url", "cl_update_rep_path",
  308. "cl_update_rep_rev"],
  309. where="cl_update_rep_name", eq=repname, limit=1))
  310. if not url or not rpath:
  311. raise UpdateError(_("Configuration variables for repositories "
  312. "are not setup"))
  313. git = self.getGit()
  314. if not git.checkUrl(url):
  315. raise UpdateError(_("Git %s is unavailable") % url)
  316. chroot_path = path.normpath(self.clVars.Get('cl_chroot_path'))
  317. if chroot_path == '/':
  318. rpath_orig = rpath
  319. else:
  320. rpath_orig = rpath[len(chroot_path):]
  321. mtime = MTimeKeeper(path.join(rpath, "profiles/updates"))
  322. mtime.save()
  323. try:
  324. if clean_on_error:
  325. try:
  326. layman = Layman(dv.Get('cl_update_layman_installed'),
  327. dv.Get('cl_update_layman_make'),
  328. dv.Get('cl_update_layman_conf'),
  329. prefix=chroot_path)
  330. if repname not in ("portage", "gentoo"):
  331. layman.add(repname, url, rpath_orig)
  332. if not self._syncRepository(repname, url, rpath, revision,
  333. cb_progress=self.setProgress,
  334. clean=check_status,
  335. notask=fallback_sync):
  336. return "skip"
  337. return True
  338. except GitError as e:
  339. if not isinstance(e, NotGitError):
  340. if e.addon:
  341. self.printWARNING(str(e.addon))
  342. self.printWARNING(str(e))
  343. self.endTask(False)
  344. self.startTask(
  345. _("Re-fetching the {name} repository").format(
  346. name=repname))
  347. self.addProgress()
  348. rpath_new = "%s_new" % rpath
  349. try:
  350. self._syncRepository(repname, url, rpath_new, revision,
  351. cb_progress=self.setProgress,
  352. clean=check_status,
  353. notask=fallback_sync)
  354. removeDir(rpath)
  355. shutil.move(rpath_new, rpath)
  356. except OSError as e:
  357. raise UpdateError(_("Failed to modify the "
  358. "{repname} repository").format(
  359. repname=repname) + _(": ") + str(e))
  360. finally:
  361. if path.exists(rpath_new):
  362. removeDir(rpath_new)
  363. else:
  364. if not self._syncRepository(repname, url, rpath, revision,
  365. clean=check_status):
  366. return "skip"
  367. layman = Layman(dv.Get('cl_update_layman_installed'),
  368. dv.Get('cl_update_layman_make'),
  369. dv.Get('cl_update_layman_conf'),
  370. prefix=chroot_path)
  371. if repname not in ("portage", "gentoo"):
  372. layman.add(repname, url, rpath_orig)
  373. finally:
  374. mtime.restore()
  375. return True
  376. metadata_cache_names = ("metadata/md5-cache", "metadata/cache")
  377. def stash_cache(self, rpath, name):
  378. """
  379. Спрятать кэш
  380. """
  381. if name in ("portage", "gentoo"):
  382. return
  383. if not name in OverlayOwnCache(self.clVars):
  384. for cachename in self.metadata_cache_names:
  385. cachedir = path.join(rpath, cachename)
  386. if path.exists(cachedir):
  387. try:
  388. cachedir_s = path.join(path.dirname(rpath),
  389. path.basename(
  390. cachename) + ".stash")
  391. if path.exists(cachedir_s):
  392. removeDir(cachedir_s)
  393. shutil.move(cachedir, cachedir_s)
  394. except BaseException as e:
  395. pass
  396. def unstash_cache(self, rpath, name):
  397. """
  398. Извлеч кэш
  399. """
  400. if name in ("portage", "gentoo"):
  401. return
  402. cachenames = self.metadata_cache_names
  403. if not name in OverlayOwnCache(self.clVars):
  404. if any(path.exists(path.join(rpath, x)) for x in cachenames):
  405. for cachename in cachenames:
  406. cachedir_s = path.join(path.dirname(rpath),
  407. path.basename(cachename) + ".stash")
  408. if path.exists(cachedir_s):
  409. try:
  410. removeDir(cachedir_s)
  411. except BaseException as e:
  412. pass
  413. OverlayOwnCache(self.clVars).add(name)
  414. else:
  415. for cachename in cachenames:
  416. cachedir = path.join(rpath, cachename)
  417. cachedir_s = path.join(path.dirname(rpath),
  418. path.basename(cachename) + ".stash")
  419. if path.exists(cachedir_s):
  420. try:
  421. shutil.move(cachedir_s, cachedir)
  422. except BaseException as e:
  423. pass
  424. else:
  425. if all(not path.exists(path.join(rpath, x)) for x in cachenames):
  426. OverlayOwnCache(self.clVars).discard(name)
  427. def syncLaymanRepository(self, repname):
  428. """
  429. Обновить репозиторий через layman
  430. """
  431. layman = self.get_prog_path('/usr/bin/layman')
  432. if not layman:
  433. raise UpdateError(_("The Layman tool is not found"))
  434. rpath = self.clVars.Select('cl_update_other_rep_path',
  435. where='cl_update_other_rep_name', eq=repname,
  436. limit=1)
  437. laymanname = path.basename(rpath)
  438. self.stash_cache(rpath, laymanname)
  439. try:
  440. if Git.is_git(rpath):
  441. self.addProgress()
  442. p = PercentProgress(layman, "-s", laymanname, part=1, atty=True)
  443. for perc in p.progress():
  444. self.setProgress(perc)
  445. else:
  446. p = process(layman, "-s", repname, stderr=STDOUT)
  447. if p.failed():
  448. raise UpdateError(
  449. _("Failed to update the {rname} repository").format(
  450. rname=repname),
  451. addon=p.read())
  452. finally:
  453. self.unstash_cache(rpath, laymanname)
  454. return True
  455. def _regenCache_process(self, progname, repname, cpu_num):
  456. return process(progname, "--repo=%s" % repname, "--update",
  457. "--jobs=%s" % cpu_num, stderr=STDOUT)
  458. def regenCache(self, repname):
  459. """
  460. Обновить кэш метаданных репозитория
  461. """
  462. egenCache = self.get_prog_path('/usr/bin/egencache')
  463. if not egenCache:
  464. raise UpdateError(_("The Portage tool is not found"))
  465. if repname in self.clVars.Get('cl_update_rep_name'):
  466. path_rep = self.clVars.Select('cl_update_rep_path',
  467. where='cl_update_rep_name',
  468. eq=repname, limit=1)
  469. repo_name = readFile(
  470. path.join(path_rep, "profiles/repo_name")).strip()
  471. if repo_name != repname:
  472. self.printWARNING(
  473. _("Repository '{repo_name}' called '{repname}'"
  474. " in cl_update_rep_name").format(
  475. repo_name=repo_name, repname=repname))
  476. raise UpdateError(_("Failed to update the cache of the {rname} "
  477. "repository").format(rname=repname))
  478. cpu_num = self.clVars.Get('hr_cpu_num')
  479. if repname in OverlayOwnCache(self.clVars):
  480. self.printWARNING(
  481. _("Repository %s has its own cache") % repname.capitalize())
  482. else:
  483. self.startTask(_("Updating the %s repository cache") %
  484. repname.capitalize())
  485. p = self._regenCache_process(egenCache, repname, cpu_num)
  486. if p.failed():
  487. raise UpdateError(_("Failed to update the cache of the {rname} "
  488. "repository").format(rname=repname),
  489. addon=p.read())
  490. return True
  491. def emergeMetadata(self):
  492. """
  493. Выполнить egencache и emerge --metadata
  494. """
  495. emerge = self.get_prog_path("/usr/bin/emerge")
  496. if not emerge:
  497. raise UpdateError(_("The Emerge tool is not found"))
  498. self.addProgress()
  499. p = PercentProgress(emerge, "--ask=n", "--metadata", part=1, atty=True)
  500. for perc in p.progress():
  501. self.setProgress(perc)
  502. if p.failed():
  503. data = p.read()
  504. with open('/var/log/calculate/failed-metadata-%d.log' % time.time(),
  505. 'w') as f:
  506. f.write(data + p.alldata)
  507. raise UpdateError(_("Failed to update metadata"), addon=data)
  508. return True
  509. def _eixUpdateCommand(self, eix_cmd, countRep):
  510. return PercentProgress(eix_cmd, "-F", part=countRep or 1, atty=True)
  511. def eixUpdate(self, repositroies):
  512. """
  513. Выполенине eix-update для репозиторием
  514. eix-update выполнятется только для тех репозиториев, которые
  515. обновлялись, если cl_update_eixsync_force==auto, либо
  516. все, если cl_update_eixupdate_force==force
  517. """
  518. eixupdate = self.get_prog_path("/usr/bin/eix-update")
  519. if not eixupdate:
  520. raise UpdateError(_("The Eix tool is not found"))
  521. self.addProgress()
  522. countRep = len(repositroies)
  523. p = self._eixUpdateCommand(eixupdate, countRep)
  524. for perc in p.progress():
  525. self.setProgress(perc)
  526. if p.failed():
  527. raise UpdateError(_("Failed to update eix cache"), addon=p.read())
  528. return True
  529. def is_binary_pkg(self, pkg, binary=None):
  530. """
  531. Является ли пакет бинарным
  532. """
  533. if binary:
  534. return True
  535. if 'PN' in pkg and pkg['PN'].endswith('-bin'):
  536. return True
  537. if binary is not None:
  538. return binary
  539. if "binary" in pkg and pkg['binary']:
  540. return True
  541. return False
  542. def _printEmergePackage(self, pkg, binary=False, num=1, max_num=1):
  543. """
  544. Вывод сообщения сборки пакета
  545. """
  546. self.endTask()
  547. _print = self.color_print
  548. if self.pkgnum is not None:
  549. num = self.pkgnum
  550. if self.pkgnummax is not None:
  551. max_num = self.pkgnummax
  552. one = _print("{0}", num)
  553. two = _print("{0}", max_num)
  554. part = _("({current} of {maximum})").format(current=one, maximum=two)
  555. _print = _print.foreground(Colors.DEFAULT)
  556. if self.is_binary_pkg(pkg, binary):
  557. _colorprint = _print.foreground(Colors.PURPLE)
  558. else:
  559. _colorprint = _print.foreground(Colors.GREEN)
  560. PackageInformation.add_info(pkg)
  561. name = ""
  562. if pkg.info['DESCRIPTION']:
  563. name = _(pkg.info['DESCRIPTION'])
  564. name = name[:1].upper() + name[1:]
  565. if not name.strip():
  566. name = str(pkg)
  567. self.printSUCCESS(
  568. _("{part} {package}").format(part=part, package=name))
  569. self.startTask(
  570. _("Emerging {package}").format(package=_colorprint(str(pkg))))
  571. def _printInstallPackage(self, pkg, binary=False):
  572. """
  573. Вывод сообщения установки пакета
  574. """
  575. self.endTask()
  576. _print = self.color_print
  577. if self.is_binary_pkg(pkg, binary):
  578. _print = _print.foreground(Colors.PURPLE)
  579. else:
  580. _print = _print.foreground(Colors.GREEN)
  581. pkg_key = "{CATEGORY}/{PF}".format(**pkg)
  582. if pkg_key in self.update_map:
  583. self.startTask(_("Installing {pkg} [{oldver}]").format(
  584. pkg=_print(str(pkg)), oldver=self.update_map[pkg_key]))
  585. self.update_map.pop(pkg_key)
  586. else:
  587. self.startTask(_("Installing %s") % (_print(str(pkg))))
  588. def _printFetching(self, fn):
  589. """
  590. Вывод сообщения о скачивании
  591. """
  592. self.endTask()
  593. self.startTask(_("Fetching binary packages"))
  594. def _printUninstallPackage(self, pkg, num=1, max_num=1):
  595. """
  596. Вывод сообщения удаления пакета
  597. """
  598. self.endTask()
  599. _print = self.color_print
  600. if num and max_num:
  601. one = _print("{0}", num)
  602. two = _print("{0}", max_num)
  603. part = _(" ({current} of {maximum})").format(current=one,
  604. maximum=two)
  605. else:
  606. part = ""
  607. _print = _print.foreground(Colors.LIGHT_RED)
  608. self.startTask(
  609. _("Unmerging{part} {package}").format(part=part,
  610. package=_print(str(pkg))))
  611. def emergelike(self, cmd, *params):
  612. """
  613. Запуск команды, которая подразумевает выполнение emerge
  614. """
  615. cmd_path = self.get_prog_path(cmd)
  616. if not cmd_path:
  617. raise UpdateError(_("Failed to find the %s command") % cmd)
  618. with EmergeParser(
  619. emerge_parser.CommandExecutor(cmd_path, params)) as emerge:
  620. self._startEmerging(emerge)
  621. return True
  622. def revdep_rebuild(self, cmd, *params):
  623. """
  624. Запуск revdep-rebulid
  625. """
  626. cmd_path = self.get_prog_path(cmd)
  627. if not cmd_path:
  628. raise UpdateError(_("Failed to find the %s command") % cmd)
  629. with EmergeParser(
  630. emerge_parser.CommandExecutor(cmd_path, params)) as emerge:
  631. revdep = RevdepPercentBlock(emerge)
  632. self.addProgress()
  633. revdep.add_observer(self.setProgress)
  634. revdep.action = lambda x: (
  635. self.endTask(), self.startTask(_("Assigning files to packages"))
  636. if "Assign" in revdep else None)
  637. self._startEmerging(emerge)
  638. return True
  639. def _display_pretty_package_list(self, pkglist, remove_list=False):
  640. """
  641. Отобразить список пакетов в "удобочитаемом" виде
  642. """
  643. _print = self.color_print
  644. ebuild_color = TextState.Colors.GREEN
  645. binary_color = TextState.Colors.PURPLE
  646. remove_color = TextState.Colors.LIGHT_RED
  647. flag_map = {"updating":
  648. _print.foreground(TextState.Colors.LIGHT_CYAN)("U"),
  649. "reinstall":
  650. _print.foreground(TextState.Colors.YELLOW)("rR"),
  651. "new":
  652. _print.foreground(TextState.Colors.LIGHT_GREEN)("N"),
  653. "newslot":
  654. _print.foreground(TextState.Colors.LIGHT_GREEN)("NS"),
  655. "downgrading": (
  656. _print.foreground(TextState.Colors.LIGHT_CYAN)("U") +
  657. _print.foreground(TextState.Colors.LIGHT_BLUE)("D"))}
  658. for pkg in sorted([PackageInformation.add_info(x) for x in
  659. pkglist],
  660. key=lambda y: y['CATEGORY/PN']):
  661. install_flag = ""
  662. if remove_list:
  663. pkgcolor = _print.foreground(remove_color)
  664. else:
  665. for flag in flag_map:
  666. if pkg[flag]:
  667. install_flag = "(%s) " % flag_map[flag]
  668. break
  669. if self.is_binary_pkg(pkg):
  670. pkgcolor = _print.foreground(binary_color)
  671. else:
  672. pkgcolor = _print.foreground(ebuild_color)
  673. if pkg.info['DESCRIPTION']:
  674. fullname = "%s " % _(pkg.info['DESCRIPTION'])
  675. fullname = fullname[:1].upper() + fullname[1:]
  676. else:
  677. fullname = ""
  678. shortname = pkgcolor("%s-%s" % (pkg["CATEGORY/PN"], pkg["PVR"]))
  679. if "SIZE" in pkg and pkg['SIZE'] and pkg["SIZE"] != "0 kB":
  680. size = " (%s)" % pkg["SIZE"]
  681. else:
  682. size = ""
  683. mult = _print.bold("*")
  684. self.printDefault(
  685. _u8("&nbsp;{mult} {fullname}{flag}{shortname}{size}").format(
  686. mult=_u8(mult), fullname=_u8(fullname), shortname=_u8(shortname),
  687. size=_u8(size),
  688. flag=_u8(install_flag)))
  689. def _display_install_package(self, emerge, emergelike=False):
  690. """
  691. Отобразить список устанавливаемых пакетов
  692. """
  693. # подробный список пакетов
  694. _print = self.color_print
  695. if emergelike:
  696. self.printPre(str(emerge.install_packages))
  697. else:
  698. pkglist = emerge.install_packages.list
  699. self.printSUCCESS(_print(
  700. _("Listing packages for installation")))
  701. self._display_pretty_package_list(pkglist)
  702. if emerge.install_packages.remove_list:
  703. self.printSUCCESS(_print(
  704. _("Listing packages for removal")))
  705. self._display_pretty_package_list(
  706. emerge.install_packages.remove_list, remove_list=True)
  707. if len(emerge.install_packages.list) > 0:
  708. install_mess = (_("{count} packages will be installed").format(
  709. count=len(emerge.install_packages.list)) + ", ")
  710. else:
  711. install_mess = ""
  712. if str(emerge.download_size) != "0 kB":
  713. self.printSUCCESS(_("{install}{size} will be downloaded").format(
  714. install=install_mess,
  715. size=str(emerge.download_size)))
  716. def _display_remove_list(self, emerge):
  717. """
  718. Отобразить список удаляемых пакетов
  719. """
  720. # подробный список пакетов
  721. if self.clVars.Get('update.cl_update_emergelist_set') == 'on':
  722. self.printPre(self._emerge_translate(
  723. emerge.uninstall_packages.verbose_result))
  724. else:
  725. _print = self.color_print
  726. pkglist = emerge.uninstall_packages.list
  727. self.printSUCCESS(_print.bold(
  728. _("Listing packages for removal")))
  729. self._display_pretty_package_list(pkglist, remove_list=True)
  730. def mark_schedule(self):
  731. """
  732. Установить отметку о запуске запланированной проверки
  733. """
  734. SystemIni(self.clVars).setVar('system', {'last_check': str(int(time.time()))})
  735. def get_default_emerge_opts(self):
  736. if self.clVars.GetBool('cl_update_with_bdeps_set'):
  737. bdeps = " --with-bdeps=y"
  738. else:
  739. bdeps = " --with-bdeps=n"
  740. return self.clVars.Get('cl_emerge_default_opts') + bdeps
  741. def premerge(self, param, *packages):
  742. """
  743. Вывести информацию об обновлении
  744. """
  745. deo = self.get_default_emerge_opts()
  746. param = [param, "-pv"]
  747. with EmergeParser(EmergeCommand(list(packages), emerge_default_opts=deo,
  748. extra_params=param)) as emerge:
  749. try:
  750. emerge.run()
  751. if not emerge.install_packages.list:
  752. self.printSUCCESS(_("The system is up to date"))
  753. self.set_need_update(False)
  754. return True
  755. emergelike = self.clVars.Get('cl_update_emergelist_set') == 'on'
  756. self._display_install_package(emerge, emergelike)
  757. if str(emerge.skipped_packages):
  758. self._display_error(emerge.skipped_packages)
  759. except EmergeError:
  760. self.set_need_update(False)
  761. self._display_install_package(emerge, emergelike=True)
  762. self._display_error(emerge.prepare_error)
  763. raise
  764. if self.clVars.Get('cl_update_pretend_set') == 'on':
  765. # установить кэш: есть обновления
  766. self.set_need_update()
  767. return True
  768. self.set_need_update(False)
  769. answer = self.askConfirm(
  770. _("Would you like to merge these packages?"), "yes")
  771. if answer == "no":
  772. raise KeyboardInterrupt
  773. return "yes"
  774. return True
  775. def set_need_update(self, val=True):
  776. """
  777. Установить флаг: есть обновления
  778. """
  779. if self.clVars.Get('update.cl_update_autocheck_set') == 'off':
  780. val = False
  781. UpdateInfo.set_update_ready(val)
  782. return True
  783. def _emerge_translate(self, s):
  784. """
  785. Перевести текст из emerge
  786. """
  787. return RegexpLocalization('cl_emerge').translate(str(s))
  788. def setUpToDateCache(self):
  789. """
  790. Установить кэш - "нет пакетов для обновления"
  791. """
  792. #self.updateCache(PackageList([]))
  793. self.set_need_update(False)
  794. return True
  795. def _startEmerging(self, emerge):
  796. """
  797. Настроить и выполнить emerge
  798. """
  799. if emerge.install_packages and emerge.install_packages.list:
  800. for pkg in emerge.install_packages.list:
  801. rv = pkg.get('REPLACING_VERSIONS', '')
  802. if rv:
  803. self.update_map["{CATEGORY}/{PF}".format(**pkg)] = \
  804. rv.partition(":")[0]
  805. emerge.command.send("yes\n")
  806. emerge.emerging.add_observer(self._printEmergePackage)
  807. emerge.installing.add_observer(self._printInstallPackage)
  808. emerge.uninstalling.add_observer(self._printUninstallPackage)
  809. emerge.fetching.add_observer(self._printFetching)
  810. def cancel_observing_fetch(fn):
  811. emerge.fetching.clear_observers()
  812. emerge.fetching.add_observer(cancel_observing_fetch)
  813. try:
  814. emerge.run()
  815. except EmergeError:
  816. if emerge.emerging_error:
  817. self._display_error(emerge.emerging_error.log)
  818. else:
  819. self._display_error(emerge.prepare_error)
  820. raise
  821. def _display_error(self, error):
  822. lines_num = int(self.clVars.Get('update.cl_update_lines_limit'))
  823. error = "<br/>".join(str(error).split('<br/>')[-lines_num:])
  824. self.printPre(self._emerge_translate(error))
  825. def emerge(self, use, param, *packages):
  826. """
  827. Выполнить сборку пакета
  828. """
  829. deo = self.get_default_emerge_opts()
  830. if not packages:
  831. packages = [param]
  832. extra_params = None
  833. else:
  834. extra_params = [param]
  835. command = EmergeCommand(list(packages), emerge_default_opts=deo,
  836. extra_params=extra_params,
  837. use=use)
  838. if self.clVars.Get('cl_chroot_path') != '/':
  839. command = Chroot(self.clVars.Get('cl_chroot_path'), command)
  840. with EmergeParser(command) as emerge:
  841. try:
  842. emerge.question.action = lambda x: False
  843. emerge.run()
  844. if not emerge.install_packages.list:
  845. return True
  846. except EmergeError:
  847. self._display_error(emerge.prepare_error)
  848. raise
  849. self._startEmerging(emerge)
  850. return True
  851. def emerge_ask(self, pretend, *params):
  852. """
  853. Вывести информацию об обновлении
  854. """
  855. deo = self.get_default_emerge_opts()
  856. param = [x for x in params if x.startswith("-")]
  857. packages = [x for x in params if not x.startswith("-")]
  858. command = EmergeCommand(list(packages), emerge_default_opts=deo,
  859. extra_params=param)
  860. with EmergeParser(command) as emerge:
  861. try:
  862. emerge.question.action = lambda x: False
  863. emerge.run()
  864. if emerge.install_packages.list:
  865. emergelike = self.clVars.Get(
  866. 'update.cl_update_emergelist_set') == 'on'
  867. self._display_install_package(emerge, emergelike)
  868. if emerge.skipped_packages:
  869. self._display_error(emerge.skipped_packages)
  870. if not pretend:
  871. answer = self.askConfirm(
  872. _("Would you like to merge these packages?"), "yes")
  873. if answer == "no":
  874. emerge.command.send("no\n")
  875. raise KeyboardInterrupt
  876. else:
  877. return True
  878. else:
  879. self.set_need_update(False)
  880. self.printSUCCESS(_("The system is up to date"))
  881. except EmergeError:
  882. self.set_need_update(False)
  883. self._display_install_package(emerge, emergelike=True)
  884. self._display_error(emerge.prepare_error)
  885. raise
  886. self._startEmerging(emerge)
  887. return True
  888. def depclean(self):
  889. """
  890. Выполнить очистку системы от лишних пакетов
  891. """
  892. deo = self.get_default_emerge_opts()
  893. emerge = None
  894. try:
  895. emerge = EmergeParser(EmergeCommand(["--depclean"],
  896. emerge_default_opts=deo))
  897. outdated_kernel = False
  898. try:
  899. emerge.question.action = lambda x: False
  900. emerge.run()
  901. if not emerge.uninstall_packages.list:
  902. UpdateInfo(self.clVars).outdated_kernel = False
  903. return True
  904. kernel_pkg = self.clVars.Get('cl_update_kernel_pkg')
  905. if any(("%s-%s" % (x['CATEGORY/PN'], x['PVR'])) == kernel_pkg
  906. for x in emerge.uninstall_packages.list):
  907. pkglist = [
  908. "=%s-%s" % (x['CATEGORY/PN'], x['PVR']) for x in
  909. emerge.uninstall_packages.list
  910. if ("%s-%s" % (x['CATEGORY/PN'],
  911. x['PVR'])) != kernel_pkg]
  912. emerge.command.send('n\n')
  913. emerge.close()
  914. emerge = None
  915. if not pkglist:
  916. UpdateInfo(self.clVars).outdated_kernel = True
  917. return True
  918. emerge = EmergeParser(
  919. EmergeCommand(pkglist,
  920. extra_params=["--unmerge", '--ask=y'],
  921. emerge_default_opts=deo))
  922. emerge.question.action = lambda x: False
  923. emerge.run()
  924. outdated_kernel = True
  925. else:
  926. outdated_kernel = False
  927. self._display_remove_list(emerge)
  928. except EmergeError:
  929. self._display_error(emerge.prepare_error)
  930. raise
  931. if (self.askConfirm(
  932. _("Would you like to unmerge these unused packages "
  933. "(recommended)?")) != 'yes'):
  934. return True
  935. UpdateInfo(self.clVars).outdated_kernel = outdated_kernel
  936. self._startEmerging(emerge)
  937. finally:
  938. if emerge:
  939. emerge.close()
  940. return True
  941. def update_task(self, task_name):
  942. """
  943. Декоратор для добавления меток запуска и останова задачи
  944. """
  945. def decor(f):
  946. def wrapper(*args, **kwargs):
  947. logger = EmergeLog(EmergeLogNamedTask(task_name))
  948. logger.mark_begin_task()
  949. ret = f(*args, **kwargs)
  950. if ret:
  951. logger.mark_end_task()
  952. return ret
  953. return wrapper
  954. return decor
  955. def migrateCacheRepository(self, url, branch, storage):
  956. """
  957. Перенести репозиторий из кэша в локальный
  958. """
  959. rep = storage.get_repository(url, branch)
  960. if rep:
  961. rep.storage = storage.storages[0]
  962. self.clVars.Invalidate('cl_update_profile_storage')
  963. return True
  964. def reconfigureProfileVars(self, profile_dv, chroot):
  965. """
  966. Синхронизировать репозитории
  967. """
  968. dv = self.clVars
  969. try:
  970. if not profile_dv:
  971. raise UpdateError(
  972. _("Failed to use the new profile. Try again."))
  973. for var_name in ('cl_update_rep_path',
  974. 'cl_update_rep_url',
  975. 'cl_update_rep_name',
  976. 'cl_update_branch',
  977. 'cl_update_binhost_list',
  978. 'cl_update_binhost_unstable_list',
  979. 'cl_update_binhost_stable_set',
  980. 'cl_update_binhost_stable_opt_set',
  981. 'cl_update_branch_name',
  982. 'cl_profile_system',
  983. 'cl_update_rep'):
  984. dv.Set(var_name, profile_dv.Get(var_name), force=True)
  985. dv.Set('cl_chroot_path', chroot, force=True)
  986. except DataVarsError as e:
  987. print str(e)
  988. raise UpdateError(_("Wrong profile"))
  989. return True
  990. def setProfile(self, profile_shortname):
  991. profile = self.clVars.Select('cl_update_profile_path',
  992. where='cl_update_profile_shortname',
  993. eq=profile_shortname, limit=1)
  994. if not profile:
  995. raise UpdateError(_("Failed to determine profile %s") %
  996. self.clVars.Get('cl_update_profile_system'))
  997. profile_path = path.relpath(profile, '/etc/portage')
  998. try:
  999. profile_file = '/etc/portage/make.profile'
  1000. if not path.exists(
  1001. path.join(path.dirname(profile_file), profile_path)):
  1002. raise UpdateError(
  1003. _("Failed to set the profile: %s") % _("Profile not found"))
  1004. for rm_fn in filter(path.lexists,
  1005. ('/etc/make.profile',
  1006. '/etc/portage/make.profile')):
  1007. os.unlink(rm_fn)
  1008. os.symlink(profile_path, profile_file)
  1009. except (OSError, IOError) as e:
  1010. raise UpdateError(_("Failed to set the profile: %s") % str(e))
  1011. return True
  1012. def applyProfileTemplates(self, useClt=None, cltFilter=False,
  1013. useDispatch=True, action="merge"):
  1014. """
  1015. Наложить шаблоны из профиля
  1016. """
  1017. from calculate.lib.cl_template import TemplatesError, ProgressTemplate
  1018. dv = DataVarsUpdate()
  1019. try:
  1020. dv.importUpdate()
  1021. dv.flIniFile()
  1022. dv.Set('cl_action', action, force=True)
  1023. try:
  1024. dv.Set('cl_templates_locate',
  1025. self.clVars.Get('cl_update_templates_locate'))
  1026. except VariableError:
  1027. self.printERROR(_("Failed to apply profiles templates"))
  1028. return True
  1029. dv.Set("cl_chroot_path", '/', True)
  1030. dv.Set("cl_root_path", '/', True)
  1031. for copyvar in ("cl_dispatch_conf", "cl_verbose_set",
  1032. "update.cl_update_world"):
  1033. dv.Set(copyvar, self.clVars.Get(copyvar), True)
  1034. # определение каталогов содержащих шаблоны
  1035. useClt = useClt in ("on", True)
  1036. self.addProgress()
  1037. nullProgress = lambda *args, **kw: None
  1038. dispatch = self.dispatchConf if useDispatch else None
  1039. clTempl = ProgressTemplate(nullProgress, dv, cltObj=useClt,
  1040. cltFilter=cltFilter,
  1041. printSUCCESS=self.printSUCCESS,
  1042. printWARNING=self.printWARNING,
  1043. askConfirm=self.askConfirm,
  1044. dispatchConf=dispatch,
  1045. printERROR=self.printERROR)
  1046. try:
  1047. clTempl.applyTemplates()
  1048. if clTempl.hasError():
  1049. if clTempl.getError():
  1050. raise TemplatesError(clTempl.getError())
  1051. finally:
  1052. if clTempl:
  1053. if clTempl.cltObj:
  1054. clTempl.cltObj.closeFiles()
  1055. clTempl.closeFiles()
  1056. finally:
  1057. dv.close()
  1058. return True
  1059. def cleanpkg(self):
  1060. """
  1061. Очистить PKGDIR и DISTFILES в текущей системе
  1062. """
  1063. portdirs = ([self.clVars.Get('cl_portdir')] +
  1064. self.clVars.Get('cl_portdir_overlay'))
  1065. pkgfiles = get_packages_files_directory(*portdirs)
  1066. distdirfiles = get_manifest_files_directory(*portdirs)
  1067. distdir = self.clVars.Get('install.cl_distfiles_path')
  1068. pkgdir = self.clVars.Get('cl_pkgdir')
  1069. logger = log("update_cleanpkg.log",
  1070. filename="/var/log/calculate/update_cleanpkg.log",
  1071. formatter="%(asctime)s - %(clean)s - %(message)s")
  1072. return self._cleanpkg(
  1073. distdir, pkgdir, distdirfiles, pkgfiles, logger)
  1074. def _update_binhost_packages(self):
  1075. os.system('/usr/sbin/emaint binhost -f &>/dev/null')
  1076. def _cleanpkg(self, distdir, pkgdir, distdirfiles, pkgfiles, logger):
  1077. """
  1078. Общий алгоритм очистки distfiles и pkgdir от устаревших пакетов
  1079. """
  1080. skip_files = ["/metadata.dtd", "/Packages"]
  1081. try:
  1082. if self.clVars.Get('client.os_remote_auth'):
  1083. skip_files += ['portage_lockfile']
  1084. except DataVarsError:
  1085. pass
  1086. for cleantype, filelist in (
  1087. ("packages",
  1088. get_remove_list(pkgdir, list(pkgfiles), depth=4)),
  1089. ("distfiles",
  1090. get_remove_list(distdir, list(distdirfiles), depth=1))):
  1091. removelist = []
  1092. for fn in filelist:
  1093. try:
  1094. if not any(fn.endswith(x) for x in skip_files):
  1095. os.unlink(fn)
  1096. removelist.append(path.basename(fn))
  1097. except OSError:
  1098. pass
  1099. removelist_str = ",".join(removelist)
  1100. if removelist_str:
  1101. logger.info(removelist_str, extra={'clean': cleantype})
  1102. if cleantype == "packages":
  1103. try:
  1104. self._update_binhost_packages()
  1105. for dn in listDirectory(pkgdir, fullPath=True):
  1106. if path.isdir(dn) and not listDirectory(dn):
  1107. os.rmdir(dn)
  1108. except OSError:
  1109. pass
  1110. return True
  1111. def updateSetupCache(self):
  1112. cache = SetupCache(self.clVars)
  1113. cache.update(force=True)
  1114. return True
  1115. def check_binhost(self, write_binhost=True):
  1116. """
  1117. Проверить, что доступен хотя бы один из binhost'ов
  1118. :return:
  1119. """
  1120. hosts = self.clVars.Get("update.cl_update_binhost_host")
  1121. datas = self.clVars.Get("update.cl_update_binhost_revisions")
  1122. bin_cache_fn = pathJoin(self.clVars.Get('cl_chroot_path'),
  1123. LayeredIni.IniPath.Grp)
  1124. if not hosts:
  1125. self.clVars.Delete('cl_update_binhost', location="system")
  1126. try:
  1127. if path.exists(bin_cache_fn):
  1128. os.unlink(bin_cache_fn)
  1129. except OSError:
  1130. raise UpdateError(
  1131. _("Failed to remove cached ini.env of binary repository"))
  1132. raise UpdateError(_("Failed to find the server with "
  1133. "appropriate updates"))
  1134. else:
  1135. with writeFile(bin_cache_fn) as f:
  1136. f.write(datas[0].strip()+"\n")
  1137. if write_binhost:
  1138. if hosts[0] != self.clVars.Get('update.cl_update_binhost'):
  1139. self.refresh_binhost = True
  1140. self.clVars.Set('cl_update_package_cache_set', 'on')
  1141. self.clVars.Write('cl_update_binhost', hosts[0], location="system")
  1142. new_ts = self.clVars.Get("update.cl_update_binhost_timestamp")
  1143. if new_ts:
  1144. new_ts = new_ts[0]
  1145. if new_ts.isdigit():
  1146. ini = SystemIni(self.clVars)
  1147. ini.setVar('system', {'last_update': new_ts})
  1148. if self.clVars.Get('cl_action') == 'sync':
  1149. value = self.clVars.GetBool('cl_update_binhost_stable_set')
  1150. new_value = self.clVars.GetBool('cl_update_binhost_stable_opt_set')
  1151. if value != new_value:
  1152. self.clVars.Write(
  1153. 'cl_update_binhost_stable_set',
  1154. self.clVars.Get('update.cl_update_binhost_stable_opt_set'),
  1155. location="system")
  1156. return True
  1157. def save_with_bdeps(self):
  1158. oldval = self.clVars.Get('cl_update_with_bdeps_set')
  1159. newval = self.clVars.Get('cl_update_with_bdeps_opt_set')
  1160. if oldval != newval:
  1161. self.clVars.Write('cl_update_with_bdeps_set', newval,
  1162. location="system")
  1163. self.clVars.Set('cl_update_force_depclean_set', 'on')
  1164. return True
  1165. def message_binhost_changed(self):
  1166. if self.refresh_binhost:
  1167. self.printWARNING(_("Update server was changed to %s") %
  1168. self.clVars.Get('cl_update_binhost'))
  1169. else:
  1170. self.printSUCCESS(_("Update server %s") %
  1171. self.clVars.Get('cl_update_binhost'))
  1172. return True
  1173. def update_binhost_list(self):
  1174. """
  1175. Обновить список binhost'ов после обновления до master веток
  1176. :return:
  1177. """
  1178. dv = DataVarsUpdate()
  1179. try:
  1180. dv.importUpdate()
  1181. dv.flIniFile()
  1182. changes = False
  1183. for varname in ('update.cl_update_binhost_list',
  1184. 'update.cl_update_binhost_unstable_list',
  1185. 'update.cl_update_binhost_timestamp_path',
  1186. 'cl_update_binhost_revision_path'):
  1187. new_value = dv.Get(varname)
  1188. old_value = self.clVars.Get(varname)
  1189. if new_value != old_value:
  1190. changes = True
  1191. self.clVars.Set(varname, new_value, force=True)
  1192. if not changes:
  1193. self.clVars.Delete('cl_update_binhost', location="system")
  1194. raise UpdateError(_("Failed to find the server with "
  1195. "appropriate updates"))
  1196. except DataVarsError:
  1197. self.clVars.Delete('cl_update_binhost', location="system")
  1198. raise UpdateError(_("Failed to find the server with "
  1199. "appropriate updates"))
  1200. return True
  1201. def drop_binhosts(self, dv):
  1202. """
  1203. Обновление до master веток
  1204. """
  1205. branch = dv.Get('update.cl_update_branch')
  1206. revs = [
  1207. branch for x in dv.Get('update.cl_update_rep_name')
  1208. ]
  1209. dv.Set('update.cl_update_branch_name', revs)
  1210. dv.Invalidate('update.cl_update_rep_rev')
  1211. return True
  1212. def download_packages(self, url_binhost, packages_fn):
  1213. fetch_packages(url_binhost, packages_fn)
  1214. return True