From f0a7624b0f23cc41edbd613ac6a903f50b3143d5 Mon Sep 17 00:00:00 2001 From: Cedric Leporcq Date: Sun, 5 Dec 2021 18:27:04 +0100 Subject: [PATCH] Add Clone command and fixes --- ordigi/cli.py | 36 ++++++++++++++++++++++++++- ordigi/collection.py | 59 +++++++++++++++++++++++++++++++++++++------- ordigi/database.py | 8 +----- ordigi/summary.py | 4 ++- ordigi/utils.py | 4 +++ tests/conftest.py | 9 +++---- tests/test_cli.py | 16 ++++++++++++ 7 files changed, 113 insertions(+), 23 deletions(-) diff --git a/ordigi/cli.py b/ordigi/cli.py index 41bb580..b9c8a85 100755 --- a/ordigi/cli.py +++ b/ordigi/cli.py @@ -8,6 +8,7 @@ import click from ordigi import constants, log, LOG from ordigi.collection import Collection from ordigi.geolocation import GeoLocation +from ordigi import utils _logger_options = [ click.option( @@ -173,7 +174,7 @@ def _check(**kwargs): if summary.errors: sys.exit(1) else: - LOG.logger.error('Db data is not accurate run `ordigi update`') + LOG.error('Db data is not accurate run `ordigi update`') sys.exit(1) @@ -256,6 +257,39 @@ def _clean(**kwargs): sys.exit(1) +@cli.command('clone') +@add_options(_logger_options) +@add_options(_dry_run_options) +@click.argument('src', required=True, nargs=1, type=click.Path()) +@click.argument('dest', required=True, nargs=1, type=click.Path()) +def _clone(**kwargs): + """Clone media collection to another location""" + + log_level = log.get_level(kwargs['verbose']) + log.console(LOG, level=log_level) + + src_path = Path(kwargs['src']).expanduser().absolute() + dest_path = Path(kwargs['dest']).expanduser().absolute() + + dry_run = kwargs['dry_run'] + + src_collection = Collection( + src_path, {'cache': True, 'dry_run': dry_run} + ) + + if dest_path.exists() and not utils.empty_dir(dest_path): + LOG.error(f'Destination collection path {dest_path} must be empty directory') + sys.exit(1) + + summary = src_collection.clone(dest_path) + + if log_level < 30: + summary.print() + + if summary.errors: + sys.exit(1) + + @cli.command('compare') @add_options(_logger_options) @add_options(_dry_run_options) diff --git a/ordigi/collection.py b/ordigi/collection.py index ef099c9..695463c 100644 --- a/ordigi/collection.py +++ b/ordigi/collection.py @@ -3,6 +3,7 @@ Collection methods. """ from copy import copy from datetime import datetime, timedelta +from distutils.dir_util import copy_tree import filecmp from fnmatch import fnmatch import os @@ -299,6 +300,12 @@ class FileIO: self.log.info(f'remove: {path}') + def mkdir(self, directory): + if not self.dry_run: + directory.mkdir(exist_ok=True) + + self.log.info(f'create dir: {directory}') + def rmdir(self, directory): if not self.dry_run: directory.rmdir() @@ -699,14 +706,8 @@ class Collection(SortMedias): if not cli_options: cli_options = {} - self.log = LOG.getChild(self.__class__.__name__) - - # Check if collection path is valid - if not root.exists(): - self.log.error(f'Collection path {root} does not exist') - sys.exit(1) - self.root = root + self.log = LOG.getChild(self.__class__.__name__) # Get config options self.opt = self.get_config_options() @@ -720,8 +721,11 @@ class Collection(SortMedias): if not self.exclude: self.exclude = set() - self.db = CollectionDb(root) self.fileio = FileIO(self.opt['Terminal']['dry_run']) + + self.root_is_valid() + + self.db = CollectionDb(root) self.paths = Paths( self.opt['Filters'], interactive=self.opt['Terminal']['interactive'], @@ -749,6 +753,16 @@ class Collection(SortMedias): self.summary = Summary(self.root) self.theme = request.load_theme() + def root_is_valid(self): + """Check if collection path is valid""" + if self.root.exists(): + if not self.root.is_dir(): + self.log.error(f'Collection path {self.root} is not a directory') + sys.exit(1) + else: + self.log.error(f'Collection path {self.root} does not exist') + sys.exit(1) + def get_config_options(self): """Get collection config""" config = Config(self.root.joinpath('.ordigi', 'ordigi.conf')) @@ -810,6 +824,14 @@ class Collection(SortMedias): return True + def check(self): + if self.db.sqlite.is_empty('metadata'): + self.log.error('Db data does not exist run `ordigi init`') + sys.exit(1) + elif not self.check_db(): + self.log.error('Db data is not accurate run `ordigi update`') + sys.exit(1) + def _init_check_db(self, loc=None): if self.db.sqlite.is_empty('metadata'): self.init(loc) @@ -817,6 +839,25 @@ class Collection(SortMedias): self.log.error('Db data is not accurate run `ordigi update`') sys.exit(1) + def clone(self, dest_path): + """Clone collection in another location""" + self.check() + + if not self.dry_run: + copy_tree(str(self.root), str(dest_path)) + + self.log.info(f'copy: {self.root} -> {dest_path}') + + if not self.dry_run: + dest_collection = Collection( + dest_path, {'cache': True, 'dry_run': self.dry_run} + ) + + if not dest_collection.check_db(): + self.summary.append('check', False) + + return self.summary + def update(self, loc): """Update collection db""" file_paths = list(self.get_collection_files()) @@ -866,7 +907,7 @@ class Collection(SortMedias): if checksum == self.db.sqlite.get_checksum(relpath): self.summary.append('check', True, file_path) else: - self.log.error('{file_path} is corrupted') + self.log.error(f'{file_path} is corrupted') self.summary.append('check', False, file_path) return self.summary diff --git a/ordigi/database.py b/ordigi/database.py index 91a239c..d347355 100644 --- a/ordigi/database.py +++ b/ordigi/database.py @@ -1,19 +1,13 @@ from datetime import datetime -import json import os from pathlib import Path import sqlite3 import sys -from shutil import copyfile -from time import strftime - -from ordigi import constants from ordigi.utils import distance_between_two_points class Sqlite: - """Methods for interacting with Sqlite database""" def __init__(self, target_dir): @@ -30,7 +24,7 @@ class Sqlite: self.db_type = 'SQLite format 3' self.types = {'text': (str, datetime), 'integer': (int,), 'real': (float,)} - self.filename = Path(db_dir, target_dir.name + '.db') + self.filename = Path(db_dir, 'collection.db') self.con = sqlite3.connect(self.filename) # Allow selecting column by name self.con.row_factory = sqlite3.Row diff --git a/ordigi/summary.py b/ordigi/summary.py index b40b224..c167ce3 100644 --- a/ordigi/summary.py +++ b/ordigi/summary.py @@ -1,6 +1,7 @@ # import pandas as pd from tabulate import tabulate + class Tables: """Create table and display result in Pandas DataFrame""" @@ -34,6 +35,7 @@ class Tables: errors_headers = self.columns return tabulate(self.table, headers=errors_headers) + class Summary: """Result summary of ordigi program call""" @@ -61,7 +63,7 @@ class Summary: self.errors_table.append(action, file_path, dest_path) if not success: - self.errors +=1 + self.errors += 1 def print(self): """Print summary""" diff --git a/ordigi/utils.py b/ordigi/utils.py index 63ae4fe..4bc1c37 100644 --- a/ordigi/utils.py +++ b/ordigi/utils.py @@ -38,6 +38,10 @@ def distance_between_two_points(lat1, lon1, lat2, lon2): return rad * sqrt(x * x + y * y) +def empty_dir(dir_path): + return not next(os.scandir(dir_path), None) + + def get_date_regex(user_regex=None): """Return date regex generator""" if user_regex: diff --git a/tests/conftest.py b/tests/conftest.py index 27cd8ab..06af8db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,18 +2,18 @@ from configparser import RawConfigParser import os -import pytest from pathlib import Path, PurePath import random import shutil -import string import tempfile -from ordigi.config import Config +import pytest + from ordigi.exiftool import _ExifToolProc ORDIGI_PATH = Path(__file__).parent.parent + @pytest.fixture(autouse=True) def reset_singletons(): """ Need to clean up any ExifTool singletons between tests """ @@ -33,7 +33,6 @@ def sample_files_paths(tmpdir_factory): def randomize_files(dest_dir): # Get files randomly - paths = Path(dest_dir).glob('*') for path, subdirs, files in os.walk(dest_dir): if '.ordigi' in path: continue @@ -50,7 +49,7 @@ def randomize_files(dest_dir): def randomize_db(dest_dir): # alterate database - file_path = Path(str(dest_dir), '.ordigi', str(dest_dir.name) + '.db') + file_path = Path(str(dest_dir), '.ordigi', 'collection.db') with open(file_path, 'wb') as fout: fout.write(os.urandom(random.randrange(128, 2048))) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5824d9d..db2d0e9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -72,6 +72,8 @@ class TestOrdigi: for command in commands: self.assert_cli(command, ['not_exist'], state=1) + self.assert_cli(cli._clone, ['not_exist'], state=2) + def test_sort(self): bool_options = ( # '--interactive', @@ -97,6 +99,20 @@ class TestOrdigi: self.assert_options(cli._sort, bool_options, arg_options, paths) self.assert_all_options(cli._sort, bool_options, arg_options, paths) + def test_clone(self, tmp_path): + + arg_options = ( + *self.logger_options, + + ) + + paths = (str(self.src_path), str(tmp_path)) + + self.assert_cli(cli._init, [str(self.src_path)]) + self.assert_cli(cli._clone, ['--dry-run', '--verbose', 'DEBUG', *paths]) + self.assert_cli(cli._clone, paths) + + def assert_init(self): for opt, arg in self.logger_options: self.assert_cli(cli._init, [opt, arg, str(self.src_path)])