diff --git a/server/eit/commands/pkgmove.py b/server/eit/commands/pkgmove.py new file mode 100644 index 000000000..f7af61ef6 --- /dev/null +++ b/server/eit/commands/pkgmove.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +""" + + @author: Fabio Erculiani + @contact: lxnay@sabayon.org + @copyright: Fabio Erculiani + @license: GPL-2 + + B{Entropy Infrastructure Toolkit}. + +""" +import sys +import os +import errno +import argparse +import tempfile +import codecs +import time + +from entropy.i18n import _ +from entropy.output import purple, teal + +from eit.commands.descriptor import EitCommandDescriptor +from eit.commands.command import EitCommand + + +class EitPkgmove(EitCommand): + """ + Main Eit pkgmove command. + """ + + NAME = "pkgmove" + ALIASES = [] + + def __init__(self, args): + EitCommand.__init__(self, args) + self._repository_id = None + + def _get_parser(self): + """ Overridden from EitCommand """ + descriptor = EitCommandDescriptor.obtain_descriptor( + EitPkgmove.NAME) + parser = argparse.ArgumentParser( + description=descriptor.get_description(), + formatter_class=argparse.RawDescriptionHelpFormatter, + prog="%s %s" % (sys.argv[0], EitPkgmove.NAME)) + + parser.add_argument("repo", default=None, + metavar="", help=_("repository")) + + return parser + + def parse(self): + parser = self._get_parser() + try: + nsargs = parser.parse_args(self._args) + except IOError: + return parser.print_help, [] + + self._repository_id = nsargs.repo + return self._call_locked, [self._pkgmove, self._repository_id] + + def _pkgmove(self, entropy_server): + """ + Open $EDITOR and let user add/remove package moves. + """ + notice_text = """\ +# This is the package move metadata for repository %s, read this: +# - the statements must start with either "move" or "slotmove". +# - move statement syntax: +# move +# - slotmove statement syntax: +# slotmove +# - the order of the statements is taken into consideration (KEPT!). +# - lines not starting with "move" or "slotmove" will be ignored. +# - any line starting with "#" will be ignored as well. +# +# Example: +# move app-foo/bar app-bar/foo +# slotmove >=x11-libs/foo-2.0 0 2.0 + +""" % (self._repository_id,) + tmp_path = None + repo = entropy_server.open_server_repository( + self._repository_id, read_only=False) + actions = repo.retrieveTreeUpdatesActions(self._repository_id) + new_actions = [] + while True: + + if tmp_path is None: + tmp_fd, tmp_path = tempfile.mkstemp( + prefix = 'entropy.server.pkgmove', + suffix = ".conf") + with os.fdopen(tmp_fd, "w") as tmp_f: + tmp_f.write(notice_text) + for action in actions: + tmp_f.write(action + "\n") + tmp_f.flush() + + success = entropy_server.edit_file(tmp_path) + if not success: + # retry ? + os.remove(tmp_path) + return 1 + + del new_actions[:] + invalid_lines = [] + with codecs.open(tmp_path, "r", encoding="utf-8") as tmp_f: + for line in tmp_f.readlines(): + if line.startswith("#"): + # skip + continue + strip_line = line.strip() + if not strip_line: + # ignore + continue + if strip_line.startswith("move"): + split_line = strip_line.split() + if len(split_line) != 3: + entropy_server.output( + "%s: %s !!!" % (_("invalid line"), strip_line), + importance=1, level="warning") + invalid_lines.append(strip_line) + else: + new_actions.append(strip_line) + elif strip_line.startswith("slotmove"): + split_line = strip_line.split() + if len(split_line) != 4: + entropy_server.output( + "%s: %s !!!" % ( + purple(_("invalid line")), strip_line), + importance=1, level="warning") + invalid_lines.append(strip_line) + else: + new_actions.append(strip_line) + else: + entropy_server.output( + "%s: %s !!!" % ( + purple(_("invalid line")), strip_line), + importance=1, level="warning") + invalid_lines.append(strip_line) + + if invalid_lines: + resp = entropy_server.ask_question( + _("Invalid syntax, what to do ?"), + responses=(_("Repeat"), _("Abort"))) + if resp == _("Abort"): + os.remove(tmp_path) + return 1 + else: + # repeat, edit same file + continue + + # show submitted info + for action in new_actions: + entropy_server.output(action, level="generic") + entropy_server.output("", level="generic") + + # ask confirmation + while True: + try: + rc_question = entropy_server.ask_question( + "[%s] %s" % ( + purple(self._repository_id), + teal(_("Do you agree?")) + ), + responses = (_("Yes"), _("Repeat"), _("No"),) + ) + except KeyboardInterrupt: + # do not allow, we're in a critical region + continue + break + if rc_question == _("Yes"): + break + elif rc_question == _("No"): + return 1 + # otherwise repeat everything again + # keep tmp_path + + if tmp_path is not None: + try: + os.remove(tmp_path) + except (OSError) as err: + if err.errno != errno.ENOENT: + raise + + # write new actions + branch = self._settings()['repositories']['branch'] + actions_meta = [] + cur_t = time.time() + # time is completely fake, no particular precision required + for action in new_actions: + cur_t += 1 + actions_meta.append((action, branch, str(cur_t))) + + repo.removeTreeUpdatesActions(self._repository_id) + try: + repo.insertTreeUpdatesActions(actions_meta, self._repository_id) + except Exception as err: + repo.rollback() + raise + repo.commit() + + return 0 + + +EitCommandDescriptor.register( + EitCommandDescriptor( + EitPkgmove, + EitPkgmove.NAME, + _('edit automatic package moves for repository')) + )