feat: allow to run daemon mode with split packages check (#120)

This commit is contained in:
2023-12-27 03:05:44 +02:00
committed by GitHub
parent f6cdd806b2
commit b4fa10781b
12 changed files with 335 additions and 25 deletions

View File

@ -607,16 +607,22 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--partitions", help="instead of updating whole repository, split updates into chunks",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
parser.add_argument("--vcs", help="fetch actual version of VCS packages",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.set_defaults(handler=handlers.Daemon, exit_code=False, package=[])
parser.set_defaults(handler=handlers.Daemon, exit_code=False,
lock=Path(tempfile.gettempdir()) / "ahriman-daemon.lock", package=[])
return parser

View File

@ -0,0 +1,133 @@
#
# Copyright (c) 2021-2023 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import time
from collections.abc import Iterator
from typing import Self
from ahriman.application.application import Application
from ahriman.core.tree import Tree
class UpdatesIterator(Iterator[list[str] | None]):
"""
class-helper for iteration over packages to check for updates. It yields list of packages which were not yet
updated
Attributes:
application(Application): application instance
interval(int): predefined interval for updates. The updates will be split into chunks in the way in which all
packages will be updated in the specified interval
updated_packages(set[str]): list of packages which have been already updated
Examples:
Typical usage of this class is something like:
>>> application = ...
>>> iterator = UpdatesIterator(application, None)
>>>
>>> for updates in iterator:
>>> print(updates)
"""
def __init__(self, application: Application, interval: int) -> None:
"""
default constructor
Args:
application(Application): application instance
interval(int): predefined interval for updates
"""
self.application = application
self.interval = interval
self.updated_packages: set[str] = set()
def select_packages(self) -> tuple[list[str] | None, int]:
"""
select next packages partition for updates
Returns:
tuple[list[str] | None, int]: packages partition for updates if any and total amount of partitions.
"""
packages = self.application.repository.packages()
if not packages: # empty repository case
return None, 1
# split packages to the maximal available amount of chunks
partitions = Tree.partition(packages, count=len(packages))
frequency = len(partitions) # must be always not-empty
for partition in partitions:
bases = [package.base for package in partition]
# check if all packages from this partition have been already updated
if self.updated_packages.issuperset(bases):
continue
# there are packages which were not checked yet, return them
return bases, frequency
# in this case there is nothing to update or repository is empty
self.updated_packages.clear()
# extract bases from the first partition and return them
bases = [package.base for package in next(iter(partitions))]
return bases, frequency
def __iter__(self) -> Self:
"""
base iterator method
Returns:
Self: iterator instance
"""
return self
def __next__(self) -> list[str] | None:
"""
retrieve next element in the iterator. This method will delay result for the amount of time equals
:attr:`interval` divided by the amount of chunks
Returns:
list[str] | None: next packages chunk to be updated. ``None`` means no updates
"""
to_update, frequency = self.select_packages()
if to_update is not None:
# update cached built packages
self.updated_packages.update(to_update)
# wait for update before emit
time.sleep(self.interval / frequency)
return to_update
class FixedUpdatesIterator(UpdatesIterator):
"""
implementation of the :class:`UpdatesIterator` which always emits empty list, which is the same as update all
"""
def select_packages(self) -> tuple[list[str] | None, int]:
"""
select next packages partition for updates
Returns:
tuple[list[str] | None, int]: packages partition for updates if any and total amount of partitions.
"""
return [], 1

View File

@ -18,8 +18,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
import threading
from ahriman.application.application import Application
from ahriman.application.application.updates_iterator import FixedUpdatesIterator, UpdatesIterator
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
@ -44,9 +45,15 @@ class Daemon(Handler):
"""
from ahriman.application.handlers import Update
event = threading.Event()
try:
while not event.wait(args.interval):
Update.run(args, repository_id, configuration, report=report)
except KeyboardInterrupt:
pass # normal exit
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
if args.partitions:
iterator = UpdatesIterator(application, args.interval)
else:
iterator = FixedUpdatesIterator(application, args.interval)
for packages in iterator:
if packages is None:
continue # nothing to check case
args.package = packages
Update.run(args, repository_id, configuration, report=report)