diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/applet.js b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/applet.js
index 07b9ec2fced..b7d453e4104 100644
--- a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/applet.js
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/applet.js
@@ -1,257 +1,195 @@
const Applet = imports.ui.applet;
-const Soup = imports.gi.Soup;
-const ByteArray = imports.byteArray;
-const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const Lang = imports.lang;
-
-const logging = false;
-
-let _httpSession;
-if (Soup.MAJOR_VERSION == 2) {
- _httpSession = new Soup.SessionAsync();
- Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
-} else { //version 3
- _httpSession = new Soup.Session();
-}
-
-const bingHost = 'https://www.bing.com';
-const bingRequestPath = '/HPImageArchive.aspx?format=js&idx=0&n=1&mbl=1';
-
-function log(message) {
- if (logging) global.log(`[bing-wallpaper@starcross.dev]: ${message}`);
-}
-
+const HttpClient = require("./httpClient").HttpClient;
+const FileManager = require("./fileManager").FileManager;
+const SettingsManager = require("./settingsManager").SettingsManager;
+const Utils = require("./utils").Utils;
+
+// Time interval for refreshing wallpaper (in seconds)
+const REFRESH_INTERVAL = 300;
+
+/**
+ * BingWallpaperApplet is a Cinnamon applet that sets the Bing daily wallpaper.
+ * It handles fetching metadata, downloading images, and updating the desktop background.
+ */
function BingWallpaperApplet(orientation, panel_height, instance_id) {
- this._init(orientation, panel_height, instance_id);
+ this._init(orientation, panel_height, instance_id);
}
BingWallpaperApplet.prototype = {
- __proto__: Applet.IconApplet.prototype,
-
- _init: function (orientation, panel_height, instance_id) {
-
- // Generic Setup
- Applet.IconApplet.prototype._init.call(this, orientation, panel_height, instance_id);
- this.set_applet_icon_symbolic_name("bing-wallpaper");
- this.set_applet_tooltip('Bing Desktop Wallpaper');
-
- // Path to store data in
- this.wallpaperDir = `${GLib.get_user_config_dir()}/bingwallpaper`;
- let dir = Gio.file_new_for_path(this.wallpaperDir);
- if (!dir.query_exists(null))
- dir.make_directory(null);
- this.wallpaperPath = `${this.wallpaperDir}/BingWallpaper.jpg`;
- this.metaDataPath = `${this.wallpaperDir}/meta.json`;
-
- // Begin refresh loop
- this._refresh();
- },
-
- _refresh: function () {
- log(`Beginning refresh`);
- this._getMetaData();
- this._setTimeout(300);
- },
-
- _removeTimeout: function () {
- if (this._timeout) {
- Mainloop.source_remove(this._timeout);
- this._timeout = null;
- }
- },
-
- _setTimeout: function (seconds) {
- /** Cancel current timeout in event of an error and try again shortly */
- this._removeTimeout();
- log(`Setting timeout (${seconds}s)`);
- this._timeout = Mainloop.timeout_add_seconds(seconds, Lang.bind(this, this._refresh));
- },
-
- destroy: function () {
- this._removeTimeout();
- },
- on_applet_removed_from_panel() {
- this._removeTimeout();
- },
-
- _getMetaData: function () {
-
- /** Check for local metadata */
- try {
- const jsonString = GLib.file_get_contents(this.metaDataPath)[1];
- const json = JSON.parse(jsonString);
-
- this.imageData = json.images[0];
- this.set_applet_tooltip(this.imageData.copyright);
- log(`Got image url from local file : ${this.imageData.url}`);
-
- /** See if this data is current */
- const start_date = GLib.DateTime.new(
- GLib.TimeZone.new_utc(),
- this.imageData.fullstartdate.substring(0,4),
- this.imageData.fullstartdate.substring(4,6),
- this.imageData.fullstartdate.substring(6,8),
- this.imageData.fullstartdate.substring(8,10),
- this.imageData.fullstartdate.substring(10,12),
- 0
- );
- const end_date = start_date.add_days(1);
- const now = GLib.DateTime.new_now_utc();
-
- if (now.to_unix() < end_date.to_unix()) {
- log('metadata up to date');
-
- // Look for image file, check this is up to date
- let image_file = Gio.file_new_for_path(this.wallpaperPath);
-
- if (image_file.query_exists(null)) {
-
- let image_file_info = image_file.query_info('*', Gio.FileQueryInfoFlags.NONE, null);
- let image_file_size = image_file_info.get_size();
- let image_file_mod_secs = image_file_info.get_modification_time().tv_sec;
-
- if ((image_file_mod_secs > end_date.to_unix()) || !image_file_size) { // Is the image old, or empty?
- this._downloadImage();
-
- } else {
- log("image appears up to date");
- }
-
- } else {
- log("No image file found");
- this._downloadImage();
- }
-
- }
- else {
- log('metadata is old, requesting new...');
- this._downloadMetaData();
- }
-
-
- } catch (err) {
- log(`Unable to get local metadata ${err}`);
- /** File does not exist or there was an error processing it */
- this._downloadMetaData();
- }
- },
-
- _downloadMetaData: function () {
- const process_result = data => {
-
- // Write to meta data file
- let gFile = Gio.file_new_for_path(this.metaDataPath);
- let fStream = gFile.replace(null, false, Gio.FileCreateFlags.NONE, null);
- let toWrite = data.length;
- while (toWrite > 0)
- toWrite -= fStream.write(data, null);
- fStream.close(null);
-
- const json = JSON.parse(data);
- this.imageData = json.images[0];
- this.set_applet_tooltip(this.imageData.copyright);
- log(`Got image url from download: ${this.imageData.url}`);
-
- this._downloadImage();
-
- };
-
- // Retrieve json metadata, either from local file or remote
- let request = Soup.Message.new('GET', `${bingHost}${bingRequestPath}`);
- if (Soup.MAJOR_VERSION === 2) {
- _httpSession.queue_message(request, (_httpSession, message) => {
- if (message.status_code === 200) {
- process_result(message.response_body.data);
- } else {
- log(`Failed to acquire image metadata (${message.status_code})`);
- this._setTimeout(60) // Try again
- }
-
- });
- } else { //version 3
- _httpSession.send_and_read_async(request, Soup.MessagePriority.NORMAL, null, (_httpSession, message) => {
- if (request.get_status() === 200) {
- const bytes = _httpSession.send_and_read_finish(message);
- process_result(ByteArray.toString(bytes.get_data()));
- } else {
- log(`Failed to acquire image metadata (${request.get_status()})`);
- this._setTimeout(60) // Try again
- }
- });
- }
- },
-
- _downloadImage: function () {
-
- log('downloading new image');
- const url = `${bingHost}${this.imageData.url}`;
- const regex = /_\d+x\d+./gm;
- const urlUHD = url.replace(regex, `_UHD.`);
- let gFile = Gio.file_new_for_path(this.wallpaperPath);
-
- // open the file
- let fStream = gFile.replace(null, false, Gio.FileCreateFlags.NONE, null);
-
- // create a http message
- let request = Soup.Message.new('GET', urlUHD);
-
- // keep track of total bytes written
- let bytesTotal = 0;
-
- if (Soup.MAJOR_VERSION === 2) {
- // got_chunk event
- request.connect('got_chunk', function (message, chunk) {
- if (message.status_code === 200) { // only save the data we want, not content of 301 redirect page
- bytesTotal += fStream.write(chunk.get_data(), null);
- }
- });
-
- // queue the http request
- _httpSession.queue_message(request, (httpSession, message) => {
- // request completed
- fStream.close(null);
- const contentLength = message.response_headers.get_content_length();
- if (message.status_code === 200 && contentLength === bytesTotal) {
- this._setBackground();
- } else {
- log("Couldn't fetch image from " + urlUHD);
- gFile.delete(null);
- this._setTimeout(60) // Try again
- }
- });
- } else { //version 3
- _httpSession.send_and_read_async(request, Soup.MessagePriority.NORMAL, null, (httpSession, message) => {
- if (request.get_status() === 200) {
- const bytes = _httpSession.send_and_read_finish(message);
- if (bytes && bytes.get_size() > 0) {
- fStream.write(bytes.get_data(), null);
- }
- // request completed
- fStream.close(null);
- log('Download successful');
- this._setBackground();
- } else {
- log("Couldn't fetch image from " + urlUHD);
- this._setTimeout(60) // Try again
- }
- });
- }
- },
-
- _setBackground: function () {
- let gSetting = new Gio.Settings({schema: 'org.cinnamon.desktop.background'});
- const uri = 'file://' + this.wallpaperPath;
- gSetting.set_string('picture-uri', uri);
- gSetting.set_string('picture-options', 'zoom');
- Gio.Settings.sync();
- gSetting.apply();
+ __proto__: Applet.IconApplet.prototype,
+
+ /**
+ * Initializes the applet, sets up directories, and starts the refresh loop.
+ * @param {number} orientation - The orientation of the panel.
+ * @param {number} panel_height - The height of the panel.
+ * @param {string} instance_id - The instance identifier of the applet.
+ */
+ _init: function (orientation, panel_height, instance_id) {
+ // Call parent class initialization
+ Applet.IconApplet.prototype._init.call(
+ this,
+ orientation,
+ panel_height,
+ instance_id
+ );
+
+ // Set applet icon and tooltip
+ this.set_applet_icon_symbolic_name("bing-wallpaper");
+ this.set_applet_tooltip("Bing Desktop Wallpaper");
+
+ // Initialize file manager and HTTP client
+ this.fileManager = new FileManager();
+ this.httpClient = new HttpClient();
+
+ this._refresh();
+ },
+
+ /**
+ * Starts the process to refresh the wallpaper by fetching metadata and setting a timeout.
+ */
+ _refresh: function () {
+ Utils.log("Beginning refresh");
+ this._getMetaData();
+ this._setTimeout(REFRESH_INTERVAL);
+ },
+
+ /**
+ * Removes the current timeout to prevent multiple timeouts.
+ */
+ _removeTimeout: function () {
+ if (this._timeout) {
+ Mainloop.source_remove(this._timeout);
+ this._timeout = null;
+ }
+ },
+
+ /**
+ * Sets a timeout for refreshing the wallpaper.
+ * @param {number} seconds - Number of seconds to wait before the next refresh.
+ */
+ _setTimeout: function (seconds) {
+ this._removeTimeout();
+ Utils.log(`Setting timeout (${seconds}s)`);
+ this._timeout = Mainloop.timeout_add_seconds(
+ seconds,
+ Lang.bind(this, this._refresh)
+ );
+ },
+
+ /**
+ * Cleanup function called when the applet is destroyed or removed.
+ */
+ destroy: function () {
+ this._removeTimeout();
+ },
+
+ /**
+ * Called when the applet is removed from the panel to stop refreshing.
+ */
+ on_applet_removed_from_panel() {
+ this._removeTimeout();
+ },
+
+ /**
+ * Retrieves metadata from local storage or the Bing server.
+ */
+ _getMetaData: function () {
+ try {
+ let metaData = this.fileManager.readMetaData();
+ if (this._isMetaDataUpToDate(metaData)) {
+ Utils.log("Metadata is up-to-date");
+ this._checkImage();
+ } else {
+ Utils.log("Metadata is old, requesting new...");
+ this._downloadMetaData();
+ }
+ } catch (err) {
+ Utils.log(`Unable to get local metadata: ${err}`);
+ this._downloadMetaData();
}
+ },
+
+ /**
+ * Checks if the retrieved metadata is still valid.
+ * @param {object} metaData - Metadata object containing image details.
+ * @returns {boolean} - True if metadata is up-to-date, false otherwise.
+ */
+ _isMetaDataUpToDate: function (metaData) {
+ const { start_date, end_date } =
+ this.fileManager.parseMetaDataDates(metaData);
+ const now = GLib.DateTime.new_now_utc();
+ return now.to_unix() < end_date.to_unix();
+ },
+
+ /**
+ * Checks if the image file is up-to-date. If not, it downloads a new image.
+ */
+ _checkImage: function () {
+ if (this.fileManager.isImageUpToDate()) {
+ Utils.log("Image appears up-to-date");
+ } else {
+ Utils.log("Image is old or missing");
+ this._downloadImage();
+ }
+ },
+
+ /**
+ * Downloads metadata from Bing's API and processes it.
+ */
+ _downloadMetaData: function () {
+ const url =
+ "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mbl=1";
+ this.httpClient.get(
+ url,
+ (data) => {
+ this.fileManager.writeMetaData(data);
+ this._downloadImage();
+ },
+ (error) => {
+ Utils.log(`Failed to acquire image metadata: ${error}`);
+ this._setTimeout(60); // Retry after 60 seconds
+ }
+ );
+ },
+
+ /**
+ * Downloads the wallpaper image and sets it as the desktop background.
+ */
+ _downloadImage: function () {
+ Utils.log("Downloading new image");
+ const url = this.fileManager.getImageUrl();
+ this.httpClient.get(
+ url,
+ (data) => {
+ this.fileManager.writeImage(data);
+ this._setBackground();
+ },
+ (error) => {
+ Utils.log(`Couldn't fetch image from ${url}: ${error}`);
+ this._setTimeout(60); // Retry after 60 seconds
+ }
+ );
+ },
+
+ /**
+ * Sets the downloaded image as the desktop wallpaper.
+ */
+ _setBackground: function () {
+ SettingsManager.setWallpaper(this.fileManager.getWallpaperPath());
+ },
};
-
+/**
+ * Main function to initialize the BingWallpaperApplet.
+ * @param {object} metadata - Metadata for the applet.
+ * @param {number} orientation - The orientation of the panel.
+ * @param {number} panelHeight - The height of the panel.
+ * @param {string} instanceId - The instance identifier of the applet.
+ * @returns {BingWallpaperApplet} - An instance of the BingWallpaperApplet.
+ */
function main(metadata, orientation, panelHeight, instanceId) {
- let bingApplet = new BingWallpaperApplet(orientation, panelHeight, instanceId);
- return bingApplet;
+ return new BingWallpaperApplet(orientation, panelHeight, instanceId);
}
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/fileManager.js b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/fileManager.js
new file mode 100644
index 00000000000..43fe93824c6
--- /dev/null
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/fileManager.js
@@ -0,0 +1,112 @@
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+
+/**
+ * FileManager handles file operations related to storing and retrieving metadata and images.
+ */
+const FileManager = function () {
+ this.wallpaperDir = `${GLib.get_user_config_dir()}/bingwallpaper`;
+ this.wallpaperPath = `${this.wallpaperDir}/BingWallpaper.jpg`;
+ this.metaDataPath = `${this.wallpaperDir}/meta.json`;
+
+ this._init();
+};
+
+FileManager.prototype = {
+ /**
+ * Initializes the file manager by creating necessary directories.
+ */
+ _init: function () {
+ let dir = Gio.file_new_for_path(this.wallpaperDir);
+ if (!dir.query_exists(null)) dir.make_directory(null);
+ },
+
+ /**
+ * Reads metadata from the local file.
+ * @returns {object} - Parsed JSON metadata.
+ */
+ readMetaData: function () {
+ const jsonString = GLib.file_get_contents(this.metaDataPath)[1];
+ return JSON.parse(jsonString);
+ },
+
+ /**
+ * Writes metadata to the local file.
+ * @param {string} data - Metadata in JSON format.
+ */
+ writeMetaData: function (data) {
+ let gFile = Gio.file_new_for_path(this.metaDataPath);
+ let fStream = gFile.replace(null, false, Gio.FileCreateFlags.NONE, null);
+ fStream.write(data, null);
+ fStream.close(null);
+ },
+
+ /**
+ * Parses metadata dates to GLib.DateTime objects.
+ * @param {object} metaData - Metadata object containing image details.
+ * @returns {object} - Object containing start_date and end_date as GLib.DateTime.
+ */
+ parseMetaDataDates: function (metaData) {
+ const startDateStr = metaData.images[0].fullstartdate;
+ const start_date = GLib.DateTime.new(
+ GLib.TimeZone.new_utc(),
+ startDateStr.substring(0, 4),
+ startDateStr.substring(4, 6),
+ startDateStr.substring(6, 8),
+ startDateStr.substring(8, 10),
+ startDateStr.substring(10, 12),
+ 0
+ );
+ const end_date = start_date.add_days(1);
+ return { start_date, end_date };
+ },
+
+ /**
+ * Checks if the image file is up-to-date.
+ * @returns {boolean} - True if the image is up-to-date, false otherwise.
+ */
+ isImageUpToDate: function () {
+ let image_file = Gio.file_new_for_path(this.wallpaperPath);
+ if (image_file.query_exists(null)) {
+ let image_file_info = image_file.query_info(
+ "*",
+ Gio.FileQueryInfoFlags.NONE,
+ null
+ );
+ let image_file_mod_secs = image_file_info.get_modification_time().tv_sec;
+ return image_file_mod_secs > GLib.DateTime.new_now_utc().to_unix();
+ }
+ return false;
+ },
+
+ /**
+ * Writes an image to the local file.
+ * @param {string} data - Image data in binary format.
+ */
+ writeImage: function (data) {
+ let gFile = Gio.file_new_for_path(this.wallpaperPath);
+ let fStream = gFile.replace(null, false, Gio.FileCreateFlags.NONE, null);
+ fStream.write(data, null);
+ fStream.close(null);
+ },
+
+ /**
+ * Returns the path to the wallpaper file.
+ * @returns {string} - Path to the wallpaper file.
+ */
+ getWallpaperPath: function () {
+ return this.wallpaperPath;
+ },
+
+ /**
+ * Returns the image URL for downloading.
+ * @returns {string} - URL of the image to download.
+ */
+ getImageUrl: function () {
+ // Implementation depends on the structure of metadata.
+ // For example:
+ return this.imageData.url.replace(/_\d+x\d+./gm, `_UHD.`);
+ },
+};
+
+exports.FileManager = FileManager;
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/httpClient.js b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/httpClient.js
new file mode 100644
index 00000000000..7ceb049d31e
--- /dev/null
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/httpClient.js
@@ -0,0 +1,61 @@
+const Soup = imports.gi.Soup;
+const ByteArray = imports.byteArray;
+const Lang = imports.lang;
+
+/**
+ * HttpClient handles HTTP requests to fetch data from the web.
+ */
+const HttpClient = function () {
+ this._init();
+};
+
+HttpClient.prototype = {
+ /**
+ * Initializes the HTTP client based on the version of Soup.
+ */
+ _init: function () {
+ this.session =
+ Soup.MAJOR_VERSION === 2 ? new Soup.SessionAsync() : new Soup.Session();
+ if (Soup.MAJOR_VERSION === 2) {
+ Soup.Session.prototype.add_feature.call(
+ this.session,
+ new Soup.ProxyResolverDefault()
+ );
+ }
+ },
+
+ /**
+ * Performs a GET request to the specified URL.
+ * @param {string} url - The URL to fetch data from.
+ * @param {function} onSuccess - Callback function to handle successful response.
+ * @param {function} onError - Callback function to handle errors.
+ */
+ get: function (url, onSuccess, onError) {
+ let request = Soup.Message.new("GET", url);
+ if (Soup.MAJOR_VERSION === 2) {
+ this.session.queue_message(request, (session, message) => {
+ if (message.status_code === 200) {
+ onSuccess(message.response_body.data);
+ } else {
+ onError(message.status_code);
+ }
+ });
+ } else {
+ this.session.send_and_read_async(
+ request,
+ Soup.MessagePriority.NORMAL,
+ null,
+ (session, message) => {
+ if (request.get_status() === 200) {
+ const bytes = this.session.send_and_read_finish(message);
+ onSuccess(ByteArray.toString(bytes.get_data()));
+ } else {
+ onError(request.get_status());
+ }
+ }
+ );
+ }
+ },
+};
+
+exports.HttpClient = HttpClient;
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icon.svg b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icon.svg
index eea8edac6c6..7a6cc2296e1 100644
--- a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icon.svg
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icon.svg
@@ -1,22 +1,61 @@
+
+
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icons/bing-wallpaper-symbolic.svg b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icons/bing-wallpaper-symbolic.svg
index 8f3ae23b8cd..7a6cc2296e1 100644
--- a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icons/bing-wallpaper-symbolic.svg
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/icons/bing-wallpaper-symbolic.svg
@@ -1,4 +1,6 @@
+
+
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/settingsManager.js b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/settingsManager.js
new file mode 100644
index 00000000000..323a2b35cdf
--- /dev/null
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/settingsManager.js
@@ -0,0 +1,25 @@
+const Gio = imports.gi.Gio;
+
+/**
+ * SettingsManager manages system settings for changing the desktop wallpaper.
+ */
+const SettingsManager = function () {};
+
+SettingsManager.prototype = {
+ /**
+ * Sets the desktop wallpaper to the specified file path.
+ * @param {string} filePath - File path of the wallpaper image.
+ */
+ setWallpaper: function (filePath) {
+ let gSetting = new Gio.Settings({
+ schema: "org.cinnamon.desktop.background",
+ });
+ const uri = "file://" + filePath;
+ gSetting.set_string("picture-uri", uri);
+ gSetting.set_string("picture-options", "zoom");
+ Gio.Settings.sync();
+ gSetting.apply();
+ },
+};
+
+exports.SettingsManager = SettingsManager;
diff --git a/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/utils.js b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/utils.js
new file mode 100644
index 00000000000..fa433992a7e
--- /dev/null
+++ b/bing-wallpaper@starcross.dev/files/bing-wallpaper@starcross.dev/utils.js
@@ -0,0 +1,17 @@
+/**
+ * Utility functions for logging and other common tasks.
+ */
+const Utils = {
+ /**
+ * Logs a message if logging is enabled.
+ * @param {string} message - The message to log.
+ */
+ log: function (message) {
+ if (false) {
+ // Set to true to enable logging
+ global.log(`[bing-wallpaper@starcross.dev]: ${message}`);
+ }
+ },
+};
+
+exports.Utils = Utils;