diff --git a/invenio_utilities_tuw/cli.py b/invenio_utilities_tuw/cli.py index 1a828e969dc0fe8781b5982ee6b9cd387ec3cca2..bb2e58b17436030338a7463417fa9f82b72127b8 100644 --- a/invenio_utilities_tuw/cli.py +++ b/invenio_utilities_tuw/cli.py @@ -2,7 +2,8 @@ import json import os -from os.path import basename +import sys +from os.path import basename, join, isfile, isdir import click from flask.cli import with_appcontext @@ -11,6 +12,7 @@ from invenio_access import any_user from invenio_access.utils import get_identity from invenio_accounts import current_accounts from invenio_pidstore.models import PersistentIdentifier +from invenio_rdm_records.records.models import DraftMetadata from invenio_rdm_records.services.services import ( BibliographicDraftFilesService as DraftFileService, ) @@ -52,6 +54,44 @@ def get_identity_for_user(user): return identity +def convert_to_recid(pid_value, pid_type): + if pid_type != "recid": + pid_value = ( + PersistentIdentifier.query.filter_by(pid_value=pid, pid_type=pid_type) + .first() + .pid_value + ) + + return pid_value + + +option_as_user = click.option( + "--as-user", + "-u", + "user", + metavar="USER", + default=None, + required=True, + help="email address of the user to use for record creation", +) +option_pid_type = click.option( + "--type", + "-t", + "pid_type", + metavar="PID_TYPE", + default="recid", + help="pid type (default: 'recid')", +) +option_pid_value = click.option( + "--pid", + "-p", + "pid", + metavar="PID_VALUE", + required=True, + help="persistent identifier of the record draft to operate on", +) + + @click.group() def utilities(): """Utility commands for InvenioRDM.""" @@ -64,16 +104,15 @@ def draft(): pass +@draft.group() +def files(): + """Manage files deposited with the draft.""" + pass + + @draft.command("create") @click.argument("metadata_path") -@click.option( - "--as-user", - "-u", - "user", - default=None, - metavar="USER", - help="email address of the user to use for record creation (default: the first user)", -) +@option_as_user @click.option( "--publish", "-p", @@ -87,71 +126,185 @@ def create_draft(metadata_path, publish, user): recid = None identity = get_identity_for_user(user) - if os.path.isfile(metadata_path): + if isfile(metadata_path): draft = create_record_from_metadata(metadata_path, identity) recid = draft["id"] - elif os.path.isdir(metadata_path): - metadata_file_path = os.path.join(metadata_path, "metadata.json") - deposit_files_path = os.path.join(metadata_path, "files") - if not os.path.isfile(metadata_file_path): + elif isdir(metadata_path): + metadata_file_path = join(metadata_path, "metadata.json") + deposit_files_path = join(metadata_path, "files") + if not isfile(metadata_file_path): raise Exception("metadata file does not exist: %s" % metadata_file_path) draft = create_record_from_metadata(metadata_file_path, identity) recid = draft["id"] file_names = [] - if os.path.isdir(deposit_files_path): - file_names = [os.path.basename(fn) for fn in os.listdir(deposit_files_path)] + if isdir(deposit_files_path): + dir_contents = os.listdir(deposit_files_path) + file_names = [basename(fn) for fn in dir_contents if isfile(join(deposit_files_path, fn))] + if len(dir_contents) != len(file_names): + ignored = [basename(fn) for fn in dir_contents if not isfile(join(deposit_files_path, fn))] + click.secho( + "ignored in '{}': {}".format(deposit_files_path, ignored), + fg="red", + err=True, + ) service = DraftFileService() - service.init_files(recid, identity, [{"key": fn} for fn in file_names]) + service.init_files( + id_=recid, identity=identity, data=[{"key": fn} for fn in file_names] + ) for fn in file_names: - file_path = os.path.join(deposit_files_path, fn) + file_path = join(deposit_files_path, fn) with open(file_path, "rb") as deposit_file: - service.set_file_content(recid, fn, identity, deposit_file) + service.set_file_content( + id_=recid, file_key=fn, identity=identity, stream=deposit_file + ) - service.commit_file(recid, fn, identity) + service.commit_file(id_=recid, file_key=fn, identity=identity) else: raise Exception("neither a file nor a directory: %s" % metadata_path) if publish: service = RecordService() - service.publish(recid, identity) + service.publish(id_=recid, identity=identity) + + click.secho(recid, fg="green") + + +@draft.command("list") +@option_as_user +@with_appcontext +def create_draft(user): + """List all drafts accessible to the given user.""" + identity = get_identity_for_user(user) + service = RecordService() + recids = [ + dm.json["id"] + for dm in DraftMetadata.query.all() + if dm is not None and dm.json is not None + ] + + for recid in recids: + try: + draft = service.read_draft(id_=recid, identity=identity) + click.secho( + "{} - {}".format(draft.id, draft.data["metadata"]["title"]), fg="green" + ) + except: + pass + - click.echo(recid) +@draft.command("delete") +@option_pid_value +@option_pid_type +@option_as_user +@with_appcontext +def publish_draft(pid, pid_type, user): + """Delete the specified draft.""" + pid = convert_to_recid(pid, pid_type) + identity = get_identity_for_user(user) + service = RecordService() + service.delete_draft(id_=pid, identity=identity) + click.secho(pid, fg="green") @draft.command("publish") -@click.argument("pid", metavar="PID_VALUE") -@click.option( - "--as-user", - "-u", - "user", - default=None, - metavar="USER", - help="email address of the user to use for record creation (default: the first user)", -) -@click.option( - "--type", - "-t", - "pid_type", - default="recid", - metavar="PID_TYPE", - help="pid type (default: 'recid')", -) +@option_pid_value +@option_pid_type +@option_as_user @with_appcontext def publish_draft(pid, pid_type, user): """Publish the specified draft.""" - if pid_type != "recid": - pid = ( - PersistentIdentifier.query.filter_by(pid_value=pid, pid_type=pid_type) - .first() - .pid_value - ) - pid_type = "recid" - + pid = convert_to_recid(pid, pid_type) identity = get_identity_for_user(user) service = RecordService() - service.publish(pid, identity) - click.echo(pid) + service.publish(id_=pid, identity=identity) + click.secho(pid, fg="green") + + +@files.command("add") +@click.argument("filepaths", metavar="PATH", type=click.Path(exists=True), nargs=-1) +@option_pid_value +@option_pid_type +@option_as_user +@with_appcontext +def add_files(filepaths, pid, pid_type, user): + """Add the specified files to the draft.""" + recid = convert_to_recid(pid, pid_type) + identity = get_identity_for_user(user) + service = DraftFileService() + + paths = [] + for file_path in filepaths: + if isdir(file_path): + # add all files (no recursion into sub-dirs) from the directory + dir_contents = os.listdir(file_path) + file_names = [ + basename(fn) for fn in dir_contents if isfile(join(file_path, fn)) + ] + if len(dir_contents) != len(file_names): + ignored = [ + basename(fn) + for fn in dir_contents + if not isfile(join(file_path, fn)) + ] + click.secho( + "ignored in '{}': {}".format(file_path, ignored), fg="red", err=True + ) + + paths_ = [join(file_path, fn) for fn in file_names] + paths.extend(paths_) + + elif isfile(file_path): + paths.append(file_path) + + keys = [basename(fp) for fp in paths] + if len(set(keys)) != len(keys): + click.secho("aborting: duplicates in file names detected", fg="red", err=True) + sys.exit(1) + + service.init_files( + id_=recid, identity=identity, data=[{"key": basename(fp)} for fp in paths] + ) + for fp in paths: + fn = basename(fp) + with open(file_path, "rb") as deposit_file: + service.set_file_content( + id_=recid, file_key=fn, identity=identity, stream=deposit_file + ) + service.commit_file(id_=recid, file_key=fn, identity=identity) + + click.secho(recid, fg="green") + + +@files.command("remove") +@click.argument("filekeys", metavar="FILE", nargs=-1) +@option_pid_value +@option_pid_type +@option_as_user +@with_appcontext +def remove_files(filekeys, pid, pid_type, user): + """Remove the deposited files.""" + recid = convert_to_recid(pid, pid_type) + identity = get_identity_for_user(user) + service = DraftFileService() + + for file_key in filekeys: + service.delete_file(id_=recid, file_key=file_key, identity=identity) + + +@files.command("list") +@option_pid_value +@option_pid_type +@option_as_user +@with_appcontext +def list_files(pid, pid_type, user): + """Show a list of files deposited with the draft.""" + recid = convert_to_recid(pid, pid_type) + identity = get_identity_for_user(user) + service = DraftFileService() + file_results = service.list_files(id_=recid, identity=identity) + for f in file_results.entries: + click.echo(f)