From 94d6c32a824045bc2a504840da147015d600fe9c Mon Sep 17 00:00:00 2001 From: Thomas Egli <38955561+thegli@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:26:07 +0200 Subject: [PATCH] yfquotes@thegli: Bug fixing and optimizations (#1264) - Setting changes no longer create multiple triggers and parallel data refresh tasks - Fix error "TypeError: symbolCustomization is undefined" - Honor custom names in sorting - Move refresh button to the bottom of the settings screen - Minor refactoring and cleanup --- yfquotes@thegli/README.md | 24 +- .../files/yfquotes@thegli/desklet.js | 514 +++++++++--------- .../files/yfquotes@thegli/metadata.json | 2 +- .../files/yfquotes@thegli/po/ca.po | 30 +- .../files/yfquotes@thegli/po/da.po | 30 +- .../files/yfquotes@thegli/po/de.po | 30 +- .../files/yfquotes@thegli/po/es.po | 30 +- .../files/yfquotes@thegli/po/fi.po | 30 +- .../files/yfquotes@thegli/po/hu.po | 30 +- .../files/yfquotes@thegli/po/it.po | 30 +- .../files/yfquotes@thegli/po/ko.po | 30 +- .../files/yfquotes@thegli/po/nl.po | 30 +- .../files/yfquotes@thegli/po/pt_BR.po | 30 +- .../files/yfquotes@thegli/po/ro.po | 30 +- .../files/yfquotes@thegli/po/ru.po | 30 +- .../yfquotes@thegli/po/yfquotes@thegli.pot | 32 +- .../yfquotes@thegli/settings-schema.json | 4 +- 17 files changed, 461 insertions(+), 475 deletions(-) diff --git a/yfquotes@thegli/README.md b/yfquotes@thegli/README.md index b84935fe..642379ed 100644 --- a/yfquotes@thegli/README.md +++ b/yfquotes@thegli/README.md @@ -23,6 +23,9 @@ Either follow the installation instructions on [Cinnamon spices](https://cinnamo Check out the desklet configuration settings, and choose the data refresh period, the list of quotes to show, and quote details to display. The default list contains the Dow 30 companies. +> Note that **any changes in the quotes list are not applied immediately** (anymore). This is true for manual changes as well as for import settings from a file. +Press the "Refresh quotes data" button to execute an immediate data update, or wait until the next automatic refresh is triggered (depending on the configured interval). + ### Individual Quote Design By default, all quotes in the list are rendered in the same style and color, following whatever settings are active and selected. @@ -42,7 +45,7 @@ The following table explains all supported properties: Some examples: -``` +```text CAT;color=#f6d001;weight=500 CSCO;name=Cisco;weight=bold;color=#00bceb HD;color=#f96300 @@ -51,8 +54,6 @@ IBM;name=Big Blue;color=blue;weight=bolder KO;name=Bubbly Brown Water;style=oblique;weight=lighter;color=#e61a27 ``` -> Note that any changes in the quotes list are not applied immediately (anymore). Press the "Refresh quotes data" button to trigger a manual data update for the current quotes list. - ## Troubleshooting Problem: The desklet fails to load data, and shows error message "Status: 429 Too Many Requests". @@ -72,12 +73,27 @@ To disable the debug log mode, delete the "DEBUG" file, and restart the Cinnamon ## Release Notes +### 0.14.1 - August 25, 2024 + +Features: + +- setting changes no longer trigger data refresh cycles, resulting in faster layout rendering and reduced network traffic +- update Catalan translation (courtesy of [Odyssey](https://github.com/odyssey)) +- update Spanish translation (courtesy of [haggen88](https://github.com/haggen88)) + +Bugfixes: + +- prevent spawning multiple data fetching tasks at the same time +- prevent creation of more than one data refresh timer +- fix error "TypeError: symbolCustomization is undefined" +- honor custom names in sorting + ### 0.14.0 - August 21, 2024 Features: - style each quote individually - see section [Individual Quote Design](#individual-quote-design) for details -- add Catalan translation (courtesy of [Odyssey]((https://github.com/odyssey)) +- add Catalan translation (courtesy of [Odyssey](https://github.com/odyssey)) - update Dutch translation (courtesy of [qadzek](https://github.com/qadzek)) - update Hungarian translation (courtesy of [bossbob88](https://github.com/bossbob88)) - update Spanish translation (courtesy of [haggen88](https://github.com/haggen88)) diff --git a/yfquotes@thegli/files/yfquotes@thegli/desklet.js b/yfquotes@thegli/files/yfquotes@thegli/desklet.js index f4995304..a7c22f15 100644 --- a/yfquotes@thegli/files/yfquotes@thegli/desklet.js +++ b/yfquotes@thegli/files/yfquotes@thegli/desklet.js @@ -57,6 +57,10 @@ const BASE_FONT_SIZE = 10; Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale"); +function _(str) { + return Gettext.dgettext(UUID, str); +} + let _httpSession; if (IS_SOUP_2) { _httpSession = new Soup.SessionAsync(); @@ -73,13 +77,16 @@ Soup.Session.prototype.add_feature.call(_httpSession, _cookieJar); let _crumb = null; -function _(str) { - return Gettext.dgettext(UUID, str); -} +let _lastResponse = { + responseResult: [], + // we should never see this error message + responseError: _("No quotes data to display"), + lastUpdated: new Date() +}; function logDebug(msg) { if (LOG_DEBUG) { - global.log(LOG_PREFIX + 'DEBUG: ' + msg); + global.log(LOG_PREFIX + "DEBUG: " + msg); } } @@ -105,24 +112,51 @@ YahooFinanceQuoteUtils.prototype = { && object[property] !== null; }, - determineQuoteName: function(quote, symbolCustomization, useLongName) { - if (symbolCustomization.name !== null) { - return symbolCustomization.name; - } + // convert the quotes list to a comma-separated one-liner, to be used as argument for the YFQ "symbols" parameter + buildSymbolsArgument: function(quoteSymbolsText) { + return quoteSymbolsText + .split("\n") + .map((line) => line.trim()) + .filter((line) => line !== "") + .map((line) => line.split(";")[0]) + .join(); + }, - if (useLongName && this.existsProperty(quote, "longName")) { - return quote.longName; + // extract any customization parameters from each entry in the quotes list, and return them in a map + buildSymbolCustomizationMap: function(quoteSymbolsText) { + const symbolCustomizations = new Map(); + for (const line of quoteSymbolsText.trim().split("\n")) { + const customization = this.parseSymbolLine(line); + symbolCustomizations.set(customization.symbol, customization); } + logDebug("symbol customization map size: " + symbolCustomizations.size); + + return symbolCustomizations; + }, - if (this.existsProperty(quote, "shortName")) { - return quote.shortName; + parseSymbolLine: function(symbolLine) { + const lineParts = symbolLine.trim().split(";"); + + const customAttributes = new Map(); + for (const attr of lineParts.slice(1)) { + const [key, value] = attr.split("="); + if (key && value) { + customAttributes.set(key, value); + } } - return ABSENT; + return this.buildSymbolCustomization(lineParts[0], customAttributes); }, - buildSymbolList: function(symbolCustomizationMap) { - return Array.from(symbolCustomizationMap.keys()).join(); + // data structure for quote customization parameters + buildSymbolCustomization: function(symbol, customAttributes) { + return { + symbol, + name: customAttributes.has("name") ? customAttributes.get("name") : null, + style: customAttributes.has("style") ? customAttributes.get("style") : "normal", + weight: customAttributes.has("weight") ? customAttributes.get("weight") : "normal", + color: customAttributes.has("color") ? customAttributes.get("color") : null, + }; }, isOkStatus: function(soupMessage) { @@ -160,6 +194,50 @@ YahooFinanceQuoteUtils.prototype = { } } return "no status available"; + }, + + // determine and store the quote name to display within the quote as new property "displayName" + populateQuoteDisplayName: function(quote, symbolCustomization, useLongName) { + let displayName = ABSENT; + + if (symbolCustomization.name !== null) { + displayName = symbolCustomization.name; + } else if (useLongName && this.existsProperty(quote, "longName")) { + displayName = quote.longName; + } else if (this.existsProperty(quote, "shortName")) { + displayName = quote.shortName; + } + + quote.displayName = displayName; + }, + + sortQuotesByProperty: function(quotes, prop, direction) { + // don't sort if no criteria was given, or the criteria says "natural order", or there is only one quote + if (prop === undefined || prop === "none" || quotes.length < 2) { + return quotes; + } + + // when sort-by-name is configured, then we want to sort by the determined display name + if (prop === "shortName") { + prop = "displayName"; + } + + const _that = this; + const clone = quotes.slice(0); + const numberPattern = /^-?\d+(\.\d+)?$/; + clone.sort(function(q1, q2) { + let p1 = ""; + if (_that.existsProperty(q1, prop)) { + p1 = q1[prop].toString().match(numberPattern) ? + q1[prop] : q1[prop].toLowerCase(); + } + let p2 = ""; + if (_that.existsProperty(q2, prop)) { + p2 = q2[prop].toString().match(numberPattern) ? + q2[prop] : q2[prop].toLowerCase(); + } + + return ((p1 < p2) ? -1 : ((p1 > p2) ? 1 : 0)) * direction; + }); + return clone; } }; @@ -314,15 +392,15 @@ YahooFinanceQuoteReader.prototype = { } }, - getFinanceData: function(symbolList, customUserAgent, callback) { + getFinanceData: function(quoteSymbolsArg, customUserAgent, callback) { const _that = this; - if (symbolList.size === 0) { + if (quoteSymbolsArg.length === 0) { callback.call(_that, _that.buildErrorResponse(_("Empty quotes list. Open settings and add some symbols."))); return; } - const requestUrl = this.createYahooQueryUrl(symbolList); + const requestUrl = this.createYahooQueryUrl(quoteSymbolsArg); const message = Soup.Message.new("GET", requestUrl); if (IS_SOUP_2) { @@ -365,8 +443,8 @@ YahooFinanceQuoteReader.prototype = { } }, - createYahooQueryUrl: function(symbolList) { - const queryUrl = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" + symbolList + "&crumb=" + _crumb; + createYahooQueryUrl: function(quoteSymbolsArg) { + const queryUrl = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" + quoteSymbolsArg + "&crumb=" + _crumb; logDebug("YF query URL: " + queryUrl); return queryUrl; }, @@ -400,7 +478,7 @@ QuotesTable.prototype = { EQUALS: "\u25B6" }, - render: function(quotes, symbolCustomizationMap, settings) { + renderTable: function(quotes, symbolCustomizationMap, settings) { for (let rowIndex = 0, l = quotes.length; rowIndex < l; rowIndex++) { this.renderTableRow(quotes[rowIndex], symbolCustomizationMap, settings, rowIndex); } @@ -415,8 +493,7 @@ QuotesTable.prototype = { cellContents.push(this.createPercentChangeIcon(quote, settings)); } if (settings.quoteName) { - cellContents.push(this.createQuoteLabel(this.quoteUtils.determineQuoteName(quote, symbolCustomization, settings.useLongName), - symbolCustomization, settings.quoteLabelWidth, settings)); + cellContents.push(this.createQuoteLabel(quote.displayName, symbolCustomization, settings.quoteLabelWidth, settings)); } if (settings.quoteSymbol) { cellContents.push(this.createQuoteLabel(symbol, symbolCustomization, settings.quoteSymbolWidth, settings)); @@ -631,95 +708,60 @@ function StockQuoteDesklet(metadata, id) { StockQuoteDesklet.prototype = { __proto__: Desklet.Desklet.prototype, - quoteUtils: new YahooFinanceQuoteUtils(), init: function(metadata, id) { this.metadata = metadata; this.id = id; + this.updateId = 0; + this.updateInProgress = false; this.quoteReader = new YahooFinanceQuoteReader(); this.quoteUtils = new YahooFinanceQuoteUtils(); this.loadSettings(); - this.onUpdate(); + this.onQuotesListChanged(); }, loadSettings: function() { this.settings = new Settings.DeskletSettings(this, this.metadata.uuid, this.id); - this.settings.bindProperty(Settings.BindingDirection.IN, "height", "height", - this.onDisplayChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "width", "width", - this.onDisplayChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "transparency", "transparency", - this.onDisplayChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showVerticalScrollbar", "showVerticalScrollbar", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "backgroundColor", "backgroundColor", - this.onDisplayChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "delayMinutes", "delayMinutes", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showLastUpdateTimestamp", "showLastUpdateTimestamp", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "manualDataUpdate", "manualDataUpdate", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "sendCustomUserAgent", "sendCustomUserAgent", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "customUserAgent", "customUserAgent", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "roundNumbers", "roundNumbers", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "decimalPlaces", "decimalPlaces", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "strictRounding", "strictRounding", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "use24HourTime", "use24HourTime", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "customTimeFormat", "customTimeFormat", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "customDateFormat", "customDateFormat", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "quoteSymbols", "quoteSymbolsText"); // no instant-refresh on change - this.settings.bindProperty(Settings.BindingDirection.IN, "sortCriteria", "sortCriteria", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "sortDirection", "sortAscending", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showChangeIcon", "showChangeIcon", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showQuoteName", "showQuoteName", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "useLongQuoteName", "useLongQuoteName", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "linkQuoteName", "linkQuoteName", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showQuoteSymbol", "showQuoteSymbol", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "linkQuoteSymbol", "linkQuoteSymbol", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showMarketPrice", "showMarketPrice", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showCurrencyCode", "showCurrencyCode", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showAbsoluteChange", "showAbsoluteChange", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showPercentChange", "showPercentChange", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "colorPercentChange", "colorPercentChange", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "showTradeTime", "showTradeTime", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "fontColor", "fontColor", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "scaleFontSize", "scaleFontSize", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "fontScale", "fontScale", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "uptrendChangeColor", "uptrendChangeColor", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "downtrendChangeColor", "downtrendChangeColor", - this.onSettingsChanged, null); - this.settings.bindProperty(Settings.BindingDirection.IN, "unchangedTrendColor", "unchangedTrendColor", - this.onSettingsChanged, null); + this.settings.bindProperty(Settings.BindingDirection.IN, "height", "height", this.onDisplaySettingChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "width", "width", this.onDisplaySettingChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "transparency", "transparency", this.onDisplaySettingChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showVerticalScrollbar", "showVerticalScrollbar", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "backgroundColor", "backgroundColor", this.onDisplaySettingChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "delayMinutes", "delayMinutes", this.onDataFetchSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showLastUpdateTimestamp", "showLastUpdateTimestamp", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "manualDataUpdate", "manualDataUpdate", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "sendCustomUserAgent", "sendCustomUserAgent"); // no callback, manual refresh required + this.settings.bindProperty(Settings.BindingDirection.IN, "customUserAgent", "customUserAgent"); // no callback, manual refresh required + this.settings.bindProperty(Settings.BindingDirection.IN, "roundNumbers", "roundNumbers", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "decimalPlaces", "decimalPlaces", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "strictRounding", "strictRounding", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "use24HourTime", "use24HourTime", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "customTimeFormat", "customTimeFormat", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "customDateFormat", "customDateFormat", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "quoteSymbols", "quoteSymbolsText"); // no callback, manual refresh required + this.settings.bindProperty(Settings.BindingDirection.IN, "sortCriteria", "sortCriteria", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "sortDirection", "sortAscending", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showChangeIcon", "showChangeIcon", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showQuoteName", "showQuoteName", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "useLongQuoteName", "useLongQuoteName", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "linkQuoteName", "linkQuoteName", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showQuoteSymbol", "showQuoteSymbol", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "linkQuoteSymbol", "linkQuoteSymbol", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showMarketPrice", "showMarketPrice", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showCurrencyCode", "showCurrencyCode", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showAbsoluteChange", "showAbsoluteChange", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showPercentChange", "showPercentChange", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "colorPercentChange", "colorPercentChange", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "showTradeTime", "showTradeTime", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "fontColor", "fontColor", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "scaleFontSize", "scaleFontSize", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "fontScale", "fontScale", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "uptrendChangeColor", "uptrendChangeColor", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "downtrendChangeColor", "downtrendChangeColor", this.onRenderSettingsChanged); + this.settings.bindProperty(Settings.BindingDirection.IN, "unchangedTrendColor", "unchangedTrendColor", this.onRenderSettingsChanged); }, - getQuoteDisplaySettings: function(quotes, symbolCustomizationMap) { + getQuoteDisplaySettings: function(quotes) { return { "changeIcon": this.showChangeIcon, "quoteName": this.showQuoteName, @@ -744,27 +786,13 @@ StockQuoteDesklet.prototype = { "downtrendChangeColor": this.downtrendChangeColor, "unchangedTrendColor": this.unchangedTrendColor, "quoteSymbolWidth": Math.max.apply(Math, quotes.map((quote) => quote.symbol.length)), - "quoteLabelWidth": Math.max.apply(Math, quotes.map((quote) => this.quoteUtils.determineQuoteName(quote, symbolCustomizationMap.get(quote.symbol), this.useLongQuoteName).length)) / 2 + 2 + "quoteLabelWidth": Math.max.apply(Math, quotes.map((quote) => quote.displayName.length)) / 2 + 2 }; }, - formatCurrentTimestamp: function(settings) { - const now = new Date(); - if (settings.customTimeFormat) { - return now.toLocaleFormat(settings.customTimeFormat); - } else { - return now.toLocaleTimeString(undefined, { - hour: "numeric", - hour12: !settings.use24HourTime, - minute: "numeric", - second: "numeric" - }); - } - }, - - createLastUpdateLabel: function(settings) { + createLastUpdateLabel: function(lastUpdated, settings) { const label = new St.Label({ - text: _("Updated at ") + this.formatCurrentTimestamp(settings), + text: _("Updated at ") + this.formatCurrentTimestamp(lastUpdated, settings), style_class: "quotes-last-update", reactive: this.manualDataUpdate, style: "color: " + settings.fontColor + "; " + (settings.fontSize > 0 ? "font-size: " + settings.fontSize + "px;" : "") @@ -774,8 +802,7 @@ StockQuoteDesklet.prototype = { const updateButton = new St.Button(); updateButton.add_actor(label); updateButton.connect("clicked", Lang.bind(this, function() { - this.removeUpdateTimer(); - this.onUpdate(); + this.onQuotesListChanged(); })); return updateButton; } else { @@ -783,6 +810,19 @@ StockQuoteDesklet.prototype = { } }, + formatCurrentTimestamp: function(lastUpdated, settings) { + if (settings.customTimeFormat) { + return lastUpdated.toLocaleFormat(settings.customTimeFormat); + } else { + return lastUpdated.toLocaleTimeString(undefined, { + hour: "numeric", + hour12: !settings.use24HourTime, + minute: "numeric", + second: "numeric" + }); + } + }, + createErrorLabel: function(errorMsg) { return new St.Label({ text: _("Error: ") + errorMsg, @@ -790,7 +830,8 @@ StockQuoteDesklet.prototype = { }); }, - onDisplayChanged: function() { + // called on events that change the desklet window + onDisplaySettingChanged: function() { this.mainBox.set_size(this.width, this.height); this.setBackground(); }, @@ -805,66 +846,58 @@ StockQuoteDesklet.prototype = { return "rgba(" + parseInt(rgb[0]) + "," + parseInt(rgb[1]) + "," + parseInt(rgb[2]) + "," + transparencyFactor + ")"; }, - onSettingsChanged: function() { - this.unrender(); - this.removeUpdateTimer(); - this.onUpdate(); + // called on events that change the quotes data layout (sorting, show/hide fields, text color, etc) + onRenderSettingsChanged: function() { + this.render(); }, - onQuotesListChanged: function() { + // called on events that change the way YFQ data are fetched (data refresh interval) + onDataFetchSettingsChanged: function() { this.removeUpdateTimer(); - this.onUpdate(); + this.setUpdateTimer(); }, - on_desklet_removed: function() { - this.unrender(); + // called on events that change the quotes data (quotes list) + // BEWARE: DO NOT use this function as callback in settings.bindProperty() - otherwise multiple YFQ requests are fired, and multiple timers are created! + onQuotesListChanged: function() { + logDebug("onQuotesListChanged"); + + if (this.updateInProgress) { + logInfo("Data refresh already in progress"); + return; + } this.removeUpdateTimer(); - }, - onUpdate: function() { - const symbolCustomizationMap = this.buildSymbolCustomizationMap(this.quoteSymbolsText); + const quoteSymbolsArg = this.quoteUtils.buildSymbolsArgument(this.quoteSymbolsText); const customUserAgent = this.sendCustomUserAgent ? this.customUserAgent : null; try { if (_crumb) { - this.renderFinanceData(symbolCustomizationMap, customUserAgent); + this.fetchFinanceDataAndRender(quoteSymbolsArg, customUserAgent); } else { - this.fetchCookieAndRender(symbolCustomizationMap, customUserAgent); + this.fetchCookieAndRender(quoteSymbolsArg, customUserAgent); } } catch (err) { - this.onError(this.quoteUtils.buildSymbolList(symbolCustomizationMap), err); + logError("Cannot fetch quotes information for symbol %s due to error: %s".format(quoteSymbolsArg, err)); + this.processFailedFetch(err); } }, - buildSymbolCustomizationMap: function(quoteSymbolsText) { - const symbolCustomizations = new Map(); - for (const line of quoteSymbolsText.trim().split("\n")) { - const customization = this.createSymbolCustomization(line) - symbolCustomizations.set(customization.symbol, customization); - } - logDebug("symbol customization map size: " + symbolCustomizations.size) - - return symbolCustomizations; - }, - - createSymbolCustomization: function(symbolLine) { - const lineParts = symbolLine.trim().split(";"); - - const customAttributes = new Map(); - for (const attr of lineParts.slice(1)) { - const [key, value] = attr.split('='); - if (key && value) { - customAttributes.set(key, value); - } - } + fetchFinanceDataAndRender: function(quoteSymbolsArg, customUserAgent) { + const _that = this; - return { - symbol: lineParts[0], - name: customAttributes.has("name") ? customAttributes.get("name") : null, - style: customAttributes.has("style") ? customAttributes.get("style") : "normal", - weight: customAttributes.has("weight") ? customAttributes.get("weight") : "normal", - color: customAttributes.has("color") ? customAttributes.get("color") : null, - }; + this.quoteReader.getFinanceData(quoteSymbolsArg, customUserAgent, function(response) { + logDebug("YF query response: " + response); + let parsedResponse = JSON.parse(response); + _lastResponse = + { + responseResult: parsedResponse.quoteResponse.result, + responseError: parsedResponse.quoteResponse.error, + lastUpdated: new Date() + }; + _that.setUpdateTimer(); + _that.render(); + }); }, existsCookie: function(name) { @@ -878,23 +911,23 @@ StockQuoteDesklet.prototype = { return false; }, - fetchCookieAndRender: function(symbolCustomizationMap, customUserAgent) { + fetchCookieAndRender: function(quoteSymbolsArg, customUserAgent) { const _that = this; this.quoteReader.getCookie(customUserAgent, function(authResponseMessage, responseBody) { logDebug("Cookie response body: " + responseBody); if (_that.existsCookie(AUTH_COOKIE)) { - _that.fetchCrumbAndRender(symbolCustomizationMap, customUserAgent); + _that.fetchCrumbAndRender(quoteSymbolsArg, customUserAgent); } else if (_that.existsCookie(CONSENT_COOKIE)) { - _that.processConsentAndRender(authResponseMessage, responseBody, symbolCustomizationMap, customUserAgent); + _that.processConsentAndRender(authResponseMessage, responseBody, quoteSymbolsArg, customUserAgent); } else { logWarning("Failed to retrieve auth cookie!"); - _that.renderErrorMessage(_("Failed to retrieve authorization parameter! Unable to fetch quotes data.\\nStatus: ") + _that.quoteUtils.getMessageStatusInfo(authResponseMessage), symbolCustomizationMap); + _that.processFailedFetch(_("Failed to retrieve authorization parameter! Unable to fetch quotes data.\\nStatus: ") + _that.quoteUtils.getMessageStatusInfo(authResponseMessage)); } }); }, - processConsentAndRender: function(authResponseMessage, consentPage, symbolCustomizationMap, customUserAgent) { + processConsentAndRender: function(authResponseMessage, consentPage, quoteSymbolsArg, customUserAgent) { const _that = this; const formElementRegex = /(