You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
4.8 KiB

  1. import asyncDB from './db';
  2. import { autoPlayGif } from '../initial_state';
  3. const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static'];
  4. const avatarKey = autoPlayGif ? 'avatar' : 'avatar_static';
  5. const limit = 1024;
  6. // ServiceWorker and Cache API is not available on iOS 11
  7. // https://webkit.org/status/#specification-service-workers
  8. const asyncCache = window.caches ? caches.open('mastodon-system') : Promise.reject();
  9. function printErrorIfAvailable(error) {
  10. if (error) {
  11. console.warn(error);
  12. }
  13. }
  14. function put(name, objects, onupdate, oncreate) {
  15. return asyncDB.then(db => new Promise((resolve, reject) => {
  16. const putTransaction = db.transaction(name, 'readwrite');
  17. const putStore = putTransaction.objectStore(name);
  18. const putIndex = putStore.index('id');
  19. objects.forEach(object => {
  20. putIndex.getKey(object.id).onsuccess = retrieval => {
  21. function addObject() {
  22. putStore.add(object);
  23. }
  24. function deleteObject() {
  25. putStore.delete(retrieval.target.result).onsuccess = addObject;
  26. }
  27. if (retrieval.target.result) {
  28. if (onupdate) {
  29. onupdate(object, retrieval.target.result, putStore, deleteObject);
  30. } else {
  31. deleteObject();
  32. }
  33. } else {
  34. if (oncreate) {
  35. oncreate(object, addObject);
  36. } else {
  37. addObject();
  38. }
  39. }
  40. };
  41. });
  42. putTransaction.oncomplete = () => {
  43. const readTransaction = db.transaction(name, 'readonly');
  44. const readStore = readTransaction.objectStore(name);
  45. const count = readStore.count();
  46. count.onsuccess = () => {
  47. const excess = count.result - limit;
  48. if (excess > 0) {
  49. const retrieval = readStore.getAll(null, excess);
  50. retrieval.onsuccess = () => resolve(retrieval.result);
  51. retrieval.onerror = reject;
  52. } else {
  53. resolve([]);
  54. }
  55. };
  56. count.onerror = reject;
  57. };
  58. putTransaction.onerror = reject;
  59. }));
  60. }
  61. function evictAccountsByRecords(records) {
  62. asyncDB.then(db => {
  63. const transaction = db.transaction(['accounts', 'statuses'], 'readwrite');
  64. const accounts = transaction.objectStore('accounts');
  65. const accountsIdIndex = accounts.index('id');
  66. const accountsMovedIndex = accounts.index('moved');
  67. const statuses = transaction.objectStore('statuses');
  68. const statusesIndex = statuses.index('account');
  69. function evict(toEvict) {
  70. toEvict.forEach(record => {
  71. asyncCache
  72. .then(cache => accountAssetKeys.forEach(key => cache.delete(records[key])))
  73. .catch(printErrorIfAvailable);
  74. accountsMovedIndex.getAll(record.id).onsuccess = ({ target }) => evict(target.result);
  75. statusesIndex.getAll(record.id).onsuccess =
  76. ({ target }) => evictStatusesByRecords(target.result);
  77. accountsIdIndex.getKey(record.id).onsuccess =
  78. ({ target }) => target.result && accounts.delete(target.result);
  79. });
  80. }
  81. evict(records);
  82. }).catch(printErrorIfAvailable);
  83. }
  84. export function evictStatus(id) {
  85. evictStatuses([id]);
  86. }
  87. export function evictStatuses(ids) {
  88. asyncDB.then(db => {
  89. const store = db.transaction('statuses', 'readwrite').objectStore('statuses');
  90. const idIndex = store.index('id');
  91. const reblogIndex = store.index('reblog');
  92. ids.forEach(id => {
  93. reblogIndex.getAllKeys(id).onsuccess =
  94. ({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey));
  95. idIndex.getKey(id).onsuccess =
  96. ({ target }) => target.result && store.delete(target.result);
  97. });
  98. }).catch(printErrorIfAvailable);
  99. }
  100. function evictStatusesByRecords(records) {
  101. evictStatuses(records.map(({ id }) => id));
  102. }
  103. export function putAccounts(records) {
  104. const newURLs = [];
  105. put('accounts', records, (newRecord, oldKey, store, oncomplete) => {
  106. store.get(oldKey).onsuccess = ({ target }) => {
  107. accountAssetKeys.forEach(key => {
  108. const newURL = newRecord[key];
  109. const oldURL = target.result[key];
  110. if (newURL !== oldURL) {
  111. asyncCache
  112. .then(cache => cache.delete(oldURL))
  113. .catch(printErrorIfAvailable);
  114. }
  115. });
  116. const newURL = newRecord[avatarKey];
  117. const oldURL = target.result[avatarKey];
  118. if (newURL !== oldURL) {
  119. newURLs.push(newURL);
  120. }
  121. oncomplete();
  122. };
  123. }, (newRecord, oncomplete) => {
  124. newURLs.push(newRecord[avatarKey]);
  125. oncomplete();
  126. }).then(records => {
  127. evictAccountsByRecords(records);
  128. asyncCache
  129. .then(cache => cache.addAll(newURLs))
  130. .catch(printErrorIfAvailable);
  131. }).catch(printErrorIfAvailable);
  132. }
  133. export function putStatuses(records) {
  134. put('statuses', records)
  135. .then(evictStatusesByRecords)
  136. .catch(printErrorIfAvailable);
  137. }