diff --git a/debian/changelog b/debian/changelog index 5e32810..b7c042d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -repolib (2.2.0-99pika7.lunar) lunar; urgency=medium +pid (3.0.4-99pika1.lunar) lunar; urgency=medium * Initial Creation diff --git a/debian/control b/debian/control index e5b2775..ded5e6a 100644 --- a/debian/control +++ b/debian/control @@ -1,4 +1,4 @@ -Source: repolib +Source: pid Section: python Priority: optional Maintainer: Ian Santopietro @@ -14,30 +14,18 @@ Build-Depends: debhelper (>= 11), python3-setuptools, python3-distutils, python3-pytest + python3-installer, + python3-wheel, + python3-build Standards-Version: 4.1.3 -Homepage: https://github.com/isantop/repolib +Homepage: https://github.com/trbs/pid XS-Python3-Version: >= 3.7 -Vcs-Browser: https://github.com/isantop/repolib -Vcs-Git: https://github.com/isantop/repolib.git +Vcs-Browser: https://github.com/trbs/pid +Vcs-Git: https://github.com/trbs/pid.git -Package: python3-repolib +Package: python3-pid Architecture: all Depends: ${python3:Depends}, ${misc:Depends}, - apt, - lsb-release, - python3-dbus, - python3-debian, - python3-gnupg -Recommends: python3-lazr.restfulclient, - python3-launchpadlib -Description: Repository Management for APT in Python (Python 3) - RepoLib is a Python library and CLI tool-set for managing your software - system software repositories. It's currently set up to handle APT repositories - on Debian-based linux distributions. - . - RepoLib is intended to operate on DEB822-format sources. It aims to provide - feature parity with software-properties for most commonly used functions. - . - This package installs the library for Python 3. +Description: Pidfile featuring stale detection and file-locking diff --git a/debian/rules b/debian/rules index faedc5c..3f18888 100755 --- a/debian/rules +++ b/debian/rules @@ -3,27 +3,22 @@ ### WTF ### # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. -#export DH_VERBOSE = 1 +export DH_VERBOSE = 1 # -#export PYBUILD_NAME=repolib -#export PYBUILD_OPTION=--test-pytest +export PYBUILD_NAME=pid # -#%: -# dh $@ --with python3 --buildsystem=pybuild +%: + dh $@ --with python3 --buildsystem=pybuild # ## Uncomment to disable testing during package builds ## NOTE for QA or Engineering Review: This should not be uncommented in a ## PR. If it is, DO NOT APPROVE THE PR!!! -# override_dh_auto_test: -# true +override_dh_auto_test: + true # If you need to rebuild the Sphinx documentation # Add spinxdoc to the dh --with line -#override_dh_auto_build: -# dh_auto_build -# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator -# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator -### WTF ### - -%: - dh $@ +override_dh_auto_build: + dh_auto_build + PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator + PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator \ No newline at end of file diff --git a/main.sh b/main.sh index 9f3f295..05b5950 100755 --- a/main.sh +++ b/main.sh @@ -1,11 +1,7 @@ # Clone Upstream -### WTF ### -#git clone https://github.com/pop-os/repolib -#rm -rvf ./repolib/debian -### WTF ### -cp -rvf ./python3-repolib.install ./debian/ -cp -rvf ./debian ./repolib/ -cd ./repolib +git clone https://github.com/trbs/pid -b 3.0.4 +cp -rvf ./debian ./pid/ +cd ./pid # Get build deps apt-get build-dep ./ -y diff --git a/python3-repolib.install b/python3-repolib.install deleted file mode 100644 index 197b8cc..0000000 --- a/python3-repolib.install +++ /dev/null @@ -1,2 +0,0 @@ -usr -etc diff --git a/repolib/etc/dbus-1/system.d/org.pop_os.repolib.conf b/repolib/etc/dbus-1/system.d/org.pop_os.repolib.conf deleted file mode 100644 index cadef8b..0000000 --- a/repolib/etc/dbus-1/system.d/org.pop_os.repolib.conf +++ /dev/null @@ -1,20 +0,0 @@ - - - - system - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/repolib/usr/bin/apt-manage b/repolib/usr/bin/apt-manage deleted file mode 100755 index 77ae812..0000000 --- a/repolib/usr/bin/apt-manage +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2020, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" -#pylint: disable=invalid-name -# Pylint will complain about our module name not being snake_case, however this -# is a command rather than a python module, and thus this is correct anyway. - -import argparse -import logging -import os -import sys - -import repolib -from repolib import command - -SOURCES_DIR = '/etc/apt/sources.list.d' - -def main(options=None): - """ Main function for apt-manage.""" - # Set up Argument Parsing. - parser = repolib.command.parser - - # Parse options - args = parser.parse_args() - if options: - args = parser.parse_args(options) - - if not args.debug: - args.debug = 0 - - if args.debug > 2: - args.debug = 2 - - verbosity = { - 0 : logging.WARN, - 1 : logging.INFO, - 2 : logging.DEBUG - } - - log = logging.getLogger('apt-manage') - handler = logging.StreamHandler() - formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s') - handler.setFormatter(formatter) - log.addHandler(handler) - log.setLevel(verbosity[args.debug]) - log.debug('Logging set up!') - repolib.set_logging_level(args.debug) - - if not args.action: - args = parser.parse_args(sys.argv[1:] + ['list']) - - log.debug('Arguments passed: %s', str(args)) - log.debug('Got command: %s', args.action) - - subcommand = args.action.capitalize() - - command = getattr(repolib.command, subcommand)(log, args, parser) - result = command.run() - if not result: - sys.exit(1) - -if __name__ == '__main__': - try: - main() - except KeyboardInterrupt: - print('') - sys.exit(130) diff --git a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/PKG-INFO b/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/PKG-INFO deleted file mode 100644 index d183cd9..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/PKG-INFO +++ /dev/null @@ -1,182 +0,0 @@ -Metadata-Version: 2.1 -Name: repolib -Version: 2.2.0 -Summary: Easily manage software sources -Home-page: https://github.com/pop-os/repolib -Author: Ian Santopietro -Author-email: ian@system76.com -License: LGPLv3 -Download-URL: https://github.com/pop-os/repolib/releases -Platform: UNKNOWN -License-File: LICENSE -License-File: LICENSE.LESSER - -======= -RepoLib -======= - -RepoLib is a Python library and CLI tool-set for managing your software -system software repositories. It's currently set up to handle APT repositories -on Debian-based linux distributions. - -RepoLib is intended to operate on DEB822-format sources. It aims to provide -feature parity with software-properties for most commonly used functions. - -Documentation -============= - -Documentation is available online at `Read The Docs `_. - - -Basic CLI Usage ---------------- - -RepoLib includes a CLI program for managing software repositories, -:code:`apt-manage` -. - -Usage is divided into subcommands for most tasks. Currently implemented commands -are: - - apt-manage add # Adds repositories to the system - apt-manage list # Lists configuration details of repositories - -Additional information is available with the built-in help: - - apt-manage --help - - -Add -^^^ - -Apt-manage allows entering a URL for a repository, a complete debian line, or a -Launchpad PPA shortcut (e.g. "ppa:user/repo"). It also adds signing keys for PPA -style repositories automatically. - - -List -^^^^ - -With no options, it outputs a list of the currently configured repositories on -the system (all those found in -:code:`/etc/apt/sources.list.d/` -. With a configured repository as an argument, it outputs the configuration -details of the specified repository. - -Remove -^^^^^^ - -Accepts one repository as an argument. Removes the specified repository. - -NOTE: The system repository (/etc/at/sources.list.d/system.sources) cannot be -removed. - -Source -^^^^^^ - -Allows enabling or disabling source code for the given repository. - -Modify -^^^^^^ - -Allows changing configuration details of a given repository - -Installation -============ - -From System Package Manager ---------------------------- - -If your operating system packages repolib, you can install it by running:: - - sudo apt install python3-repolib - - -Uninstall -^^^^^^^^^ - -To uninstall, simply do:: - - sudo apt remove python3-repolib - - -From PyPI ---------- - -Repolib is available on PyPI. You can install it for your current user with:: - - pip3 install repolib - -Alternatively, you can install it system-wide using:: - - sudo pip3 install repolib - -Uninstall -^^^^^^^^^ - -To uninstall, simply do:: - - sudo pip3 uninstall repolib - -From Git --------- - -First, clone the git repository onto your local system:: - - git clone https://github.com/isantop/repolib - cd repolib - -Debian ------- - -On debian based distributions, you can build a .deb package locally and install -it onto your system. You will need the following build-dependencies: - - * debhelper (>=11) - * dh-python - * python3-all - * python3-setuptools - -You can use this command to install these all in one go:: - - sudo apt install debhelper dh-python python3-all python3-setuptools - -Then build and install the package:: - - debuild -us -uc - cd .. - sudo dpkg -i python3-repolib_*.deb - -Uninstall -^^^^^^^^^ - -To uninstall, simply do:: - - sudo apt remove python3-repolib - -setuptools setup.py -------------------- - -You can build and install the package using python3-setuptools. First, install -the dependencies:: - - sudo apt install python3-all python3-setuptools - -Then build and install the package:: - - sudo python3 ./setup.py install - -Uninstall -^^^^^^^^^ - -You can uninstall RepoLib by removing the following files/directories: - - * /usr/local/lib/python3.7/dist-packages/repolib/ - * /usr/local/lib/python3.7/dist-packages/repolib-\*.egg-info - * /usr/local/bin/apt-manage - -This command will remove all of these for you:: - - sudo rm -r /usr/local/lib/python3.7/dist-packages/repolib* /usr/local/bin/apt-manage - - diff --git a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/dependency_links.txt b/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/requires.txt b/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/requires.txt deleted file mode 100644 index a8ef404..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/requires.txt +++ /dev/null @@ -1 +0,0 @@ -gnupg diff --git a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/top_level.txt b/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/top_level.txt deleted file mode 100644 index d4b84bf..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib-2.2.0.egg-info/top_level.txt +++ /dev/null @@ -1,4 +0,0 @@ -repolib -repolib/command -repolib/shortcuts -repolib/unittest diff --git a/repolib/usr/lib/python3/dist-packages/repolib/__init__.py b/repolib/usr/lib/python3/dist-packages/repolib/__init__.py deleted file mode 100644 index 4b4b7e5..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/__init__.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging -import logging.handlers as handlers - -from pathlib import Path - -from . import __version__ - -VERSION = __version__.__version__ - -from .file import SourceFile, SourceFileError -from .source import Source, SourceError -from .shortcuts import PPASource, PopdevSource, shortcut_prefixes -from .key import SourceKey, KeyFileError -from . import util -from . import system - -LOG_FILE_PATH = '/var/log/repolib.log' -LOG_LEVEL = logging.WARNING -KEYS_DIR = util.KEYS_DIR -SOURCES_DIR = util.SOURCES_DIR -TESTING = util.TESTING -KEYSERVER_QUERY_URL = util.KEYSERVER_QUERY_URL -DISTRO_CODENAME = util.DISTRO_CODENAME -PRETTY_PRINT = util.PRETTY_PRINT -CLEAN_CHARS = util.CLEAN_CHARS - -try: - from systemd.journal import JournalHandler - systemd_support = True -except ImportError: - systemd_support = False - -## Setup logging -stream_fmt = logging.Formatter( - '%(name)-21s: %(levelname)-8s %(message)s' -) -file_fmt = logging.Formatter( - '%(asctime)s - %(name)-21s: %(levelname)-8s %(message)s' -) -log = logging.getLogger(__name__) - -console_log = logging.StreamHandler() -console_log.setFormatter(stream_fmt) -console_log.setLevel(LOG_LEVEL) - -# file_log = handlers.RotatingFileHandler( -# LOG_FILE_PATH, maxBytes=(1048576*5), backupCount=5 -# ) -# file_log.setFormatter(file_fmt) -# file_log.setLevel(LOG_LEVEL) - -log.addHandler(console_log) -# log.addHandler(file_log) - -log_level_map:dict = { - 0: logging.WARNING, - 1: logging.INFO, - 2: logging.DEBUG -} - -if systemd_support: - journald_log = JournalHandler() # type: ignore (this is handled by the wrapping if) - journald_log.setLevel(logging.INFO) - journald_log.setFormatter(stream_fmt) - log.addHandler(journald_log) - -log.setLevel(logging.DEBUG) - -def set_testing(testing:bool = True) -> None: - """Puts Repolib into testing mode""" - global KEYS_DIR - global SOURCES_DIR - - util.set_testing(testing=testing) - KEYS_DIR = util.KEYS_DIR - SOURCES_DIR = util.SOURCES_DIR - - -def set_logging_level(level:int) -> None: - """Set the logging level for this current repolib - - Accepts an integer between 0 and 2, with 0 being the default loglevel of - logging.WARNING, 1 being logging.INFO, and 2 being logging.DEBUG. - - Values greater than 2 are clamped to 2. Values less than 0 are clamped to 0. - - Note: This only affects console output. Log file output remains - at logging.INFO - - Arguments: - level(int): A logging level from 0-2 - """ - if level > 2: - level = 2 - if level < 0: - level = 0 - LOG_LEVEL = log_level_map[level] - console_log.setLevel(LOG_LEVEL) - -RepoError = util.RepoError -SourceFormat = util.SourceFormat -SourceType = util.SourceType -AptSourceEnabled = util.AptSourceEnabled - -scrub_filename = util.scrub_filename -url_validator = util.url_validator -prettyprint_enable = util.prettyprint_enable -validate_debline = util.validate_debline -strip_hashes = util.strip_hashes -compare_sources = util.compare_sources -combine_sources = util.combine_sources -sources = util.sources -files = util.files -keys = util.keys -errors = util.errors - -valid_keys = util.valid_keys -options_inmap = util.options_inmap -options_outmap = util.options_outmap -true_values = util.true_values - -load_all_sources = system.load_all_sources diff --git a/repolib/usr/lib/python3/dist-packages/repolib/__version__.py b/repolib/usr/lib/python3/dist-packages/repolib/__version__.py deleted file mode 100644 index a56edd5..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/__version__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" -__version__ = "2.2.0" diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/__init__.py b/repolib/usr/lib/python3/dist-packages/repolib/command/__init__.py deleted file mode 100644 index 86f8e6c..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from .argparser import get_argparser -from .add import Add -from .list import List -from .remove import Remove -from .modify import Modify -from .key import Key - -parser = get_argparser() diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/add.py b/repolib/usr/lib/python3/dist-packages/repolib/command/add.py deleted file mode 100644 index 5ba3f14..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/add.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from httplib2.error import ServerNotFoundError -from urllib.error import URLError - -from .. import util -from ..source import Source, SourceError -from ..file import SourceFile, SourceFileError -from ..shortcuts import ppa, popdev, shortcut_prefixes -from .command import Command, RepolibCommandError - -class Add(Command): - """Add subcommand - - Adds a new source into the system. Requests root, if not present. - - Options: - --disable, d - --source-code, -s - --terse, -t - --name, -n - --identifier, -i - --format, -f - --skip-keys, -k - """ - - @classmethod - def init_options(cls, subparsers): - """ Sets up the argument parser for this command. - - Returns: argparse.subparser - The subparser for this command - """ - - sub = subparsers.add_parser( - 'add', - help='Add a new repository to the system' - ) - - sub.add_argument( - 'deb_line', - nargs='*', - default=['822styledeb'], - help='The deb line of the repository to add' - ) - sub.add_argument( - '-d', - '--disable', - action='store_true', - help='Add the repository and then set it to disabled.' - ) - sub.add_argument( - '-s', - '--source-code', - action='store_true', - help='Also enable source code packages for the repository.' - ) - sub.add_argument( - '-t', - '--terse', - action='store_true', - help='Do not display expanded info about a repository before adding it.' - ) - sub.add_argument( - '-n', - '--name', - default='', - help='A name to set for the new repo' - ) - sub.add_argument( - '-i', - '--identifier', - default='', - help='The filename to use for the new source' - ) - sub.add_argument( - '-f', - '--format', - default='sources', - help='The source format to save as, `sources` or `list`' - ) - sub.add_argument( - '-k', - '--skip-keys', - action='store_true', - help='Skip adding signing keys (not recommended!)' - ) - - return sub - - def finalize_options(self, args): - super().finalize_options(args) - self.deb_line = ' '.join(args.deb_line) - self.terse = args.terse - self.source_code = args.source_code - self.disable = args.disable - self.log.debug(args) - - try: - name = args.name.split() - except AttributeError: - name = args.name - - try: - ident = args.identifier.split() - except AttributeError: - ident = args.identifier - - self.name = ' '.join(name) - pre_ident:str = '-'.join(ident) - self.ident = util.scrub_filename(pre_ident) - self.skip_keys = args.skip_keys - self.format = args.format.lower() - - def run(self) -> bool: - """Run the command, return `True` if successful; otherwise `False`""" - if self.deb_line == '822styledeb': - self.parser.print_usage() - self.log.error('A repository is required') - return False - - repo_is_url = self.deb_line.startswith('http') - repo_is_nospaced = len(self.deb_line.split()) == 1 - - if repo_is_url and repo_is_nospaced: - self.deb_line = f'deb {self.deb_line} {util.DISTRO_CODENAME} main' - - print('Fetching repository information...') - - self.log.debug('Adding line %s', self.deb_line) - - new_source: Source = Source() - - for prefix in shortcut_prefixes: - self.log.debug('Trying prefix %s', prefix) - if self.deb_line.startswith(prefix): - self.log.debug('Line is prefix: %s', prefix) - new_source = shortcut_prefixes[prefix]() - if not new_source.validator(self.deb_line): - self.log.error( - 'The shortcut "%s" is malformed', - self.deb_line - ) - - # Try and get a suggested correction for common errors - try: - if self.deb_line[len(prefix)] != ':': - fixed_debline: str = self.deb_line.replace( - self.deb_line[len(prefix)], - ":", - 1 - ) - print(f'Did you mean "{fixed_debline}"?') - except IndexError: - pass - return False - - try: - new_source.load_from_data([self.deb_line]) - except (URLError, ServerNotFoundError) as err: - import traceback - self.log.debug( - 'Exception info: %s \n %s \n %s', - type(err), - ''.join(traceback.format_exception(err)), - err.args - ) - self.log.error( - 'System is offline. A connection is required to add ' - 'PPA and Popdev sources.' - ) - return False - except Exception as err: - import traceback - self.log.debug( - 'Exception info: %s \n %s \n %s', - type(err), - err.__traceback__, - err - ) - self.log.error('An error ocurred: %s', err) - return False - break - - - if not new_source: - self.log.error( - f'Could not parse line "{self.deb_line}". Double-check the ' - 'spelling.' - ) - valid_shortcuts: str = '' - for shortcut in shortcut_prefixes: - if shortcut.startswith('deb'): - continue - valid_shortcuts += f'{shortcut}, ' - valid_shortcuts = valid_shortcuts.strip(', ') - print(f'Supported repository shortcuts:\n {valid_shortcuts}') - return False - - new_source.twin_source = True - new_source.sourcecode_enabled = self.source_code - - if self.name: - new_source.name = self.name - if self.ident: - new_source.ident = self.ident - if self.disable: - new_source.enabled = False - - if not new_source.ident: - new_source.ident = new_source.generate_default_ident() - - new_file = SourceFile(name=new_source.ident) - new_file.format = new_source.default_format - if self.format: - self.log.info('Setting new source format to %s', self.format) - for format in util.SourceFormat: - if self.format == format.value: - new_file.format = format - self.log.debug('New source format set to %s', format.value) - - new_file.add_source(new_source) - new_source.file = new_file - self.log.debug('File format: %s', new_file.format) - self.log.debug('File path: %s', new_file.path) - - self.log.debug('Sources in file %s:\n%s', new_file.path, new_file.sources) - - if not self.terse: - print( - 'Adding the following source: \n', - new_source.get_description(), - '\n\n', - new_source.ui - ) - try: - input( - 'Press ENTER to continue adding this source or Ctrl+C ' - 'to cancel' - ) - except KeyboardInterrupt: - # Handled here to avoid printing the exception to console - exit(0) - - new_file.save() - util.dbus_quit() - return True diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/argparser.py b/repolib/usr/lib/python3/dist-packages/repolib/command/argparser.py deleted file mode 100644 index 182ccd7..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/argparser.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2020, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . - -Module for getting an argparser. Used by apt-manage -""" - -import argparse -import inspect - -from repolib import command as cmd - -from .. import __version__ - -def get_argparser(): - """ Get an argument parser with our arguments. - - Returns: - An argparse.ArgumentParser. - """ - parser = argparse.ArgumentParser( - prog='apt-manage', - description='Manage software sources and repositories', - epilog='apt-manage version: {}'.format(__version__.__version__) - ) - - parser.add_argument( - '-b', - '--debug', - action='count', - help=argparse.SUPPRESS - ) - - subparsers = parser.add_subparsers( - help='...', - dest='action', - metavar='COMMAND' - ) - - subcommands = [] - for i in inspect.getmembers(cmd, inspect.isclass): - obj = getattr(cmd, i[0]) - subcommands.append(obj) - - for i in subcommands: - i.init_options(subparsers) - - return parser diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/command.py b/repolib/usr/lib/python3/dist-packages/repolib/command/command.py deleted file mode 100644 index b4afa41..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/command.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Copyright (c) 2020, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . - -Command line application helper class. -""" - -from ..util import SOURCES_DIR - -class RepolibCommandError(Exception): - """ Exceptions generated by Repolib CLIs.""" - - def __init__(self, *args, code=1, **kwargs): - """Exception from a CLI - - Arguments: - code (:obj:`int`, optional, default=1): Exception error code. - """ - super().__init__(*args, **kwargs) - self.code = code - -class Command: - # pylint: disable=no-self-use,too-few-public-methods - # This is a base class for other things to inherit and give other programs - # a standardized interface for interacting with commands. - """ CLI helper class for developing command line applications.""" - - def __init__(self, log, args, parser): - self.log = log - self.parser = parser - self.sources_dir = SOURCES_DIR - self.finalize_options(args) - - def finalize_options(self, args): - """ Base options parsing class. - - Use this class in commands to set up the final options parsing and set - instance variables for later use. - """ - self.verbose = False - self.debug = False - if args.debug: - if args.debug > 1: - self.verbose = True - if args.debug != 0: - self.log.info('Debug mode set, not-saving any changes.') - self.debug = True - - def run(self): - """ The initial base for running the command. - - This needs to have a standardized format, argument list, and return - either True or False depending on whether the command was successful or - not. - - Returns: - True if the command succeeded, otherwise False. - """ - - # Since this is just the base, it should always return True (because - # you shouldn't fail at doing nothing). - return True diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/key.py b/repolib/usr/lib/python3/dist-packages/repolib/command/key.py deleted file mode 100644 index ccd97d1..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/key.py +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from pathlib import Path - -from ..key import SourceKey -from .. import system, util - -from .command import Command - -KEYS_PATH = Path(util.KEYS_DIR) - -class Key(Command): - """Key subcommand. - - Manages signing keys for repositories, allowing adding, removal, and - fetching of remote keys. - - Options: - --name, -n - --path, -p - --url, -u - --ascii, -a - --fingerprint, -f - --keyserver, -s - --remove, -r - """ - - @classmethod - def init_options(cls, subparsers) -> None: - """Sets up this command's options parser""" - - sub = subparsers.add_parser( - 'key', - help='Manage repository signing keys', - epilog=( - 'Note that no verification of key validity is performed when ' - 'managing key using apt-mange. Ensure that keys are valid to ' - 'avoid errors with updates.' - ) - ) - - sub.add_argument( - 'repository', - default=['x-repolib-none'], - help='Which repository to manage keys for.' - ) - - sub_group = sub.add_mutually_exclusive_group() - - sub_group.add_argument( - '-n', - '--name', - help='The name of an existing key file to set.' - ) - - sub_group.add_argument( - '-p', - '--path', - help='Sets a path on disk to a signing key.' - ) - - sub_group.add_argument( - '-u', - '--url', - help='Download a key over HTTPS' - ) - - sub_group.add_argument( - '-a', - '--ascii', - help='Add an ascii-armored key' - ) - - sub_group.add_argument( - '-f', - '--fingerprint', - help=( - 'Fetch a key via fingerprint from a keyserver. Use --keyserver ' - 'to specify the URL to the keyserver.' - ) - ) - - sub_group.add_argument( - '-i', - '--info', - action='store_true', - help='Print key information' - ) - - sub_group.add_argument( - '-r', - '--remove', - action='store_true', - help=( - 'Remove a signing key from the repo. If no other sources use ' - 'this key, its file will be deleted.' - ) - ) - - sub.add_argument( - '-s', - '--keyserver', - help=( - 'The keyserver from which to fetch the given fingerprint. ' - '(Default: keyserver.ubuntu.com)' - ) - ) - - def finalize_options(self, args): - super().finalize_options(args) - self.repo = args.repository - self.keyserver = args.keyserver - - self.actions:dict = {} - self.system_source = False - - for act in [ - 'name', - 'path', - 'url', - 'ascii', - 'fingerprint', - 'info', - 'remove', - ]: - self.actions[act] = getattr(args, act) - - system.load_all_sources() - - def run(self): - """Run the command""" - self.log.info('Modifying signing key settings for %s', self.repo) - - if not self.repo: - self.log.error('No repository provided') - return False - - try: - self.source = util.sources[self.repo] - if self.repo == 'system': - self.system_source = True - except KeyError: - self.log.error( - 'The repository %s was not found. Check the spelling', - self.repo - ) - return False - - self.log.debug('Actions to take:\n%s', self.actions) - # Run info, unless a different action is specified - self.actions['info'] = True - for key in self.actions: - if key == 'info': - self.log.debug('Skipping info key') - continue - if self.actions[key]: - self.log.info('Got an action, skipping info') - self.actions['info'] = False - break - - self.log.debug('Source before:\n%s', self.source) - - rets = [] - for action in self.actions: - if self.actions[action]: - self.log.debug('Running action: %s - (%s)', action, self.actions[action]) - ret = getattr(self, action)(self.actions[action]) - rets.append(ret) - break - - self.log.debug('Results: %s', rets) - self.log.debug('Source after: \n%s', self.source) - - if self.actions['info']: - self.log.info('Running Info, skipping saving %s', self.source.ident) - return True - - if True in rets: - self.log.info('Saving source %s', self.source.ident) - self.source.file.save() - return True - else: - self.log.warning('No valid changes specified, no actions taken.') - return False - - def name(self, value:str) -> bool: - """Sets the key file to a name in the key file directory""" - if not value: - return False - - key_path = None - - for ext in ['.gpg', '.asc']: - if value.endswith(ext): - path = KEYS_PATH / value - if path.exists(): - key_path = path - break - else: - if 'archive-keyring' not in value: - value += '-archive-keyring' - path = KEYS_PATH / f'{value}{ext}' - if path.exists(): - key_path = path - break - - if not key_path: - self.log.error('The key file %s could not be found', value) - return False - - return self.path(str(key_path)) - - def path(self, value:str) -> bool: - """Sets the key file to the given path""" - if not value: - return False - - key_path = Path(value) - - if not key_path.exists(): - self.log.error( - 'The path %s does not exist', value - ) - return False - - self.source.signed_by = str(key_path) - self.source.load_key() - return True - - def url(self, value:str) -> bool: - """Downloads a key to use from a provided URL.""" - if not value: - return False - - if not value.startswith('https:'): - response = 'n' - self.log.warning( - 'The key is not being downloaded over an encrypted connection, ' - 'and may be tampered with in-transit. Only add keys that you ' - 'trust, from sources which you trust.' - ) - response = input('Do you want to continue? (y/N): ') - if response not in util.true_values: - return False - - key = SourceKey(name=self.source.ident) - key.load_key_data(url=value) - self.source.key = key - self.source.signed_by = str(key.path) - self.source.load_key() - return True - - def ascii(self, value:str) -> bool: - """Loads the key from provided ASCII-armored data""" - if not value: - return False - - response = 'n' - print('Only add signing keys from data you trust.') - response = input('Do you want to continue? (y/N): ') - - if response not in util.true_values: - return False - - key = SourceKey(name=self.source.ident) - key.load_key_data(ascii=value) - self.source.key = key - self.source.signed_by = str(key.path) - self.source.load_key() - return True - - def fingerprint(self, value:str) -> bool: - """Downloads the key with the given fingerprint from a keyserver""" - if not value: - return False - - key = SourceKey(name=self.source.ident) - - if self.keyserver: - key.load_key_data(fingerprint=value, keyserver=self.keyserver) - else: - key.load_key_data(fingerprint=value) - - self.source.key = key - self.source.signed_by = str(key.path) - self.source.load_key() - return True - - def info(self, value:str) -> bool: - """Prints key information""" - from datetime import date - if not self.source.key: - self.log.error( - 'The source %s does not have a key configured.', - self.repo - ) - return True - - else: - key:dict = self.source.get_key_info() - output: str = f'Key information for source {self.source.ident}:\n' - output += f'Key ID: {key["uids"][0]}\n' - output += f'Fingerprint: {key["keyid"]}\n' - if key['type'] == 'pub': - output += 'Key Type: Public\n' - else: - output += 'Key Type: Private\n' - keydate = date.fromtimestamp(int(key['date'])) - output += f'Issue Date: {keydate.ctime()}\n' - output += f'Length: {key["length"]} Bytes\n' - output += f'Keyring Path: {str(self.source.key.path)}\n' - print(output) - - return True - - def remove(self, value:str) -> bool: - """Removes the key from the source""" - - if not self.source.key: - self.log.error( - 'The source %s does not have a key configured.', - self.repo - ) - return False - - response = 'n' - print( - 'If you remove the key, there may no longer be any verification ' - 'of software packages from this repository, including for future ' - 'updates. This may cause errors with your updates.' - ) - response = input('Do you want to continue? (y/N): ') - if response not in util.true_values: - return False - - old_key = self.source.key - self.source.key = None - self.source.signed_by = '' - - for source in util.sources.values(): - if source.key == old_key: - self.log.info( - 'Key file %s in use with another key, not deleting', - old_key.path - ) - return True - - response = 'n' - print('No other sources were found which use this key.') - response = input('Do you want to remove it? (Y/n): ') - if response in util.true_values: - old_key.delete_key() - - return True - diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/list.py b/repolib/usr/lib/python3/dist-packages/repolib/command/list.py deleted file mode 100644 index aad121b..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/list.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import argparse -import textwrap -import traceback - -from ..file import SourceFile, SourceFileError -from ..source import Source, SourceError -from .. import RepoError, util, system - -from .command import Command, RepolibCommandError - -class List(Command): - """List subcommand - - Lists information about currently-configured sources on the system. - - Options: - --legacy, -l - --verbose, -v - --all, -a - --no-names, -n - --file-names, -f - --no-indentation - """ - - @classmethod - def init_options(cls, subparsers): - """Sets up ths argument parser for this command. - - Returns: argparse.subparser: - This command's subparser - """ - - sub = subparsers.add_parser( - 'list', - help=( - 'List information for configured repostiories. If a repository ' - 'name is provided, details about that repository are printed.' - ) - ) - - sub.add_argument( - 'repository', - nargs='*', - default=['x-repolib-all-sources'], - help='The repository for which to list configuration' - ) - sub.add_argument( - '-v', - '--verbose', - action='store_true', - help='Show additional information, if available.' - ) - sub.add_argument( - '-a', - '--all', - action='store_true', - help='Display full configuration for all configured repositories.' - ) - sub.add_argument( - '-l', - '--legacy', - action='store_true', - help='Include repositories configured in legacy sources.list file.' - ) - sub.add_argument( - '-n', - '--no-names', - action='store_true', - dest='skip_names', - help=argparse.SUPPRESS - ) - sub.add_argument( - '-f', - '--file-names', - action='store_true', - dest='print_files', - help="Don't print names of files" - ) - sub.add_argument( - '--no-indentation', - action='store_true', - dest='no_indent', - help=argparse.SUPPRESS - ) - - def finalize_options(self, args): - super().finalize_options(args) - self.repo = ' '.join(args.repository) - self.verbose = args.verbose - self.all = args.all - self.legacy = args.legacy - self.skip_names = args.skip_names - self.print_files = args.print_files - self.no_indent = args.no_indent - - def list_legacy(self, indent) -> None: - """List the contents of the sources.list file. - - Arguments: - list_file(Path): The sources.list file to try and parse. - indent(str): An indentation to append to the output - """ - try: - sources_list_file = util.SOURCES_DIR.parent / 'sources.list' - except FileNotFoundError: - sources_list_file = None - - print('Legacy source.list sources:') - if sources_list_file: - with open(sources_list_file, mode='r') as file: - for line in file: - if 'cdrom' in line: - line = '' - - try: - source = Source() - source.load_from_data([line]) - print(textwrap.indent(source.ui, indent)) - except SourceError: - pass - - def list_all(self): - """List all sources presently configured in the system - - This may include the configuration data for each source as well - """ - - indent = ' ' - if self.no_indent: - indent = '' - - if self.print_files: - print('Configured source files:') - - for file in util.files: - print(f'{file.path.name}:') - - for source in file.sources: - print(textwrap.indent(source.ui, indent)) - - if self.legacy: - self.list_legacy(indent) - - else: - print('Configured Sources:') - for source in util.sources: - output = util.sources[source] - print(textwrap.indent(output.ui, indent)) - - if self.legacy: - self.list_legacy(indent) - - if util.errors: - print('\n\nThe following files contain formatting errors:') - for err in util.errors: - print(err) - if self.verbose or self.debug: - print('\nDetails about failing files:') - for err in util.errors: - print(f'{err}: {util.errors[err].args[0]}') - - return True - - def run(self): - """Run the command""" - system.load_all_sources() - self.log.debug("Current sources: %s", util.sources) - ret = False - - if self.all: - return self.list_all() - - if self.repo == 'x-repolib-all-sources' and not self.all: - if not self.skip_names: - print('Configured Sources:') - for source in util.sources: - line = source - if not self.skip_names: - line += f' - {util.sources[source].name}' - print(line) - - return True - - else: - try: - output = util.sources[self.repo] - print(f'Details for source {output.ui}') - return True - except KeyError: - self.log.error( - "Couldn't find the source file for %s, check the spelling", - self.repo - ) - return False diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/modify.py b/repolib/usr/lib/python3/dist-packages/repolib/command/modify.py deleted file mode 100644 index 2738684..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/modify.py +++ /dev/null @@ -1,502 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from argparse import SUPPRESS - -from .command import Command, RepolibCommandError -from .. import util, system - -class Modify(Command): - """Modify Subcommand - - Makes modifications to the specified repository - - Options: - --enable, -e - --disable, -d - --default-mirror - --name - --add-suite - --remove-suite - --add-component - --remove-component - --add-uri - --remove-uri - - Hidden Options - --add-option - --remove-option - --default-mirror - """ - - @classmethod - def init_options(cls, subparsers): - """ Sets up this command's options parser""" - - sub = subparsers.add_parser( - 'modify', - help='Change a configured repository.' - ) - sub.add_argument( - 'repository', - nargs='*', - default=['system'], - help='The repository to modify. Default is the system repository.' - ) - - modify_enable = sub.add_mutually_exclusive_group( - required=False - ) - modify_enable.add_argument( - '-e', - '--enable', - action='store_true', - help='Enable the repository, if disabled.' - ) - modify_enable.add_argument( - '-d', - '--disable', - action='store_true', - help=( - 'Disable the repository, if enabled. The system repository cannot ' - 'be disabled.' - ) - ) - - modify_source = sub.add_mutually_exclusive_group( - required=False - ) - - modify_source.add_argument( - '--source-enable', - action='store_true', - help='Enable source code for the repository, if disabled.' - ) - modify_source.add_argument( - '--source-disable', - action='store_true', - help='Disable source code for the repository, if enabled.' - ) - - sub.add_argument( - '--default-mirror', - help=SUPPRESS - #help='Sets the default mirror for the system source.' - ) - - # Name - sub.add_argument( - '-n', - '--name', - help='Set the repository name to NAME' - ) - - # Suites - sub.add_argument( - '--add-suite', - metavar='SUITE[,SUITE]', - help=( - 'Add the specified suite(s) to the repository. Multiple ' - 'repositories should be separated with commas. NOTE: Legacy deb ' - 'repositories may only contain one suite.' - ) - ) - sub.add_argument( - '--remove-suite', - metavar='SUITE[,SUITE]', - help=( - 'Remove the specified suite(s) from the repository. Multiple ' - 'repositories should be separated with commas. NOTE: The last ' - 'suite in a repository cannot be removed.' - ) - ) - - # Components - sub.add_argument( - '--add-component', - metavar='COMPONENT[,COMPONENT]', - help=( - 'Add the specified component(s) to the repository. Multiple ' - 'repositories should be separated with commas.' - ) - ) - sub.add_argument( - '--remove-component', - metavar='COMPONENT[,COMPONENT]', - help=( - 'Remove the specified component(s) from the repository. Multiple ' - 'repositories should be separated with commas. NOTE: The last ' - 'component in a repository cannot be removed.' - ) - ) - - # URIs - sub.add_argument( - '--add-uri', - metavar='URI[,URI]', - help=( - 'Add the specified URI(s) to the repository. Multiple ' - 'repositories should be separated with commas. NOTE: Legacy deb ' - 'repositories may only contain one uri.' - ) - ) - sub.add_argument( - '--remove-uri', - metavar='URI[,URI]', - help=( - 'Remove the specified URI(s) from the repository. Multiple ' - 'repositories should be separated with commas. NOTE: The last ' - 'uri in a repository cannot be removed.' - ) - ) - - # Options - sub.add_argument( - '--add-option', - metavar='OPTION=VALUE[,OPTION=VALUE]', - help=SUPPRESS - ) - sub.add_argument( - '--remove-option', - metavar='OPTION=VALUE[,OPTION=VALUE]', - help=SUPPRESS - ) - - def finalize_options(self, args): - super().finalize_options(args) - self.count = 0 - self.repo = ' '.join(args.repository) - self.enable = args.enable - self.disable = args.disable - self.source_enable = args.source_enable - self.source_disable = args.source_disable - - self.actions:dict = {} - - self.actions['endisable'] = None - for i in ['enable', 'disable']: - if getattr(args, i): - self.actions['endisable'] = i - - self.actions['source_endisable'] = None - for i in ['source_enable', 'source_disable']: - if getattr(args, i): - self.actions['source_endisable'] = i - - for arg in [ - 'default_mirror', - 'name', - 'add_uri', - 'add_suite', - 'add_component', - 'remove_uri', - 'remove_suite', - 'remove_component', - ]: - self.actions[arg] = getattr(args, arg) - - system.load_all_sources() - - def run(self): - """Run the command""" - self.log.info('Modifying repository %s', self.repo) - - self.system_source = False - try: - self.source = util.sources[self.repo] - except KeyError: - self.log.error( - 'The source %s could not be found. Check the spelling', - self.repo - ) - return False - - if self.source.ident == 'system': - self.system_source = True - - self.log.debug('Actions to take:\n%s', self.actions) - self.log.debug('Source before:\n%s', self.source) - - rets = [] - for action in self.actions: - ret = getattr(self, action)(self.actions[action]) - rets.append(ret) - - self.log.debug('Results: %s', rets) - self.log.debug('Source after: \n%s', self.source) - - if True in rets: - self.source.file.save() - return True - else: - self.log.warning('No valid changes specified, no actions taken.') - return False - - def default_mirror(self, value:str) -> bool: - """Checks if this is the system source, then set the default mirror""" - if not value: - return False - - if self.system_source: - self.source['X-Repolib-Default-Mirror'] = value - return True - return False - - def name(self, value:str) -> bool: - """Sets the source name""" - if not value: - return False - - self.log.info('Setting name for %s to %s', self.repo, value) - self.source.name = value - return True - - def endisable(self, value:str) -> bool: - """Enable or disable the source""" - if not value: - return False - - self.log.info('%sing source %s', value[:-1], self.repo) - if value == 'disable': - self.source.enabled = False - return True - - self.source.enabled = True - return True - - def source_endisable(self, value:str) -> bool: - """Enable/disable source code for the repo""" - if not value: - return False - - self.log.info('%sing source code for source %s', value[7:-1], self.repo) - if value == 'source_disable': - self.source.sourcecode_enabled = False - return True - - self.source.sourcecode_enabled = True - return True - - def add_uri(self, values:str) -> bool: - """Adds URIs to the source, if not already present.""" - if not values: - return False - - self.log.info('Adding URIs: %s', values) - uris = self.source.uris - - for uri in values.split(): - if not util.url_validator(uri): - raise RepolibCommandError( - f'Cannot add URI {uri} to {self.repo}. The URI is ' - 'malformed' - ) - - if uri not in uris: - uris.append(uri) - self.log.debug('Added URI %s', uri) - - else: - self.log.warning( - 'The URI %s was already present in %s', - uri, - self.repo - ) - - if uris != self.source.uris: - self.source.uris = uris - return True - return False - - def remove_uri(self, values:str) -> bool: - """Remove URIs from the soruce, if they are added.""" - if not values: - return False - - self.log.info('Removing URIs %s from source %s', values, self.repo) - uris = self.source.uris - self.log.debug('Starting uris: %s', uris) - - for uri in values.split(): - try: - uris.remove(uri) - self.log.debug('Removed URI %s', uri) - - except ValueError: - self.log.warning( - 'The URI %s was not present in %s', - uri, - self.repo - ) - - if len(uris) == 0: - self.log.error( - 'Cannot remove the last URI from %s. If you meant to delete the source, try REMOVE instead.', - self.repo - ) - return False - - if uris != self.source.uris: - self.source.uris = uris - return True - - return False - - def add_suite(self, values:str) -> bool: - """Adds a suite to the source""" - if not values: - return False - - self.log.info('Adding suites: %s', values) - suites = self.source.suites - - for suite in values.split(): - if suite not in suites: - suites.append(suite) - self.log.debug('Added suite %s', suite) - - else: - self.log.warning( - 'The suite %s was already present in %s', - suite, - self.repo - ) - - if suites != self.source.suites: - self.source.suites = suites - return True - return False - - def remove_suite(self, values:str) -> bool: - """Remove a suite from the source""" - if not values: - return False - - self.log.info('Removing suites %s from source %s', values, self.repo) - suites = self.source.suites - self.log.debug('Starting suites: %s', suites) - - for suite in values.split(): - try: - suites.remove(suite) - self.log.debug('Removed suite %s', suite) - - except ValueError: - self.log.warning( - 'The suite %s was not present in %s', - suite, - self.repo - ) - - if len(suites) == 0: - self.log.error( - 'Cannot remove the last suite from %s. If you meant to delete the source, try REMOVE instead.', - self.repo - ) - return False - - if suites != self.source.suites: - self.source.suites = suites - return True - - return False - - def add_component(self, values:str) -> bool: - """Adds components to the source""" - if not values: - return False - - self.log.info('Adding components: %s', values) - components = self.source.components - - for component in values.split(): - if component not in components: - components.append(component) - self.log.debug('Added component %s', component) - - else: - self.log.warning( - 'The component %s was already present in %s', - component, - self.repo - ) - - if len(components) > 1: - if self.source.file.format == util.SourceFormat.LEGACY: - self.log.warning( - 'Adding multiple components to a legacy source is not ' - 'supported. Consider converting the source to DEB822 format.' - ) - - if components != self.source.components: - self.source.components = components - return True - return False - - def remove_component(self, values:str) -> bool: - """Removes components from the source""" - if not values: - return False - - self.log.info('Removing components %s from source %s', values, self.repo) - components = self.source.components - self.log.debug('Starting components: %s', components) - - for component in values.split(): - try: - components.remove(component) - self.log.debug('Removed component %s', component) - - except ValueError: - self.log.warning( - 'The component %s was not present in %s', - component, - self.repo - ) - - if len(components) == 0: - self.log.error( - 'Cannot remove the last component from %s. If you meant to delete the source, try REMOVE instead.', - self.repo - ) - return False - - if components != self.source.components: - self.source.components = components - return True - - return False - - def add_option(self, values) -> bool: - """TODO: Support options""" - raise NotImplementedError( - 'Options have not been implemented in this version of repolib yet. ' - f'Please edit the file {self.source.file.path} manually.' - ) - - - def remove_option(self, values) -> bool: - """TODO: Support options""" - raise NotImplementedError( - 'Options have not been implemented in this version of repolib yet. ' - f'Please edit the file {self.source.file.path} manually.' - ) diff --git a/repolib/usr/lib/python3/dist-packages/repolib/command/remove.py b/repolib/usr/lib/python3/dist-packages/repolib/command/remove.py deleted file mode 100644 index 851be03..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/command/remove.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from .. import util, system -from .command import Command - -class Remove(Command): - """Remove subcommand - - Removes configured sources from the system - - Options: - --assume-yes, -y - """ - - @classmethod - def init_options(cls, subparsers): - """Sets up the argument parser for this command - - Returns: argparse.subparser - This command's subparser - """ - - sub = subparsers.add_parser( - 'remove', - help='Remove a configured repository' - ) - - sub.add_argument( - 'repository', - help='The identifier of the repository to remove. See LIST' - ) - sub.add_argument( - '-y', - '--assume-yes', - action='store_true', - help='Remove without prompting for confirmation' - ) - - def finalize_options(self, args): - super().finalize_options(args) - system.load_all_sources() - self.source_name = args.repository - self.assume_yes = args.assume_yes - self.source = None - - def run(self): - """Run the command""" - - self.log.info('Looking up %s for removal', self.source_name) - - if self.source_name == 'system': - self.log.error('You cannot remove the system sources') - return False - - if self.source_name not in util.sources: - self.log.error( - 'Source %s was not found. Double-check the spelling', - self.source_name - ) - source_list:list = [] - for source in util.sources: - source_list.append(source) - suggested_source:str = self.source_name.replace(':', '-') - suggested_source = suggested_source.translate(util.CLEAN_CHARS) - if not suggested_source in source_list: - return False - - response:str = input(f'Did you mean "{suggested_source}"? (Y/n) ') - if not response: - response = 'y' - if response not in util.true_values: - return False - self.source_name = suggested_source - - self.source = util.sources[self.source_name] - self.key = self.source.key - self.file = self.source.file - - print(f'This will remove the source {self.source_name}') - print(self.source.ui) - response:str = 'n' - if self.assume_yes: - response = 'y' - else: - response = input('Are you sure you want to do this? (y/N) ') - - if response in util.true_values: - self.file.remove_source(self.source_name) - self.file.save() - - system.load_all_sources() - for source in util.sources.values(): - self.log.debug('Checking key for %s', source.ident) - try: - if source.key.path == self.key.path: - self.log.info('Source key in use with another source') - return True - except AttributeError: - pass - - self.log.info('No other sources found using key, deleting key') - if self.key: - self.key.delete_key() - return True - - else: - print('Canceled.') - return False diff --git a/repolib/usr/lib/python3/dist-packages/repolib/file.py b/repolib/usr/lib/python3/dist-packages/repolib/file.py deleted file mode 100644 index 5541a56..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/file.py +++ /dev/null @@ -1,544 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" -import logging - -from pathlib import Path - -import dbus - -from .source import Source, SourceError -from . import util - -FILE_COMMENT = "## Added/managed by repolib ##" - -class SourceFileError(util.RepoError): - """ Exception from a source file.""" - - def __init__(self, *args, code:int = 1, **kwargs): - """Exception with a source file - - Arguments: - :code int, optional, default=1: Exception error code. - """ - super().__init__(*args, **kwargs) - self.code: int = code - -class SourceFile: - """ A Source File on disk - - Attributes: - path(Pathlib.Path): the path for this file on disk - name(str): The name for this source (filename less the extension) - format(SourceFormat): The format used by this source file - contents(list): A list containing all of this file's contents - """ - - def __init__(self, name:str='') -> None: - """Initialize a source file - - Arguments: - name(str): The filename within the sources directory to load - """ - self.log = logging.getLogger(__name__) - self.name:str = '' - self.path:Path = Path() - self.alt_path:Path = Path() - self.format:util.SourceFormat = util.SourceFormat.DEFAULT - self.contents:list = [] - self.sources:list = [] - - self.contents.append(FILE_COMMENT) - self.contents.append('#') - - if name: - self.name = name - self.reset_path() - - if self.path.exists(): - self.contents = [] - self.load() - - def __str__(self): - return self.output - - def __repr__(self): - return f'SourceFile(name={self.name})' - - def add_source(self, source:Source) -> None: - """Adds a source to the file - - Arguments: - source(Source): The source to add - """ - if source not in self.sources: - self.contents.append(source) - self.sources.append(source) - source.file = self - - def remove_source(self, ident:str) -> None: - """Removes a source from the file - - Arguments: - ident(str): The ident of the source to remove - """ - source = self.get_source_by_ident(ident) - self.contents.remove(source) - self.sources.remove(source) - self.save() - - ## Remove sources prefs files/pin-priority - prefs_path = source.prefs - try: - if prefs_path.exists() and prefs_path.name: - prefs_path.unlink() - - except AttributeError: - # No prefs path - pass - - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.delete_prefs_file(str(prefs_path)) - - def get_source_by_ident(self, ident: str) -> Source: - """Find a source within this file by its ident - - Arguments: - ident(str): The ident to search for - - Returns: Source - The located source - """ - self.log.debug(f'Looking up ident {ident} in {self.name}') - for source in self.sources: - if source.ident == ident: - self.log.debug(f'{ident} found') - return source - raise SourceFileError( - f'The file {self.path} does not contain the source {ident}' - ) - - def reset_path(self) -> None: - """Attempt to detect the correct path for this File. - - We default to DEB822 .sources format files, but if that file doesn't - exist, fallback to legacy .list format. If this also doesn't exist, we - swap back to DEB822 format, as this is likely a new file.""" - self.log.debug('Resetting path') - - default_path = util.SOURCES_DIR / f'{self.name}.sources' - legacy_path = util.SOURCES_DIR / f'{self.name}.list' - - if default_path.exists(): - self.path = default_path - self.alt_path = legacy_path - self.format = util.SourceFormat.DEFAULT - - elif legacy_path.exists(): - self.path = legacy_path - self.alt_path = default_path - self.format = util.SourceFormat.LEGACY - - else: - self.path = default_path - self.alt_path = legacy_path - - return - - def find_unique_ident(self, source1:Source, source2:Source) -> bool: - """Takes two sources with identical idents, and finds a new, unique - idents for them. - - The rules for this are mildly complicated, and vary depending on the - situation: - - * (DEB822) If the sources are identical other than some portion of - data, then the two will be combined into a single source. - * (legacy) If the two sources are identical other than source type - (common with legacy-format PPAs with source code) then the second - source will be dropped until export. - * (legacy) If the sources differ by URIs, Components, or Suites, then - the differing data will be appended to the sources' idents. - * (Either) If no other rules can be determined, then the sources will - have a number appended to them - - Arguments: - source1(Source): The original source with the ident - source2(Source): The new colliding source with the ident - - Returns: bool - `True` if the two sources were successfully deduped, `False` if the - second source should be discarded. - """ - ident_src1:str = source1.ident - ident_src2:str = source2.ident - - self.log.debug(f'Idents {ident_src1} and {ident_src2} conflict') - - if self.format == util.SourceFormat.DEFAULT: - util.combine_sources(source1, source2) - ident_src2 = '' - - else: - excl_keys = [ - 'X-Repolib-Name', - 'X-Repolib-ID', - 'X-Repolib-Comment', - 'Enabled', - 'Types' - ] - if len(source1.types) == 1 and len(source2.types) == 1: - if util.compare_sources(source1, source2, excl_keys): - util.combine_sources(source1, source2) - source1.types = [ - util.SourceType.BINARY, util.SourceType.SOURCECODE - ] - source1.twin_source = True - source1.sourcecode_enabled = source2.enabled - ident_src2 = '' - diffs = util.find_differences_sources(source1, source2, excl_keys) - if diffs: - for key in diffs: - raw_diffs:tuple = diffs[key] - diff1_list = raw_diffs[0].strip().split() - diff2_list = raw_diffs[1].strip().split() - for i in diff1_list: - if i not in diff2_list: - ident_src1 += f'-{i}' - break - for i in diff2_list: - if i not in diff1_list: - ident_src2 += f'-{i}' - break - if ident_src1 != ident_src2: - break - if ident_src2 and ident_src1 != ident_src2: - source1.ident = ident_src1 - source2.ident = ident_src2 - return True - - elif ident_src2 and ident_src1 == ident_src2: - for source in self.sources: - src_index = self.sources.index(source) - source.ident = f'{self.name}-{src_index}' - return True - - elif not ident_src2: - return False - - return True - - - def load(self) -> None: - """Loads the sources from the file on disk""" - self.log.debug(f'Loading source file {self.path}') - self.contents = [] - self.sources = [] - - if not self.name: - raise SourceFileError('You must provide a filename to load.') - - if not self.path.exists(): - raise SourceFileError(f'The file {self.path} does not exist.') - - with open(self.path, 'r') as source_file: - srcfile_data = source_file.readlines() - - item:int = 0 - raw822:list = [] - parsing_deb822:bool = False - source_name:str = '' - commented:bool = False - idents:dict = {} - - # Main file parsing loop - for line in srcfile_data: - comment_found:str = '' - name_line:bool = 'X-Repolib-Name' in line - - if not parsing_deb822: - commented = line.startswith('#') - - # Find commented out lines - if commented: - # Exclude disabled legacy deblines - valid_legacy = util.validate_debline(line.strip()) - if not valid_legacy and not name_line: - # Found a standard comment - self.contents.append(line.strip()) - - elif valid_legacy: - if self.format != util.SourceFormat.LEGACY: - raise SourceFileError( - f'File {self.path.name} is an updated file, but ' - 'contains legacy-format sources. This is not ' - 'allowed. Please fix the file manually.' - ) - new_source = Source() - new_source.load_from_data([line]) - if source_name: - new_source.name = source_name - if not new_source.ident: - new_source.ident = self.name - to_add:bool = True - if new_source.ident in idents: - old_source = idents[new_source.ident] - idents.pop(old_source.ident) - to_add = self.find_unique_ident(old_source, new_source) - idents[old_source.ident] = old_source - idents[new_source.ident] = new_source - if to_add: - new_source.file = self - self.contents.append(new_source) - self.sources.append(new_source) - - elif name_line: - source_name = ':'.join(line.split(':')[1:]) - source_name = source_name.strip() - - # Active legacy line - elif not commented: - if util.validate_debline(line.strip()): - if self.format != util.SourceFormat.LEGACY: - raise SourceFileError( - f'File {self.path.name} is an updated file, but ' - 'contains legacy-format sources. This is not ' - 'allowed. Please fix the file manually.' - ) - new_source = Source() - new_source.load_from_data([line]) - if source_name: - new_source.name = source_name - if not new_source.ident: - new_source.ident = self.name - to_add:bool = True - if new_source.ident in idents: - old_source = idents[new_source.ident] - idents.pop(old_source.ident) - to_add = self.find_unique_ident(old_source, new_source) - idents[old_source.ident] = old_source - idents[new_source.ident] = new_source - if to_add: - new_source.file = self - self.contents.append(new_source) - self.sources.append(new_source) - - # Empty lines are treated as comments - if line.strip() == '': - self.contents.append('') - - # Find 822 sources - # Valid sources can begin with any key: - for key in util.valid_keys: - if line.startswith(key): - if self.format == util.SourceFormat.LEGACY: - raise SourceFileError( - f'File {self.path.name} is a DEB822-format file, but ' - 'contains legacy sources. This is not allowed. ' - 'Please fix the file manually.' - ) - parsing_deb822 = True - raw822.append(line.strip()) - - item += 1 - - elif parsing_deb822: - # Deb822 sources are terminated with an empty line - if line.strip() == '': - parsing_deb822 = False - new_source = Source() - new_source.load_from_data(raw822) - new_source.file = self - if source_name: - new_source.name = source_name - if not new_source.ident: - new_source.ident = self.name - if new_source.ident in idents: - old_source = idents[new_source.ident] - idents.pop(old_source.ident) - self.find_unique_ident(old_source, new_source) - idents[old_source.ident] = old_source - idents[new_source.ident] = new_source - new_source.file = self - self.contents.append(new_source) - self.sources.append(new_source) - raw822 = [] - item += 1 - self.contents.append('') - else: - raw822.append(line.strip()) - - if raw822: - parsing_deb822 = False - new_source = Source() - new_source.load_from_data(raw822) - new_source.file = self - if source_name: - new_source.name = source_name - if not new_source.ident: - new_source.ident = self.name - if new_source.ident in idents: - old_source = idents[new_source.ident] - idents.pop(old_source.ident) - self.find_unique_ident(old_source, new_source) - idents[old_source.ident] = old_source - idents[new_source.ident] = new_source - new_source.file = self - self.contents.append(new_source) - self.sources.append(new_source) - raw822 = [] - item += 1 - self.contents.append('') - - for source in self.sources: - if not source.has_required_parts: - raise SourceFileError( - f'The file {self.path.name} is malformed and contains ' - 'errors. Maybe it has some extra new-lines?' - ) - - self.log.debug('File %s loaded', self.path) - - def save(self) -> None: - """Saves the source file to disk using the current format""" - self.log.debug(f'Saving source file to {self.path}') - - for source in self.sources: - self.log.debug('New Source %s: \n%s', source.ident, source) - - save_path = util.SOURCES_DIR / f'{self.name}.save' - - for source in self.sources: - if source.key: - source.key.save_gpg() - source.tasks_save() - - if not self.name or not self.format: - raise SourceFileError('There was not a complete filename to save') - - if not util.SOURCES_DIR.exists(): - try: - util.SOURCES_DIR.mkdir(parents=True) - except PermissionError: - self.log.error( - 'Source destination path does not exist and cannot be created ' - 'Failures expected now.' - ) - - if len(self.sources) > 0: - self.log.debug('Saving, Main path %s; Alt path: %s', self.path, self.alt_path) - try: - with open(self.path, mode='w') as output_file: - output_file.write(self.output) - if self.alt_path.exists(): - self.alt_path.rename(save_path) - - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.output_file_to_disk(self.path.name, self.output) - self.log.debug('File %s saved', self.path) - else: - try: - self.path.unlink(missing_ok=True) - self.alt_path.unlink(missing_ok=True) - save_path.unlink(missing_ok=True) - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.delete_source_file(self.path.name) - self.log.debug('File %s removed', self.path) - - - ## Attribute properties - @property - def format(self) -> util.SourceFormat: # type: ignore (We don't use str.format) - """The format of the file on disk""" - return self._format - - @format.setter - def format(self, format:util.SourceFormat) -> None: # type: ignore (We don't use str.format) - """The path needs to be updated when the format changes""" - alt_format:util.SourceFormat = util.SourceFormat.LEGACY - self._format = format - self.path = util.SOURCES_DIR / f'{self.name}.{self._format.value}' - for format_ in util.SourceFormat: - if format != format_: - alt_format = format_ - self.alt_path = util.SOURCES_DIR / f'{self.name}.{alt_format.value}' - - ## Output properties - @property - def legacy(self) -> str: - """Outputs the file in the output_legacy format""" - legacy_output:str = '' - for item in self.contents: - try: - legacy_output += item.legacy - except AttributeError: - legacy_output += item - legacy_output += '\n' - return legacy_output - - @property - def deb822(self) -> str: - """Outputs the file in the output_822 format""" - deb822_output:str = '' - for item in self.contents: - try: - deb822_output += item.deb822 - except AttributeError: - deb822_output += item - deb822_output += '\n' - return deb822_output - - - @property - def ui(self) -> str: - """Outputs the file in the output_ui format""" - ui_output:str = '' - for item in self.contents: - try: - ui_output += item.ui - except AttributeError: - pass # Skip file comments in UI mode - ui_output += '\n' - return ui_output - - - @property - def output(self) -> str: - """Outputs the file in the output format""" - default_output:str = '' - for item in self.contents: - try: - if self.format == util.SourceFormat.DEFAULT: - default_output += item.deb822 - elif self.format == util.SourceFormat.LEGACY: - default_output += item.legacy - default_output += '\n' - except AttributeError: - default_output += item - default_output += '\n' - return default_output - diff --git a/repolib/usr/lib/python3/dist-packages/repolib/key.py b/repolib/usr/lib/python3/dist-packages/repolib/key.py deleted file mode 100644 index 766881b..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/key.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging -import shutil - -import dbus -import gnupg -from pathlib import Path -from urllib import request - -from . import util - -SKS_KEYSERVER = 'https://keyserver.ubuntu.com/' -SKS_KEYLOOKUP_PATH = 'pks/lookup?op=get&options=mr&exact=on&search=0x' - -class KeyFileError(util.RepoError): - """ Exceptions related to apt key files.""" - - def __init__(self, *args, code=1, **kwargs): - """Exceptions related to apt key files. - - Arguments: - code (:obj:`int`, optional, default=1): Exception error code. - """ - super().__init__(*args, **kwargs) - self.code = code - -class SourceKey: - """A signing key for an apt source.""" - - def __init__(self, name:str = '') -> None: - self.log = logging.getLogger(__name__) - self.tmp_path = Path() - self.path = Path() - self.gpg = gnupg.GPG() - self.data = b'' - - if name: - self.reset_path(name=name) - self.setup_gpg() - - def reset_path(self, name: str = '', path:str = '', suffix: str = 'archive-keyring') -> None: - """Set the path for this key - - Arguments: - suffix(str): The suffix to append to the end of the name to get the - file name (default: 'archive-keyring') - name(str): The name of the source - path(str): The entire path to the key - """ - self.log.info('Setting path') - if not name and not path: - raise KeyFileError('A name is required to set the path for this key') - - if name: - file_name = f'{name}-{suffix}.gpg' - self.tmp_path = util.TEMP_DIR / file_name - self.path = util.KEYS_DIR / file_name - elif path: - self.path = Path(path) - self.tmp_path = util.TEMP_DIR / self.path.name - - self.setup_gpg() - - self.log.debug('Key Path: %s', self.path) - self.log.debug('Temp Path: %s', self.tmp_path) - - def setup_gpg(self) -> None: - """Set up the GPG object for this key.""" - self.log.info('Setting up GPG') - self.log.debug('Copying %s to %s', self.path, self.tmp_path) - try: - shutil.copy2(self.path, self.tmp_path) - - except FileNotFoundError: - pass - - self.gpg = gnupg.GPG(keyring=str(self.tmp_path)) - self.log.debug('GPG Setup: %s', self.gpg.keyring) - - def save_gpg(self) -> None: - """Saves the key to disk.""" - self.log.info('Saving key file %s from %s', self.path, self.tmp_path) - self.log.debug('Key contents: %s', self.gpg.list_keys()) - self.log.debug('Temp key exists? %s', self.tmp_path.exists()) - if not util.KEYS_DIR.exists(): - try: - util.KEYS_DIR.mkdir(parents=True) - except PermissionError: - self.log.error( - 'Key destination path does not exist and cannot be created ' - 'Failures expected now.' - ) - try: - shutil.copy(self.tmp_path, self.path) - - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.install_signing_key( - str(self.tmp_path), - str(self.path) - ) - - def delete_key(self) -> None: - """Deletes the key file from disk.""" - try: - self.tmp_path.unlink() - self.path.unlink() - - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.delete_signing_key(str(self.path)) - - except FileNotFoundError: - pass - - def load_key_data(self, **kwargs) -> None: - """Loads the key data from disk into the object for processing. - - Each of the keyword options specifies one place to look to import key - data. Once one is successfully imported, the method returns, so passing - multiple won't import multiple keys. - - Keyword Arguments: - raw(bytes): Raw data to import to the keyring - ascii(str): ASCII-armored key data to import directly - url(str): A URL to download key data from - fingerprint(str): A key fingerprint to download from `keyserver` - keyserver(str): A keyserver to download from. - keypath(str): The path on the keyserver from which to download. - - NOTE: The keyserver and keypath args only affect the operation of the - `fingerprint` keyword. - """ - - if self.path.exists(): - with open(self.path, mode='rb') as keyfile: - self.data = keyfile.read() - return - - self.tmp_path.touch() - - if 'raw' in kwargs: - self.data = kwargs['raw'] - self.gpg.import_keys(self.data) - return - - if 'ascii' in kwargs: - self.gpg.import_keys(kwargs['ascii']) - if self.tmp_path.exists(): - with open(self.tmp_path, mode='rb') as keyfile: - self.data = keyfile.read() - return - - if 'url' in kwargs: - req = request.Request(kwargs['url']) - with request.urlopen(req) as response: - self.data = response.read().decode('UTF-8') - self.gpg.import_keys(self.data) - return - - if 'fingerprint' in kwargs: - if not 'keyserver' in kwargs: - kwargs['keyserver'] = SKS_KEYSERVER - - if not 'keypath' in kwargs: - kwargs['keypath'] = SKS_KEYLOOKUP_PATH - - key_url = kwargs['keyserver'] + kwargs['keypath'] + kwargs['fingerprint'] - req = request.Request(key_url) - with request.urlopen(req) as response: - self.data = response.read().decode('UTF-8') - self.gpg.import_keys(self.data) - return - - raise TypeError( - f'load_key_data() got an unexpected keyword argument "{kwargs.keys()}', - ' Expected keyword arguments are: [raw, ascii, url, fingerprint]' - ) - - - diff --git a/repolib/usr/lib/python3/dist-packages/repolib/parsedeb.py b/repolib/usr/lib/python3/dist-packages/repolib/parsedeb.py deleted file mode 100644 index 1e0d992..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/parsedeb.py +++ /dev/null @@ -1,371 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging - -from . import util - -log = logging.getLogger(__name__) - -class DebParseError(util.RepoError): - """ Exceptions related to parsing deb lines.""" - - def __init__(self, *args, code=1, **kwargs): - """Exceptions related to parsing deb lines. - - Arguments: - code (:obj:`int`, optional, default=1): Exception error code. - """ - super().__init__(*args, **kwargs) - self.code = code - -def debsplit(line:str) -> list: - """ Improved string.split() with support for things like [] options. - - Adapted from python-apt - - Arguments: - line(str): The line to split up. - """ - line = line.strip() - line_list = line.split() - for i in line_list: - if util.url_validator(i): - line_list[line_list.index(i)] = decode_brackets(i) - line = ' '.join(line_list) - pieces:list = [] - tmp:str = "" - # we are inside a [..] block - p_found = False - for char in line: - if char == '[': - p_found = True - tmp += char - elif char == ']': - p_found = False - tmp += char - elif char.isspace() and not p_found: - pieces.append(tmp) - tmp = '' - continue - else: - tmp += char - # append last piece - if len(tmp) > 0: - pieces.append(tmp) - return pieces - -def encode_brackets(word:str) -> str: - """ Encodes any [ and ] brackets into URL-safe form - - Technically we should never be recieving these, and there are other things - which should technically be encoded as well. However, square brackets - actively break the URL parsing, and must be strictly avoided. - - Arguments: - word (str): the string to encode brackets in. - - Returns: - `str`: the encoded string. - """ - word = word.replace('[', '%5B') - word = word.replace(']', '%5D') - return word - -def decode_brackets(word:str) -> str: - """ Un-encodes [ and ] from the input - - Since our downstream libraries should also be encoding these correctly, it - is better to retain these as the user entered, as that ensures they can - recognize it properly. - - Arguments: - word (str): The string to decode. - - Returns: - `str`: the decoded string. - """ - word = word.replace('%5B', '[') - word = word.replace('%5D', ']') - return word - -def parse_name_ident(tail:str) -> tuple: - """ Find a Repolib name within the given comment string. - - The name should be headed with "X-Repolib-Name:" and is not space terminated. - The ident should be headed with "X-Repolib-ID:" and is space terminated. - - Either field ends at the end of a line, or at a subsequent definition of a - different field, or at a subsequent ' #' substring. Additionally, the ident - field ends with a subsequent space. - - Arguments: - tail (str): The comment to search within. - - Returns: tuple(name, ident, comment): - name (str): The detected name, or None - ident (str): The detected ident, or None - comment (str): The string with the name and ident removed - """ - tail = util.strip_hashes(tail) - - # Used for sanity checking later - has_name = 'X-Repolib-Name' in tail - log.debug('Line name found: %s', has_name) - has_ident = 'X-Repolib-ID' in tail - log.debug('Line ident found: %s', has_ident) - - parts: list = tail.split() - name_found = False - ident_found = False - name:str = '' - ident:str = '' - comment:str = '' - for item in parts: - log.debug("Checking line item: %s", item) - item_is_name = item.strip('#').strip().startswith('X-Repolib-Name') - item_is_ident = item.strip('#').strip().startswith('X-Repolib-ID') - - if '#' in item and not item_is_name and not item_is_ident: - name_found = False - ident_found = False - - elif item_is_name: - name_found = True - ident_found = False - continue - - elif item_is_ident: - name_found = False - ident_found = True - continue - - if name_found and not item_is_name: - name += f'{item} ' - continue - - elif ident_found and not item_is_ident: - ident += f'{item}' - ident_found = False - continue - - elif not name_found and not ident_found: - c = item.strip('#') - comment += f'{c} ' - - name = name.strip() - ident = ident.strip() - comment = comment.strip() - - if not name: - if ident: - name = ident - - # Final sanity checking - if has_name and not name: - raise DebParseError( - f'Could not parse repository name from comment {comment}. Make sure ' - 'you have a space between the colon and the Name' - ) - if has_ident and not ident: - raise DebParseError( - f'Could not parse repository ident from comment {comment}. Make sure ' - 'you have a space between the colon and the Ident' - ) - - return name, ident, comment - - -class ParseDeb: - """ Parsing for source entries. - - Contains parsing helpers for one-line format sources. - """ - - def __init__(self, debug:bool = False) -> None: - """ - Arguments: - debug (bool): In debug mode, the structured data is always returned - at the end, instead of checking for sanity (default: `False`) - """ - self.debug = debug - self.last_line: str = '' - self.last_line_valid: bool = False - self.curr_line: str = '' - self.curr_line_valid: bool = False - - def parse_options(self, opt:str) -> dict: - """ Parses a string of options into a dictionary that repolib can use. - - Arguments: - opt(str): The string with options returned from the line parser. - - Returns: - `dict`: The dictionary of options with key:val pairs (may be {}) - """ - opt = opt.strip() - opt = opt[1:-1].strip() # Remove enclosing brackets - options = opt.split() - - parsed_options:dict = {} - - for opt in options: - pre_key, values = opt.split('=') - values = values.split(',') - value:str = ' '.join(values) - try: - key:str = util.options_inmap[pre_key] - except KeyError: - raise DebParseError( - f'Could not parse line {self.curr_line}: option {opt} is ' - 'not a valid debian repository option or is unsupported.' - ) - parsed_options[key] = value - - return parsed_options - - - def parse_line(self, line:str) -> dict: - """ Parse a deb line into its individual parts. - - Adapted from python-apt - - Arguments: - line (str): The line input to parse - - Returns: - (dict): a dict containing the requisite data. - """ - self.last_line = self.curr_line - self.last_line_valid = self.curr_line_valid - self.curr_line = line.strip() - parts:list = [] - - line_is_comment = self.curr_line == '#' - line_is_empty = self.curr_line == '' - if line_is_comment or line_is_empty: - raise DebParseError(f'Current line "{self.curr_line}" is empty') - - line_parsed: dict = {} - line_parsed['enabled'] = True - line_parsed['name'] = '' - line_parsed['ident'] = '' - line_parsed['comments'] = [] - line_parsed['repo_type'] = '' - line_parsed['uri'] = '' - line_parsed['suite'] = '' - line_parsed['components'] = [] - line_parsed['options'] = {} - - if line.startswith('#'): - line_parsed['enabled'] = False - line = util.strip_hashes(line) - parts = line.split() - if not parts[0] in ('deb', 'deb-src'): - raise DebParseError(f'Current line "{self.curr_line}" is invalid') - - comments_index = line.find('#') - if comments_index > 0: - raw_comments:str = line[comments_index + 1:].strip() - ( - line_parsed['name'], - line_parsed['ident'], - comments - ) = parse_name_ident(raw_comments) - line_parsed['comments'].append(comments) - line = line[:comments_index] - - parts = debsplit(line) - if len(parts) < 3: # We need at least a type, a URL, and a component - raise DebParseError( - f'The line "{self.curr_line}" does not have enough pieces to be' - 'valid' - ) - # Determine the type of the repo - repo_type:str = parts.pop(0) - if repo_type in ['deb', 'deb-src']: - line_parsed['repo_type'] = util.SourceType(repo_type) - else: - raise DebParseError(f'The line "{self.curr_line}" is of invalid type.') - - # Determine the properties of our repo line - uri_index:int = 0 - is_cdrom: bool = False - ## The URI index is the vital piece of information we need to parse the - ## deb line, as it's position determines what other components are - ## present and where they are. This determines the location of the URI - ## regardless of where it's at. - for part in parts: - if part.startswith('['): - if 'cdrom' in part: - is_cdrom = True - uri_index = parts.index(part) - else: - uri_index = 1 - - if is_cdrom: - # This could maybe change if the parser now differentiates between - # CDROM URIs and option lists - raise DebParseError('Repolib cannot currently accept CDROM Sources') - - if uri_index != 0: - line_parsed['options'] = self.parse_options(parts.pop(0)) - - if len(line_parsed) < 2: # Should have at minimum a URI and a suite/path - raise DebParseError( - f'The line "{self.curr_line}" does not have enough pieces to be' - 'valid' - ) - - line_uri = parts.pop(0) - if util.url_validator(line_uri): - line_parsed['uri'] = line_uri - - else: - raise DebParseError( - f'The line "{self.curr_line}" has invalid URI: {line_uri}' - ) - - line_parsed['suite'] = parts.pop(0) - - line_components:list = [] - for comp in parts: - line_parsed['components'].append(comp) - - - has_type = line_parsed['repo_type'] - has_uri = line_parsed['uri'] - has_suite = line_parsed['suite'] - - if has_type and has_uri and has_suite: - # if we have these three minimum components, we can proceed and the - # line is valid. Otherwise, error out. - return line_parsed.copy() - - if self.debug: - return line_parsed.copy() - - raise DebParseError( - f'The line {self.curr_line} could not be parsed due to an ' - 'unknown error (Probably missing the repo type, URI, or a ' - 'suite/path).' - ) diff --git a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/__init__.py b/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/__init__.py deleted file mode 100644 index 43c1503..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -from .popdev import PopdevSource -from .ppa import PPASource -from ..source import Source - -shortcut_prefixes = { - 'deb': Source, - 'deb-src': Source, - ppa.prefix: ppa.PPASource, - popdev.prefix: popdev.PopdevSource -} \ No newline at end of file diff --git a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/popdev.py b/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/popdev.py deleted file mode 100644 index f4efc6e..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/popdev.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging -from pathlib import Path - -import dbus - -from repolib.key import SourceKey - -from ..source import Source, SourceError -from ..file import SourceFile -from .. import util - -BASE_FORMAT = util.SourceFormat.DEFAULT -BASE_URL = 'http://apt.pop-os.org/staging' -BASE_COMPS = 'main' -BASE_KEYURL = 'https://raw.githubusercontent.com/pop-os/pop/master/scripts/.iso.asc' - -DEFAULT_FORMAT = util.SourceFormat.DEFAULT - -prefix = 'popdev' -delineator = ':' - -class PopdevSource(Source): - """ PopDev Source shortcut - - These are given in the format popdev:branchname. - - Arguments: - shortcut (str): The ppa: shortcut to process - """ - prefs_dir = Path('/etc/apt/preferences.d') - default_format = BASE_FORMAT - - @staticmethod - def validator(shortcut:str) -> bool: - """Determine whether a PPA shortcut is valid. - - Arguments: - shortcut(str): The shortcut to validate - - Returns: bool - `True` if the PPA is valid, otherwise False - """ - if '/' in shortcut: - return False - - shortcut_split = shortcut.split(':') - try: - if not shortcut_split[1]: - return False - except IndexError: - return False - - if shortcut.startswith(f'{prefix}:'): - shortlist = shortcut.split(':') - if len(shortlist) > 0: - return True - - return False - - def __init__(self, *args, line='', fetch_data=True, **kwargs): - if line: - if not line.startswith('ppa:'): - raise SourceError(f'The PPA shortcut {line} is malformed') - super().__init__(args, kwargs) - self.log = logging.getLogger(__name__) - self.line = line - self.twin_source:bool = True - self.prefs_path = None - self.branch_name:str = '' - self.branch_url:str = '' - if line: - self.load_from_shortcut(line) - - def tasks_save(self, *args, **kwargs) -> None: - super().tasks_save(*args, **kwargs) - self.log.info('Saving prefs file for %s', self.ident) - prefs_contents = 'Package: *\n' - prefs_contents += f'Pin: release o=pop-os-staging-{self.branch_url}\n' - prefs_contents += 'Pin-Priority: 1002\n' - - self.log.debug('%s prefs for pin priority:\n%s', self.ident, prefs_contents) - - try: - with open(self.prefs, mode='w') as prefs_file: - prefs_file.write(prefs_contents) - except PermissionError: - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.output_prefs_to_disk(str(self.prefs), prefs_contents) - - self.log.debug('Pin priority saved for %s', self.ident) - - - def get_description(self) -> str: - return f'Pop Development Staging branch' - - - def load_from_data(self, data: list) -> None: - self.log.debug('Loading line %s', data[0]) - self.load_from_shortcut(shortcut=data[0]) - - def load_from_shortcut(self, shortcut:str='', meta:bool=True, get_key:bool=True) -> None: - """Translates the shortcut line into a full repo. - - Arguments: - shortcut(str): The shortcut to load, if one hasn't been loaded yet. - """ - self.reset_values() - if shortcut: - self.line = shortcut - - if not self.line: - raise SourceError('No PPA shortcut provided') - - if not self.validator(self.line): - raise SourceError(f'The line {self.line} is malformed') - - self.log.debug('Loading shortcut %s', self.line) - - self.info_parts = shortcut.split(delineator) - self.branch_url = ':'.join(self.info_parts[1:]) - self.branch_name = util.scrub_filename(name=self.branch_url) - self.log.debug('Popdev branch name: %s', self.branch_name) - - self.ident = f'{prefix}-{self.branch_name}' - if f'{self.ident}.{BASE_FORMAT.value}' not in util.files: - new_file = SourceFile(name=self.ident) - new_file.format = BASE_FORMAT - self.file = new_file - util.files[str(self.file.path)] = self.file - else: - self.file = util.files[str(self.file.path)] - - self.file.add_source(self) - - self.name = f'Pop Development Branch {self.branch_name}' - self.uris = [f'{BASE_URL}/{self.branch_url}'] - self.suites = [util.DISTRO_CODENAME] - self.components = [BASE_COMPS] - - key = SourceKey(name='popdev') - key.load_key_data(url=BASE_KEYURL) - self.key = key - self.signed_by = str(self.key.path) - - self.prefs_path = self.prefs_dir / f'pop-os-staging-{self.branch_name}' - self.prefs = self.prefs_path - - self.enabled = True diff --git a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/ppa.py b/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/ppa.py deleted file mode 100644 index 624ac4e..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/shortcuts/ppa.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging - -from repolib.key import SourceKey - -from ..source import Source, SourceError -from ..file import SourceFile -from .. import util - -try: - from launchpadlib.launchpad import Launchpad - from lazr.restfulclient.errors import BadRequest, NotFound, Unauthorized -except ImportError: - raise SourceError( - 'Missing optional dependency "launchpadlib". Try `sudo apt install ' - 'python3-launchpadlib` to install it.' - ) - -BASE_FORMAT = util.SourceFormat.LEGACY -BASE_URL = 'http://ppa.launchpad.net' -BASE_DIST = 'ubuntu' -BASE_COMPS = 'main' - -DEFAULT_FORMAT = util.SourceFormat.LEGACY - -prefix = 'ppa' -delineator = ':' - -class PPASource(Source): - """ PPA Source shortcut - - These are given in the format ppa:owner/name. Much of this code is adapted - from SoftwareProperties. - - Arguments: - shortcut (str): The ppa: shortcut to process - fetch_data (bool): Whether to try and fetch metadata from Launchpad. - """ - - default_format = BASE_FORMAT - - @staticmethod - def validator(shortcut:str) -> bool: - """Determine whether a PPA shortcut is valid. - - Arguments: - shortcut(str): The shortcut to validate - - Returns: bool - `True` if the PPA is valid, otherwise False - """ - - if shortcut.startswith(f'{prefix}:'): - shortlist = shortcut.split('/') - if len(shortlist) > 1: - return True - return False - - def __init__(self, *args, line='', fetch_data=True, **kwargs): - if line: - if not line.startswith('ppa:'): - raise SourceError(f'The PPA shortcut {line} is malformed') - super().__init__(args, kwargs) - self.log = logging.getLogger(__name__) - self.line = line - self.ppa = None - self.twin_source = True - self._displayname = '' - self._description = '' - if line: - self.load_from_shortcut(self.line) - - def get_description(self) -> str: - output:str = '' - output += self.displayname - output += '\n\n' - output += self.description - return output - - def load_from_data(self, data: list) -> None: - self.load_from_shortcut(shortcut=data[0]) - - def load_from_shortcut(self, shortcut:str='', meta:bool=True, key:bool=True) -> None: - """Translates the shortcut line into a full repo. - - Arguments: - shortcut(str): The shortcut to load, if one hasn't been loaded yet. - meta(bool): Whether to fetch repo metadata from Launchpad - key(bool): Whether to fetch and install a signing key - """ - self.reset_values() - if shortcut: - self.line = shortcut - - if not self.line: - raise SourceError('No PPA shortcut provided') - - if not self.validator(self.line): - raise SourceError(f'The line {self.line} is malformed') - - line = self.line.replace(prefix + delineator, '') - self.info_parts = line.split('/') - ppa_owner = self.info_parts[0] - ppa_name = self.info_parts[1] - - self.ident = f'{prefix}-{ppa_owner}-{ppa_name}' - if f'{self.ident}.{BASE_FORMAT.value}' not in util.files: - new_file = SourceFile(name=self.ident) - new_file.format = BASE_FORMAT - self.file = new_file - util.files[str(self.file.path)] = self.file - else: - self.file = util.files[str(self.file.path)] - - self.file.add_source(self) - - self.name = self.ident - self.uris = [f'{BASE_URL}/{ppa_owner}/{ppa_name}/{BASE_DIST}'] - self.suites = [util.DISTRO_CODENAME] - self.components = [BASE_COMPS] - - if meta or key: - self.ppa = get_info_from_lp(ppa_owner, ppa_name) - self.displayname = self.ppa.displayname - self.description = self.ppa.description - - if self.ppa and meta: - self.name = self.ppa.displayname - - if self.ppa and key: - repo_key = SourceKey(name=self.ident) - if str(repo_key.path) not in util.keys: - repo_key.load_key_data(fingerprint=self.ppa.fingerprint) - util.keys[str(repo_key.path)] = repo_key - self.key:SourceKey = repo_key - else: - self.key = util.keys[repo_key.path] - self.signed_by = str(self.key.path) - - self.enabled = True - - @property - def displayname(self) -> str: - """The name of the PPA provided by launchpad""" - if self._displayname: - return self._displayname - if self.ppa: - self._displayname = self.ppa.displayname - return self._displayname - - @displayname.setter - def displayname(self, displayname) -> None: - """Cache this for use without hitting LP""" - self._displayname = displayname - - @property - def description(self) -> str: - """The description of the PPA provided by Launchpad""" - if self._description: - return self._description - if self.ppa: - self._description = self.ppa.description - return self._description - - @description.setter - def description(self, desc) -> None: - """Cache this for use without hitting LP""" - self._description = desc - -class PPA: - """ An object to fetch data from PPAs. - - Portions of this class were adapted from Software Properties - """ - - def __init__(self, teamname, ppaname): - self.teamname = teamname - self.ppaname = ppaname - self._lap = None - self._lpteam = None - self._lpppa = None - self._signing_key_data = None - self._fingerprint = None - - @property - def lap(self): - """ The Launchpad Object.""" - if not self._lap: - self._lap = Launchpad.login_anonymously( - f'{self.__module__}.{self.__class__.__name__}', - service_root='production', - version='devel' - ) - return self._lap - - @property - def lpteam(self): - """ The Launchpad object for the PPA's owner.""" - if not self._lpteam: - try: - self._lpteam = self.lap.people(self.teamname) # type: ignore (This won't actually be unbound because of the property) - except NotFound as err: - msg = f'User/Team "{self.teamname}" not found' - raise SourceError(msg) from err - except Unauthorized as err: - msg = f'Invalid user/team name "{self.teamname}"' - raise SourceError(msg) from err - return self._lpteam - - @property - def lpppa(self): - """ The Launchpad object for the PPA.""" - if not self._lpppa: - try: - self._lpppa = self.lpteam.getPPAByName(name=self.ppaname) - except NotFound as err: - msg = f'PPA "{self.teamname}/{self.ppaname}"" not found' - raise SourceError(msg) from err - except BadRequest as err: - msg = f'Invalid PPA name "{self.ppaname}"' - raise SourceError(msg) from err - return self._lpppa - - @property - def description(self) -> str: - """str: The description of the PPA.""" - return self.lpppa.description or '' - - @property - def displayname(self) -> str: - """ str: the fancy name of the PPA.""" - return self.lpppa.displayname or '' - - @property - def fingerprint(self): - """ str: the fingerprint of the signing key.""" - if not self._fingerprint: - self._fingerprint = self.lpppa.signing_key_fingerprint - return self._fingerprint - - -def get_info_from_lp(owner_name, ppa): - """ Attempt to get information on a PPA from launchpad over the internet. - - Arguments: - owner_name (str): The Launchpad user owning the PPA. - ppa (str): The name of the PPA - - Returns: - json: The PPA information as a JSON object. - """ - ppa = PPA(owner_name, ppa) - return ppa diff --git a/repolib/usr/lib/python3/dist-packages/repolib/source.py b/repolib/usr/lib/python3/dist-packages/repolib/source.py deleted file mode 100644 index ba89573..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/source.py +++ /dev/null @@ -1,945 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging -from pathlib import Path - -from debian import deb822 - -from .parsedeb import ParseDeb -from .key import SourceKey -from . import util - -DEFAULT_FORMAT = util.SourceFormat.LEGACY - -class SourceError(util.RepoError): - """ Exception from a source object.""" - - def __init__(self, *args, code=1, **kwargs): - """Exception with a source object - - Arguments: - code (:obj:`int`, optional, default=1): Exception error code. - """ - super().__init__(*args, **kwargs) - self.code = code - -class Source(deb822.Deb822): - """A DEB822 object representing a single software source. - - Attributes: - ident(str): The unique id for this source - name(str): The user-readable name for this source - enabled(bool): Whether or not the source is enabled - types([SourceType]): A list of repository types for this source - uris([str]): A list of possible URIs for this source - suites([str]): A list of enabled suites for this source - components([str]): A list of enabled components for this source - comments(str): Comments for this source - signed_by(Path): The path to this source's key file - file(SourceFile): The file this source belongs to - key(SourceKey): The key which signs this source - """ - - default_format = DEFAULT_FORMAT - - @staticmethod - def validator(shortcut:str) -> bool: - """Determine whether a deb line is valid. - - Arguments: - shortcut(str): The shortcut to validate - - Returns: bool - `True` if the PPA is valid, otherwise False - """ - shortcut_list:list = shortcut.split() - - if not shortcut.startswith('deb'): - return False - - if not len(shortcut_list) > 3: - return False - - if not util.validate_debline: - return False - - if len(shortcut_list) == 3 and '/' not in shortcut_list[-1]: - return False - - return True - - def __init__(self, *args, file=None, **kwargs) -> None: - """Initialize this source object""" - self.log = logging.getLogger(__name__) - super().__init__(*args, **kwargs) - self.reset_values() - self.file = file - self.twin_source = False - self.twin_enabled = False - - def __repr__(self): - """type: () -> str""" - # Append comments to the item - # if self.options: - - if self.comments: - self['Comments'] = '# ' - self['Comments'] += ' # '.join(self.comments) - - rep:str = '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) - - rep:str = '{' - for key in self: - rep += f"{util.PRETTY_PRINT}'{key}': '{self[key]}', " - - rep = rep[:-2] - rep += f"{util.PRETTY_PRINT.replace(' ', '')}" - rep += '}' - - if self.comments: - self.pop('Comments') - - return rep - - def __bool__(self) -> bool: - has_uri:bool = len(self.uris) > 0 - has_suite:bool = len(self.suites) > 0 - has_component:bool = len(self.components) > 0 - - if has_uri and has_suite and has_component: - return True - return False - - - def get_description(self) -> str: - """Get a UI-compatible description for a source. - - Returns: (str) - The formatted description. - """ - return self.name - - def get_key_info(self, halt_errors: bool = False) -> dict: - """ Get a dictionary containing information for the signing key for - this source. - - Arguments: - halt_errors (bool): if there are errors or other unexpected data, - raise a SourceError exception. Otherwise, simply log a warning. - - Returns: dict - The dictionary from gnupg with key info. - """ - if self.key: - keys:list = self.key.gpg.list_keys() - if len(keys) > 1: - error_msg = ( - f'The keyring for {self.ident} contains {len(keys)} keys' - '. Check the keyring object for details about the keys.' - ) - if halt_errors: - raise SourceError(error_msg) - self.log.warning(error_msg) - return keys[0] - - return {} - - def reset_values(self) -> None: - """Reset the default values for all attributes""" - self.log.info('Resetting source info') - self.ident = '' - self.name = '' - self.enabled = True - self.types = [util.SourceType.BINARY] - self.uris = [] - self.suites = [] - self.components = [] - self.comments = [] - self.signed_by = None - self.architectures = '' - self.languages = '' - self.targets = '' - self.pdiffs = '' - self.by_hash = '' - self.allow_insecure = '' - self.allow_weak = '' - self.allow_downgrade_to_insecure = '' - self.trusted = '' - self.signed_by = '' - self.check_valid_until = '' - self.valid_until_min = '' - self.valid_until_max = '' - self.prefs = '' - self._update_legacy_options() - self.file = None - self.key = None - - def load_from_data(self, data:list) -> None: - """Loads source information from the provided data - - Should correctly load either a lecagy Deb line (optionally with - preceeding comment) or a DEB822 source. - - Arguments: - data(list): the data to load into the source. - """ - self.log.info('Loading source from data') - self.reset_values() - - if util.validate_debline(data[0]): # Legacy Source - if len(data) > 1: - raise SourceError( - f'The source is a legacy source but contains {len(data)} entries. ' - 'It may only contain one entry.' - ) - deb_parser = ParseDeb() - parsed_debline = deb_parser.parse_line(data[0]) - self.ident = parsed_debline['ident'] - self.name = parsed_debline['name'] - self.enabled = parsed_debline['enabled'] - self.types = [parsed_debline['repo_type']] - self.uris = [parsed_debline['uri']] - self.suites = [parsed_debline['suite']] - self.components = parsed_debline['components'] - for key in parsed_debline['options']: - self[key] = parsed_debline['options'][key] - self._update_legacy_options() - for comment in parsed_debline['comments']: - self.comments.append(comment) - if self.comments == ['']: - self.comments = [] - - if not self.name: - self.name = self.generate_default_name() - - if self.signed_by: - self.load_key() - return - - # DEB822 Source - super().__init__(sequence=data) - if self.signed_by: - self.load_key() - return - - @property - def sourcecode_enabled(self) -> bool: - """`True` if this source also provides source code, otherwise `False`""" - if util.SourceType.SOURCECODE in self.types: - return True - return False - - @sourcecode_enabled.setter - def sourcecode_enabled(self, enabled) -> None: - """Accept a variety of input values""" - types = [util.SourceType.BINARY] - if enabled in util.true_values: - types.append(util.SourceType.SOURCECODE) - self.types = types - - - def generate_default_ident(self, prefix='') -> str: - """Generates a suitable ID for the source - - Returns: str - A sane default-id - """ - ident:str = '' - if len(self.uris) > 0: - uri:str = self.uris[0].replace('/', ' ') - uri_list:list = uri.split() - uri_str:str = '-'.join(uri_list[1:]) - branch_name:str = util.scrub_filename(uri_str) - ident = f'{prefix}{branch_name}' - ident += f'-{self.types[0].ident()}' - try: - if not self['X-Repolib-ID']: - self['X-Repolib-ID'] = ident - except KeyError: - self['X-Repolib-ID'] = ident - return ident - - def generate_default_name(self) -> str: - """Generate a default name based on the ident - - Returns: str - A name based on the ident - """ - name:str = self.ident - try: - name = self['X-Repolib-Name'] - except KeyError: - self['X-Repolib-Name'] = self.ident - return self['X-Repolib-Name'] - - if not name: - self['X-Repolib-Name'] = self.ident - - return self['X-Repolib-Name'] - - def load_key(self, ignore_errors:bool = True) -> None: - """Finds and loads the signing key from the system - - Arguments: - ignore_errors(bool): If `False`, throw a SourceError exception if - the key can't be found/doesn't exist (Default: `True`) - """ - self.log.info('Finding key for source %s', self.ident) - if not self.signed_by: - if ignore_errors: - self.log.warning('No key configured for source %s', self.ident) - else: - raise SourceError('No key configured for source {self.ident}') - - if self.signed_by not in util.keys: - new_key = SourceKey() - new_key.reset_path(path=self.signed_by) - self.key = new_key - util.keys[str(new_key.path)] = new_key - else: - self.key = util.keys[self.signed_by] - - def save(self) -> None: - """Proxy method to save the source""" - if not self.file == None: - self.file.save() - - def output_legacy(self) -> str: - """Outputs a legacy representation of this source - - Note: this is expected to fail if there is more than one type, URI, or - Suite; the one-line format does not support having multiple of these - properties. - - Returns: str - The source output formatted as Legacy - """ - return self.legacy - - def output_822(self) -> str: - """Outputs a DEB822 representation of this source - - Returns: str - The source output formatted as Deb822 - """ - return self.deb822 - - def output_ui(self) -> str: - """Outputs a string representation of this source for use in UIs - - Returns: str - The source output string - """ - return self.ui - - def prop_append(self, prop:list, item:str) -> None: - """Appends an item to a list property of this source. - NOTE: List properties are `types`, `uris`, `suites`, and `components`. - - Arguments: - prop(list): The property on which to append the item. - item(str): The item to append to the propery - """ - _list = prop - _list.append(item) - prop = _list - - def tasks_save(self, *args, **kwargs) -> None: - """Extra tasks to perform when saving a source""" - return - - ## Properties are stored/retrieved from the underlying Deb822 dict - @property - def has_required_parts(self) -> bool: - """(RO) True if all required attributes are set, otherwise false.""" - required_parts = ['uris', 'suites', 'ident'] - - for attr in required_parts: - if len(getattr(self, attr)) < 1: - return False - - return True - - - @property - def ident(self) -> str: - """The ident for this source within the file""" - try: - return self['X-Repolib-ID'] - except KeyError: - return '' - - - @ident.setter - def ident(self, ident: str) -> None: - ident = util.scrub_filename(ident) - self['X-Repolib-ID'] = ident - - - @property - def name(self) -> str: - """The human-friendly name for this source""" - try: - _name = self['X-Repolib-Name'] - except KeyError: - _name = '' - - if not _name: - self.generate_default_name() - return self['X-Repolib-Name'] - - @name.setter - def name(self, name: str) -> None: - self['X-Repolib-Name'] = name - - - @property - def enabled(self) -> util.AptSourceEnabled: - """Whether or not the source is enabled/active""" - try: - enabled = self['Enabled'] in util.true_values - except KeyError: - return util.AptSourceEnabled.FALSE - - if enabled and self.has_required_parts: - return util.AptSourceEnabled.TRUE - return util.AptSourceEnabled.FALSE - - @enabled.setter - def enabled(self, enabled) -> None: - """For convenience, accept a wide varietry of input value types""" - self['Enabled'] = 'no' - if enabled in util.true_values: - self['Enabled'] = 'yes' - - - @property - def types(self) -> list: - """The list of source types for this source""" - _types:list = [] - try: - for sourcetype in self['types'].split(): - _types.append(util.SourceType(sourcetype)) - except KeyError: - pass - return _types - - @types.setter - def types(self, types: list) -> None: - """Turn this list into a string of values for storage""" - self['Types'] = '' - _types:list = [] - for sourcetype in types: - if sourcetype not in _types: - _types.append(sourcetype) - for sourcetype in _types: - self['Types'] += f'{sourcetype.value} ' - self['Types'] = self['Types'].strip() - - - @property - def uris(self) -> list: - """The list of URIs for this source""" - try: - return self['URIs'].split() - except KeyError: - return [] - - @uris.setter - def uris(self, uris: list) -> None: - self['URIs'] = ' '.join(uris).strip() - - - @property - def suites(self) -> list: - """The list of URIs for this source""" - try: - return self['Suites'].split() - except KeyError: - return [] - - @suites.setter - def suites(self, suites: list) -> None: - self['Suites'] = ' '.join(suites).strip() - - - @property - def components(self) -> list: - """The list of URIs for this source""" - try: - return self['Components'].split() - except KeyError: - return [] - - @components.setter - def components(self, components: list) -> None: - self['Components'] = ' '.join(components).strip() - - - @property - def options(self) -> dict: - """The options for this source""" - return self._options - - @options.setter - def options(self, options:dict) -> None: - if 'Signed-By' in options: - self.signed_by = options['Signed-By'] - if self.signed_by: - options.pop('Signed-By') - self._options = options - - @property - def prefs(self): - """The path to any apt preferences files for this source.""" - try: - prefs = self['X-Repolib-Prefs'] - except KeyError: - prefs = '' - - if prefs: - return Path(prefs) - return Path() - - @prefs.setter - def prefs(self, prefs): - """Accept a str or a Path-like object""" - try: - del self['X-Repolib-Prefs'] - except KeyError: - pass - - if prefs: - prefs_str = str(prefs) - self['X-Repolib-Prefs'] = prefs_str - - - ## Option properties - - @property - def architectures (self) -> str: - """architectures option""" - try: - return self['Architectures'] - except KeyError: - return '' - - @architectures.setter - def architectures(self, data) -> None: - try: - self.pop('Architectures') - except KeyError: - pass - - if data: - self['Architectures'] = data - self._update_legacy_options() - - - @property - def languages (self) -> str: - """languages option""" - try: - return self['Languages'] - except KeyError: - return '' - - @languages.setter - def languages(self, data) -> None: - try: - self.pop('Languages') - except KeyError: - pass - - if data: - self['Languages'] = data - self._update_legacy_options() - - - @property - def targets (self) -> str: - """targets option""" - try: - return self['Targets'] - except KeyError: - return '' - - @targets.setter - def targets(self, data) -> None: - try: - self.pop('Targets') - except KeyError: - pass - - if data: - self['Targets'] = data - self._update_legacy_options() - - - @property - def pdiffs (self) -> str: - """pdiffs option""" - try: - return self['Pdiffs'] - except KeyError: - return '' - - @pdiffs.setter - def pdiffs(self, data) -> None: - try: - self.pop('Pdiffs') - except KeyError: - pass - - if data: - self['Pdiffs'] = data - self._update_legacy_options() - - - @property - def by_hash (self) -> str: - """by_hash option""" - try: - return self['By-Hash'] - except KeyError: - return '' - - @by_hash.setter - def by_hash(self, data) -> None: - try: - self.pop('By-Hash') - except KeyError: - pass - - if data: - self['By-Hash'] = data - self._update_legacy_options() - - - @property - def allow_insecure (self) -> str: - """allow_insecure option""" - try: - return self['Allow-Insecure'] - except KeyError: - return '' - - @allow_insecure.setter - def allow_insecure(self, data) -> None: - try: - self.pop('Allow-Insecure') - except KeyError: - pass - - if data: - self['Allow-Insecure'] = data - self._update_legacy_options() - - - @property - def allow_weak (self) -> str: - """allow_weak option""" - try: - return self['Allow-Weak'] - except KeyError: - return '' - - @allow_weak.setter - def allow_weak(self, data) -> None: - try: - self.pop('Allow-Weak') - except KeyError: - pass - - if data: - self['Allow-Weak'] = data - self._update_legacy_options() - - - @property - def allow_downgrade_to_insecure (self) -> str: - """allow_downgrade_to_insecure option""" - try: - return self['Allow-Downgrade-To-Insecure'] - except KeyError: - return '' - - @allow_downgrade_to_insecure.setter - def allow_downgrade_to_insecure(self, data) -> None: - try: - self.pop('Allow-Downgrade-To-Insecure') - except KeyError: - pass - - if data: - self['Allow-Downgrade-To-Insecure'] = data - self._update_legacy_options() - - - @property - def trusted (self) -> str: - """trusted option""" - try: - return self['Trusted'] - except KeyError: - return '' - - @trusted.setter - def trusted(self, data) -> None: - try: - self.pop('Trusted') - except KeyError: - pass - - if data: - self['Trusted'] = data - self._update_legacy_options() - - - @property - def signed_by (self) -> str: - """signed_by option""" - try: - return self['Signed-By'] - except KeyError: - return '' - - @signed_by.setter - def signed_by(self, data) -> None: - try: - self.pop('Signed-By') - except KeyError: - pass - - if data: - self['Signed-By'] = data - self._update_legacy_options() - - - @property - def check_valid_until (self) -> str: - """check_valid_until option""" - try: - return self['Check-Valid-Until'] - except KeyError: - return '' - - @check_valid_until.setter - def check_valid_until(self, data) -> None: - try: - self.pop('Check-Valid-Until') - except KeyError: - pass - - if data: - self['Check-Valid-Until'] = data - self._update_legacy_options() - - - @property - def valid_until_min (self) -> str: - """valid_until_min option""" - try: - return self['Valid-Until-Min'] - except KeyError: - return '' - - @valid_until_min.setter - def valid_until_min(self, data) -> None: - try: - self.pop('Valid-Until-Min') - except KeyError: - pass - - if data: - self['Valid-Until-Min'] = data - self._update_legacy_options() - - - @property - def valid_until_max (self) -> str: - """valid_until_max option""" - try: - return self['Valid-Until-Max'] - except KeyError: - return '' - - @valid_until_max.setter - def valid_until_max(self, data) -> None: - try: - self.pop('Valid-Until-Max') - except KeyError: - pass - - if data: - self['Valid-Until-Max'] = data - self._update_legacy_options() - - @property - def default_mirror(self) -> str: - """The default mirror/URI for the source""" - try: - return self['X-Repolib-Default-Mirror'] - except KeyError: - return '' - - @default_mirror.setter - def default_mirror(self, mirror) -> None: - if mirror: - self['X-Repolib-Default-Mirror'] = mirror - else: - self['X-Repolib-Default-Mirror'] = '' - - - ## Output Properties - @property - def deb822(self) -> str: - """The DEB822 representation of this source""" - self._update_legacy_options() - # comments get handled separately because they're a list, and list - # properties don't support .append() - if self.comments: - self['X-Repolib-Comments'] = '# ' - self['X-Repolib-Comments'] += ' # '.join(self.comments) - _deb822 = self.dump() - if self.comments: - self.pop('X-Repolib-Comments') - if _deb822: - return _deb822 - return '' - - @property - def ui(self) -> str: - """The UI-friendly representation of this source""" - self._update_legacy_options() - _ui_list:list = self.deb822.split('\n') - ui_output: str = f'{self.ident}:\n' - for line in _ui_list: - key = line.split(':')[0] - if key not in util.output_skip_keys: - if line: - ui_output += f'{line}\n' - for key in util.keys_map: - ui_output = ui_output.replace(key, util.keys_map[key]) - return ui_output - - @property - def legacy(self) -> str: - """The legacy/one-line format representation of this source""" - self._update_legacy_options() - - if str(self.prefs) != '.': - raise SourceError( - 'Apt Preferences files can only be used with DEB822-format sources.' - ) - - sourcecode = self.sourcecode_enabled - if len(self.types) > 1: - self.twin_source = True - self.types = [util.SourceType.BINARY] - sourcecode = True - - legacy = '' - - legacy += self._generate_legacy_output() - if self.twin_source: - legacy += '\n' - legacy += self._generate_legacy_output(sourcecode=True, enabled=sourcecode) - - return legacy - - def _generate_legacy_output(self, sourcecode=False, enabled=True) -> str: - """Generate a string of the current source in legacy format""" - legacy = '' - - if len(self.types) > 1: - self.twin_source = True - self.types = [util.SourceType.BINARY] - for attr in ['types', 'uris', 'suites']: - if len(getattr(self, attr)) > 1: - msg = f'The source has too many {attr}.' - msg += f'Legacy-format sources support one {attr[:-1]} only.' - raise SourceError(msg) - - if not self.enabled.get_bool() and not sourcecode: - legacy += '# ' - - if sourcecode and not enabled: - legacy += '# ' - - if sourcecode: - legacy += 'deb-src ' - else: - legacy += self.types[0].value - legacy += ' ' - - options_string = self._legacy_options() - if options_string: - legacy += '[' - legacy += options_string - legacy = legacy.strip() - legacy += '] ' - - legacy += f'{self.uris[0]} ' - legacy += f'{self.suites[0]} ' - - for component in self.components: - legacy += f'{component} ' - - legacy += f' ## X-Repolib-Name: {self.name}' - legacy += f' # X-Repolib-ID: {self.ident}' - if self.comments: - for comment in self.comments: - legacy += f' # {comment}' - - return legacy - - def _legacy_options(self) -> str: - """Turn the current options into a oneline-style string - - Returns: str - The one-line-format options string - """ - options_str = '' - for key in self.options: - if self.options[key] != '': - options_str += f'{key}={self.options[key].replace(" ", ",")} ' - return options_str - - def _update_legacy_options(self) -> None: - """Updates the current set of legacy options""" - self.options = { - 'arch': self.architectures, - 'lang': self.languages, - 'target': self.targets, - 'pdiffs': self.pdiffs, - 'by-hash': self.by_hash, - 'allow-insecure': self.allow_insecure, - 'allow-weak': self.allow_weak, - 'allow-downgrade-to-insecure': self.allow_downgrade_to_insecure, - 'trusted': self.trusted, - 'signed-by': self.signed_by, - 'check-valid-until': self.check_valid_until, - 'valid-until-min': self.valid_until_min, - 'valid-until-max': self.valid_until_max, - } \ No newline at end of file diff --git a/repolib/usr/lib/python3/dist-packages/repolib/system.py b/repolib/usr/lib/python3/dist-packages/repolib/system.py deleted file mode 100644 index b22ef09..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/system.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import logging - -from pathlib import Path - -from . import util -from .file import SourceFile -from .source import Source -from .shortcuts import popdev, ppa - - -log = logging.getLogger(__name__) - -def load_all_sources() -> None: - """Loads all of the sources present on the system.""" - log.info('Loading all sources') - - util.sources.clear() - util.files.clear() - util.keys.clear() - util.errors.clear() - - sources_path = Path(util.SOURCES_DIR) - sources_files = sources_path.glob('*.sources') - legacy_files = sources_path.glob('*.list') - - for file in sources_files: - try: - sourcefile = SourceFile(name=file.stem) - log.debug('Loading %s', file) - sourcefile.load() - if file.name not in util.files: - util.files[file.name] = sourcefile - - except Exception as err: - util.errors[file.name] = err - - for file in legacy_files: - try: - sourcefile = SourceFile(name=file.stem) - sourcefile.load() - util.files[file.name] = sourcefile - except Exception as err: - util.errors[file.name] = err - - for f in util.files: - file = util.files[f] - for source in file.sources: - if source.ident in util.sources: - source.ident = f'{file.name}-{source.ident}' - source.file.save() - util.sources[source.ident] = source diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/__init__.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_key.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_key.py deleted file mode 100644 index 977324b..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_key.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import unittest - -from ..key import SourceKey -from .. import util, system -from .. import set_testing - -# System76 Signing PubKey, for test import -KEY_DATA = ( - '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFlL+3MBEADdNM9Xy2t3EtKU1i3R1o' - '1OCgJqLiDm8OZZq47InYID8oAPKRjd\n0UDVJTrvfsB4oJH97VRi2hGv2xmc19OaFE/NsQBZW/' - '7/3ypLr8eyaNgvscsmG/WN\ncM1cbMZtwd1b0JOr3bNTzp6WKRI3jo9uRw7duM8FwPjKm76Lbo' - 'DQbAR+4Szm3O8x\n/om8Gs1MRPUkY2dVz5KzednFLHwy7qnUXR3WRB5K1L9EBZkFDDNqnyViUI' - 'rE4bTm\nBC9mTg/Xfw/QXUFYz3t/YTYduAU0o1q2yei+8tVAJKh7H9t3PrQ95l3RUUcaAvba\n' - 'A9zlCrI8fonpxu7eSpkqzT4uCkfxdLVwittl1DumKTEkSXDQ5txY21igbSZZQwBA\nZf9MnFhJ' - 'fPsEIq2YHRc1FBcQxiAIpnGizv7FgYY5FxmZQ7592dMQOZ00h+lDSQug\nNMxloHCogaXR038u' - 'IKGTQnQEVcT46FtTRkLMSvbigy+RVSchdu9MEBBPgD3vSv53\nNEobXsLiZ9hF6Hk7XI2WxP5j' - '1zWTPmzxvf9NDOWz2Sw9Z+ilf252LXoxZQaMngp8\nXL32uvw7q+mjB6F1W/qpe3b32uu7eGNr' - 'DWJ5veE808hpXXj803TllmRUfMGUrtY9\nk7uUTQQWtrJ5uZ0QmsTk1oJHCPIUjjuiNtQfq28+' - 'bfg8FEJ/F1N1mB0IvwARAQAB\ntCxQb3AgT1MgKElTTyBTaWduaW5nIEtleSkgPGluZm9Ac3lz' - 'dGVtNzYuY29tPokC\nNwQTAQIAIgUCWUv7cwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA' - 'CgkQIE3Y\nrsM6ev8kXw/4p/8fOH8wM59ZoU0t1+fv3O8dYaDdTVTVIEno9snrsx5A5tbMu59r' - '\nHoBaxGenv/PB0l8yANhRX+HVmU/l0Sj0lTlEkYzgH/IT2Ne60s1ETgI7DlgSuYyP\nH8wq61' - '85+2DyE2+R/XcXGq0I++QUq1Y6rS+B4KIyYcgpJotcVNFaZiwuJZE31uLg\nkVMZrm1oObHear' - '7P2JQTbgsENMZDJEhQBCGKVdnAfVdKUeUrd07syr0cDe3kwY9o\ncNc00bhIh23cLTJE2omok9' - 'yCsXoeFJlPMyZw8WvEa5oaYWzP4Yw7nF8/27JTzZ70\nDjK2D2xoTkr0cP87LtZulS6FC3lxLu' - 'Z6hSaxsqoBH8Dd1uyYVEzLDsIRMtSHsXk+\n3kLrr1p7/7/vjGShlYkbLtP4jWnlHc6vSxIzm/' - 'MQmQMCfjeo3QH7GGw88mYtXngQ\n/Zna6wz0oL6pGM/4t90SCxTxRqCnoxMxzkcpt9n42bj79g' - 'rESOMH4wm3ExfuPk7I\nDtY+SqzIq0QvoPbC3XJLusWVgwUsRF2FpTTRTHEiWEMjWDKDVEyT4K' - '1k1k3f/gi2\n6LdtXwqDwzUvJJU5HYwVFywt+0jt5F0ZlTlPizz3iHw4gMLOielRShl+gZrU2U' - '0O\naj1Hyts9LymEKMUvRQGwMqCZcXo6sGjs59tTsfeGX16PTOyBri8eoLkCDQRZS/tz\nARAA' - 'pD9IWm4zS1AuBcOTuvh1E/ciKHGIUtW3JftD9ah8loEeckakgG5Xn9he1X6J\nyxPULpsptcCC' - 'cKXlw853ZQK9PLQJX6moWLH+qf2Zo3UAn/YEsWk+KsHoxPXHNUds\nu/j6UKkqEk8c7H92hUo8' - 'aWghO3p4HDVJ9KmGtueQ3jOv8Qun7Eh9cIo0A59cKmMv\njKUiYHLIJw8bkveQ8rVPul1ZHn56' - 'ORiBi58vm3tzjI4UWHQMjiKxXT6H5eG/f5K6\nuaK8lljh6n6jhdnQCpBcdtSIbhE/6YRv2+Ig' - 'L+BRssvprBtx4/sBwKjNNqzWPeGy\nUDHMiF88ETYqZ8DfukQ/e5XuaxjU41g/F8cw8BeVTBMv' - 'eb1YTyOoWcWvTL+hoBfS\nqYc/lvDHmmJ7/IgeMvUE6KoByP4ub5wX52mJTqgMC4GMhA04BC60' - 'B+NfVAXLh2pa\nTRJAHoWTDswOxbR6q9zPEFGZzV04B9Y96EavwMpT5IzG2fOPBwvdT0EDnt+v' - 'Q/iB\nc9O7CvkRTROAV+RoNCLY2XU8yNc/XxuI66PCE4Q96jW4uDzHvi6sPW/glsfRi2NT\nRW' - 'CO15KMVf0aypXeBpSbHIXIYGdXRQRpw980IW6PrElPpqZ5/DGbkXei5CuruF2R\nmltuu3MqYQ' - 'jcUvP9T7s0e5GAFgQFrR/8q29nVULq8IF4vzUAEQEAAYkCHwQYAQIA\nCQUCWUv7cwIbDAAKCR' - 'AgTdiuwzp6/wTGD/9Co4gEmTTOW++FneMMJo5K4WqeWVRg\ng1q5+yoVqgWq3k6lLsEC5kxR30' - '5BAAcvXo9XPKdo62ySYmhIFOpIz/TkeTUxDZaw\nsLtcBxXUME2L5j/1od1V9lxecUvLAgA11o' - '5Kb8TMKn5ZcmGhadtTLslWQcYsKqhw\nLaYQRlcxLDHYT8DXFkHgDhUMMbpt07dU5v5lIjgtGN' - 'HRhdS7/lCmSWOBtYapwpAH\nGYSmahN0zO36VHzOB5uwFue0tSoQiBEvLrCV/8ZtT2S5NkXaSm' - 'isz6B5Vr6DRtWI\nOamW5pMbSL8WQNQ99Kik05ctERjv2NgxI4JQo/a4KKthRrT4JlixXmrfJD' - 'uPyDPp\nRuTu1Elo6snoqWKQNf1sEPKvcv7EviNxBOhbTKivWrJXMnbOme7+UlNLcq7VAFp3\n' - 'x5hxk/ap0WqH/hs7+8jMBC8nS402MoM7EyLS0++kbOuEL/Prf3+JxFRqIu5Df77J\n+bUmTtKI' - 'CV43ikiVWmnP5OuJj2JPSOTR+rLxAQYpyHmo7HKXE63FbH1FVLgsT88+\nEW6VtI01I7EYmKQX' - 'EqQo52yfeHKDrQjGNVBWMKcXj0SVU+QQ1Ue/4yLwA+74VD2d\nfOyJI22NfTI+3SMAsMQ8L+WV' - 'QI+58bu7+iEqoEfHCXikE8BtTbJAN4Oob1lrjfOe\n5utH/lMP9suRWw==\n=NL3f\n-----EN' - 'D PGP PUBLIC KEY BLOCK-----\n' -) - -class KeyTestCase(unittest.TestCase): - def setUp(self): - set_testing() - self.key_data = KEY_DATA - self.keys_dir = util.KEYS_DIR - self.key_id = '204DD8AEC33A7AFF' - self.key_uids = ['Pop OS (ISO Signing Key) '] - self.key_length = '4096' - self.key_date = '1498151795' - - def test_import_ascii(self): - key = SourceKey(name='popdev') - key.load_key_data(ascii=self.key_data) - key_dict = key.gpg.list_keys()[0] - key_path = self.keys_dir / 'popdev-archive-keyring.gpg' - - self.assertEqual(key.path, key_path) - self.assertEqual(len(key.gpg.list_keys()), 1) - self.assertEqual(key_dict['keyid'], self.key_id) - self.assertEqual(key_dict['uids'], self.key_uids) - self.assertEqual(key_dict['length'], self.key_length) - self.assertEqual(key_dict['date'], self.key_date) - - def test_key_save_load(self): - print(self.keys_dir) - key_path = self.keys_dir / 'popdev-archive-keyring.gpg' - if key_path.exists(): - key_path.unlink() - - self.assertFalse(key_path.exists()) - key_save = SourceKey(name='popdev') - key_save.load_key_data(ascii=self.key_data) - key_save.save_gpg() - - self.assertTrue(key_save.path.exists()) - - key_load = SourceKey() - key_load.reset_path(name='popdev') - key_dict = key_load.gpg.list_keys()[0] - - self.assertEqual(key_load.path, key_path) - self.assertEqual(len(key_load.gpg.list_keys()), 1) - self.assertEqual(key_dict['keyid'], self.key_id) - self.assertEqual(key_dict['uids'], self.key_uids) - self.assertEqual(key_dict['length'], self.key_length) - self.assertEqual(key_dict['date'], self.key_date) - - def test_delete_key(self): - key_path = self.keys_dir / 'popdev-archive-keyring.gpg' - if key_path.exists(): - key_path.unlink() - - self.assertFalse(key_path.exists()) - - self.assertFalse(key_path.exists()) - key_save = SourceKey(name='popdev') - key_save.load_key_data(ascii=self.key_data) - - key_save.save_gpg() - - self.assertTrue(key_save.path.exists()) - - key_load = SourceKey() - key_load.reset_path(name='popdev') - key_load.delete_key() - - self.assertFalse(key_load.path.exists()) diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_parsedeb.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_parsedeb.py deleted file mode 100644 index 0a975a0..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_parsedeb.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . - -This is a library for parsing deb lines into deb822-format data. -""" - -import unittest - -from ..source import Source -from .. import util - -class DebTestCase(unittest.TestCase): - def test_normal_source(self): - source = Source() - source.load_from_data([ - 'deb http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.types, [util.SourceType.BINARY]) - self.assertTrue(source.enabled.get_bool()) - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.suites, ['suite']) - self.assertEqual(source.components, ['main']) - self.assertEqual(source.ident, 'example-com-binary') - - def test_source_with_multiple_components(self): - source = Source() - source.load_from_data([ - 'deb http://example.com/ suite main nonfree' - ]) - source.generate_default_ident() - self.assertEqual(source.suites, ['suite']) - self.assertEqual(source.components, ['main', 'nonfree']) - - def test_source_with_option(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64 ] http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.architectures, 'amd64') - - def test_source_uri_with_brackets(self): - source = Source() - source.load_from_data([ - 'deb http://example.com/[release]/ubuntu suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/[release]/ubuntu']) - - def test_source_options_with_colons(self): - source = Source() - source.load_from_data([ - 'deb [ arch=arm:2 ] http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.architectures, 'arm:2') - - def test_source_with_multiple_option_values(self): - source = Source() - source.load_from_data([ - 'deb [ arch=armel,amd64 ] http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.architectures, 'armel amd64') - - def test_source_with_multiple_options(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64 lang=en_US ] http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.architectures, 'amd64') - self.assertEqual(source.languages, 'en_US') - - def test_source_with_multiple_options_with_multiple_values(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64,armel lang=en_US,en_CA ] ' - 'http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example.com/']) - self.assertEqual(source.architectures, 'amd64 armel') - self.assertEqual(source.languages, 'en_US en_CA') - - def test_source_uri_with_brackets_and_options(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64 lang=en_US,en_CA ] ' - 'http://example][.com/[release]/ubuntu suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example][.com/[release]/ubuntu']) - self.assertEqual(source.architectures, 'amd64') - self.assertEqual(source.languages, 'en_US en_CA') - - def test_source_uri_with_brackets_and_options_with_colons(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64,arm:2 lang=en_US,en_CA ] ' - 'http://example][.com/[release]/ubuntu suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example][.com/[release]/ubuntu']) - self.assertEqual(source.architectures, 'amd64 arm:2') - self.assertEqual(source.languages, 'en_US en_CA') - - def test_worst_case_sourcenario(self): - source = Source() - source.load_from_data([ - 'deb [ arch=amd64,arm:2,arm][ lang=en_US,en_CA ] ' - 'http://example][.com/[release:good]/ubuntu suite main restricted ' - 'nonfree not-a-component' - ]) - source.generate_default_ident() - self.assertEqual(source.uris, ['http://example][.com/[release:good]/ubuntu']) - self.assertEqual(source.suites, ['suite']) - self.assertEqual(source.components, [ - 'main', 'restricted', 'nonfree', 'not-a-component' - ]) - source.generate_default_ident() - self.assertEqual(source.architectures, 'amd64 arm:2 arm][') - self.assertEqual(source.languages, 'en_US en_CA') - - def test_source_code_source(self): - source = Source() - source.load_from_data([ - 'deb-src http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertEqual(source.types, [util.SourceType.SOURCECODE]) - - def test_disabled_source(self): - source = Source() - source.load_from_data([ - '# deb http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertFalse(source.enabled.get_bool()) - - def test_disabled_source_without_space(self): - source = Source() - source.load_from_data([ - '#deb http://example.com/ suite main' - ]) - source.generate_default_ident() - self.assertFalse(source.enabled.get_bool()) - - def test_source_with_trailing_comment(self): - source = Source() - source.load_from_data([ - 'deb http://example.com/ suite main # This is a comment' - ]) - source.generate_default_ident() - self.assertEqual(source.suites, ['suite']) - self.assertEqual(source.components, ['main']) - - def test_disabled_source_with_trailing_comment(self): - source = Source() - source.load_from_data([ - '# deb http://example.com/ suite main # This is a comment' - ]) - source.generate_default_ident() - self.assertEqual(source.suites, ['suite']) - self.assertEqual(source.components, ['main']) diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_popdev.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_popdev.py deleted file mode 100644 index 8207b09..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_popdev.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import unittest - -from ..shortcuts import popdev -from .. import util - -class PopdevTestCase(unittest.TestCase): - - def test_ppa(self): - source = popdev.PopdevSource() - - # Verification data - uris_test = ['http://apt.pop-os.org/staging/master'] - signed_test = '/usr/share/keyrings/popdev-archive-keyring.gpg' - source.load_from_shortcut(shortcut='popdev:master') - - self.assertEqual(source.uris, uris_test) - self.assertEqual(source.ident, 'popdev-master') - self.assertEqual(source.suites, [util.DISTRO_CODENAME]) - self.assertEqual(source.components, ['main']) - self.assertEqual(source.types, [util.SourceType.BINARY]) - self.assertTrue(source.signed_by.endswith(signed_test)) \ No newline at end of file diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_ppa.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_ppa.py deleted file mode 100644 index 9b346cf..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_ppa.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import unittest - -from ..shortcuts import ppa -from .. import util - -class PPATestCase(unittest.TestCase): - - def test_ppa(self): - source = ppa.PPASource() - - # Verification data - uris_test = ['http://ppa.launchpad.net/system76/pop/ubuntu'] - signed_test = '/usr/share/keyrings/ppa-system76-pop-archive-keyring.gpg' - source.load_from_shortcut(shortcut='ppa:system76/pop', meta=False, key=False) - - self.assertEqual(source.uris, uris_test) - self.assertEqual(source.ident, 'ppa-system76-pop') - self.assertEqual(source.suites, [util.DISTRO_CODENAME]) - self.assertEqual(source.components, ['main']) - self.assertEqual(source.types, [util.SourceType.BINARY]) diff --git a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_source.py b/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_source.py deleted file mode 100644 index ae7f38e..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/unittest/test_source.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import unittest - -from .. import file, util, source - -class SourceTestCase(unittest.TestCase): - def setUp(self): - util.set_testing() - self.source = source.Source() - self.source.ident = 'test' - self.source.name = 'Test Source' - self.source.enabled = True - self.source.types = [util.SourceType.BINARY, util.SourceType.SOURCECODE] - self.source.uris = ['http://example.com/ubuntu', 'http://example.com/mirror'] - self.source.suites = ['suite', 'suite-updates'] - self.source.components = ['main', 'contrib', 'nonfree'] - self.source.architectures = 'amd64 armel' - self.source.languages = 'en_US en_CA' - self.file = file.SourceFile(name=self.source.ident) - self.file.add_source(self.source) - self.source.file = self.file - - self.source_legacy = source.Source() - self.source_legacy.ident = 'test-legacy' - self.source_legacy.name = 'Test Legacy Source' - self.source_legacy.enabled = True - self.source_legacy.types = [util.SourceType.BINARY] - self.source_legacy.uris = ['http://example.com/ubuntu'] - self.source_legacy.suites = ['suite'] - self.source_legacy.components = ['main', 'contrib', 'nonfree'] - self.source_legacy.architectures = 'amd64 armel' - self.source_legacy.languages = 'en_US en_CA' - self.source_legacy.file = file.SourceFile(name=self.source_legacy.ident) - self.source_legacy.file.format = util.SourceFormat.LEGACY - - - def test_default_source_data(self): - self.assertEqual(self.source.name, 'Test Source') - self.assertTrue(self.source.enabled.get_bool()) - self.assertEqual( - self.source.types, - [util.SourceType.BINARY, util.SourceType.SOURCECODE] - ) - self.assertTrue(self.source.sourcecode_enabled) - self.assertEqual( - self.source.uris, - ['http://example.com/ubuntu', 'http://example.com/mirror'] - ) - self.assertEqual( - self.source.suites, - ['suite', 'suite-updates'] - ) - self.assertEqual( - self.source.components, - ['main', 'contrib', 'nonfree'] - ) - self.assertEqual(self.source.architectures, 'amd64 armel') - self.assertEqual(self.source.languages, 'en_US en_CA') - self.assertEqual(self.source.file.path.name, 'test.sources') - - def test_output_822(self): - source_string = ( - 'X-Repolib-ID: test\n' - 'X-Repolib-Name: Test Source\n' - 'Enabled: yes\n' - 'Types: deb deb-src\n' - 'URIs: http://example.com/ubuntu http://example.com/mirror\n' - 'Suites: suite suite-updates\n' - 'Components: main contrib nonfree\n' - 'Architectures: amd64 armel\n' - 'Languages: en_US en_CA\n' - ) - legacy_source_string = ( - 'X-Repolib-ID: test-legacy\n' - 'X-Repolib-Name: Test Legacy Source\n' - 'Enabled: yes\n' - 'Types: deb\n' - 'URIs: http://example.com/ubuntu\n' - 'Suites: suite\n' - 'Components: main contrib nonfree\n' - 'Architectures: amd64 armel\n' - 'Languages: en_US en_CA\n' - ) - self.assertEqual(self.source.deb822, source_string) - self.assertEqual(self.source_legacy.deb822, legacy_source_string) - - def test_output_ui(self): - source_string = ( - 'test:\n' - 'Name: Test Source\n' - 'Enabled: yes\n' - 'Types: deb deb-src\n' - 'URIs: http://example.com/ubuntu http://example.com/mirror\n' - 'Suites: suite suite-updates\n' - 'Components: main contrib nonfree\n' - 'Architectures: amd64 armel\n' - 'Languages: en_US en_CA\n' - '' - ) - legacy_source_string = ( - 'test-legacy:\n' - 'Name: Test Legacy Source\n' - 'Enabled: yes\n' - 'Types: deb\n' - 'URIs: http://example.com/ubuntu\n' - 'Suites: suite\n' - 'Components: main contrib nonfree\n' - 'Architectures: amd64 armel\n' - 'Languages: en_US en_CA\n' - ) - self.assertEqual(self.source.ui, source_string) - self.assertEqual(self.source_legacy.ui, legacy_source_string) - - def test_output_legacy(self): - source_string = ( - 'deb [arch=amd64,armel lang=en_US,en_CA] http://example.com/ubuntu suite main contrib nonfree ## X-Repolib-Name: Test Legacy Source # X-Repolib-ID: test-legacy' - ) - self.assertEqual(self.source_legacy.legacy, source_string) - - def test_enabled(self): - self.source.enabled = False - self.assertFalse(self.source.enabled.get_bool()) - - def test_sourcecode_enabled(self): - self.source.sourcecode_enabled = False - self.assertEqual(self.source.types, [util.SourceType.BINARY]) - - def test_dict_access(self): - self.assertEqual(self.source['X-Repolib-ID'], 'test') - self.assertEqual(self.source['X-Repolib-Name'], 'Test Source') - self.assertEqual(self.source['Enabled'], 'yes') - self.assertEqual(self.source['Enabled'], 'yes') - self.assertEqual(self.source['Types'], 'deb deb-src') - self.assertEqual(self.source['URIs'], 'http://example.com/ubuntu http://example.com/mirror') - self.assertEqual(self.source['Suites'], 'suite suite-updates') - self.assertEqual(self.source['Components'], 'main contrib nonfree') - self.assertEqual(self.source['Architectures'], 'amd64 armel') - self.assertEqual(self.source['Languages'], 'en_US en_CA') - - def test_load(self): - load_source = source.Source() - load_source.load_from_data([ - 'X-Repolib-ID: load-test', - 'X-Repolib-Name: Test Source Loading', - 'Enabled: yes', - 'Types: deb', - 'URIs: http://example.com/ubuntu http://example.com/mirror', - 'Suites: suite suite-updates', - 'Components: main contrib nonfree', - 'Architectures: amd64 armel', - 'Languages: en_US en_CA', - ]) - - self.assertEqual(load_source.ident, 'load-test') - self.assertEqual(load_source.name, 'Test Source Loading') - self.assertTrue(load_source.enabled.get_bool()) - self.assertEqual( - load_source.types, - [util.SourceType.BINARY] - ) - self.assertEqual( - load_source.uris, - ['http://example.com/ubuntu', 'http://example.com/mirror'] - ) - self.assertEqual( - load_source.suites, - ['suite', 'suite-updates'] - ) - self.assertEqual( - load_source.components, - ['main', 'contrib', 'nonfree'] - ) - self.assertEqual(load_source.architectures, 'amd64 armel') - self.assertEqual(load_source.languages, 'en_US en_CA') - - load_legacy_source = source.Source() - load_legacy_source.load_from_data( - ['deb [arch=amd64,armel lang=en_US,en_CA] http://example.com/ubuntu suite main contrib nonfree ## X-Repolib-Name: Test Legacy Source Loading # X-Repolib-ID: test-load-legacy'] - ) - - self.assertEqual(load_legacy_source.ident, 'test-load-legacy') - self.assertEqual(load_legacy_source.name, 'Test Legacy Source Loading') - self.assertTrue(load_legacy_source.enabled.get_bool()) - self.assertEqual( - load_legacy_source.types, - [util.SourceType.BINARY] - ) - self.assertEqual( - load_legacy_source.uris, - ['http://example.com/ubuntu'] - ) - self.assertEqual( - load_legacy_source.suites, - ['suite'] - ) - self.assertEqual( - load_legacy_source.components, - ['main', 'contrib', 'nonfree'] - ) - self.assertEqual(load_legacy_source.architectures, 'amd64 armel') - self.assertEqual(load_legacy_source.languages, 'en_US en_CA') - - def test_save_load(self): - self.source.file.save() - load_source_file = file.SourceFile(name='test') - load_source_file.load() - self.assertGreater(len(load_source_file.sources), 0) - self.assertGreater( - len(load_source_file.contents), len(load_source_file.sources) - ) - load_source = load_source_file.sources[0] - - self.assertEqual(load_source.ident, self.source.ident) - self.assertEqual(load_source.name, self.source.name) - self.assertEqual(load_source.enabled, self.source.enabled) - self.assertEqual(load_source.types, self.source.types) - self.assertEqual(load_source.sourcecode_enabled, self.source.sourcecode_enabled) - self.assertEqual(load_source.uris, self.source.uris) - self.assertEqual(load_source.suites, self.source.suites) - self.assertEqual(load_source.components, self.source.components) - self.assertEqual(load_source.architectures, self.source.architectures) - self.assertEqual(load_source.languages, self.source.languages) - self.assertEqual(load_source.file.name, self.source.file.name) - \ No newline at end of file diff --git a/repolib/usr/lib/python3/dist-packages/repolib/util.py b/repolib/usr/lib/python3/dist-packages/repolib/util.py deleted file mode 100644 index cb303a9..0000000 --- a/repolib/usr/lib/python3/dist-packages/repolib/util.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2022, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" - -import atexit -import logging -import re -import tempfile - -from enum import Enum -from pathlib import Path -from urllib.parse import urlparse -from urllib import request, error - -import dbus - -SOURCES_DIR = Path('/etc/apt/sources.list.d') -KEYS_DIR = Path('/etc/apt/keyrings/') -TESTING = False -KEYSERVER_QUERY_URL = 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x' - -log = logging.getLogger(__name__) - -class RepoError(Exception): - """ Exception from this module.""" - - def __init__(self, *args, code=1, **kwargs): - """Exception with a source object - - Arguments: - code (:obj:`int`, optional, default=1): Exception error code. - """ - super().__init__(*args, **kwargs) - self.code = code - -try: - import distro - DISTRO_CODENAME = distro.codename() -except ImportError: - DISTRO_CODENAME = 'linux' - -class SourceFormat(Enum): - """Enum of SourceFile Formats""" - DEFAULT = "sources" - LEGACY = "list" - -class SourceType(Enum): - """Enum of repository types""" - BINARY = 'deb' - SOURCECODE = 'deb-src' - - def ident(self) -> str: - """Used for getting a version of the format for idents""" - ident = f'{self.value}' - ident = ident.replace('deb-src', 'source') - ident = ident.replace('deb', 'binary') - return ident - -class AptSourceEnabled(Enum): - """ Helper Enum to translate between bool data and the Deb822 format. """ - TRUE = 'yes' - FALSE = 'no' - - def get_bool(self): - """ Return a bool based on the value. """ - # pylint: disable=comparison-with-callable - # This doesnt seem to actually be a callable in this case. - if self.value == "yes": - return True - - return False - -valid_keys = [ - 'X-Repolib-Name:', - 'X-Repolib-ID:', - 'X-Repolib-Default-Mirror:', - 'X-Repolib-Comment', - 'X-Repolib-Prefs', - 'Enabled:', - 'Types:', - 'URIs:', - 'Suites:', - 'Components:', - 'Architectures:', - 'Languages:', - 'Targets:', - 'PDiffs:', - 'By-Hash:', - 'Allow-Insecure:', - 'Allow-Weak:', - 'Allow-Downgrade-To-Insecure:', - 'Trusted:', - 'Signed-By:', - 'Check-Valid-Until:', - 'Valid-Until-Min:', - 'Valid-Until-Max:', -] - -output_skip_keys = [ - 'X-Repolib-Prefs', - 'X-Repolib-ID', -] - -options_inmap = { - 'arch': 'Architectures', - 'lang': 'Languages', - 'target': 'Targets', - 'pdiffs': 'PDiffs', - 'by-hash': 'By-Hash', - 'allow-insecure': 'Allow-Insecure', - 'allow-weak': 'Allow-Weak', - 'allow-downgrade-to-insecure': 'Allow-Downgrade-To-Insecure', - 'trusted': 'Trusted', - 'signed-by': 'Signed-By', - 'check-valid-until': 'Check-Valid-Until', - 'valid-until-min': 'Valid-Until-Min', - 'valid-until-max': 'Valid-Until-Max' -} - -options_outmap = { - 'Architectures': 'arch', - 'Languages': 'lang', - 'Targets': 'target', - 'PDiffs': 'pdiffs', - 'By-Hash': 'by-hash', - 'Allow-Insecure': 'allow-insecure', - 'Allow-Weak': 'allow-weak', - 'Allow-Downgrade-To-Insecure': 'allow-downgrade-to-insecure', - 'Trusted': 'trusted', - 'Signed-By': 'signed-by', - 'Check-Valid-Until': 'check-valid-until', - 'Valid-Until-Min': 'valid-until-min', - 'Valid-Until-Max': 'valid-until-max' -} - -true_values = [ - True, - 'True', - 'true', - 'Yes', - 'yes', - 'YES', - 'y', - 'Y', - AptSourceEnabled.TRUE, - 1 -] - -keys_map = { - 'X-Repolib-Name: ': 'Name: ', - 'X-Repolib-ID: ': 'Ident: ', - 'X-Repolib-Comments: ': 'Comments: ', - 'X-Repolib-Default-Mirror: ': 'Default Mirror: ', -} - -PRETTY_PRINT = '\n ' - -_KEYS_TEMPDIR = tempfile.TemporaryDirectory() -TEMP_DIR = Path(_KEYS_TEMPDIR.name) - -options_re = re.compile(r'[^@.+]\[([^[]+.+)\]\ ') -uri_re = re.compile(r'\w+:(\/?\/?)[^\s]+') - -CLEAN_CHARS = { - 33: None, - 64: 45, - 35: 45, - 36: 45, - 37: 45, - 94: 45, - 38: 45, - 42: 45, - 41: None, - 40: None, - 43: 45, - 61: 45, - 91: None, - 92: None, - 93: None, - 123: None, - 125: None, - 124: 95, - 63: None, - 47: 95, - 46: 45, - 60: 95, - 62: 95, - 44: 95, - 96: None, - 126: None, - 32: 95, - 58: None, - 59: None, -} - -sources:dict = {} -files:dict = {} -keys:dict = {} -errors:dict = {} - - -def scrub_filename(name: str = '') -> str: - """ Clean up a string intended for a filename. - - Arguments: - name (str): The prospective name to scrub. - - Returns: str - The cleaned-up name. - """ - return name.translate(CLEAN_CHARS) - -def set_testing(testing:bool=True) -> None: - """Sets Repolib in testing mode where changes will not be saved. - - Arguments: - testing(bool): Whether testing mode should be enabled or disabled - (Defaul: True) - """ - global KEYS_DIR - global SOURCES_DIR - - testing_tempdir = tempfile.TemporaryDirectory() - - if not testing: - KEYS_DIR = '/usr/share/keyrings' - SOURCES_DIR = '/etc/apt/sources.list.d' - return - - testing_root = Path(testing_tempdir.name) - KEYS_DIR = testing_root / 'usr' / 'share' / 'keyrings' - SOURCES_DIR = testing_root / 'etc' / 'apt' / 'sources.list.d' - - -def _cleanup_temsps() -> None: - """Clean up our tempdir""" - _KEYS_TEMPDIR.cleanup() - # _TESTING_TEMPDIR.cleanup() - -atexit.register(_cleanup_temsps) - -def dbus_quit(): - bus = dbus.SystemBus() - privileged_object = bus.get_object('org.pop_os.repolib', '/Repo') - privileged_object.exit() - -def compare_sources(source1, source2, excl_keys:list) -> bool: - """Compare two sources based on arbitrary criteria. - - This looks at a given list of keys, and if the given keys between the two - given sources are identical, returns True. - - Arguments: - source1, source2(Source): The two sources to compare - excl_keys([str]): Any keys to exclude from the comparison - - Returns: bool - `True` if the sources are identical, otherwise `False`. - """ - for key in source1: - if key in excl_keys: - continue - if key in source2: - if source1[key] != source2[key]: - return False - else: - continue - else: - return False - for key in source2: - if key in excl_keys: - continue - if key in source1: - if source1[key] != source2[key]: - return False - else: - continue - else: - return False - return True - -def find_differences_sources(source1, source2, excl_keys:list) -> dict: - """Find key-value pairs which differ between two sources. - - Arguments: - source1, source2(Source): The two sources to compare - excl_keys([str]): Any keys to exclude from the comparison - - Returns: dict{'key': ('source1[key]','source2[key]')} - The dictionary of different keys, with the key values from each source. - """ - differing_keys:dict = {} - - for key in source1: - if key in excl_keys: - continue - if key in source2: - if source1[key] == source2[key]: - continue - differing_keys[key] = (source1[key], source2[key]) - differing_keys[key] = (source1[key], '') - for key in source2: - if key in excl_keys: - continue - if key in source1: - if source1[key] == source2[key]: - continue - differing_keys[key] = ('', source2[key]) - - return differing_keys - -def combine_sources(source1, source2) -> None: - """Combine the data in two sources into one. - - Arguments: - source1(Source): The source to be merged into - source2(Source): The source to merge from - """ - for key in source1: - if key in ('X-Repolib-Name', 'X-Repolib-ID', 'Enabled', 'Types'): - continue - if key in source2: - source1[key] += f' {source2[key]}' - for key in source2: - if key in ('X-Repolib-Name', 'X-Repolib-ID', 'Enabled', 'Types'): - continue - if key in source1: - source1[key] += f' {source2[key]}' - - # Need to deduplicate the list - for key in source1: - vals = source1[key].strip().split() - newvals = [] - for val in vals: - if val not in newvals: - newvals.append(val) - source1[key] = ' '.join(newvals) - for key in source2: - vals = source2[key].strip().split() - newvals = [] - for val in vals: - if val not in newvals: - newvals.append(val) - source2[key] = ' '.join(newvals) - - -def prettyprint_enable(enabled: bool = True) -> None: - """Easy helper to enable/disable pretty-printing for object reprs. - - Can also be used as an easy way to reset to defaults. - - Arguments: - enabled(bool): Whether or not Pretty Printing should be enabled - """ - global PRETTY_PRINT - if enabled: - PRETTY_PRINT = '\n ' - else: - PRETTY_PRINT = '' - -def url_validator(url): - """ Validate a url and tell if it's good or not. - - Arguments: - url (str): The URL to validate. - - Returns: - `True` if `url` is not malformed, otherwise `False`. - """ - try: - # pylint: disable=no-else-return,bare-except - # A) We want to return false if the URL doesn't contain those parts - # B) We need this to not throw any exceptions, regardless what they are - result = urlparse(url) - if not result.scheme: - return False - if result.scheme == 'x-repolib-name': - return False - if result.netloc: - # We need at least a scheme and a netlocation/hostname or... - return all([result.scheme, result.netloc]) - elif result.path: - # ...a scheme and a path (this allows file:/// URIs which are valid) - return all([result.scheme, result.path]) - return False - except: - return False - -def validate_debline(valid): - """ Basic checks to see if a given debline is valid or not. - - Arguments: - valid (str): The line to validate. - - Returns: - True if the line is valid, False otherwise. - """ - comment:bool = False - if valid.startswith('#'): - comment = True - valid = valid.replace('#', '') - valid = valid.strip() - - if valid.startswith("deb"): - words = valid.split() - for word in words: - if url_validator(word): - return True - - elif valid.startswith("ppa:"): - if "/" in valid: - return True - - else: - if valid.endswith('.flatpakrepo'): - return False - if len(valid.split()) == 1 and not comment: - return url_validator(valid) - return False - -def strip_hashes(line:str) -> str: - """ Strips the leading #'s from the given line. - - Arguments: - line (str): The line to strip. - - Returns: - (str): The input line without any leading/trailing hashes or - leading/trailing whitespace. - """ - while True: - line = line.strip('#') - line = line.strip() - if not line.startswith('#'): - break - - return line diff --git a/repolib/usr/lib/repolib/add-apt-repository b/repolib/usr/lib/repolib/add-apt-repository deleted file mode 100755 index 5c2f26c..0000000 --- a/repolib/usr/lib/repolib/add-apt-repository +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/python3 - -""" -Copyright (c) 2019-2020, Ian Santopietro -All rights reserved. - -This file is part of RepoLib. - -RepoLib is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -RepoLib 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with RepoLib. If not, see . -""" -#pylint: disable=invalid-name -# Pylint will complain about our module name not being snake_case, however this -# is a command rather than a python module, and thus this is correct anyway. - -import argparse -import os -import subprocess - -import repolib - -system_codename = repolib.util.DISTRO_CODENAME - -system_components = [ - 'main', - 'universe', - 'multiverse', - 'restricted' -] - -system_suites = [ - system_codename, - f'{system_codename}-updates', - f'{system_codename}-security', - f'{system_codename}-backports', - f'{system_codename}-proposed', - 'updates', - 'security', - 'backports', - 'proposed', -] - -def get_args(): - parser = argparse.ArgumentParser( - prog='add-apt-repository', - description=( - 'add-apt-repository is a script for adding apt sources.list entries.' - '\nThis command has been deprecated in favor of `apt-manage`. See ' - '`apt-manage --help` for more information.' - ) - ) - - parser.add_argument( - 'sourceline', - metavar='' - ) - - group = parser.add_mutually_exclusive_group() - - group.add_argument( - '-m', - '--massive-debug', - dest='debug', - action='store_true', - help='Print a lot of debug information to the command line' - ) - - group.add_argument( - '-r', - '--remove', - action='store_true', - help='remove repository from sources.list.d directory' - ) - - group.add_argument( - '-s', - '--enable-source', - dest='source', - action='store_true', - help='Allow downloading of source packages from the repository' - ) - - parser.add_argument( - '-y', - '--yes', - action='store_true', - help='Assum yes to all queries' - ) - - parser.add_argument( - '-n', - '--no-update', - dest='noupdate', - action='store_true', - help='Do not update package cache after adding' - ) - - parser.add_argument( - '-u', - '--update', - action='store_true', - help='Update package cache after adding (legacy option)' - ) - - parser.add_argument( - '-k', - '--keyserver', - metavar='KEYSERVER', - help='Legacy option, unused.' - ) - - return parser - -parser = get_args() -args = parser.parse_args() - -command = ['apt-manage'] - -if args.debug: - command.append('-bb') - -sourceline = args.sourceline -run = True -remove = False - -if sourceline in system_components: - command.append('modify') - command.append('system') - if not args.remove: - command.append('--add-component') - else: - command.append('--remove-component') - -elif sourceline in system_suites: - command.append('modify') - command.append('system') - if not args.remove: - command.append('--add-suite') - else: - command.append('--remove-suite') - -else: - - if args.source: - command.append('source') - - elif args.remove: - remove = True - command.append('remove') - - else: - command.append('add') - if not args.yes: - command.append('--expand') - -if not remove: - command.append(sourceline) -else: - sources = repolib.get_all_sources() - comp_source = repolib.DebLine(sourceline) - for source in sources: - if comp_source.uris[0] in source.uris: - name = str(source.filename.name) - name = name.replace(".list", "") - name = name.replace(".sources", "") - command.append(name) - -run = True - -if os.geteuid() != 0: - print('Error: must run as root') - run = False - -if run: - subprocess.run(command) - - if not args.noupdate: - subprocess.run(['apt', 'update']) - -print('NOTE: add-apt-repository is deprecated in Pop!_OS. Use this instead:') -print_command = command.copy() -if '--expand' in print_command: - print_command.remove('--expand') -print(' '.join(print_command)) diff --git a/repolib/usr/lib/repolib/service.py b/repolib/usr/lib/repolib/service.py deleted file mode 100755 index b8d79dd..0000000 --- a/repolib/usr/lib/repolib/service.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/python3 -''' - Copyright 2020 Ian Santopietro (ian@system76.com) - - This file is part of Repolib. - - Repolib 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. - - Repolib 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 Repolib. If not, see . -''' -#pylint: skip-file - -import shutil -import subprocess - -import gi -from gi.repository import GObject, GLib - -from pathlib import Path -import dbus -import dbus.service -import dbus.mainloop.glib -import time - -import repolib - -class RepolibException(dbus.DBusException): - _dbus_error_name = 'org.pop_os.repolib.RepolibException' - -class PermissionDeniedByPolicy(dbus.DBusException): - _dbus_error_name = 'org.pop_os.repolib.PermissionDeniedByPolicy' - -class AptException(Exception): - pass - -class Repo(dbus.service.Object): - def __init__(self, conn=None, object_path=None, bus_name=None): - dbus.service.Object.__init__(self, conn, object_path, bus_name) - - # These are used by PolKit to check privileges - self.dbus_info = None - self.polkit = None - self.enforce_polkit = True - - try: - self.system_repo = repolib.SystemSource() - except: - self.system_repo = None - - self.source = None - self.sources_dir = Path('/etc/apt/sources.list.d') - self.keys_dir = Path('/etc/apt/trusted.gpg.d') - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='as', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def add_apt_signing_key(self, cmd, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - print(cmd) - key_path = str(cmd.pop(-1)) - with open(key_path, mode='wb') as keyfile: - try: - subprocess.run(cmd, check=True, stdout=keyfile) - except subprocess.CalledProcessError as e: - raise e - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='ss', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def install_signing_key(self, src, dest, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - shutil.copy2(src, dest) - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='s', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def delete_signing_key(self, src, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - key_path = Path(src) - key_path.unlink() - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='s', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def delete_prefs_file(self, src, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - prefs_path = Path(src) - prefs_path.unlink() - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='ss', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def output_prefs_to_disk(self, path, contents, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - full_path = Path(path) - with open(full_path, mode='w') as output_file: - output_file.write(contents) - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='ss', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def output_file_to_disk(self, filename, source, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - full_path = self.sources_dir / filename - with open(full_path, mode='w') as output_file: - output_file.write(source) - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='ss', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def backup_alt_file(self, alt_file, save_file, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - alt_path = self.sources_dir / alt_file - save_path = self.sources_dir / save_file - if alt_path.exists(): - alt_path.rename(save_path) - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='s', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def delete_source_file(self, filename, sender=None, conn=None): - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - source_file = self.sources_dir / filename - source_file.unlink() - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='', out_signature='', - sender_keyword='sender', connection_keyword='conn' - ) - def exit(self, sender=None, conn=None): - mainloop.quit() - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='b', out_signature='b', - sender_keyword='sender', connection_keyword='conn' - ) - def set_system_source_code_enabled(self, enabled, sender=None, conn=None): - """ Enable or disable source code in the system source. - - Arguments: - enabled (bool): The new state to set, True = Enabled. - """ - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - if self.system_repo: - self.system_repo.load_from_file() - new_types = [repolib.util.AptSourceType.BINARY] - if enabled: - new_types.append(repolib.util.AptSourceType.SOURCE) - self.system_repo.types = new_types - self.system_repo.save_to_disk() - return enabled - return False - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='sb', out_signature='b', - sender_keyword='sender', connection_keyword='conn' - ) - def set_system_comp_enabled(self, comp, enable, sender=None, conn=None): - """ Enable or disable a component in the system source. - - Arguments: - comp (str): the component to set - enable (bool): The new state to set, True = Enabled. - """ - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - if self.system_repo: - self.system_repo.load_from_file() - self.system_repo.set_component_enabled(component=comp, enabled=enable) - self.system_repo.save_to_disk() - return True - return False - - @dbus.service.method( - "org.pop_os.repolib.Interface", - in_signature='sb', out_signature='b', - sender_keyword='sender', connection_keyword='conn' - ) - def set_system_suite_enabled(self, suite, enable, sender=None, conn=None): - """ Enable or disable a suite in the system source. - - Arguments: - suite (str): the suite to set - enable (bool): The new state to set, True = Enabled. - """ - self._check_polkit_privilege( - sender, conn, 'org.pop_os.repolib.modifysources' - ) - if self.system_repo: - self.system_repo.load_from_file() - self.system_repo.set_suite_enabled(suite=suite, enabled=enable) - self.system_repo.save_to_disk() - return True - return False - - @classmethod - def _log_in_file(klass, filename, string): - date = time.asctime(time.localtime()) - ff = open(filename, "a") - ff.write("%s : %s\n" %(date,str(string))) - ff.close() - - @classmethod - def _strip_source_line(self, source): - source = source.replace("#", "# ") - source = source.replace("[", "") - source = source.replace("]", "") - source = source.replace("'", "") - source = source.replace(" ", " ") - return source - - def _check_polkit_privilege(self, sender, conn, privilege): - # from jockey - '''Verify that sender has a given PolicyKit privilege. - sender is the sender's (private) D-BUS name, such as ":1:42" - (sender_keyword in @dbus.service.methods). conn is - the dbus.Connection object (connection_keyword in - @dbus.service.methods). privilege is the PolicyKit privilege string. - This method returns if the caller is privileged, and otherwise throws a - PermissionDeniedByPolicy exception. - ''' - if sender is None and conn is None: - # called locally, not through D-BUS - return - if not self.enforce_polkit: - # that happens for testing purposes when running on the session - # bus, and it does not make sense to restrict operations here - return - - # get peer PID - if self.dbus_info is None: - self.dbus_info = dbus.Interface(conn.get_object('org.freedesktop.DBus', - '/org/freedesktop/DBus/Bus', False), 'org.freedesktop.DBus') - pid = self.dbus_info.GetConnectionUnixProcessID(sender) - - # query PolicyKit - if self.polkit is None: - self.polkit = dbus.Interface(dbus.SystemBus().get_object( - 'org.freedesktop.PolicyKit1', - '/org/freedesktop/PolicyKit1/Authority', False), - 'org.freedesktop.PolicyKit1.Authority') - try: - # we don't need is_challenge return here, since we call with AllowUserInteraction - (is_auth, _, details) = self.polkit.CheckAuthorization( - ('unix-process', {'pid': dbus.UInt32(pid, variant_level=1), - 'start-time': dbus.UInt64(0, variant_level=1)}), - privilege, {'': ''}, dbus.UInt32(1), '', timeout=600) - except dbus.DBusException as e: - if e._dbus_error_name == 'org.freedesktop.DBus.Error.ServiceUnknown': - # polkitd timed out, connect again - self.polkit = None - return self._check_polkit_privilege(sender, conn, privilege) - else: - raise - - if not is_auth: - Repo._log_in_file('/tmp/repolib.log','_check_polkit_privilege: sender %s on connection %s pid %i is not authorized for %s: %s' % - (sender, conn, pid, privilege, str(details))) - raise PermissionDeniedByPolicy(privilege) - -if __name__ == '__main__': - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - - bus = dbus.SystemBus() - name = dbus.service.BusName("org.pop_os.repolib", bus) - object = Repo(bus, '/Repo') - - mainloop = GLib.MainLoop() - mainloop.run() \ No newline at end of file diff --git a/repolib/usr/share/bash-completion/completions/apt-manage b/repolib/usr/share/bash-completion/completions/apt-manage deleted file mode 100755 index f881bc4..0000000 --- a/repolib/usr/share/bash-completion/completions/apt-manage +++ /dev/null @@ -1,42 +0,0 @@ -# Debian apt-manage completion - -_apt_manage() -{ - local cur prev words cword package - _init_completion -n ':=' || return - - local special i - i=0 - for (( i=0; i < ${#words[@]}-1; i++ )); do - if [[ ${words[i]} == @(add|list|modify|remove|key) ]]; then - special=${words[i]} - fi - done - - if [[ -n $special ]]; then - case $special in - list|modify|remove|key) - COMPREPLY=( $( compgen -W '$(apt-manage list -n)' -- "$cur" ) ) - return - ;; - *) - ;; - esac - fi - - - if [[ "$cur" == -* ]]; then - return - # COMPREPLY=( $(compgen -W ' - # --help --disable --source-code --expand - # --verbose --legacy --no-names - # --enable --disable --name --add-suite --remove-suite - # --add-component --remove-component --add-uri --remove-uri - # ' -- "$cur") ) - else - COMPREPLY=( $(compgen -W 'add list modify remove key' \ - -- "$cur") ) - fi - -} && -complete -F _apt_manage apt-manage diff --git a/repolib/usr/share/dbus-1/system-services/org.pop_os.repolib.service b/repolib/usr/share/dbus-1/system-services/org.pop_os.repolib.service deleted file mode 100644 index 2f354e3..0000000 --- a/repolib/usr/share/dbus-1/system-services/org.pop_os.repolib.service +++ /dev/null @@ -1,4 +0,0 @@ -[D-BUS Service] -Name=org.pop_os.repolib -Exec=/usr/bin/python3 /usr/lib/repolib/service.py -User=root diff --git a/repolib/usr/share/polkit-1/actions/org.pop_os.repolib.policy b/repolib/usr/share/polkit-1/actions/org.pop_os.repolib.policy deleted file mode 100644 index 7a6caef..0000000 --- a/repolib/usr/share/polkit-1/actions/org.pop_os.repolib.policy +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Repoman - https://github.com/pop-os/repolib - x-system-software-sources - - - Modifies a Debian Repository in the system software sources. - Authentication is required to change software sources. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - \ No newline at end of file diff --git a/repolib/usr/share/zsh/vendor-completions/_apt-manage b/repolib/usr/share/zsh/vendor-completions/_apt-manage deleted file mode 100644 index ffe1fb1..0000000 --- a/repolib/usr/share/zsh/vendor-completions/_apt-manage +++ /dev/null @@ -1,71 +0,0 @@ -#compdef apt-manage -typeset -A opt_args - -typeset -A opt_args - -_arguments -C \ - '1:cmd:->cmds' \ - '2:sources:->source_lists' \ - '*:: :->args' \ -&& ret=0 - -case "$state" in - (cmds) - local commands; commands=( - 'add' - 'list' - 'modify' - 'remove' - 'key' - ) - _describe -t commands 'command' commands && ret=0 - ;; - (source_lists) - local sources - sources=( $(apt-manage list -n)) - _describe -t sources 'source' sources && ret=0 - ;; - (args) - local arguments - arguments=( - # Main command - '--help' - # Add subcommand - '--disable' - '--source-code' - '--terse' - '--name' - '--identifier' - '--format' - '--skip-keys' - # Key Ssbcommand - '--path' - '--url' - '--ascii' - '--fingerprint' - '--keyserver' - '--info' - '--remove' - # List subcommand - '--legacy' - '--verbose' - '--all' - '--file-names' - # Modify subcommand - '--enable' - '--source-enable' - '--source-disable' - '--add-uri' - '--add-suite' - '--add-component' - '--remove-uri' - '--remove-suite' - '--remove-component' - # Remove subcommand - '--assume-yes' - ) - _describe -t arguments 'argument' arguments && ret=0 - ;; -esac - -return 1 \ No newline at end of file