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] | ||||
| # day_begins: what hour of the day you want the day to begin (only for | ||||
| # classification purposes).  Defaults at 0 as midnight. Can be | ||||
| @ -5,14 +13,7 @@ | ||||
| # be a number between 0-23') | ||||
| day_begins=4 | ||||
| 
 | ||||
| # Path format | ||||
| dirs_path={%Y}/{%m-%b}-{city}-{folder} | ||||
| 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 | ||||
| 
 | ||||
| from ordigi import constants, log, LOG | ||||
| from ordigi.config import Config | ||||
| from ordigi.collection import Collection | ||||
| from ordigi.geolocation import GeoLocation | ||||
| 
 | ||||
| @ -116,13 +115,6 @@ def add_options(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): | ||||
|     root = Path(root).expanduser().absolute() | ||||
|     if not paths: | ||||
|  | ||||
| @ -14,6 +14,7 @@ from pathlib import Path, PurePath | ||||
| import inquirer | ||||
| 
 | ||||
| from ordigi import LOG | ||||
| from ordigi.config import Config | ||||
| from ordigi.database import Sqlite | ||||
| from ordigi.media import Medias | ||||
| from ordigi.images import Image, Images | ||||
| @ -220,6 +221,9 @@ class FPath: | ||||
|         Returns file path. | ||||
|         """ | ||||
|         path_format = self.path_format | ||||
| 
 | ||||
|         # Each element in the list represents a folder. | ||||
|         # Fallback folders are supported and are nested lists. | ||||
|         path = [] | ||||
|         path_parts = path_format.split('/') | ||||
|         for path_part in path_parts: | ||||
| @ -706,50 +710,58 @@ class Collection(SortMedias): | ||||
|         use_file_dates=False, | ||||
|     ): | ||||
| 
 | ||||
|         day_begins=0 | ||||
|         max_deep=None | ||||
|         # TODO move to cli | ||||
|         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.glob = glob | ||||
| 
 | ||||
|         # Check if collection path is valid | ||||
|         if not root.exists(): | ||||
|             self.log.error(f'Collection path {root} does not exist') | ||||
|             sys.exit(1) | ||||
| 
 | ||||
|         # def get_collection_config(root): | ||||
|         #     return Config(root.joinpath('.ordigi', 'ordigi.conf')) | ||||
|         self.root = root | ||||
| 
 | ||||
|         # TODO Collection options | ||||
|         # config = get_collection_config(root) | ||||
|         # opt = config.get_options() | ||||
|         # exclude = _get_exclude(opt, kwargs['exclude']) | ||||
|         # path_format = opt['path_format'] | ||||
|         # if kwargs['path_format']: | ||||
|         #     path_format = kwargs['path_format'] | ||||
|         # Get config options | ||||
|         config = self.get_config() | ||||
|         self.opt = config.get_options() | ||||
| 
 | ||||
|         # Set client options | ||||
|         for option, value in cli_options.items(): | ||||
|             self.opt[option] = value | ||||
|         self._set_cli_option('exclude', exclude) | ||||
| 
 | ||||
|         self.db = CollectionDb(root) | ||||
|         self.fileio = FileIO(dry_run) | ||||
|         self.fileio = FileIO(self.opt['dry_run']) | ||||
|         self.paths = Paths( | ||||
|             exclude, | ||||
|             extensions, | ||||
|             glob, | ||||
|             interactive, | ||||
|             max_deep, | ||||
|             self.opt['exclude'], | ||||
|             self.opt['extensions'], | ||||
|             self.opt['glob'], | ||||
|             self.opt['interactive'], | ||||
|             self.opt['max_deep'], | ||||
|         ) | ||||
| 
 | ||||
|         self.medias = Medias( | ||||
|             self.paths, | ||||
|             root, | ||||
|             album_from_folder, | ||||
|             cache, | ||||
|             self.opt['album_from_folder'], | ||||
|             self.opt['cache'], | ||||
|             self.db, | ||||
|             interactive, | ||||
|             ignore_tags, | ||||
|             use_date_filename, | ||||
|             use_file_dates, | ||||
|             self.opt['interactive'], | ||||
|             self.opt['ignore_tags'], | ||||
|             self.opt['use_date_filename'], | ||||
|             self.opt['use_file_dates'], | ||||
|         ) | ||||
| 
 | ||||
|         # Features | ||||
| @ -758,21 +770,30 @@ class Collection(SortMedias): | ||||
|             self.medias, | ||||
|             root, | ||||
|             self.db, | ||||
|             dry_run, | ||||
|             interactive, | ||||
|             self.opt['dry_run'], | ||||
|             self.opt['interactive'], | ||||
|         ) | ||||
| 
 | ||||
|         # Attributes | ||||
|         self.summary = Summary(self.root) | ||||
|         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): | ||||
|         if exclude: | ||||
|             exclude = self.paths.exclude | ||||
| 
 | ||||
|         paths = Paths( | ||||
|             exclude, | ||||
|             interactive=self.interactive, | ||||
|             interactive=self.opt['interactive'], | ||||
|         ) | ||||
|         for file_path in paths.get_files(self.root): | ||||
|             yield file_path | ||||
| @ -932,7 +953,7 @@ class Collection(SortMedias): | ||||
|         files = os.listdir(directory) | ||||
|         if len(files) == 0 and remove_root: | ||||
|             self.log.info(f"Removing empty folder: {directory}") | ||||
|             if not self.dry_run: | ||||
|             if not self.opt['dry_run']: | ||||
|                 os.rmdir(directory) | ||||
|             self.summary.append('remove', True, directory) | ||||
| 
 | ||||
| @ -948,11 +969,14 @@ class Collection(SortMedias): | ||||
|         # Check db | ||||
|         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 | ||||
|         subdirs = set() | ||||
|         for src_path, metadata in self.medias.get_metadatas(src_dirs, imp=imp, loc=loc): | ||||
|             # 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) | ||||
|             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 os import path | ||||
| from ordigi import constants | ||||
| 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: | ||||
|     """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 | ||||
|         if conf == {}: | ||||
|         if conf is None: | ||||
|             self.conf = self.load_config() | ||||
|             if self.conf == {}: | ||||
|                 # Fallback to default config | ||||
| @ -33,66 +98,94 @@ class Config: | ||||
|         conf.read(self.conf_path) | ||||
|         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]: | ||||
|             return self.conf[section][option] | ||||
|             return True | ||||
| 
 | ||||
|         return False | ||||
| 
 | ||||
|     def get_path_definition(self): | ||||
|         """Returns a list of folder definitions. | ||||
|     @check_option | ||||
|     def _getboolean(self, section, option): | ||||
|         return self.conf.getboolean(section, option) | ||||
|     getboolean = check_option(_getboolean) | ||||
| 
 | ||||
|         Each element in the list represents a folder. | ||||
|         Fallback folders are supported and are nested lists. | ||||
|     @check_option | ||||
|     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: | ||||
|             if 'format' in self.conf['Path']: | ||||
|                 return self.conf['Path']['format'] | ||||
|             elif 'dirs_path' and 'name' in self.conf['Path']: | ||||
|                 return self.conf['Path']['dirs_path'] + '/' + self.conf['Path']['name'] | ||||
|     @check_re | ||||
|     def _getre(self, section, option): | ||||
|         return re.compile(self.conf.get(section, option)) | ||||
|     getre = check_re(_getre) | ||||
| 
 | ||||
|         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): | ||||
|         """Get config options | ||||
|         :returns: dict | ||||
|         """ | ||||
|         int_options = { | ||||
|             'day_begins', | ||||
|             'max_deep', | ||||
|             'timeout', | ||||
|         } | ||||
| 
 | ||||
|         options = {} | ||||
|         geocoder = self.get_option('geocoder', 'Geolocation') | ||||
|         if geocoder and geocoder in ('Nominatim',): | ||||
|             options['geocoder'] = geocoder | ||||
|         else: | ||||
|             options['geocoder'] = constants.DEFAULT_GEOCODER | ||||
|         string_options = { | ||||
|             'glob', | ||||
|             'geocoder', | ||||
|         } | ||||
| 
 | ||||
|         prefer_english_names = self.get_option('prefer_english_names', 'Geolocation') | ||||
|         if prefer_english_names: | ||||
|             options['prefer_english_names'] = bool(prefer_english_names) | ||||
|         else: | ||||
|             options['prefer_english_names'] = False | ||||
|         multi_options = { | ||||
|             'exclude', | ||||
|             'extensions', | ||||
|             'ignore_tags', | ||||
|         } | ||||
| 
 | ||||
|         timeout = self.get_option('timeout', 'Geolocation') | ||||
|         if timeout: | ||||
|             options['timeout'] = timeout | ||||
|         else: | ||||
|             options['timeout'] = gopt.default_timeout | ||||
|         value = self.options[section][option] | ||||
|         if self.is_option(section, option): | ||||
|             if option in bool_options: | ||||
|                 return self.getboolean(section, option) | ||||
|             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 | ||||
|         options['max_deep'] = None | ||||
|         if 'Path' in self.conf: | ||||
|             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']) | ||||
|         if self.is_option('Path', 'name') and self.is_option('dirs_path', option): | ||||
|             # Path format is split in two parts | ||||
|             value = self.conf['Path']['dirs_path'] + '/' + self.conf['Path']['name'] | ||||
| 
 | ||||
|         options['exclude'] = [] | ||||
|         if 'Exclusions' in self.conf: | ||||
|             options['exclude'] = [value for key, value in self.conf.items('Exclusions')] | ||||
|         return value | ||||
| 
 | ||||
|         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) | ||||
|         assert e.typename == 'MissingSectionHeaderError' | ||||
| 
 | ||||
|     def test_get_path_definition(self, conf): | ||||
|         """ | ||||
|         Get path definition from config | ||||
|         """ | ||||
|         config = Config(conf=conf) | ||||
|         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}' | ||||
|     # def test_get_path_definition(self, conf): | ||||
|     #     """ | ||||
|     #     Get path definition from config | ||||
|     #     """ | ||||
|     #     config = Config(conf=conf) | ||||
|     #     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}' | ||||
| 
 | ||||
|     def test_get_options(self, conf): | ||||
|         config = Config(conf=conf) | ||||
|         options = config.get_options() | ||||
|         assert isinstance(options, dict) | ||||
|         # assert isinstance(options['Path'], dict) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user