diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ca109d..a643cd87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v5.1.0 + +### New Features + +* `edit original puid` and `edit master puid` commands to set the PUID of original and master files respectively +* `manual extract` command to add manually extracted files +* `manual convert` command to add manually converted files + ## v5.0.3 ### Fixes diff --git a/README.md b/README.md index fd1acade..10261136 100755 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ * [extract](#digiarch-extract) * [edit](#digiarch-edit) * [original](#digiarch-edit-original) + * [puid](#digiarch-edit-original-puid) * [action](#digiarch-edit-original-action) * [convert](#digiarch-edit-original-action-convert) * [extract](#digiarch-edit-original-action-extract) @@ -21,6 +22,7 @@ * [rename](#digiarch-edit-original-rename) * [remove](#digiarch-edit-original-remove) * [master](#digiarch-edit-master) + * [puid](#digiarch-edit-master-puid) * [convert](#digiarch-edit-master-convert) * [processed](#digiarch-edit-master-processed) * [remove](#digiarch-edit-master-remove) @@ -29,6 +31,9 @@ * [statutory](#digiarch-edit-statutory) * [remove](#digiarch-edit-statutory-remove) * [rollback](#digiarch-edit-rollback) + * [manual](#digiarch-manual) + * [extract](#digiarch-manual-extract) + * [convert](#digiarch-manual-convert) * [search](#digiarch-search) * [original](#digiarch-search-original) * [master](#digiarch-search-master) @@ -55,6 +60,7 @@ Commands: identify Identify files. extract Unpack archives. edit Edit the database. + manual Perform actions manually. search Search the database. log Display the event log. upgrade Upgrade the database. @@ -326,6 +332,7 @@ Options: --help Show this message and exit. Commands: + puid Change PUID. action Change actions of original files. processed Set original files as processed. lock Lock files. @@ -333,6 +340,25 @@ Commands: remove Remove files. ``` +##### digiarch edit original puid + +``` +Usage: digiarch edit original puid [OPTIONS] PUID QUERY REASON + + Change PUID of original files. + + To lock the files after editing them, use the --lock option. + + To see the changes without committing them, use the --dry-run option. + + For details on the QUERY argument, see the edit command. + +Options: + --lock Lock the edited files. + --dry-run Show changes without committing them. + --help Show this message and exit. +``` + ##### digiarch edit original action ``` @@ -583,11 +609,28 @@ Options: --help Show this message and exit. Commands: + puid Change PUID. convert Set access convert action. processed Set master files as processed. remove Remove files. ``` +##### digiarch edit master puid + +``` +Usage: digiarch edit master puid [OPTIONS] PUID QUERY REASON + + Change PUID of master files. + + To see the changes without committing them, use the --dry-run option. + + For details on the QUERY argument, see the edit command. + +Options: + --dry-run Show changes without committing them. + --help Show this message and exit. +``` + ##### digiarch edit master convert ``` @@ -757,6 +800,77 @@ Options: --help Show this message and exit. ``` +### digiarch manual + +``` +Usage: digiarch manual [OPTIONS] COMMAND [ARGS]... + + Perform complex actions manually when the automated tools fail or when one + is not available. + +Options: + --help Show this message and exit. + +Commands: + convert Add converted files. + extract Add extracted files. +``` + +#### digiarch manual extract + +``` +Usage: digiarch manual extract [OPTIONS] PARENT FILE... + + Manually add files extracted from an archive, and assign them the PARENT + UUID. + + The given FILEs can be single files or folders and must be located inside + OriginalDocuments. + + To exclude children files when using a folder as target, use the --exclude + option. + + If the files are not already in the database they will be added without + identification. Run the identify command to assign them a PUID and action. + + If the files are in the database their parent value will be set to ORIGINAL + unless they already have one assigned, in which case they will be ignored. + Run the identify command to assign a PUID and action to newly-added files. + + To see the changes without committing them, use the --dry-run option. + +Options: + --exclude TEXT File and folder names to exclude. [multiple] + --dry-run Show changes without committing them. + --help Show this message and exit. +``` + +#### digiarch manual convert + +``` +Usage: digiarch manual convert [OPTIONS] ORIGINAL {master|access|statutory} + FILE... + + Manually add converted files with ORIGINAL UUID as their parent. + + \b Depending on the TARGET, a different type of ORIGINAL file will be + needed: * "master": original file parent * "access": master file parent * + "statutory": master file parent + + The given FILEs must be located inside the MasterDocuments, AccessDocuments, + or Documents folder depending on the TARGET. + + If the files are already in the database they will be ignored. Run the + identify command to assign a PUID (and action where applicable) to newly- + added files. + + To see the changes without committing them, use the --dry-run option. + +Options: + --dry-run Show changes without committing them. + --help Show this message and exit. +``` + ### digiarch search ``` diff --git a/digiarch/__version__.py b/digiarch/__version__.py index 4682e613..0d72820f 100644 --- a/digiarch/__version__.py +++ b/digiarch/__version__.py @@ -1 +1 @@ -__version__ = "5.0.3" +__version__ = "5.1.0" diff --git a/digiarch/cli.py b/digiarch/cli.py index b3344cb8..590dcbfb 100644 --- a/digiarch/cli.py +++ b/digiarch/cli.py @@ -11,6 +11,7 @@ from .commands.identify import grp_identify from .commands.init import cmd_init from .commands.log import cmd_log +from .commands.manual import grp_manual from .commands.search import grp_search from .commands.upgrade import cmd_upgrade @@ -28,6 +29,7 @@ def app(): app.add_command(grp_identify, grp_identify.name) app.add_command(cmd_extract, cmd_extract.name) app.add_command(grp_edit, grp_edit.name) +app.add_command(grp_manual, grp_manual.name) app.add_command(grp_search, grp_search.name) app.add_command(cmd_log, cmd_log.name) app.add_command(cmd_upgrade, cmd_upgrade.name) diff --git a/digiarch/commands/edit/common.py b/digiarch/commands/edit/common.py index 9b43b388..6c8f5dbe 100644 --- a/digiarch/commands/edit/common.py +++ b/digiarch/commands/edit/common.py @@ -27,6 +27,7 @@ def edit_file_value( property_value: Callable[[Any], Any] | Any, # noqa: ANN401 dry_run: bool, *loggers: Logger, + lock: bool = False, ): for file in query_table(table, query, [("lower(relative_path)", "asc")]): value = property_value(getattr(file, property_name)) if callable(property_value) else property_value @@ -46,6 +47,8 @@ def edit_file_value( ) if not dry_run: setattr(file, property_name, value) + if lock and hasattr(file, "lock"): + setattr(file, "lock", True) table.update(file) database.log.insert(event) event.log(INFO, *loggers, show_args=["uuid", "data"], path=file.relative_path) diff --git a/digiarch/commands/edit/edit.py b/digiarch/commands/edit/edit.py index 7ee18b44..3c90c839 100644 --- a/digiarch/commands/edit/edit.py +++ b/digiarch/commands/edit/edit.py @@ -5,6 +5,8 @@ from .lock import cmd_lock_original from .processed import cmd_processed_master from .processed import cmd_processed_original +from .puid import cmd_puid_master +from .puid import cmd_puid_original from .remove import cmd_remove_access from .remove import cmd_remove_master from .remove import cmd_remove_original @@ -70,12 +72,14 @@ def grp_edit_statutory(): # noinspection DuplicatedCode grp_edit.add_command(cmd_rollback, cmd_rollback.name) +grp_edit_original.add_command(cmd_puid_original, cmd_puid_original.name) grp_edit_original.add_command(grp_action_original, grp_action_original.name) grp_edit_original.add_command(cmd_processed_original, cmd_processed_original.name) grp_edit_original.add_command(cmd_lock_original, cmd_lock_original.name) grp_edit_original.add_command(cmd_rename_original, cmd_rename_original.name) grp_edit_original.add_command(cmd_remove_original, cmd_remove_original.name) +grp_edit_master.add_command(cmd_puid_master, cmd_puid_master.name) grp_edit_master.add_command(cmd_action_master_convert, cmd_action_master_convert.name) grp_edit_master.add_command(cmd_processed_master, cmd_processed_master.name) grp_edit_master.add_command(cmd_remove_master, cmd_remove_master.name) diff --git a/digiarch/commands/edit/puid.py b/digiarch/commands/edit/puid.py new file mode 100644 index 00000000..39418ff1 --- /dev/null +++ b/digiarch/commands/edit/puid.py @@ -0,0 +1,110 @@ +from acacore.utils.click import end_program +from acacore.utils.click import start_program +from acacore.utils.helpers import ExceptionManager +from click import argument +from click import command +from click import Context +from click import option +from click import pass_context + +from digiarch.__version__ import __version__ +from digiarch.commands.edit.common import edit_file_value +from digiarch.commands.edit.common import rollback_file_value +from digiarch.common import CommandWithRollback +from digiarch.common import get_avid +from digiarch.common import open_database +from digiarch.common import option_dry_run +from digiarch.common import rollback +from digiarch.query import argument_query +from digiarch.query import TQuery + + +@rollback("edit", rollback_file_value("puid")) +@command("puid", no_args_is_help=True, short_help="Change PUID.", cls=CommandWithRollback) +@argument("puid", nargs=1, type=str, required=True) +@argument_query(True, "uuid", ["uuid", "checksum", "puid", "relative_path", "action", "warning", "processed", "lock"]) +@argument("reason", nargs=1, type=str, required=True) +@option("--lock", is_flag=True, default=False, help="Lock the edited files.") +@option_dry_run() +@pass_context +def cmd_puid_original( + ctx: Context, + puid: str, + query: TQuery, + reason: str, + lock: bool, + dry_run: bool, +): + """ + Change PUID of original files. + + To lock the files after editing them, use the --lock option. + + To see the changes without committing them, use the --dry-run option. + + For details on the QUERY argument, see the edit command. + """ + avid = get_avid(ctx) + + with open_database(ctx, avid) as database: + log_file, log_stdout, _ = start_program(ctx, database, __version__, None, True, True, dry_run) + + with ExceptionManager(BaseException) as exception: + edit_file_value( + ctx, + database, + database.original_files, + query, + reason, + "original", + "puid", + puid, + dry_run, + log_stdout, + lock=lock, + ) + + end_program(ctx, database, exception, dry_run, log_file, log_stdout) + + +@rollback("edit", rollback_file_value("puid")) +@command("puid", no_args_is_help=True, short_help="Change PUID.", cls=CommandWithRollback) +@argument("puid", nargs=1, type=str, required=True) +@argument_query(True, "uuid", ["uuid", "checksum", "puid", "relative_path", "warning", "processed"]) +@argument("reason", nargs=1, type=str, required=True) +@option_dry_run() +@pass_context +def cmd_puid_master( + ctx: Context, + puid: str, + query: TQuery, + reason: str, + dry_run: bool, +): + """ + Change PUID of master files. + + To see the changes without committing them, use the --dry-run option. + + For details on the QUERY argument, see the edit command. + """ + avid = get_avid(ctx) + + with open_database(ctx, avid) as database: + log_file, log_stdout, _ = start_program(ctx, database, __version__, None, True, True, dry_run) + + with ExceptionManager(BaseException) as exception: + edit_file_value( + ctx, + database, + database.master_files, + query, + reason, + "master", + "puid", + puid, + dry_run, + log_stdout, + ) + + end_program(ctx, database, exception, dry_run, log_file, log_stdout) diff --git a/digiarch/commands/manual.py b/digiarch/commands/manual.py new file mode 100644 index 00000000..058af831 --- /dev/null +++ b/digiarch/commands/manual.py @@ -0,0 +1,235 @@ +from logging import INFO +from logging import WARNING +from pathlib import Path +from typing import Literal +from typing import Type +from uuid import UUID + +from acacore.database.table import Table +from acacore.models.event import Event +from acacore.models.file import BaseFile +from acacore.models.file import ConvertedFile +from acacore.models.file import MasterFile +from acacore.models.file import OriginalFile +from acacore.utils.click import ctx_params +from acacore.utils.click import end_program +from acacore.utils.click import start_program +from acacore.utils.functions import find_files +from acacore.utils.helpers import ExceptionManager +from click import argument +from click import BadParameter +from click import Choice +from click import Context +from click import group +from click import option +from click import Parameter +from click import pass_context +from click import Path as ClickPath + +from digiarch.__version__ import __version__ +from digiarch.common import get_avid +from digiarch.common import open_database +from digiarch.common import option_dry_run + + +def callback_uuid(ctx: Context, param: Parameter, value: str | None) -> UUID | None: + if value is None: + return value + + try: + return UUID(value) + except ValueError as err: + raise BadParameter(err.args[0] if err.args else "Invalid UUID.", ctx, param) + + +@group("manual", no_args_is_help=True, short_help="Perform actions manually.") +def grp_manual(): + """Perform complex actions manually when the automated tools fail or when one is not available.""" + + +@grp_manual.command("extract", no_args_is_help=True, short_help="Add extracted files.") +@argument("parent", type=str, callback=callback_uuid, required=True) +@argument( + "files", + metavar="FILE...", + type=ClickPath(exists=True, readable=True, resolve_path=True), + nargs=-1, + required=True, +) +@option("--exclude", type=str, multiple=True, help="File and folder names to exclude. [multiple]") +@option_dry_run() +@pass_context +def cmd_manual_extract( + ctx: Context, + parent: UUID, + files: tuple[str | Path, ...], + exclude: tuple[str, ...], + dry_run: bool, +): + """ + Manually add files extracted from an archive, and assign them the PARENT UUID. + + The given FILEs can be single files or folders and must be located inside OriginalDocuments. + + To exclude children files when using a folder as target, use the --exclude option. + + If the files are not already in the database they will be added without identification. + Run the identify command to assign them a PUID and action. + + If the files are in the database their parent value will be set to ORIGINAL unless they already have one + assigned, in which case they will be ignored. + Run the identify command to assign a PUID and action to newly-added files. + + To see the changes without committing them, use the --dry-run option. + """ + avid = get_avid(ctx) + files = tuple(map(Path, files)) + + if any(f == avid.dirs.original_documents or not f.is_relative_to(avid.dirs.original_documents) for f in files): + raise BadParameter("Files not in OriginalDocuments.", ctx, ctx_params(ctx)["files"]) + + with open_database(ctx, avid) as database: + log_file, log_stdout, _ = start_program(ctx, database, __version__, None, True, True, dry_run) + + with ExceptionManager() as exception: + parent_file = database.original_files[{"uuid": str(parent)}] + + if not parent_file: + raise FileNotFoundError(f"No original file with UUID {parent}.") + + file_paths = (p for f in files for p in find_files(f, exclude=[avid.dirs.original_documents / "_metadata"])) + + for path in file_paths: + if exclude and any(p in exclude for p in path.parts): + continue + + file = database.original_files[{"relative_path": str(path.relative_to(avid.path))}] + exists: bool = file is not None + + if file and file.parent: + Event.from_command(ctx, "skip", (file.uuid, "original")).log( + WARNING, + log_stdout, + path=file.relative_path, + parent=file.parent, + reason="File already has a parent.", + ) + continue + elif not file: # noqa: RET507 + file = OriginalFile.from_file(path, avid.path) + + event = Event.from_command( + ctx, + "edit" if exists else "new", + (file.uuid, "original"), + {"parent": parent_file.uuid}, + ) + + if not dry_run: + file.parent = parent_file.uuid + database.original_files.insert(file, on_exists="replace") + database.log.insert(event) + + event.log(INFO, log_stdout, show_args=["uuid"], path=file.relative_path) + + end_program(ctx, database, exception, dry_run, log_file, log_stdout) + + +@grp_manual.command("convert", no_args_is_help=True, short_help="Add converted files.") +@argument("original", type=str, callback=callback_uuid, required=True) +@argument("target", type=Choice(["master", "access", "statutory"]), required=True) +@argument( + "files", + metavar="FILE...", + type=ClickPath(exists=True, dir_okay=False, readable=True, resolve_path=True), + nargs=-1, + required=True, +) +@option_dry_run() +@pass_context +def cmd_manual_convert( + ctx: Context, + original: UUID, + target: Literal["master", "access", "statutory"], + files: tuple[str | Path, ...], + dry_run: bool, +): + r""" + Manually add converted files with ORIGINAL UUID as their parent. + + \b + Depending on the TARGET, a different type of ORIGINAL file will be needed: + * "master": original file parent + * "access": master file parent + * "statutory": master file parent + + The given FILEs must be located inside the MasterDocuments, AccessDocuments, or Documents folder depending on the + TARGET. + + If the files are already in the database they will be ignored. + Run the identify command to assign a PUID (and action where applicable) to newly-added files. + + To see the changes without committing them, use the --dry-run option. + """ + avid = get_avid(ctx) + + target_dir: Path + + if target == "master": + target_dir = avid.dirs.master_documents + elif target == "access": + target_dir = avid.dirs.access_documents + elif target == "statutory": + target_dir = avid.dirs.documents + else: + raise BadParameter("Invalid target.", ctx, ctx_params(ctx)["target"]) + + if any(Path(f) == target_dir or not Path(f).is_relative_to(target_dir) for f in files): + raise BadParameter(f"Files not in {target_dir.name}.", ctx, ctx_params(ctx)["files"]) + + with open_database(ctx, avid) as database: + log_file, log_stdout, _ = start_program(ctx, database, __version__, None, True, True, dry_run) + parent_table: Table[BaseFile] + target_table: Table[ConvertedFile] + target_class: Type[ConvertedFile] + + if target == "master": + parent_table = database.original_files + target_table = database.master_files + target_class = MasterFile + elif target == "access": + parent_table = database.master_files + target_table = database.access_files + target_class = ConvertedFile + elif target == "statutory": + parent_table = database.master_files + target_table = database.statutory_files + target_class = ConvertedFile + + with ExceptionManager() as exception: + parent_file = parent_table[{"uuid": str(original)}] + + if not parent_file: + raise FileNotFoundError(f"No file with UUID {original}.") + + for path in map(Path, files): + if file_db := target_table[{"relative_path": str(path.relative_to(avid.path))}]: + if file_db.original_uuid != parent_file.uuid: + Event.from_command(ctx, "skip", (file_db.uuid, target)).log( + WARNING, + log_stdout, + path=file_db.relative_path, + reason="File already exists.", + ) + continue + + file = target_class.from_file(path, avid.path, parent_file.uuid) + event = Event.from_command(ctx, "new", (file.uuid, target), {"original_uuid": parent_file.uuid}) + + if not dry_run: + target_table.insert(file) + database.log.insert(event) + + event.log(INFO, log_stdout, show_args=["uuid"], path=file.relative_path) + + end_program(ctx, database, exception, dry_run, log_file, log_stdout) diff --git a/pyproject.toml b/pyproject.toml index d96d1678..c1b65f6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "digiarch" -version = "5.0.3" +version = "5.1.0" description = "Tools for the Digital Archive Project at Aarhus Stadsarkiv" authors = ["Aarhus Stadsarkiv "] license = "GPL-3.0" diff --git a/tests/test_edit.py b/tests/test_edit.py index a38b1be4..9048da66 100644 --- a/tests/test_edit.py +++ b/tests/test_edit.py @@ -1,7 +1,7 @@ -from asyncio import Event from pathlib import Path from acacore.database import FilesDB +from acacore.models.event import Event from acacore.models.file import OriginalFile from acacore.models.reference_files import ConvertAction from acacore.models.reference_files import ExtractAction @@ -14,6 +14,63 @@ from tests.conftest import run_click +# noinspection DuplicatedCode +def test_edit_original_puid(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + puid: str = "fmt/test" + reason: str = "lock file" + + with FilesDB(avid.database_path) as database: + base_file = database.original_files.select(order_by=[("random()", "asc")], limit=1).fetchone() + assert base_file + + run_click(avid.path, app, "edit", "original", "puid", puid, f"@uuid {base_file.uuid}", reason, "--lock") + + with FilesDB(avid.database_path) as database: + test_file = database.original_files.select({"uuid": str(base_file.uuid)}, limit=1).fetchone() + assert test_file + assert test_file.puid == puid + + event = database.log.select( + "file_uuid = ? and operation = ?", + [str(base_file.uuid), f"{app.name}.edit.original.puid:edit"], + ).fetchone() + assert event is not None + assert event.data == [base_file.puid, puid] + + run_click(avid.path, app, "edit", "rollback", 1) + + with FilesDB(avid.database_path) as database: + test_file = database.original_files.select({"uuid": str(base_file.uuid)}, limit=1).fetchone() + assert test_file + assert test_file.puid == base_file.puid + + +# noinspection DuplicatedCode +def test_edit_master_puid(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + puid: str = "fmt/test" + reason: str = "lock file" + + with FilesDB(avid.database_path) as database: + base_file = database.master_files.select(order_by=[("random()", "asc")], limit=1).fetchone() + assert base_file + + run_click(avid.path, app, "edit", "master", "puid", puid, f"@uuid {base_file.uuid}", reason) + + with FilesDB(avid.database_path) as database: + test_file = database.master_files.select({"uuid": str(base_file.uuid)}, limit=1).fetchone() + assert test_file + assert test_file.puid == puid + + event = database.log.select( + "file_uuid = ? and operation = ?", + [str(base_file.uuid), f"{app.name}.edit.master.puid:edit"], + ).fetchone() + assert event is not None + assert event.data == [base_file.puid, puid] + + # noinspection DuplicatedCode def test_edit_original_action(tests_folder: Path, avid_folder_copy: Path): avid = AVID(avid_folder_copy) @@ -260,6 +317,34 @@ def test_edit_original_lock(avid_folder_copy: Path): assert event.data == [base_file.lock, test_file.lock] +# noinspection DuplicatedCode +def test_edit_original_lock_file_query(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + reason: str = "lock file" + + with FilesDB(avid.database_path) as database: + base_files = database.original_files.select(order_by=[("random()", "asc")], limit=2).fetchmany(2) + assert base_files + + avid.path.joinpath("uuids.txt").write_text("\n".join(str(f.uuid) for f in base_files)) + + run_click(avid.path, app, "edit", "original", "lock", "@uuid @file uuids.txt", reason, "--lock") + + with FilesDB(avid.database_path) as database: + for base_file in base_files: + test_file = database.original_files[{"uuid": str(base_file.uuid)}] + assert test_file is not None + assert test_file.lock + + event = database.log.select( + "file_uuid = ? and operation = ?", + [str(base_file.uuid), f"{app.name}.edit.original.lock:edit"], + ).fetchone() + assert event is not None + assert event.reason == reason + assert event.data == [base_file.lock, test_file.lock] + + # noinspection DuplicatedCode def test_edit_original_processed(avid_folder_copy: Path): avid = AVID(avid_folder_copy) diff --git a/tests/test_manual.py b/tests/test_manual.py new file mode 100644 index 00000000..1cd3b610 --- /dev/null +++ b/tests/test_manual.py @@ -0,0 +1,164 @@ +from pathlib import Path + +from acacore.database import FilesDB + +from digiarch.cli import app +from digiarch.common import AVID +from tests.conftest import run_click + + +# noinspection DuplicatedCode +def test_manual_extract(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + + with FilesDB(avid.database_path) as database: + parent_file = database.original_files.select( + "parent is null", + order_by=[("random()", "asc")], + limit=1, + ).fetchone() + assert parent_file + + children = database.original_files.select( + "parent is null and uuid != ?", + [str(parent_file.uuid)], + order_by=[("random()", "asc")], + limit=3, + ).fetchall() + extracted_child = children.pop() + assert children + assert extracted_child + + extracted_child.parent = extracted_child.uuid + database.original_files.update(extracted_child) + database.commit() + + extra_file_path: Path = avid.dirs.original_documents.joinpath("manual_extract.test") + extra_file_path.write_text("\n") + + run_click( + avid.path, + app, + "manual", + "extract", + parent_file.uuid, + *(c.get_absolute_path(avid.path) for c in children), + extracted_child.get_absolute_path(avid.path), + extra_file_path, + ) + + with FilesDB(avid.database_path) as database: + for child in children: + test_file = database.original_files[{"relative_path": str(child.relative_path)}] + assert test_file + assert test_file.parent == parent_file.uuid + + event = database.log.select( + "operation = ? and file_uuid = ?", + [f"{app.name}.manual.extract:edit", str(child.uuid)], + limit=1, + ).fetchone() + assert event + assert isinstance(event.data, dict) + assert event.data["parent"] == str(parent_file.uuid) + + test_file = database.original_files[{"relative_path": str(extracted_child.relative_path)}] + assert test_file + assert test_file.parent == extracted_child.uuid + + test_file = database.original_files[{"relative_path": str(extra_file_path.relative_to(avid.path))}] + assert test_file + assert test_file.parent == parent_file.uuid + event = database.log.select( + "operation = ? and file_uuid = ?", + [f"{app.name}.manual.extract:new", str(test_file.uuid)], + limit=1, + ).fetchone() + assert event + + +# noinspection DuplicatedCode +def test_manual_convert_master(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + + with FilesDB(avid.database_path) as database: + original_file = database.original_files.select(order_by=[("random()", "asc")], limit=1).fetchone() + assert original_file + + converted_file: Path = avid.dirs.master_documents.joinpath("converted.test") + converted_file.parent.mkdir(parents=True, exist_ok=True) + converted_file.write_text("\n") + + run_click(avid.path, app, "manual", "convert", original_file.uuid, "master", converted_file) + + with FilesDB(avid.database_path) as database: + test_file = database.master_files[{"relative_path": str(converted_file.relative_to(avid.path))}] + assert test_file + assert test_file.original_uuid == original_file.uuid + + event = database.log.select( + "operation = ? and file_uuid = ?", + [f"{app.name}.manual.convert:new", str(test_file.uuid)], + limit=1, + ).fetchone() + assert event + assert isinstance(event.data, dict) + assert event.data["original_uuid"] == str(original_file.uuid) + + +# noinspection DuplicatedCode +def test_manual_convert_access(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + + with FilesDB(avid.database_path) as database: + original_file = database.master_files.select(order_by=[("random()", "asc")], limit=1).fetchone() + assert original_file + + converted_file: Path = avid.dirs.access_documents.joinpath("converted.test") + converted_file.parent.mkdir(parents=True, exist_ok=True) + converted_file.write_text("\n") + + run_click(avid.path, app, "manual", "convert", original_file.uuid, "access", converted_file) + + with FilesDB(avid.database_path) as database: + test_file = database.access_files[{"relative_path": str(converted_file.relative_to(avid.path))}] + assert test_file + assert test_file.original_uuid == original_file.uuid + + event = database.log.select( + "operation = ? and file_uuid = ?", + [f"{app.name}.manual.convert:new", str(test_file.uuid)], + limit=1, + ).fetchone() + assert event + assert isinstance(event.data, dict) + assert event.data["original_uuid"] == str(original_file.uuid) + + +# noinspection DuplicatedCode +def test_manual_convert_statutory(avid_folder_copy: Path): + avid = AVID(avid_folder_copy) + + with FilesDB(avid.database_path) as database: + original_file = database.master_files.select(order_by=[("random()", "asc")], limit=1).fetchone() + assert original_file + + converted_file: Path = avid.dirs.documents.joinpath("converted.test") + converted_file.parent.mkdir(parents=True, exist_ok=True) + converted_file.write_text("\n") + + run_click(avid.path, app, "manual", "convert", original_file.uuid, "statutory", converted_file) + + with FilesDB(avid.database_path) as database: + test_file = database.statutory_files[{"relative_path": str(converted_file.relative_to(avid.path))}] + assert test_file + assert test_file.original_uuid == original_file.uuid + + event = database.log.select( + "operation = ? and file_uuid = ?", + [f"{app.name}.manual.convert:new", str(test_file.uuid)], + limit=1, + ).fetchone() + assert event + assert isinstance(event.data, dict) + assert event.data["original_uuid"] == str(original_file.uuid)