diff --git a/.build/main.yml b/.build/main.yml index b2b407f3..542f5f03 100644 --- a/.build/main.yml +++ b/.build/main.yml @@ -78,7 +78,7 @@ stages: python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip - pip install setup + pip install packaging==21.3 pip install -r requirements.txt workingDirectory: $(workingDirectory) displayName: 'Install application dependencies' @@ -126,12 +126,6 @@ stages: flattenFolders: false OverWrite: true - - bash: | - source .venv/bin/activate - kodi-addon-checker --branch matrix $(build.artifactstagingdirectory)/$(addonName)/ - workingDirectory: '$(workingDirectory)' - displayName: 'Run Kodi addon checker' - - task: ArchiveFiles@2 inputs: rootFolderOrFile: '$(build.artifactstagingdirectory)/$(addonName)' @@ -140,6 +134,12 @@ stages: archiveFile: '$(build.artifactstagingdirectory)/package/$(addonName)-$(addonVersion).zip' replaceExistingArchive: true + - bash: | + source .venv/bin/activate + kodi-addon-checker --branch matrix $(build.artifactstagingdirectory)/$(addonName)/ + workingDirectory: '$(workingDirectory)' + displayName: 'Run Kodi addon checker' + - task: CopyFiles@2 displayName: 'Copy addon files for repository' inputs: diff --git a/addon.xml b/addon.xml index ccb99205..01f49c5c 100644 --- a/addon.xml +++ b/addon.xml @@ -3,7 +3,7 @@ - + executable game diff --git a/changelog.md b/changelog.md index 1476d520..509798f1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,5 @@ ## Current +- Custom skin view for View ROM - More details about addon plugins ## Previous diff --git a/requirements.txt b/requirements.txt index 5cfa50fd..87b39290 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,6 @@ kodi-addon-checker==0.0.26 Kodistubs==19.0.3 routing==0.2.3 pytest==6.2.5 -script.module.akl==1.0.9 +script.module.akl==1.0.11 requests==2.22.0 flake8==5.0.4 diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 858e2f60..4fa20038 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -161,6 +161,10 @@ msgctxt "#40415" msgid "Hide Global Reports" msgstr "settings.xml" +msgctxt "#40416" +msgid "Launch ROM directly by default on item action" +msgstr "settings.xml" + ############################ # Setting options - Paths ############################ @@ -208,6 +212,86 @@ msgctxt "#40702" msgid "Google scraper plugin" msgstr "settings.xml" +############################ +# Metadata labels +############################ + +msgctxt "#40801" +msgid "Genre" +msgstr "" + +msgctxt "#40802" +msgid "Developer" +msgstr "" + +msgctxt "#40803" +msgid "Year released" +msgstr "" + +msgctxt "#40804" +msgid "ESRB Rating" +msgstr "" + +msgctxt "#40805" +msgid "PEGI Rating" +msgstr "" + +msgctxt "#40806" +msgid "Rating" +msgstr "" + +msgctxt "#40807" +msgid "Platform" +msgstr "" + +msgctxt "#40808" +msgid "Number of players" +msgstr "" + +msgctxt "#40809" +msgid "Number of players online" +msgstr "" + +msgctxt "#40810" +msgid "Tags" +msgstr "" + +msgctxt "#40811" +msgid "Plot" +msgstr "" + +msgctxt "#40812" +msgid "Title" +msgstr "" + +msgctxt "#40813" +msgid "Identifier" +msgstr "" + +############################ +# Actions +############################ + +msgctxt "#40851" +msgid "Launch" +msgstr "" + +msgctxt "#40852" +msgid "Play trailer" +msgstr "" + +msgctxt "#40853" +msgid "Edit Metadata" +msgstr "" + +msgctxt "#40854" +msgid "Edit Assets/Artwork" +msgstr "" + +msgctxt "#40855" +msgid "Scrape ROM" +msgstr "" + ############################ # Scraping settings ############################ diff --git a/resources/lib/commands/api_commands.py b/resources/lib/commands/api_commands.py index 360d22e3..2b6900c1 100644 --- a/resources/lib/commands/api_commands.py +++ b/resources/lib/commands/api_commands.py @@ -137,7 +137,7 @@ def cmd_store_scanned_roms(args) -> bool: api_rom_obj = ROMObj(rom_data) rom_obj = ROM() - rom_obj.update_with(api_rom_obj, overwrite_existing=True, update_scanned_data=True) + rom_obj.update_with(api_rom_obj, overwrite_existing_metadata=True, update_scanned_data=True) rom_obj.set_platform(romcollection.get_platform()) rom_obj.scanned_with(scanner_id) rom_obj.apply_romcollection_asset_paths(romcollection) @@ -208,7 +208,9 @@ def cmd_store_scraped_roms(args) -> bool: logger.debug('========================== Applied scraper settings ==========================') logger.debug('Metadata IDs: {}'.format(', '.join(applied_settings.metadata_IDs_to_scrape))) logger.debug('Asset IDs: {}'.format(', '.join(applied_settings.asset_IDs_to_scrape))) - logger.debug('Overwrite existing: {}'.format('Yes' if applied_settings.overwrite_existing else 'No')) + logger.debug('Overwrite existing:') + logger.debug(' - Metadata {}'.format('Yes' if applied_settings.overwrite_existing_meta else 'No')) + logger.debug(' - Assets {}'.format('Yes' if applied_settings.overwrite_existing_assets else 'No')) for rom_data in scraped_roms: api_rom_obj = ROMObj(rom_data) @@ -226,7 +228,8 @@ def cmd_store_scraped_roms(args) -> bool: api_rom_obj, metadata_to_update, assets_to_update, - overwrite_existing=applied_settings.overwrite_existing) + overwrite_existing_metadata=applied_settings.overwrite_existing_meta, + overwrite_existing_assets=applied_settings.overwrite_existing_assets) #rom_obj.scraped_with(scraper_id) rom_repository.update_rom(rom_obj) @@ -277,7 +280,8 @@ def cmd_store_scraped_single_rom(args) -> bool: rom.update_with(scraped_rom, metadata_to_update, assets_to_update, - overwrite_existing=applied_settings.overwrite_existing) + overwrite_existing_metadata=applied_settings.overwrite_existing_meta, + overwrite_existing_assets=applied_settings.overwrite_existing_assets) #rom_obj.scraped_with(scraper_id) rom_repository.update_rom(rom) diff --git a/resources/lib/commands/category_commands.py b/resources/lib/commands/category_commands.py index 597d9c22..5cb3403f 100644 --- a/resources/lib/commands/category_commands.py +++ b/resources/lib/commands/category_commands.py @@ -224,21 +224,21 @@ def cmd_category_delete(args): category_name = category.get_name() if category.has_items(): - question = 'Category "{}" has {} sub-categories and {} romcollections. '.format(category_name, category.num_categories(), category.num_romcollections()) + \ - 'Deleting it will also delete related items. ' + \ - 'Are you sure you want to delete "{}"?'.format(category_name) + question = (f'Category "{category_name}" has {category.num_categories()} sub-categories and ' + f'{category.num_romcollections()} romcollections. Deleting it will also delete related items. ' + f'Are you sure you want to delete "{category_name}"?') else: - question = 'Category "{}" has no categories or romcollections. '.format(category_name) + \ - 'Are you sure you want to delete "{}"?'.format(category_name) + question = (f'Category "{category_name}" has no categories or romcollections. ' + f'Are you sure you want to delete "{category_name}"?') ret = kodi.dialog_yesno(question) if not ret: return - logger.info('Deleting category "{}" ID {}'.format(category_name, category.get_id())) + logger.info(f'Deleting category "{category_name}" ID {category.get_id()}') repository.delete_category(category_id) uow.commit() - kodi.notify('Deleted category {0}'.format(category_name)) + kodi.notify(f'Deleted category {category_name}') AppMediator.async_cmd('RENDER_CATEGORY_VIEW', {'category_id': category.get_parent_id()}) AppMediator.async_cmd('CLEANUP_VIEWS') AppMediator.sync_cmd('EDIT_CATEGORY', args) diff --git a/resources/lib/commands/rom_commands.py b/resources/lib/commands/rom_commands.py index f1868862..261c6a23 100644 --- a/resources/lib/commands/rom_commands.py +++ b/resources/lib/commands/rom_commands.py @@ -52,14 +52,14 @@ def cmd_edit_rom(args): rom = repository.find_rom(rom_id) options = collections.OrderedDict() - options['ROM_EDIT_METADATA'] = 'Edit Metadata ...' - options['ROM_EDIT_ASSETS'] = 'Edit Assets/Artwork ...' + options['ROM_EDIT_METADATA'] = f'{kodi.translate(40853)} ...' + options['ROM_EDIT_ASSETS'] = f'{kodi.translate(40854)} ...' options['EDIT_ROM_STATUS'] = f'ROM status: {rom.get_finished_str()}' if rom.has_launchers(): options['EDIT_ROM_LAUNCHERS'] = 'Manage associated launchers' else: options['ADD_ROM_LAUNCHER'] = 'Add new launcher to ROM' options['DELETE_ROM'] = 'Delete ROM' - options['SCRAPE_ROM'] = 'Scrape ROM' + options['SCRAPE_ROM'] = kodi.translate(40855) s = f'Edit ROM "{rom.get_name()}"' selected_option = kodi.OrdDictionaryDialog().select(s, options) diff --git a/resources/lib/commands/rom_scraper_commands.py b/resources/lib/commands/rom_scraper_commands.py index c14e571c..c52277d6 100644 --- a/resources/lib/commands/rom_scraper_commands.py +++ b/resources/lib/commands/rom_scraper_commands.py @@ -41,7 +41,7 @@ def cmd_scrape_romcollection(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: collection_repository = ROMCollectionRepository(uow) - collection = collection_repository.find_romcollection(romcollection_id) + collection = collection_repository.find_romcollection(romcollection_id) scraper_settings:ScraperSettings = ScraperSettings.from_addon_settings() @@ -53,14 +53,14 @@ def cmd_scrape_romcollection(args): AppMediator.sync_cmd('ROMCOLLECTION_MANAGE_ROMS', args) return - scraper_settings.asset_IDs_to_scrape = selected_addon.get_supported_assets() + scraper_settings.asset_IDs_to_scrape = selected_addon.get_supported_assets() scraper_settings.metadata_IDs_to_scrape = selected_addon.get_supported_metadata() logger.debug(f'cmd_scrape_romcollection() Selected scraper#{selected_addon.get_name()}') - args['scraper_settings'] = scraper_settings - args['scraper_id'] = selected_addon.addon.get_id() - args['scraper_supported_metadata'] = selected_addon.get_supported_metadata() - args['scraper_supported_assets'] = selected_addon.get_supported_assets() + args['scraper_settings'] = scraper_settings + args['scraper_id'] = selected_addon.addon.get_id() + args['scraper_supported_metadata'] = selected_addon.get_supported_metadata() + args['scraper_supported_assets'] = selected_addon.get_supported_assets() AppMediator.sync_cmd('SCRAPE_ROMS_WITH_SETTINGS', args) @@ -76,22 +76,22 @@ def cmd_scrape_rom(args): scraper_settings:ScraperSettings = ScraperSettings.from_addon_settings() - dialog_title = f'Scrape ROM "{rom.get_name()}"' - selected_addon = _select_scraper(uow, dialog_title, scraper_settings) + dialog_title = f'Scrape ROM "{rom.get_name()}"' + selected_addon = _select_scraper(uow, dialog_title, scraper_settings) if selected_addon is None: # >> Exits context menu - logger.debug('SCRAPE_ROM: cmd_scrape_rom() Selected None. Closing context menu') + logger.debug('SCRAPE_ROM: Selected None. Closing context menu') AppMediator.sync_cmd('EDIT_ROM', args) return - scraper_settings.asset_IDs_to_scrape = selected_addon.get_supported_assets() + scraper_settings.asset_IDs_to_scrape = selected_addon.get_supported_assets() scraper_settings.metadata_IDs_to_scrape = selected_addon.get_supported_metadata() - logger.debug('cmd_scrape_rom() Selected scraper#{}'.format(selected_addon.get_name())) - args['scraper_settings'] = scraper_settings - args['scraper_id'] = selected_addon.addon.get_id() - args['scraper_supported_metadata'] = selected_addon.get_supported_metadata() - args['scraper_supported_assets'] = selected_addon.get_supported_assets() + logger.debug(f'Selected scraper#{selected_addon.get_name()}') + args['scraper_settings'] = scraper_settings + args['scraper_id'] = selected_addon.addon.get_id() + args['scraper_supported_metadata'] = selected_addon.get_supported_metadata() + args['scraper_supported_assets'] = selected_addon.get_supported_assets() AppMediator.sync_cmd('SCRAPE_ROM_WITH_SETTINGS', args) @@ -114,15 +114,16 @@ def cmd_scrape_roms_in_romcollection(args): metadata_to_scrape = [constants.METADATA_DESCRIPTIONS[meta_id] for meta_id in scraper_settings.metadata_IDs_to_scrape] 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_GAME_SELECTION_MODE'] = 'Game selection mode: "{}"'.format(kodi.translate(scraper_settings.game_selection_mode)) + 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_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)) - options['SCRAPER_ASSETS_TO_SCRAPE'] = 'Assets to scrape: "{}"'.format(', '.join([a.plural for a in assets_to_scrape])) - options['SCRAPER_OVERWRITE_MODE'] = 'Overwrite existing files: "{}"'.format('Yes' if scraper_settings.overwrite_existing else 'No') - options['SCRAPER_IGNORE_TITLES_MODE'] = 'Ignore scraped titles: "{}"'.format('Yes' if scraper_settings.ignore_scrap_title else 'No') - options['SCRAPE'] = 'Scrape' + options['SCRAPER_META_TO_SCRAPE'] = 'Metadata to scrape: "{}"'.format(', '.join(metadata_to_scrape)) + options['SCRAPER_ASSETS_TO_SCRAPE'] = 'Assets to scrape: "{}"'.format(', '.join([a.plural for a in assets_to_scrape])) + options['SCRAPER_OVERWRITE_META_MODE'] = 'Overwrite existing metadata: "{}"'.format('Yes' if scraper_settings.overwrite_existing_meta else 'No') + options['SCRAPER_OVERWRITE_ASSETS_MODE'] = 'Overwrite existing assets/files: "{}"'.format('Yes' if scraper_settings.overwrite_existing_assets else 'No') + options['SCRAPER_IGNORE_TITLES_MODE'] = 'Ignore scraped titles: "{}"'.format('Yes' if scraper_settings.ignore_scrap_title else 'No') + options['SCRAPE'] = 'Scrape' dialog_title = f'Scrape collection "{collection.get_name()}" ROMs with "{selected_addon.get_name()}"' selected_option = kodi.OrdDictionaryDialog().select(dialog_title, options, preselect='SCRAPE') @@ -155,26 +156,27 @@ def cmd_scrape_rom_with_settings(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: addon_repository = AelAddonRepository(uow) - roms_repository = ROMsRepository(uow) + roms_repository = ROMsRepository(uow) - rom = roms_repository.find_rom(rom_id) - addon = addon_repository.find(scraper_id) - selected_addon = ScraperAddon(addon, scraper_settings) + rom = roms_repository.find_rom(rom_id) + addon = addon_repository.find(scraper_id) + selected_addon = ScraperAddon(addon, scraper_settings) assets_to_scrape = g_assetFactory.get_asset_list_by_IDs(scraper_settings.asset_IDs_to_scrape) metadata_to_scrape = [constants.METADATA_DESCRIPTIONS[meta_id] for meta_id in scraper_settings.metadata_IDs_to_scrape] 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_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)) - options['SCRAPER_ASSETS_TO_SCRAPE'] = 'Assets to scrape: "{}"'.format(', '.join([a.plural for a in assets_to_scrape])) - options['SCRAPER_OVERWRITE_MODE'] = 'Overwrite existing files: "{}"'.format('Yes' if scraper_settings.overwrite_existing else 'No') - options['SCRAPER_IGNORE_TITLES_MODE'] = 'Ignore scraped titles: "{}"'.format('Yes' if scraper_settings.ignore_scrap_title else 'No') - options['SCRAPE'] = 'Scrape' + options['SCRAPER_META_TO_SCRAPE'] = 'Metadata to scrape: "{}"'.format(', '.join(metadata_to_scrape)) + options['SCRAPER_ASSETS_TO_SCRAPE'] = 'Assets to scrape: "{}"'.format(', '.join([a.plural for a in assets_to_scrape])) + options['SCRAPER_OVERWRITE_META_MODE'] = 'Overwrite existing metadata: "{}"'.format('Yes' if scraper_settings.overwrite_existing_meta else 'No') + options['SCRAPER_OVERWRITE_ASSETS_MODE'] = 'Overwrite existing assets/files: "{}"'.format('Yes' if scraper_settings.overwrite_existing_assets else 'No') + options['SCRAPER_IGNORE_TITLES_MODE'] = 'Ignore scraped titles: "{}"'.format('Yes' if scraper_settings.ignore_scrap_title else 'No') + options['SCRAPE'] = 'Scrape' s = f'Scrape ROM "{rom.get_name()}" with "{selected_addon.get_name()}' selected_option = kodi.OrdDictionaryDialog().select(s, options, preselect='SCRAPE') @@ -203,22 +205,23 @@ def cmd_scrape_rom_metadata(args): uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) with uow: roms_repository = ROMsRepository(uow) - rom = roms_repository.find_rom(rom_id) + rom = roms_repository.find_rom(rom_id) scraper_settings = ScraperSettings().from_addon_settings() - scraper_settings.scrape_metadata_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY - scraper_settings.scrape_assets_policy = constants.SCRAPE_ACTION_NONE - scraper_settings.search_term_mode = constants.SCRAPE_MANUAL - scraper_settings.game_selection_mode = constants.SCRAPE_MANUAL + scraper_settings.scrape_metadata_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY + scraper_settings.scrape_assets_policy = constants.SCRAPE_ACTION_NONE + scraper_settings.search_term_mode = constants.SCRAPE_MANUAL + scraper_settings.game_selection_mode = constants.SCRAPE_MANUAL + scraper_settings.overwrite_existing_meta = True selected_addon = _select_scraper(uow, 'Scrape ROM metadata', scraper_settings) if selected_addon is None: # >> Exits context menu - logger.debug('SCRAPE_ROM_METADATA: cmd_scrape_rom_metadata() Selected None. Closing context menu') + logger.debug('SCRAPE_ROM_METADATA: Selected None. Closing context menu') AppMediator.sync_cmd('ROM_EDIT_METADATA', args) return - logger.debug(f'SCRAPE_ROM_METADATA: cmd_scrape_rom_metadata() Selected scraper#{selected_addon.get_name()}') + logger.debug(f'SCRAPE_ROM_METADATA: Selected scraper#{selected_addon.get_name()}') scraper_settings.metadata_IDs_to_scrape = selected_addon.get_supported_metadata() options = collections.OrderedDict() @@ -251,13 +254,13 @@ def cmd_scrape_rom_asset(args): rom = roms_repository.find_rom(rom_id) scraper_settings = ScraperSettings() - scraper_settings.scrape_assets_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY - scraper_settings.scrape_metadata_policy = constants.SCRAPE_ACTION_NONE - scraper_settings.search_term_mode = constants.SCRAPE_MANUAL - scraper_settings.game_selection_mode = constants.SCRAPE_MANUAL - scraper_settings.asset_selection_mode = constants.SCRAPE_MANUAL - scraper_settings.asset_IDs_to_scrape = [asset_id] - scraper_settings.overwrite_existing = True + scraper_settings.scrape_assets_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY + scraper_settings.scrape_metadata_policy = constants.SCRAPE_ACTION_NONE + scraper_settings.search_term_mode = constants.SCRAPE_MANUAL + scraper_settings.game_selection_mode = constants.SCRAPE_MANUAL + scraper_settings.asset_selection_mode = constants.SCRAPE_MANUAL + scraper_settings.asset_IDs_to_scrape = [asset_id] + scraper_settings.overwrite_existing_assets = True selected_addon = _select_scraper(uow, 'Scrape ROM {} asset'.format(asset_to_scrape.name), scraper_settings) if selected_addon is None: @@ -267,7 +270,7 @@ def cmd_scrape_rom_asset(args): return # >> Execute scraper - logger.debug('SCRAPE_ROM_ASSET: cmd_scrape_rom_asset() Selected scraper#{}'.format(selected_addon.get_name())) + logger.debug('SCRAPE_ROM_ASSET: Selected scraper#{}'.format(selected_addon.get_name())) kodi.notify('Preparing scraper') kodi.run_script( @@ -284,19 +287,19 @@ def cmd_scrape_rom_assets(args): rom = roms_repository.find_rom(rom_id) scraper_settings = ScraperSettings.from_addon_settings() - scraper_settings.scrape_assets_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY - scraper_settings.scrape_metadata_policy = constants.SCRAPE_ACTION_NONE - scraper_settings.search_term_mode = constants.SCRAPE_MANUAL - scraper_settings.asset_selection_mode = constants.SCRAPE_MANUAL + scraper_settings.scrape_assets_policy = constants.SCRAPE_POLICY_SCRAPE_ONLY + scraper_settings.scrape_metadata_policy = constants.SCRAPE_ACTION_NONE + scraper_settings.search_term_mode = constants.SCRAPE_MANUAL + scraper_settings.asset_selection_mode = constants.SCRAPE_MANUAL selected_addon = _select_scraper(uow, 'Scrape ROM assets', scraper_settings) if selected_addon is None: # >> Exits context menu - logger.debug('cmd_scrape_rom_assets() Selected None. Closing context menu') + logger.debug('SCRAPE_ROM_ASSETS: Selected None. Closing context menu') AppMediator.sync_cmd('ROM_EDIT_ASSETS', args) return - logger.debug('cmd_scrape_rom_assets() Selected scraper#{}'.format(selected_addon.get_name())) + logger.debug('SCRAPE_ROM_ASSETS: Selected scraper#{}'.format(selected_addon.get_name())) scraper_settings.asset_IDs_to_scrape = selected_addon.get_supported_assets() asset_options = g_assetFactory.get_all() @@ -344,7 +347,8 @@ def cmd_configure_scraper_metadata_policy(args): args['scraper_settings'] = scraper_settings AppMediator.sync_cmd(args['ret_cmd'], args) return - + + @AppMediator.register('SCRAPER_ASSET_POLICY') def cmd_configure_scraper_asset_policy(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -365,7 +369,8 @@ def cmd_configure_scraper_asset_policy(args): scraper_settings.scrape_assets_policy = selected_option args['scraper_settings'] = scraper_settings AppMediator.sync_cmd(args['ret_cmd'], args) - + + @AppMediator.register('SCRAPER_SEARCH_TERM_MODE') def cmd_configure_scraper_search_term_mode(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -385,6 +390,7 @@ def cmd_configure_scraper_search_term_mode(args): AppMediator.sync_cmd(args['ret_cmd'], args) return + @AppMediator.register('SCRAPER_GAME_SELECTION_MODE') def cmd_configure_scraper_game_selection_mode(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -404,6 +410,7 @@ def cmd_configure_scraper_game_selection_mode(args): AppMediator.sync_cmd(args['ret_cmd'], args) return + @AppMediator.register('SCRAPER_ASSET_SELECTION_MODE') def cmd_configure_scraper_asset_selection_mode(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -423,6 +430,7 @@ def cmd_configure_scraper_asset_selection_mode(args): AppMediator.sync_cmd(args['ret_cmd'], args) return + @AppMediator.register('SCRAPER_META_TO_SCRAPE') def cmd_configure_scraper_metadata_to_scrape(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -444,6 +452,7 @@ def cmd_configure_scraper_metadata_to_scrape(args): AppMediator.sync_cmd(args['ret_cmd'], args) return + @AppMediator.register('SCRAPER_ASSETS_TO_SCRAPE') def cmd_configure_scraper_assets_to_scrape(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() @@ -466,13 +475,23 @@ def cmd_configure_scraper_assets_to_scrape(args): AppMediator.sync_cmd(args['ret_cmd'], args) return -@AppMediator.register('SCRAPER_OVERWRITE_MODE') -def cmd_configure_scraper_overwrite_mode(args): + +@AppMediator.register('SCRAPER_OVERWRITE_META_MODE') +def cmd_configure_scraper_overwrite_meta_mode(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() - scraper_settings.overwrite_existing = not scraper_settings.overwrite_existing + scraper_settings.overwrite_existing_meta = not scraper_settings.overwrite_existing_meta args['scraper_settings'] = scraper_settings AppMediator.sync_cmd(args['ret_cmd'], args) + +@AppMediator.register('SCRAPER_OVERWRITE_ASSETS_MODE') +def cmd_configure_scraper_overwrite_assets_mode(args): + scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() + scraper_settings.overwrite_existing_assets = not scraper_settings.overwrite_existing_assets + args['scraper_settings'] = scraper_settings + AppMediator.sync_cmd(args['ret_cmd'], args) + + @AppMediator.register('SCRAPER_IGNORE_TITLES_MODE') def cmd_configure_scraper_ignore_mode(args): scraper_settings:ScraperSettings = args['scraper_settings'] if 'scraper_settings' in args else ScraperSettings.from_addon_settings() diff --git a/resources/lib/commands/view_rendering_commands.py b/resources/lib/commands/view_rendering_commands.py index 433d55fc..48da325c 100644 --- a/resources/lib/commands/view_rendering_commands.py +++ b/resources/lib/commands/view_rendering_commands.py @@ -275,7 +275,7 @@ def _render_root_view(categories_repository: CategoryRepository, romcollections_ for rom in root_roms: try: - root_items.append(_render_rom_listitem(rom)) + root_items.append(render_rom_listitem(rom)) except Exception: logger.exception(f"Exception while rendering list item ROM '{rom.get_name()}'") @@ -336,7 +336,7 @@ def _render_category_view(category_obj: Category, categories_repository: Categor for rom in roms: try: - view_items.append(_render_rom_listitem(rom)) + view_items.append(render_rom_listitem(rom)) except Exception: logger.exception(f"Exception while rendering list item ROM '{rom.get_name()}'") @@ -362,11 +362,11 @@ def _render_romcollection_view(romcollection_obj: ROMCollection, roms_repository for rom in roms: try: rom.apply_romcollection_asset_mapping(romcollection_obj) - view_items.append(_render_rom_listitem(rom)) + view_items.append(render_rom_listitem(rom)) except Exception: logger.exception(f'Exception while rendering list item ROM "{rom.get_name()}"') - logger.debug(f'Storing {len(view_items)} items for romcollection "{romcollection_obj.get_name()}" view.') + logger.debug(f'Found {len(view_items)} items for romcollection "{romcollection_obj.get_name()}" view.') view_data['items'] = view_items return view_data @@ -435,10 +435,14 @@ def _render_romcollection_listitem(romcollection_obj: ROMCollection) -> dict: 'is_folder': True, 'type': 'video', 'info': { - 'title' : romcollection_name, 'year' : romcollection_obj.get_releaseyear(), - 'genre' : romcollection_obj.get_genre(), 'studio' : romcollection_obj.get_developer(), - 'rating' : romcollection_obj.get_rating(), 'plot' : romcollection_obj.get_plot(), - 'trailer' : romcollection_obj.get_trailer(), 'overlay' : ICON_OVERLAY + 'title': romcollection_name, + 'year': romcollection_obj.get_releaseyear(), + 'genre': romcollection_obj.get_genre(), + 'studio': romcollection_obj.get_developer(), + 'rating': romcollection_obj.get_rating(), + 'plot': romcollection_obj.get_plot(), + 'trailer': romcollection_obj.get_trailer(), + 'overlay': ICON_OVERLAY }, 'art': assets, 'properties': { @@ -458,7 +462,7 @@ def _render_romcollection_listitem(romcollection_obj: ROMCollection) -> dict: #if not settings.getSettingAsBool('display_hide_LB_scraper'): render_vcategory_LB_offline_scraper_row() -def _render_rom_listitem(rom_obj: ROM) -> dict: +def render_rom_listitem(rom_obj: ROM) -> dict: # --- Do not render row if romcollection finished --- if rom_obj.is_finished() and settings.getSettingAsBool('display_hide_finished'): return @@ -512,13 +516,22 @@ def _render_rom_listitem(rom_obj: ROM) -> dict: AKL_MultiDisc_bool_value = constants.AKL_MULTIDISC_BOOL_VALUE_TRUE list_name = rom_obj.get_name() + sub_label = rom_obj.get_rom_identifier() if list_name is None or list_name == '': - list_name = rom_obj.get_rom_identifier() + list_name = sub_label + if list_name == sub_label: + sub_label = None + + if settings.getSettingAsBool("display_execute_rom_by_default"): + item_url = globals.router.url_for_path(f'execute/rom/{rom_obj.get_id()}') + else: + item_url = globals.router.url_for_path(f'rom/view/{rom_obj.get_id()}') return { 'id': rom_obj.get_id(), 'name': list_name, - 'url': globals.router.url_for_path(f'execute/rom/{rom_obj.get_id()}'), + 'name2': sub_label, + 'url': item_url, 'is_folder': False, 'type': 'video', 'info': { @@ -533,6 +546,8 @@ def _render_rom_listitem(rom_obj: ROM) -> dict: }, 'art': assets, 'properties': { + 'entityid': rom_obj.get_id(), + 'identifier': rom_obj.get_rom_identifier(), 'platform': rom_obj.get_platform(), 'nplayers': rom_obj.get_number_of_players(), 'nplayers_online': rom_obj.get_number_of_players_online(), diff --git a/resources/lib/domain.py b/resources/lib/domain.py index 85798694..627aa3cd 100644 --- a/resources/lib/domain.py +++ b/resources/lib/domain.py @@ -2,7 +2,8 @@ # # Advanced Kodi Launcher miscellaneous set of objects # -# Copyright (c) Wintermute0110 / Chrisism +# Copyright (c) Chrisism +# Portions (c) Wintermute0110 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -44,6 +45,11 @@ def _is_a_number(input: any): def _is_empty(input: any) -> bool: return input is None or (not _is_a_number(input) and len(input) == 0) +def _is_empty_or_default(input: any, default: any): + if _is_empty(input): + return True + return input == default + # ------------------------------------------------------------------------------------------------- # Gets all required information about an asset: path, name, etc. # Returns an object with all the information @@ -202,6 +208,9 @@ def set_path(self, path_str): def set_asset_info(self, info:AssetInfo): self.asset_info = info + def is_assigned(self) -> bool: + return self.get_path() != '' + def clear(self): self.entity_data['filepath'] = '' @@ -849,7 +858,8 @@ def __init__(self, category_dic: typing.Dict[str, typing.Any] = None, assets: ty def get_object_name(self): return '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 @@ -864,13 +874,16 @@ def num_categories(self) -> int: return self.entity_data['num_categories'] if 'num_categories' in self.entity_data else 0 def has_items(self) -> bool: - return len(self.num_romcollections()) > 0 or len(self.num_categories()) > 0 + return self.num_romcollections() > 0 or self.num_categories() > 0 - def get_asset_ids_list(self): return constants.CATEGORY_ASSET_ID_LIST + def get_asset_ids_list(self): + return constants.CATEGORY_ASSET_ID_LIST - def get_mappable_asset_ids_list(self): return constants.MAPPABLE_CATEGORY_ASSET_ID_LIST + def get_mappable_asset_ids_list(self): + return constants.MAPPABLE_CATEGORY_ASSET_ID_LIST - def get_default_icon(self) -> str: return 'DefaultFolder.png' + def get_default_icon(self) -> str: + return 'DefaultFolder.png' def get_NFO_name(self) -> io.FileName: nfo_dir = io.FileName(settings.getSetting('categories_asset_dir'), isdir = True) @@ -1024,13 +1037,16 @@ 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 - def set_box_sizing(self, box_size): self.entity_data['box_size'] = box_size + def set_box_sizing(self, box_size): + self.entity_data['box_size'] = box_size - def get_asset_ids_list(self): return constants.LAUNCHER_ASSET_ID_LIST + 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_default_icon(self) -> str: return 'DefaultFolder.png' + def get_default_icon(self) -> str: + return 'DefaultGameAddons.png' def get_ROM_mappable_asset_list(self) -> typing.List[AssetInfo]: return g_assetFactory.get_asset_list_by_IDs(constants.MAPPABLE_ROM_ASSET_ID_LIST) @@ -1312,7 +1328,8 @@ def __init__(self, def get_object_name(self): return 'ROM' - def get_assets_kind(self): return constants.KIND_ASSET_ROM + def get_assets_kind(self): + return constants.KIND_ASSET_ROM def get_type(self): return constants.OBJ_ROM @@ -1450,6 +1467,9 @@ def set_clone(self, clone): def scanned_with(self, scanner_id: str): self.entity_data['scanned_by_id'] = scanner_id + def get_scanned_data(self): + return self.scanned_data + def get_scanned_data_element(self, key:str): return self.scanned_data[key] if key in self.scanned_data else None @@ -1633,57 +1653,71 @@ def update_with(self, api_rom_obj: api.ROMObj, metadata_to_update=[], assets_to_update=[], - overwrite_existing=False, + overwrite_existing_metadata=False, + overwrite_existing_assets=False, update_scanned_data=False): + logger.debug(f"Overwriting existing metadata in domain: {overwrite_existing_metadata}") + logger.debug(f"Overwriting existing assets in domain: {overwrite_existing_assets}") + if constants.META_TITLE_ID in metadata_to_update \ - and api_rom_obj.get_name(): + and api_rom_obj.get_name() \ + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_name(), constants.DEFAULT_META_TITLE)): self.set_name(api_rom_obj.get_name()) if constants.META_PLOT_ID in metadata_to_update \ and api_rom_obj.get_plot() \ - and (overwrite_existing or _is_empty(self.get_plot())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_plot(), constants.DEFAULT_META_PLOT)): self.set_plot(api_rom_obj.get_plot()) if constants.META_YEAR_ID in metadata_to_update \ and api_rom_obj.get_releaseyear() \ - and (overwrite_existing or _is_empty(self.get_releaseyear())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_releaseyear(), constants.DEFAULT_META_YEAR)): self.set_releaseyear(api_rom_obj.get_releaseyear()) if constants.META_GENRE_ID in metadata_to_update \ and api_rom_obj.get_genre() \ - and (overwrite_existing or _is_empty(self.get_genre())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_genre(), constants.DEFAULT_META_GENRE)): self.set_genre(api_rom_obj.get_genre()) if constants.META_DEVELOPER_ID in metadata_to_update \ and api_rom_obj.get_developer() \ - and (overwrite_existing or _is_empty(self.get_developer())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_developer(), constants.DEFAULT_META_DEVELOPER)): self.set_developer(api_rom_obj.get_developer()) if constants.META_NPLAYERS_ID in metadata_to_update \ and api_rom_obj.get_number_of_players() \ - and (overwrite_existing or _is_empty(self.get_number_of_players())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_number_of_players(), constants.DEFAULT_META_NPLAYERS)): self.set_number_of_players(api_rom_obj.get_number_of_players()) if constants.META_NPLAYERS_ONLINE_ID in metadata_to_update \ and api_rom_obj.get_number_of_players_online() \ - and (overwrite_existing or _is_empty(self.get_number_of_players_online())): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_number_of_players_online(), constants.DEFAULT_META_NPLAYERS)): self.set_number_of_players_online(api_rom_obj.get_number_of_players_online()) if constants.META_ESRB_ID in metadata_to_update\ and api_rom_obj.get_esrb_rating() \ - and (overwrite_existing or _is_empty(self.get_esrb_rating()) \ - or self.get_esrb_rating() == constants.ESRB_PENDING): + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_esrb_rating(), constants.DEFAULT_META_ESRB)): self.set_esrb_rating(api_rom_obj.get_esrb_rating()) if constants.META_PEGI_ID in metadata_to_update\ - and api_rom_obj.get_pegi_rating() \ - and (overwrite_existing or _is_empty(self.get_pegi_rating())): + and api_rom_obj.get_pegi_rating() \ + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_pegi_rating(), constants.DEFAULT_META_PEGI)): self.set_pegi_rating(api_rom_obj.get_pegi_rating()) if constants.META_RATING_ID in metadata_to_update \ - and api_rom_obj.get_rating() \ - and (overwrite_existing or _is_empty(self.get_rating())): + and api_rom_obj.get_rating() \ + and (overwrite_existing_metadata or \ + _is_empty_or_default(self.get_rating(), constants.DEFAULT_META_RATING)): self.set_rating(api_rom_obj.get_rating()) if constants.META_TAGS_ID in metadata_to_update and api_rom_obj.get_tags() is not None: @@ -1691,20 +1725,24 @@ def update_with(self, self.add_tag(tag) if len(assets_to_update) > 0: - for asset_id in self.get_asset_ids_list(): - if api_rom_obj.get_asset(asset_id) is not None: + for asset_id in assets_to_update: + existing_asset = self.get_asset(asset_id) + new_asset = api_rom_obj.get_asset(asset_id) + if new_asset is not None and \ + (overwrite_existing_assets or existing_asset is None or not existing_asset.is_assigned()): if asset_id == constants.ASSET_TRAILER_ID: - self.set_trailer(api_rom_obj.get_asset(asset_id)) + self.set_trailer(new_asset) else: asset_info = g_assetFactory.get_asset_info(asset_id) - asset_path = io.FileName(api_rom_obj.get_asset(asset_id)) + asset_path = io.FileName(new_asset) self.set_asset(asset_info, asset_path) if update_scanned_data: scanned_name = api_rom_obj.get_name() scanned_data = api_rom_obj.get_scanned_data() - if scanned_name: self.set_name(scanned_name) + if scanned_name: + self.set_name(scanned_name) for scanned_entry in list(scanned_data.keys()): self.set_scanned_data_element(scanned_entry, scanned_data[scanned_entry]) diff --git a/resources/lib/editors.py b/resources/lib/editors.py index 77754293..323dfe3d 100644 --- a/resources/lib/editors.py +++ b/resources/lib/editors.py @@ -3,7 +3,8 @@ # Advanced Kodi Launcher: metadata editor actions # -# Copyright (c) Wintermute0110 / Chrisism +# Copyright (c) Chrisism +# Portions (c) Wintermute0110 # Portions (c) 2010-2015 Angelscry # # This program is free software; you can redistribute it and/or modify diff --git a/resources/lib/globals.py b/resources/lib/globals.py index 6ef4f19b..ff6f7232 100644 --- a/resources/lib/globals.py +++ b/resources/lib/globals.py @@ -31,6 +31,7 @@ addon_author = addon.getAddonInfo('author') addon_profile = addon.getAddonInfo('profile') addon_type = addon.getAddonInfo('type') +addon_path = addon.getAddonInfo('path') # --- Addon paths and constant definition --- # _PATH is a filename | _DIR is a directory. diff --git a/resources/lib/queries.py b/resources/lib/queries.py index bd1f9edf..5635220f 100644 --- a/resources/lib/queries.py +++ b/resources/lib/queries.py @@ -31,7 +31,7 @@ WHERE id =? """ INSERT_CATEGORY_ASSET = "INSERT INTO category_assets (category_id, asset_id) VALUES (?, ?)" -DELETE_CATEGORY = "DELETE FROM category WHERE id = ?" +DELETE_CATEGORY = "DELETE FROM categories WHERE id = ?" INSERT_ROM_IN_CATEGORY = "INSERT INTO roms_in_category (rom_id, category_id) VALUES (?,?)" INSERT_ROM_IN_ROOT_CATEGORY = "INSERT INTO roms_in_category (rom_id, category_id) VALUES (?,NULL)" diff --git a/resources/lib/repositories.py b/resources/lib/repositories.py index 33c83a96..fc122a18 100644 --- a/resources/lib/repositories.py +++ b/resources/lib/repositories.py @@ -1061,7 +1061,7 @@ def find_rom(self, rom_id:str) -> ROM: rom_data = self._uow.single_result() self._uow.execute(qry.SELECT_ROM_ASSETS, rom_id) - assets_result_set = self._uow.result_set() + assets_result_set = self._uow.result_set() assets = [] for asset_data in assets_result_set: assets.append(Asset(asset_data)) diff --git a/resources/lib/viewqueries.py b/resources/lib/viewqueries.py index 6dc21189..4693add3 100644 --- a/resources/lib/viewqueries.py +++ b/resources/lib/viewqueries.py @@ -34,9 +34,12 @@ from resources.lib import globals from resources.lib.commands.mediator import AppMediator from resources.lib.commands import view_rendering_commands -from resources.lib.repositories import ViewRepository +from resources.lib.repositories import ViewRepository, UnitOfWork, ROMsRepository, g_assetFactory + logger = logging.getLogger(__name__) + + # # Root view items # @@ -98,6 +101,7 @@ def qry_get_root_items(): return container + # # View pre-rendered items. # @@ -107,10 +111,165 @@ def qry_get_view_items(view_id: str, is_virtual_view=False): return container # -# View database unrendered items. +# DB based items # -def qry_get_database_view_items(category_id: str, collection_value: str): - return view_rendering_commands.cmd_render_virtual_collection(category_id, collection_value) +def qry_get_view_item(rom_id: str): + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + container = None + with uow: + roms_repository = ROMsRepository(uow) + rom = roms_repository.find_rom(rom_id) + + item = view_rendering_commands.render_rom_listitem(rom) + container = { + 'id': rom_id, + 'name': rom.get_name(), + 'obj_type': constants.OBJ_ROM, + 'items': [item] + } + + return container + +def qry_get_view_metadata(rom_id: str): + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + container = None + with uow: + roms_repository = ROMsRepository(uow) + rom = roms_repository.find_rom(rom_id) + + items = [] + items.append({ + 'id': 40801, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_GENRE_ID}/items?value={rom.get_genre()}'), + 'name': kodi.translate(40801), 'name2': rom.get_genre(), + 'info': {}, 'art': {}, 'properties': {'field': 'genre'}}) + items.append({ + 'id': 40803, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_YEARS_ID}/items?value={rom.get_releaseyear()}'), + 'name': kodi.translate(40803), 'name2': rom.get_releaseyear(), + 'info': {}, 'art': {}, 'properties': {'field': 'releaseyear'}}) + items.append({ + 'id': 40802, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_DEVELOPER_ID}/items?value={rom.get_developer()}'), + 'name': kodi.translate(40802), 'name2': rom.get_developer(), + 'info': {}, 'art': {}, 'properties': {'field': 'developer'}}) + items.append({ + 'id': 40806, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_RATING_ID}/items?value={rom.get_rating()}'), + 'name': kodi.translate(40806), 'name2': str(rom.get_rating()), + 'info': {}, 'art': {}, 'properties': {'field': 'rating'}}) + items.append({ + 'id': 40804, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_ESRB_ID}/items?value={rom.get_esrb_rating()}'), + 'name': kodi.translate(40804), 'name2': rom.get_esrb_rating(), + 'info': {}, 'art': {}, 'properties': {'field': 'esrb'}}) + items.append({ + 'id': 40805, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_PEGI_ID}/items?value={rom.get_pegi_rating()}'), + 'name': kodi.translate(40805), 'name2': rom.get_pegi_rating(), + 'info': {}, 'art': {}, 'properties': {'field': 'pegi'}}) + items.append({ + 'id': 40808, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/{constants.VCATEGORY_NPLAYERS_ID}/items?value={rom.get_number_of_players()}'), + 'name': kodi.translate(40808), 'name2': str(rom.get_number_of_players()), + 'info': {}, 'art': {}, 'properties': {'field': 'nplayers'}}) + items.append({ + 'id': 40809, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path('execute/command/reset_database'), + 'name': kodi.translate(40809), 'name2': str(rom.get_number_of_players_online()), + 'info': {}, 'art': {}, 'properties': {'field': 'nplayers_online'}}) + items.append({ + 'id': 40810, 'is_folder': False, 'type': 'game', + 'url': globals.router.url_for_path( + f'/collection/virtual/items?value={rom.get_genre()}'), + 'name': kodi.translate(40810), 'name2': ','.join(rom.get_tags()), + 'info': {}, 'art': {}, 'properties': {'field': 'tags'}}) + + container = { + 'id': rom_id, + 'name': rom.get_name(), + 'obj_type': constants.OBJ_ROM, + 'items': items + } + + return container + + +def qry_get_view_assets(rom_id: str): + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + container = None + with uow: + roms_repository = ROMsRepository(uow) + rom = roms_repository.find_rom(rom_id) + + assigned_assets = rom.get_assets() + asset_ids = rom.get_asset_ids_list() + items = [] + for asset_id in asset_ids: + asset = next((a for a in assigned_assets if a.get_asset_info_id() == asset_id), None) + asset_info = asset.asset_info if asset else g_assetFactory.get_asset_info(asset_id) + items.append({ + 'id': asset_id, + 'is_folder': False, + 'type': 'pictures', + 'name': asset_info.name, + 'name2': asset.get_path() if asset else None, + 'url': asset.get_path() if asset else globals.router.url_for_path( + f'/execute/command/rom_edit_assets/?rom_id={rom_id}&selected_asset={asset_id}'), + 'info': { + 'title': asset_info.name, + 'picturepath': asset.get_path() if asset else None, + }, + 'art': { + 'thumb': asset.get_path() if asset else 'DefaultAddonImages.png' + }, + 'properties': { + 'is_set': str(asset and asset.is_assigned()), + 'assetid': asset_id + } + }) + + container = { + 'name': rom.get_name(), + 'id': rom_id, + 'obj_type': constants.OBJ_NONE, + 'items': items + } + + return container + + +def qry_get_view_scanned_data(rom_id: str): + uow = UnitOfWork(globals.g_PATHS.DATABASE_FILE_PATH) + container = None + with uow: + roms_repository = ROMsRepository(uow) + rom = roms_repository.find_rom(rom_id) + scanned_data = rom.get_scanned_data() + items = [] + for key, value in scanned_data.items(): + items.append({ + 'is_folder': False, 'type': 'game', + 'name': key, 'name2': value, + 'url': globals.router.url_for_path( + f'/rom/{rom.get_id()}/view/scanneddata?field={key}'), + 'info': {}, 'art': {}, 'properties': {}}) + container = { + 'id': rom_id, + 'name': rom.get_name(), + 'obj_type': constants.OBJ_ROM, + 'items': items + } + + return container + # # Utilities items @@ -299,6 +458,7 @@ def qry_get_utilities_items(): return container + # # Global Reports items # @@ -377,6 +537,7 @@ def qry_get_globalreport_items(): }) return container + # # Default context menu items for the whole container. # @@ -417,6 +578,7 @@ def qry_container_context_menu_items(container_data) -> typing.List[typing.Tuple return commands + # # ListItem specific context menu items. # @@ -442,7 +604,7 @@ def qry_listitem_context_menu_items(list_item_data, container_data)-> typing.Lis commands = [] if is_rom: - commands.append(('View ROM', _context_menu_url_for(f'/rom/{item_id}/view'))) + commands.append(('View ROM', _context_menu_url_for(f'/rom/view/{item_id}'))) commands.append(('Edit ROM', _context_menu_url_for(f'/rom/edit/{item_id}'))) commands.append(('Link ROM in other collection', _context_menu_url_for('/execute/command/link_rom',{'rom_id':item_id}))) commands.append(('Add ROM to AKL Favourites', _context_menu_url_for('/execute/command/add_rom_to_favourites',{'rom_id':item_id}))) @@ -468,8 +630,9 @@ def qry_listitem_context_menu_items(list_item_data, container_data)-> typing.Lis return commands + def _context_menu_url_for(url: str, params: dict = None) -> str: if params is not None: url = '{}?{}'.format(url, urlencode(params)) url = globals.router.url_for_path(url) - return 'RunPlugin({})'.format(url) \ No newline at end of file + return f'RunPlugin({url})' \ No newline at end of file diff --git a/resources/lib/views.py b/resources/lib/views.py index 6ec37d81..4922fe3d 100644 --- a/resources/lib/views.py +++ b/resources/lib/views.py @@ -35,6 +35,7 @@ import sys import abc import logging +import typing # --- Kodi stuff --- import xbmc @@ -46,6 +47,7 @@ from resources.lib import viewqueries, globals from resources.lib.commands.mediator import AppMediator +from resources.lib.commands import view_rendering_commands from resources.lib.globals import router logger = logging.getLogger(__name__) @@ -63,8 +65,16 @@ def run_plugin(addon_argv): # --- Bootstrap object instances --- globals.g_bootstrap_instances() + + argv = None + if sys.argv[0] == "addon.py": + argv = [] + argv.append(sys.argv[1]) + argv.append(router.handle) + argv.append(sys.argv[2]) + try: - router.run() + router.run(argv) except Exception as e: logger.error('Exception while executing route', exc_info=e) kodi.notify_error('Failed to execute route or command') @@ -80,7 +90,7 @@ def vw_route_render_root(): container = viewqueries.qry_get_root_items() container_context_items = viewqueries.qry_container_context_menu_items(container) - render_list_items(container, container_context_items) + _render_list_items(container, container_context_items) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) @router.route('/category/') @@ -103,7 +113,7 @@ def vw_route_render_collection(view_id: str): if container_type == constants.OBJ_ROMCOLLECTION or container_type == constants.OBJ_COLLECTION_VIRTUAL: kodi.notify('Collection {} has no items. Add ROMs'.format(container['name'])) else: - render_list_items(container, container_context_items, filter) + _render_list_items(container, container_context_items, filter) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) @@ -135,21 +145,21 @@ def vw_route_render_virtual_view(view_id: str): if kodi.dialog_yesno(f"Virtual collection {container['name']} has no items. Regenerate the views now?"): AppMediator.async_cmd('RENDER_VCATEGORY_VIEW', {'vcategory_id': container['parent_id']}) else: - render_list_items(container, container_context_items, filter) + _render_list_items(container, container_context_items, filter) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) @router.route('/collection/virtual//items') def vw_route_render_virtual_items_view(category_id: str): collection_value = router.args["value"][0] - container = viewqueries.qry_get_database_view_items(category_id, collection_value) + container = view_rendering_commands.cmd_render_virtual_collection(category_id, collection_value) container_context_items = viewqueries.qry_container_context_menu_items(container) filter_type = router.args['filter'][0] if 'filter' in router.args else None filter_term = router.args['term'][0] if 'term' in router.args else None filter = vw_create_filter(filter_type, filter_term) - render_list_items(container, container_context_items, filter) + _render_list_items(container, container_context_items, filter) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) @@ -161,7 +171,7 @@ def vw_route_render_utilities(): container = viewqueries.qry_get_utilities_items() container_context_items = viewqueries.qry_container_context_menu_items(container) - render_list_items(container, container_context_items) + _render_list_items(container, container_context_items) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) @router.route('/globalreports') @@ -169,7 +179,7 @@ def vw_route_render_globalreports(): container = viewqueries.qry_get_globalreport_items() container_context_items = viewqueries.qry_container_context_menu_items(container) - render_list_items(container, container_context_items) + _render_list_items(container, container_context_items) xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) # ------------------------------------------------------------------------------------------------- @@ -218,19 +228,60 @@ def vw_edit_rom(rom_id: str): AppMediator.async_cmd('EDIT_ROM', {'rom_id': rom_id }) # ------------------------------------------------------------------------------------------------- -# ROM execution +# ROM execution / view # ------------------------------------------------------------------------------------------------- @router.route('/execute/rom/') def vw_route_execute_rom(rom_id): AppMediator.async_cmd("EXECUTE_ROM", {'rom_id': rom_id} ) - + + +@router.route('/rom/view/') +def vw_view_rom(rom_id): + xbmc.executebuiltin('Dialog.Close(busydialog)') + container = viewqueries.qry_get_view_item(rom_id) + ui = ViewRomGUI('script-akl-romdetails.xml', globals.addon_path, 'default', '1080i', True, + container_id=19801,container_data=container) + ui.doModal() + del ui + + +@router.route('/rom//metadata') +def vw_view_rom_metadata(rom_id): + container = viewqueries.qry_get_view_metadata(rom_id) + _render_list_items(container) + xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) + + +@router.route('/rom//assets') +def vw_view_rom_assets(rom_id): + container = viewqueries.qry_get_view_assets(rom_id) + _render_list_items(container) + xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) + + +@router.route('/rom//scanneddata') +def vw_view_rom_metadata(rom_id): + container = viewqueries.qry_get_view_scanned_data(rom_id) + _render_list_items(container) + xbmcplugin.endOfDirectory(handle = router.handle, succeeded = True, cacheToDisc = False) + +@router.route('/rom//view/scanneddata') +def vw_view_rom_metadata(rom_id): + field = router.args['field'][0] if 'field' in router.args else None + if not field: + kodi.notify_warn("No field specified") + return + container = viewqueries.qry_get_view_scanned_data(rom_id) + requested_item = next((i for i in container['items'] if i['name'] == field), None) + xbmcgui.Dialog().textviewer(str(field), str(requested_item['name2'])) + # ------------------------------------------------------------------------------------------------- # UI render methods # ------------------------------------------------------------------------------------------------- # # Renders items for a view. # -def render_list_items(container_data:dict, container_context_items = [], filter_method:ListFilter = None): +def _render_list_items(container_data:dict, container_context_items = [], filter_method:ListFilter = None): vw_misc_set_all_sorting_methods() vw_misc_set_AEL_Content(container_data['obj_type'] if 'obj_type' in container_data else constants.OBJ_NONE) vw_misc_clear_AEL_Launcher_Content() @@ -244,15 +295,9 @@ def render_list_items(container_data:dict, container_context_items = [], filter_ if filter_method and not filter_method.is_valid(list_item_data): continue - name = list_item_data['name'] - url_str = list_item_data['url'] - folder_flag = list_item_data['is_folder'] - item_type = list_item_data['type'] - - list_item = xbmcgui.ListItem(name) - list_item.setInfo(item_type, list_item_data['info']) - list_item.setArt(list_item_data['art']) - list_item.setProperties(list_item_data['properties']) + list_item = _render_list_item(list_item_data) + url_str = list_item_data['url'] + folder_flag = list_item_data['is_folder'] if xbmc.getCondVisibility("!Skin.HasSetting(KioskMode.Enabled)"): item_context_items = viewqueries.qry_listitem_context_menu_items(list_item_data, container_data) @@ -260,6 +305,71 @@ def render_list_items(container_data:dict, container_context_items = [], filter_ xbmcplugin.addDirectoryItem(handle = router.handle, url = url_str, listitem = list_item, isFolder = folder_flag) +def _render_list_item(list_item_data: dict) -> xbmcgui.ListItem: + name = list_item_data['name'] + name2 = list_item_data['name2'] if 'name2' in list_item_data else "" + item_type = list_item_data['type'] + + list_item = xbmcgui.ListItem(name, label2=name2) + list_item.setInfo(item_type, list_item_data['info']) + list_item.setArt(list_item_data['art']) + list_item.setProperties(list_item_data['properties']) + + return list_item + + +class ViewRomGUI(xbmcgui.WindowXML): + + def __init__(self, *args, **kwargs): + self.first_load = True + + self.container_id = kwargs['container_id'] + self.container_data = kwargs['container_data'] + + def onInit(self): + if self.first_load: + self._render_items() + + self.textviewer_id = self.getProperty("TextViewerButtonId") + self.global_button_id = self.getProperty("GlobalButtonId") + + def onClick(self, controlId: int): + str_control_id = str(controlId) + + if str_control_id == self.textviewer_id: + plot = self.container_data["items"][0]["info"]["plot"] + xbmcgui.Dialog().textviewer(kodi.translate(40811), plot) + + if str_control_id == self.global_button_id: + uri = self.getProperty("call") + args = self.getProperty("callargs") + uri_args = None + if args: + args_lst = args.split("=") + uri_args = { args_lst[0]: args_lst[1]} + self.close() + kodi.update_uri(uri, uri_args) + + return super().onClick(controlId) + + def _render_items(self): + self.first_load = False + self.clearList() + try: + cntrl = self.getControl(self.container_id) + # Container Properties + if 'properties' in self.container_data: + for property, value in self.container_data['properties'].items(): + self.setContainerProperty(property, value) + + for list_item_data in self.container_data['items']: + list_item = _render_list_item(list_item_data) + cntrl.addItem(list_item) + except RuntimeError as error: + logger.error(f'Control cannot be filled. Error: {error}') + pass + + # ------------------------------------------------------------------------------------------------- # UI helper methods # ------------------------------------------------------------------------------------------------- @@ -268,12 +378,13 @@ def render_list_items(container_data:dict, container_context_items = [], filter_ # def vw_misc_set_all_sorting_methods(): # >> This must be called only if router.handle > 0, otherwise Kodi will complain in the log. - if router.handle < 0: return + if router.handle < 0: + return xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_LABEL_IGNORE_FOLDERS) xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_VIDEO_YEAR) xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_STUDIO) - xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_GENRE) xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_UNSORTED) + xbmcplugin.addSortMethod(handle = router.handle, sortMethod = xbmcplugin.SORT_METHOD_GENRE) # # Set the AEL content type. @@ -309,18 +420,29 @@ def vw_misc_clear_AEL_Launcher_Content(): xbmcgui.Window(constants.AKL_CONTENT_WINDOW_ID).setProperty(constants.AKL_LAUNCHER_BOXSIZE_LABEL, '') def vw_create_filter(filter_on_type:str, filter_on_value:str) -> ListFilter: - if filter_on_type is None: return None - if filter_on_value == 'UNDEFINED': filter_on_value = '' + if filter_on_type is None: + return None + if filter_on_value == 'UNDEFINED': + filter_on_value = '' - if filter_on_type == constants.META_TITLE_ID: return OnTitleFilter(filter_on_value) - if filter_on_type == constants.META_DEVELOPER_ID: return OnDeveloperFilter(filter_on_value) - if filter_on_type == constants.META_GENRE_ID: return OnGenreFilter(filter_on_value) - if filter_on_type == constants.META_YEAR_ID: return OnReleaseYearFilter(filter_on_value) - if filter_on_type == constants.META_RATING_ID: return OnRatingFilter(filter_on_value) - if filter_on_type == constants.META_ESRB_ID: return OnESRBFilter(filter_on_value) - if filter_on_type == constants.META_PEGI_ID: return OnPEGIFilter(filter_on_value) - if filter_on_type == constants.META_NPLAYERS_ID: return OnNumberOfPlayersFilter(filter_on_value) - if filter_on_type == 'platform': return OnPlatformFilter(filter_on_value) + if filter_on_type == constants.META_TITLE_ID: + return OnTitleFilter(filter_on_value) + if filter_on_type == constants.META_DEVELOPER_ID: + return OnDeveloperFilter(filter_on_value) + if filter_on_type == constants.META_GENRE_ID: + return OnGenreFilter(filter_on_value) + if filter_on_type == constants.META_YEAR_ID: + return OnReleaseYearFilter(filter_on_value) + if filter_on_type == constants.META_RATING_ID: + return OnRatingFilter(filter_on_value) + if filter_on_type == constants.META_ESRB_ID: + return OnESRBFilter(filter_on_value) + if filter_on_type == constants.META_PEGI_ID: + return OnPEGIFilter(filter_on_value) + if filter_on_type == constants.META_NPLAYERS_ID: + return OnNumberOfPlayersFilter(filter_on_value) + if filter_on_type == 'platform': + return OnPlatformFilter(filter_on_value) logger.debug(f'Filter called without proper filter type. "{filter_on_type}"') return None @@ -370,4 +492,3 @@ def is_valid(self, subject: dict) -> bool: class OnPlatformFilter(ListFilter): def is_valid(self, subject: dict) -> bool: return 'properties' in subject and 'platform' in subject['properties'] and subject['properties']['platform'] == self.filter_on_value - \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index 904b2a5b..ff30e321 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -126,6 +126,11 @@ + + 0 + false + + 0 false diff --git a/resources/skins/default/1080i/script-akl-romdetails.xml b/resources/skins/default/1080i/script-akl-romdetails.xml new file mode 100644 index 00000000..1654e319 --- /dev/null +++ b/resources/skins/default/1080i/script-akl-romdetails.xml @@ -0,0 +1,750 @@ + + + 5000 + SetProperty(TextViewerButtonId,138) + SetProperty(GlobalButtonId,19810) + SetProperty(DataListView,metadata) + + + Storage container for the item details + -3000 + -3000 + 1 + 1 + + + + + Hidden general action button + -3000 + -3000 + 1 + 1 + + + + Main window + 50% + 1080 + 50% + 1920 + + 1920 + 1080 + scale + colors/black.png + + + Conditional + + 1920 + 1080 + scale + backgrounds/grayfade.jpg + + + + + Poster/Boxfront + 150 + 66 + + + + + + + + + + -16 + -16 + 566 + 841 + colors/black.png + overlays/shadow.png + 20 + + + 0.10 + 4 + 4 + 526 + 801 + scale + $INFO[Container(19801).ListItem.Art(boxfront)] + + + !String.IsEmpty(Container(19801).ListItem.Art(thumb)) + !String.IsEqual(Container(19801).ListItem.Art(thumb),Container(19801).ListItem.Art(boxfront)) + + 4 + 4 + 526 + 801 + stretch + colors/black.png + + + 14 + 4 + 506 + 801 + keep + $INFO[Container(19801).ListItem.Art(thumb)] + overlays/shadow.png + 20 + + + + + + Lists and boxes + + + + + + + + + 620 + -30 + + + Plot box + 468 + + 754 + 418 + 40 + 20 + bottom + + 19802 + 4000 + 4000 + 4010 + 4010 + 5000 + buttons/button-fo.png + dialogs/dialog-bg.png + + + 735 + 512 + 418 + bottom + dialogs/dialog-bg.png + + + 40 + 25 + 670 + 363 + + true + + + + + Metadata list + vertical + String.IsEmpty(Window.Property(DataListView)) | String.IsEqual(Window.Property(DataListView),metadata) + 755 + 488 + 377 + -8 + 5000 + SetFocus(5000,4) + 19802 + 140 + 140 + SetProperty(call,$INFO[Container(4000).ListItem.Property(action)]) + SetProperty(callargs,$INFO[Container(4000).ListItem.Property(args)]) + SendClick(,19810) + + + + + $INFO[Container(19801).ListItem.Label] + plugin://plugin.program.akl/collection/virtual/vcategory_title/items/ + $INFO[Container(19801).ListItem.Label,value=,] + !String.IsEmpty(Container(19801).ListItem.Label) + + + + $INFO[Container(19801).ListItem.Property(platform)] + plugin://plugin.program.akl/collection/virtual/vcategory_title/items/ + $INFO[Container(19801).ListItem.Property(platform),value=,] + !String.IsEmpty(Container(19801).ListItem.Property(platform)) + + + + $INFO[Container(19801).ListItem.Property(identifier)] + plugin://plugin.program.akl/collection/virtual/vcategory_title/items/ + $INFO[Container(19801).ListItem.Property(identifier),value=,] + !String.IsEmpty(Container(19801).ListItem.Property(identifier)) + + + + $INFO[Container(19801).ListItem.Genre] + plugin://plugin.program.akl/collection/virtual/vcategory_genre/items/ + $INFO[Container(19801).ListItem.Genre,value=,] + !String.IsEmpty(Container(19801).ListItem.Genre) + + + + $INFO[Container(19801).ListItem.Year] + plugin://plugin.program.akl/collection/virtual/vcategory_year/items/ + $INFO[Container(19801).ListItem.Year,value=,] + !String.IsEmpty(Container(19801).ListItem.Year) + + + + $INFO[Container(19801).ListItem.Studio] + plugin://plugin.program.akl/collection/virtual/vcategory_developer/items/ + $INFO[Container(19801).ListItem.Studio,value=,] + !String.IsEmpty(Container(19801).ListItem.Studio) + + + + $INFO[Container(19801).ListItem.Rating] + plugin://plugin.program.akl/collection/virtual/vcategory_rating/items/ + $INFO[Container(19801).ListItem.Rating,value=,] + !String.IsEmpty(Container(19801).ListItem.Rating) + + + + $INFO[Container(19801).ListItem.Property(esrb)] + plugin://plugin.program.akl/collection/virtual/vcategory_esrb/items/ + $INFO[Container(19801).ListItem.Property(esrb),value=,] + !String.IsEmpty(Container(19801).ListItem.Property(esrb)) + + + + $INFO[Container(19801).ListItem.Property(pegi)] + plugin://plugin.program.akl/collection/virtual/vcategory_pegi/items/ + $INFO[Container(19801).ListItem.Property(pegi),value=,] + !String.IsEmpty(Container(19801).ListItem.Property(pegi)) + + + + $INFO[Container(19801).ListItem.Property(nplayers)] + plugin://plugin.program.akl/collection/virtual/vcategory_nplayers/items/ + $INFO[Container(19801).ListItem.Property(nplayers),value=,] + !String.IsEmpty(Container(19801).ListItem.Property(nplayers)) + + + + $INFO[Container(19801).ListItem.Property(nplayers_online)] + + !String.IsEmpty(Container(19801).ListItem.Property(nplayers_online)) + + + + $INFO[Container(19801).ListItem.Property(tags)] + + !String.IsEmpty(Container(19801).ListItem.Property(tags)) + + + + + 472 + 49 + center + left + + + + 0 + 472 + 49 + 16 + center + left + font12 + + + + + + 472 + 49 + center + left + lists/focus.png + lists/focus.png + + + + 0 + 472 + 49 + 16 + center + left + font12 + + + + + + + Scanned data list + vertical + String.IsEqual(Window.Property(DataListView),scanneddata) + 755 + 488 + 377 + -8 + 5000 + SetFocus(5000,4) + 19802 + 140 + 140 + $INFO[Container(19801).ListItem.Property(entityid),plugin://plugin.program.akl/rom/,/scanneddata] + + + 472 + 49 + center + left + + + + 0 + 472 + 49 + 16 + center + left + font12 + + + + + + 472 + 49 + center + left + lists/focus.png + lists/focus.png + + + + 0 + 472 + 49 + 16 + center + left + font12 + + + + + + + Assets list + 158 + 1235 + 370 + 19802 + 19802 + 140 + 200 + horizontal + $INFO[Container(19801).ListItem.Property(entityid),plugin://plugin.program.akl/rom/,/assets] + + + 10 + + 0 + 264 + 317 + DefaultAddonImages.png + scale + overlays/shadow.png + 20 + + + 20 + 20 + 224 + 277 + $INFO[ListItem.Thumb] + scale + + + 20 + 80 + 224 + 10 + overlays/overlayfade.png + Conditional + + + 25 + 214 + 67 + 218 + center + center + font12 + + + + 25 + 214 + 67 + 245 + center + center + font12 + grey + + + + + + + 0 + 10 + + 0 + 264 + 317 + DefaultAddonImages.png + scale + overlays/shadow.png + 20 + + + 20 + 20 + 224 + 277 + $INFO[ListItem.Thumb] + scale + + + 20 + 224 + 80 + 10 + overlays/overlayfade.png + Conditional + + + 25 + 214 + 67 + 218 + center + center + font12 + true + + + + 25 + 214 + 67 + 245 + center + center + font12 + grey + true + + + + 16 + 16 + 232 + 285 + buttons/thumbnail_focused.png + + + + + + + Buttons List + 0 + 864 + 1246 + 400 + 5000 + 5000 + 140 + SetFocus(19802,$INFO[Container(5000).Position]) + SetFocus(19802,4) + -18 + center + horizontal + 200 + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + 22 + 78 + 108 + 16 + 48 + font12 + icons/launch.png + icons/launch.png + icons/launch.png + icons/launch.png + icons/launch.png + icons/launch.png + + RunPlugin(plugin://plugin.program.akl/execute/rom/$INFO[Container(19801).ListItem.Property(entityid)]) + Hidden + + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + 22 + 78 + 108 + 16 + 48 + font12 + icons/trailer.png + icons/trailer.png + icons/trailer.png + icons/trailer.png + icons/trailer.png + icons/trailer.png + + PlayMedia($INFO[Container(19801).ListItem.Trailer]) + Hidden + + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + buttons/button-fo.png + buttons/button-nofo.png + 35 + 78 + font12 + + Show metadata + SetProperty(DataListView,scanneddata) + SetProperty(DataListView,metadata) + + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + 22 + 78 + 108 + 16 + 48 + font12 + icons/system.png + icons/system.png + icons/system.png + icons/system.png + icons/system.png + icons/system.png + + RunPlugin(plugin://plugin.program.akl/execute/command/rom_edit_metadata/?rom_id=$INFO[Container(19801).ListItem.Property(entityid)]) + Hidden + + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + 22 + 78 + 108 + 16 + 48 + font12 + icons/media.png + icons/media.png + icons/media.png + icons/media.png + icons/media.png + icons/media.png + + RunPlugin(plugin://plugin.program.akl/execute/command/rom_edit_assets/?rom_id=$INFO[Container(19801).ListItem.Property(entityid)]) + Hidden + + + 262 + 142 + center + top + buttons/button-fo.png + buttons/button-nofo.png + 22 + 78 + 108 + 16 + 48 + font12 + icons/search.png + icons/search.png + icons/search.png + icons/search.png + icons/search.png + icons/search.png + + RunPlugin(plugin://plugin.program.akl/execute/command/scrape_rom/?rom_id=$INFO[Container(19801).ListItem.Property(entityid)]) + Hidden + + + + + -15 + 924 + 28 + 28 + overlays/arrowright.png + VisibleChange + WindowOpen + WindowClose + Control.IsVisible(5000) + Container(5000).HasPrevious + true + + + 1240 + 924 + 28 + 28 + overlays/arrowright.png + VisibleChange + WindowOpen + WindowClose + Control.IsVisible(5000) + Container(5000).HasNext + true + + + -15 + 924 + 28 + 28 + + + Control.Move(5000,-1) + Container(5000).HasPrevious + [Control.HasFocus(5000) | Control.HasFocus(5000500)] + + + -15 + 320 + 28 + 28 + overlays/arrowright.png + VisibleChange + WindowOpen + WindowClose + Control.IsVisible(19802) + Container(19802).HasPrevious + + + 1240 + 320 + 28 + 28 + overlays/arrowright.png + VisibleChange + WindowOpen + WindowClose + Control.IsVisible(19802) + Container(19802).HasNext + + + -15 + 320 + 28 + 28 + + + Control.Move(19802,-1) + Container(19802).HasPrevious + [Control.HasFocus(19802) | Control.HasFocus(19802500)] + + + 1240 + 320 + 28 + 28 + + + Control.Move(19802,1) + Container(19802).HasNext + [Control.HasFocus(19802) | Control.HasFocus(19802501)] + + + + + 80 + 970 + right + 1400 + 44 + font20_title + 99FFFFFF + 22000000 + true + + Control.HasFocus(6) + WindowOpen + WindowClose + Visible + Hidden + + + + Header + 100 + 100 + 20 + WindowOpen + WindowClose + 150 + 0 + vertical + + 100% + + center + font52_title + 22000000 + 65 + true + Conditional + + + + 85 + 22000000 + center + 100 + 100% + + + + + + \ No newline at end of file diff --git a/resources/skins/default/media/backgrounds/grayfade.jpg b/resources/skins/default/media/backgrounds/grayfade.jpg new file mode 100644 index 00000000..ca82ba10 Binary files /dev/null and b/resources/skins/default/media/backgrounds/grayfade.jpg differ diff --git a/resources/skins/default/media/buttons/button-fo.png b/resources/skins/default/media/buttons/button-fo.png new file mode 100644 index 00000000..05936eac Binary files /dev/null and b/resources/skins/default/media/buttons/button-fo.png differ diff --git a/resources/skins/default/media/buttons/button-nofo.png b/resources/skins/default/media/buttons/button-nofo.png new file mode 100644 index 00000000..7b7e1e4b Binary files /dev/null and b/resources/skins/default/media/buttons/button-nofo.png differ diff --git a/resources/skins/default/media/buttons/thumbnail_focused.png b/resources/skins/default/media/buttons/thumbnail_focused.png new file mode 100644 index 00000000..e9d8dfe1 Binary files /dev/null and b/resources/skins/default/media/buttons/thumbnail_focused.png differ diff --git a/resources/skins/default/media/colors/black.png b/resources/skins/default/media/colors/black.png new file mode 100644 index 00000000..2ff1770d Binary files /dev/null and b/resources/skins/default/media/colors/black.png differ diff --git a/resources/skins/default/media/dialogs/dialog-bg-nobo.png b/resources/skins/default/media/dialogs/dialog-bg-nobo.png new file mode 100644 index 00000000..78ef2392 Binary files /dev/null and b/resources/skins/default/media/dialogs/dialog-bg-nobo.png differ diff --git a/resources/skins/default/media/dialogs/dialog-bg.png b/resources/skins/default/media/dialogs/dialog-bg.png new file mode 100644 index 00000000..7b7e1e4b Binary files /dev/null and b/resources/skins/default/media/dialogs/dialog-bg.png differ diff --git a/resources/skins/default/media/dialogs/separator-grey.png b/resources/skins/default/media/dialogs/separator-grey.png new file mode 100644 index 00000000..28ca4152 Binary files /dev/null and b/resources/skins/default/media/dialogs/separator-grey.png differ diff --git a/resources/skins/default/media/icons/addons.png b/resources/skins/default/media/icons/addons.png new file mode 100644 index 00000000..4a5f3a3b Binary files /dev/null and b/resources/skins/default/media/icons/addons.png differ diff --git a/resources/skins/default/media/icons/android.png b/resources/skins/default/media/icons/android.png new file mode 100644 index 00000000..400ead1e Binary files /dev/null and b/resources/skins/default/media/icons/android.png differ diff --git a/resources/skins/default/media/icons/download.png b/resources/skins/default/media/icons/download.png new file mode 100644 index 00000000..e3c4e22f Binary files /dev/null and b/resources/skins/default/media/icons/download.png differ diff --git a/resources/skins/default/media/icons/enabled.png b/resources/skins/default/media/icons/enabled.png new file mode 100644 index 00000000..c602a805 Binary files /dev/null and b/resources/skins/default/media/icons/enabled.png differ diff --git a/resources/skins/default/media/icons/games.png b/resources/skins/default/media/icons/games.png new file mode 100644 index 00000000..7ff17590 Binary files /dev/null and b/resources/skins/default/media/icons/games.png differ diff --git a/resources/skins/default/media/icons/image.png b/resources/skins/default/media/icons/image.png new file mode 100644 index 00000000..cdc69826 Binary files /dev/null and b/resources/skins/default/media/icons/image.png differ diff --git a/resources/skins/default/media/icons/launch.png b/resources/skins/default/media/icons/launch.png new file mode 100644 index 00000000..94277277 Binary files /dev/null and b/resources/skins/default/media/icons/launch.png differ diff --git a/resources/skins/default/media/icons/media.png b/resources/skins/default/media/icons/media.png new file mode 100644 index 00000000..1ee43583 Binary files /dev/null and b/resources/skins/default/media/icons/media.png differ diff --git a/resources/skins/default/media/icons/rating.png b/resources/skins/default/media/icons/rating.png new file mode 100644 index 00000000..2480dd2d Binary files /dev/null and b/resources/skins/default/media/icons/rating.png differ diff --git a/resources/skins/default/media/icons/search.png b/resources/skins/default/media/icons/search.png new file mode 100644 index 00000000..b85be636 Binary files /dev/null and b/resources/skins/default/media/icons/search.png differ diff --git a/resources/skins/default/media/icons/settings.png b/resources/skins/default/media/icons/settings.png new file mode 100644 index 00000000..f985035a Binary files /dev/null and b/resources/skins/default/media/icons/settings.png differ diff --git a/resources/skins/default/media/icons/system.png b/resources/skins/default/media/icons/system.png new file mode 100644 index 00000000..1206ffba Binary files /dev/null and b/resources/skins/default/media/icons/system.png differ diff --git a/resources/skins/default/media/icons/trailer.png b/resources/skins/default/media/icons/trailer.png new file mode 100644 index 00000000..d58bc6a7 Binary files /dev/null and b/resources/skins/default/media/icons/trailer.png differ diff --git a/resources/skins/default/media/icons/uninstall.png b/resources/skins/default/media/icons/uninstall.png new file mode 100644 index 00000000..f6d7cccd Binary files /dev/null and b/resources/skins/default/media/icons/uninstall.png differ diff --git a/resources/skins/default/media/icons/update.png b/resources/skins/default/media/icons/update.png new file mode 100644 index 00000000..7d305c7b Binary files /dev/null and b/resources/skins/default/media/icons/update.png differ diff --git a/resources/skins/default/media/lists/focus.png b/resources/skins/default/media/lists/focus.png new file mode 100644 index 00000000..007c9c99 Binary files /dev/null and b/resources/skins/default/media/lists/focus.png differ diff --git a/resources/skins/default/media/lists/panel.png b/resources/skins/default/media/lists/panel.png new file mode 100644 index 00000000..0140fe04 Binary files /dev/null and b/resources/skins/default/media/lists/panel.png differ diff --git a/resources/skins/default/media/overlays/arrowright.png b/resources/skins/default/media/overlays/arrowright.png new file mode 100644 index 00000000..6f6e66d3 Binary files /dev/null and b/resources/skins/default/media/overlays/arrowright.png differ diff --git a/resources/skins/default/media/overlays/folder.png b/resources/skins/default/media/overlays/folder.png new file mode 100644 index 00000000..0072e7d2 Binary files /dev/null and b/resources/skins/default/media/overlays/folder.png differ diff --git a/resources/skins/default/media/overlays/overlayfade.png b/resources/skins/default/media/overlays/overlayfade.png new file mode 100644 index 00000000..6a0b5b3b Binary files /dev/null and b/resources/skins/default/media/overlays/overlayfade.png differ diff --git a/resources/skins/default/media/overlays/shadow.png b/resources/skins/default/media/overlays/shadow.png new file mode 100644 index 00000000..d66bc61d Binary files /dev/null and b/resources/skins/default/media/overlays/shadow.png differ