#!/usr/bin/env python3 # Libervia: an XMPP client # Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org) # 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 . from pathlib import Path import shutil from libervia.backend.core.constants import Const as C from .core import ( DB_FILE, DbDriver, FILES_FILE_TPL, GlobalHandler, Handler, ProfileHandler, Report, Staging, get_db_driver, sqlite_db_config, ) from .gpg import GpgHandler from .pie import ParamsHandler, PepHandler, PrivateHandler, RosterHandler class FilesHandler(GlobalHandler): name = "files" def export( self, local_dir: Path, staging: Staging, report: Report, profiles: list[str] | None = None, ) -> None: """Copy the file sharing blobs to the staging @param local_dir: local data directory @param staging: staging where the blobs are copied @param report: export report """ files_dir = local_dir / C.FILES_DIR if not files_dir.is_dir(): return count = 0 for path in sorted(files_dir.rglob("*")): if path.is_file(): staging.add_file( FILES_FILE_TPL.format(path=path.relative_to(files_dir).as_posix()), path, 0o600, ) count += 1 if count: report.done.append(f"files: {count} file(s)") def import_( self, local_dir: Path, staging: Staging, report: Report, profiles: list[str] | None = None, force: bool = False, ) -> None: """Copy the staged file sharing blobs to the local data directory The blobs are content-addressed (the name is the content hash), so overwriting an existing file is safe and repairs a corrupted one. @param local_dir: local data directory @param staging: staging holding the blobs @param report: import report """ files_dir = local_dir / C.FILES_DIR # entry name prefix, derived from the staging name template prefix = FILES_FILE_TPL.split("{}")[0] count = 0 for name in staging.names(): if not name.startswith(prefix): continue dst = files_dir / name[len(prefix) :] dst.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(staging.path(name), dst) count += 1 if count: report.done.append(f"files: {count} file(s)") class DbHandler(GlobalHandler): name = "db" def _driver(self, local_dir: Path) -> DbDriver: return get_db_driver(sqlite_db_config(local_dir)) def export( self, local_dir: Path, staging: Staging, report: Report, profiles: list[str] | None = None, ) -> None: """Write a snapshot of the database to the staging @param report: export report """ self._driver(local_dir).snapshot(staging) def import_( self, local_dir: Path, staging: Staging, report: Report, profiles: list[str] | None = None, force: bool = False, ) -> None: """Restore the database snapshot from the staging (destructive) @param report: import report @param force: the restore is destructive, it requires force """ if DB_FILE not in staging: return res = self._driver(local_dir).restore(staging, force) report.done.extend(res.done) report.skipped.extend(res.skipped) # one handler per data type, in export order (the order is semantic: the database restore # comes last, it replaces the database) REGISTRY: list[Handler] = [ ParamsHandler(), PrivateHandler(), RosterHandler(), PepHandler(), GpgHandler(), FilesHandler(), DbHandler(), ] # "db" is a non-portable copy of the database (it will be migrated automatically on start # if the Libervia version is newer): it covers all the data, while the portable types # don't cover everything yet. DATA_TYPES: tuple[str, ...] = tuple(handler.name for handler in REGISTRY) # data types stored in the database: a database restore brings them back, so # there is no need to import them on top of it DB_BACKED_TYPES: tuple[str, ...] = tuple( handler.name for handler in REGISTRY if isinstance(handler, ProfileHandler) and handler.uses_db )