From 2abd23b344c47e599660f6655fd9568044a21e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bryan=20Gald=C3=A1mez?= Date: Sun, 18 May 2025 06:54:50 -0600 Subject: [PATCH] bugfix(mods): add file download retry and try catchs to fallback --- src/zen/mods/ZenThemesCommon.mjs | 67 +++++++++------- src/zen/mods/ZenThemesImporter.mjs | 45 +++++------ .../actors/ZenThemeMarketplaceChild.sys.mjs | 11 ++- .../actors/ZenThemeMarketplaceParent.sys.mjs | 79 +++++++++++++------ 4 files changed, 121 insertions(+), 81 deletions(-) diff --git a/src/zen/mods/ZenThemesCommon.mjs b/src/zen/mods/ZenThemesCommon.mjs index 14ce3593d..b08055b5f 100644 --- a/src/zen/mods/ZenThemesCommon.mjs +++ b/src/zen/mods/ZenThemesCommon.mjs @@ -47,9 +47,10 @@ var ZenThemesCommon = { if (themes === null || typeof themes !== 'object') { throw new Error('Themes data file is null'); } - } catch (e) { + } catch { // If we have a corrupted file, reset it await IOUtils.writeJSON(this.themesDataFile, {}); + Services.wm .getMostRecentWindow('navigator:browser') .gZenUIManager.showToast('zen-themes-corrupted', { @@ -61,46 +62,54 @@ var ZenThemesCommon = { async getThemePreferences(theme) { const themePath = PathUtils.join(this.themesRootPath, theme.id, 'preferences.json'); + if (!(await IOUtils.exists(themePath)) || !theme.preferences) { return []; } - const preferences = await IOUtils.readJSON(themePath); + try { + const preferences = await IOUtils.readJSON(themePath); - // compat mode for old preferences, all of them can only be checkboxes - if (typeof preferences === 'object' && !Array.isArray(preferences)) { - console.warn( - `[ZenThemes]: Warning, ${theme.name} uses legacy preferences, please migrate them to the new preferences style, as legacy preferences might be removed at a future release. More information at: https://docs.zen-browser.app/themes-store/themes-marketplace-preferences` - ); - const newThemePreferences = []; + // compat mode for old preferences, all of them can only be checkboxes + if (typeof preferences === 'object' && !Array.isArray(preferences)) { + console.warn( + `[ZenThemes]: Warning, ${theme.name} uses legacy preferences, please migrate them to the new preferences style, as legacy preferences might be removed at a future release. More information at: https://docs.zen-browser.app/themes-store/themes-marketplace-preferences` + ); + const newThemePreferences = []; - for (let [entry, label] of Object.entries(preferences)) { - const [_, negation = '', os = '', property] = - /(!?)(?:(macos|windows|linux):)?([A-Za-z0-9-_.]+)/g.exec(entry); - const isNegation = negation === '!'; + for (let [entry, label] of Object.entries(preferences)) { + const [, negation = '', os = '', property] = + /(!?)(?:(macos|windows|linux):)?([A-Za-z0-9-_.]+)/g.exec(entry); + const isNegation = negation === '!'; - if ( - (isNegation && os === gZenOperatingSystemCommonUtils.currentOperatingSystem) || - (os !== '' && os !== gZenOperatingSystemCommonUtils.currentOperatingSystem && !isNegation) - ) { - continue; + if ( + (isNegation && os === gZenOperatingSystemCommonUtils.currentOperatingSystem) || + (os !== '' && + os !== gZenOperatingSystemCommonUtils.currentOperatingSystem && + !isNegation) + ) { + continue; + } + + newThemePreferences.push({ + property, + label, + type: 'checkbox', + disabledOn: os !== '' ? [os] : [], + }); } - newThemePreferences.push({ - property, - label, - type: 'checkbox', - disabledOn: os !== '' ? [os] : [], - }); + return newThemePreferences; } - return newThemePreferences; + return preferences.filter( + ({ disabledOn = [] }) => + !disabledOn.includes(gZenOperatingSystemCommonUtils.currentOperatingSystem) + ); + } catch (e) { + console.error(`[ZenThemes]: Error reading preferences for ${theme.name}:`, e); + return []; } - - return preferences.filter( - ({ disabledOn = [] }) => - !disabledOn.includes(gZenOperatingSystemCommonUtils.currentOperatingSystem) - ); }, throttle(mainFunction, delay) { diff --git a/src/zen/mods/ZenThemesImporter.mjs b/src/zen/mods/ZenThemesImporter.mjs index 2b97602d7..ad610d71d 100644 --- a/src/zen/mods/ZenThemesImporter.mjs +++ b/src/zen/mods/ZenThemesImporter.mjs @@ -211,35 +211,30 @@ var gZenThemesImporter = new (class { continue; } - switch (type) { - case 'checkbox': { - const value = Services.prefs.getBoolPref(property, false); - if (typeof defaultValue !== 'boolean') { - console.log( - `[ZenThemesImporter]: Warning, invalid data type received for expected type boolean, skipping.` - ); - continue; - } - - if (!value) { - Services.prefs.setBoolPref(property, defaultValue); - } - break; + if (type === 'checkbox') { + const value = Services.prefs.getBoolPref(property, false); + if (typeof defaultValue !== 'boolean') { + console.log( + `[ZenThemesImporter]: Warning, invalid data type received for expected type boolean, skipping.` + ); + continue; } - default: { - const value = Services.prefs.getStringPref(property, 'zen-property-no-saved'); + if (!value) { + Services.prefs.setBoolPref(property, defaultValue); + } + } else { + const value = Services.prefs.getStringPref(property, 'zen-property-no-saved'); - if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') { - console.log( - `[ZenThemesImporter]: Warning, invalid data type received (${typeof defaultValue}), skipping.` - ); - continue; - } + if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') { + console.log( + `[ZenThemesImporter]: Warning, invalid data type received (${typeof defaultValue}), skipping.` + ); + continue; + } - if (value === 'zen-property-no-saved') { - Services.prefs.setStringPref(property, defaultValue.toString()); - } + if (value === 'zen-property-no-saved') { + Services.prefs.setStringPref(property, defaultValue.toString()); } } } diff --git a/src/zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs b/src/zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs index a63bbc2c2..c45ec7d61 100644 --- a/src/zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs +++ b/src/zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs @@ -162,9 +162,12 @@ export class ZenThemeMarketplaceChild extends JSWindowActorChild { const obj = await data.json(); return obj; } catch (e) { - console.error('ZTM: Error parsing theme info: ', e); + console.error('ZenThemeMarketplace: Error parsing theme info: ', e); } - } else console.log(data.status); + } else { + console.error('ZenThemeMarketplace: Error fetching theme info: ', data.status); + } + return null; } @@ -185,11 +188,11 @@ export class ZenThemeMarketplaceChild extends JSWindowActorChild { } else { themeId = object.themeId; } - console.info('ZTM: Installing theme with id: ', themeId); + console.info('ZenThemeMarketplace: Installing theme with id: ', themeId); const theme = await this.getThemeInfo(themeId); if (!theme) { - console.error('ZTM: Error fetching theme info'); + console.error('ZenThemeMarketplace: Error fetching theme info'); return; } this.addTheme(theme); diff --git a/src/zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs b/src/zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs index e4979b1e4..c98c3eb7f 100644 --- a/src/zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs +++ b/src/zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs @@ -28,7 +28,8 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent { case 'ZenThemeMarketplace:IsThemeInstalled': { const themeId = message.data.themeId; const themes = await this.getThemes(); - return themes[themeId] ? true : false; + + return Boolean(themes?.[themeId]); } case 'ZenThemeMarketplace:CheckForUpdates': { this.checkForThemeUpdates(); @@ -46,14 +47,17 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent { } compareVersions(version1, version2) { - var result = false; + let result = false; + if (typeof version1 !== 'object') { version1 = version1.toString().split('.'); } + if (typeof version2 !== 'object') { version2 = version2.toString().split('.'); } - for (var i = 0; i < Math.max(version1.length, version2.length); i++) { + + for (let i = 0; i < Math.max(version1.length, version2.length); i++) { if (version1[i] == undefined) { version1[i] = 0; } @@ -76,7 +80,7 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent { let updates = []; const themes = await this.getThemes(); - for (const theme of Object.values(await this.getThemes())) { + for (const theme of Object.values(themes)) { try { const themeInfo = await this.sendQuery('ZenThemeMarketplace:GetThemeInfo', { themeId: theme.id, @@ -141,29 +145,58 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent { return stylesheet; } - async downloadUrlToFile(url, path, isStyleSheet = false) { - try { - const response = await fetch(url); - const data = await response.text(); - const content = isStyleSheet ? this.getStyleSheetFullContent(data) : data; - // convert the data into a Uint8Array - let buffer = new TextEncoder().encode(content); - await IOUtils.write(path, buffer); - } catch (e) { - console.error('ZenThemeMarketplaceParent: Error downloading file', url, e); + async downloadUrlToFile(url, path, isStyleSheet = false, maxRetries = 3, retryDelayMs = 500) { + let attempt = 0; + + while (attempt < maxRetries) { + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `ZenThemeMarketplaceParent: HTTP error! status: ${response.status} for url: ${url}` + ); + } + + const data = await response.text(); + const content = isStyleSheet ? this.getStyleSheetFullContent(data) : data; + // convert the data into a Uint8Array + const buffer = new TextEncoder().encode(content); + await IOUtils.write(path, buffer); + + return; + } catch (e) { + attempt++; + if (attempt >= maxRetries) { + console.error('ZenThemeMarketplaceParent: Error downloading file after retries', url, e); + } else { + console.warn( + `ZenThemeMarketplaceParent: Download failed (attempt ${attempt} of ${maxRetries}), retrying in ${retryDelayMs}ms...`, + url, + e + ); + await new Promise((res) => setTimeout(res, retryDelayMs)); + } + } } } async downloadThemeFileContents(theme) { - const themePath = PathUtils.join(this.themesRootPath, theme.id); - await IOUtils.makeDirectory(themePath, { ignoreExisting: true }); - await this.downloadUrlToFile(theme.style, PathUtils.join(themePath, 'chrome.css'), true); - await this.downloadUrlToFile(theme.readme, PathUtils.join(themePath, 'readme.md')); - if (theme.preferences) { - await this.downloadUrlToFile( - theme.preferences, - PathUtils.join(themePath, 'preferences.json') - ); + try { + const themePath = PathUtils.join(this.themesRootPath, theme.id); + await IOUtils.makeDirectory(themePath, { ignoreExisting: true }); + + await this.downloadUrlToFile(theme.style, PathUtils.join(themePath, 'chrome.css'), true); + await this.downloadUrlToFile(theme.readme, PathUtils.join(themePath, 'readme.md')); + + if (theme.preferences) { + await this.downloadUrlToFile( + theme.preferences, + PathUtils.join(themePath, 'preferences.json') + ); + } + } catch (e) { + console.log('ZenThemeMarketplaceParent: Error downloading theme file contents', theme.id, e); } }