Skip to content

Commit 676b7c5

Browse files
Export individual Launcher XML configuration.
And some other small refactoring here and there.
1 parent da3534e commit 676b7c5

File tree

5 files changed

+170
-86
lines changed

5 files changed

+170
-86
lines changed

changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ WIP Options to generate the 1G1R parent list, like prefer ROMs from Europe,
6969

7070
[B]Advanced Emulator Launcher | version 0.9.7 | 23 November 2017[/B]
7171

72+
FEATURE Export individual Launcher XML configuration.
73+
7274
FIX Fixed crash when editing Launchr Title in context menu.
7375

7476

resources/autoconfig.py

Lines changed: 115 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,79 @@
3939
# Exports launchers to an XML file.
4040
# Currently categories are not supported.
4141
# -------------------------------------------------------------------------------------------------
42+
# Export Category
43+
def autoconfig_export_category_str_list(category, str_list):
44+
str_list.append('<category>\n')
45+
str_list.append(XML_text('name', category['m_name']))
46+
str_list.append(XML_text('genre', category['m_genre']))
47+
str_list.append(XML_text('rating', category['m_rating']))
48+
str_list.append(XML_text('plot', category['m_plot']))
49+
str_list.append(XML_text('Asset_Prefix', category['Asset_Prefix']))
50+
str_list.append(XML_text('s_icon', category['s_icon']))
51+
str_list.append(XML_text('s_fanart', category['s_fanart']))
52+
str_list.append(XML_text('s_banner', category['s_banner']))
53+
str_list.append(XML_text('s_poster', category['s_poster']))
54+
str_list.append(XML_text('s_clearlogo', category['s_clearlogo']))
55+
str_list.append(XML_text('s_trailer', category['s_trailer']))
56+
str_list.append('</category>\n')
57+
58+
# Export Launcher
59+
def autoconfig_export_launcher_str_list(launcher, category_name, str_list):
60+
# >> Check if all artwork paths share the same ROM_asset_path. Unless the user has
61+
# >> customised the ROM artwork paths this should be the case.
62+
# >> A) This function checks if all path_* share a common root directory. If so
63+
# >> this function returns that common directory as an Unicode string. In this
64+
# >> case AEL will write the tag <ROM_asset_path> only.
65+
# >> B) If path_* do not share a common root directory this function returns '' and then
66+
# >> AEL writes all <path_*> tags in the XML file.
67+
ROM_asset_path = assets_get_ROM_asset_path(launcher)
68+
log_verb('autoconfig_export_all() ROM_asset_path "{0}"'.format(ROM_asset_path))
69+
70+
# >> Export Launcher
71+
str_list.append('<launcher>\n')
72+
str_list.append(XML_text('name', launcher['m_name']))
73+
str_list.append(XML_text('category', category_name))
74+
str_list.append(XML_text('year', launcher['m_year']))
75+
str_list.append(XML_text('genre', launcher['m_genre']))
76+
str_list.append(XML_text('developer', launcher['m_developer']))
77+
str_list.append(XML_text('rating', launcher['m_rating']))
78+
str_list.append(XML_text('plot', launcher['m_plot']))
79+
str_list.append(XML_text('platform', launcher['platform']))
80+
str_list.append(XML_text('application', launcher['application']))
81+
str_list.append(XML_text('args', launcher['args']))
82+
if launcher['args_extra']:
83+
for extra_arg in launcher['args_extra']: str_list.append(XML_text('args_extra', extra_arg))
84+
else:
85+
str_list.append(XML_text('args_extra', ''))
86+
str_list.append(XML_text('ROM_path', launcher['rompath']))
87+
str_list.append(XML_text('ROM_ext', launcher['romext']))
88+
if ROM_asset_path:
89+
str_list.append(XML_text('ROM_asset_path', ROM_asset_path))
90+
else:
91+
str_list.append(XML_text('path_title', launcher['path_title']))
92+
str_list.append(XML_text('path_snap', launcher['path_snap']))
93+
str_list.append(XML_text('path_boxfront', launcher['path_boxfront']))
94+
str_list.append(XML_text('path_boxback', launcher['path_boxback']))
95+
str_list.append(XML_text('path_cartridge', launcher['path_cartridge']))
96+
str_list.append(XML_text('path_fanart', launcher['path_fanart']))
97+
str_list.append(XML_text('path_banner', launcher['path_banner']))
98+
str_list.append(XML_text('path_clearlogo', launcher['path_clearlogo']))
99+
str_list.append(XML_text('path_flyer', launcher['path_flyer']))
100+
str_list.append(XML_text('path_map', launcher['path_map']))
101+
str_list.append(XML_text('path_manual', launcher['path_manual']))
102+
str_list.append(XML_text('path_trailer', launcher['path_trailer']))
103+
str_list.append(XML_text('Asset_Prefix', launcher['Asset_Prefix']))
104+
str_list.append(XML_text('s_icon', launcher['s_icon']))
105+
str_list.append(XML_text('s_fanart', launcher['s_fanart']))
106+
str_list.append(XML_text('s_banner', launcher['s_banner']))
107+
str_list.append(XML_text('s_poster', launcher['s_poster']))
108+
str_list.append(XML_text('s_clearlogo', launcher['s_clearlogo']))
109+
str_list.append(XML_text('s_controller', launcher['s_controller']))
110+
str_list.append(XML_text('s_trailer', launcher['s_trailer']))
111+
str_list.append('</launcher>\n')
112+
42113
def autoconfig_export_all(categories, launchers, export_FN):
43-
# >> Traverse all launchers and add to the XML file.
114+
# --- XML header ---
44115
str_list = []
45116
str_list.append('<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n')
46117
str_list.append('<advanced_emulator_launcher_configuration>\n')
@@ -50,20 +121,7 @@ def autoconfig_export_all(categories, launchers, export_FN):
50121
for categoryID in sorted(categories, key = lambda x : categories[x]['m_name']):
51122
category = categories[categoryID]
52123
log_verb('autoconfig_export_all() Category "{0}" (ID "{1}")'.format(category['m_name'], categoryID))
53-
# >> Export Category
54-
str_list.append('<category>\n')
55-
str_list.append(XML_text('name', category['m_name']))
56-
str_list.append(XML_text('genre', category['m_genre']))
57-
str_list.append(XML_text('rating', category['m_rating']))
58-
str_list.append(XML_text('plot', category['m_plot']))
59-
str_list.append(XML_text('Asset_Prefix', category['Asset_Prefix']))
60-
str_list.append(XML_text('s_icon', category['s_icon']))
61-
str_list.append(XML_text('s_fanart', category['s_fanart']))
62-
str_list.append(XML_text('s_banner', category['s_banner']))
63-
str_list.append(XML_text('s_poster', category['s_poster']))
64-
str_list.append(XML_text('s_clearlogo', category['s_clearlogo']))
65-
str_list.append(XML_text('s_trailer', category['s_trailer']))
66-
str_list.append('</category>\n')
124+
autoconfig_export_category_str_list(category, str_list)
67125

68126
# --- Export Launchers ---
69127
# >> Data which is not string must be converted to string
@@ -77,59 +135,9 @@ def autoconfig_export_all(categories, launchers, export_FN):
77135
kodi_dialog_OK('Launcher category not found. This is a bug, please report it.')
78136
return
79137
log_verb('autoconfig_export_all() Launcher "{0}" (ID "{1}")'.format(launcher['m_name'], launcherID))
138+
autoconfig_export_launcher_str_list(launcher, category_name, str_list)
80139

81-
# >> Check if all artwork paths share the same ROM_asset_path. Unless the user has
82-
# >> customised the ROM artwork paths this should be the case.
83-
# >> A) This function checks if all path_* share a common root directory. If so
84-
# >> this function returns that common directory as an Unicode string. In this
85-
# >> case AEL will write the tag <ROM_asset_path> only.
86-
# >> B) If path_* do not share a common root directory this function returns '' and then
87-
# >> AEL writes all <path_*> tags in the XML file.
88-
ROM_asset_path = assets_get_ROM_asset_path(launcher)
89-
log_verb('autoconfig_export_all() ROM_asset_path "{0}"'.format(ROM_asset_path))
90-
91-
# >> Export Launcher
92-
str_list.append('<launcher>\n')
93-
str_list.append(XML_text('name', launcher['m_name']))
94-
str_list.append(XML_text('category', category_name))
95-
str_list.append(XML_text('year', launcher['m_year']))
96-
str_list.append(XML_text('genre', launcher['m_genre']))
97-
str_list.append(XML_text('developer', launcher['m_developer']))
98-
str_list.append(XML_text('rating', launcher['m_rating']))
99-
str_list.append(XML_text('plot', launcher['m_plot']))
100-
str_list.append(XML_text('platform', launcher['platform']))
101-
str_list.append(XML_text('application', launcher['application']))
102-
str_list.append(XML_text('args', launcher['args']))
103-
if launcher['args_extra']:
104-
for extra_arg in launcher['args_extra']: str_list.append(XML_text('args_extra', extra_arg))
105-
else:
106-
str_list.append(XML_text('args_extra', ''))
107-
str_list.append(XML_text('ROM_path', launcher['rompath']))
108-
str_list.append(XML_text('ROM_ext', launcher['romext']))
109-
if ROM_asset_path:
110-
str_list.append(XML_text('ROM_asset_path', ROM_asset_path))
111-
else:
112-
str_list.append(XML_text('path_title', launcher['path_title']))
113-
str_list.append(XML_text('path_snap', launcher['path_snap']))
114-
str_list.append(XML_text('path_boxfront', launcher['path_boxfront']))
115-
str_list.append(XML_text('path_boxback', launcher['path_boxback']))
116-
str_list.append(XML_text('path_cartridge', launcher['path_cartridge']))
117-
str_list.append(XML_text('path_fanart', launcher['path_fanart']))
118-
str_list.append(XML_text('path_banner', launcher['path_banner']))
119-
str_list.append(XML_text('path_clearlogo', launcher['path_clearlogo']))
120-
str_list.append(XML_text('path_flyer', launcher['path_flyer']))
121-
str_list.append(XML_text('path_map', launcher['path_map']))
122-
str_list.append(XML_text('path_manual', launcher['path_manual']))
123-
str_list.append(XML_text('path_trailer', launcher['path_trailer']))
124-
str_list.append(XML_text('Asset_Prefix', launcher['Asset_Prefix']))
125-
str_list.append(XML_text('s_icon', launcher['s_icon']))
126-
str_list.append(XML_text('s_fanart', launcher['s_fanart']))
127-
str_list.append(XML_text('s_banner', launcher['s_banner']))
128-
str_list.append(XML_text('s_poster', launcher['s_poster']))
129-
str_list.append(XML_text('s_clearlogo', launcher['s_clearlogo']))
130-
str_list.append(XML_text('s_controller', launcher['s_controller']))
131-
str_list.append(XML_text('s_trailer', launcher['s_trailer']))
132-
str_list.append('</launcher>\n')
140+
# --- XML tail ---
133141
str_list.append('</advanced_emulator_launcher_configuration>\n')
134142

135143
# >> Export file
@@ -151,6 +159,46 @@ def autoconfig_export_all(categories, launchers, export_FN):
151159
log_verb('autoconfig_export_all() Exported P "{0}"'.format(export_FN.getPath()))
152160
kodi_notify('Exported AEL Categories and Launchers XML configuration')
153161

162+
#
163+
# Export a single Launcher XML configuration.
164+
#
165+
def autoconfig_export_launcher(launcher, export_FN, categories):
166+
# --- Export single Launcher ---
167+
launcherID = launcher['id']
168+
if launcher['categoryID'] in categories:
169+
category_name = categories[launcher['categoryID']]['m_name']
170+
elif launcher['categoryID'] == VCATEGORY_ADDONROOT_ID:
171+
category_name = VCATEGORY_ADDONROOT_ID
172+
else:
173+
kodi_dialog_OK('Launcher category not found. This is a bug, please report it.')
174+
return
175+
log_verb('autoconfig_export_launcher() Launcher "{0}" (ID "{1}")'.format(launcher['m_name'], launcherID))
176+
177+
# --- Create list of strings ---
178+
str_list = []
179+
str_list.append('<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n')
180+
str_list.append('<advanced_emulator_launcher_configuration>\n')
181+
autoconfig_export_launcher_str_list(launcher, category_name, str_list)
182+
str_list.append('</advanced_emulator_launcher_configuration>\n')
183+
184+
# >> Export file. Strings in the list are Unicode. Encode to UTF-8.
185+
# >> Join string, and save categories.xml file
186+
try:
187+
full_string = ''.join(str_list).encode('utf-8')
188+
file_obj = open(export_FN.getPath(), 'w')
189+
file_obj.write(full_string)
190+
file_obj.close()
191+
except OSError:
192+
log_error('(OSError) Cannot write {0} file'.format(export_FN.getBase()))
193+
kodi_notify_warn('(OSError) Cannot write {0} file'.format(export_FN.getBase()))
194+
return
195+
except IOError:
196+
log_error('(IOError) Cannot write {0} file'.format(export_FN.getBase()))
197+
kodi_notify_warn('(IOError) Cannot write {0} file'.format(export_FN.getBase()))
198+
return
199+
log_verb('autoconfig_export_launcher() Exported OP "{0}"'.format(export_FN.getOriginalPath()))
200+
log_verb('autoconfig_export_launcher() Exported P "{0}"'.format(export_FN.getPath()))
201+
154202
# -------------------------------------------------------------------------------------------------
155203
# Import AEL launcher configuration
156204
# -------------------------------------------------------------------------------------------------

resources/main.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,13 +1078,13 @@ def _command_edit_launcher(self, categoryID, launcherID):
10781078
type = dialog.select('Select action for launcher {0}'.format(self.launchers[launcherID]['m_name']),
10791079
['Edit Metadata ...', 'Edit Assets/Artwork ...', 'Choose default Assets/Artwork ...',
10801080
'Change Category: {0}'.format(category_name), finished_display,
1081-
'Advanced Modifications ...', 'Delete Launcher'])
1081+
'Advanced Modifications ...', 'Export Launcher XML configuration ...', 'Delete Launcher'])
10821082
else:
10831083
type = dialog.select('Select action for launcher {0}'.format(self.launchers[launcherID]['m_name']),
10841084
['Edit Metadata ...', 'Edit Assets/Artwork ...', 'Choose default Assets/Artwork ...',
10851085
'Change Category: {0}'.format(category_name), finished_display,
10861086
'Manage ROMs ...', 'Audit ROMs / Launcher view mode ...',
1087-
'Advanced Modifications ...', 'Delete Launcher'])
1087+
'Advanced Modifications ...', 'Export Launcher XML configuration ...', 'Delete Launcher'])
10881088
if type < 0: return
10891089

10901090
# --- Edition of the launcher metadata ---
@@ -2303,10 +2303,35 @@ def _command_edit_launcher(self, categoryID, launcherID):
23032303
non_blocking_str = 'ON' if self.launchers[launcherID]['non_blocking'] else 'OFF'
23042304
kodi_notify('Launcher Non-blocking is {0}'.format(non_blocking_str))
23052305

2306+
# --- Export Launcher XML configuration ---
2307+
type_nb = type_nb + 1
2308+
if type == type_nb:
2309+
launcher = self.launchers[launcherID]
2310+
launcher_fn_str = text_title_to_filename_str(launcher['m_name']) + '.xml'
2311+
log_debug('_command_edit_launcher() Exporting Launcher configuration')
2312+
log_debug('_command_edit_launcher() Name "{0}"'.format(launcher['m_name']))
2313+
log_debug('_command_edit_launcher() ID {0}'.format(launcher['id']))
2314+
log_debug('_command_edit_launcher() l_fn_str "{0}"'.format(launcher_fn_str))
2315+
2316+
# >> Ask user for a path to export the launcher configuration
2317+
dir_path = xbmcgui.Dialog().browse(0, 'Select XML export directory', 'files',
2318+
'', False, False).decode('utf-8')
2319+
if not dir_path: return
2320+
export_FN = FileName(dir_path).pjoin(launcher_fn_str)
2321+
if export_FN.exists():
2322+
ret = kodi_dialog_yesno('Overwrite file {0}?'.format(export_FN.getPath()))
2323+
if not ret:
2324+
kodi_notify_warn('Export of Launcher XML cancelled')
2325+
return
2326+
autoconfig_export_launcher(launcher, export_FN, self.categories)
2327+
kodi_notify('Exported Launcher "{0}" XML config'.format(launcher['m_name']))
2328+
# >> No need to update categories.xml and timestamps so return now.
2329+
return
2330+
23062331
# --- Remove Launcher menu option ---
23072332
type_nb = type_nb + 1
23082333
if type == type_nb:
2309-
rompath = self.launchers[launcherID]['rompath']
2334+
rompath = self.launchers[launcherID]['rompath']
23102335
launcher_name = self.launchers[launcherID]['m_name']
23112336
# >> Standalone launcher
23122337
if rompath == '':
@@ -2320,21 +2345,19 @@ def _command_edit_launcher(self, categoryID, launcherID):
23202345
'Are you sure you want to delete it?')
23212346
if not ret: return
23222347

2323-
# --- Remove JSON/XML file if exist ---
2348+
# >> Remove JSON/XML file if exist
2349+
# >> Remove launcher from database. Categories.xml will be saved at the end of function
23242350
fs_unlink_ROMs_database(ROMS_DIR, self.launchers[launcherID])
2325-
2326-
# --- Remove launcher from database. Categories.xml will be saved at the end of function ---
23272351
self.launchers.pop(launcherID)
2328-
kodi_notify('Deleted launcher {0}'.format(launcher_name))
2352+
kodi_notify('Deleted Launcher {0}'.format(launcher_name))
23292353

23302354
# User pressed cancel or close dialog
23312355
if type < 0: return
23322356

23332357
# >> If this point is reached then changes to launcher metadata/assets were made.
23342358
# >> Save categories and update container contents so user sees those changes inmediately.
23352359
# NOTE Update edited launcher timestamp only if launcher was not deleted!
2336-
if launcherID in self.launchers:
2337-
self.launchers[launcherID]['timestamp_launcher'] = time.time()
2360+
if launcherID in self.launchers: self.launchers[launcherID]['timestamp_launcher'] = time.time()
23382361
fs_write_catfile(CATEGORIES_FILE_PATH, self.categories, self.launchers)
23392362
kodi_refresh_container()
23402363

resources/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ def text_limit_string(string, max_length):
4141

4242
return string
4343

44+
#
45+
# Given a Category/Launcher name clean it so the cleaned srt can be used as a filename.
46+
# 1) Convert any non-printable character into '_'
47+
# 2) Convert spaces ' ' into '_'
48+
#
49+
def text_title_to_filename_str(title_str):
50+
cleaned_str_1 = ''.join([i if i in string.printable else '_' for i in title_str])
51+
cleaned_str_2 = cleaned_str_1.replace(' ', '_')
52+
53+
return cleaned_str_2
54+
4455
#
4556
# Writes a XML text tag line, indented 2 spaces by default.
4657
# Both tag_name and tag_text must be Unicode strings.

resources/utils_kodi.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,13 @@ def log_error(str_text):
9797
# 2) ret = kodi_dialog_OK('Launch ROM?', title = 'AEL - Launcher')
9898
#
9999
def kodi_dialog_OK(row1, row2='', row3='', title = 'Advanced Emulator Launcher'):
100-
dialog = xbmcgui.Dialog()
101-
dialog.ok(title, row1, row2, row3)
100+
xbmcgui.Dialog().ok(title, row1, row2, row3)
102101

103102
#
104103
# Returns True is YES was pressed, returns False if NO was pressed or dialog canceled.
104+
#
105105
def kodi_dialog_yesno(row1, row2='', row3='', title = 'Advanced Emulator Launcher'):
106-
dialog = xbmcgui.Dialog()
107-
ret = dialog.yesno(title, row1, row2, row3)
106+
ret = xbmcgui.Dialog().yesno(title, row1, row2, row3)
108107

109108
return ret
110109

@@ -116,20 +115,21 @@ def kodi_notify(text, title = 'Advanced Emulator Launcher', time = 5000):
116115
# xbmc.executebuiltin("XBMC.Notification(%s,%s,%s,%s)" % (title, text, time, ICON_IMG_FILE_PATH))
117116

118117
# --- New way ---
119-
dialog = xbmcgui.Dialog()
120-
dialog.notification(title, text, xbmcgui.NOTIFICATION_INFO, time)
118+
xbmcgui.Dialog().notification(title, text, xbmcgui.NOTIFICATION_INFO, time)
121119

122120
def kodi_notify_warn(text, title = 'Advanced Emulator Launcher warning', time = 7000):
123-
dialog = xbmcgui.Dialog()
124-
dialog.notification(title, text, xbmcgui.NOTIFICATION_WARNING, time)
121+
xbmcgui.Dialog().notification(title, text, xbmcgui.NOTIFICATION_WARNING, time)
125122

126123
#
127124
# Do not use this function much because it is the same icon as when Python fails, and that may confuse the user.
128125
#
129126
def kodi_notify_error(text, title = 'Advanced Emulator Launcher error', time = 7000):
130-
dialog = xbmcgui.Dialog()
131-
dialog.notification(title, text, xbmcgui.NOTIFICATION_ERROR, time)
127+
xbmcgui.Dialog().notification(title, text, xbmcgui.NOTIFICATION_ERROR, time)
132128

129+
#
130+
# NOTE I think Krypton introduced new API functions to activate the busy dialog window. Check that
131+
# out!
132+
#
133133
def kodi_busydialog_ON():
134134
xbmc.executebuiltin('ActivateWindow(busydialog)')
135135

0 commit comments

Comments
 (0)