LinksCache.jsm 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const EXPORTED_SYMBOLS = ["LinksCache"];
  6. // This should be slightly less than SYSTEM_TICK_INTERVAL as timer
  7. // comparisons are too exact while the async/await functionality will make the
  8. // last recorded time a little bit later. This causes the comparasion to skip
  9. // updates.
  10. // It should be 10% less than SYSTEM_TICK to update at least once every 5 mins.
  11. // https://github.com/mozilla/activity-stream/pull/3695#discussion_r144678214
  12. const EXPIRATION_TIME = 4.5 * 60 * 1000; // 4.5 minutes
  13. /**
  14. * Cache link results from a provided object property and refresh after some
  15. * amount of time has passed. Allows for migrating data from previously cached
  16. * links to the new links with the same url.
  17. */
  18. this.LinksCache = class LinksCache {
  19. /**
  20. * Create a links cache for a given object property.
  21. *
  22. * @param {object} linkObject Object containing the link property
  23. * @param {string} linkProperty Name of property on object to access
  24. * @param {array} properties Optional properties list to migrate to new links.
  25. * @param {function} shouldRefresh Optional callback receiving the old and new
  26. * options to refresh even when not expired.
  27. */
  28. constructor(linkObject, linkProperty, properties = [], shouldRefresh = () => {}) {
  29. this.clear();
  30. // Allow getting links from both methods and array properties
  31. this.linkGetter = options => {
  32. const ret = linkObject[linkProperty];
  33. return typeof ret === "function" ? ret.call(linkObject, options) : ret;
  34. };
  35. // Always migrate the shared cache data in addition to any custom properties
  36. this.migrateProperties = ["__sharedCache", ...properties];
  37. this.shouldRefresh = shouldRefresh;
  38. }
  39. /**
  40. * Clear the cached data.
  41. */
  42. clear() {
  43. this.cache = Promise.resolve([]);
  44. this.lastOptions = {};
  45. this.expire();
  46. }
  47. /**
  48. * Force the next request to update the cache.
  49. */
  50. expire() {
  51. delete this.lastUpdate;
  52. }
  53. /**
  54. * Request data and update the cache if necessary.
  55. *
  56. * @param {object} options Optional data to pass to the underlying method.
  57. * @returns {promise(array)} Links array with objects that can be modified.
  58. */
  59. async request(options = {}) {
  60. // Update the cache if the data has been expired
  61. const now = Date.now();
  62. if (this.lastUpdate === undefined ||
  63. now > this.lastUpdate + EXPIRATION_TIME ||
  64. // Allow custom rules around refreshing based on options
  65. this.shouldRefresh(this.lastOptions, options)) {
  66. // Update request state early so concurrent requests can refer to it
  67. this.lastOptions = options;
  68. this.lastUpdate = now;
  69. // Save a promise before awaits, so other requests wait for correct data
  70. this.cache = new Promise(async resolve => {
  71. // Allow fast lookup of old links by url that might need to migrate
  72. const toMigrate = new Map();
  73. for (const oldLink of await this.cache) {
  74. if (oldLink) {
  75. toMigrate.set(oldLink.url, oldLink);
  76. }
  77. }
  78. // Update the cache with migrated links without modifying source objects
  79. resolve((await this.linkGetter(options)).map(link => {
  80. // Keep original array hole positions
  81. if (!link) {
  82. return link;
  83. }
  84. // Migrate data to the new link copy if we have an old link
  85. const newLink = Object.assign({}, link);
  86. const oldLink = toMigrate.get(newLink.url);
  87. if (oldLink) {
  88. for (const property of this.migrateProperties) {
  89. const oldValue = oldLink[property];
  90. if (oldValue !== undefined) {
  91. newLink[property] = oldValue;
  92. }
  93. }
  94. } else {
  95. // Share data among link copies and new links from future requests
  96. newLink.__sharedCache = {};
  97. }
  98. // Provide a helper to update the cached link
  99. newLink.__sharedCache.updateLink = (property, value) => {
  100. newLink[property] = value;
  101. };
  102. return newLink;
  103. }));
  104. });
  105. }
  106. // Provide a shallow copy of the cached link objects for callers to modify
  107. return (await this.cache).map(link => link && Object.assign({}, link));
  108. }
  109. };