medias.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. class Media {
  2. constructor(data) {
  3. this.date = new Date(data.date);
  4. this.md5sum = data.md5sum;
  5. this.fixedSum = data.fixedSum;
  6. this.path = data.path;
  7. this.fileName = data.fileName;
  8. this.meta = data.meta || {};
  9. this.fixedTags = [];
  10. this.version = data.version;
  11. this.tags = [];
  12. this.writeAccess = data.writeAccess !== undefined ? data.writeAccess : (data.accessType === 2);
  13. this.thumbnail = `/api/media/thumbnail/${data.fixedSum}.jpg`;
  14. this.original = `/api/media/original/${data.fixedSum}`;
  15. this.ui = null;
  16. this.setTags(data.fixedTags || [], data.tags || []);
  17. for (let i in this.meta) {
  18. if (this.meta[i].type === 'date')
  19. this.meta[i].value = new Date(parseInt(this.meta[i].value));
  20. else if (this.meta[i].type === 'number' || this.meta[i].type === 'octet')
  21. this.meta[i].value = parseInt(this.meta[i].value);
  22. else if (this.meta[i].type === 'string')
  23. this.meta[i].value = '' + this.meta[i].value;
  24. }
  25. }
  26. resize(maxWidth, maxHeight) {
  27. let ratio = Math.min(1, Math.max(
  28. maxWidth / (this.meta?.width?.value || maxWidth),
  29. maxHeight / (this.meta?.height?.value || maxHeight)));
  30. let result = {
  31. width: Math.floor(this.meta.width?.value *ratio),
  32. height: Math.floor(this.meta.height?.value *ratio),
  33. };
  34. if (isNaN(result.width) || isNaN(result.height) || !result.height || !result.width) {
  35. console.error("Failed to resize image ", this);
  36. return null;
  37. }
  38. return result;
  39. }
  40. setTags(fixedTags, tags) {
  41. this.tags = tags.reduce((acc, tag) => { acc.add(tag.replaceAll(/\/\/+/gi, '/')); return acc; }, new Set());
  42. this.fixedTags = fixedTags.reduce((acc, tag) => { acc.add(tag.replaceAll(/\/\/+/gi, '/')); return acc; }, new Set());
  43. }
  44. allTags() {
  45. return Array.from(new Set([...this.fixedTags, ...this.tags])).sort();
  46. }
  47. }
  48. function tryLoadMedia(md5sum) {
  49. return new Promise((ok, ko) => {
  50. $.get("/api/media/" +md5sum, data => {
  51. let item = new Media(data);
  52. MediaStorage.Instance.pushAll([item], true);
  53. ok(item);
  54. }).fail(err => {
  55. console.error("Trying to get media with md5sum " +md5sum +" failed:", err.responseText);
  56. ok(null);
  57. });
  58. });
  59. }
  60. class MediaStorage extends EventTarget
  61. {
  62. allMeta = {};
  63. allMetaTypes = {};
  64. allTags = new Set();
  65. medias = [];
  66. oldest = null;
  67. newest = null;
  68. #dbVersion = 0;
  69. loadingVersion = 0;
  70. constructor() {
  71. super();
  72. this.#reset();
  73. }
  74. #reset() {
  75. this.allMeta = {};
  76. this.allMetaTypes = {};
  77. this.allTags = new Set();
  78. this.medias = [];
  79. this.oldest = null;
  80. this.newest = null;
  81. this.#dbVersion = 0;
  82. this.loadingVersion = 0;
  83. }
  84. async rebuildMetaList() {
  85. this.#reset();
  86. await window.indexedData.clean();
  87. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  88. window.chronology.reset();
  89. this.downloadMetaList();
  90. }
  91. update() {
  92. this.downloadMetaList(true);
  93. }
  94. async restoreMetaList() {
  95. if (this.isLoading())
  96. return;
  97. this.#isLoading = true;
  98. document.getElementById("pch-infiniteScrollLoading").classList.remove("hidden");
  99. let hasData = false;
  100. try {
  101. await LoadingTasks.push(async () => {
  102. let data = await window.indexedData.listMedias();
  103. this.pushAll(data.medias.map(x => new Media(x)));
  104. this.#updateDbVersion(data.version);
  105. this.#isLoading = false;
  106. hasData = !!(data.medias?.length);
  107. });
  108. } catch (err) {
  109. console.error(err);
  110. }
  111. this.downloadMetaList(hasData);
  112. }
  113. #doDownloadMetaList(isUpdate) {
  114. this.#isLoading = true;
  115. document.getElementById("pch-infiniteScrollLoading").classList.remove("hidden");
  116. LoadingTasks.push(() => {
  117. return new Promise(ok => {
  118. let chronology = window.chronology.isInitialized() ? "" : "&chronology"
  119. let oldest = isUpdate !== true ? (this.oldest?.date?.getTime() || 0) : 0;
  120. let oldestArg = oldest ? `&from=${oldest}` : "";
  121. let requestCount = 300;
  122. $.get(`/api/media/list?count=${requestCount}${chronology}${oldestArg}&version=${this.#dbVersion}`, data => {
  123. this.pushAll(data.data.map(i => new Media(i)));
  124. if (data.first || data.last)
  125. window.chronology.rebuildRange(data.first, data.last);
  126. if ((data.data?.length || 0) < requestCount) {
  127. this.#isLoading = false;
  128. document.getElementById("pch-infiniteScrollLoading").classList.add("hidden");
  129. this.#updateDbVersion(this.loadingVersion);
  130. window.ReloadFilters(MediaStorage.Instance);
  131. this.dispatchEvent(new CustomEvent("doneLoading"));
  132. } else {
  133. this.#doDownloadMetaList(isUpdate);
  134. }
  135. ok();
  136. });
  137. });
  138. });
  139. }
  140. downloadMetaList(isUpdate) {
  141. if (this.isLoading())
  142. return;
  143. this.#doDownloadMetaList(isUpdate);
  144. }
  145. getDbVersion() { return this.#dbVersion; }
  146. #updateDbVersion(version) {
  147. this.#dbVersion = Math.max(this.#dbVersion, version);
  148. }
  149. #pushMeta(metaKey, metaVal) {
  150. if (metaKey === 'dateTime')
  151. return;
  152. if (!this.allMeta[metaKey])
  153. this.allMeta[metaKey] = new Set();
  154. this.allMeta[metaKey].add(metaVal.value);
  155. if (!this.allMetaTypes[metaKey])
  156. this.allMetaTypes[metaKey] = { type: metaVal.type, canBeEmpty: !!this.medias.length, canWrite: metaVal.canWrite };
  157. }
  158. #pushTag(tag, first) {
  159. while (tag.length && tag.endsWith('/'))
  160. tag = tag.substr(0, tag.length -1);
  161. this.allTags.add(tag);
  162. let index = tag.lastIndexOf('/');
  163. if (index >= 0)
  164. this.#pushTag(tag.substr(0, index));
  165. }
  166. #pushUnique(media) {
  167. for (let i of media.tags)
  168. this.#pushTag(i, true);
  169. for (let i of media.fixedTags)
  170. this.#pushTag(i, true);
  171. for (let key in media.meta)
  172. this.#pushMeta(key, media.meta[key]);
  173. for (let key in this.allMetaTypes)
  174. if (!media.meta[key])
  175. this.allMetaTypes[key].canBeEmpty = true;
  176. this.medias.push(media);
  177. }
  178. #isLoading = false;
  179. isLoading() { return this.#isLoading; }
  180. pushAll(arr, partialLoad) {
  181. let reorder = false;
  182. let newItems = [];
  183. for (let i of arr) {
  184. this.loadingVersion = Math.max(this.loadingVersion, i.version);
  185. if (partialLoad !== true) {
  186. this.oldest = !this.oldest || this.oldest.date.getTime() > i.date.getTime() ? i : this.oldest;
  187. this.newest = !this.newest || this.newest.date.getTime() < i.date.getTime() ? i : this.newest;
  188. }
  189. if (this.medias.length && this.medias[this.medias.length -1].date.getTime() < i.date.getTime())
  190. reorder = true;
  191. let previous = this.medias.find(x => x.fixedSum === i.fixedSum);
  192. if (previous) {
  193. this.medias = this.medias.filter(x => x.fixedSum !== i.fixedSum);
  194. i.ui = previous.ui;
  195. } else {
  196. newItems.push(i);
  197. }
  198. this.#pushUnique(i);
  199. }
  200. for (let i of newItems)
  201. this.dispatchEvent(new CustomEvent("newMedia", { detail: i }));
  202. if (reorder) {
  203. this.medias.sort((a, b) => b.date.getTime() - a.date.getTime());
  204. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  205. }
  206. }
  207. onFilterUpdated() {
  208. this.dispatchEvent(new CustomEvent("rebuildMedia"));
  209. }
  210. getMediaIndex(media) {
  211. return this.medias.indexOf(media);
  212. }
  213. nextMedia(current) {
  214. return this.medias[this.getMediaIndex(current) +1];
  215. }
  216. previousMedia(current) {
  217. return this.medias[this.getMediaIndex(current) -1];
  218. }
  219. getMediaBetweenIndexes(a, b) {
  220. if (a > b)
  221. return this.getMediaBetweenIndexes(b, a);
  222. return this.medias.slice(a, b +1);
  223. }
  224. getMediaBetween(a, b) {
  225. let aIndex = this.medias.indexOf(a);
  226. let bIndex = this.medias.indexOf(b);
  227. if (aIndex < 0 || bIndex < 0 || aIndex === bIndex)
  228. return [];
  229. return this.getMediaBetweenIndexes(aIndex, bIndex);
  230. }
  231. getMediaLocal(md5sum) {
  232. return this.medias.find(x => x.fixedSum === md5sum);
  233. }
  234. async getMedia(md5sum) {
  235. let media = this.medias.find(x => x.fixedSum === md5sum);
  236. if (media)
  237. return media;
  238. return await tryLoadMedia(md5sum);
  239. }
  240. setMetaValue(md5sum, key, value) {
  241. let md5arr = undefined;
  242. if (Array.isArray(md5sum)) {
  243. md5arr = md5sum;
  244. md5sum = "list";
  245. }
  246. return LoadingTasks.push(() => {
  247. return new Promise(ok => {
  248. let mediaCount = (md5arr || [ md5sum ]).map(checksum => this.medias.find(x => x.fixedSum === checksum)).filter(x => x.writeAccess).length;
  249. if (mediaCount != (md5arr || [ md5sum ]).length)
  250. return ok(false);
  251. $.ajax({
  252. url: `/api/media/${encodeURIComponent(md5sum)}/meta/${encodeURIComponent(key)}`,
  253. type: "PATCH",
  254. data: { value: value, list: md5arr },
  255. success: allData => {
  256. allData.forEach(data => {
  257. let media = this.medias.find(x => x.fixedSum === data.fixedSum);
  258. let meta = data.meta[key] || { type: 'string', value: value, canWrite: true };
  259. meta.value = value;
  260. this.#pushMeta(key, meta);
  261. media.meta[key] = meta;
  262. });
  263. window.ReloadFilters(this);
  264. ok(true);
  265. },
  266. error: err => ok(false),
  267. });
  268. });
  269. });
  270. }
  271. removeTag(md5sum, tagName) {
  272. let md5arr = undefined;
  273. if (Array.isArray(md5sum)) {
  274. md5arr = md5sum;
  275. md5sum = "list";
  276. }
  277. return LoadingTasks.push(() => {
  278. return new Promise(ok => {
  279. let mediaCount = (md5arr || [ md5sum ]).map(checksum => this.medias.find(x => x.fixedSum === checksum)).filter(x => x.writeAccess).length;
  280. if (mediaCount != (md5arr || [ md5sum ]).length)
  281. return ok(false);
  282. $.ajax({
  283. url: `/api/media/${encodeURIComponent(md5sum)}/tag/del/${encodeURIComponent(tagName)}`,
  284. type: "POST",
  285. data: { list: md5arr || [0] },
  286. success: allData => {
  287. allData.forEach(data => {
  288. let media = this.medias.find(x => x.fixedSum === data.fixedSum);
  289. media.setTags(data.fixedTags, data.tags);
  290. for (let i of data.tags)
  291. this.#pushTag(i, true);
  292. for (let i of data.fixedTags)
  293. this.#pushTag(i, true);
  294. });
  295. ok(true);
  296. },
  297. error: err => ok(false),
  298. });
  299. });
  300. });
  301. }
  302. addTag(md5sum, tagName) {
  303. let md5arr = undefined;
  304. if (Array.isArray(md5sum)) {
  305. md5arr = md5sum;
  306. md5sum = "list";
  307. }
  308. return LoadingTasks.push(() => {
  309. return new Promise(ok => {
  310. let mediaCount = (md5arr || [ md5sum ]).map(checksum => this.medias.find(x => x.fixedSum === checksum)).filter(x => x.writeAccess).length;
  311. if (mediaCount != (md5arr || [ md5sum ]).length)
  312. return ok(false);
  313. $.ajax({
  314. url: `/api/media/${encodeURIComponent(md5sum)}/tag`,
  315. type: "PUT",
  316. data: { tag: tagName, list: md5arr },
  317. success: allData => {
  318. allData.forEach(data => {
  319. let media = this.medias.find(x => x.fixedSum === data.fixedSum);
  320. media.setTags(data.fixedTags, data.tags);
  321. for (let i of data.tags)
  322. this.#pushTag(i, true);
  323. for (let i of data.fixedTags)
  324. this.#pushTag(i, true);
  325. });
  326. ok(true);
  327. },
  328. error: err => ok(false),
  329. });
  330. });
  331. });
  332. }
  333. }
  334. MediaStorage.Instance = new MediaStorage();
  335. setInterval(MediaStorage.Instance.update.bind(MediaStorage.Instance), 60000);