Refactoring collection options (2)
This commit is contained in:
parent
0fdf09ea42
commit
27af9bb55e
17
ordigi.conf
17
ordigi.conf
|
@ -1,3 +1,11 @@
|
||||||
|
[Filters]
|
||||||
|
exclude= ["**/.directory", "**/.DS_Store"]
|
||||||
|
|
||||||
|
[Geolocation]
|
||||||
|
geocoder=Nominatim
|
||||||
|
prefer_english_names=hjkh
|
||||||
|
timeout=1
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
# day_begins: what hour of the day you want the day to begin (only for
|
# day_begins: what hour of the day you want the day to begin (only for
|
||||||
# classification purposes). Defaults at 0 as midnight. Can be
|
# classification purposes). Defaults at 0 as midnight. Can be
|
||||||
|
@ -5,14 +13,7 @@
|
||||||
# be a number between 0-23')
|
# be a number between 0-23')
|
||||||
day_begins=4
|
day_begins=4
|
||||||
|
|
||||||
|
# Path format
|
||||||
dirs_path={%Y}/{%m-%b}-{city}-{folder}
|
dirs_path={%Y}/{%m-%b}-{city}-{folder}
|
||||||
name={%Y%m%d-%H%M%S}-%u{original_name}|%u{basename}.%l{ext}
|
name={%Y%m%d-%H%M%S}-%u{original_name}|%u{basename}.%l{ext}
|
||||||
|
|
||||||
[Exclusions]
|
|
||||||
path1=**/.directory
|
|
||||||
path2=**/.DS_Store
|
|
||||||
|
|
||||||
[Geolocation]
|
|
||||||
geocoder=Nominatim
|
|
||||||
prefer_english_names=False
|
|
||||||
# timeout=1
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import sys
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from ordigi import constants, log, LOG
|
from ordigi import constants, log, LOG
|
||||||
from ordigi.config import Config
|
|
||||||
from ordigi.collection import Collection
|
from ordigi.collection import Collection
|
||||||
from ordigi.geolocation import GeoLocation
|
from ordigi.geolocation import GeoLocation
|
||||||
|
|
||||||
|
@ -116,13 +115,6 @@ def add_options(options):
|
||||||
return _add_options
|
return _add_options
|
||||||
|
|
||||||
|
|
||||||
def _get_exclude(opt, exclude):
|
|
||||||
# if no exclude list was passed in we check if there's a config
|
|
||||||
if len(exclude) == 0:
|
|
||||||
exclude = opt['exclude']
|
|
||||||
return set(exclude)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_paths(paths, root):
|
def _get_paths(paths, root):
|
||||||
root = Path(root).expanduser().absolute()
|
root = Path(root).expanduser().absolute()
|
||||||
if not paths:
|
if not paths:
|
||||||
|
|
|
@ -14,6 +14,7 @@ from pathlib import Path, PurePath
|
||||||
import inquirer
|
import inquirer
|
||||||
|
|
||||||
from ordigi import LOG
|
from ordigi import LOG
|
||||||
|
from ordigi.config import Config
|
||||||
from ordigi.database import Sqlite
|
from ordigi.database import Sqlite
|
||||||
from ordigi.media import Medias
|
from ordigi.media import Medias
|
||||||
from ordigi.images import Image, Images
|
from ordigi.images import Image, Images
|
||||||
|
@ -220,6 +221,9 @@ class FPath:
|
||||||
Returns file path.
|
Returns file path.
|
||||||
"""
|
"""
|
||||||
path_format = self.path_format
|
path_format = self.path_format
|
||||||
|
|
||||||
|
# Each element in the list represents a folder.
|
||||||
|
# Fallback folders are supported and are nested lists.
|
||||||
path = []
|
path = []
|
||||||
path_parts = path_format.split('/')
|
path_parts = path_format.split('/')
|
||||||
for path_part in path_parts:
|
for path_part in path_parts:
|
||||||
|
@ -706,50 +710,58 @@ class Collection(SortMedias):
|
||||||
use_file_dates=False,
|
use_file_dates=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
day_begins=0
|
# TODO move to cli
|
||||||
max_deep=None
|
cli_options = {
|
||||||
|
'album_from_folder': album_from_folder,
|
||||||
|
'cache': cache,
|
||||||
|
'dry_run': dry_run,
|
||||||
|
'exclude': exclude,
|
||||||
|
'extensions': extensions,
|
||||||
|
'glob': '**/*',
|
||||||
|
'interactive': interactive,
|
||||||
|
'ignore_tags': ignore_tags,
|
||||||
|
'use_date_filename': use_date_filename,
|
||||||
|
'use_file_dates': use_file_dates,
|
||||||
|
}
|
||||||
|
|
||||||
# Options
|
|
||||||
self.day_begins = day_begins
|
|
||||||
self.log = LOG.getChild(self.__class__.__name__)
|
self.log = LOG.getChild(self.__class__.__name__)
|
||||||
self.glob = glob
|
|
||||||
|
|
||||||
# Check if collection path is valid
|
# Check if collection path is valid
|
||||||
if not root.exists():
|
if not root.exists():
|
||||||
self.log.error(f'Collection path {root} does not exist')
|
self.log.error(f'Collection path {root} does not exist')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# def get_collection_config(root):
|
self.root = root
|
||||||
# return Config(root.joinpath('.ordigi', 'ordigi.conf'))
|
|
||||||
|
|
||||||
# TODO Collection options
|
# Get config options
|
||||||
# config = get_collection_config(root)
|
config = self.get_config()
|
||||||
# opt = config.get_options()
|
self.opt = config.get_options()
|
||||||
# exclude = _get_exclude(opt, kwargs['exclude'])
|
|
||||||
# path_format = opt['path_format']
|
# Set client options
|
||||||
# if kwargs['path_format']:
|
for option, value in cli_options.items():
|
||||||
# path_format = kwargs['path_format']
|
self.opt[option] = value
|
||||||
|
self._set_cli_option('exclude', exclude)
|
||||||
|
|
||||||
self.db = CollectionDb(root)
|
self.db = CollectionDb(root)
|
||||||
self.fileio = FileIO(dry_run)
|
self.fileio = FileIO(self.opt['dry_run'])
|
||||||
self.paths = Paths(
|
self.paths = Paths(
|
||||||
exclude,
|
self.opt['exclude'],
|
||||||
extensions,
|
self.opt['extensions'],
|
||||||
glob,
|
self.opt['glob'],
|
||||||
interactive,
|
self.opt['interactive'],
|
||||||
max_deep,
|
self.opt['max_deep'],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.medias = Medias(
|
self.medias = Medias(
|
||||||
self.paths,
|
self.paths,
|
||||||
root,
|
root,
|
||||||
album_from_folder,
|
self.opt['album_from_folder'],
|
||||||
cache,
|
self.opt['cache'],
|
||||||
self.db,
|
self.db,
|
||||||
interactive,
|
self.opt['interactive'],
|
||||||
ignore_tags,
|
self.opt['ignore_tags'],
|
||||||
use_date_filename,
|
self.opt['use_date_filename'],
|
||||||
use_file_dates,
|
self.opt['use_file_dates'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
@ -758,21 +770,30 @@ class Collection(SortMedias):
|
||||||
self.medias,
|
self.medias,
|
||||||
root,
|
root,
|
||||||
self.db,
|
self.db,
|
||||||
dry_run,
|
self.opt['dry_run'],
|
||||||
interactive,
|
self.opt['interactive'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
self.summary = Summary(self.root)
|
self.summary = Summary(self.root)
|
||||||
self.theme = request.load_theme()
|
self.theme = request.load_theme()
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
"""Get collection config"""
|
||||||
|
return Config(self.root.joinpath('.ordigi', 'ordigi.conf'))
|
||||||
|
|
||||||
|
def _set_cli_option(self, option, cli_option):
|
||||||
|
"""if client option is set overwrite collection option value"""
|
||||||
|
if cli_option:
|
||||||
|
self.opt['option'] = cli_option
|
||||||
|
|
||||||
def get_collection_files(self, exclude=True):
|
def get_collection_files(self, exclude=True):
|
||||||
if exclude:
|
if exclude:
|
||||||
exclude = self.paths.exclude
|
exclude = self.paths.exclude
|
||||||
|
|
||||||
paths = Paths(
|
paths = Paths(
|
||||||
exclude,
|
exclude,
|
||||||
interactive=self.interactive,
|
interactive=self.opt['interactive'],
|
||||||
)
|
)
|
||||||
for file_path in paths.get_files(self.root):
|
for file_path in paths.get_files(self.root):
|
||||||
yield file_path
|
yield file_path
|
||||||
|
@ -932,7 +953,7 @@ class Collection(SortMedias):
|
||||||
files = os.listdir(directory)
|
files = os.listdir(directory)
|
||||||
if len(files) == 0 and remove_root:
|
if len(files) == 0 and remove_root:
|
||||||
self.log.info(f"Removing empty folder: {directory}")
|
self.log.info(f"Removing empty folder: {directory}")
|
||||||
if not self.dry_run:
|
if not self.opt['dry_run']:
|
||||||
os.rmdir(directory)
|
os.rmdir(directory)
|
||||||
self.summary.append('remove', True, directory)
|
self.summary.append('remove', True, directory)
|
||||||
|
|
||||||
|
@ -948,11 +969,14 @@ class Collection(SortMedias):
|
||||||
# Check db
|
# Check db
|
||||||
self._init_check_db(loc)
|
self._init_check_db(loc)
|
||||||
|
|
||||||
|
# if path format client option is set overwrite it
|
||||||
|
self._set_cli_option('path_format', path_format)
|
||||||
|
|
||||||
# Get medias data
|
# Get medias data
|
||||||
subdirs = set()
|
subdirs = set()
|
||||||
for src_path, metadata in self.medias.get_metadatas(src_dirs, imp=imp, loc=loc):
|
for src_path, metadata in self.medias.get_metadatas(src_dirs, imp=imp, loc=loc):
|
||||||
# Get the destination path according to metadata
|
# Get the destination path according to metadata
|
||||||
fpath = FPath(path_format, self.day_begins)
|
fpath = FPath(path_format, self.opt['day_begins'])
|
||||||
metadata['file_path'] = fpath.get_path(metadata)
|
metadata['file_path'] = fpath.get_path(metadata)
|
||||||
subdirs.add(src_path.parent)
|
subdirs.add(src_path.parent)
|
||||||
|
|
||||||
|
|
191
ordigi/config.py
191
ordigi/config.py
|
@ -1,15 +1,80 @@
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
from os import path
|
from os import path
|
||||||
from ordigi import constants
|
from ordigi import constants
|
||||||
from geopy.geocoders import options as gopt
|
from geopy.geocoders import options as gopt
|
||||||
|
|
||||||
|
|
||||||
|
# TODO make one method???
|
||||||
|
def check_option(getoption):
|
||||||
|
"""Check option type int or boolean"""
|
||||||
|
try:
|
||||||
|
getoption
|
||||||
|
except ValueError as e:
|
||||||
|
# TODO
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return getoption
|
||||||
|
|
||||||
|
def check_json(getoption):
|
||||||
|
"""Check if json string is valid"""
|
||||||
|
try:
|
||||||
|
getoption
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
# TODO
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return getoption
|
||||||
|
|
||||||
|
def check_re(getoption):
|
||||||
|
"""Check if regex string is valid"""
|
||||||
|
try:
|
||||||
|
getoption
|
||||||
|
except re.error as e:
|
||||||
|
# TODO
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return getoption
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
"""Manage config file"""
|
"""Manage config file"""
|
||||||
|
|
||||||
def __init__(self, conf_path=constants.CONFIG_FILE, conf={}):
|
# Initialize with default options
|
||||||
|
options: dict = {
|
||||||
|
'Console': {
|
||||||
|
'dry_run': False,
|
||||||
|
'interactive': False,
|
||||||
|
},
|
||||||
|
'Database': {
|
||||||
|
'cache': False,
|
||||||
|
'album_from_folder': False,
|
||||||
|
'ignore_tags': None,
|
||||||
|
'use_date_filename': False,
|
||||||
|
'use_file_dates': False,
|
||||||
|
},
|
||||||
|
'Filters': {
|
||||||
|
'exclude': None,
|
||||||
|
'extensions': None,
|
||||||
|
'glob': '**/*',
|
||||||
|
'max_deep': None,
|
||||||
|
},
|
||||||
|
'Geolocation': {
|
||||||
|
'geocoder': constants.DEFAULT_GEOCODER,
|
||||||
|
'prefer_english_names': False,
|
||||||
|
'timeout': gopt.default_timeout,
|
||||||
|
},
|
||||||
|
'Path': {
|
||||||
|
'day_begins': 0,
|
||||||
|
'path_format': constants.DEFAULT_PATH_FORMAT,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, conf_path=constants.CONFIG_FILE, conf=None):
|
||||||
self.conf_path = conf_path
|
self.conf_path = conf_path
|
||||||
if conf == {}:
|
if conf is None:
|
||||||
self.conf = self.load_config()
|
self.conf = self.load_config()
|
||||||
if self.conf == {}:
|
if self.conf == {}:
|
||||||
# Fallback to default config
|
# Fallback to default config
|
||||||
|
@ -33,66 +98,94 @@ class Config:
|
||||||
conf.read(self.conf_path)
|
conf.read(self.conf_path)
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
def get_option(self, option, section):
|
def is_option(self, section, option):
|
||||||
|
"""Get ConfigParser options"""
|
||||||
if section in self.conf and option in self.conf[section]:
|
if section in self.conf and option in self.conf[section]:
|
||||||
return self.conf[section][option]
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_path_definition(self):
|
@check_option
|
||||||
"""Returns a list of folder definitions.
|
def _getboolean(self, section, option):
|
||||||
|
return self.conf.getboolean(section, option)
|
||||||
|
getboolean = check_option(_getboolean)
|
||||||
|
|
||||||
Each element in the list represents a folder.
|
@check_option
|
||||||
Fallback folders are supported and are nested lists.
|
def _getint(self, section, option):
|
||||||
|
return self.conf.getint(section, option)
|
||||||
|
getint = check_option(_getint)
|
||||||
|
|
||||||
:returns: string
|
@check_json
|
||||||
"""
|
def _getjson(self, section, option):
|
||||||
|
return json.loads(self.conf.get(section, option))
|
||||||
|
getjson = check_json(_getjson)
|
||||||
|
|
||||||
if 'Path' in self.conf:
|
@check_re
|
||||||
if 'format' in self.conf['Path']:
|
def _getre(self, section, option):
|
||||||
return self.conf['Path']['format']
|
return re.compile(self.conf.get(section, option))
|
||||||
elif 'dirs_path' and 'name' in self.conf['Path']:
|
getre = check_re(_getre)
|
||||||
return self.conf['Path']['dirs_path'] + '/' + self.conf['Path']['name']
|
|
||||||
|
|
||||||
return constants.DEFAULT_PATH_FORMAT
|
def get_option(self, section, option):
|
||||||
|
bool_options = {
|
||||||
|
'cache',
|
||||||
|
'dry_run',
|
||||||
|
'prefer_english_names',
|
||||||
|
'album_from_folder',
|
||||||
|
'interactive',
|
||||||
|
'use_date_filename',
|
||||||
|
'use_file_dates',
|
||||||
|
}
|
||||||
|
|
||||||
def get_options(self):
|
int_options = {
|
||||||
"""Get config options
|
'day_begins',
|
||||||
:returns: dict
|
'max_deep',
|
||||||
"""
|
'timeout',
|
||||||
|
}
|
||||||
|
|
||||||
options = {}
|
string_options = {
|
||||||
geocoder = self.get_option('geocoder', 'Geolocation')
|
'glob',
|
||||||
if geocoder and geocoder in ('Nominatim',):
|
'geocoder',
|
||||||
options['geocoder'] = geocoder
|
}
|
||||||
else:
|
|
||||||
options['geocoder'] = constants.DEFAULT_GEOCODER
|
|
||||||
|
|
||||||
prefer_english_names = self.get_option('prefer_english_names', 'Geolocation')
|
multi_options = {
|
||||||
if prefer_english_names:
|
'exclude',
|
||||||
options['prefer_english_names'] = bool(prefer_english_names)
|
'extensions',
|
||||||
else:
|
'ignore_tags',
|
||||||
options['prefer_english_names'] = False
|
}
|
||||||
|
|
||||||
timeout = self.get_option('timeout', 'Geolocation')
|
value = self.options[section][option]
|
||||||
if timeout:
|
if self.is_option(section, option):
|
||||||
options['timeout'] = timeout
|
if option in bool_options:
|
||||||
else:
|
return self.getboolean(section, option)
|
||||||
options['timeout'] = gopt.default_timeout
|
if option in int_options:
|
||||||
|
return self.getint(section, option)
|
||||||
|
if option == 'geocoder' and value in ('Nominatim',):
|
||||||
|
return self.conf[section][option]
|
||||||
|
if option == 'glob':
|
||||||
|
return self.conf[section][option]
|
||||||
|
if option == 'path_format':
|
||||||
|
return self.getre(section, option)
|
||||||
|
if option in multi_options:
|
||||||
|
return set(self.getjson(section, option))
|
||||||
|
|
||||||
options['path_format'] = self.get_path_definition()
|
return value
|
||||||
|
|
||||||
options['day_begins'] = 0
|
if self.is_option('Path', 'name') and self.is_option('dirs_path', option):
|
||||||
options['max_deep'] = None
|
# Path format is split in two parts
|
||||||
if 'Path' in self.conf:
|
value = self.conf['Path']['dirs_path'] + '/' + self.conf['Path']['name']
|
||||||
if 'day_begins' in self.conf['Path']:
|
|
||||||
options['day_begins'] = int(self.conf['Path']['day_begins'])
|
|
||||||
if 'max_deep' in self.conf['Path']:
|
|
||||||
options['max_deep'] = int(self.conf['Path']['max_deep'])
|
|
||||||
|
|
||||||
options['exclude'] = []
|
return value
|
||||||
if 'Exclusions' in self.conf:
|
|
||||||
options['exclude'] = [value for key, value in self.conf.items('Exclusions')]
|
|
||||||
|
|
||||||
return options
|
def get_options(self) -> dict:
|
||||||
|
"""Get config options"""
|
||||||
|
|
||||||
|
old_options = {}
|
||||||
|
for section in self.options:
|
||||||
|
for option in self.options[section]:
|
||||||
|
# Option is in section
|
||||||
|
# TODO make a function
|
||||||
|
value = self.get_option(section, option)
|
||||||
|
old_options[option] = value
|
||||||
|
self.options[section][option] = value
|
||||||
|
|
||||||
|
return old_options
|
||||||
|
|
|
@ -50,11 +50,16 @@ class TestConfig:
|
||||||
config = Config(conf_path)
|
config = Config(conf_path)
|
||||||
assert e.typename == 'MissingSectionHeaderError'
|
assert e.typename == 'MissingSectionHeaderError'
|
||||||
|
|
||||||
def test_get_path_definition(self, conf):
|
# def test_get_path_definition(self, conf):
|
||||||
"""
|
# """
|
||||||
Get path definition from config
|
# Get path definition from config
|
||||||
"""
|
# """
|
||||||
config = Config(conf=conf)
|
# config = Config(conf=conf)
|
||||||
path = config.get_path_definition()
|
# path = config.get_path_definition()
|
||||||
assert path == '%u{%Y-%m}/{city}|{city}-{%Y}/{folders[:1]}/{folder}/{%Y-%m-%b-%H-%M-%S}-{basename}.%l{ext}'
|
# assert path == '%u{%Y-%m}/{city}|{city}-{%Y}/{folders[:1]}/{folder}/{%Y-%m-%b-%H-%M-%S}-{basename}.%l{ext}'
|
||||||
|
|
||||||
|
def test_get_options(self, conf):
|
||||||
|
config = Config(conf=conf)
|
||||||
|
options = config.get_options()
|
||||||
|
assert isinstance(options, dict)
|
||||||
|
# assert isinstance(options['Path'], dict)
|
||||||
|
|
Loading…
Reference in New Issue