From c91b6db006bdeea3228ba0888411fb9f3385db8d Mon Sep 17 00:00:00 2001 From: Christian Jungerius Date: Wed, 1 Mar 2023 20:47:30 +0100 Subject: [PATCH 01/19] Feature/search terms when scraping (#26) * #39 Added option to apply search term mode * updated changelog --- changelog.md | 16 ++++------------ resources/lib/commands/rom_scraper_commands.py | 1 + 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 14b505b0..6d448183 100644 --- a/changelog.md +++ b/changelog.md @@ -1,19 +1,11 @@ ## Current +- Search term mode applicable for multi ROM scraping + +## Previous - Custom skin view for View ROM - More details about addon plugins - Fixed linking assets to wrong directory - Fixed category rendering - Added rebuild views option in settings dialog - -## Previous - Added support for PEGI rating -- Fixed bug deleting collections -- Proper icon/thumb mapping for virtual collections -- Virtual categories now render items from database -- Add and execute single instance ROMs or Games -- Added an overview option with installed plugins -- Fix adding tags to ROMs -- Minor bugfixes -- Updated dependency -- Fixed virtual collections (Favs, Most recent ..) -- Fixed scraping ROM assets only \ No newline at end of file +- Fixed bug deleting collections \ No newline at end of file diff --git a/resources/lib/commands/rom_scraper_commands.py b/resources/lib/commands/rom_scraper_commands.py index c52277d6..46211811 100644 --- a/resources/lib/commands/rom_scraper_commands.py +++ b/resources/lib/commands/rom_scraper_commands.py @@ -116,6 +116,7 @@ def cmd_scrape_roms_in_romcollection(args): options = collections.OrderedDict() options['SCRAPER_METADATA_POLICY'] = 'Metadata scan policy: "{}"'.format(kodi.translate(scraper_settings.scrape_metadata_policy)) options['SCRAPER_ASSET_POLICY'] = 'Asset scan policy: "{}"'.format(kodi.translate(scraper_settings.scrape_assets_policy)) + options['SCRAPER_SEARCH_TERM_MODE'] = f'Search term mode: "{kodi.translate(scraper_settings.search_term_mode)}"' options['SCRAPER_GAME_SELECTION_MODE'] = 'Game selection mode: "{}"'.format(kodi.translate(scraper_settings.game_selection_mode)) options['SCRAPER_ASSET_SELECTION_MODE'] = 'Asset selection mode: "{}"'.format(kodi.translate(scraper_settings.asset_selection_mode)) options['SCRAPER_META_TO_SCRAPE'] = 'Metadata to scrape: "{}"'.format(', '.join(metadata_to_scrape)) From f0d91eb288e8f97f1912c64928a35cd62f9022a5 Mon Sep 17 00:00:00 2001 From: Christian Jungerius Date: Tue, 2 May 2023 18:31:08 +0200 Subject: [PATCH 02/19] Feature/refactor asset mappings (#27) * SQL migration to new mapping * Added support for asset mapping objects * Implemented support for romcollection rom mappings * Updated complete schema * Fixed tests * name change * Updated changelog * Added support for db actions * Updated SQL migration script * Updated migrations code * Fixes with migrations script * Fixes for default ROM asset mappings * Fixed rendering mapped assets * Added asset mapping support for ROMs * Add default ROM mappings * Cleanup of old asset keys --- addon.xml | 2 +- changelog.md | 1 + .../resource.language.en_gb/strings.po | 35 ++ resources/lib/commands/category_commands.py | 20 +- resources/lib/commands/misc_commands.py | 36 +- resources/lib/commands/rom_commands.py | 6 +- .../lib/commands/romcollection_commands.py | 21 +- .../commands/romcollection_roms_commands.py | 21 +- .../lib/commands/view_rendering_commands.py | 2 +- resources/lib/domain.py | 436 ++++++++---------- resources/lib/editors.py | 13 +- resources/lib/queries.py | 214 ++++++--- resources/lib/report.py | 2 +- resources/lib/repositories.py | 374 +++++++++++---- resources/lib/services.py | 17 +- resources/lib/viewqueries.py | 39 +- resources/migrations/1.4.0.sql | 242 ++++++++++ resources/schema.sql | 57 +-- resources/settings.xml | 20 + tests/domain_test.py | 1 - tests/fakes.py | 2 +- tests/regex_test.py | 4 +- tests/services_test.py | 1 + 23 files changed, 1064 insertions(+), 502 deletions(-) create mode 100644 resources/migrations/1.4.0.sql diff --git a/addon.xml b/addon.xml index 01f49c5c..699b6b82 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.md b/changelog.md index 6d448183..5fb3ac5f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,6 @@ ## Current - Search term mode applicable for multi ROM scraping +- Refactoring of default asset mapping ## Previous - Custom skin view for View ROM diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 8dce6cc6..7e1e80ca 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -296,6 +296,41 @@ msgctxt "#40856" msgid "Rebuild all views" msgstr "" +msgctxt "#40857" +msgid "Run database migrations" +msgstr "" + +msgctxt "#40858" +msgid "Reset database (warning!)" +msgstr "" + +msgctxt "#40859" +msgid "Choose default Assets/Artwork" +msgstr "" + +msgctxt "#40860" +msgid "Category status:" +msgstr "" + +msgctxt "#40861" +msgid "Export Category XML configuration" +msgstr "" + +msgctxt "#40862" +msgid "Delete Category" +msgstr "" + +############################ +# Headers and texts / notifications +############################ +msgctxt "#40950" +msgid "Select action for Category" +msgstr "" + +msgctxt "#40951" +msgid "Invalid parameters supplied." +msgstr "" + ############################ # Scraping settings ############################ diff --git a/resources/lib/commands/category_commands.py b/resources/lib/commands/category_commands.py index 5cb3403f..ea6be45e 100644 --- a/resources/lib/commands/category_commands.py +++ b/resources/lib/commands/category_commands.py @@ -70,7 +70,7 @@ def cmd_edit_category(args): if category_id is None: logger.warning('cmd_add_category(): No category id supplied.') - kodi.notify_warn("Invalid parameters supplied.") + kodi.notify_warn(kodi.translate(40951)) return selected_option = None @@ -80,23 +80,23 @@ def cmd_edit_category(args): category = repository.find_category(category_id) options = collections.OrderedDict() - options['CATEGORY_EDIT_METADATA'] = 'Edit Metadata ...' - options['CATEGORY_EDIT_ASSETS'] = 'Edit Assets/Artwork ...' - options['CATEGORY_EDIT_DEFAULT_ASSETS'] = 'Choose default Assets/Artwork ...' - options['CATEGORY_STATUS'] = 'Category status: {0}'.format(category.get_finished_str()) - options['EXPORT_CATEGORY_XML'] = 'Export Category XML configuration ...' - options['DELETE_CATEGORY'] = 'Delete Category' + options['CATEGORY_EDIT_METADATA'] = kodi.translate(40853) + options['CATEGORY_EDIT_ASSETS'] = kodi.translate(40854) + options['CATEGORY_EDIT_DEFAULT_ASSETS'] = kodi.translate(40859) + options['CATEGORY_STATUS'] = f'{kodi.translate(40859)} {category.get_finished_str()}' + options['EXPORT_CATEGORY_XML'] = kodi.translate(40861) + options['DELETE_CATEGORY'] = kodi.translate(40862) - s = 'Select action for Category "{0}"'.format(category.get_name()) + s = f'{kodi.translate(40950)} "{category.get_name}"' selected_option = kodi.OrdDictionaryDialog().select(s, options) if selected_option is None: # >> Exits context menu - logger.debug('EDIT_CATEGORY: cmd_edit_category() Selected None. Closing context menu') + logger.debug('EDIT_CATEGORY: Selected None. Closing context menu') return # >> Execute subcommand. May be atomic, maybe a submenu. - logger.debug('EDIT_CATEGORY: cmd_edit_category() Selected {}'.format(selected_option)) + logger.debug(f'EDIT_CATEGORY: Selected {selected_option}') AppMediator.sync_cmd(selected_option, args) # --- Submenu command --- diff --git a/resources/lib/commands/misc_commands.py b/resources/lib/commands/misc_commands.py index 88a6611b..f31f122a 100644 --- a/resources/lib/commands/misc_commands.py +++ b/resources/lib/commands/misc_commands.py @@ -19,10 +19,12 @@ import logging import typing +import collections from datetime import datetime from xml.etree import cElementTree as ET from xml.dom import minidom +from distutils.version import LooseVersion from akl.utils import kodi, io from akl import constants @@ -152,7 +154,7 @@ def cmd_export_to_xml(args): ET.SubElement(category_xml,'plot').text = category.get_plot() ET.SubElement(category_xml,'Asset_Prefix').text = category.get_custom_attribute('Asset_Prefix') for asset in category.get_assets(): - ET.SubElement(category_xml,asset.get_asset_info().key).text = asset.get_path() + ET.SubElement(category_xml, f"s_{asset.get_asset_info().id}").text = asset.get_path() # --- Export Launchers and add XML tail --- # Data which is not string must be converted to string @@ -199,7 +201,7 @@ def cmd_export_to_xml(args): ET.SubElement(launcher_xml, path.get_asset_info().path_key).text = path.get_path() for asset in collection.get_assets(): - ET.SubElement(launcher_xml,asset.get_asset_info().key).text = asset.get_path() + ET.SubElement(launcher_xml, f"s_{asset.get_asset_info().id}").text = asset.get_path() result_xml = ET.tostring(root, 'utf-8') parsed_xml = minidom.parseString(result_xml) @@ -222,6 +224,36 @@ def cmd_execute_reset_db(args): AppMediator.async_cmd('SCAN_FOR_ADDONS') kodi.notify('Finished resetting the database') +@AppMediator.register('RUN_DB_MIGRATIONS') +def cmd_execute_reset_db(args): + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + if not globals.g_PATHS.DATABASE_MIGRATIONS_PATH.exists(): + globals.g_PATHS.DATABASE_MIGRATIONS_PATH.makedirs() + + options = collections.OrderedDict() + db_version = LooseVersion(uow.get_database_version()) + migrations_files_available = globals.g_PATHS.DATABASE_MIGRATIONS_PATH.scanFilesInPath("*.sql") + + for migration_file in migrations_files_available: + options[migration_file.getPath()] = migration_file.getBase() + + dialog = kodi.OrdDictionaryDialog() + selected_file = dialog.select(f"Select migrations to execute (Current version {db_version})", options) + + if selected_file is None: + return + + migration_file = io.FileName(selected_file) + version_to_store = LooseVersion(globals.addon_version) + file_version = LooseVersion(migration_file.getBaseNoExt()) + if file_version > version_to_store: + version_to_store = file_version + if db_version > version_to_store: + version_to_store = db_version + + uow.migrate_database([migration_file], version_to_store) + kodi.notify('Done running migrations on the database') + @AppMediator.register('CHECK_DUPLICATE_ASSET_DIRS') def cmd_check_duplicate_asset_dirs(args): romcollection_id:str = args['romcollection_id'] if 'romcollection_id' in args else None diff --git a/resources/lib/commands/rom_commands.py b/resources/lib/commands/rom_commands.py index 1b327bf3..dc6de4ca 100644 --- a/resources/lib/commands/rom_commands.py +++ b/resources/lib/commands/rom_commands.py @@ -52,9 +52,9 @@ def cmd_edit_rom(args): rom = repository.find_rom(rom_id) options = collections.OrderedDict() - options['ROM_EDIT_METADATA'] = f'{kodi.translate(40853)} ...' - options['ROM_EDIT_ASSETS'] = f'{kodi.translate(40854)} ...' - # options['ROM_EDIT_DEFAULT_ASSETS'] = 'Choose default Assets/Artwork ...' + options['ROM_EDIT_METADATA'] = kodi.translate(40853) + options['ROM_EDIT_ASSETS'] = kodi.translate(40854) + options['ROM_EDIT_DEFAULT_ASSETS'] = kodi.translate(40859) options['EDIT_ROM_STATUS'] = f'ROM status: {rom.get_finished_str()}' if rom.has_launchers(): options['EDIT_ROM_LAUNCHERS'] = 'Manage associated launchers' diff --git a/resources/lib/commands/romcollection_commands.py b/resources/lib/commands/romcollection_commands.py index ba7d8529..1f1352c5 100644 --- a/resources/lib/commands/romcollection_commands.py +++ b/resources/lib/commands/romcollection_commands.py @@ -109,17 +109,18 @@ def cmd_edit_romcollection(args): category_name = 'None' if category is None else category.get_name() options = collections.OrderedDict() - options['ROMCOLLECTION_EDIT_METADATA'] = 'Edit Metadata ...' - options['ROMCOLLECTION_EDIT_ASSETS'] = 'Edit Assets/Artwork ...' - options['ROMCOLLECTION_EDIT_DEFAULT_ASSETS'] = 'Choose default Assets/Artwork ...' + options['ROMCOLLECTION_EDIT_METADATA'] = kodi.translate(40853) + options['ROMCOLLECTION_EDIT_ASSETS'] = kodi.translate(40854) + options['ROMCOLLECTION_EDIT_DEFAULT_ASSETS'] = kodi.translate(40859) if romcollection.has_launchers(): - options['EDIT_ROMCOLLECTION_LAUNCHERS'] = 'Manage associated launchers' - else: options['ADD_LAUNCHER'] = 'Add new launcher' - options['ROMCOLLECTION_MANAGE_ROMS'] = 'Manage ROMs ...' - options['EDIT_ROMCOLLECTION_CATEGORY'] = f"Change Category: '{category_name}'" - options['EDIT_ROMCOLLECTION_STATUS'] = f'ROM Collection status: {romcollection.get_finished_str()}' - options['EXPORT_ROMCOLLECTION'] = 'Export ROM Collection XML configuration ...' - options['DELETE_ROMCOLLECTION'] = 'Delete ROM Collection' + options['EDIT_ROMCOLLECTION_LAUNCHERS'] = 'Manage associated launchers' + else: + options['ADD_LAUNCHER'] = 'Add new launcher' + options['ROMCOLLECTION_MANAGE_ROMS'] = 'Manage ROMs ...' + options['EDIT_ROMCOLLECTION_CATEGORY'] = f"Change Category: '{category_name}'" + options['EDIT_ROMCOLLECTION_STATUS'] = f'ROM Collection status: {romcollection.get_finished_str()}' + options['EXPORT_ROMCOLLECTION'] = 'Export ROM Collection XML configuration ...' + options['DELETE_ROMCOLLECTION'] = 'Delete ROM Collection' s = 'Select action for ROM Collection "{}"'.format(romcollection.get_name()) selected_option = kodi.OrdDictionaryDialog().select(s, options) diff --git a/resources/lib/commands/romcollection_roms_commands.py b/resources/lib/commands/romcollection_roms_commands.py index b80a6e45..f01ff06e 100644 --- a/resources/lib/commands/romcollection_roms_commands.py +++ b/resources/lib/commands/romcollection_roms_commands.py @@ -99,8 +99,7 @@ def cmd_set_roms_default_artwork(args): options = collections.OrderedDict() for default_asset_info in default_assets_list: # >> Label is the string 'Choose asset for XXXX (currently YYYYY)' - mapped_asset_key = romcollection.get_mapped_ROM_asset_key(default_asset_info) - mapped_asset_info = g_assetFactory.get_asset_info_by_key(mapped_asset_key) + mapped_asset_info = romcollection.get_ROM_asset_mapping(default_asset_info) # --- Append to list of ListItems --- options[default_asset_info] = 'Choose asset for {0} (currently {1})'.format(default_asset_info.name, mapped_asset_info.name) @@ -109,15 +108,14 @@ def cmd_set_roms_default_artwork(args): if selected_asset_info is None: # >> Return to parent menu. - logger.debug('cmd_set_roms_default_artwork() Main selected NONE. Returning to parent menu.') + logger.debug('Main selected NONE. Returning to parent menu.') AppMediator.async_cmd('ROMCOLLECTION_MANAGE_ROMS', args) return - logger.debug('cmd_set_roms_default_artwork() Main select() returned {0}'.format(selected_asset_info.name)) - mapped_asset_key = romcollection.get_mapped_ROM_asset_key(selected_asset_info) - mapped_asset_info = g_assetFactory.get_asset_info_by_key(mapped_asset_key) + logger.debug('Main select() returned {0}'.format(selected_asset_info.name)) + mapped_asset_info = romcollection.get_ROM_asset_mapping(selected_asset_info) mappable_asset_list = g_assetFactory.get_asset_list_by_IDs(constants.ROM_ASSET_ID_LIST, 'image') - logger.debug('cmd_set_roms_default_artwork() {0} currently is mapped to {1}'.format(selected_asset_info.name, mapped_asset_info.name)) + logger.debug(f'{selected_asset_info.name} currently is mapped to {mapped_asset_info.name}') # --- Create ListItems --- options = collections.OrderedDict() @@ -126,18 +124,17 @@ def cmd_set_roms_default_artwork(args): options[mappable_asset_info] = mappable_asset_info.name dialog = kodi.OrdDictionaryDialog() - dialog_title_str = 'Edit {0} {1} mapped asset'.format( - romcollection.get_object_name(), selected_asset_info.name) + dialog_title_str = f'Edit {romcollection.get_object_name()} {selected_asset_info.name} mapped asset' new_selected_asset_info = dialog.select(dialog_title_str, options, mapped_asset_info) if new_selected_asset_info is None: # >> Return to this method recursively to previous menu. - logger.debug('cmd_set_roms_default_artwork() Mapable selected NONE. Returning to previous menu.') + logger.debug('Mapable selected NONE. Returning to previous menu.') AppMediator.async_cmd('ROMCOLLECTION_MANAGE_ROMS', args) return - logger.debug('cmd_set_roms_default_artwork() Mapable selected {0}.'.format(new_selected_asset_info.name)) - romcollection.set_mapped_ROM_asset_key(selected_asset_info, new_selected_asset_info) + logger.debug(f'Mapable selected {new_selected_asset_info.name}.') + romcollection.set_mapped_ROM_asset(selected_asset_info, new_selected_asset_info) kodi.notify('{0} {1} mapped to {2}'.format( romcollection.get_object_name(), selected_asset_info.name, new_selected_asset_info.name )) diff --git a/resources/lib/commands/view_rendering_commands.py b/resources/lib/commands/view_rendering_commands.py index 7ee7247b..e5e2a0e7 100644 --- a/resources/lib/commands/view_rendering_commands.py +++ b/resources/lib/commands/view_rendering_commands.py @@ -432,7 +432,7 @@ def _render_romcollection_listitem(romcollection_obj: ROMCollection) -> dict: romcollection_name = romcollection_obj.get_name() ICON_OVERLAY = 5 if romcollection_obj.is_finished() else 4 assets = romcollection_obj.get_view_assets() - + if romcollection_obj.get_type() == constants.OBJ_COLLECTION_VIRTUAL: if romcollection_obj.get_parent_id() is None: url = globals.router.url_for_path(f'collection/virtual/{romcollection_obj.get_id()}') diff --git a/resources/lib/domain.py b/resources/lib/domain.py index 772dd634..fb0d91f5 100644 --- a/resources/lib/domain.py +++ b/resources/lib/domain.py @@ -57,8 +57,7 @@ def _is_empty_or_default(input: any, default: any): class AssetInfo(object): id = '' key = '' - default_key = '' - rom_default_key = '' + name = '' description = name plural = '' @@ -226,8 +225,12 @@ class AssetPath(EntityABC): def __init__(self, entity_data: typing.Dict[str, typing.Any] = None): self.asset_info:AssetInfo = None if entity_data is None: - entity_data = _get_default_asset_path_data_model() - + entity_data = { + 'id' : '', + 'path' : '', + 'asset_type' : '' + } + if 'asset_type' in entity_data and entity_data['asset_type']: self.asset_info = g_assetFactory.get_asset_info(entity_data['asset_type']) @@ -255,6 +258,71 @@ def clear(self): self.entity_data['path'] = None +class AssetMapping(EntityABC): + + def __init__(self, entity_data: typing.Dict[str, typing.Any] = None): + self.asset_info:AssetInfo = None + self.to_asset_info:AssetInfo = None + + if entity_data is None: + entity_data = { + 'id' : '', + 'mapped_asset_type' : '', + 'to_asset_type' : '' + } + + if 'mapped_asset_type' in entity_data and entity_data['mapped_asset_type']: + self.asset_info = g_assetFactory.get_asset_info(entity_data['mapped_asset_type']) + if 'to_asset_type' in entity_data and entity_data['to_asset_type']: + self.to_asset_info = g_assetFactory.get_asset_info(entity_data['to_asset_type']) + + super(AssetMapping, self).__init__(entity_data) + + def get_asset_info_id(self) -> str: + return self.asset_info.id + + def get_asset_info(self) -> AssetInfo: + return self.asset_info + + def get_mapped_to_asset_info(self) -> str: + return self.to_asset_info + + def set_mapping(self, info:AssetInfo, to:AssetInfo): + self.asset_info = info + self.to_asset_info = to + + def clear(self): + self.to_asset_info = None + + def is_mapped(self): + if self.to_asset_info is None: + return False + if self.to_asset_info.id == self.asset_info.id: + return False + return True + + +class RomAssetMapping(AssetMapping): + + def is_mapped(self): + if self.to_asset_info is None: + return False + + if self.asset_info.id == constants.ASSET_ICON_ID or \ + self.asset_info.id == constants.ASSET_POSTER_ID: + if self.asset_info.id == constants.ASSET_ICON_ID and \ + self.to_asset_info.id == constants.ASSET_BOXFRONT_ID: + return False + if self.asset_info.id == constants.ASSET_POSTER_ID and \ + self.to_asset_info.id == constants.ASSET_FLYER_ID: + return False + return True + + if self.to_asset_info.id == self.asset_info.id: + return False + return True + + # legacy # |----- LauncherABC (abstract class) # | @@ -541,7 +609,8 @@ class MetaDataItemABC(EntityABC): def __init__(self, entity_data: typing.Dict[str, typing.Any], assets: typing.List[Asset], - asset_paths_data: typing.List[AssetPath] = None): + asset_paths_data: typing.List[AssetPath] = None, + asset_mappings: typing.List[AssetMapping] = []): self.assets: typing.Dict[str, Asset] = {} if assets is not None: @@ -552,7 +621,8 @@ def __init__(self, if asset_paths_data is not None: for path in asset_paths_data: self.asset_paths[path.get_asset_info_id()] = path - + + self.asset_mappings = asset_mappings super(MetaDataItemABC, self).__init__(entity_data) # -------------------------------------------------------------------------------------------- @@ -570,7 +640,10 @@ def get_assets_kind(self) -> int: def get_type(self) -> str: pass - # --- Metadata -------------------------------------------------------------------------------- + # --- Metadata -------------------------------------------------------------------------------- + def get_metadata_id(self): + return self.entity_data['metadata_id'] + def get_name(self): return self.entity_data['m_name'] if 'm_name' in self.entity_data else 'Unknown' @@ -770,31 +843,24 @@ def get_view_assets(self) -> typing.Dict[str,str]: view_assets = {} for asset_id in view_asset_ids: asset_info = g_assetFactory.get_asset_info(asset_id) + applied_asset_info = asset_info value = '' fallback_str = '' - - if asset_id in self.assets: - asset = self.assets[asset_id] + if asset_info.id == constants.ASSET_ICON_ID: + fallback_str = self.get_default_icon() + + if self.is_mappable_asset(asset_info): + applied_asset_info = self.get_asset_mapping(asset_info) + + if applied_asset_info.id in self.assets: + asset = self.assets[applied_asset_info.id] value = asset.get_path() - if self.is_mappable_asset(asset_info): - if asset_info.id == constants.ASSET_ICON_ID: - fallback_str = self.get_default_icon() - value = self.get_mapped_asset_str(asset_info, fallback=fallback_str) - + if value == '': + value = fallback_str + view_assets[asset_info.fname_infix] = value return view_assets - # - # Gets the asset path (str) of the mapped asset type following - # the given input of either an assetinfo object or asset id. - # - def get_mapped_asset_str(self, asset_info=None, asset_id=None, fallback = '') -> str: - asset_info = self.get_mapped_asset_info(asset_info, asset_id) - asset = self.get_asset(asset_info.id) - if asset is not None and asset.get_path() != '': - return asset.get_path() - - return fallback # # Get a list of the assets that can be mapped to a defaultable asset. @@ -803,68 +869,55 @@ def get_mapped_asset_str(self, asset_info=None, asset_id=None, fallback = '') -> def get_mappable_asset_list(self) -> typing.List[AssetInfo]: return g_assetFactory.get_asset_list_by_IDs(self.get_mappable_asset_ids_list(), 'image') - def get_mapped_assets(self) -> typing.Dict[str,str]: - mappable_assets = self.get_mappable_asset_list() - mapped_assets = {} - for mappable_asset in mappable_assets: - if mappable_asset.id == constants.ASSET_ICON_ID: - mapped_assets[mappable_asset.fname_infix] = self.get_mapped_asset_str(mappable_asset, fallback=self.get_default_icon()) - else: mapped_assets[mappable_asset.fname_infix] = self.get_mapped_asset_str(mappable_asset) - return mapped_assets - # # Gets the actual assetinfo object that is mapped for # the given assetinfo for this particular MetaDataItem. # - def get_mapped_asset_info(self, asset_info=None, asset_id=None): - if asset_info is None and asset_id is None: return None - if asset_id is not None: asset_info = g_assetFactory.get_asset_info(asset_id) - - mapped_key = self.get_mapped_asset_key(asset_info) - mapped_asset_info = g_assetFactory.get_asset_info_by_key(mapped_key) - return mapped_asset_info - - # - # Gets the database filename mapped for asset_info. - # Note that the mapped asset uses diferent fields wheter it is a Category/Launcher/ROM - # - def get_mapped_asset_key(self, asset_info: AssetInfo): - if asset_info.default_key == '': - logger.error('Requested mapping for AssetInfo without default key. Type {}'.format(asset_info.id)) - raise constants.AddonError('Not supported asset type used. This might be a bug!') - - if asset_info.default_key not in self.entity_data: - return asset_info.key - - return self.entity_data[asset_info.default_key] + def get_asset_mapping(self, asset_info: AssetInfo): + if not self.asset_mappings: + return asset_info + mapped_asset = next((m for m in self.asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset or not mapped_asset.to_asset_info: + return asset_info + return mapped_asset.to_asset_info - def set_mapped_asset_key(self, asset_info: AssetInfo, mapped_to_info: AssetInfo): - self.entity_data[asset_info.default_key] = mapped_to_info.key + def set_mapped_asset(self, asset_info: AssetInfo, mapped_to_info: AssetInfo): + mapped_asset = next((m for m in self.asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset: + mapped_asset = AssetMapping() + self.asset_mappings.append(mapped_asset) + + mapped_asset.set_mapping(asset_info, mapped_to_info) def __str__(self): return '{}#{}: {}'.format(self.get_object_name(), self.get_id(), self.get_name()) # ------------------------------------------------------------------------------------------------- -# Class representing an AKL Cateogry. +# Class representing an AKL Category. # Contains code to generate the context menus passed to Dialog.select() # ------------------------------------------------------------------------------------------------- class Category(MetaDataItemABC): __metaclass__ = abc.ABCMeta - def __init__(self, category_dic: typing.Dict[str, typing.Any] = None, assets: typing.List[Asset] = None): + def __init__(self, + category_dic: typing.Dict[str, typing.Any] = None, + assets: typing.List[Asset] = None, + asset_mappings: typing.List[AssetMapping] = []): # Concrete classes are responsible of creating a default entity_data dictionary # with sensible defaults. if category_dic is None: category_dic = _get_default_category_data_model() category_dic['id'] = text.misc_generate_random_SID() - super(Category, self).__init__(category_dic, assets) + super(Category, self).__init__(category_dic, assets, None, asset_mappings) - def get_object_name(self): return 'Category' + def get_object_name(self): + return 'Category' def get_assets_kind(self): return constants.KIND_ASSET_CATEGORY - def get_type(self): return constants.OBJ_CATEGORY + def get_type(self): + return constants.OBJ_CATEGORY # parent category / romcollection this item belongs to. def get_parent_id(self) -> str: @@ -995,11 +1048,14 @@ def __str__(self): class VirtualCategory(Category): - def get_object_name(self): return 'Virtual Category' + def get_object_name(self): + return 'Virtual Category' - def get_assets_kind(self): return constants.KIND_ASSET_CATEGORY + def get_assets_kind(self): + return constants.KIND_ASSET_CATEGORY - def get_type(self): return constants.OBJ_CATEGORY_VIRTUAL + def get_type(self): + return constants.OBJ_CATEGORY_VIRTUAL # ------------------------------------------------------------------------------------------------- # Class representing a collection of ROMs. @@ -1011,6 +1067,8 @@ def __init__(self, entity_data: dict = None, assets_data: typing.List[Asset] = None, asset_paths: typing.List[AssetPath] = None, + asset_mappings: typing.List[AssetMapping] = [], + rom_asset_mappings: typing.List[RomAssetMapping] = [], launchers_data: typing.List[ROMLauncherAddon] = [], scanners_data: typing.List[ROMCollectionScanner] = []): # Concrete classes are responsible of creating a default entity_data dictionary @@ -1021,7 +1079,17 @@ def __init__(self, self.launchers_data = launchers_data self.scanners_data = scanners_data - super(ROMCollection, self).__init__(entity_data, assets_data, asset_paths) + + self.rom_asset_mappings = rom_asset_mappings + mappable_assets = self.get_ROM_mappable_asset_list() + if len(rom_asset_mappings) != len(mappable_assets): + already_mapped_assets_ids = [m.asset_info.id for m in rom_asset_mappings] + for asset_info in [a for a in mappable_assets if a.id not in already_mapped_assets_ids]: + mapping = RomAssetMapping() + mapping.asset_info = asset_info + self.rom_asset_mappings.append(mapping) + + super(ROMCollection, self).__init__(entity_data, assets_data, asset_paths, asset_mappings) def get_object_name(self): return 'ROM Collection' @@ -1046,7 +1114,8 @@ def set_box_sizing(self, box_size): def get_asset_ids_list(self): return constants.LAUNCHER_ASSET_ID_LIST - def get_mappable_asset_ids_list(self): return constants.MAPPABLE_LAUNCHER_ASSET_ID_LIST + def get_mappable_asset_ids_list(self): + return constants.MAPPABLE_LAUNCHER_ASSET_ID_LIST def get_default_icon(self) -> str: return 'DefaultGameAddons.png' @@ -1056,33 +1125,26 @@ def get_ROM_mappable_asset_list(self) -> typing.List[AssetInfo]: # # Gets the actual assetinfo object that is mapped for - # the given (ROM) assetinfo for this particular MetaDataItem. - # - def get_mapped_ROM_asset_info(self, asset_info=None, asset_id=None) -> AssetInfo: - if asset_info is None and asset_id is None: - return None - if asset_id is not None: - asset_info = g_assetFactory.get_asset_info(asset_id) - - mapped_key = self.get_mapped_ROM_asset_key(asset_info) - mapped_asset_info = g_assetFactory.get_asset_info_by_key(mapped_key) - return mapped_asset_info - - # - # Gets the database filename mapped for asset_info. - # Note that the mapped asset uses diferent fields wheter it is a Category/Launcher/ROM + # the given assetinfo for ROMs within this collection. # - def get_mapped_ROM_asset_key(self, asset_info: AssetInfo) -> str: - if asset_info.rom_default_key == '': - logger.error(f'Requested mapping for AssetInfo without default key. Type {asset_info.id}') - raise constants.AddonError('Not supported asset type used. This might be a bug!') - - if asset_info.rom_default_key not in self.entity_data: - return asset_info.key - return self.entity_data[asset_info.rom_default_key] + def get_ROM_asset_mapping(self, asset_info: AssetInfo): + mapped_asset = next((m for m in self.rom_asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset: + # exception cases + if asset_info.id == constants.ASSET_ICON_ID: + return g_assetFactory.get_asset_info(constants.ASSET_BOXFRONT_ID) + if asset_info.id == constants.ASSET_POSTER_ID: + return g_assetFactory.get_asset_info(constants.ASSET_FLYER_ID) + return asset_info + return mapped_asset.to_asset_info + + def set_mapped_ROM_asset(self, asset_info: AssetInfo, mapped_to_info: AssetInfo): + mapped_asset = next((m for m in self.rom_asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset: + mapped_asset = RomAssetMapping() + self.rom_asset_mappings.append(mapped_asset) - def set_mapped_ROM_asset_key(self, asset_info: AssetInfo, mapped_to_info: AssetInfo): - self.entity_data[asset_info.rom_default_key] = mapped_to_info.key + mapped_asset.set_mapping(asset_info, mapped_to_info) # # Get a list of assets with duplicated paths. Refuse to do anything if duplicated paths found. @@ -1313,6 +1375,7 @@ def __init__(self, tag_data: dict = None, assets_data: typing.List[Asset] = None, asset_paths_data: typing.List[AssetPath] = None, + asset_mappings: typing.List[RomAssetMapping] = [], scanned_data: dict = {}, launchers_data: typing.List[ROMLauncherAddon] = []): if rom_data is None: @@ -1327,7 +1390,15 @@ def __init__(self, tag_data_str = str(rom_data['rom_tags']) self.tags = {t: '' for t in tag_data_str.split(',')} - super(ROM, self).__init__(rom_data, assets_data, asset_paths_data) + mappable_assets = self.get_mappable_asset_list() + if len(asset_mappings) != len(mappable_assets): + already_mapped_assets_ids = [m.asset_info.id for m in asset_mappings] + for asset_info in [a for a in mappable_assets if a.id not in already_mapped_assets_ids]: + mapping = RomAssetMapping() + mapping.asset_info = asset_info + asset_mappings.append(mapping) + + super(ROM, self).__init__(rom_data, assets_data, asset_paths_data, asset_mappings) def get_object_name(self): return 'ROM' @@ -1549,7 +1620,26 @@ def get_asset_ids_list(self): def get_mappable_asset_ids_list(self): return constants.MAPPABLE_ROM_ASSET_ID_LIST - + + def get_asset_mapping(self, asset_info: AssetInfo): + mapped_asset = next((m for m in self.asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset: + # exception cases + if asset_info.id == constants.ASSET_ICON_ID: + return g_assetFactory.get_asset_info(constants.ASSET_BOXFRONT_ID) + if asset_info.id == constants.ASSET_POSTER_ID: + return g_assetFactory.get_asset_info(constants.ASSET_FLYER_ID) + return asset_info + return mapped_asset.to_asset_info + + def set_mapped_asset(self, asset_info: AssetInfo, mapped_to_info: AssetInfo): + mapped_asset = next((m for m in self.asset_mappings if m.asset_info.id == asset_info.id), None) + if not mapped_asset: + mapped_asset = RomAssetMapping() + self.asset_mappings.append(mapped_asset) + + mapped_asset.set_mapping(asset_info, mapped_to_info) + def get_default_icon(self) -> str: return 'DefaultProgram.png' @@ -1766,8 +1856,8 @@ def apply_romcollection_asset_paths(self, romcollection: ROMCollection): def apply_romcollection_asset_mapping(self, romcollection: ROMCollection): mappable_assets = romcollection.get_ROM_mappable_asset_list() for mappable_asset in mappable_assets: - mapped_asset = romcollection.get_mapped_ROM_asset_info(mappable_asset) - self.set_mapped_asset_key(mappable_asset, mapped_asset) + mapped_asset = romcollection.get_ROM_asset_mapping(mappable_asset) + self.set_mapped_asset(mappable_asset, mapped_asset) def __str__(self): """Overrides the default implementation""" @@ -1802,17 +1892,6 @@ def get_asset_info(self, asset_ID) -> AssetInfo: return asset_info - # Returns the corresponding assetinfo object for the - # given key (eg: 's_icon') - def get_asset_info_by_key(self, asset_key): - asset_info = next((a for a in list(self.ASSET_INFO_ID_DICT.values()) if a.key == asset_key), None) - - if asset_info is None: - logger.error('get_asset_info_by_key() Wrong asset_key = {0}'.format(asset_key)) - return AssetInfo() - - return asset_info - # Returns the corresponding assetinfo object for the # given path key (eg: 'path_icon') def get_asset_info_by_pathkey(self, path_key): @@ -1856,12 +1935,6 @@ def get_asset_list_by_IDs(self, IDs, kind = None) -> typing.List[AssetInfo]: return asset_info_list - # todo: use 1 type of identifier not number constants and name strings ('s_icon') - def get_asset_info_by_namekey(self, name_key): - if name_key == '': return None - kind = constants.ASSET_KEYS_TO_CONSTANTS[name_key] - - return self.get_asset_info(kind) # # Get extensions to search for files # Input : ['png', 'jpg'] @@ -1983,58 +2056,6 @@ def asset_get_regexp_extension_list(exts): return '(' + ext_string + ')' - # - # This must match the order of the list Category_asset_ListItem_list in _command_edit_category() - # TODO: deprecated? - @staticmethod - def assets_choose_Category_mapped_artwork(dict_object, key, index): - if index == 0: dict_object[key] = 's_icon' - elif index == 1: dict_object[key] = 's_fanart' - elif index == 2: dict_object[key] = 's_banner' - elif index == 3: dict_object[key] = 's_poster' - elif index == 4: dict_object[key] = 's_clearlogo' - - # - # This must match the order of the list Category_asset_ListItem_list in _command_edit_category() - # TODO: deprecated? - @staticmethod - def assets_get_Category_mapped_asset_idx(dict_object, key): - if dict_object[key] == 's_icon': index = 0 - elif dict_object[key] == 's_fanart': index = 1 - elif dict_object[key] == 's_banner': index = 2 - elif dict_object[key] == 's_poster': index = 3 - elif dict_object[key] == 's_clearlogo': index = 4 - else: index = 0 - - return index - - # - # This must match the order of the list Launcher_asset_ListItem_list in _command_edit_launcher() - # TODO: deprecated? - @staticmethod - def assets_choose_Launcher_mapped_artwork(dict_object, key, index): - if index == 0: dict_object[key] = 's_icon' - elif index == 1: dict_object[key] = 's_fanart' - elif index == 2: dict_object[key] = 's_banner' - elif index == 3: dict_object[key] = 's_poster' - elif index == 4: dict_object[key] = 's_clearlogo' - elif index == 5: dict_object[key] = 's_controller' - - # - # This must match the order of the list Launcher_asset_ListItem_list in _command_edit_launcher() - # TODO: deprecated? - @staticmethod - def assets_get_Launcher_mapped_asset_idx(dict_object, key): - if dict_object[key] == 's_icon': index = 0 - elif dict_object[key] == 's_fanart': index = 1 - elif dict_object[key] == 's_banner': index = 2 - elif dict_object[key] == 's_poster': index = 3 - elif dict_object[key] == 's_clearlogo': index = 4 - elif dict_object[key] == 's_controller': index = 5 - else: index = 0 - - return index - # since we are using a single instance for the assetinfo factory we can automatically load # all the asset objects into the memory def _load_asset_data(self): @@ -2042,9 +2063,6 @@ def _load_asset_data(self): # >> These are used very frequently so I think it is better to have a cached list. a = AssetInfo() a.id = constants.ASSET_ICON_ID - a.key = 's_icon' - a.default_key = 'default_icon' - a.rom_default_key = 'roms_default_icon' a.name = 'Icon' a.plural = 'Icons' a.fname_infix = 'icon' @@ -2056,9 +2074,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_FANART_ID - a.key = 's_fanart' - a.default_key = 'default_fanart' - a.rom_default_key = 'roms_default_fanart' a.name = 'Fanart' a.plural = 'Fanarts' a.fname_infix = 'fanart' @@ -2070,9 +2085,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_BANNER_ID - a.key = 's_banner' - a.default_key = 'default_banner' - a.rom_default_key = 'roms_default_banner' a.name = 'Banner' a.description = 'Banner / Marquee' a.plural = 'Banners' @@ -2085,9 +2097,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_POSTER_ID - a.key = 's_poster' - a.default_key = 'default_poster' - a.rom_default_key = 'roms_default_poster' a.name = 'Poster' a.plural = 'Posters' a.fname_infix = 'poster' @@ -2099,9 +2108,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_CLEARLOGO_ID - a.key = 's_clearlogo' - a.default_key = 'default_clearlogo' - a.rom_default_key = 'roms_default_clearlogo' a.name = 'Clearlogo' a.plural = 'Clearlogos' a.fname_infix = 'clearlogo' @@ -2113,8 +2119,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_CONTROLLER_ID - a.key = 's_controller' - a.default_key = 'default_controller' a.name = 'Controller' a.plural = 'Controllers' a.fname_infix = 'controller' @@ -2126,7 +2130,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_TRAILER_ID - a.key = 's_trailer' a.name = 'Trailer' a.plural = 'Trailers' a.fname_infix = 'trailer' @@ -2138,9 +2141,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_TITLE_ID - a.key = 's_title' - a.default_key = 'default_title' - a.rom_default_key = 'roms_default_title' a.name = 'Title' a.plural = 'Titles' a.fname_infix = 'title' @@ -2152,7 +2152,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_SNAP_ID - a.key = 's_snap' a.name = 'Snap' a.plural = 'Snaps' a.fname_infix = 'snap' @@ -2164,7 +2163,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_BOXFRONT_ID - a.key = 's_boxfront' a.name = 'Boxfront' a.description = 'Boxfront / Cabinet' a.plural = 'Boxfronts' @@ -2177,7 +2175,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_BOXBACK_ID - a.key = 's_boxback' a.name = 'Boxback' a.description = 'Boxback / CPanel' a.plural = 'Boxbacks' @@ -2190,7 +2187,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_CARTRIDGE_ID - a.key = 's_cartridge' a.name = 'Cartridge' a.description = 'Cartridge / PCB' a.plural = 'Cartridges' @@ -2203,7 +2199,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_FLYER_ID - a.key = 's_flyer' a.name = 'Flyer' a.plural = 'Flyers' a.fname_infix = 'flyer' @@ -2216,7 +2211,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_MAP_ID - a.key = 's_map' a.name = 'Map' a.plural = 'Maps' a.fname_infix = 'map' @@ -2228,7 +2222,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_MANUAL_ID - a.key = 's_manual' a.name = 'Manual' a.plural = 'Manuals' a.fname_infix = 'manual' @@ -2240,7 +2233,6 @@ def _load_asset_data(self): a = AssetInfo() a.id = constants.ASSET_3DBOX_ID - a.key = 's_3dbox' a.name = '3D Box' a.plural = '3D Boxes' a.fname_infix = '3dbox' @@ -2459,18 +2451,7 @@ def _get_default_category_data_model(): 'm_rating' : '', 'm_plot' : '', 'finished' : False, - 'default_icon' : 's_icon', - 'default_fanart' : 's_fanart', - 'default_banner' : 's_banner', - 'default_poster' : 's_poster', - 'default_clearlogo' : 's_clearlogo', #'Asset_Prefix' : '', - 's_icon' : '', - 's_fanart' : '', - 's_banner' : '', - 's_poster' : '', - 's_clearlogo' : '', - 's_trailer' : '' } def _get_default_ROMCollection_data_model(): @@ -2512,25 +2493,7 @@ def _get_default_ROMCollection_data_model(): 'num_extra' : 0, 'timestamp_launcher' : 0.0, 'timestamp_report' : 0.0, - 'default_icon' : 's_icon', - 'default_fanart' : 's_fanart', - 'default_banner' : 's_banner', - 'default_poster' : 's_poster', - 'default_clearlogo' : 's_clearlogo', - 'default_controller' : 's_controller', 'Asset_Prefix' : '', - 's_icon' : '', - 's_fanart' : '', - 's_banner' : '', - 's_poster' : '', - 's_clearlogo' : '', - 's_controller' : '', - 's_trailer' : '', - 'roms_default_icon' : 's_boxfront', - 'roms_default_fanart' : 's_fanart', - 'roms_default_banner' : 's_banner', - 'roms_default_poster' : 's_flyer', - 'roms_default_clearlogo' : 's_clearlogo', 'ROM_asset_path' : '', 'path_3dbox' : '', 'path_title' : '', @@ -2567,20 +2530,7 @@ def _get_default_ROM_data_model(): 'finished' : False, 'nointro_status' : constants.AUDIT_STATUS_NONE, 'pclone_status' : constants.PCLONE_STATUS_NONE, - 'cloneof' : '', - 's_3dbox' : '', - 's_title' : '', - 's_snap' : '', - 's_boxfront' : '', - 's_boxback' : '', - 's_cartridge' : '', - 's_fanart' : '', - 's_banner' : '', - 's_clearlogo' : '', - 's_flyer' : '', - 's_map' : '', - 's_manual' : '', - 's_trailer' : '' + 'cloneof' : '' } def _get_default_asset_data_model(): @@ -2588,12 +2538,4 @@ def _get_default_asset_data_model(): 'id' : '', 'filepath' : '', 'asset_type' : '' - } - -def _get_default_asset_path_data_model(): - return { - 'id' : '', - 'path' : '', - 'asset_type' : '' - } - \ No newline at end of file + } \ No newline at end of file diff --git a/resources/lib/editors.py b/resources/lib/editors.py index 603aa4b0..9d873aa0 100644 --- a/resources/lib/editors.py +++ b/resources/lib/editors.py @@ -305,7 +305,7 @@ def edit_asset(obj_instance: MetaDataItemABC, asset_info: AssetInfo) -> str: # --- Update object --- obj_instance.set_asset(asset_info, new_asset_FN) - logger.debug('edit_asset() Asset key "{0}"'.format(asset_info.key)) + logger.debug(f'edit_asset() Asset id "{asset_info.id}"') logger.debug('edit_asset() Linked {0} {1} to "{2}"'.format( obj_instance.get_object_name(), asset_info.name, new_asset_FN.getPath()) ) @@ -376,9 +376,9 @@ def edit_asset(obj_instance: MetaDataItemABC, asset_info: AssetInfo) -> str: # --- Update object --- # >> Always store original/raw paths in database. obj_instance.set_asset(asset_info, dest_asset_file) - logger.debug('edit_asset() Asset key "{0}"'.format(asset_info.key)) - logger.debug('edit_asset() Copied file "{0}"'.format(new_asset_file.getPath())) - logger.debug('edit_asset() Into "{0}"'.format(dest_asset_file.getPath())) + logger.debug(f'edit_asset() Asset ID "{asset_info.id}"') + logger.debug(f'edit_asset() Copied file "{new_asset_file.getPath()}"') + logger.debug(f'edit_asset() Into "{dest_asset_file.getPath()}"') logger.debug('edit_asset() Linked {0} {1} to "{2}"'.format( obj_instance.get_object_name(), asset_info.name, dest_asset_file.getPath()) ) @@ -442,8 +442,7 @@ def edit_object_default_assets(obj_instance: MetaDataItemABC, preselected_asset_ # >> Label 1 is the string 'Choose asset for XXXX (currently YYYYY)' # >> Label 2 is the fname string of the current mapped asset or 'Not set' # >> icon is the fname string of the current mapped asset. - mapped_asset_key = obj_instance.get_mapped_asset_key(default_asset_info) - mapped_asset_info = g_assetFactory.get_asset_info_by_key(mapped_asset_key) + mapped_asset_info = obj_instance.get_asset_mapping(default_asset_info) mapped_asset_str = obj_instance.get_asset_str(mapped_asset_info) label1_str = f'Choose asset for {default_asset_info.name} (currently {mapped_asset_info.name})' label2_str = mapped_asset_str @@ -521,7 +520,7 @@ def edit_default_asset(obj_instance: MetaDataItemABC, asset_info: AssetInfo) -> new_selected_asset_info = asset_info_list[secondary_selected_option] logger.debug(f'edit_default_asset() Mapable selected {new_selected_asset_info.name}.') - obj_instance.set_mapped_asset_key(asset_info, new_selected_asset_info) + obj_instance.set_mapped_asset(asset_info, new_selected_asset_info) kodi.notify('{0} {1} mapped to {2}'.format( obj_instance.get_object_name(), asset_info.name, new_selected_asset_info.name )) diff --git a/resources/lib/queries.py b/resources/lib/queries.py index 753caffa..7ca6599f 100644 --- a/resources/lib/queries.py +++ b/resources/lib/queries.py @@ -7,29 +7,50 @@ UPDATE_ASSET = "UPDATE assets SET filepath = ?, asset_type = ? WHERE id = ?" UPDATE_ASSET_PATH = "UPDATE assetpaths SET path = ?, asset_type = ? WHERE id = ?" +SELECT_ITEM_ASSET_MAPPINGS = "SELECT am.* FROM assetmappings AS am INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id WHERE mm.metadata_id = ?" +INSERT_ASSET_MAPPING = "INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) VALUES (?,?,?)" +UPDATE_ASSET_MAPPING = "UPDATE assetmappings SET mapped_asset_type = ?, to_asset_type = ? WHERE id = ?" +INSERT_MAPPING_WITH_METADATA = "INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) VALUES (?,?)" +DELETE_ASSET_MAPPING = "DELETE FROM assetmappings WHERE id = ?" + # CATEGORIES SELECT_CATEGORY = "SELECT * FROM vw_categories WHERE id = ?" SELECT_CATEGORY_ASSETS = "SELECT * FROM vw_category_assets WHERE category_id = ?" SELECT_CATEGORIES = "SELECT * FROM vw_categories ORDER BY m_name" SELECT_ALL_CATEGORY_ASSETS = "SELECT * FROM vw_category_assets" +SELECT_ALL_CATEGORY_ASSET_MAPPINGS = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN category AS c ON mm.metadata_id = c.metadata_id + """ SELECT_ROOT_CATEGORIES = "SELECT * FROM vw_categories WHERE parent_id IS NULL ORDER BY m_name" SELECT_ROOT_CATEGORY_ASSETS = "SELECT * FROM vw_category_assets WHERE parent_id IS NULL" +SELECT_ROOT_CATEGORY_ASSET_MAPPINGS = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN categories AS c ON mm.metadata_id = c.metadata_id + WHERE c.parent_id IS NULL + """ SELECT_CATEGORIES_BY_PARENT = "SELECT * FROM vw_categories WHERE parent_id = ? ORDER BY m_name" SELECT_CATEGORY_ASSETS_BY_PARENT = "SELECT * FROM vw_category_assets WHERE parent_id = ?" +SELECT_CATEGORY_ASSET_MAPPINGS_BY_PARENT = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN categories AS c ON mm.metadata_id = c.metadata_id + WHERE c.parent_id = ? + """ SELECT_CATEGORIES_BY_ROM = "SELECT c.* FROM vw_categories AS c INNER JOIN roms_in_category AS rc ON rc.category_id = c.id WHERE rc.rom_id = ?" SELECT_CATEGORIES_ASSETS_BY_ROM = "SELECT ca.* FROM vw_category_assets AS ca INNER JOIN roms_in_category AS rc ON rc.category_id = ca.category_id WHERE rc.rom_id = ?" - - -INSERT_CATEGORY = """ - INSERT INTO categories (id,name,parent_id,metadata_id,default_icon,default_fanart,default_banner,default_poster,default_clearlogo) - VALUES (?,?,?,?,?,?,?,?,?) - """ -UPDATE_CATEGORY = """ - UPDATE categories SET name=?, - default_icon=?, default_fanart=?, default_banner=?, default_poster=?, default_clearlogo=? - WHERE id =? +SELECT_CATEGORY_ASSET_MAPPINGS_BY_ROM = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN categories AS c ON mm.metadata_id = c.metadata_id + INNER JOIN roms_in_category AS rc ON rc.category_id = c.id WHERE rc.rom_id = ? """ + +INSERT_CATEGORY = "INSERT INTO categories (id,name,parent_id,metadata_id) VALUES (?,?,?,?)" +UPDATE_CATEGORY = "UPDATE categories SET name=? WHERE id =?" INSERT_CATEGORY_ASSET = "INSERT INTO category_assets (category_id, asset_id) VALUES (?, ?)" DELETE_CATEGORY = "DELETE FROM categories WHERE id = ?" @@ -58,75 +79,144 @@ SELECT_VCOLLECTION_NPLAYERS = "SELECT DISTINCT(m_nplayers) AS option_value FROM vw_roms" SELECT_VCOLLECTION_RATING = "SELECT DISTINCT(m_rating) AS option_value FROM vw_roms" -INSERT_ROMCOLLECTION = """ - INSERT INTO romcollections ( - id,name,parent_id,metadata_id,platform,box_size, - default_icon,default_fanart,default_banner,default_poster,default_controller,default_clearlogo, - roms_default_icon,roms_default_fanart,roms_default_banner,roms_default_poster,roms_default_clearlogo - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) - """ -UPDATE_ROMCOLLECTION = """ - UPDATE romcollections SET - name=?, platform=?, box_size=?, - default_icon=?, default_fanart=?, default_banner=?, default_poster=?, default_controller=?, default_clearlogo=?, - roms_default_icon=?, roms_default_fanart=?, roms_default_banner=?, roms_default_poster=?, roms_default_clearlogo=? - WHERE id =? - """ -UPDATE_ROMCOLLECTION_PARENT = "UPDATE romcollections SET parent_id = ? WHERE id =?" -DELETE_ROMCOLLECTION = "DELETE FROM romcollections WHERE id = ?" - -SELECT_ROMCOLLECTION_ASSETS_BY_SET = "SELECT * FROM vw_romcollection_assets WHERE romcollection_id = ?" -SELECT_ROOT_ROMCOLLECTION_ASSETS = "SELECT * FROM vw_romcollection_assets WHERE parent_id IS NULL" -SELECT_ROMCOLLECTIONS_ASSETS_BY_PARENT = "SELECT * FROM vw_romcollection_assets WHERE parent_id = ?" -SELECT_ROMCOLLECTION_ASSETS_BY_ROM = "SELECT ra.* FROM vw_romcollection_assets AS ra INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = ra.romcollection_id WHERE rr.rom_id = ?" -SELECT_ROMCOLLECTION_ASSETS = "SELECT * FROM vw_romcollection_assets" -SELECT_ROMCOLLECTION_ASSET_PATHS = "SELECT * FROM vw_romcollection_asset_paths WHERE romcollection_id = ?" +INSERT_ROMCOLLECTION = """ + INSERT INTO romcollections (id,name,parent_id,metadata_id,platform,box_size) + VALUES (?,?,?,?,?,?) + """ +UPDATE_ROMCOLLECTION = "UPDATE romcollections SET name=?, platform=?, box_size=? WHERE id =?" +UPDATE_ROMCOLLECTION_PARENT = "UPDATE romcollections SET parent_id = ? WHERE id =?" +DELETE_ROMCOLLECTION = "DELETE FROM romcollections WHERE id = ?" + +SELECT_ROMCOLLECTION_ASSETS_BY_SET = "SELECT * FROM vw_romcollection_assets WHERE romcollection_id = ?" +SELECT_ROOT_ROMCOLLECTION_ASSETS = "SELECT * FROM vw_romcollection_assets WHERE parent_id IS NULL" +SELECT_ROMCOLLECTIONS_ASSETS_BY_PARENT = "SELECT * FROM vw_romcollection_assets WHERE parent_id = ?" +SELECT_ROMCOLLECTION_ASSETS_BY_ROM = "SELECT ra.* FROM vw_romcollection_assets AS ra INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = ra.romcollection_id WHERE rr.rom_id = ?" +SELECT_ROMCOLLECTION_ASSETS = "SELECT * FROM vw_romcollection_assets" +SELECT_ROMCOLLECTION_ASSET_PATHS = "SELECT * FROM vw_romcollection_asset_paths WHERE romcollection_id = ?" SELECT_ROMCOLLECTION_ASSETS_PATHS_BY_ROM = "SELECT rap.* FROM vw_romcollection_asset_paths AS rap INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rap.romcollection_id WHERE rr.rom_id = ?" +SELECT_ROMCOLLECTION_ASSET_MAPPINGS = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON mm.metadata_id = rc.metadata_id + """ +SELECT_ROOT_ROMCOLLECTION_ASSET_MAPPINGS = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON mm.metadata_id = rc.metadata_id + WHERE parent_id IS NULL + """ +SELECT_ROMCOLLECTION_ASSET_MAPPINGS_BY_PARENT = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON mm.metadata_id = rc.metadata_id + WHERE parent_id = ? + """ +SELECT_ROMCOLLECTION_ASSET_MAPPINGS_BY_ROM = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON mm.metadata_id = rc.metadata_id + INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rc.id WHERE rr.rom_id = ? + """ +SELECT_SPECIFIC_ROMCOLLECTION_ROM_ASSET_MAPPINGS = """ + SELECT am.*, rm.romcollection_id FROM assetmappings AS am + INNER JOIN romcollection_roms_assetmappings AS rm ON rm.assetmapping_id = am.id + AND rm.romcollection_id = ? + """ +SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS = """ + SELECT am.*, rm.romcollection_id FROM assetmappings AS am + INNER JOIN romcollection_roms_assetmappings AS rm ON rm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON rm.romcollection_id = rc.id + """ +SELECT_ROOT_ROMCOLLECTION_ROM_ASSET_MAPPINGS = """ + SELECT am.*, rm.romcollection_id FROM assetmappings AS am + INNER JOIN romcollection_roms_assetmappings AS rm ON rm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON rm.romcollection_id = rc.id + WHERE parent_id IS NULL + """ +SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS_BY_PARENT = """ + SELECT am.*, rm.romcollection_id FROM assetmappings AS am + INNER JOIN romcollection_roms_assetmappings AS rm ON rm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON rm.romcollection_id = rc.id + WHERE parent_id = ? + """ +SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS_BY_ROM = """ + SELECT am.*, rm.romcollection_id FROM assetmappings AS am + INNER JOIN romcollection_roms_assetmappings AS rm ON rm.assetmapping_id = am.id + INNER JOIN romcollections AS rc ON rm.romcollection_id = rc.id + INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rc.id WHERE rr.rom_id = ? + """ -INSERT_ROMCOLLECTION_ASSET = "INSERT INTO romcollection_assets (romcollection_id, asset_id) VALUES (?, ?)" -INSERT_ROMCOLLECTION_ASSET_PATH = "INSERT INTO romcollection_assetpaths (romcollection_id, assetpaths_id) VALUES (?, ?)" +INSERT_ROMCOLLECTION_ASSET = "INSERT INTO romcollection_assets (romcollection_id, asset_id) VALUES (?, ?)" +INSERT_ROMCOLLECTION_ASSET_PATH = "INSERT INTO romcollection_assetpaths (romcollection_id, assetpaths_id) VALUES (?, ?)" +INSERT_ROMCOLLECTION_ROM_ASSET_MAPPING = "INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) VALUES (?,?)" -INSERT_ROM_IN_ROMCOLLECTION = "INSERT INTO roms_in_romcollection (rom_id, romcollection_id) VALUES (?,?)" -REMOVE_ROM_FROM_ROMCOLLECTION = "DELETE FROM roms_in_romcollection WHERE rom_id = ? AND romcollection_id = ?" -REMOVE_ROMS_FROM_ROMCOLLECTION = "DELETE FROM roms_in_romcollection WHERE romcollection_id = ?" +INSERT_ROM_IN_ROMCOLLECTION = "INSERT INTO roms_in_romcollection (rom_id, romcollection_id) VALUES (?,?)" +REMOVE_ROM_FROM_ROMCOLLECTION = "DELETE FROM roms_in_romcollection WHERE rom_id = ? AND romcollection_id = ?" +REMOVE_ROMS_FROM_ROMCOLLECTION = "DELETE FROM roms_in_romcollection WHERE romcollection_id = ?" -SELECT_ROMCOLLECTION_LAUNCHERS = "SELECT * FROM vw_romcollection_launchers WHERE romcollection_id = ?" -INSERT_ROMCOLLECTION_LAUNCHER = "INSERT INTO romcollection_launchers (id, romcollection_id, akl_addon_id, settings, is_default) VALUES (?,?,?,?,?)" -UPDATE_ROMCOLLECTION_LAUNCHER = "UPDATE romcollection_launchers SET settings = ?, is_default = ? WHERE id = ?" -DELETE_ROMCOLLECTION_LAUNCHERS = "DELETE FROM romcollection_launchers WHERE romcollection_id = ?" -DELETE_ROMCOLLECTION_LAUNCHER = "DELETE FROM romcollection_launchers WHERE romcollection_id = ? AND id = ?" +SELECT_ROMCOLLECTION_LAUNCHERS = "SELECT * FROM vw_romcollection_launchers WHERE romcollection_id = ?" +INSERT_ROMCOLLECTION_LAUNCHER = "INSERT INTO romcollection_launchers (id, romcollection_id, akl_addon_id, settings, is_default) VALUES (?,?,?,?,?)" +UPDATE_ROMCOLLECTION_LAUNCHER = "UPDATE romcollection_launchers SET settings = ?, is_default = ? WHERE id = ?" +DELETE_ROMCOLLECTION_LAUNCHERS = "DELETE FROM romcollection_launchers WHERE romcollection_id = ?" +DELETE_ROMCOLLECTION_LAUNCHER = "DELETE FROM romcollection_launchers WHERE romcollection_id = ? AND id = ?" -SELECT_ROMCOLLECTION_SCANNERS = "SELECT * FROM vw_romcollection_scanners WHERE romcollection_id = ?" -INSERT_ROMCOLLECTION_SCANNER = "INSERT INTO romcollection_scanners (id, romcollection_id, akl_addon_id, settings) VALUES (?,?,?,?)" -UPDATE_ROMCOLLECTION_SCANNER = "UPDATE romcollection_scanners SET settings = ? WHERE id = ?" -DELETE_ROMCOLLECTION_SCANNER = "DELETE FROM romcollection_scanners WHERE romcollection_id = ? AND id = ?" +SELECT_ROMCOLLECTION_SCANNERS = "SELECT * FROM vw_romcollection_scanners WHERE romcollection_id = ?" +INSERT_ROMCOLLECTION_SCANNER = "INSERT INTO romcollection_scanners (id, romcollection_id, akl_addon_id, settings) VALUES (?,?,?,?)" +UPDATE_ROMCOLLECTION_SCANNER = "UPDATE romcollection_scanners SET settings = ? WHERE id = ?" +DELETE_ROMCOLLECTION_SCANNER = "DELETE FROM romcollection_scanners WHERE romcollection_id = ? AND id = ?" SELECT_ROMCOLLECTION_LAUNCHERS_BY_ROM = "SELECT rl.* FROM vw_romcollection_launchers AS rl INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rl.romcollection_id WHERE rr.rom_id = ?" -SELECT_ROMCOLLECTION_SCANNERS_BY_ROM = "SELECT rs.* FROM vw_romcollection_scanners AS rs INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rs.romcollection_id WHERE rr.rom_id = ?" +SELECT_ROMCOLLECTION_SCANNERS_BY_ROM = "SELECT rs.* FROM vw_romcollection_scanners AS rs INNER JOIN roms_in_romcollection AS rr ON rr.romcollection_id = rs.romcollection_id WHERE rr.rom_id = ?" # # ROMsRepository -> ROMs from SQLite DB # -SELECT_ROM = "SELECT * FROM vw_roms WHERE id = ?" -SELECT_ROM_ASSETS = "SELECT * FROM vw_rom_assets WHERE rom_id = ?" -SELECT_ROM_ASSETPATHS = "SELECT * FROM vw_rom_asset_paths WHERE rom_id = ?" -SELECT_ROM_TAGS = "SELECT * FROM vw_rom_tags WHERE rom_id = ?" - -SELECT_ROMS_BY_SET = "SELECT r.* FROM vw_roms AS r INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = r.id AND rs.romcollection_id = ?" -SELECT_ROM_ASSETS_BY_SET = "SELECT ra.* FROM vw_rom_assets AS ra INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = ra.rom_id AND rs.romcollection_id = ?" -SELECT_ROM_ASSETPATHS_BY_SET = "SELECT rap.* FROM vw_rom_asset_paths AS rap INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = rap.rom_id AND rs.romcollection_id = ?" -SELECT_ROM_TAGS_BY_SET = "SELECT rt.* FROM vw_rom_tags AS rt INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = rt.rom_id AND rs.romcollection_id = ?" - -SELECT_ROMS_BY_CATEGORY = "SELECT r.* FROM vw_roms AS r INNER JOIN roms_in_category AS rc ON rc.rom_id = r.id AND rc.category_id = ?" -SELECT_ROM_ASSETS_BY_CATEGORY = "SELECT ra.* FROM vw_rom_assets AS ra INNER JOIN roms_in_category AS rc ON rc.rom_id = ra.rom_id AND rc.category_id = ?" +SELECT_ROM = "SELECT * FROM vw_roms WHERE id = ?" +SELECT_ROM_ASSETS = "SELECT * FROM vw_rom_assets WHERE rom_id = ?" +SELECT_ROM_ASSETPATHS = "SELECT * FROM vw_rom_asset_paths WHERE rom_id = ?" +SELECT_ROM_TAGS = "SELECT * FROM vw_rom_tags WHERE rom_id = ?" +SELECT_ROM_ASSET_MAPPINGS = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN roms AS r ON mm.metadata_id = r.metadata_id + AND r.id = ? + """ + +SELECT_ROMS_BY_SET = "SELECT r.* FROM vw_roms AS r INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = r.id AND rs.romcollection_id = ?" +SELECT_ROM_ASSETS_BY_SET = "SELECT ra.* FROM vw_rom_assets AS ra INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = ra.rom_id AND rs.romcollection_id = ?" +SELECT_ROM_ASSETPATHS_BY_SET = "SELECT rap.* FROM vw_rom_asset_paths AS rap INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = rap.rom_id AND rs.romcollection_id = ?" +SELECT_ROM_TAGS_BY_SET = "SELECT rt.* FROM vw_rom_tags AS rt INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = rt.rom_id AND rs.romcollection_id = ?" +SELECT_ROM_ASSET_MAPPINGS_BY_SET = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN roms AS r ON mm.metadata_id = r.metadata_id + INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = r.id + AND rs.romcollection_id = ? + """ + +SELECT_ROMS_BY_CATEGORY = "SELECT r.* FROM vw_roms AS r INNER JOIN roms_in_category AS rc ON rc.rom_id = r.id AND rc.category_id = ?" +SELECT_ROM_ASSETS_BY_CATEGORY = "SELECT ra.* FROM vw_rom_assets AS ra INNER JOIN roms_in_category AS rc ON rc.rom_id = ra.rom_id AND rc.category_id = ?" SELECT_ROM_ASSETPATHS_BY_CATEGORY = "SELECT rap.* FROM vw_rom_asset_paths AS rap INNER JOIN roms_in_category AS rc ON rc.rom_id = rap.rom_id AND rc.category_id = ?" -SELECT_ROM_TAGS_BY_CATEGORY = "SELECT rt.* FROM vw_rom_tags AS rt INNER JOIN roms_in_category AS rc ON rc.rom_id = rt.rom_id AND rc.category_id = ?" +SELECT_ROM_TAGS_BY_CATEGORY = "SELECT rt.* FROM vw_rom_tags AS rt INNER JOIN roms_in_category AS rc ON rc.rom_id = rt.rom_id AND rc.category_id = ?" +SELECT_ROM_ASSET_MAPPINGS_BY_CATEGORY = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN roms AS r ON mm.metadata_id = r.metadata_id + INNER JOIN roms_in_category AS rc ON rc.rom_id = r.id + AND rc.category_id = ? + """ SELECT_ROMS_BY_ROOT_CATEGORY = "SELECT r.* FROM vw_roms AS r INNER JOIN roms_in_category AS rc ON rc.rom_id = r.id AND rc.category_id IS NULL" SELECT_ROM_ASSETS_BY_ROOT_CATEGORY = "SELECT ra.* FROM vw_rom_assets AS ra INNER JOIN roms_in_category AS rc ON rc.rom_id = ra.rom_id AND rc.category_id IS NULL" SELECT_ROM_ASSETPATHS_BY_ROOT_CATEGORY = "SELECT rap.* FROM vw_rom_asset_paths AS rap INNER JOIN roms_in_category AS rc ON rc.rom_id = rap.rom_id AND rc.category_id IS NULL" SELECT_ROM_TAGS_BY_ROOT_CATEGORY = "SELECT rt.* FROM vw_rom_tags AS rt INNER JOIN roms_in_category AS rc ON rc.rom_id = rt.rom_id AND rc.category_id IS NULL" - +SELECT_ROM_ASSET_MAPPINGS_BY_ROOT_CATEGORY = """ + SELECT am.*, mm.metadata_id FROM assetmappings AS am + INNER JOIN metadata_assetmappings AS mm ON mm.assetmapping_id = am.id + INNER JOIN roms AS r ON mm.metadata_id = r.metadata_id + INNER JOIN roms_in_category AS rc ON rc.rom_id = r.id + AND rc.category_id IS NULL + """ # Filter values SELECT_GENRES_BY_COLLECTION = "SELECT DISTINCT(r.m_genre) AS genre FROM vw_roms AS r INNER JOIN roms_in_romcollection AS rs ON rs.rom_id = r.id AND rs.romcollection_id = ? ORDER BY genre" diff --git a/resources/lib/report.py b/resources/lib/report.py index 32e57e05..17f2e2a8 100644 --- a/resources/lib/report.py +++ b/resources/lib/report.py @@ -49,7 +49,7 @@ def report_print_ROM(slist: list, rom: ROM): # >> Assets/artwork asset_infos = g_assetFactory.get_asset_kinds_for_roms() for asset_info in asset_infos: - slist.append("[COLOR violet]{0}[/COLOR]: '{1}'".format(asset_info.key, rom.get_asset(asset_info))) + slist.append("[COLOR violet]{0}[/COLOR]: '{1}'".format(asset_info.id, rom.get_asset(asset_info))) #return info_text return slist diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 63cde2d5..0814869a 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -10,7 +10,7 @@ from resources.lib import globals from resources.lib import queries as qry -from resources.lib.domain import Category, ROMCollection, ROM, Asset, AssetPath, VirtualCollection +from resources.lib.domain import MetaDataItemABC, Category, ROMCollection, ROM, Asset, AssetPath, AssetMapping, RomAssetMapping, VirtualCollection from resources.lib.domain import VirtualCategoryFactory, VirtualCollectionFactory, ROMLauncherAddonFactory, g_assetFactory from resources.lib.domain import ROMCollectionScanner, ROMLauncherAddon, AelAddon @@ -149,7 +149,7 @@ def get_categories(self) -> typing.Iterator[Category]: category_temp[xml_tag] = text_XML_line if xml_tag.startswith('s_'): - asset_info = g_assetFactory.get_asset_info_by_key(xml_tag) + asset_info = g_assetFactory.get_asset_info(xml_tag[2:]) asset_data = { 'filepath': text_XML_line, 'asset_type': asset_info.id } assets.append(Asset(asset_data)) @@ -188,7 +188,7 @@ def get_launchers(self) -> typing.Iterator[ROMCollection]: launcher_temp[xml_tag] = text_XML_line if xml_tag.startswith('s_'): - asset_info = g_assetFactory.get_asset_info_by_key(xml_tag) + asset_info = g_assetFactory.get_asset_info(xml_tag[2:]) asset_data = { 'filepath': text_XML_line, 'asset_type': asset_info.id } assets.append(Asset(asset_data)) @@ -269,7 +269,7 @@ def _get_assets_from_romdata(self, rom_data: dict) -> typing.List[Asset]: assets = [] for key, value in rom_data.items(): if key.startswith('s_'): - asset_info = g_assetFactory.get_asset_info_by_key(key) + asset_info = g_assetFactory.get_asset_info(key[2:]) asset_data = { 'filepath': value, 'asset_type': asset_info.id } assets.append(Asset(asset_data)) return assets @@ -349,16 +349,22 @@ def reset_database(self, schema_file_path: io.FileName): self.create_empty_database(schema_file_path) - def migrate_database(self, migration_files:typing.List[io.FileName]): + def migrate_database(self, migration_files:typing.List[io.FileName], new_db_version): + # make copy for backup + backup_filepath = self._db_path.changeExtension(".db.bak") + if backup_filepath.exists(): + backup_filepath.unlink() + self._db_path.copy(backup_filepath) + self.open_session() for migration_file in migration_files: self.logger.info(f'Executing migration script: {migration_file.getPath()}') sql_statements = migration_file.loadFileToStr() self.execute_script(sql_statements) - - self.logger.info(f'Updating database schema version of app {globals.addon_id} to {globals.addon_version}') - self.conn.execute("UPDATE akl_version SET version=? WHERE app=?", [globals.addon_version, globals.addon_id]) + + self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') + self.conn.execute("UPDATE akl_version SET version=? WHERE app=?", [globals.addon_version, str(new_db_version)]) self.commit() self.close_session() @@ -439,12 +445,17 @@ def find_category(self, category_id: str) -> Category: self._uow.execute(qry.SELECT_CATEGORY_ASSETS, category_id) assets_result_set = self._uow.result_set() - assets = [] for asset_data in assets_result_set: assets.append(Asset(asset_data)) + + self._uow.execute(qry.SELECT_ITEM_ASSET_MAPPINGS, category_data['metadata_id']) + asset_mappings_result_set = self._uow.result_set() + asset_mappings = [] + for mapping_data in asset_mappings_result_set: + asset_mappings.append(AssetMapping(mapping_data)) - return Category(category_data, assets) + return Category(category_data, assets, asset_mappings) def find_root_categories(self) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_ROOT_CATEGORIES) @@ -452,13 +463,20 @@ def find_root_categories(self) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_ROOT_CATEGORY_ASSETS) assets_result_set = self._uow.result_set() - + + self._uow.execute(qry.SELECT_ROOT_CATEGORY_ASSET_MAPPINGS) + asset_mappings_result_set = self._uow.result_set() + for category_data in result_set: assets = [] for asset_data in filter(lambda a: a['category_id'] == category_data['id'], assets_result_set): assets.append(Asset(asset_data)) + + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == category_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) - yield Category(category_data, assets) + yield Category(category_data, assets, asset_mappings) def find_categories_by_parent(self, category_id) -> typing.Iterator[Category]: if category_id == constants.VCATEGORY_ROOT_ID: @@ -473,13 +491,20 @@ def find_categories_by_parent(self, category_id) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_CATEGORY_ASSETS_BY_PARENT, category_id) assets_result_set = self._uow.result_set() - + + self._uow.execute(qry.SELECT_CATEGORY_ASSET_MAPPINGS_BY_PARENT, category_id) + asset_mappings_result_set = self._uow.result_set() + for category_data in result_set: assets = [] for asset_data in filter(lambda a: a['category_id'] == category_data['id'], assets_result_set): assets.append(Asset(asset_data)) - - yield Category(category_data, assets) + + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == category_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) + + yield Category(category_data, assets, asset_mappings) def find_all_categories(self) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_CATEGORIES) @@ -487,13 +512,20 @@ def find_all_categories(self) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_ALL_CATEGORY_ASSETS) assets_result_set = self._uow.result_set() - + + self._uow.execute(qry.SELECT_ALL_CATEGORY_ASSET_MAPPINGS) + asset_mappings_result_set = self._uow.result_set() + for category_data in result_set: assets = [] for asset_data in filter(lambda a: a['category_id'] == category_data['id'], assets_result_set): assets.append(Asset(asset_data)) + + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == category_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) - yield Category(category_data, assets) + yield Category(category_data, assets, asset_mappings) def find_categories_by_rom(self, rom_id: str) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_CATEGORIES_BY_ROM, rom_id) @@ -502,12 +534,19 @@ def find_categories_by_rom(self, rom_id: str) -> typing.Iterator[Category]: self._uow.execute(qry.SELECT_CATEGORIES_ASSETS_BY_ROM, rom_id) assets_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_CATEGORY_ASSET_MAPPINGS_BY_ROM, rom_id) + asset_mappings_result_set = self._uow.result_set() + for category_data in result_set: assets = [] for asset_data in filter(lambda a: a['category_id'] == category_data['id'], assets_result_set): assets.append(Asset(asset_data)) - yield Category(category_data, assets) + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == category_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) + + yield Category(category_data, assets, asset_mappings) def insert_category(self, category_obj: Category, parent_obj: Category = None): self.logger.info("CategoryRepository.insert_category(): Inserting new category '{}'".format(category_obj.get_name())) @@ -529,19 +568,17 @@ def insert_category(self, category_obj: Category, parent_obj: Category = None): category_obj.get_id(), category_obj.get_name(), parent_category_id, - metadata_id, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_ICON_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_FANART_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key) + metadata_id) category_assets = category_obj.get_assets() for asset in category_assets: - self._insert_asset(asset, category_obj) - + self._insert_asset(asset, category_obj) + + for mapping in category_obj.asset_mappings: + self._insert_asset_mapping(mapping, category_obj) + def update_category(self, category_obj: Category): - self.logger.info("CategoryRepository.update_category(): Updating category '{}'".format(category_obj.get_name())) + self.logger.info(f" Updating category '{category_obj.get_name()}'") assets_path = category_obj.get_assets_root_path() self._uow.execute(qry.UPDATE_METADATA, @@ -556,16 +593,19 @@ def update_category(self, category_obj: Category): self._uow.execute(qry.UPDATE_CATEGORY, category_obj.get_name(), - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_ICON_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_FANART_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - category_obj.get_mapped_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key, category_obj.get_id()) for asset in category_obj.get_assets(): - if asset.get_id() == '': self._insert_asset(asset, category_obj) - else: self._update_asset(asset, category_obj) + if asset.get_id() == '': + self._insert_asset(asset, category_obj) + else: + self._update_asset(asset, category_obj) + + for mapping in category_obj.asset_mappings: + if mapping.get_id() == '': + self._insert_asset_mapping(mapping, category_obj) + else: + self._update_asset_mapping(mapping, category_obj) def delete_category(self, category_id: str): self.logger.info("CategoryRepository.delete_category(): Deleting category '{}'".format(category_id)) @@ -592,7 +632,21 @@ def _update_asset(self, asset: Asset, category_obj: Category): self._uow.execute(qry.UPDATE_ASSET, asset.get_path(), asset.get_asset_info_id(), asset.get_id()) if asset.get_custom_attribute('category_id') is None: self._uow.execute(qry.INSERT_CATEGORY_ASSET, category_obj.get_id(), asset.get_id()) + + def _insert_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if not mapping.is_mapped(): + return + mapping_db_id = text.misc_generate_random_SID() + self._uow.execute(qry.INSERT_ASSET_MAPPING, mapping_db_id, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id) + self._uow.execute(qry.INSERT_MAPPING_WITH_METADATA, obj.get_metadata_id(), mapping_db_id) + + def _update_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if mapping.is_mapped(): + self._uow.execute(qry.UPDATE_ASSET_MAPPING, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id, mapping.get_id()) + return + self._uow.execute(qry.DELETE_ASSET_MAPPING, mapping.get_id()) + # # ROMCollectionRepository -> ROM Sets from SQLite DB # @@ -624,6 +678,18 @@ def find_romcollection(self, romcollection_id: str) -> ROMCollection: for asset_paths_data in asset_paths_result_set: asset_paths.append(AssetPath(asset_paths_data)) + self._uow.execute(qry.SELECT_ITEM_ASSET_MAPPINGS, romcollection_data['metadata_id']) + asset_mappings_result_set = self._uow.result_set() + asset_mappings = [] + for mapping_data in asset_mappings_result_set: + asset_mappings.append(AssetMapping(mapping_data)) + + self._uow.execute(qry.SELECT_SPECIFIC_ROMCOLLECTION_ROM_ASSET_MAPPINGS, romcollection_data['id']) + rom_asset_mappings_result_set = self._uow.result_set() + rom_asset_mappings = [] + for mapping_data in rom_asset_mappings_result_set: + rom_asset_mappings.append(RomAssetMapping(mapping_data)) + self._uow.execute(qry.SELECT_ROMCOLLECTION_LAUNCHERS, romcollection_id) launchers_data = self._uow.result_set() launchers = [] @@ -639,7 +705,7 @@ def find_romcollection(self, romcollection_id: str) -> ROMCollection: addon = AelAddon(scanner_data.copy()) scanners.append(ROMCollectionScanner(addon, scanner_data)) - return ROMCollection(romcollection_data, assets, asset_paths, launchers, scanners) + return ROMCollection(romcollection_data, assets, asset_paths, asset_mappings, rom_asset_mappings, launchers, scanners) def find_all_romcollections(self) -> typing.Iterator[ROMCollection]: self._uow.execute(qry.SELECT_ROMCOLLECTIONS) @@ -648,12 +714,26 @@ def find_all_romcollections(self) -> typing.Iterator[ROMCollection]: self._uow.execute(qry.SELECT_ROMCOLLECTION_ASSETS) assets_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROMCOLLECTION_ASSET_MAPPINGS) + asset_mappings_result_set = self._uow.result_set() + + self._uow.execute(qry.SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS) + rom_asset_mappings_result_set = self._uow.result_set() + for romcollection_data in result_set: assets = [] for asset_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], assets_result_set): assets.append(Asset(asset_data)) - - yield ROMCollection(romcollection_data, assets) + + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == romcollection_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) + + rom_asset_mappings = [] + for mapping_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], rom_asset_mappings_result_set): + rom_asset_mappings.append(RomAssetMapping(mapping_data)) + + yield ROMCollection(romcollection_data, assets, asset_mappings=asset_mappings, rom_asset_mappings=rom_asset_mappings) def find_root_romcollections(self) -> typing.Iterator[ROMCollection]: self._uow.execute(qry.SELECT_ROOT_ROMCOLLECTIONS) @@ -662,12 +742,26 @@ def find_root_romcollections(self) -> typing.Iterator[ROMCollection]: self._uow.execute(qry.SELECT_ROOT_ROMCOLLECTION_ASSETS) assets_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROOT_ROMCOLLECTION_ASSET_MAPPINGS) + asset_mappings_result_set = self._uow.result_set() + + self._uow.execute(qry.SELECT_ROOT_ROMCOLLECTION_ROM_ASSET_MAPPINGS) + rom_asset_mappings_result_set = self._uow.result_set() + for romcollection_data in result_set: assets = [] for asset_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], assets_result_set): assets.append(Asset(asset_data)) - yield ROMCollection(romcollection_data, assets) + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == romcollection_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) + + rom_asset_mappings = [] + for mapping_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], rom_asset_mappings_result_set): + rom_asset_mappings.append(RomAssetMapping(mapping_data)) + + yield ROMCollection(romcollection_data, assets, asset_mappings=asset_mappings, rom_asset_mappings=rom_asset_mappings) def find_romcollections_by_parent(self, category_id:str) -> typing.Iterator[ROMCollection]: @@ -681,12 +775,26 @@ def find_romcollections_by_parent(self, category_id:str) -> typing.Iterator[ROMC self._uow.execute(qry.SELECT_ROMCOLLECTIONS_ASSETS_BY_PARENT, category_id) assets_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROMCOLLECTION_ASSET_MAPPINGS_BY_PARENT, category_id) + asset_mappings_result_set = self._uow.result_set() + + self._uow.execute(qry.SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS_BY_PARENT, category_id) + rom_asset_mappings_result_set = self._uow.result_set() + for romcollection_data in result_set: assets = [] for asset_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], assets_result_set): assets.append(Asset(asset_data)) + + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == romcollection_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) - yield ROMCollection(romcollection_data, assets) + rom_asset_mappings = [] + for mapping_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], rom_asset_mappings_result_set): + rom_asset_mappings.append(RomAssetMapping(mapping_data)) + + yield ROMCollection(romcollection_data, assets, asset_mappings=asset_mappings, rom_asset_mappings=rom_asset_mappings) def find_virtualcollections_by_category(self, vcategory_id:str) -> typing.Iterator[VirtualCollection]: query = self._get_collections_query_by_vcategory_id(vcategory_id) @@ -710,6 +818,12 @@ def find_romcollections_by_rom(self, rom_id:str) -> typing.Iterator[ROMCollectio self._uow.execute(qry.SELECT_ROMCOLLECTION_ASSETS_PATHS_BY_ROM, rom_id) asset_paths_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROMCOLLECTION_ASSET_MAPPINGS_BY_ROM, rom_id) + asset_mappings_result_set = self._uow.result_set() + + self._uow.execute(qry.SELECT_ROMCOLLECTION_ROM_ASSET_MAPPINGS_BY_ROM, rom_id) + rom_asset_mappings_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROMCOLLECTION_LAUNCHERS_BY_ROM, rom_id) launchers_data = self._uow.result_set() @@ -725,6 +839,14 @@ def find_romcollections_by_rom(self, rom_id:str) -> typing.Iterator[ROMCollectio for asset_path_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], asset_paths_result_set): asset_paths.append(AssetPath(asset_path_data)) + asset_mappings = [] + for mapping_data in filter(lambda a: a['metadata_id'] == romcollection_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(AssetMapping(mapping_data)) + + rom_asset_mappings = [] + for mapping_data in filter(lambda a: a['romcollection_id'] == romcollection_data['id'], rom_asset_mappings_result_set): + rom_asset_mappings.append(RomAssetMapping(mapping_data)) + launchers = [] for launcher_data in launchers_data: addon = AelAddon(launcher_data.copy()) @@ -736,7 +858,7 @@ def find_romcollections_by_rom(self, rom_id:str) -> typing.Iterator[ROMCollectio addon = AelAddon(scanner_data.copy()) scanners.append(ROMCollectionScanner(addon, scanner_data)) - yield ROMCollection(romcollection_data, assets, asset_paths, launchers, scanners) + yield ROMCollection(romcollection_data, assets, asset_paths, asset_mappings, rom_asset_mappings, launchers, scanners) def insert_romcollection(self, romcollection_obj: ROMCollection, parent_obj: Category = None): self.logger.info("ROMCollectionRepository.insert_romcollection(): Inserting new romcollection '{}'".format(romcollection_obj.get_name())) @@ -760,18 +882,7 @@ def insert_romcollection(self, romcollection_obj: ROMCollection, parent_obj: Cat parent_category_id, metadata_id, romcollection_obj.get_platform(), - romcollection_obj.get_box_sizing(), - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_ICON_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_FANART_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_CONTROLLER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_ICON_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_FANART_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key) + romcollection_obj.get_box_sizing()) romcollection_assets = romcollection_obj.get_assets() for asset in romcollection_assets: @@ -780,7 +891,13 @@ def insert_romcollection(self, romcollection_obj: ROMCollection, parent_obj: Cat asset_paths = romcollection_obj.get_asset_paths() for asset_path in asset_paths: self._insert_asset_path(asset_path, romcollection_obj) - + + for mapping in romcollection_obj.asset_mappings: + self._insert_asset_mapping(mapping, romcollection_obj) + + for mapping in romcollection_obj.rom_asset_mappings: + self._insert_rom_asset_mapping(mapping, romcollection_obj) + romcollection_launchers = romcollection_obj.get_launchers() for romcollection_launcher in romcollection_launchers: romcollection_launcher.set_id(text.misc_generate_random_SID()) @@ -818,17 +935,6 @@ def update_romcollection(self, romcollection_obj: ROMCollection): romcollection_obj.get_name(), romcollection_obj.get_platform(), romcollection_obj.get_box_sizing(), - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_ICON_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_FANART_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_CONTROLLER_ID).key, - romcollection_obj.get_mapped_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_ICON_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_FANART_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_BANNER_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_POSTER_ID).key, - romcollection_obj.get_mapped_ROM_asset_info(asset_id=constants.ASSET_CLEARLOGO_ID).key, romcollection_obj.get_id()) romcollection_launchers = romcollection_obj.get_launchers() @@ -869,6 +975,18 @@ def update_romcollection(self, romcollection_obj: ROMCollection): if asset_path.get_id() == '': self._insert_asset_path(asset_path, romcollection_obj) else: self._update_asset_path(asset_path, romcollection_obj) + for mapping in romcollection_obj.asset_mappings: + if mapping.get_id() == '': + self._insert_asset_mapping(mapping, romcollection_obj) + else: + self._update_asset_mapping(mapping, romcollection_obj) + + for mapping in romcollection_obj.rom_asset_mappings: + if mapping.get_id() == '': + self._insert_rom_asset_mapping(mapping, romcollection_obj) + else: + self._update_rom_asset_mapping(mapping, romcollection_obj) + def update_romcollection_parent_reference(self, romcollection_obj: ROMCollection, parent_obj: Category = None): self.logger.info(f"ROMCollectionRepository.update_romcollection_parent_reference(): Updating romcollection '{romcollection_obj.get_name()}'") parent_category_id = parent_obj.get_id() if parent_obj is not None and parent_obj.get_id() != constants.VCATEGORY_ADDONROOT_ID else None @@ -912,17 +1030,50 @@ def _update_asset_path(self, asset_path: AssetPath, romcollection_obj: ROMCollec self._uow.execute(qry.UPDATE_ASSET_PATH, asset_path.get_path(), asset_path.get_asset_info_id(), asset_path.get_id()) if asset_path.get_custom_attribute('romcollection_id') is None: self._uow.execute(qry.INSERT_ROMCOLLECTION_ASSET_PATH, romcollection_obj.get_id(), asset_path.get_id()) - - def _get_collections_query_by_vcategory_id(self, vcategory_id:str) -> str: - - if vcategory_id == constants.VCATEGORY_TITLE_ID: return qry.SELECT_VCOLLECTION_TITLES - if vcategory_id == constants.VCATEGORY_GENRE_ID: return qry.SELECT_VCOLLECTION_GENRES - if vcategory_id == constants.VCATEGORY_DEVELOPER_ID:return qry.SELECT_VCOLLECTION_DEVELOPER - if vcategory_id == constants.VCATEGORY_ESRB_ID: return qry.SELECT_VCOLLECTION_ESRB - if vcategory_id == constants.VCATEGORY_PEGI_ID: return qry.SELECT_VCOLLECTION_PEGI - if vcategory_id == constants.VCATEGORY_YEARS_ID: return qry.SELECT_VCOLLECTION_YEAR - if vcategory_id == constants.VCATEGORY_NPLAYERS_ID: return qry.SELECT_VCOLLECTION_NPLAYERS - if vcategory_id == constants.VCATEGORY_RATING_ID: return qry.SELECT_VCOLLECTION_RATING + + def _insert_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if not mapping.is_mapped(): + return + mapping_db_id = text.misc_generate_random_SID() + self._uow.execute(qry.INSERT_ASSET_MAPPING, mapping_db_id, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id) + self._uow.execute(qry.INSERT_MAPPING_WITH_METADATA, obj.get_metadata_id(), mapping_db_id) + + def _update_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if mapping.is_mapped(): + self._uow.execute(qry.UPDATE_ASSET_MAPPING, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id, mapping.get_id()) + return + self._uow.execute(qry.DELETE_ASSET_MAPPING, mapping.get_id()) + + def _insert_rom_asset_mapping(self, mapping: RomAssetMapping, obj: ROMCollection): + if not mapping.is_mapped(): + return + mapping_db_id = text.misc_generate_random_SID() + self._uow.execute(qry.INSERT_ASSET_MAPPING, mapping_db_id, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id) + self._uow.execute(qry.INSERT_ROMCOLLECTION_ROM_ASSET_MAPPING, obj.get_id(), mapping_db_id) + + def _update_rom_asset_mapping(self, mapping: RomAssetMapping, obj: MetaDataItemABC): + if mapping.is_mapped(): + self._uow.execute(qry.UPDATE_ASSET_MAPPING, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id, mapping.get_id()) + return + self._uow.execute(qry.DELETE_ASSET_MAPPING, mapping.get_id()) + + def _get_collections_query_by_vcategory_id(self, vcategory_id:str) -> str: + if vcategory_id == constants.VCATEGORY_TITLE_ID: + return qry.SELECT_VCOLLECTION_TITLES + if vcategory_id == constants.VCATEGORY_GENRE_ID: + return qry.SELECT_VCOLLECTION_GENRES + if vcategory_id == constants.VCATEGORY_DEVELOPER_ID: + return qry.SELECT_VCOLLECTION_DEVELOPER + if vcategory_id == constants.VCATEGORY_ESRB_ID: + return qry.SELECT_VCOLLECTION_ESRB + if vcategory_id == constants.VCATEGORY_PEGI_ID: + return qry.SELECT_VCOLLECTION_PEGI + if vcategory_id == constants.VCATEGORY_YEARS_ID: + return qry.SELECT_VCOLLECTION_YEAR + if vcategory_id == constants.VCATEGORY_NPLAYERS_ID: + return qry.SELECT_VCOLLECTION_NPLAYERS + if vcategory_id == constants.VCATEGORY_RATING_ID: + return qry.SELECT_VCOLLECTION_RATING return None @@ -942,6 +1093,9 @@ def find_root_roms(self)-> typing.Iterator[ROM]: self._uow.execute(qry.SELECT_ROM_ASSETPATHS_BY_ROOT_CATEGORY) asset_paths_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROM_ASSET_MAPPINGS_BY_ROOT_CATEGORY) + asset_mappings_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROM_SCANNED_DATA_BY_ROOT_CATEGORY) scanned_data_result_set = self._uow.result_set() @@ -951,11 +1105,14 @@ def find_root_roms(self)-> typing.Iterator[ROM]: for rom_data in result_set: assets = [] asset_paths = [] + asset_mappings = [] tags = {} for asset_data in filter(lambda a: a['rom_id'] == rom_data['id'], assets_result_set): assets.append(Asset(asset_data)) for asset_paths_data in filter(lambda a: a['rom_id'] == rom_data['id'], asset_paths_result_set): asset_paths.append(AssetPath(asset_paths_data)) + for mapping_data in filter(lambda a: a['metadata_id'] == rom_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(RomAssetMapping(mapping_data)) for tag in filter(lambda t: t['rom_id'] == rom_data['id'], tags_data_set): tags[tag['tag']] = tag['id'] @@ -963,7 +1120,7 @@ def find_root_roms(self)-> typing.Iterator[ROM]: entry['data_key']: entry['data_value'] for entry in filter(lambda s: s['rom_id'] == rom_data['id'], scanned_data_result_set) } - yield ROM(rom_data, tags, assets, asset_paths, scanned_data) + yield ROM(rom_data, tags, assets, asset_paths, asset_mappings, scanned_data) def find_roms_by_category(self, category: Category) -> typing.Iterator[ROM]: category_id = category.get_id() if category else None @@ -976,6 +1133,9 @@ def find_roms_by_category(self, category: Category) -> typing.Iterator[ROM]: self._uow.execute(qry.SELECT_ROM_ASSETPATHS_BY_CATEGORY, category_id) asset_paths_result_set = self._uow.result_set() + + self._uow.execute(qry.SELECT_ROM_ASSET_MAPPINGS_BY_CATEGORY, category_id) + asset_mappings_result_set = self._uow.result_set() self._uow.execute(qry.SELECT_ROM_SCANNED_DATA_BY_CATEGORY, category_id) scanned_data_result_set = self._uow.result_set() @@ -986,11 +1146,14 @@ def find_roms_by_category(self, category: Category) -> typing.Iterator[ROM]: for rom_data in result_set: assets = [] asset_paths = [] + asset_mappings = [] tags = {} for asset_data in filter(lambda a: a['rom_id'] == rom_data['id'], assets_result_set): assets.append(Asset(asset_data)) for asset_paths_data in filter(lambda a: a['rom_id'] == rom_data['id'], asset_paths_result_set): asset_paths.append(AssetPath(asset_paths_data)) + for mapping_data in filter(lambda a: a['metadata_id'] == rom_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(RomAssetMapping(mapping_data)) for tag in filter(lambda t: t['rom_id'] == rom_data['id'], tags_data_set): tags[tag['tag']] = tag['id'] @@ -998,7 +1161,7 @@ def find_roms_by_category(self, category: Category) -> typing.Iterator[ROM]: entry['data_key']: entry['data_value'] for entry in filter(lambda s: s['rom_id'] == rom_data['id'], scanned_data_result_set) } - yield ROM(rom_data, tags, assets, asset_paths, scanned_data) + yield ROM(rom_data, tags, assets, asset_paths, asset_mappings, scanned_data) def find_roms_by_romcollection(self, romcollection: ROMCollection) -> typing.Iterator[ROM]: is_virtual = romcollection.get_type() == constants.OBJ_COLLECTION_VIRTUAL @@ -1021,6 +1184,7 @@ def find_roms_by_romcollection(self, romcollection: ROMCollection) -> typing.Ite assets_result_set = self._uow.result_set() asset_paths_result_set = [] + asset_mappings_result_set = [] scanned_data_result_set = [] tags_data_set = {} else: @@ -1032,7 +1196,10 @@ def find_roms_by_romcollection(self, romcollection: ROMCollection) -> typing.Ite self._uow.execute(qry.SELECT_ROM_ASSETPATHS_BY_SET, romcollection_id) asset_paths_result_set = self._uow.result_set() - + + self._uow.execute(qry.SELECT_ROM_ASSET_MAPPINGS_BY_SET, romcollection_id) + asset_mappings_result_set = self._uow.result_set() + self._uow.execute(qry.SELECT_ROM_SCANNED_DATA_BY_SET, romcollection_id) scanned_data_result_set = self._uow.result_set() @@ -1042,11 +1209,14 @@ def find_roms_by_romcollection(self, romcollection: ROMCollection) -> typing.Ite for rom_data in result_set: assets = [] asset_paths = [] + asset_mappings = [] tags = {} for asset_data in filter(lambda a: a['rom_id'] == rom_data['id'], assets_result_set): assets.append(Asset(asset_data)) for asset_paths_data in filter(lambda a: a['rom_id'] == rom_data['id'], asset_paths_result_set): asset_paths.append(AssetPath(asset_paths_data)) + for mapping_data in filter(lambda a: a['metadata_id'] == rom_data['metadata_id'], asset_mappings_result_set): + asset_mappings.append(RomAssetMapping(mapping_data)) for tag in filter(lambda t: t['rom_id'] == rom_data['id'], tags_data_set): tags[tag['tag']] = tag['id'] @@ -1054,7 +1224,7 @@ def find_roms_by_romcollection(self, romcollection: ROMCollection) -> typing.Ite entry['data_key']: entry['data_value'] for entry in filter(lambda s: s['rom_id'] == rom_data['id'], scanned_data_result_set) } - yield ROM(rom_data, tags, assets, asset_paths, scanned_data) + yield ROM(rom_data, tags, assets, asset_paths, asset_mappings, scanned_data) def find_rom(self, rom_id:str) -> ROM: self._uow.execute(qry.SELECT_ROM, rom_id) @@ -1071,7 +1241,13 @@ def find_rom(self, rom_id:str) -> ROM: asset_paths = [] for asset_paths_data in asset_paths_result_set: asset_paths.append(AssetPath(asset_paths_data)) - + + self._uow.execute(qry.SELECT_ITEM_ASSET_MAPPINGS, rom_data['metadata_id']) + asset_mappings_result_set = self._uow.result_set() + asset_mappings = [] + for mapping_data in asset_mappings_result_set: + asset_mappings.append(RomAssetMapping(mapping_data)) + self._uow.execute(qry.SELECT_ROM_SCANNED_DATA, rom_id) scanned_data_result_set = self._uow.result_set() scanned_data = { entry['data_key']: entry['data_value'] for entry in scanned_data_result_set } @@ -1090,7 +1266,7 @@ def find_rom(self, rom_id:str) -> ROM: for tag_data in tags_data: tags[tag_data['tag']] = tag_data['id'] - return ROM(rom_data, tags, assets, asset_paths, scanned_data, launchers) + return ROM(rom_data, tags, assets, asset_paths, asset_mappings, scanned_data, launchers) def find_all_tags(self) -> dict: self._uow.execute(qry.SELECT_TAGS) @@ -1138,6 +1314,9 @@ def insert_rom(self, rom_obj: ROM): if not asset_path.get_id(): self._insert_asset_path(asset_path, rom_obj) else: self._update_asset_path(asset_path, rom_obj) + for mapping in rom_obj.asset_mappings: + self._insert_asset_mapping(mapping, rom_obj) + tag_data = rom_obj.get_tag_data() self._insert_tags(tag_data, metadata_id) @@ -1182,6 +1361,12 @@ def update_rom(self, rom_obj: ROM): for asset_path in rom_obj.get_asset_paths(): if not asset_path.get_id(): self._insert_asset_path(asset_path, rom_obj) else: self._update_asset_path(asset_path, rom_obj) + + for mapping in rom_obj.asset_mappings: + if mapping.get_id() == '': + self._insert_asset_mapping(mapping, rom_obj) + else: + self._update_asset_mapping(mapping, rom_obj) tag_data = rom_obj.get_tag_data() self._update_tags(tag_data, rom_obj.get_custom_attribute('metadata_id')) @@ -1226,7 +1411,20 @@ def _update_asset_path(self, asset_path: AssetPath, rom_obj: ROM): self._uow.execute(qry.UPDATE_ASSET_PATH, asset_path.get_path(), asset_path.get_asset_info_id(), asset_path.get_id()) if asset_path.get_custom_attribute('rom_id') is None: self._uow.execute(qry.INSERT_ROM_ASSET_PATH, rom_obj.get_id(), asset_path.get_id()) - + + def _insert_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if not mapping.is_mapped(): + return + mapping_db_id = text.misc_generate_random_SID() + self._uow.execute(qry.INSERT_ASSET_MAPPING, mapping_db_id, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id) + self._uow.execute(qry.INSERT_MAPPING_WITH_METADATA, obj.get_metadata_id(), mapping_db_id) + + def _update_asset_mapping(self, mapping: AssetMapping, obj: MetaDataItemABC): + if mapping.is_mapped(): + self._uow.execute(qry.UPDATE_ASSET_MAPPING, mapping.get_asset_info().id, mapping.get_mapped_to_asset_info().id, mapping.get_id()) + return + self._uow.execute(qry.DELETE_ASSET_MAPPING, mapping.get_id()) + def _update_launchers(self, rom_id:str, rom_launchers:typing.List[ROMLauncherAddon]): for rom_launcher in rom_launchers: if rom_launcher.get_id() is None: @@ -1297,10 +1495,14 @@ def _get_queries_by_vcollection_type(self, vcollection:VirtualCollection) -> typ return None, None def _get_query_by_filter(self, filter:str) -> typing.Tuple[str, str]: - if filter == constants.META_GENRE_ID: return qry.SELECT_GENRES_BY_COLLECTION - if filter == constants.META_YEAR_ID: return qry.SELECT_YEARS_BY_COLLECTION - if filter == constants.META_DEVELOPER_ID: return qry.SELECT_DEVELOPER_BY_COLLECTION - if filter == constants.META_RATING_ID: return qry.SELECT_RATING_BY_COLLECTION + if filter == constants.META_GENRE_ID: + return qry.SELECT_GENRES_BY_COLLECTION + if filter == constants.META_YEAR_ID: + return qry.SELECT_YEARS_BY_COLLECTION + if filter == constants.META_DEVELOPER_ID: + return qry.SELECT_DEVELOPER_BY_COLLECTION + if filter == constants.META_RATING_ID: + return qry.SELECT_RATING_BY_COLLECTION return None class AelAddonRepository(object): diff --git a/resources/lib/services.py b/resources/lib/services.py index f2cd279b..c60197c2 100644 --- a/resources/lib/services.py +++ b/resources/lib/services.py @@ -57,9 +57,8 @@ def run(self): self._initial_setup(uow) db_version = uow.get_database_version() - current_version = kodi.get_addon_version() - logger.debug(f'db.id "{db_version}"') - if db_version is None or LooseVersion(db_version) < LooseVersion(current_version): + logger.debug(f'db.version "{db_version}"') + if db_version is None or LooseVersion(db_version) < LooseVersion(globals.addon_version): self._do_version_upgrade(uow, LooseVersion(db_version)) if self._last_time_scanned_is_too_long_ago(): @@ -112,8 +111,18 @@ def _do_version_upgrade(self, uow:UnitOfWork, db_version:LooseVersion): if LooseVersion(migration_file.getBaseNoExt()) > db_version: migrations_files_to_execute.append(migration_file) + if len(migrations_files_to_execute) == 0: + return + + logger.info(f"Found {len(migrations_files_to_execute)} migration files to process.") migrations_files_to_execute.sort(key = lambda f: (LooseVersion(f.getBaseNoExt()))) - uow.migrate_database(migrations_files_to_execute) + + version_to_store = LooseVersion(globals.addon_version) + file_version = LooseVersion(migrations_files_to_execute[-1].getBaseNoExt()) + if file_version > version_to_store: + version_to_store = file_version + + uow.migrate_database(migrations_files_to_execute, version_to_store) def _perform_scans(self): # SCAN FOR ADDONS diff --git a/resources/lib/viewqueries.py b/resources/lib/viewqueries.py index c147973c..7042ab2a 100644 --- a/resources/lib/viewqueries.py +++ b/resources/lib/viewqueries.py @@ -545,16 +545,16 @@ def qry_container_context_menu_items(container_data) -> typing.List[typing.Tuple if container_data is None: return [] # --- Create context menu items to be applied to each item in this container --- - container_type = container_data['obj_type'] if 'obj_type' in container_data else constants.OBJ_NONE - container_name = container_data['name'] if 'name' in container_data else 'Unknown' - container_id = container_data['id'] if 'id' in container_data else '' + container_type = container_data['obj_type'] if 'obj_type' in container_data else constants.OBJ_NONE + container_name = container_data['name'] if 'name' in container_data else 'Unknown' + container_id = container_data['id'] if 'id' in container_data else '' container_parentid = container_data['parent_id'] if 'parent_id' in container_data else '' - is_category: bool = container_type == constants.OBJ_CATEGORY - is_romcollection: bool = container_type == constants.OBJ_ROMCOLLECTION - is_virtual_category: bool = container_type == constants.OBJ_CATEGORY_VIRTUAL + is_category: bool = container_type == constants.OBJ_CATEGORY + is_romcollection: bool = container_type == constants.OBJ_ROMCOLLECTION + is_virtual_category: bool = container_type == constants.OBJ_CATEGORY_VIRTUAL is_virtual_collection: bool = container_type == constants.OBJ_COLLECTION_VIRTUAL - is_root: bool = container_data['id'] == '' + is_root: bool = container_data['id'] == '' commands = [] if is_category: @@ -586,21 +586,22 @@ def qry_listitem_context_menu_items(list_item_data, container_data)-> typing.Lis if container_data is None or list_item_data is None: return [] # --- Create context menu items only applicable on this item --- - properties = list_item_data['properties'] if 'properties' in list_item_data else {} - item_type = properties['obj_type'] if 'obj_type' in properties else constants.OBJ_NONE - item_name = list_item_data['name'] if 'name' in list_item_data else 'Unknown' - item_id = list_item_data['id'] if 'id' in list_item_data else '' + properties = list_item_data['properties'] if 'properties' in list_item_data else {} + item_type = properties['obj_type'] if 'obj_type' in properties else constants.OBJ_NONE + item_name = list_item_data['name'] if 'name' in list_item_data else 'Unknown' + item_id = list_item_data['id'] if 'id' in list_item_data else '' - container_id = container_data['id'] if 'id' in container_data else constants.VCATEGORY_ADDONROOT_ID - container_type = container_data['obj_type'] if 'obj_type' in container_data else constants.OBJ_NONE - if container_id == '': container_id = constants.VCATEGORY_ADDONROOT_ID + container_id = container_data['id'] if 'id' in container_data else constants.VCATEGORY_ADDONROOT_ID + container_type = container_data['obj_type'] if 'obj_type' in container_data else constants.OBJ_NONE + if container_id == '': + container_id = constants.VCATEGORY_ADDONROOT_ID container_is_category: bool = container_type == constants.OBJ_CATEGORY - is_category: bool = item_type == constants.OBJ_CATEGORY - is_romcollection: bool = item_type == constants.OBJ_ROMCOLLECTION - is_virtual_category: bool = item_type == constants.OBJ_CATEGORY_VIRTUAL - is_rom: bool = item_type == constants.OBJ_ROM + is_category: bool = item_type == constants.OBJ_CATEGORY + is_romcollection: bool = item_type == constants.OBJ_ROMCOLLECTION + is_virtual_category: bool = item_type == constants.OBJ_CATEGORY_VIRTUAL + is_rom: bool = item_type == constants.OBJ_ROM commands = [] if is_rom: @@ -614,7 +615,7 @@ def qry_listitem_context_menu_items(list_item_data, container_data)-> typing.Lis commands.append(('Edit Category', _context_menu_url_for(f'/categories/edit/{item_id}'))) commands.append(('Add new Category',_context_menu_url_for(f'/categories/add/{item_id}/in/{container_id}'))) commands.append(('Add new ROM Collection', _context_menu_url_for(f'/romcollection/add/{item_id}/in/{container_id}'))) - commands.append(('Add new ROM (Standalone)', _context_menu_url_for(f'/categories/addrom/{item_id}/in/{container_id}'))) + commands.append(( 'Add new ROM (Standalone)', _context_menu_url_for(f'/categories/addrom/{item_id}/in/{container_id}'))) if is_romcollection: commands.append(('View ROM Collection', _context_menu_url_for(f'/romcollection/view/{item_id}'))) diff --git a/resources/migrations/1.4.0.sql b/resources/migrations/1.4.0.sql new file mode 100644 index 00000000..8bbd47b9 --- /dev/null +++ b/resources/migrations/1.4.0.sql @@ -0,0 +1,242 @@ +-- CREATE NEW TABLES +CREATE TABLE IF NOT EXISTS assetmappings ( + id TEXT PRIMARY KEY, + mapped_asset_type TEXT NOT NULL, + to_asset_type TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS metadata_assetmappings( + metadata_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE TABLE IF NOT EXISTS romcollection_roms_assetmappings( + romcollection_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (romcollection_id) REFERENCES romcollections (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); +--------------------- UPDATE ROWS +DELETE FROM assetmappings; +DELETE FROM metadata_assetmappings; +DELETE FROM romcollection_roms_assetmappings; + +-- Category default mappings +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT c.id || 'ico', 'icon', substr(c.default_icon, 3) + FROM categories as c WHERE c.default_icon != 's_icon'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT c.metadata_id, c.id || 'ico' + FROM categories as c WHERE c.default_icon != 's_icon'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT c.id || 'fan', 'fanart', substr(c.default_fanart, 3) + FROM categories as c WHERE c.default_fanart != 's_fanart'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT c.metadata_id, c.id || 'fan' + FROM categories as c WHERE c.default_fanart != 's_fanart'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT c.id || 'ban', 'banner', substr(c.default_banner, 3) + FROM categories as c WHERE c.default_banner != 's_banner'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT c.metadata_id, c.id || 'ban' + FROM categories as c WHERE c.default_banner != 's_banner'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT c.id || 'pos', 'poster', substr(c.default_poster, 3) + FROM categories as c WHERE c.default_poster != 's_poster'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT c.metadata_id, c.id || 'pos' + FROM categories as c WHERE c.default_poster != 's_poster'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT c.id || 'log', 'clearlogo', substr(c.default_clearlogo, 3) + FROM categories as c WHERE c.default_clearlogo != 's_clearlogo'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT c.metadata_id, c.id || 'log' + FROM categories as c WHERE c.default_clearlogo != 's_clearlogo'; + +-- RomCollection default mappings -------------------- +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'ico', 'icon', substr(rc.default_icon, 3) + FROM romcollections as rc WHERE rc.default_icon != 's_icon'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'ico' + FROM romcollections as rc WHERE rc.default_icon != 's_icon'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'fan', 'fanart', substr(rc.default_fanart, 3) + FROM romcollections as rc WHERE rc.default_fanart != 's_fanart'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'fan' + FROM romcollections as rc WHERE rc.default_fanart != 's_fanart'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'ban', 'banner', substr(rc.default_banner, 3) + FROM romcollections as rc WHERE rc.default_banner != 's_banner'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'ban' + FROM romcollections as rc WHERE rc.default_banner != 's_banner'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'pos', 'poster', substr(rc.default_poster, 3) + FROM romcollections as rc WHERE rc.default_poster != 's_poster'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'pos' + FROM romcollections as rc WHERE rc.default_poster != 's_poster'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'con', 'controller', substr(rc.default_controller, 3) + FROM romcollections as rc WHERE rc.default_controller != 's_controller'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'con' + FROM romcollections as rc WHERE rc.default_controller != 's_controller'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'log', 'clearlogo', substr(rc.default_clearlogo, 3) + FROM romcollections as rc WHERE rc.default_clearlogo != 's_clearlogo'; + +INSERT INTO metadata_assetmappings (metadata_id, assetmapping_id) + SELECT rc.metadata_id, rc.id || 'log' + FROM romcollections as rc WHERE rc.default_clearlogo != 's_clearlogo'; + +-- ROMs default mappings -------------------- +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'rmico', 'icon', substr(rc.roms_default_icon, 3) + FROM romcollections as rc WHERE rc.roms_default_icon != 's_boxfront'; + +INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) + SELECT rc.id, rc.id || 'rmico' + FROM romcollections as rc WHERE rc.roms_default_icon != 's_boxfront'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'rmfan', 'fanart', substr(rc.roms_default_fanart, 3) + FROM romcollections as rc WHERE rc.roms_default_fanart != 's_fanart'; + +INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) + SELECT rc.id, rc.id || 'rmfan' + FROM romcollections as rc WHERE rc.roms_default_fanart != 's_fanart'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'rmban', 'banner', substr(rc.roms_default_banner, 3) + FROM romcollections as rc WHERE rc.roms_default_banner != 's_banner'; + +INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) + SELECT rc.id, rc.id || 'rmban' + FROM romcollections as rc WHERE rc.roms_default_banner != 's_banner'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'rmpos', 'poster', substr(rc.roms_default_poster, 3) + FROM romcollections as rc WHERE rc.roms_default_poster != 's_flyer'; + +INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) + SELECT rc.id, rc.id || 'rmpos' + FROM romcollections as rc WHERE rc.roms_default_poster != 's_flyer'; + +INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) + SELECT rc.id || 'rmlog', 'clearlogo', substr(rc.roms_default_clearlogo, 3) + FROM romcollections as rc WHERE rc.roms_default_clearlogo != 's_clearlogo'; + +INSERT INTO romcollection_roms_assetmappings (romcollection_id, assetmapping_id) + SELECT rc.id, rc.id || 'rmlog' + FROM romcollections as rc WHERE rc.roms_default_clearlogo != 's_clearlogo'; + +-- UPDATE EXISTING TABLES AND VIEWS +PRAGMA foreign_keys=OFF; +PRAGMA legacy_alter_table=ON; + +DROP VIEW IF EXiSTS vw_romcollections; +DROP VIEW IF EXiSTS vw_categories; + +ALTER TABLE categories RENAME TO categories_temp; + +CREATE TABLE IF NOT EXISTS categories( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + parent_id TEXT NULL, + metadata_id TEXT, + FOREIGN KEY (parent_id) REFERENCES categories (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +INSERT INTO categories (id, name, parent_id, metadata_id) + SELECT id, name, parent_id, metadata_id FROM categories_temp; + +DROP TABLE categories_temp; + +ALTER TABLE romcollections RENAME TO romcollections_temp; + +CREATE TABLE IF NOT EXISTS romcollections( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + platform TEXT, + box_size TEXT, + parent_id TEXT NULL, + metadata_id TEXT, + FOREIGN KEY (parent_id) REFERENCES categories (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +INSERT INTO romcollections (id, name, platform, box_size, parent_id, metadata_id) + SELECT id, name, platform, box_size, parent_id, metadata_id FROM romcollections_temp; + +DROP TABLE romcollections_temp; + +CREATE VIEW IF NOT EXISTS vw_categories AS SELECT + c.id AS id, + c.parent_id AS parent_id, + c.metadata_id, + c.name AS m_name, + m.year AS m_year, + m.genre AS m_genre, + m.developer AS m_developer, + m.rating AS m_rating, + m.plot AS m_plot, + m.finished AS finished, + m.assets_path AS assets_path, + (SELECT COUNT(*) FROM categories AS sc WHERE sc.parent_id = c.id) AS num_categories, + (SELECT COUNT(*) FROM romcollections AS sr WHERE sr.parent_id = c.id) AS num_collections +FROM categories AS c + INNER JOIN metadata AS m ON c.metadata_id = m.id; + +CREATE VIEW IF NOT EXISTS vw_romcollections AS SELECT + r.id AS id, + r.parent_id AS parent_id, + r.metadata_id, + r.name AS m_name, + m.year AS m_year, + m.genre AS m_genre, + m.developer AS m_developer, + m.rating AS m_rating, + m.plot AS m_plot, + m.finished AS finished, + m.assets_path AS assets_path, + r.platform AS platform, + r.box_size AS box_size, + (SELECT COUNT(*) FROM roms AS rms INNER JOIN roms_in_romcollection AS rrs ON rms.id = rrs.rom_id AND rrs.romcollection_id = r.id) as num_roms +FROM romcollections AS r + INNER JOIN metadata AS m ON r.metadata_id = m.id; + +PRAGMA foreign_keys=ON; +PRAGMA legacy_alter_table=OFF; \ No newline at end of file diff --git a/resources/schema.sql b/resources/schema.sql index 8cc0d097..fbce0875 100644 --- a/resources/schema.sql +++ b/resources/schema.sql @@ -35,6 +35,12 @@ CREATE TABLE IF NOT EXISTS assetpaths( asset_type TEXT NOT NULL ); +CREATE TABLE IF NOT EXISTS assetmappings ( + id TEXT PRIMARY KEY, + mapped_asset_type TEXT NOT NULL, + to_asset_type TEXT NOT NULL +); + CREATE TABLE IF NOT EXISTS akl_addon( id TEXT PRIMARY KEY, name TEXT, @@ -49,11 +55,6 @@ CREATE TABLE IF NOT EXISTS categories( name TEXT NOT NULL, parent_id TEXT NULL, metadata_id TEXT, - default_icon TEXT DEFAULT 's_icon' NOT NULL, - default_fanart TEXT DEFAULT 's_fanart' NOT NULL, - default_banner TEXT DEFAULT 's_banner' NOT NULL, - default_poster TEXT DEFAULT 's_poster' NOT NULL, - default_clearlogo TEXT DEFAULT 's_clearlogo' NOT NULL, FOREIGN KEY (parent_id) REFERENCES categories (id) ON DELETE CASCADE ON UPDATE NO ACTION, FOREIGN KEY (metadata_id) REFERENCES metadata (id) @@ -67,17 +68,6 @@ CREATE TABLE IF NOT EXISTS romcollections( box_size TEXT, parent_id TEXT NULL, metadata_id TEXT, - default_icon TEXT DEFAULT 's_icon' NOT NULL, - default_fanart TEXT DEFAULT 's_fanart' NOT NULL, - default_banner TEXT DEFAULT 's_banner' NOT NULL, - default_poster TEXT DEFAULT 's_poster' NOT NULL, - default_controller TEXT DEFAULT 's_controller' NOT NULL, - default_clearlogo TEXT DEFAULT 's_clearlogo' NOT NULL, - roms_default_icon TEXT DEFAULT 's_boxfront' NOT NULL, - roms_default_fanart TEXT DEFAULT 's_fanart' NOT NULL, - roms_default_banner TEXT DEFAULT 's_banner' NOT NULL, - roms_default_poster TEXT DEFAULT 's_flyer' NOT NULL, - roms_default_clearlogo TEXT DEFAULT 's_clearlogo' NOT NULL, FOREIGN KEY (parent_id) REFERENCES categories (id) ON DELETE CASCADE ON UPDATE NO ACTION, FOREIGN KEY (metadata_id) REFERENCES metadata (id) @@ -171,6 +161,24 @@ CREATE TABLE IF NOT EXISTS rom_launchers( ------------------------------------------------- -- ASSETS JOIN TABLES ------------------------------------------------- +CREATE TABLE IF NOT EXISTS metadata_assetmappings( + metadata_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE TABLE IF NOT EXISTS romcollection_roms_assetmappings( + romcollection_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (romcollection_id) REFERENCES romcollections (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + CREATE TABLE IF NOT EXISTS category_assets( category_id TEXT, asset_id TEXT, @@ -215,7 +223,6 @@ CREATE TABLE IF NOT EXISTS rom_assetpaths( FOREIGN KEY (assetpaths_id) REFERENCES assetpaths (id) ON DELETE CASCADE ON UPDATE NO ACTION ); - ------------------------------------------------- -- VIEWS ------------------------------------------------- @@ -231,11 +238,6 @@ CREATE VIEW IF NOT EXISTS vw_categories AS SELECT m.plot AS m_plot, m.finished AS finished, m.assets_path AS assets_path, - c.default_icon AS default_icon, - c.default_fanart AS default_fanart, - c.default_banner AS default_banner, - c.default_poster AS default_poster, - c.default_clearlogo AS default_clearlogo, (SELECT COUNT(*) FROM categories AS sc WHERE sc.parent_id = c.id) AS num_categories, (SELECT COUNT(*) FROM romcollections AS sr WHERE sr.parent_id = c.id) AS num_collections FROM categories AS c @@ -255,17 +257,6 @@ CREATE VIEW IF NOT EXISTS vw_romcollections AS SELECT m.assets_path AS assets_path, r.platform AS platform, r.box_size AS box_size, - r.default_icon AS default_icon, - r.default_fanart AS default_fanart, - r.default_banner AS default_banner, - r.default_poster AS default_poster, - r.default_controller AS default_controller, - r.default_clearlogo AS default_clearlogo, - r.roms_default_icon AS roms_default_icon, - r.roms_default_fanart AS roms_default_fanart, - r.roms_default_banner AS roms_default_banner, - r.roms_default_poster AS roms_default_poster, - r.roms_default_clearlogo AS roms_default_clearlogo, (SELECT COUNT(*) FROM roms AS rms INNER JOIN roms_in_romcollection AS rrs ON rms.id = rrs.rom_id AND rrs.romcollection_id = r.id) as num_roms FROM romcollections AS r INNER JOIN metadata AS m ON r.metadata_id = m.id; diff --git a/resources/settings.xml b/resources/settings.xml index 5b2db519..d4e18ae2 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -315,6 +315,26 @@ RunPlugin(plugin://plugin.program.akl/execute/command/render_views) + + 3 + + + true + + + RunPlugin(plugin://plugin.program.akl/execute/command/run_db_migrations) + + + + 3 + + + true + + + RunPlugin(plugin://plugin.program.akl/execute/command/reset_database) + + diff --git a/tests/domain_test.py b/tests/domain_test.py index ff49139f..9849049e 100644 --- a/tests/domain_test.py +++ b/tests/domain_test.py @@ -18,7 +18,6 @@ logging.basicConfig(format = '%(asctime)s %(module)s %(levelname)s: %(message)s', datefmt = '%m/%d/%Y %I:%M:%S %p', level = logging.INFO) - class Test_objectstests(unittest.TestCase): ROOT_DIR = '' diff --git a/tests/fakes.py b/tests/fakes.py index 5e7b6966..ed440717 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -82,6 +82,6 @@ class FakeUnitOfWork(): def __init__(self): self.executed_files = [] - def migrate_database(self, migration_files): + def migrate_database(self, migration_files, new_db_version): for f in migration_files: self.executed_files.append(f.getPath()) \ No newline at end of file diff --git a/tests/regex_test.py b/tests/regex_test.py index b9ebcdf8..65990e63 100644 --- a/tests/regex_test.py +++ b/tests/regex_test.py @@ -15,8 +15,8 @@ class Test_Regex(unittest.TestCase): def test_regex_patterns(self): - test_path = 'E:\AEL-stuff\AEL-DATs-No-Intro\Atari - 2600 (20191018-075817).dat' - test_patt = '.*Atari - 2600\s\((\d\d\d\d\d\d\d\d)-(\d\d\d\d\d\d)\)\.dat' + test_path = "E:\AEL-stuff\AEL-DATs-No-Intro\Atari - 2600 (20191018-075817).dat" + test_patt = ".*Atari - 2600\s\((\d\d\d\d\d\d\d\d)-(\d\d\d\d\d\d)\)\.dat" print('Filename "{}"'.format(test_path)) print('Pattern "{}"'.format(test_patt)) diff --git a/tests/services_test.py b/tests/services_test.py index a58f6f52..c9da338c 100644 --- a/tests/services_test.py +++ b/tests/services_test.py @@ -54,6 +54,7 @@ def test_version_compare(self, file_mock:MagicMock, globals_mock): service = AppService() start_version = LooseVersion('1.1.1') + globals.addon_version = '1.0.0' # act service._do_version_upgrade(uow_mock, start_version) From 9b6e1b2e14b957187946c565d4c736dc3c490f00 Mon Sep 17 00:00:00 2001 From: Christian Jungerius Date: Tue, 2 May 2023 19:55:04 +0200 Subject: [PATCH 03/19] Feature/disable notifications when launching (#28) * Added option to hide render notifications * Updated changelog * Fix for mapping issue --- changelog.md | 1 + .../resource.language.en_gb/strings.po | 4 ++ .../lib/commands/rom_launcher_commands.py | 5 +- resources/lib/commands/stats_commands.py | 6 +-- .../lib/commands/view_rendering_commands.py | 49 +++++++++++++------ resources/lib/domain.py | 2 +- resources/settings.xml | 5 ++ 7 files changed, 51 insertions(+), 21 deletions(-) diff --git a/changelog.md b/changelog.md index 5fb3ac5f..b4349058 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ ## Current - Search term mode applicable for multi ROM scraping - Refactoring of default asset mapping +- Added setting to disable view rendering notifications ## Previous - Custom skin view for View ROM diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 7e1e80ca..01cd6fa4 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -165,6 +165,10 @@ msgctxt "#40416" msgid "Launch ROM directly by default on item action" msgstr "settings.xml" +msgctxt "#40417" +msgid "Hide rendering notifications" +msgstr "settings.xml" + ############################ # Setting options - Paths ############################ diff --git a/resources/lib/commands/rom_launcher_commands.py b/resources/lib/commands/rom_launcher_commands.py index b4759a0a..da23000a 100644 --- a/resources/lib/commands/rom_launcher_commands.py +++ b/resources/lib/commands/rom_launcher_commands.py @@ -410,7 +410,7 @@ def cmd_execute_rom_with_launcher(args): return logger.info('Automatic fallback to Retroplayer as launcher applied.') - retroplayer_addon = addon_repository.find_by_addon_id(constants.RETROPLAYER_LAUNCHER_APP_NAME, constants.AddonType.LAUNCHER) + retroplayer_addon = addon_repository.find_by_addon_id(constants.RETROPLAYER_LAUNCHER_APP_NAME, constants.AddonType.LAUNCHER) retroplayer_launcher = ROMLauncherAddonFactory.create(retroplayer_addon, {}) launchers.append(retroplayer_launcher) @@ -425,7 +425,8 @@ def cmd_execute_rom_with_launcher(args): dialog = kodi.OrdDictionaryDialog() selected_launcher = dialog.select('Choose launcher', launcher_options,preselect=preselected) - if selected_launcher is None: return + if selected_launcher is None: + return selected_launcher.launch(rom) AppMediator.async_cmd('ROM_WAS_LAUNCHED', args) \ No newline at end of file diff --git a/resources/lib/commands/stats_commands.py b/resources/lib/commands/stats_commands.py index 09c0c5cf..15c4c245 100644 --- a/resources/lib/commands/stats_commands.py +++ b/resources/lib/commands/stats_commands.py @@ -35,10 +35,10 @@ @AppMediator.register('ROM_WAS_LAUNCHED') def cmd_process_launching_of_rom(args): - logger.debug('ROM_WAS_LAUNCHED: cmd_process_launching_of_rom() Processing that a ROM was launched') + logger.debug('ROM_WAS_LAUNCHED: Processing that a ROM was launched') rom_id: str = args['rom_id'] if 'rom_id' in args else None if rom_id is None: - logger.warning('cmd_process_launching_of_rom(): No rom id supplied.') + logger.warning('No rom id supplied.') return uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) @@ -50,7 +50,7 @@ def cmd_process_launching_of_rom(args): repository.update_rom(rom) uow.commit() - logger.debug('ROM_WAS_LAUNCHED: cmd_process_launching_of_rom() Processed stats for ROM {}'.format(rom.get_name())) + logger.debug(f'ROM_WAS_LAUNCHED: Processed stats for ROM {rom.get_name()}') AppMediator.async_cmd('RENDER_VCOLLECTION_VIEW', {'vcollection_id': constants.VCOLLECTION_RECENT_ID}) AppMediator.async_cmd('RENDER_VCOLLECTION_VIEW', {'vcollection_id': constants.VCOLLECTION_MOST_PLAYED_ID}) diff --git a/resources/lib/commands/view_rendering_commands.py b/resources/lib/commands/view_rendering_commands.py index e5e2a0e7..1d3c3b21 100644 --- a/resources/lib/commands/view_rendering_commands.py +++ b/resources/lib/commands/view_rendering_commands.py @@ -67,13 +67,16 @@ def cmd_render_view_data(args): else: category = categories_repository.find_category(category_id) _render_category_view(category, categories_repository, romcollections_repository, roms_repository, views_repository) - - kodi.notify('Selected views rendered') + + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") + if do_notification: + kodi.notify('Selected views rendered') kodi.refresh_container() @AppMediator.register('RENDER_VIRTUAL_VIEWS') def cmd_render_virtual_views(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") with uow: categories_repository = CategoryRepository(uow) romcollections_repository = ROMCollectionRepository(uow) @@ -97,15 +100,18 @@ def cmd_render_virtual_views(args): for vcategory_id in constants.VCATEGORIES: vcategory = VirtualCategoryFactory.create(vcategory_id) - kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') + if do_notification: + kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') _render_category_view(vcategory, categories_repository, romcollections_repository, roms_repository, views_repository) - kodi.notify('Virtual views rendered') + if do_notification: + kodi.notify('Virtual views rendered') kodi.refresh_container() @AppMediator.register('RENDER_VCATEGORY_VIEWS') def cmd_render_vcategory(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") with uow: categories_repository = CategoryRepository(uow) romcollections_repository = ROMCollectionRepository(uow) @@ -117,16 +123,19 @@ def cmd_render_vcategory(args): for vcategory_id in constants.VCATEGORIES: vcategory = VirtualCategoryFactory.create(vcategory_id) - - kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') + + if do_notification: + kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') _render_category_view(vcategory, categories_repository, romcollections_repository, roms_repository, views_repository) - kodi.notify(f'{vcategory.get_name()} view rendered') + if do_notification: + kodi.notify(f'{vcategory.get_name()} view rendered') kodi.refresh_container() @AppMediator.register('RENDER_VCATEGORY_VIEW') def cmd_render_vcategory(args): vcategory_id = args['vcategory_id'] if 'vcategory_id' in args else None + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: @@ -144,16 +153,19 @@ def cmd_render_vcategory(args): # cleanup first views_repository.cleanup_virtual_category_views(vcategory.get_id()) - kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') + if do_notification: + kodi.notify(f'Rendering virtual category "{vcategory.get_name()}"') _render_category_view(vcategory, categories_repository, romcollections_repository, roms_repository, views_repository) - kodi.notify('{} view rendered'.format(vcategory.get_name())) + if do_notification: + kodi.notify(f'{vcategory.get_name()} view rendered') kodi.refresh_container() @AppMediator.register('RENDER_ROMCOLLECTION_VIEW') def cmd_render_romcollection_view_data(args): kodi.notify('Rendering romcollection views') romcollection_id = args['romcollection_id'] if 'romcollection_id' in args else None + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: @@ -165,13 +177,15 @@ def cmd_render_romcollection_view_data(args): collection_view_data = _render_romcollection_view(romcollection, roms_repository) views_repository.store_view(romcollection.get_id(), romcollection.get_type(), collection_view_data) - kodi.notify('Selected views rendered') + if do_notification: + kodi.notify('Selected views rendered') kodi.refresh_container() @AppMediator.register('RENDER_VCOLLECTION_VIEW') def cmd_render_vcollection(args): vcollection_id = args['vcollection_id'] if 'vcollection_id' in args else None + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: @@ -179,18 +193,21 @@ def cmd_render_vcollection(args): views_repository = ViewRepository(globals.g_PATHS) vcollection = VirtualCollectionFactory.create(vcollection_id) - - kodi.notify(f'Rendering virtual collection "{vcollection.get_name()}"') + + if do_notification: + kodi.notify(f'Rendering virtual collection "{vcollection.get_name()}"') collection_view_data = _render_romcollection_view(vcollection, roms_repository) views_repository.store_view(vcollection.get_id(), vcollection.get_type(), collection_view_data) - kodi.notify('{} view rendered'.format(vcollection.get_name())) + if do_notification: + kodi.notify(f'{vcollection.get_name()} view rendered') kodi.refresh_container() @AppMediator.register('RENDER_ROM_VIEWS') def cmd_render_rom_views(args): rom_id = args['rom_id'] if 'rom_id' in args else None + do_notification = not settings.getSettingAsBool("display_hide_rendering_notifications") uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: @@ -199,7 +216,8 @@ def cmd_render_rom_views(args): views_repository = ViewRepository(globals.g_PATHS) rom_obj = roms_repository.find_rom(rom_id) - kodi.notify(f'Rendering all views containing ROM#{rom_obj.get_rom_identifier()}') + if do_notification: + kodi.notify(f'Rendering all views containing ROM#{rom_obj.get_rom_identifier()}') romcollections = romcollections_repository.find_romcollections_by_rom(rom_id) for romcollection in romcollections: @@ -211,7 +229,8 @@ def cmd_render_rom_views(args): collection_view_data = _render_romcollection_view(vcollection, roms_repository) views_repository.store_view(vcollection.get_id(), vcollection.get_type(), collection_view_data) - kodi.notify('Views rendered') + if do_notification: + kodi.notify('Views rendered') kodi.refresh_container() @AppMediator.register('CLEANUP_VIEWS') diff --git a/resources/lib/domain.py b/resources/lib/domain.py index fb0d91f5..8fce7086 100644 --- a/resources/lib/domain.py +++ b/resources/lib/domain.py @@ -1623,7 +1623,7 @@ def get_mappable_asset_ids_list(self): def get_asset_mapping(self, asset_info: AssetInfo): mapped_asset = next((m for m in self.asset_mappings if m.asset_info.id == asset_info.id), None) - if not mapped_asset: + if not mapped_asset or not mapped_asset.to_asset_info: # exception cases if asset_info.id == constants.ASSET_ICON_ID: return g_assetFactory.get_asset_info(constants.ASSET_BOXFRONT_ID) diff --git a/resources/settings.xml b/resources/settings.xml index d4e18ae2..38578409 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -200,6 +200,11 @@ false + + 0 + false + + From 5334d64597c371823a303756b52278836300d2e8 Mon Sep 17 00:00:00 2001 From: chrisism Date: Sat, 6 May 2023 18:04:23 +0200 Subject: [PATCH 04/19] No hard fail with migration issues --- resources/lib/services.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/lib/services.py b/resources/lib/services.py index c60197c2..11c9bfd4 100644 --- a/resources/lib/services.py +++ b/resources/lib/services.py @@ -59,7 +59,11 @@ def run(self): db_version = uow.get_database_version() logger.debug(f'db.version "{db_version}"') if db_version is None or LooseVersion(db_version) < LooseVersion(globals.addon_version): - self._do_version_upgrade(uow, LooseVersion(db_version)) + try: + self._do_version_upgrade(uow, LooseVersion(db_version)) + except: + logger.exception("Failure while doing database migration") + kodi.notify_error("Failure while doing database migration.") if self._last_time_scanned_is_too_long_ago(): self._perform_scans() From d5b017ed3363c34b7b8ecba52a578a0ccd65135f Mon Sep 17 00:00:00 2001 From: chrisism Date: Wed, 10 May 2023 09:16:53 +0200 Subject: [PATCH 05/19] Updated texts --- resources/language/resource.language.en_gb/strings.po | 8 ++++++++ resources/lib/commands/rom_commands.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 01cd6fa4..d373cb3e 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -335,6 +335,14 @@ msgctxt "#40951" msgid "Invalid parameters supplied." msgstr "" +msgctxt "#40952" +msgid "Is it a directly launchable file/executable?\nChoose no if you want to assign a separate launcher afterwards." +msgstr "" + +msgctxt "#40953" +msgid "Select file" +msgstr "" + ############################ # Scraping settings ############################ diff --git a/resources/lib/commands/rom_commands.py b/resources/lib/commands/rom_commands.py index dc6de4ca..de0d7c2e 100644 --- a/resources/lib/commands/rom_commands.py +++ b/resources/lib/commands/rom_commands.py @@ -713,10 +713,10 @@ def cmd_add_rom(args): parent_category = grand_parent_category rom_name = "" - is_file_based = kodi.dialog_yesno("Is it a file based ROM/executable?") + is_file_based = kodi.dialog_yesno(kodi.translate(40952)) file_path = None if is_file_based: - file_path = kodi.dialog_get_file("Select file") + file_path = kodi.dialog_get_file(kodi.translate(40953)) if file_path is not None: path = io.FileName(file_path) rom_name = path.getBaseNoExt() From de8d920bb73a943acc159b409ae7275508ed81fa Mon Sep 17 00:00:00 2001 From: chrisism Date: Thu, 11 May 2023 08:33:33 +0200 Subject: [PATCH 06/19] Fixing migrations --- .../resource.language.en_gb/strings.po | 4 + resources/lib/commands/misc_commands.py | 33 +++++-- resources/lib/queries.py | 4 + resources/lib/repositories.py | 90 ++++++++++++++++--- resources/lib/services.py | 24 ++--- resources/migrations/1.4.0_001.sql | 31 +++++++ .../migrations/{1.4.0.sql => 1.4.0_002.sql} | 29 +----- resources/migrations/1.4.0_003.sql | 82 +++++++++++++++++ resources/schema.sql | 12 ++- 9 files changed, 243 insertions(+), 66 deletions(-) create mode 100644 resources/migrations/1.4.0_001.sql rename resources/migrations/{1.4.0.sql => 1.4.0_002.sql} (90%) create mode 100644 resources/migrations/1.4.0_003.sql diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index d373cb3e..2672aa0d 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -343,6 +343,10 @@ msgctxt "#40953" msgid "Select file" msgstr "" +msgctxt "#40954" +msgid "Failure while doing database migration." +msgstr "" + ############################ # Scraping settings ############################ diff --git a/resources/lib/commands/misc_commands.py b/resources/lib/commands/misc_commands.py index f31f122a..f7009a9d 100644 --- a/resources/lib/commands/misc_commands.py +++ b/resources/lib/commands/misc_commands.py @@ -225,17 +225,20 @@ def cmd_execute_reset_db(args): kodi.notify('Finished resetting the database') @AppMediator.register('RUN_DB_MIGRATIONS') -def cmd_execute_reset_db(args): +def cmd_execute_migrations(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) - if not globals.g_PATHS.DATABASE_MIGRATIONS_PATH.exists(): - globals.g_PATHS.DATABASE_MIGRATIONS_PATH.makedirs() + + db_version = LooseVersion(uow.get_database_version()) + migrations_in_database = uow.get_migrations_history() options = collections.OrderedDict() - db_version = LooseVersion(uow.get_database_version()) - migrations_files_available = globals.g_PATHS.DATABASE_MIGRATIONS_PATH.scanFilesInPath("*.sql") + migrations_files_available = uow.get_migration_files(LooseVersion("0.0.0")) for migration_file in migrations_files_available: - options[migration_file.getPath()] = migration_file.getBase() + file_name = migration_file.getBase() + existing_migration = next((m for m in migrations_in_database if m["migration_file"] == file_name), None) + state = "NEW" if existing_migration is None else ("DONE" if existing_migration["applied"] else "FAILED") + options[migration_file.getPath()] = f"{migration_file.getBase()} [{state}]" dialog = kodi.OrdDictionaryDialog() selected_file = dialog.select(f"Select migrations to execute (Current version {db_version})", options) @@ -245,13 +248,25 @@ def cmd_execute_reset_db(args): migration_file = io.FileName(selected_file) version_to_store = LooseVersion(globals.addon_version) - file_version = LooseVersion(migration_file.getBaseNoExt()) + file_version = uow.get_version_from_migration_file(migration_file) if file_version > version_to_store: version_to_store = file_version if db_version > version_to_store: version_to_store = db_version - - uow.migrate_database([migration_file], version_to_store) + + dialog = kodi.ListDialog() + selected_index = dialog.select(f"Migration {migration_file.getBaseNoExt()}",[ + "Run migration", + "Mark as executed without running" + ]) + if not selected_index: + return + + if selected_index == 0: + if not kodi.dialog_yesno(f"Run migration {migration_file.getBaseNoExt()}?"): + return + + uow.migrate_database([migration_file], version_to_store, selected_index==1) kodi.notify('Done running migrations on the database') @AppMediator.register('CHECK_DUPLICATE_ASSET_DIRS') diff --git a/resources/lib/queries.py b/resources/lib/queries.py index 7ca6599f..b1ad535b 100644 --- a/resources/lib/queries.py +++ b/resources/lib/queries.py @@ -1,3 +1,7 @@ +# App Queries +AKL_UPDATE_VERSION = "UPDATE akl_version SET version=? WHERE app=?" +AKL_INSERT_MIGRATION = "INSERT OR REPLACE INTO akl_migrations (migration_file, applied_version, execution_date, applied) VALUES(?,?,?,?)" +AKL_SELECT_MIGRATIONS = "SELECT * FROM akl_migrations" # Shared Queries INSERT_METADATA = "INSERT INTO metadata (id,year,genre,developer,rating,plot,assets_path,finished) VALUES (?,?,?,?,?,?,?,?)" diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 0814869a..4a2a3f68 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -2,10 +2,13 @@ import logging import typing +import datetime +from distutils.version import LooseVersion + import sqlite3 from sqlite3.dbapi2 import Cursor -from akl.utils import text, io +from akl.utils import text, io, kodi from akl import constants from resources.lib import globals @@ -349,27 +352,88 @@ def reset_database(self, schema_file_path: io.FileName): self.create_empty_database(schema_file_path) - def migrate_database(self, migration_files:typing.List[io.FileName], new_db_version): - # make copy for backup - backup_filepath = self._db_path.changeExtension(".db.bak") - if backup_filepath.exists(): - backup_filepath.unlink() - self._db_path.copy(backup_filepath) - - self.open_session() + def migrate_database(self, migration_files:typing.List[io.FileName], new_db_version, skip_scripts_execution=False): + if not skip_scripts_execution: + # make copy of existing database file to execute migration on. + temp_filepath = self._db_path.changeExtension(f".{new_db_version}.db") + backup_filepath = self._db_path.changeExtension(f".db.bak") + if temp_filepath.exists(): + temp_filepath.unlink() + else: + if backup_filepath.exists(): + backup_filepath.unlink() + self._db_path.copy(backup_filepath) + self._db_path.copy(temp_filepath) + else: + temp_filepath = self._db_path + + self.open_session(temp_filepath) + any_failed = False for migration_file in migration_files: self.logger.info(f'Executing migration script: {migration_file.getPath()}') sql_statements = migration_file.loadFileToStr() - self.execute_script(sql_statements) + failed = False + try: + if not skip_scripts_execution: + self.execute_script(sql_statements) + except: + self.logger.exception(f"Failure with database migration '{migration_file.getBase()}'") + kodi.notify_error(kodi.translate(40954)) + failed = True + any_failed = True + self.conn.execute(qry.AKL_INSERT_MIGRATION,[ + migration_file.getBase(), str(new_db_version), + datetime.datetime.now(), not failed]) self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') - self.conn.execute("UPDATE akl_version SET version=? WHERE app=?", [globals.addon_version, str(new_db_version)]) + self.conn.execute(qry.AKL_UPDATE_VERSION, [globals.addon_version, str(new_db_version)]) self.commit() self.close_session() - def open_session(self): - self.conn = sqlite3.connect(self._db_path.getPathTranslated()) + # restore file after migrations + if not skip_scripts_execution: + self._db_path.unlink() + temp_filepath.copy(self._db_path) + if not any_failed: + temp_filepath.unlink() + + def get_migrations_history(self): + self.open_session() + + migrations_data_set = [] + try: + self.execute(qry.AKL_SELECT_MIGRATIONS) + migrations_data_set = self.result_set() + except: + self.logger.exception("Failure getting executed migrations") + finally: + self.close_session() + return migrations_data_set + + def get_migration_files(self, db_version): + if not globals.g_PATHS.DATABASE_MIGRATIONS_PATH.exists(): + globals.g_PATHS.DATABASE_MIGRATIONS_PATH.makedirs() + + migrations_files_available = globals.g_PATHS.DATABASE_MIGRATIONS_PATH.scanFilesInPath("*.sql") + migrations_files_to_execute = [] + for migration_file in migrations_files_available: + file_version = self.get_version_from_migration_file(migration_file) + if file_version > db_version: + migrations_files_to_execute.append(migration_file) + + migrations_files_to_execute.sort(key = lambda f: f.getBaseNoExt()) + return migrations_files_to_execute + + def get_version_from_migration_file(self, file: io.FileName): + if "_" not in file.getBaseNoExt(): + return LooseVersion(file.getBaseNoExt()) + return LooseVersion(file.getBaseNoExt().split("_")[0]) + + def open_session(self, db_path: io.FileName = None): + if db_path is None: + db_path = self._db_path + self.conn = sqlite3.connect(db_path.getPathTranslated()) self.conn.row_factory = UnitOfWork.dict_factory self.cursor = self.conn.cursor() diff --git a/resources/lib/services.py b/resources/lib/services.py index 11c9bfd4..37ecca86 100644 --- a/resources/lib/services.py +++ b/resources/lib/services.py @@ -63,7 +63,7 @@ def run(self): self._do_version_upgrade(uow, LooseVersion(db_version)) except: logger.exception("Failure while doing database migration") - kodi.notify_error("Failure while doing database migration.") + kodi.notify_error(kodi.translate(40954)) if self._last_time_scanned_is_too_long_ago(): self._perform_scans() @@ -106,27 +106,21 @@ def _initial_setup(self, uow:UnitOfWork): self._perform_scans() def _do_version_upgrade(self, uow:UnitOfWork, db_version:LooseVersion): - if not globals.g_PATHS.DATABASE_MIGRATIONS_PATH.exists(): - globals.g_PATHS.DATABASE_MIGRATIONS_PATH.makedirs() - - migrations_files_available = globals.g_PATHS.DATABASE_MIGRATIONS_PATH.scanFilesInPath("*.sql") - migrations_files_to_execute = [] - for migration_file in migrations_files_available: - if LooseVersion(migration_file.getBaseNoExt()) > db_version: - migrations_files_to_execute.append(migration_file) - + migrations_files_to_execute = uow.get_migration_files(db_version) if len(migrations_files_to_execute) == 0: return - - logger.info(f"Found {len(migrations_files_to_execute)} migration files to process.") - migrations_files_to_execute.sort(key = lambda f: (LooseVersion(f.getBaseNoExt()))) + migrations_executed = uow.get_migrations_history() + executed_files = [f["migration_file"] for f in migrations_executed] + new_migration_files_to_execute = [f for f in migrations_files_to_execute if f.getBase() not in executed_files] + + logger.info(f"Found {len(new_migration_files_to_execute)} migration files to process.") version_to_store = LooseVersion(globals.addon_version) - file_version = LooseVersion(migrations_files_to_execute[-1].getBaseNoExt()) + file_version = uow.get_version_from_migration_file(new_migration_files_to_execute[-1]) if file_version > version_to_store: version_to_store = file_version - uow.migrate_database(migrations_files_to_execute, version_to_store) + uow.migrate_database(new_migration_files_to_execute, version_to_store) def _perform_scans(self): # SCAN FOR ADDONS diff --git a/resources/migrations/1.4.0_001.sql b/resources/migrations/1.4.0_001.sql new file mode 100644 index 00000000..8dc4687b --- /dev/null +++ b/resources/migrations/1.4.0_001.sql @@ -0,0 +1,31 @@ +-- CREATE NEW TABLES +CREATE TABLE IF NOT EXISTS akl_migrations( + migration_file TEXT UNIQUE, + at_version TEXT, + execution_data TIMESTAMP, + applied INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS assetmappings ( + id TEXT PRIMARY KEY, + mapped_asset_type TEXT NOT NULL, + to_asset_type TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS metadata_assetmappings( + metadata_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +CREATE TABLE IF NOT EXISTS romcollection_roms_assetmappings( + romcollection_id TEXT, + assetmapping_id TEXT, + FOREIGN KEY (romcollection_id) REFERENCES romcollections (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); \ No newline at end of file diff --git a/resources/migrations/1.4.0.sql b/resources/migrations/1.4.0_002.sql similarity index 90% rename from resources/migrations/1.4.0.sql rename to resources/migrations/1.4.0_002.sql index 8bbd47b9..f83c70ce 100644 --- a/resources/migrations/1.4.0.sql +++ b/resources/migrations/1.4.0_002.sql @@ -1,33 +1,6 @@ --- CREATE NEW TABLES -CREATE TABLE IF NOT EXISTS assetmappings ( - id TEXT PRIMARY KEY, - mapped_asset_type TEXT NOT NULL, - to_asset_type TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS metadata_assetmappings( - metadata_id TEXT, - assetmapping_id TEXT, - FOREIGN KEY (metadata_id) REFERENCES metadata (id) - ON DELETE CASCADE ON UPDATE NO ACTION, - FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) - ON DELETE CASCADE ON UPDATE NO ACTION -); - -CREATE TABLE IF NOT EXISTS romcollection_roms_assetmappings( - romcollection_id TEXT, - assetmapping_id TEXT, - FOREIGN KEY (romcollection_id) REFERENCES romcollections (id) - ON DELETE CASCADE ON UPDATE NO ACTION, - FOREIGN KEY (assetmapping_id) REFERENCES assetmappings (id) - ON DELETE CASCADE ON UPDATE NO ACTION -); --------------------- UPDATE ROWS -DELETE FROM assetmappings; -DELETE FROM metadata_assetmappings; -DELETE FROM romcollection_roms_assetmappings; - -- Category default mappings + INSERT INTO assetmappings (id, mapped_asset_type, to_asset_type) SELECT c.id || 'ico', 'icon', substr(c.default_icon, 3) FROM categories as c WHERE c.default_icon != 's_icon'; diff --git a/resources/migrations/1.4.0_003.sql b/resources/migrations/1.4.0_003.sql new file mode 100644 index 00000000..01af0ed5 --- /dev/null +++ b/resources/migrations/1.4.0_003.sql @@ -0,0 +1,82 @@ +-- UPDATE EXISTING TABLES AND VIEWS +PRAGMA foreign_keys=OFF; +PRAGMA legacy_alter_table=ON; + +DROP VIEW IF EXiSTS vw_romcollections; +DROP VIEW IF EXiSTS vw_categories; + +ALTER TABLE categories RENAME TO categories_temp; + +CREATE TABLE IF NOT EXISTS categories( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + parent_id TEXT NULL, + metadata_id TEXT, + FOREIGN KEY (parent_id) REFERENCES categories (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +INSERT INTO categories (id, name, parent_id, metadata_id) + SELECT id, name, parent_id, metadata_id FROM categories_temp; + +DROP TABLE categories_temp; + +ALTER TABLE romcollections RENAME TO romcollections_temp; + +CREATE TABLE IF NOT EXISTS romcollections( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + platform TEXT, + box_size TEXT, + parent_id TEXT NULL, + metadata_id TEXT, + FOREIGN KEY (parent_id) REFERENCES categories (id) + ON DELETE CASCADE ON UPDATE NO ACTION, + FOREIGN KEY (metadata_id) REFERENCES metadata (id) + ON DELETE CASCADE ON UPDATE NO ACTION +); + +INSERT INTO romcollections (id, name, platform, box_size, parent_id, metadata_id) + SELECT id, name, platform, box_size, parent_id, metadata_id FROM romcollections_temp; + +DROP TABLE romcollections_temp; + +CREATE VIEW IF NOT EXISTS vw_categories AS SELECT + c.id AS id, + c.parent_id AS parent_id, + c.metadata_id, + c.name AS m_name, + m.year AS m_year, + m.genre AS m_genre, + m.developer AS m_developer, + m.rating AS m_rating, + m.plot AS m_plot, + m.finished AS finished, + m.assets_path AS assets_path, + (SELECT COUNT(*) FROM categories AS sc WHERE sc.parent_id = c.id) AS num_categories, + (SELECT COUNT(*) FROM romcollections AS sr WHERE sr.parent_id = c.id) AS num_collections +FROM categories AS c + INNER JOIN metadata AS m ON c.metadata_id = m.id; + +CREATE VIEW IF NOT EXISTS vw_romcollections AS SELECT + r.id AS id, + r.parent_id AS parent_id, + r.metadata_id, + r.name AS m_name, + m.year AS m_year, + m.genre AS m_genre, + m.developer AS m_developer, + m.rating AS m_rating, + m.plot AS m_plot, + m.finished AS finished, + m.assets_path AS assets_path, + r.platform AS platform, + r.box_size AS box_size, + (SELECT COUNT(*) FROM roms AS rms INNER JOIN roms_in_romcollection AS rrs ON rms.id = rrs.rom_id AND rrs.romcollection_id = r.id) as num_roms +FROM romcollections AS r + INNER JOIN metadata AS m ON r.metadata_id = m.id; + +PRAGMA foreign_keys=ON; +PRAGMA legacy_alter_table=OFF; \ No newline at end of file diff --git a/resources/schema.sql b/resources/schema.sql index fbce0875..1eb56eab 100644 --- a/resources/schema.sql +++ b/resources/schema.sql @@ -392,7 +392,17 @@ CREATE VIEW IF NOT EXISTS vw_rom_launchers AS SELECT FROM rom_launchers AS l INNER JOIN akl_addon AS a ON l.akl_addon_id = a.id; -CREATE TABLE IF NOT EXISTS akl_version(app TEXT, version TEXT); +CREATE TABLE IF NOT EXISTS akl_version( + app TEXT, + version TEXT +); + +CREATE TABLE IF NOT EXISTS akl_migrations( + migration_file TEXT UNIQUE, + applied_version TEXT, + execution_date TIMESTAMP, + applied INTEGER DEFAULT 0 +); -- STATIC VALUES INSERT INTO akl_addon (id, name, addon_id, version, addon_type) From 69570fc102b3961df4bf967d9f68181bb3c68c1a Mon Sep 17 00:00:00 2001 From: chrisism Date: Thu, 11 May 2023 08:48:40 +0200 Subject: [PATCH 07/19] Tests fixed --- tests/fakes.py | 5 ++++- tests/services_test.py | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/fakes.py b/tests/fakes.py index ed440717..976c453e 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -84,4 +84,7 @@ def __init__(self): def migrate_database(self, migration_files, new_db_version): for f in migration_files: - self.executed_files.append(f.getPath()) \ No newline at end of file + self.executed_files.append(f.getPath()) + + def get_migrations_history(self): + return [] \ No newline at end of file diff --git a/tests/services_test.py b/tests/services_test.py index c9da338c..a84f47cf 100644 --- a/tests/services_test.py +++ b/tests/services_test.py @@ -15,6 +15,7 @@ from resources.lib import globals from resources.lib.services import AppService +from resources.lib.repositories import UnitOfWork logger = logging.getLogger(__name__) logging.basicConfig(format = '%(asctime)s %(module)s %(levelname)s: %(message)s', @@ -47,21 +48,27 @@ def test_version_compare(self, file_mock:MagicMock, globals_mock): file_mock.return_value = [ FakeFile('1.2.1.sql'), FakeFile('1.1.0.sql'), + FakeFile('1.3.0_004.sql'), + FakeFile('1.3.0_001.sql'), + FakeFile('1.3.0_002.sql'), FakeFile('/files/1.3.0.sql'), FakeFile('1.1.5.sql'), - FakeFile('/migrations/with/1.2.7.sql')] - uow_mock = FakeUnitOfWork() + FakeFile('/migrations/with/1.2.7.sql') + ] - service = AppService() + target = UnitOfWork(FakeFile("/x.db")) start_version = LooseVersion('1.1.1') globals.addon_version = '1.0.0' # act - service._do_version_upgrade(uow_mock, start_version) + actual = target.get_migration_files(start_version) # assert - self.assertIsNotNone(uow_mock.executed_files) - self.assertEqual(uow_mock.executed_files[0], '1.1.5.sql') - self.assertEqual(uow_mock.executed_files[1], '1.2.1.sql') - self.assertEqual(uow_mock.executed_files[2], '/migrations/with/1.2.7.sql') - self.assertEqual(uow_mock.executed_files[3], '/files/1.3.0.sql') + self.assertIsNotNone(actual) + self.assertEqual(actual[0].getPath(), '1.1.5.sql') + self.assertEqual(actual[1].getPath(), '1.2.1.sql') + self.assertEqual(actual[2].getPath(), '/migrations/with/1.2.7.sql') + self.assertEqual(actual[3].getPath(), '/files/1.3.0.sql') + self.assertEqual(actual[4].getPath(), '1.3.0_001.sql') + self.assertEqual(actual[5].getPath(), '1.3.0_002.sql') + self.assertEqual(actual[6].getPath(), '1.3.0_004.sql') From 3d969240eddf08c932cddf0c801c2ea8b3ddcb4f Mon Sep 17 00:00:00 2001 From: chrisism Date: Thu, 11 May 2023 15:58:05 +0200 Subject: [PATCH 08/19] Fixed sql statement --- resources/migrations/1.4.0_001.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/migrations/1.4.0_001.sql b/resources/migrations/1.4.0_001.sql index 8dc4687b..98c7fae6 100644 --- a/resources/migrations/1.4.0_001.sql +++ b/resources/migrations/1.4.0_001.sql @@ -1,8 +1,8 @@ -- CREATE NEW TABLES CREATE TABLE IF NOT EXISTS akl_migrations( migration_file TEXT UNIQUE, - at_version TEXT, - execution_data TIMESTAMP, + applied_version TEXT, + execution_date TIMESTAMP, applied INTEGER DEFAULT 0 ); From c3c5d24185e69ef7afb8646bd905332289067340 Mon Sep 17 00:00:00 2001 From: chrisism Date: Thu, 11 May 2023 20:05:28 +0200 Subject: [PATCH 09/19] Fixes for executing old migrations --- resources/lib/repositories.py | 11 +++++++---- resources/lib/services.py | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 4a2a3f68..46e25d82 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -369,9 +369,11 @@ def migrate_database(self, migration_files:typing.List[io.FileName], new_db_vers self.open_session(temp_filepath) + check_version = LooseVersion("1.3.99") any_failed = False for migration_file in migration_files: self.logger.info(f'Executing migration script: {migration_file.getPath()}') + file_version = self.get_version_from_migration_file(migration_file) sql_statements = migration_file.loadFileToStr() failed = False try: @@ -382,9 +384,10 @@ def migrate_database(self, migration_files:typing.List[io.FileName], new_db_vers kodi.notify_error(kodi.translate(40954)) failed = True any_failed = True - self.conn.execute(qry.AKL_INSERT_MIGRATION,[ - migration_file.getBase(), str(new_db_version), - datetime.datetime.now(), not failed]) + if file_version > check_version: + self.conn.execute(qry.AKL_INSERT_MIGRATION,[ + migration_file.getBase(), str(new_db_version), + datetime.datetime.now(), not failed]) self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') self.conn.execute(qry.AKL_UPDATE_VERSION, [globals.addon_version, str(new_db_version)]) @@ -406,7 +409,7 @@ def get_migrations_history(self): self.execute(qry.AKL_SELECT_MIGRATIONS) migrations_data_set = self.result_set() except: - self.logger.exception("Failure getting executed migrations") + self.logger.error("Failure getting executed migrations") finally: self.close_session() return migrations_data_set diff --git a/resources/lib/services.py b/resources/lib/services.py index 37ecca86..2d4e48e3 100644 --- a/resources/lib/services.py +++ b/resources/lib/services.py @@ -115,6 +115,8 @@ def _do_version_upgrade(self, uow:UnitOfWork, db_version:LooseVersion): new_migration_files_to_execute = [f for f in migrations_files_to_execute if f.getBase() not in executed_files] logger.info(f"Found {len(new_migration_files_to_execute)} migration files to process.") + if len(new_migration_files_to_execute) == 0: + return version_to_store = LooseVersion(globals.addon_version) file_version = uow.get_version_from_migration_file(new_migration_files_to_execute[-1]) if file_version > version_to_store: From 4696e7449195669325b94c0c43e8beb2177d4efa Mon Sep 17 00:00:00 2001 From: chrisism Date: Fri, 12 May 2023 09:24:13 +0200 Subject: [PATCH 10/19] Execute migrations single action --- resources/lib/repositories.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 46e25d82..2770b8da 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -367,8 +367,6 @@ def migrate_database(self, migration_files:typing.List[io.FileName], new_db_vers else: temp_filepath = self._db_path - self.open_session(temp_filepath) - check_version = LooseVersion("1.3.99") any_failed = False for migration_file in migration_files: @@ -376,23 +374,29 @@ def migrate_database(self, migration_files:typing.List[io.FileName], new_db_vers file_version = self.get_version_from_migration_file(migration_file) sql_statements = migration_file.loadFileToStr() failed = False + + self.open_session(temp_filepath) try: if not skip_scripts_execution: self.execute_script(sql_statements) + self.commit() except: self.logger.exception(f"Failure with database migration '{migration_file.getBase()}'") kodi.notify_error(kodi.translate(40954)) failed = True any_failed = True + self.rollback() + finally: + self.close_session() + if file_version > check_version: - self.conn.execute(qry.AKL_INSERT_MIGRATION,[ + self.execute_single_session(temp_filepath, qry.AKL_INSERT_MIGRATION,[ migration_file.getBase(), str(new_db_version), - datetime.datetime.now(), not failed]) + datetime.datetime.now(), not failed]) - self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') - self.conn.execute(qry.AKL_UPDATE_VERSION, [globals.addon_version, str(new_db_version)]) - self.commit() - self.close_session() + self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') + self.execute_single_session(temp_filepath, qry.AKL_UPDATE_VERSION, [ + globals.addon_version, str(new_db_version)]) # restore file after migrations if not skip_scripts_execution: @@ -466,7 +470,13 @@ def execute(self, sql, *args) -> Cursor: sql_args_str = ','.join(map(str, args)) self.logger.error(f'Used arguments: {sql_args_str}') raise - + + def execute_single_session(self, db_path, sql, *args): + self.open_session(db_path) + self.execute(sql, args) + self.commit() + self.close_session() + def execute_script(self, sql_statements): self.conn.executescript(sql_statements) From b3ffa417b5fcb224cb90d1f81aad9927b740f1cb Mon Sep 17 00:00:00 2001 From: chrisism Date: Fri, 12 May 2023 09:27:17 +0200 Subject: [PATCH 11/19] Added check to execute migration if not applied --- resources/lib/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/services.py b/resources/lib/services.py index 2d4e48e3..24f680fd 100644 --- a/resources/lib/services.py +++ b/resources/lib/services.py @@ -111,7 +111,7 @@ def _do_version_upgrade(self, uow:UnitOfWork, db_version:LooseVersion): return migrations_executed = uow.get_migrations_history() - executed_files = [f["migration_file"] for f in migrations_executed] + executed_files = [f["migration_file"] for f in migrations_executed if f["applied"] == 1] new_migration_files_to_execute = [f for f in migrations_files_to_execute if f.getBase() not in executed_files] logger.info(f"Found {len(new_migration_files_to_execute)} migration files to process.") From 943e6571d7c30b3836e26059f9b06245cd79a0be Mon Sep 17 00:00:00 2001 From: chrisism Date: Fri, 12 May 2023 09:40:26 +0200 Subject: [PATCH 12/19] param changed to array --- resources/lib/repositories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 2770b8da..f06e7888 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -471,7 +471,7 @@ def execute(self, sql, *args) -> Cursor: self.logger.error(f'Used arguments: {sql_args_str}') raise - def execute_single_session(self, db_path, sql, *args): + def execute_single_session(self, db_path, sql, args): self.open_session(db_path) self.execute(sql, args) self.commit() From bb32ba9f6289ad36cd8442295af8d15e987bbe72 Mon Sep 17 00:00:00 2001 From: chrisism Date: Mon, 15 May 2023 15:37:13 +0200 Subject: [PATCH 13/19] Fix insert statement --- resources/lib/repositories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index f06e7888..cd14bb22 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -473,7 +473,7 @@ def execute(self, sql, *args) -> Cursor: def execute_single_session(self, db_path, sql, args): self.open_session(db_path) - self.execute(sql, args) + self.execute(sql, *args) self.commit() self.close_session() From 4ab52eb5b9770811eb14cfd3d0cf5bfef297daaa Mon Sep 17 00:00:00 2001 From: chrisism Date: Mon, 15 May 2023 16:35:39 +0200 Subject: [PATCH 14/19] Fix version query --- resources/lib/repositories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index cd14bb22..2e4b46e9 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -396,7 +396,7 @@ def migrate_database(self, migration_files:typing.List[io.FileName], new_db_vers self.logger.info(f'Updating database schema version of app {globals.addon_id} to {new_db_version}') self.execute_single_session(temp_filepath, qry.AKL_UPDATE_VERSION, [ - globals.addon_version, str(new_db_version)]) + str(new_db_version), globals.addon_id]) # restore file after migrations if not skip_scripts_execution: From bea58a0afccd3bd61c795c8d59a82db5ac586f6c Mon Sep 17 00:00:00 2001 From: chrisism Date: Mon, 15 May 2023 22:58:49 +0200 Subject: [PATCH 15/19] Added edit platform to ROM menu --- .../resource.language.en_gb/strings.po | 12 ++++++++++ resources/lib/commands/rom_commands.py | 23 ++++++++++++++++--- resources/lib/domain.py | 7 +++--- resources/lib/editors.py | 3 ++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 2672aa0d..41fb5b3b 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -324,6 +324,18 @@ msgctxt "#40862" msgid "Delete Category" msgstr "" +msgctxt "#40863" +msgid "Edit Title" +msgstr "" + +msgctxt "#40864" +msgid "Edit Platform" +msgstr "" + +msgctxt "#40865" +msgid "Edit Release Year" +msgstr "" + ############################ # Headers and texts / notifications ############################ diff --git a/resources/lib/commands/rom_commands.py b/resources/lib/commands/rom_commands.py index de0d7c2e..c368f379 100644 --- a/resources/lib/commands/rom_commands.py +++ b/resources/lib/commands/rom_commands.py @@ -20,7 +20,7 @@ import logging import collections -from akl import constants +from akl import constants, platforms from akl.utils import kodi, text, io from resources.lib.commands.mediator import AppMediator @@ -90,8 +90,9 @@ def cmd_rom_metadata(args): NFO_found_str = 'NFO found' if NFO_FileName and NFO_FileName.exists() else 'NFO not found' options = collections.OrderedDict() - options['ROM_EDIT_METADATA_TITLE'] = "Edit Title: '{}'".format(rom.get_name()) - options['ROM_EDIT_METADATA_RELEASEYEAR'] = "Edit Release Year: {}".format(rom.get_releaseyear()) + options['ROM_EDIT_METADATA_TITLE'] = f"{kodi.translate(40863)}: '{rom.get_name()}'" + options['ROM_EDIT_METADATA_PLATFORM'] = f"{kodi.translate(40864)}: {rom.get_platform()}" + options['ROM_EDIT_METADATA_RELEASEYEAR'] = f"{kodi.translate(40865)}: {rom.get_releaseyear()}" options['ROM_EDIT_METADATA_GENRE'] = "Edit Genre: '{}'".format(rom.get_genre()) options['ROM_EDIT_METADATA_DEVELOPER'] = "Edit Developer: '{}'".format(rom.get_developer()) options['ROM_EDIT_METADATA_NPLAYERS'] = "Edit NPlayers: '{}'".format(rom.get_number_of_players()) @@ -238,6 +239,22 @@ def cmd_rom_metadata_title(args): AppMediator.sync_cmd('ROM_EDIT_METADATA', args) +@AppMediator.register('ROM_EDIT_METADATA_PLATFORM') +def cmd_rom_metadata_platform(args): + rom_id = args['rom_id'] if 'rom_id' in args else None + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + with uow: + repository = ROMsRepository(uow) + rom = repository.find_rom(rom_id) + + if editors.edit_field_by_list(rom, 'Platform', platforms.AKL_platform_list, + rom.get_platform, rom.set_platform): + repository.update_rom(rom) + uow.commit() + AppMediator.async_cmd('RENDER_ROM_VIEWS', {'rom_id': rom.get_id()}) + + AppMediator.sync_cmd('ROM_EDIT_METADATA', args) + @AppMediator.register('ROM_EDIT_METADATA_ESRB') def cmd_rom_metadata_esrb(args): rom_id = args['rom_id'] if 'rom_id' in args else None diff --git a/resources/lib/domain.py b/resources/lib/domain.py index 8fce7086..965007e0 100644 --- a/resources/lib/domain.py +++ b/resources/lib/domain.py @@ -1101,9 +1101,11 @@ def get_type(self): return constants.OBJ_ROMCOLLECTION def get_parent_id(self) -> str: return self.entity_data['parent_id'] if 'parent_id' in self.entity_data else None - def get_platform(self): return self.entity_data['platform'] if 'platform' in self.entity_data else None + def get_platform(self): + return self.entity_data['platform'] if 'platform' in self.entity_data else None - def set_platform(self, platform): self.entity_data['platform'] = platform + def set_platform(self, platform): + self.entity_data['platform'] = platform def get_box_sizing(self): return self.entity_data['box_size'] if 'box_size' in self.entity_data else constants.BOX_SIZE_POSTER @@ -1418,7 +1420,6 @@ def get_rom_identifier(self) -> str: return f'ROM_{self.get_id()}' - # inherited value from ROMCollection def get_platform(self): return self.entity_data['platform'] if 'platform' in self.entity_data else None diff --git a/resources/lib/editors.py b/resources/lib/editors.py index 9d873aa0..0c38e184 100644 --- a/resources/lib/editors.py +++ b/resources/lib/editors.py @@ -86,7 +86,8 @@ def edit_field_by_list(obj_instance: MetaDataItemABC, metadata_name:str, str_lis preselect_idx = 0 dialog_title = 'Edit {0} {1}'.format(object_name, metadata_name) selected = kodi.ListDialog().select(dialog_title, str_list, preselect_idx) - if selected is None: return + if selected is None: + return new_value = str_list[selected] if old_value == new_value: kodi.notify('{0} {1} not changed'.format(object_name, metadata_name)) From f73a7b6ed704ea2ae46576a5b49c29da67254617 Mon Sep 17 00:00:00 2001 From: chrisism Date: Mon, 15 May 2023 23:01:47 +0200 Subject: [PATCH 16/19] Update changelog --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index b4349058..bd589e47 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ - Search term mode applicable for multi ROM scraping - Refactoring of default asset mapping - Added setting to disable view rendering notifications +- Changed database migrations system +- Added 'edit platform' to ROMs ## Previous - Custom skin view for View ROM From 2cf237a9062f7c7700c36f7c0cfa532754cf0477 Mon Sep 17 00:00:00 2001 From: chrisism Date: Wed, 17 May 2023 20:03:46 +0200 Subject: [PATCH 17/19] Added option to apply platform to other roms --- .../language/resource.language.en_gb/strings.po | 6 +++++- resources/lib/commands/romcollection_commands.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 41fb5b3b..4ed288ee 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -359,6 +359,10 @@ msgctxt "#40954" msgid "Failure while doing database migration." msgstr "" +msgctxt "#40955" +msgid "Should new platform be applied to existing ROMs in this collection?" +msgstr "" + ############################ # Scraping settings ############################ @@ -406,7 +410,7 @@ msgid "Clones" msgstr "settings.xml" ############################ -# Advaced Enum values +# Advanced Enum values ############################ msgctxt "#30911" diff --git a/resources/lib/commands/romcollection_commands.py b/resources/lib/commands/romcollection_commands.py index 1f1352c5..472a366e 100644 --- a/resources/lib/commands/romcollection_commands.py +++ b/resources/lib/commands/romcollection_commands.py @@ -25,7 +25,7 @@ from resources.lib.commands.mediator import AppMediator from resources.lib import globals, editors -from resources.lib.repositories import UnitOfWork, CategoryRepository, ROMCollectionRepository +from resources.lib.repositories import UnitOfWork, CategoryRepository, ROMCollectionRepository, ROMsRepository from resources.lib.domain import ROMCollection, Category, g_assetFactory logger = logging.getLogger(__name__) @@ -308,6 +308,16 @@ def cmd_romcollection_metadata_platform(args): if editors.edit_field_by_list(romcollection, 'Platform', platforms.AKL_platform_list, romcollection.get_platform, romcollection.set_platform): repository.update_romcollection(romcollection) + update_roms_too = kodi.dialog_yesno(kodi.translate(40955)) + + if update_roms_too: + roms_repository = ROMsRepository(uow) + roms_to_update = roms_repository.find_roms_by_romcollection(romcollection) + platform_to_apply = romcollection.get_platform() + for rom in roms_to_update: + rom.set_platform(platform_to_apply) + roms_repository.update_rom(rom) + uow.commit() AppMediator.async_cmd('RENDER_ROMCOLLECTION_VIEW', {'romcollection_id': romcollection.get_id()}) AppMediator.async_cmd('RENDER_CATEGORY_VIEW', {'category_id': romcollection.get_parent_id()}) From 6a2ae3bed0b32a2103090994f7e02be5a1019906 Mon Sep 17 00:00:00 2001 From: chrisism Date: Wed, 17 May 2023 20:09:51 +0200 Subject: [PATCH 18/19] Added 1.2.0 migration in migrations table --- resources/migrations/1.4.0_003.sql | 3 +++ resources/schema.sql | 3 +++ 2 files changed, 6 insertions(+) diff --git a/resources/migrations/1.4.0_003.sql b/resources/migrations/1.4.0_003.sql index 01af0ed5..4f381526 100644 --- a/resources/migrations/1.4.0_003.sql +++ b/resources/migrations/1.4.0_003.sql @@ -78,5 +78,8 @@ CREATE VIEW IF NOT EXISTS vw_romcollections AS SELECT FROM romcollections AS r INNER JOIN metadata AS m ON r.metadata_id = m.id; +INSERT INTO akl_migrations (migration_file, applied_version, execution_date, applied) + VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1) + PRAGMA foreign_keys=ON; PRAGMA legacy_alter_table=OFF; \ No newline at end of file diff --git a/resources/schema.sql b/resources/schema.sql index 1eb56eab..6448ad11 100644 --- a/resources/schema.sql +++ b/resources/schema.sql @@ -415,3 +415,6 @@ INSERT INTO tags (id, tag) VALUES ('1c67d7a0cccb47dfb1b36839a4bb1c1f', '4k'), ('ccd62da94f7a4bf4ba593670329c2690', '720'), ('bf62ca2ffb0347559b1a52ec70b0b189', '1080'); + +INSERT INTO akl_migrations (migration_file, applied_version, execution_date, applied) + VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1) \ No newline at end of file From 72976e5d33eedde357c755ebe4c2bd41eaa14782 Mon Sep 17 00:00:00 2001 From: chrisism Date: Wed, 17 May 2023 20:10:41 +0200 Subject: [PATCH 19/19] Added missing quotes and updated changelog --- changelog.md | 1 + resources/migrations/1.4.0_003.sql | 2 +- resources/schema.sql | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index bd589e47..f029590a 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ - Added setting to disable view rendering notifications - Changed database migrations system - Added 'edit platform' to ROMs +- Changed platform can be applied to all ROMs in a collection ## Previous - Custom skin view for View ROM diff --git a/resources/migrations/1.4.0_003.sql b/resources/migrations/1.4.0_003.sql index 4f381526..6f93b978 100644 --- a/resources/migrations/1.4.0_003.sql +++ b/resources/migrations/1.4.0_003.sql @@ -79,7 +79,7 @@ FROM romcollections AS r INNER JOIN metadata AS m ON r.metadata_id = m.id; INSERT INTO akl_migrations (migration_file, applied_version, execution_date, applied) - VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1) + VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1); PRAGMA foreign_keys=ON; PRAGMA legacy_alter_table=OFF; \ No newline at end of file diff --git a/resources/schema.sql b/resources/schema.sql index 6448ad11..1f734129 100644 --- a/resources/schema.sql +++ b/resources/schema.sql @@ -417,4 +417,4 @@ INSERT INTO tags (id, tag) VALUES ('bf62ca2ffb0347559b1a52ec70b0b189', '1080'); INSERT INTO akl_migrations (migration_file, applied_version, execution_date, applied) - VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1) \ No newline at end of file + VALUES('1.2.0.sql','1.4.0',CURRENT_TIMESTAMP,1); \ No newline at end of file