mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-06-29 20:53:04 +00:00
fix: abort ongoing requests when changing pages (#3927)
This commit is contained in:
parent
2d1a82b73f
commit
93c4b2e03c
@ -2,14 +2,22 @@ import { useAuthStore } from "@/stores/auth";
|
|||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { upload as postTus, useTus } from "./tus";
|
import { upload as postTus, useTus } from "./tus";
|
||||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
import { createURL, fetchURL, removePrefix, StatusError } from "./utils";
|
||||||
|
|
||||||
export async function fetch(url: string) {
|
export async function fetch(url: string, signal?: AbortSignal) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
const res = await fetchURL(`/api/resources${url}`, { signal });
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}`, {});
|
let data: Resource;
|
||||||
|
try {
|
||||||
const data = (await res.json()) as Resource;
|
data = (await res.json()) as Resource;
|
||||||
|
} catch (e) {
|
||||||
|
// Check if the error is an intentional cancellation
|
||||||
|
if (e instanceof Error && e.name === "AbortError") {
|
||||||
|
throw new StatusError("000 No connection", 0, true);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
data.url = `/files${url}`;
|
data.url = `/files${url}`;
|
||||||
|
|
||||||
if (data.isDir) {
|
if (data.isDir) {
|
||||||
@ -205,10 +213,18 @@ export function getSubtitlesURL(file: ResourceItem) {
|
|||||||
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function usage(url: string) {
|
export async function usage(url: string, signal: AbortSignal) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/usage${url}`, {});
|
const res = await fetchURL(`/api/usage${url}`, { signal });
|
||||||
|
|
||||||
return await res.json();
|
try {
|
||||||
|
return await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
// Check if the error is an intentional cancellation
|
||||||
|
if (e instanceof Error && e.name == "AbortError") {
|
||||||
|
throw new StatusError("000 No connection", 0, true);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import { encodePath } from "@/utils/url";
|
|||||||
export class StatusError extends Error {
|
export class StatusError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: any,
|
message: any,
|
||||||
public status?: number
|
public status?: number,
|
||||||
|
public is_canceled?: boolean
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "StatusError";
|
this.name = "StatusError";
|
||||||
@ -33,7 +34,11 @@ export async function fetchURL(
|
|||||||
},
|
},
|
||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
// Check if the error is an intentional cancellation
|
||||||
|
if (e instanceof Error && e.name === "AbortError") {
|
||||||
|
throw new StatusError("000 No connection", 0, true);
|
||||||
|
}
|
||||||
throw new StatusError("000 No connection", 0);
|
throw new StatusError("000 No connection", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ import {
|
|||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "@/components/ProgressBar.vue";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
|
import { StatusError } from "@/api/utils.js";
|
||||||
|
|
||||||
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ export default {
|
|||||||
name: "sidebar",
|
name: "sidebar",
|
||||||
setup() {
|
setup() {
|
||||||
const usage = reactive(USAGE_DEFAULT);
|
const usage = reactive(USAGE_DEFAULT);
|
||||||
return { usage };
|
return { usage, usageAbortController: new AbortController() };
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
@ -157,6 +158,9 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||||
|
abortOngoingFetchUsage() {
|
||||||
|
this.usageAbortController.abort();
|
||||||
|
},
|
||||||
async fetchUsage() {
|
async fetchUsage() {
|
||||||
const path = this.$route.path.endsWith("/")
|
const path = this.$route.path.endsWith("/")
|
||||||
? this.$route.path
|
? this.$route.path
|
||||||
@ -166,13 +170,18 @@ export default {
|
|||||||
return Object.assign(this.usage, usageStats);
|
return Object.assign(this.usage, usageStats);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const usage = await api.usage(path);
|
this.abortOngoingFetchUsage();
|
||||||
|
this.usageAbortController = new AbortController();
|
||||||
|
const usage = await api.usage(path, this.usageAbortController.signal);
|
||||||
usageStats = {
|
usageStats = {
|
||||||
used: prettyBytes(usage.used, { binary: true }),
|
used: prettyBytes(usage.used, { binary: true }),
|
||||||
total: prettyBytes(usage.total, { binary: true }),
|
total: prettyBytes(usage.total, { binary: true }),
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof StatusError && error.is_canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$showError(error);
|
this.$showError(error);
|
||||||
}
|
}
|
||||||
return Object.assign(this.usage, usageStats);
|
return Object.assign(this.usage, usageStats);
|
||||||
@ -200,5 +209,8 @@ export default {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.abortOngoingFetchUsage();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -31,6 +31,7 @@ import { useFileStore } from "@/stores/file";
|
|||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
|
import { StatusError } from "@/api/utils.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "file-list",
|
name: "file-list",
|
||||||
@ -43,6 +44,7 @@ export default {
|
|||||||
},
|
},
|
||||||
selected: null,
|
selected: null,
|
||||||
current: window.location.pathname,
|
current: window.location.pathname,
|
||||||
|
nextAbortController: new AbortController(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ["$showError"],
|
inject: ["$showError"],
|
||||||
@ -56,7 +58,13 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.fillOptions(this.req);
|
this.fillOptions(this.req);
|
||||||
},
|
},
|
||||||
|
unmounted() {
|
||||||
|
this.abortOngoingNext();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
abortOngoingNext() {
|
||||||
|
this.nextAbortController.abort();
|
||||||
|
},
|
||||||
fillOptions(req) {
|
fillOptions(req) {
|
||||||
// Sets the current path and resets
|
// Sets the current path and resets
|
||||||
// the current items.
|
// the current items.
|
||||||
@ -94,8 +102,17 @@ export default {
|
|||||||
// just clicked in and fill the options with its
|
// just clicked in and fill the options with its
|
||||||
// content.
|
// content.
|
||||||
const uri = event.currentTarget.dataset.url;
|
const uri = event.currentTarget.dataset.url;
|
||||||
|
this.abortOngoingNext();
|
||||||
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
this.nextAbortController = new AbortController();
|
||||||
|
files
|
||||||
|
.fetch(uri, this.nextAbortController.signal)
|
||||||
|
.then(this.fillOptions)
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof StatusError && e.is_canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$showError(e);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
touchstart(event) {
|
touchstart(event) {
|
||||||
const url = event.currentTarget.dataset.url;
|
const url = event.currentTarget.dataset.url;
|
||||||
|
1
frontend/src/types/api.d.ts
vendored
1
frontend/src/types/api.d.ts
vendored
@ -10,6 +10,7 @@ interface ApiOpts {
|
|||||||
method?: ApiMethod;
|
method?: ApiMethod;
|
||||||
headers?: object;
|
headers?: object;
|
||||||
body?: any;
|
body?: any;
|
||||||
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TusSettings {
|
interface TusSettings {
|
||||||
|
@ -61,9 +61,7 @@ const route = useRoute();
|
|||||||
|
|
||||||
const { t } = useI18n({});
|
const { t } = useI18n({});
|
||||||
|
|
||||||
const clean = (path: string) => {
|
let fetchDataController = new AbortController();
|
||||||
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
||||||
};
|
|
||||||
|
|
||||||
const error = ref<StatusError | null>(null);
|
const error = ref<StatusError | null>(null);
|
||||||
|
|
||||||
@ -101,6 +99,7 @@ onUnmounted(() => {
|
|||||||
layoutStore.toggleShell();
|
layoutStore.toggleShell();
|
||||||
}
|
}
|
||||||
fileStore.updateRequest(null);
|
fileStore.updateRequest(null);
|
||||||
|
fetchDataController.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(route, (to, from) => {
|
watch(route, (to, from) => {
|
||||||
@ -142,20 +141,21 @@ const fetchData = async () => {
|
|||||||
let url = route.path;
|
let url = route.path;
|
||||||
if (url === "") url = "/";
|
if (url === "") url = "/";
|
||||||
if (url[0] !== "/") url = "/" + url;
|
if (url[0] !== "/") url = "/" + url;
|
||||||
|
// Cancel the ongoing request
|
||||||
|
fetchDataController.abort();
|
||||||
|
fetchDataController = new AbortController();
|
||||||
try {
|
try {
|
||||||
const res = await api.fetch(url);
|
const res = await api.fetch(url, fetchDataController.signal);
|
||||||
|
|
||||||
if (clean(res.path) !== clean(`/${[...route.params.path].join("/")}`)) {
|
|
||||||
throw new Error("Data Mismatch!");
|
|
||||||
}
|
|
||||||
|
|
||||||
fileStore.updateRequest(res);
|
fileStore.updateRequest(res);
|
||||||
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
|
document.title = `${res.name || t("sidebar.myFiles")} - ${t("files.files")} - ${name}`;
|
||||||
|
layoutStore.loading = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof StatusError && err.is_canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
error.value = err;
|
error.value = err;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
layoutStore.loading = false;
|
layoutStore.loading = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user