diff --git a/src/components/frame/Photo.vue b/src/components/frame/Photo.vue
index 0aae6201..ffd89020 100644
--- a/src/components/frame/Photo.vue
+++ b/src/components/frame/Photo.vue
@@ -44,8 +44,8 @@
@touchend.passive="$emit('touchend', $event)"
@touchcancel.passive="$emit('touchend', $event)"
>
-
\ No newline at end of file
+
diff --git a/src/components/frame/XImg.vue b/src/components/frame/XImg.vue
new file mode 100644
index 00000000..99fe7462
--- /dev/null
+++ b/src/components/frame/XImg.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
diff --git a/src/service-worker-custom.ts b/src/components/frame/XImgCache.ts
similarity index 55%
rename from src/service-worker-custom.ts
rename to src/components/frame/XImgCache.ts
index 45e8b52d..1abaef50 100644
--- a/src/service-worker-custom.ts
+++ b/src/components/frame/XImgCache.ts
@@ -1,8 +1,10 @@
-import { registerRoute } from "workbox-routing";
import { CacheExpiration } from "workbox-expiration";
+import { API } from "../../services/API";
+import axios from "@nextcloud/axios";
// Queue of requests to fetch preview images
interface FetchPreviewObject {
+ origUrl: string;
url: URL;
fileid: number;
reqid: number;
@@ -35,7 +37,7 @@ async function flushPreviewQueue() {
// Check if only one request
if (fetchPreviewQueueCopy.length === 1) {
const p = fetchPreviewQueueCopy[0];
- return p.callback(await fetch(p.url));
+ return p.callback(await fetchOneImage(p.origUrl));
}
// Create aggregated request body
@@ -48,26 +50,15 @@ async function flushPreviewQueue() {
}));
try {
- // infer the url from the first file
- const firstUrl = fetchPreviewQueueCopy[0].url;
- const url = new URL(firstUrl.toString());
- const path = url.pathname.split("/");
- const previewIndex = path.indexOf("preview");
- url.pathname = path.slice(0, previewIndex).join("/") + "/multipreview";
- url.searchParams.delete("x");
- url.searchParams.delete("y");
- url.searchParams.delete("a");
- url.searchParams.delete("c");
-
// Fetch multipreview
- const res = await fetch(url, {
- method: "POST",
- body: JSON.stringify(files),
+ const multiUrl = API.IMAGE_MULTIPREVIEW();
+ const res = await axios.post(multiUrl, files, {
+ responseType: "blob",
});
// Get blob
if (res.status !== 200) throw new Error("Error fetching multi-preview");
- const blob = await res.blob();
+ const blob = res.data;
let idx = 0;
while (idx < blob.size) {
@@ -80,8 +71,6 @@ async function flushPreviewQueue() {
const reqid = jsonParsed["reqid"];
idx += newlineIndex + 1;
- console.debug("multi-preview", jsonParsed);
-
// Read the image data
const imgBlob = blob.slice(idx, idx + imgLen);
idx += imgLen;
@@ -91,14 +80,11 @@ async function flushPreviewQueue() {
.filter((p) => p.reqid === reqid)
.forEach((p) => {
p.callback(
- new Response(imgBlob, {
- status: 200,
- headers: {
- "Content-Type": imgType,
- "Content-Length": imgLen,
- Expires: res.headers.get("Expires"),
- "Cache-Control": res.headers.get("Cache-Control"),
- },
+ getResponse(imgBlob, imgType, {
+ "Content-Type": imgType,
+ "Content-Length": imgLen,
+ "Cache-Control": res.headers["Cache-Control"],
+ Expires: res.headers["Expires"],
})
);
p.callback = null;
@@ -119,46 +105,70 @@ async function flushPreviewQueue() {
});
}
-// Intercept preview requests
-registerRoute(
- /^.*\/apps\/memories\/api\/image\/preview\/.*/,
- async ({ url, request }) => {
- // Check if in cache
- const cache = await imageCache?.match(url);
- if (cache) return cache;
+/** Accepts a URL and returns a promise with a blob */
+export async function fetchImage(url: string): Promise {
+ // Check if in cache
+ const cache = await imageCache?.match(url);
+ if (cache) return await cache.blob();
- // Get file id from URL
- const fileid = Number(url.pathname.split("/").pop());
+ // Get file id from URL
+ const urlObj = new URL(url, window.location.origin);
+ const fileid = Number(urlObj.pathname.split("/").pop());
- // Aggregate requests
- let res: Response = await new Promise((callback) => {
+ // Check if preview image
+ const regex = /^.*\/apps\/memories\/api\/image\/preview\/.*/;
+
+ // Aggregate requests
+ let res: Response;
+
+ if (regex.test(url)) {
+ res = await new Promise((callback) => {
fetchPreviewQueue.push({
- url,
+ origUrl: url,
+ url: urlObj,
fileid,
reqid: Math.random(),
callback,
});
if (!fetchPreviewTimer) {
- fetchPreviewTimer = setTimeout(flushPreviewQueue, 50);
+ fetchPreviewTimer = setTimeout(flushPreviewQueue, 10);
}
});
-
- // Fallback to single request
- if (res.status !== 200) {
- res = await fetch(url);
- }
-
- // Cache response
- if (res.status === 200) {
- imageCache?.put(request, res.clone());
- expirationManager.updateTimestamp(request.url);
- }
-
- // Run expiration once in every 20 requests
- if (Math.random() < 0.05) {
- expirationManager.expireEntries();
- }
-
- return res;
}
-);
+
+ // Fallback to single request
+ if (!res || res.status !== 200) {
+ res = await fetchOneImage(url);
+ }
+
+ // Cache response
+ if (res.status === 200) {
+ imageCache?.put(url, res.clone());
+ expirationManager.updateTimestamp(url.toString());
+ }
+
+ // Run expiration once in every 100 requests
+ if (Math.random() < 0.01) {
+ expirationManager.expireEntries();
+ }
+
+ return await res.blob();
+}
+
+export async function fetchOneImage(url: string) {
+ const res = await axios.get(url, {
+ responseType: "blob",
+ });
+ return getResponse(res.data, res.headers["content-type"], res.headers);
+}
+
+function getResponse(blob: Blob, type: string, headers: any = {}) {
+ return new Response(blob, {
+ status: 200,
+ headers: {
+ "Content-Type": type,
+ "Content-Length": blob.size.toString(),
+ ...headers,
+ },
+ });
+}
diff --git a/src/service-worker.js b/src/service-worker.js
index b17811ab..fefd8f29 100644
--- a/src/service-worker.js
+++ b/src/service-worker.js
@@ -5,10 +5,9 @@ import { ExpirationPlugin } from 'workbox-expiration';
precacheAndRoute(self.__WB_MANIFEST);
-import './service-worker-custom';
-
registerRoute(/^.*\/apps\/memories\/api\/video\/transcode\/.*/, new NetworkOnly());
registerRoute(/^.*\/apps\/memories\/api\/image\/jpeg\/.*/, new NetworkOnly());
+registerRoute(/^.*\/apps\/memories\/api\/image\/preview\/.*/, new NetworkOnly());
registerRoute(/^.*\/remote.php\/.*/, new NetworkOnly());
registerRoute(/^.*\/apps\/files\/ajax\/download.php?.*/, new NetworkOnly());
@@ -22,7 +21,6 @@ const imageCache = new CacheFirst({
],
});
-registerRoute(/^.*\/apps\/memories\/api\/image\/preview\/.*/, imageCache);
registerRoute(/^.*\/apps\/memories\/api\/video\/livephoto\/.*/, imageCache);
registerRoute(/^.*\/apps\/memories\/api\/faces\/preview\/.*/, imageCache);
registerRoute(/^.*\/apps\/memories\/api\/tags\/preview\/.*/, imageCache);
diff --git a/src/services/API.ts b/src/services/API.ts
index c110d8df..53cbd6ea 100644
--- a/src/services/API.ts
+++ b/src/services/API.ts
@@ -82,6 +82,10 @@ export class API {
return tok(gen(`${BASE}/image/preview/{fileid}`, { fileid }));
}
+ static IMAGE_MULTIPREVIEW() {
+ return tok(gen(`${BASE}/image/multipreview`));
+ }
+
static IMAGE_INFO(id: number) {
return tok(gen(`${BASE}/image/info/{id}`, { id }));
}