utils.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. // Bing Wallpaper GNOME extension
  2. // Copyright (C) 2017-2025 Michael Carroll
  3. // This extension is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Lesser General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. // See the GNU General Public License, version 3 or later for details.
  8. // Based on GNOME shell extension NASA APOD by Elia Argentieri https://github.com/Elinvention/gnome-shell-extension-nasa-apod
  9. import Gio from 'gi://Gio';
  10. import GLib from 'gi://GLib';
  11. import Soup from 'gi://Soup';
  12. import GdkPixbuf from 'gi://GdkPixbuf';
  13. export var PRESET_GNOME_DEFAULT = { blur: 45, dim: 65 }; // as at GNOME 40
  14. export var PRESET_NO_BLUR = { blur: 0, dim: 65 };
  15. export var PRESET_SLIGHT_BLUR = { blur: 2, dim: 30 };
  16. export var BING_SCHEMA = 'org.gnome.shell.extensions.bingwallpaper';
  17. export var DESKTOP_SCHEMA = 'org.gnome.desktop.background';
  18. var vertical_blur = null;
  19. var horizontal_blur = null;
  20. let gitreleaseurl = 'https://api.github.com/repos/neffo/bing-wallpaper-gnome-extension/releases/tags/';
  21. let debug = false;
  22. export var icon_list = ['bing-symbolic', 'brick-symbolic', 'high-frame-symbolic', 'mid-frame-symbolic', 'low-frame-symbolic'];
  23. export var resolutions = ['auto', 'UHD', '1920x1200', '1920x1080', '1366x768', '1280x720', '1024x768', '800x600'];
  24. export var markets = ['auto', 'ar-XA', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'en-AU', 'en-CA', 'en-GB',
  25. 'en-ID', 'en-IE', 'en-IN', 'en-MY', 'en-NZ', 'en-PH', 'en-SG', 'en-US', 'en-WW', 'en-XA', 'en-ZA', 'es-AR',
  26. 'es-CL', 'es-ES', 'es-MX', 'es-US', 'es-XL', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-CH', 'fr-FR',
  27. 'he-IL', 'hr-HR', 'hu-HU', 'it-IT', 'ja-JP', 'ko-KR', 'lt-LT', 'lv-LV', 'nb-NO', 'nl-BE', 'nl-NL',
  28. 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'sk-SK', 'sl-SL', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA',
  29. 'zh-CN', 'zh-HK', 'zh-TW'];
  30. export var marketName = [
  31. 'auto', '(شبه الجزيرة العربية‎) العربية', 'dansk (Danmark)', 'Deutsch (Österreich)',
  32. 'Deutsch (Schweiz)', 'Deutsch (Deutschland)', 'English (Australia)', 'English (Canada)',
  33. 'English (United Kingdom)', 'English (Indonesia)', 'English (Ireland)', 'English (India)', 'English (Malaysia)',
  34. 'English (New Zealand)', 'English (Philippines)', 'English (Singapore)', 'English (United States)',
  35. 'English (International)', 'English (Arabia)', 'English (South Africa)', 'español (Argentina)', 'español (Chile)',
  36. 'español (España)', 'español (México)', 'español (Estados Unidos)', 'español (Latinoamérica)', 'eesti (Eesti)',
  37. 'suomi (Suomi)', 'français (Belgique)', 'français (Canada)', 'français (Suisse)', 'français (France)',
  38. '(עברית (ישראל', 'hrvatski (Hrvatska)', 'magyar (Magyarország)', 'italiano (Italia)', '日本語 (日本)', '한국어(대한민국)',
  39. 'lietuvių (Lietuva)', 'latviešu (Latvija)', 'norsk bokmål (Norge)', 'Nederlands (België)', 'Nederlands (Nederland)',
  40. 'polski (Polska)', 'português (Brasil)', 'português (Portugal)', 'română (România)', 'русский (Россия)',
  41. 'slovenčina (Slovensko)', 'slovenščina (Slovenija)', 'svenska (Sverige)', 'ไทย (ไทย)', 'Türkçe (Türkiye)',
  42. 'українська (Україна)', '中文(中国)', '中文(中國香港特別行政區)', '中文(台灣)'
  43. ];
  44. export var backgroundStyle = ['none', 'wallpaper', 'centered', 'scaled', 'stretched', 'zoom', 'spanned']; // this may change in the future
  45. export var randomIntervals = [ {value: 'hourly', title: ('on the hour')},
  46. {value: 'daily', title: ('every day at midnight')},
  47. {value: 'weekly', title: ('Sunday at midnight')},
  48. { value: 'custom', title: ('User defined interval')} ];
  49. export var BingImageURL = 'https://www.bing.com/HPImageArchive.aspx';
  50. export var BingParams = { format: 'js', idx: '0' , n: '8' , mbl: '1' , mkt: '' } ;
  51. export function validate_icon(
  52. settings,
  53. extension_path,
  54. icon_image = null,
  55. app_icon_image = null
  56. ) {
  57. BingLog('validate_icon()');
  58. let icon_name = settings.get_string('icon-name');
  59. if (icon_name == '' || icon_list.indexOf(icon_name) == -1) {
  60. settings.reset('icon-name');
  61. icon_name = settings.get_string('icon-name');
  62. }
  63. // if called from prefs
  64. if (icon_image && app_icon_image) {
  65. BingLog('set icon to: ' + extension_path + '/icons/' + icon_name + '.svg');
  66. let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(extension_path + '/icons/' + icon_name + '.svg', 64, 64);
  67. icon_image.set_from_pixbuf(pixbuf);
  68. app_icon_image.set_from_pixbuf(pixbuf);
  69. }
  70. }
  71. export function validate_resolution(settings) {
  72. let resolution = settings.get_string('resolution');
  73. if (resolution == '' || resolutions.indexOf(resolution) == -1) // if not a valid resolution
  74. settings.reset('resolution');
  75. }
  76. export function validate_interval(settings) {
  77. let index = randomIntervals.map( e => e.value).indexOf(settings.get_string('random-interval-mode'));
  78. if (index == -1) // if not a valid interval
  79. settings.reset('random-interval-mode');
  80. }
  81. // FIXME: needs work
  82. export function validate_imagename(settings) {
  83. let filename = settings.get_string('selected-image');
  84. if (filename != 'current' || filename != 'random') // FIXME: remove this when we move to new shuffle mode
  85. return;
  86. if (!inImageList(getImageList(settings), filename)) {
  87. BingLog('invalid image selected');
  88. //settings.reset('selected-image');
  89. settings.set_string('selected-image', 'current');
  90. }
  91. }
  92. export function get_current_bg(schema) {
  93. let gsettings = new Gio.Settings({ schema: schema });
  94. let cur = gsettings.get_string('picture-uri');
  95. return (cur);
  96. }
  97. export function fetch_change_log(version, label, httpSession) {
  98. const decoder = new TextDecoder();
  99. // create an http message
  100. let url = gitreleaseurl + "v" + version;
  101. let request = Soup.Message.new('GET', url);
  102. request.request_headers.append('Accept', 'application/json');
  103. BingLog("Fetching " + url);
  104. // queue the http request
  105. try {
  106. if (Soup.MAJOR_VERSION >= 3) {
  107. httpSession.send_and_read_async(request, GLib.PRIORITY_DEFAULT, null, (httpSession, message) => {
  108. let data = decoder.decode(httpSession.send_and_read_finish(message).get_data());
  109. let text = JSON.parse(data).body;
  110. if (text)
  111. label.set_label(text);
  112. });
  113. }
  114. else {
  115. httpSession.queue_message(request, (httpSession, message) => {
  116. let data = message.response_body.data;
  117. let text = JSON.parse(data).body;
  118. if (text)
  119. label.set_label(text);
  120. });
  121. }
  122. }
  123. catch (error) {
  124. BingLog("Error fetching change log: " + error);
  125. label.set_label(_("Error fetching change log: "+error));
  126. }
  127. }
  128. export function set_blur_preset(settings, preset) {
  129. settings.set_int('lockscreen-blur-strength', preset.blur);
  130. settings.set_int('lockscreen-blur-brightness', preset.dim);
  131. BingLog("Set blur preset to " + preset.blur + " brightness to " + preset.dim);
  132. }
  133. export function imageHasBasename(image_item, i, b) {
  134. //log("imageHasBasename : " + image_item.urlbase + " =? " + this);
  135. if (this && this.search(image_item.urlbase.replace('th?id=OHR.', '')))
  136. return true;
  137. return false;
  138. }
  139. export function dateFromLongDate(longdate, add_seconds) {
  140. if (typeof longdate === 'number')
  141. longdate = longdate.toString();
  142. return GLib.DateTime.new(GLib.TimeZone.new_utc(),
  143. parseInt(longdate.substr(0, 4)), // year
  144. parseInt(longdate.substr(4, 2)), // month
  145. parseInt(longdate.substr(6, 2)), // day
  146. parseInt(longdate.substr(8, 2)), // hour
  147. parseInt(longdate.substr(10, 2)), // mins
  148. 0 ).add_seconds(add_seconds); // seconds
  149. }
  150. export function dateFromShortDate(shortdate) {
  151. if (typeof shortdate === 'number')
  152. shortdate = shortdate.toString();
  153. return GLib.DateTime.new(GLib.TimeZone.new_utc(),
  154. parseInt(shortdate.substr(0, 4)), // year
  155. parseInt(shortdate.substr(4, 2)), // month
  156. parseInt(shortdate.substr(6, 2)), // day
  157. 0, 0, 0 );
  158. }
  159. export function getImageList(settings, filter = null) {
  160. let image_list = JSON.parse(settings.get_string('bing-json'));
  161. if (!filter) {
  162. return image_list;
  163. }
  164. else {
  165. return image_list.filter((x, i) => {
  166. if (filter.faves && !x.favourite)
  167. return false;
  168. if (filter.min_height && x.height < filter.min_height)
  169. return false;
  170. if (filter.hidden && x.hidden)
  171. return false;
  172. return true;
  173. });
  174. }
  175. }
  176. export function setImageList(settings, imageList) {
  177. settings.set_string('bing-json', JSON.stringify(imageList));
  178. if (settings.get_boolean('always-export-bing-json')) { // save copy of current JSON
  179. exportBingJSON(settings);
  180. }
  181. }
  182. export function setImageHiddenStatus(settings, hide_image, hide_status) {
  183. // get current image list
  184. let image_list = getImageList(settings);
  185. log ('image count = '+image_list.length+', hide_image = '+hide_image);
  186. image_list.forEach( (x, i) => {
  187. if (hide_image.includes(x.urlbase)) {
  188. // mark as hidden
  189. x.hidden = hide_status;
  190. }
  191. });
  192. // export image list back to settings
  193. setImageList(settings, image_list);
  194. }
  195. export function getImageTitle(image_data) {
  196. return image_data.copyright.replace(/\s*\(.*?\)\s*/g, '');
  197. }
  198. export function getImageUrlBase(image_data) {
  199. return image_data.urlbase.replace('/th?id=OHR.', '');
  200. }
  201. export function getMaxLongDate(settings) {
  202. let imageList = getImageList(settings);
  203. return Math.max.apply(Math, imageList.map(function(o) { return o.fullstartdate; }));
  204. }
  205. export function getCurrentImageIndex (imageList) {
  206. if (!imageList)
  207. return -1;
  208. let maxLongDate = Math.max.apply(Math, imageList.map(function(o) { return o.fullstartdate; }));
  209. let index = imageList.map(p => parseInt(p.fullstartdate)).indexOf(maxLongDate);
  210. BingLog('getCurrentImageIndex for ' + maxLongDate + ': ' + index);
  211. return index;
  212. }
  213. export function setImageFavouriteStatus(settings, imageURL, newState) {
  214. BingLog('set favourite status of '+imageURL+' to '+newState);
  215. let imageList = getImageList(settings);
  216. imageList.forEach(function(x, i) {
  217. //log('testing: '+imageURL+' includes '+x.urlbase);
  218. if (imageURL.includes(x.urlbase)) {
  219. BingLog('setting index '+i+' to '+newState?'true':'false');
  220. imageList[i].favourite = newState;
  221. }
  222. });
  223. setImageList(settings, imageList); // save back to settings
  224. }
  225. export function getCurrentImage(imageList) {
  226. if (!imageList || imageList.length == 0)
  227. return null;
  228. let index = getCurrentImageIndex(imageList);
  229. if (index == -1)
  230. return imageList[0]; // give something sensible
  231. return imageList[index];
  232. }
  233. export function inImageList(imageList, urlbase) {
  234. let image = null;
  235. imageList.forEach(function(x, i) {
  236. if (urlbase.replace('/th?id=OHR.', '') == x.urlbase.replace('/th?id=OHR.', ''))
  237. image = x;
  238. });
  239. return image;
  240. }
  241. export function inImageListByTitle(imageList, title) {
  242. let image = null;
  243. imageList.forEach(function(x, i) {
  244. BingLog('inImageListbyTitle(): ' + title + ' == ' + getImageTitle(x));
  245. if (getImageTitle(x) == title)
  246. image = x;
  247. });
  248. return image;
  249. }
  250. export function mergeImageLists(settings, imageList) {
  251. let curList = getImageList(settings);
  252. let newList = []; // list of only new images (for future notifications)
  253. imageList.forEach(function(x, i) {
  254. if (!inImageList(curList, x.urlbase)) {// if not in the list, add it
  255. curList.unshift(x); // use unshift to maintain reverse chronological order
  256. newList.unshift(x);
  257. }
  258. });
  259. setImageList(settings, imageListSortByDate(curList)); // sort then save back to settings
  260. return newList; // return this to caller for notifications
  261. }
  262. export function imageIndex(imageList, urlbase) {
  263. return imageList.map(p => p.urlbase.replace('/th?id=OHR.', '')).indexOf(urlbase.replace('/th?id=OHR.', ''));
  264. }
  265. export function isFavourite(image) {
  266. return (image.favourite && image.favourite === true);
  267. }
  268. export function getImageByIndex(imageList, index) {
  269. if (imageList.length == 0 || index < 0 || index > imageList.length - 1)
  270. return null;
  271. return imageList[index];
  272. }
  273. export function populateImageListResolutions(settings) {
  274. let curList = imageListSortByDate(getImageList(settings));
  275. let newList = [];
  276. curList.forEach( function (x, i) {
  277. let filename = imageToFilename(settings, x);
  278. let width, height;
  279. if (!x.width || !x.height) {
  280. [width, height] = getFileDimensions(filename);
  281. x.width = width;
  282. x.height = height;
  283. }
  284. newList.push(x);
  285. });
  286. setImageList(settings, newList);
  287. }
  288. export function getFetchableImageList(settings) {
  289. let imageList = getImageList(settings);
  290. let maxpictures = settings.get_int('previous-days');
  291. let maxdownload = 8;
  292. if (maxpictures < maxdownload && maxpictures >=1)
  293. maxdownload = maxpictures;
  294. let cutOff = GLib.DateTime.new_now_utc().add_days(-maxdownload); // default 8 days ago, 1 day = 1 picture
  295. let dlList = [];
  296. imageList.forEach( function (x, i) {
  297. let diff = dateFromLongDate(x.fullstartdate, 0).difference(cutOff);
  298. let filename = imageToFilename(settings, x);
  299. // image is still downloadable (< 8 days old) but not on disk
  300. if (diff > 0 && !Gio.file_new_for_path(filename).query_exists(null)) {
  301. dlList.push(x);
  302. }
  303. });
  304. return dlList;
  305. }
  306. export function getWallpaperDir(settings) {
  307. let homeDir = GLib.get_home_dir();
  308. let BingWallpaperDir = settings.get_string('download-folder').replace('~', homeDir);
  309. let userPicturesDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
  310. let userDesktopDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP); // seems to be a safer default
  311. if (BingWallpaperDir == '') {
  312. BingWallpaperDir = (userPicturesDir?userPicturesDir:userDesktopDir) + '/BingWallpaper/';
  313. BingLog('Using default download folder: ' + BingWallpaperDir);
  314. setWallpaperDir(settings, BingWallpaperDir);
  315. }
  316. else if (!BingWallpaperDir.endsWith('/')) {
  317. BingWallpaperDir += '/';
  318. }
  319. let dir = Gio.file_new_for_path(BingWallpaperDir);
  320. if (!dir.query_exists(null)) {
  321. dir.make_directory_with_parents(null);
  322. }
  323. //FIXME: test if dir is good and writable
  324. if (dir.query_exists(null))
  325. return BingWallpaperDir;
  326. else
  327. return null;
  328. }
  329. export function setWallpaperDir(settings, uri) {
  330. let homeDir = GLib.get_home_dir();
  331. let relUri = uri.replace(homeDir, '~');
  332. settings.set_string('download-folder', relUri);
  333. }
  334. export function imageToFilename(settings, image, resolution = null) {
  335. return getWallpaperDir(settings) + image.startdate + '-' +
  336. image.urlbase.replace(/^.*[\\\/]/, '').replace('th?id=OHR.', '') + '_'
  337. + (resolution ? resolution : getResolution(settings, image)) + '.jpg';
  338. }
  339. export function getRandomInt(max) {
  340. return Math.floor(Math.random() * max);
  341. }
  342. // Utility function
  343. export function dump(object, level = 0) {
  344. let output = '';
  345. for (let property in object) {
  346. output += ' - '.repeat(level)+property + ': ' + object[property]+'\n ';
  347. if ( typeof object[property] === 'object' )
  348. output += dump(object[property], level+1);
  349. }
  350. if (level == 0)
  351. BingLog(output);
  352. return(output);
  353. }
  354. export function friendly_time_diff(time, short = true) {
  355. // short we want to keep ~4-5 characters
  356. let now = GLib.DateTime.new_now_local().to_unix();
  357. let seconds = time.to_unix() - now;
  358. if (seconds <= 0) {
  359. return "now";
  360. }
  361. else if (seconds < 60) {
  362. return "< 1 " + (short ? "m" : _("minutes"));
  363. }
  364. else if (seconds < 3600) {
  365. return Math.round(seconds / 60) + " " + (short ? "m" : _("minutes"));
  366. }
  367. else if (seconds > 86400) {
  368. return Math.round(seconds / 86400) + " " + (short ? "d" : _("days"));
  369. }
  370. else {
  371. return Math.round(seconds / 3600) + " " + (short ? "h" : _("hours"));
  372. }
  373. }
  374. export function seconds_until(until) {
  375. let now = GLib.DateTime.new_now_local();
  376. let end, day;
  377. if (until == 'hourly') {
  378. end = GLib.DateTime.new_local(
  379. now.get_year(),
  380. now.get_month(),
  381. now.get_day_of_month(),
  382. now.get_hour()+1, // should roll over to next day if results in >23
  383. 0, 0);
  384. }
  385. else {
  386. if (until == 'weekly') {
  387. day = now.add_days(7 - now.get_day_of_week());
  388. }
  389. else {
  390. day = now.add_days(1);
  391. }
  392. end = GLib.DateTime.new_local(
  393. day.get_year(),
  394. day.get_month(),
  395. day.get_day_of_month(),
  396. 0, 0, 0); // midnight
  397. }
  398. BingLog('shuffle timer will be set to '+end.format_iso8601());
  399. return(Math.floor(end.difference(now)/1000000)); // difference in μs -> s
  400. }
  401. export function getResolution(settings, image) {
  402. let resolution = settings.get_string('resolution');
  403. if (resolutions.indexOf(resolution) == -1 || (image ? image.wp == false : true) || // wp == false when background is animated
  404. settings.get_string('resolution') == 'auto' ) {
  405. // resolution invalid, animated background or autoselected
  406. resolution = 'UHD';
  407. }
  408. return resolution;
  409. }
  410. export function openImageFolder(settings) {
  411. //const context = global?global.create_app_launch_context(0, -1):null;
  412. Gio.AppInfo.launch_default_for_uri('file://' + getWallpaperDir(settings), null);
  413. }
  414. export function imageListSortByDate(imageList) {
  415. return imageList.sort(function(a, b) {
  416. var x = parseInt(a.fullstartdate); var y = parseInt(b.fullstartdate);
  417. return ((x < y) ? -1 : ((x > y) ? 1 : 0));
  418. });
  419. }
  420. export function shortenName(string, limit) {
  421. if (string.length > limit) {
  422. string = string.substr(0, limit - 4) + '...';
  423. }
  424. return string;
  425. }
  426. export function moveImagesToNewFolder(settings, oldPath, newPath) {
  427. // possible race condition here, need to think about how to fix it
  428. //let BingWallpaperDir = settings.get_string('download-folder');
  429. let dir = Gio.file_new_for_path(oldPath);
  430. let dirIter = dir.enumerate_children('', Gio.FileQueryInfoFlags.NONE, null );
  431. let newDir = Gio.file_new_for_path(newPath);
  432. if (!newDir.query_exists(null)) {
  433. newDir.make_directory_with_parents(null);
  434. }
  435. let file = null;
  436. while (file = dirIter.next_file(null)) {
  437. let filename = file.get_name(); // we only want to move files that we think we own
  438. if (filename.match(/\d{8}\-.+\.jpg/i)) {
  439. BingLog('file: ' + slash(oldPath) + filename + ' -> ' + slash(newPath) + filename);
  440. let cur = Gio.file_new_for_path(slash(oldPath) + filename);
  441. let dest = Gio.file_new_for_path(slash(newPath) + filename);
  442. cur.move(dest, Gio.FileCopyFlags.OVERWRITE, null, function () { BingLog ('...moved'); });
  443. }
  444. }
  445. // correct filenames for GNOME backgrounds
  446. if (settings.get_boolean('set-background'))
  447. moveBackground(oldPath, newPath, DESKTOP_SCHEMA);
  448. }
  449. export function dirname(path) {
  450. return path.match(/.*\//);
  451. }
  452. export function slash(path) {
  453. if (!path.endsWith('/'))
  454. return path += '/';
  455. return path;
  456. }
  457. export function moveBackground(oldPath, newPath, schema) {
  458. let gsettings = new Gio.Settings({schema: schema});
  459. let uri = gsettings.get_string('picture-uri');
  460. gsettings.set_string('picture-uri', uri.replace(oldPath, newPath));
  461. try {
  462. let dark_uri = gsettings.get_string('picture-uri-dark');
  463. gsettings.set_string('picture-uri-dark', dark_uri.replace(oldPath, newPath));
  464. }
  465. catch (e) {
  466. BingLog('no dark background gsettings key found ('+e+')');
  467. }
  468. Gio.Settings.sync();
  469. gsettings.apply();
  470. }
  471. export function BingLog(msg) {
  472. if (debug)
  473. print("BingWallpaper extension: " + msg); // disable to keep the noise down in journal
  474. }
  475. export function deleteImage(to_delete) {
  476. var file = Gio.file_new_for_path(to_delete);
  477. if (file.query_exists(null)) {
  478. try {
  479. file.delete(null);
  480. BingLog("deleted file: " + to_delete);
  481. }
  482. catch (error) {
  483. BingLog("an error occured deleting " + to_delete + " : " + error);
  484. }
  485. }
  486. }
  487. // optionally purge trashed images (default is not, these just don't get select in random mode), optionally purge older images
  488. export function purgeImages(settings) {
  489. let deleteprevious = settings.get_boolean('delete-previous');
  490. let keepfavourites = settings.get_boolean('keep-favourites');
  491. let emptytrash = settings.get_boolean('trash-deletes-images');
  492. let maxDays = settings.get_int('previous-days');
  493. BingLog('purgeImages() dp: '+(deleteprevious?'true':'false')+'days:'+maxDays+' favs: '+(keepfavourites?'true':'false')+' trash: '+(emptytrash?'true':'false'));
  494. /*if (deleteprevious === false)
  495. return;*/
  496. let imagelist = imageListSortByDate(getImageList(settings));
  497. let origlength = imagelist.length;
  498. let cutOff = GLib.DateTime.new_now_utc().add_days(-maxDays); // 8 days ago
  499. let newList = [];
  500. imagelist.forEach( function (image, i) {
  501. var diff = dateFromLongDate(image.fullstartdate, 0).difference(cutOff); // relative age of image, < 0 we can delete
  502. // always keep favourites, keep images that are less than minimum period (previous days) or if clean up delete previous is disabled (default)
  503. var keep_image = (keepfavourites && image.favourite && image.favourite === true) || diff > 0 || !deleteprevious;
  504. var ok_to_delete = !keep_image || (emptytrash && image.hidden);
  505. var imageFilename = imageToFilename(settings, image);
  506. if (emptytrash && image.hidden && diff < 0)
  507. ok_to_delete = true;
  508. if (deleteprevious && image != '' && ok_to_delete) {
  509. BingLog('deleting '+imageFilename);
  510. deleteImage(imageFilename);
  511. }
  512. else {
  513. BingLog('keeping '+imageFilename);
  514. newList.push(image);
  515. }
  516. });
  517. setImageList(settings, newList);
  518. BingLog('cleaned up image list, count was '+origlength+' now '+imagelist.length);
  519. //cleanupImageList(settings);
  520. validate_imagename(settings); // if we deleted our current image, we want to reset it to something valid
  521. }
  522. export function openInSystemViewer(filename, is_file = true) {
  523. let context;
  524. try {
  525. context = global.create_app_launch_context(0, -1);
  526. }
  527. catch (error) {
  528. context = null;
  529. }
  530. if (is_file)
  531. filename = 'file://'+filename;
  532. Gio.AppInfo.launch_default_for_uri(filename, context);
  533. }
  534. export function exportBingJSON(settings) {
  535. let json = settings.get_string('bing-json');
  536. let filepath = getWallpaperDir(settings) + 'bing.json';
  537. let file = Gio.file_new_for_path(filepath);
  538. let [success, error] = file.replace_contents(json, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
  539. if (!success) {
  540. BingLog('error saving bing-json from '+filepath+': '+error);
  541. }
  542. }
  543. export function importBingJSON(settings) {
  544. const decoder = new TextDecoder();
  545. let filepath = getWallpaperDir(settings) + 'bing.json';
  546. let file = Gio.file_new_for_path(filepath);
  547. if (file.query_exists(null)) {
  548. let [success, contents, etag_out] = file.load_contents(null);
  549. if (!success) {
  550. BingLog('error loading bing-json '+filepath+' - '+etag_out);
  551. }
  552. else {
  553. BingLog('JSON import success');
  554. let parsed = JSON.parse(decoder.decode(contents)); // FIXME: triggers GJS warning without the conversion, need to investigate
  555. // need to implement some checks for validity here
  556. mergeImageLists(settings, parsed);
  557. purgeImages(settings); // remove the older missing images
  558. //cleanupImageList(settings);
  559. }
  560. }
  561. else {
  562. BingLog('JSON import file not found');
  563. }
  564. }
  565. export function getFileDimensions(filepath) {
  566. let format, width, height;
  567. try {
  568. [format, width, height] = GdkPixbuf.Pixbuf.get_file_info(filepath);
  569. return [width, height];
  570. }
  571. catch (e) {
  572. BingLog('unable to getFileDimensions('+filepath+') '+e);
  573. return [null, null];
  574. }
  575. }
  576. export function toFilename(wallpaperDir, startdate, imageURL, resolution) {
  577. return wallpaperDir + startdate + '-' + imageURL.replace(/^.*[\\\/]/, '').replace('th?id=OHR.', '') + '_' + resolution + '.jpg';
  578. }