You've already forked AstralRinth
forked from didirus/AstralRinth
refactor: migrate to common eslint+prettier configs (#4168)
* refactor: migrate to common eslint+prettier configs * fix: prettier frontend * feat: config changes * fix: lint issues * fix: lint * fix: type imports * fix: cyclical import issue * fix: lockfile * fix: missing dep * fix: switch to tabs * fix: continue switch to tabs * fix: rustfmt parity * fix: moderation lint issue * fix: lint issues * fix: ui intl * fix: lint issues * Revert "fix: rustfmt parity" This reverts commit cb99d2376c321d813d4b7fc7e2a213bb30a54711. * feat: revert last rs
This commit is contained in:
@@ -1,247 +1,248 @@
|
||||
import type {
|
||||
FileUploadQuery,
|
||||
JWTAuth,
|
||||
DirectoryResponse,
|
||||
FilesystemOp,
|
||||
FSQueuedOp,
|
||||
} from "@modrinth/utils";
|
||||
import { ModrinthServerError } from "@modrinth/utils";
|
||||
import { useServersFetch } from "../servers-fetch.ts";
|
||||
import { ServerModule } from "./base.ts";
|
||||
DirectoryResponse,
|
||||
FilesystemOp,
|
||||
FileUploadQuery,
|
||||
FSQueuedOp,
|
||||
JWTAuth,
|
||||
} from '@modrinth/utils'
|
||||
import { ModrinthServerError } from '@modrinth/utils'
|
||||
|
||||
import { useServersFetch } from '../servers-fetch.ts'
|
||||
import { ServerModule } from './base.ts'
|
||||
|
||||
export class FSModule extends ServerModule {
|
||||
auth!: JWTAuth;
|
||||
ops: FilesystemOp[] = [];
|
||||
queuedOps: FSQueuedOp[] = [];
|
||||
opsQueuedForModification: string[] = [];
|
||||
auth!: JWTAuth
|
||||
ops: FilesystemOp[] = []
|
||||
queuedOps: FSQueuedOp[] = []
|
||||
opsQueuedForModification: string[] = []
|
||||
|
||||
async fetch(): Promise<void> {
|
||||
this.auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`, {}, "fs");
|
||||
this.ops = [];
|
||||
this.queuedOps = [];
|
||||
this.opsQueuedForModification = [];
|
||||
}
|
||||
async fetch(): Promise<void> {
|
||||
this.auth = await useServersFetch<JWTAuth>(`servers/${this.serverId}/fs`, {}, 'fs')
|
||||
this.ops = []
|
||||
this.queuedOps = []
|
||||
this.opsQueuedForModification = []
|
||||
}
|
||||
|
||||
private async retryWithAuth<T>(
|
||||
requestFn: () => Promise<T>,
|
||||
ignoreFailure: boolean = false,
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
if (error instanceof ModrinthServerError && error.statusCode === 401) {
|
||||
console.debug("Auth failed, refreshing JWT and retrying");
|
||||
await this.fetch(); // Refresh auth
|
||||
return await requestFn();
|
||||
}
|
||||
private async retryWithAuth<T>(
|
||||
requestFn: () => Promise<T>,
|
||||
ignoreFailure: boolean = false,
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await requestFn()
|
||||
} catch (error) {
|
||||
if (error instanceof ModrinthServerError && error.statusCode === 401) {
|
||||
console.debug('Auth failed, refreshing JWT and retrying')
|
||||
await this.fetch() // Refresh auth
|
||||
return await requestFn()
|
||||
}
|
||||
|
||||
const available = await this.server.testNodeReachability();
|
||||
if (!available && !ignoreFailure) {
|
||||
this.server.moduleErrors.general = {
|
||||
error: new ModrinthServerError(
|
||||
"Unable to reach node. FS operation failed and subsequent ping test failed.",
|
||||
500,
|
||||
error as Error,
|
||||
"fs",
|
||||
),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
const available = await this.server.testNodeReachability()
|
||||
if (!available && !ignoreFailure) {
|
||||
this.server.moduleErrors.general = {
|
||||
error: new ModrinthServerError(
|
||||
'Unable to reach node. FS operation failed and subsequent ping test failed.',
|
||||
500,
|
||||
error as Error,
|
||||
'fs',
|
||||
),
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
listDirContents(
|
||||
path: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
ignoreFailure: boolean = false,
|
||||
): Promise<DirectoryResponse> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
return await useServersFetch(`/list?path=${encodedPath}&page=${page}&page_size=${pageSize}`, {
|
||||
override: this.auth,
|
||||
retry: false,
|
||||
});
|
||||
}, ignoreFailure);
|
||||
}
|
||||
listDirContents(
|
||||
path: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
ignoreFailure: boolean = false,
|
||||
): Promise<DirectoryResponse> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
return await useServersFetch(`/list?path=${encodedPath}&page=${page}&page_size=${pageSize}`, {
|
||||
override: this.auth,
|
||||
retry: false,
|
||||
})
|
||||
}, ignoreFailure)
|
||||
}
|
||||
|
||||
createFileOrFolder(path: string, type: "file" | "directory"): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
await useServersFetch(`/create?path=${encodedPath}&type=${type}`, {
|
||||
method: "POST",
|
||||
contentType: "application/octet-stream",
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
createFileOrFolder(path: string, type: 'file' | 'directory'): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
await useServersFetch(`/create?path=${encodedPath}&type=${type}`, {
|
||||
method: 'POST',
|
||||
contentType: 'application/octet-stream',
|
||||
override: this.auth,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
uploadFile(path: string, file: File): FileUploadQuery {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
const progressSubject = new EventTarget();
|
||||
const abortController = new AbortController();
|
||||
uploadFile(path: string, file: File): FileUploadQuery {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
const progressSubject = new EventTarget()
|
||||
const abortController = new AbortController()
|
||||
|
||||
const uploadPromise = new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
const uploadPromise = new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const progress = (e.loaded / e.total) * 100;
|
||||
progressSubject.dispatchEvent(
|
||||
new CustomEvent("progress", {
|
||||
detail: { loaded: e.loaded, total: e.total, progress },
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
xhr.upload.addEventListener('progress', (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const progress = (e.loaded / e.total) * 100
|
||||
progressSubject.dispatchEvent(
|
||||
new CustomEvent('progress', {
|
||||
detail: { loaded: e.loaded, total: e.total, progress },
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject(new Error(`Upload failed with status ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response)
|
||||
} else {
|
||||
reject(new Error(`Upload failed with status ${xhr.status}`))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => reject(new Error("Upload failed"));
|
||||
xhr.onabort = () => reject(new Error("Upload cancelled"));
|
||||
xhr.onerror = () => reject(new Error('Upload failed'))
|
||||
xhr.onabort = () => reject(new Error('Upload cancelled'))
|
||||
|
||||
xhr.open("POST", `https://${this.auth.url}/create?path=${encodedPath}&type=file`);
|
||||
xhr.setRequestHeader("Authorization", `Bearer ${this.auth.token}`);
|
||||
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
||||
xhr.send(file);
|
||||
xhr.open('POST', `https://${this.auth.url}/create?path=${encodedPath}&type=file`)
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${this.auth.token}`)
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream')
|
||||
xhr.send(file)
|
||||
|
||||
abortController.signal.addEventListener("abort", () => xhr.abort());
|
||||
});
|
||||
abortController.signal.addEventListener('abort', () => xhr.abort())
|
||||
})
|
||||
|
||||
return {
|
||||
promise: uploadPromise,
|
||||
onProgress: (
|
||||
callback: (progress: { loaded: number; total: number; progress: number }) => void,
|
||||
) => {
|
||||
progressSubject.addEventListener("progress", ((e: CustomEvent) => {
|
||||
callback(e.detail);
|
||||
}) as EventListener);
|
||||
},
|
||||
cancel: () => abortController.abort(),
|
||||
} as FileUploadQuery;
|
||||
}
|
||||
return {
|
||||
promise: uploadPromise,
|
||||
onProgress: (
|
||||
callback: (progress: { loaded: number; total: number; progress: number }) => void,
|
||||
) => {
|
||||
progressSubject.addEventListener('progress', ((e: CustomEvent) => {
|
||||
callback(e.detail)
|
||||
}) as EventListener)
|
||||
},
|
||||
cancel: () => abortController.abort(),
|
||||
} as FileUploadQuery
|
||||
}
|
||||
|
||||
renameFileOrFolder(path: string, name: string): Promise<void> {
|
||||
const pathName = path.split("/").slice(0, -1).join("/") + "/" + name;
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/move`, {
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
body: { source: path, destination: pathName },
|
||||
});
|
||||
});
|
||||
}
|
||||
renameFileOrFolder(path: string, name: string): Promise<void> {
|
||||
const pathName = path.split('/').slice(0, -1).join('/') + '/' + name
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/move`, {
|
||||
method: 'POST',
|
||||
override: this.auth,
|
||||
body: { source: path, destination: pathName },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateFile(path: string, content: string): Promise<void> {
|
||||
const octetStream = new Blob([content], { type: "application/octet-stream" });
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/update?path=${path}`, {
|
||||
method: "PUT",
|
||||
contentType: "application/octet-stream",
|
||||
body: octetStream,
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
updateFile(path: string, content: string): Promise<void> {
|
||||
const octetStream = new Blob([content], { type: 'application/octet-stream' })
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/update?path=${path}`, {
|
||||
method: 'PUT',
|
||||
contentType: 'application/octet-stream',
|
||||
body: octetStream,
|
||||
override: this.auth,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
moveFileOrFolder(path: string, newPath: string): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await this.server.createMissingFolders(newPath.substring(0, newPath.lastIndexOf("/")));
|
||||
await useServersFetch(`/move`, {
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
body: { source: path, destination: newPath },
|
||||
});
|
||||
});
|
||||
}
|
||||
moveFileOrFolder(path: string, newPath: string): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await this.server.createMissingFolders(newPath.substring(0, newPath.lastIndexOf('/')))
|
||||
await useServersFetch(`/move`, {
|
||||
method: 'POST',
|
||||
override: this.auth,
|
||||
body: { source: path, destination: newPath },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deleteFileOrFolder(path: string, recursive: boolean): Promise<void> {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/delete?path=${encodedPath}&recursive=${recursive}`, {
|
||||
method: "DELETE",
|
||||
override: this.auth,
|
||||
});
|
||||
});
|
||||
}
|
||||
deleteFileOrFolder(path: string, recursive: boolean): Promise<void> {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(`/delete?path=${encodedPath}&recursive=${recursive}`, {
|
||||
method: 'DELETE',
|
||||
override: this.auth,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
downloadFile(path: string, raw: boolean = false, ignoreFailure: boolean = false): Promise<any> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
const fileData = await useServersFetch(`/download?path=${encodedPath}`, {
|
||||
override: this.auth,
|
||||
});
|
||||
downloadFile(path: string, raw: boolean = false, ignoreFailure: boolean = false): Promise<any> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
const fileData = await useServersFetch(`/download?path=${encodedPath}`, {
|
||||
override: this.auth,
|
||||
})
|
||||
|
||||
if (fileData instanceof Blob) {
|
||||
return raw ? fileData : await fileData.text();
|
||||
}
|
||||
return fileData;
|
||||
}, ignoreFailure);
|
||||
}
|
||||
if (fileData instanceof Blob) {
|
||||
return raw ? fileData : await fileData.text()
|
||||
}
|
||||
return fileData
|
||||
}, ignoreFailure)
|
||||
}
|
||||
|
||||
extractFile(
|
||||
path: string,
|
||||
override = true,
|
||||
dry = false,
|
||||
silentQueue = false,
|
||||
): Promise<{ modpack_name: string | null; conflicting_files: string[] }> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
extractFile(
|
||||
path: string,
|
||||
override = true,
|
||||
dry = false,
|
||||
silentQueue = false,
|
||||
): Promise<{ modpack_name: string | null; conflicting_files: string[] }> {
|
||||
return this.retryWithAuth(async () => {
|
||||
const encodedPath = encodeURIComponent(path)
|
||||
|
||||
if (!silentQueue) {
|
||||
this.queuedOps.push({ op: "unarchive", src: path });
|
||||
setTimeout(() => this.removeQueuedOp("unarchive", path), 4000);
|
||||
}
|
||||
if (!silentQueue) {
|
||||
this.queuedOps.push({ op: 'unarchive', src: path })
|
||||
setTimeout(() => this.removeQueuedOp('unarchive', path), 4000)
|
||||
}
|
||||
|
||||
try {
|
||||
return await useServersFetch(
|
||||
`/unarchive?src=${encodedPath}&trg=/&override=${override}&dry=${dry}`,
|
||||
{
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
"Error extracting file",
|
||||
);
|
||||
} catch (err) {
|
||||
this.removeQueuedOp("unarchive", path);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
return await useServersFetch(
|
||||
`/unarchive?src=${encodedPath}&trg=/&override=${override}&dry=${dry}`,
|
||||
{
|
||||
method: 'POST',
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
'Error extracting file',
|
||||
)
|
||||
} catch (err) {
|
||||
this.removeQueuedOp('unarchive', path)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
modifyOp(id: string, action: "dismiss" | "cancel"): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(
|
||||
`/ops/${action}?id=${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
`Error ${action === "dismiss" ? "dismissing" : "cancelling"} filesystem operation`,
|
||||
);
|
||||
modifyOp(id: string, action: 'dismiss' | 'cancel'): Promise<void> {
|
||||
return this.retryWithAuth(async () => {
|
||||
await useServersFetch(
|
||||
`/ops/${action}?id=${id}`,
|
||||
{
|
||||
method: 'POST',
|
||||
override: this.auth,
|
||||
version: 1,
|
||||
},
|
||||
undefined,
|
||||
`Error ${action === 'dismiss' ? 'dismissing' : 'cancelling'} filesystem operation`,
|
||||
)
|
||||
|
||||
this.opsQueuedForModification = this.opsQueuedForModification.filter((x: string) => x !== id);
|
||||
this.ops = this.ops.filter((x: FilesystemOp) => x.id !== id);
|
||||
});
|
||||
}
|
||||
this.opsQueuedForModification = this.opsQueuedForModification.filter((x: string) => x !== id)
|
||||
this.ops = this.ops.filter((x: FilesystemOp) => x.id !== id)
|
||||
})
|
||||
}
|
||||
|
||||
removeQueuedOp(op: FSQueuedOp["op"], src: string): void {
|
||||
this.queuedOps = this.queuedOps.filter((x: FSQueuedOp) => x.op !== op || x.src !== src);
|
||||
}
|
||||
removeQueuedOp(op: FSQueuedOp['op'], src: string): void {
|
||||
this.queuedOps = this.queuedOps.filter((x: FSQueuedOp) => x.op !== op || x.src !== src)
|
||||
}
|
||||
|
||||
clearQueuedOps(): void {
|
||||
this.queuedOps = [];
|
||||
}
|
||||
clearQueuedOps(): void {
|
||||
this.queuedOps = []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user