Add Clone command and fixes
This commit is contained in:
parent
d55fc63a41
commit
f0a7624b0f
|
@ -8,6 +8,7 @@ import click
|
||||||
from ordigi import constants, log, LOG
|
from ordigi import constants, log, LOG
|
||||||
from ordigi.collection import Collection
|
from ordigi.collection import Collection
|
||||||
from ordigi.geolocation import GeoLocation
|
from ordigi.geolocation import GeoLocation
|
||||||
|
from ordigi import utils
|
||||||
|
|
||||||
_logger_options = [
|
_logger_options = [
|
||||||
click.option(
|
click.option(
|
||||||
|
@ -173,7 +174,7 @@ def _check(**kwargs):
|
||||||
if summary.errors:
|
if summary.errors:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,6 +257,39 @@ def _clean(**kwargs):
|
||||||
sys.exit(1)
|
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')
|
@cli.command('compare')
|
||||||
@add_options(_logger_options)
|
@add_options(_logger_options)
|
||||||
@add_options(_dry_run_options)
|
@add_options(_dry_run_options)
|
||||||
|
|
|
@ -3,6 +3,7 @@ Collection methods.
|
||||||
"""
|
"""
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from distutils.dir_util import copy_tree
|
||||||
import filecmp
|
import filecmp
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
import os
|
import os
|
||||||
|
@ -299,6 +300,12 @@ class FileIO:
|
||||||
|
|
||||||
self.log.info(f'remove: {path}')
|
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):
|
def rmdir(self, directory):
|
||||||
if not self.dry_run:
|
if not self.dry_run:
|
||||||
directory.rmdir()
|
directory.rmdir()
|
||||||
|
@ -699,14 +706,8 @@ class Collection(SortMedias):
|
||||||
if not cli_options:
|
if not cli_options:
|
||||||
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.root = root
|
||||||
|
self.log = LOG.getChild(self.__class__.__name__)
|
||||||
|
|
||||||
# Get config options
|
# Get config options
|
||||||
self.opt = self.get_config_options()
|
self.opt = self.get_config_options()
|
||||||
|
@ -720,8 +721,11 @@ class Collection(SortMedias):
|
||||||
if not self.exclude:
|
if not self.exclude:
|
||||||
self.exclude = set()
|
self.exclude = set()
|
||||||
|
|
||||||
self.db = CollectionDb(root)
|
|
||||||
self.fileio = FileIO(self.opt['Terminal']['dry_run'])
|
self.fileio = FileIO(self.opt['Terminal']['dry_run'])
|
||||||
|
|
||||||
|
self.root_is_valid()
|
||||||
|
|
||||||
|
self.db = CollectionDb(root)
|
||||||
self.paths = Paths(
|
self.paths = Paths(
|
||||||
self.opt['Filters'],
|
self.opt['Filters'],
|
||||||
interactive=self.opt['Terminal']['interactive'],
|
interactive=self.opt['Terminal']['interactive'],
|
||||||
|
@ -749,6 +753,16 @@ class Collection(SortMedias):
|
||||||
self.summary = Summary(self.root)
|
self.summary = Summary(self.root)
|
||||||
self.theme = request.load_theme()
|
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):
|
def get_config_options(self):
|
||||||
"""Get collection config"""
|
"""Get collection config"""
|
||||||
config = Config(self.root.joinpath('.ordigi', 'ordigi.conf'))
|
config = Config(self.root.joinpath('.ordigi', 'ordigi.conf'))
|
||||||
|
@ -810,6 +824,14 @@ class Collection(SortMedias):
|
||||||
|
|
||||||
return True
|
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):
|
def _init_check_db(self, loc=None):
|
||||||
if self.db.sqlite.is_empty('metadata'):
|
if self.db.sqlite.is_empty('metadata'):
|
||||||
self.init(loc)
|
self.init(loc)
|
||||||
|
@ -817,6 +839,25 @@ class Collection(SortMedias):
|
||||||
self.log.error('Db data is not accurate run `ordigi update`')
|
self.log.error('Db data is not accurate run `ordigi update`')
|
||||||
sys.exit(1)
|
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):
|
def update(self, loc):
|
||||||
"""Update collection db"""
|
"""Update collection db"""
|
||||||
file_paths = list(self.get_collection_files())
|
file_paths = list(self.get_collection_files())
|
||||||
|
@ -866,7 +907,7 @@ class Collection(SortMedias):
|
||||||
if checksum == self.db.sqlite.get_checksum(relpath):
|
if checksum == self.db.sqlite.get_checksum(relpath):
|
||||||
self.summary.append('check', True, file_path)
|
self.summary.append('check', True, file_path)
|
||||||
else:
|
else:
|
||||||
self.log.error('{file_path} is corrupted')
|
self.log.error(f'{file_path} is corrupted')
|
||||||
self.summary.append('check', False, file_path)
|
self.summary.append('check', False, file_path)
|
||||||
|
|
||||||
return self.summary
|
return self.summary
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from shutil import copyfile
|
|
||||||
from time import strftime
|
|
||||||
|
|
||||||
from ordigi import constants
|
|
||||||
from ordigi.utils import distance_between_two_points
|
from ordigi.utils import distance_between_two_points
|
||||||
|
|
||||||
|
|
||||||
class Sqlite:
|
class Sqlite:
|
||||||
|
|
||||||
"""Methods for interacting with Sqlite database"""
|
"""Methods for interacting with Sqlite database"""
|
||||||
|
|
||||||
def __init__(self, target_dir):
|
def __init__(self, target_dir):
|
||||||
|
@ -30,7 +24,7 @@ class Sqlite:
|
||||||
self.db_type = 'SQLite format 3'
|
self.db_type = 'SQLite format 3'
|
||||||
self.types = {'text': (str, datetime), 'integer': (int,), 'real': (float,)}
|
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)
|
self.con = sqlite3.connect(self.filename)
|
||||||
# Allow selecting column by name
|
# Allow selecting column by name
|
||||||
self.con.row_factory = sqlite3.Row
|
self.con.row_factory = sqlite3.Row
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# import pandas as pd
|
# import pandas as pd
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
|
||||||
class Tables:
|
class Tables:
|
||||||
"""Create table and display result in Pandas DataFrame"""
|
"""Create table and display result in Pandas DataFrame"""
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ class Tables:
|
||||||
errors_headers = self.columns
|
errors_headers = self.columns
|
||||||
return tabulate(self.table, headers=errors_headers)
|
return tabulate(self.table, headers=errors_headers)
|
||||||
|
|
||||||
|
|
||||||
class Summary:
|
class Summary:
|
||||||
"""Result summary of ordigi program call"""
|
"""Result summary of ordigi program call"""
|
||||||
|
|
||||||
|
@ -61,7 +63,7 @@ class Summary:
|
||||||
self.errors_table.append(action, file_path, dest_path)
|
self.errors_table.append(action, file_path, dest_path)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
self.errors +=1
|
self.errors += 1
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
"""Print summary"""
|
"""Print summary"""
|
||||||
|
|
|
@ -38,6 +38,10 @@ def distance_between_two_points(lat1, lon1, lat2, lon2):
|
||||||
return rad * sqrt(x * x + y * y)
|
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):
|
def get_date_regex(user_regex=None):
|
||||||
"""Return date regex generator"""
|
"""Return date regex generator"""
|
||||||
if user_regex:
|
if user_regex:
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
|
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
import os
|
import os
|
||||||
import pytest
|
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import string
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from ordigi.config import Config
|
import pytest
|
||||||
|
|
||||||
from ordigi.exiftool import _ExifToolProc
|
from ordigi.exiftool import _ExifToolProc
|
||||||
|
|
||||||
ORDIGI_PATH = Path(__file__).parent.parent
|
ORDIGI_PATH = Path(__file__).parent.parent
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_singletons():
|
def reset_singletons():
|
||||||
""" Need to clean up any ExifTool singletons between tests """
|
""" Need to clean up any ExifTool singletons between tests """
|
||||||
|
@ -33,7 +33,6 @@ def sample_files_paths(tmpdir_factory):
|
||||||
|
|
||||||
def randomize_files(dest_dir):
|
def randomize_files(dest_dir):
|
||||||
# Get files randomly
|
# Get files randomly
|
||||||
paths = Path(dest_dir).glob('*')
|
|
||||||
for path, subdirs, files in os.walk(dest_dir):
|
for path, subdirs, files in os.walk(dest_dir):
|
||||||
if '.ordigi' in path:
|
if '.ordigi' in path:
|
||||||
continue
|
continue
|
||||||
|
@ -50,7 +49,7 @@ def randomize_files(dest_dir):
|
||||||
|
|
||||||
def randomize_db(dest_dir):
|
def randomize_db(dest_dir):
|
||||||
# alterate database
|
# 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:
|
with open(file_path, 'wb') as fout:
|
||||||
fout.write(os.urandom(random.randrange(128, 2048)))
|
fout.write(os.urandom(random.randrange(128, 2048)))
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,8 @@ class TestOrdigi:
|
||||||
for command in commands:
|
for command in commands:
|
||||||
self.assert_cli(command, ['not_exist'], state=1)
|
self.assert_cli(command, ['not_exist'], state=1)
|
||||||
|
|
||||||
|
self.assert_cli(cli._clone, ['not_exist'], state=2)
|
||||||
|
|
||||||
def test_sort(self):
|
def test_sort(self):
|
||||||
bool_options = (
|
bool_options = (
|
||||||
# '--interactive',
|
# '--interactive',
|
||||||
|
@ -97,6 +99,20 @@ class TestOrdigi:
|
||||||
self.assert_options(cli._sort, bool_options, arg_options, paths)
|
self.assert_options(cli._sort, bool_options, arg_options, paths)
|
||||||
self.assert_all_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):
|
def assert_init(self):
|
||||||
for opt, arg in self.logger_options:
|
for opt, arg in self.logger_options:
|
||||||
self.assert_cli(cli._init, [opt, arg, str(self.src_path)])
|
self.assert_cli(cli._init, [opt, arg, str(self.src_path)])
|
||||||
|
|
Loading…
Reference in New Issue