From 164a5b61681f09bee7054cb954cb70359e1878a7 Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Wed, 28 Feb 2024 17:10:07 +0100 Subject: [PATCH] Add shares export script, better notification for shares and moderate refactor --- l10n/fr.js | 2 +- l10n/fr.json | 2 +- lib/Exceptions/TooManyUserEmail.php | 35 +++++++ lib/Migration/Notifier.php | 2 +- lib/Migration/Utils.php | 3 +- scripts/shares_export.py | 149 ++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 lib/Exceptions/TooManyUserEmail.php create mode 100644 scripts/shares_export.py diff --git a/l10n/fr.js b/l10n/fr.js index a9c14a4..0fa69e4 100644 --- a/l10n/fr.js +++ b/l10n/fr.js @@ -18,6 +18,6 @@ OC.L10N.register( "Shares migration scheduled": "Migration des partages planifiée", "Shares migration started": "Vos partages seront bientôt disponibles", "Shares migration complete": "Migration des partages terminée", - "%s shares migrated, waiting for %s users, %s missing files, %s errors": "%s partages migrés, attente de %s utilisateurs, %s fichiers manquants, %s erreurs" + "%s shares migrated, %s shares waiting for users, %s missing files, %s errors": "%s partages migrés, %s partages en attente d'utilisateurs, %s fichiers manquants, %s erreurs" }, "nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"); diff --git a/l10n/fr.json b/l10n/fr.json index 170f7c3..e7582ee 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -16,6 +16,6 @@ "Shares migration scheduled": "Migration des partages planifiée", "Shares migration started": "Vos partages seront bientôt disponibles", "Shares migration complete": "Migration des partages terminée", - "%s shares migrated, waiting for %s users, %s missing files, %s errors": "%s partages migrés, attente de %s utilisateurs, %s fichiers manquants, %s erreurs" + "%s shares migrated, %s shares waiting for users, %s missing files, %s errors": "%s partages migrés, %s partages en attente d'utilisateurs, %s fichiers manquants, %s erreurs" },"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" } \ No newline at end of file diff --git a/lib/Exceptions/TooManyUserEmail.php b/lib/Exceptions/TooManyUserEmail.php new file mode 100644 index 0000000..f61a3aa --- /dev/null +++ b/lib/Exceptions/TooManyUserEmail.php @@ -0,0 +1,35 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\FirstRunMigrate\Exceptions; + +class TooManyUserEmail extends \Exception { + protected string $email; + + public function __construct(string $email) { + parent::__construct("Multiples users with the same email: '$email'"); + $this->email = $email; + } + + public function getEmail() : string { + return $this->email; + } +} diff --git a/lib/Migration/Notifier.php b/lib/Migration/Notifier.php index 308acb6..5d128cb 100644 --- a/lib/Migration/Notifier.php +++ b/lib/Migration/Notifier.php @@ -128,7 +128,7 @@ class Notifier implements INotifier { break; case 'share_finished': $subject = $l->t('Shares migration complete'); - $message = $l->t('%s shares migrated, waiting for %s users, %s missing files, %s errors', + $message = $l->t('%s shares migrated, %s shares waiting for users, %s missing files, %s errors', $notify_parameters); break; default: diff --git a/lib/Migration/Utils.php b/lib/Migration/Utils.php index 29a0306..a8d3309 100644 --- a/lib/Migration/Utils.php +++ b/lib/Migration/Utils.php @@ -6,6 +6,7 @@ use Exception; use OCP\IConfig; use OCP\IUser; use OCA\FirstRunMigrate\AppInfo\Application; +use OCA\FirstRunMigrate\Exceptions\TooManyUserEmail; use OCP\IUserManager; use OCP\Notification\IManager as INotificationManager; @@ -60,7 +61,7 @@ class Utils { } if (count($users) > 1) { - throw new \Exception("Multiples users with the same email"); + throw new TooManyUserEmail($id); } return $users[0]; diff --git a/scripts/shares_export.py b/scripts/shares_export.py new file mode 100644 index 0000000..6f996eb --- /dev/null +++ b/scripts/shares_export.py @@ -0,0 +1,149 @@ +from json import loads, dump +from subprocess import run, PIPE +from argparse import ArgumentParser +from pathlib import Path + + +def main(path : str, php :str, output_shares_path : str): + output_shares = Path(output_shares_path) + + out = run([php, "occ", "config:list", "--private", "--output", "json"], cwd=path, check=True, + stdout=PIPE, stderr=PIPE) + config = loads(out.stdout.decode().strip()) + + db_type = config["system"]["dbtype"] + db_socket = None + db_host, db_port = config["system"]["dbhost"].split(":") + if db_port and not db_port.isdigit(): + db_socket = db_port + db_port = None + elif not db_port: + db_port = config["system"]["dbport"] + db_user = config["system"]["dbuser"] + db_password = config["system"]["dbpassword"] + db_name = config["system"]["dbname"] + db_prefix = config["system"]["dbtableprefix"] + + shares = [] + if db_type == "mysql": + query = f"""SELECT + JSON_ARRAYAGG( + JSON_OBJECT( + 'type', s.share_type, + 'permissions', s.permissions, + 'by', p_initiator.configvalue, + 'owner', p_owner.configvalue, + 'path', REGEXP_REPLACE(fc.path, '^files/', ''), + 'target', s.file_target, + 'with', CASE + WHEN s.share_type = 0 + THEN p_with.configvalue + WHEN s.share_type = 1 + THEN s.share_with + ELSE null + END, + 'attributes', s.attributes, + 'expiration', s.expiration, + 'note', s.note, + 'accepted', s.accepted, + 'group_users_accepted', CASE + WHEN s.share_type = 1 + THEN ( + SELECT + JSON_OBJECTAGG( + ss_with.configvalue, + JSON_OBJECT( + 'accepted', ss.accepted, + 'target', ss.file_target + ) + ) + FROM {db_prefix}share ss + INNER JOIN {db_prefix}preferences ss_with ON ss_with.userid = ss.share_with AND ss_with.appid = 'settings' AND ss_with.configkey = 'email' + WHERE ss.parent = s.id + ) + ELSE null + END + ) + ) +FROM {db_prefix}share s +INNER JOIN {db_prefix}preferences p_initiator ON p_initiator.userid = s.uid_initiator AND p_initiator.appid = 'settings' AND p_initiator.configkey = 'email' +INNER JOIN {db_prefix}preferences p_owner ON p_owner.userid = s.uid_owner AND p_owner.appid = 'settings' AND p_owner.configkey = 'email' +INNER JOIN {db_prefix}filecache fc ON fc.fileid = file_source +LEFT JOIN {db_prefix}preferences p_with ON p_with.userid = s.share_with AND p_with.appid = 'settings' AND p_with.configkey = 'email' +WHERE s.share_type IN (0, 1); +""" + args = ["mysql", "-B", "-r", "--disable-column-names", "-h", db_host] + if db_port: + args += ["-P", db_port] + elif db_socket: + args += ["-S", db_socket] + args += ["-u", db_user, f"-p{db_password}", "-e", query, db_name] + + out = run(args, cwd=path, check=True, stdout=PIPE, text=True) + shares = loads(out.stdout) + elif db_type == "psql": + query = f"""SELECT + jsonb_agg( + jsonb_build_object( + 'type', s.share_type, + 'permissions', s.permissions, + 'by', p_initiator.configvalue, + 'owner', p_owner.configvalue, + 'path', regexp_replace(fc.path, '^files/', ''), + 'target', s.file_target, + 'with', CASE + WHEN s.share_type = 0 + THEN p_with.configvalue + WHEN s.share_type = 1 + THEN s.share_with + ELSE null + END, + 'attributes', s.attributes, + 'expiration', s.expiration, + 'note', s.note, + 'accepted', s.accepted, + 'group_users_accepted', CASE + WHEN s.share_type = 1 + THEN ( + SELECT + JSONB_OBJECT_AGG( + ss_with.configvalue, + jsonb_build_object( + 'accepted', ss.accepted, + 'target', ss.file_target + ) + ) + FROM {db_prefix}share ss + INNER JOIN {db_prefix}preferences ss_with ON ss_with.userid = ss.share_with AND ss_with.appid = 'settings' AND ss_with.configkey = 'email' + WHERE ss.parent = s.id + ) + ELSE null + END + ) + ) +FROM {db_prefix}share s +INNER JOIN {db_prefix}preferences p_initiator ON p_initiator.userid = s.uid_initiator AND p_initiator.appid = 'settings' AND p_initiator.configkey = 'email' +INNER JOIN {db_prefix}preferences p_owner ON p_owner.userid = s.uid_owner AND p_owner.appid = 'settings' AND p_owner.configkey = 'email' +INNER JOIN {db_prefix}filecache fc ON fc.fileid = file_source +LEFT JOIN {db_prefix}preferences p_with ON p_with.userid = s.share_with AND p_with.appid = 'settings' AND p_with.configkey = 'email' +WHERE s.share_type IN (0, 1); +""" + pass + elif db_type == "sqlite": + pass + + print(f"Got {len(shares)} shares") + + with output_shares.open("w", encoding="UTF-8") as file: + dump(shares, file) + + +if __name__ == '__main__': + parser = ArgumentParser(prog="shares_export") + parser.add_argument("--path", "-p", default="/var/www/nextcloud") + parser.add_argument("--php", "-P", default="php") + parser.add_argument("--output-shares", "-s", default="shares.json") + + args = parser.parse_args() + + main(args.path, args.php, args.output_shares)