mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
refactor(download): handle stream saver download outside of driver implementation
This commit is contained in:
parent
3e8b1eabfc
commit
c4d4d3aa6f
|
|
@ -284,10 +284,15 @@ export interface FileURLService extends MultipleUriService {
|
|||
}
|
||||
|
||||
export interface FileURLResponse {
|
||||
urls: string[];
|
||||
urls: EntityURLResponse[];
|
||||
expires: string;
|
||||
}
|
||||
|
||||
export interface EntityURLResponse {
|
||||
url: string;
|
||||
stream_saver_display_name?: string;
|
||||
}
|
||||
|
||||
export interface GetFileInfoService {
|
||||
uri: string;
|
||||
extended?: boolean;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx";
|
||||
import React, { Suspense, useCallback, useEffect, useState } from "react";
|
||||
import { closeEpubViewer } from "../../../redux/globalStateSlice.ts";
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import React, { Suspense, useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFileEntityUrl } from "../../../api/api.ts";
|
||||
import { getFileLinkedUri } from "../../../util";
|
||||
import { closeEpubViewer } from "../../../redux/globalStateSlice.ts";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import SessionManager, { UserSettings } from "../../../session";
|
||||
import { getFileLinkedUri } from "../../../util";
|
||||
import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx";
|
||||
|
||||
const Epub = React.lazy(() => import("./Epub.tsx"));
|
||||
|
||||
|
|
@ -23,10 +23,7 @@ const EpubViewer = () => {
|
|||
(epubcifi: string) => {
|
||||
setLocation(epubcifi);
|
||||
if (viewerState?.file) {
|
||||
SessionManager.set(
|
||||
`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`,
|
||||
epubcifi,
|
||||
);
|
||||
SessionManager.set(`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`, epubcifi);
|
||||
}
|
||||
},
|
||||
[viewerState?.file],
|
||||
|
|
@ -46,10 +43,8 @@ const EpubViewer = () => {
|
|||
}),
|
||||
)
|
||||
.then((res) => {
|
||||
setSrc(res.urls[0]);
|
||||
const location = SessionManager.get(
|
||||
`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`,
|
||||
);
|
||||
setSrc(res.urls[0].url);
|
||||
const location = SessionManager.get(`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`);
|
||||
if (location) {
|
||||
setLocation(location);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const ImageEditor = () => {
|
|||
}),
|
||||
)
|
||||
.then((res) => {
|
||||
setImageSrc(res.urls[0]);
|
||||
setImageSrc(res.urls[0].url);
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(switchToImageViewer());
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ export interface IPhotoProps extends React.HTMLAttributes<HTMLElement> {
|
|||
broken: boolean;
|
||||
onPhotoLoad: (params: IPhotoLoadedParams) => void;
|
||||
loadingElement?: JSX.Element;
|
||||
brokenElement?:
|
||||
| JSX.Element
|
||||
| ((photoProps: BrokenElementParams) => JSX.Element);
|
||||
brokenElement?: JSX.Element | ((photoProps: BrokenElementParams) => JSX.Element);
|
||||
}
|
||||
|
||||
export default function Photo({
|
||||
|
|
@ -51,7 +49,7 @@ export default function Photo({
|
|||
}),
|
||||
)
|
||||
.then((res) => {
|
||||
setImageSrc(res.urls[0]);
|
||||
setImageSrc(res.urls[0].url);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (mountedRef.current) {
|
||||
|
|
@ -96,11 +94,7 @@ export default function Photo({
|
|||
/>
|
||||
)}
|
||||
{!loaded && (
|
||||
<FacebookCircularProgress
|
||||
sx={{ position: "absolute", top: 0 }}
|
||||
fgColor={"#fff"}
|
||||
bgColor={grey[800]}
|
||||
/>
|
||||
<FacebookCircularProgress sx={{ position: "absolute", top: 0 }} fgColor={"#fff"} bgColor={grey[800]} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
@ -109,9 +103,7 @@ export default function Photo({
|
|||
if (brokenElement) {
|
||||
return (
|
||||
<span className="PhotoView__icon">
|
||||
{typeof brokenElement === "function"
|
||||
? brokenElement({ src: imageSrc ?? "" })
|
||||
: brokenElement}
|
||||
{typeof brokenElement === "function" ? brokenElement({ src: imageSrc ?? "" }) : brokenElement}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const MusicPlayer = () => {
|
|||
entity: playerState.version,
|
||||
}),
|
||||
);
|
||||
audio.current.src = res.urls[0];
|
||||
audio.current.src = res.urls[0].url;
|
||||
audio.current.currentTime = 0;
|
||||
audio.current.play();
|
||||
audio.current.volume = latestVolume ?? volume;
|
||||
|
|
@ -83,10 +83,7 @@ const MusicPlayer = () => {
|
|||
if (isNext) {
|
||||
playIndex(((index ?? 0) + 1) % playerState?.files.length);
|
||||
} else {
|
||||
playIndex(
|
||||
((index ?? 0) - 1 + playerState?.files.length) %
|
||||
playerState?.files.length,
|
||||
);
|
||||
playIndex(((index ?? 0) - 1 + playerState?.files.length) % playerState?.files.length);
|
||||
}
|
||||
break;
|
||||
case LoopMode.single_repeat:
|
||||
|
|
@ -94,9 +91,7 @@ const MusicPlayer = () => {
|
|||
break;
|
||||
case LoopMode.shuffle:
|
||||
if (isNext) {
|
||||
const nextIndex = Math.floor(
|
||||
Math.random() * playerState?.files.length,
|
||||
);
|
||||
const nextIndex = Math.floor(Math.random() * playerState?.files.length);
|
||||
playIndex(nextIndex);
|
||||
} else {
|
||||
playHistory.current.pop();
|
||||
|
|
@ -124,9 +119,7 @@ const MusicPlayer = () => {
|
|||
}, []);
|
||||
|
||||
const playingTooltip = playerState
|
||||
? `[${(index ?? 0) + 1}/${playerState.files.length}] ${playerState?.files[
|
||||
index ?? 0
|
||||
]?.name}`
|
||||
? `[${(index ?? 0) + 1}/${playerState.files.length}] ${playerState?.files[index ?? 0]?.name}`
|
||||
: "";
|
||||
|
||||
const onPlayerPopoverClose = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||
import ViewerDialog, { ViewerLoading } from "./ViewerDialog.tsx";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { closePdfViewer } from "../../redux/globalStateSlice.ts";
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import { getFileEntityUrl } from "../../api/api.ts";
|
||||
import { getFileLinkedUri } from "../../util";
|
||||
import i18next from "i18next";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { getFileEntityUrl } from "../../api/api.ts";
|
||||
import { closePdfViewer } from "../../redux/globalStateSlice.ts";
|
||||
import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts";
|
||||
import { getFileLinkedUri } from "../../util";
|
||||
import ViewerDialog, { ViewerLoading } from "./ViewerDialog.tsx";
|
||||
|
||||
const viewerBase = "/pdfviewer.html";
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ const PdfViewer = () => {
|
|||
)
|
||||
.then((res) => {
|
||||
const search = new URLSearchParams();
|
||||
search.set("file", res.urls[0]);
|
||||
search.set("file", res.urls[0].url);
|
||||
search.set("lng", i18next.language);
|
||||
search.set("darkMode", theme.palette.mode == "dark" ? "2" : "1");
|
||||
setSrc(`${viewerBase}?${search.toString()}`);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
closePhotopeaViewer,
|
||||
GeneralViewerState,
|
||||
} from "../../../redux/globalStateSlice.ts";
|
||||
import { Box, Button, ButtonGroup, ListItemText, Menu } from "@mui/material";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFileEntityUrl } from "../../../api/api.ts";
|
||||
import { fileExtension, getFileLinkedUri } from "../../../util";
|
||||
import { closePhotopeaViewer, GeneralViewerState } from "../../../redux/globalStateSlice.ts";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
|
||||
import { savePhotopea } from "../../../redux/thunks/viewer.ts";
|
||||
import useActionDisplayOpt, {
|
||||
canUpdate,
|
||||
} from "../../FileManager/ContextMenu/useActionDisplayOpt.ts";
|
||||
import CaretDown from "../../Icons/CaretDown.tsx";
|
||||
import { fileExtension, getFileLinkedUri } from "../../../util";
|
||||
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||
import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts";
|
||||
import CaretDown from "../../Icons/CaretDown.tsx";
|
||||
import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx";
|
||||
import SaveAsNewFormat from "./SaveAsNewFormat.tsx";
|
||||
|
||||
const photopeiaOrigin = "https://www.photopea.com";
|
||||
|
|
@ -23,10 +18,7 @@ const photopeiaUrl =
|
|||
const saveCommand = "SAVE";
|
||||
const savePSDCommand = "SAVEPSD";
|
||||
|
||||
const appendBuffer = function (
|
||||
buffer1: ArrayBuffer,
|
||||
buffer2: ArrayBuffer,
|
||||
): ArrayBuffer {
|
||||
const appendBuffer = function (buffer1: ArrayBuffer, buffer2: ArrayBuffer): ArrayBuffer {
|
||||
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
|
||||
tmp.set(new Uint8Array(buffer1), 0);
|
||||
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
|
||||
|
|
@ -41,13 +33,9 @@ const saveOpt = {
|
|||
const Photopea = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const viewerState = useAppSelector(
|
||||
(state) => state.globalState.photopeaViewer,
|
||||
);
|
||||
const viewerState = useAppSelector((state) => state.globalState.photopeaViewer);
|
||||
|
||||
const displayOpt = useActionDisplayOpt(
|
||||
viewerState?.file ? [viewerState?.file] : [],
|
||||
);
|
||||
const displayOpt = useActionDisplayOpt(viewerState?.file ? [viewerState?.file] : []);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
|
@ -88,7 +76,7 @@ const Photopea = () => {
|
|||
}),
|
||||
)
|
||||
.then((res) => {
|
||||
entityUrl.current = res.urls[0];
|
||||
entityUrl.current = res.urls[0].url;
|
||||
setSrc(photopeiaUrl);
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -110,10 +98,7 @@ const Photopea = () => {
|
|||
}
|
||||
}
|
||||
|
||||
pp.current.contentWindow?.postMessage(
|
||||
`app.activeDocument.saveToOE("${ext}")`,
|
||||
"*",
|
||||
);
|
||||
pp.current.contentWindow?.postMessage(`app.activeDocument.saveToOE("${ext}")`, "*");
|
||||
saveStarted.current = newFile ? saveOpt.saveAs : saveOpt.started;
|
||||
};
|
||||
|
||||
|
|
@ -124,15 +109,10 @@ const Photopea = () => {
|
|||
console.log(e);
|
||||
if (e.data == "done") {
|
||||
if (doneCount.current == 0) {
|
||||
pp.current?.contentWindow?.postMessage(
|
||||
`app.open("${entityUrl.current}","",false)`,
|
||||
"*",
|
||||
);
|
||||
pp.current?.contentWindow?.postMessage(`app.open("${entityUrl.current}","",false)`, "*");
|
||||
} else if (doneCount.current == 2) {
|
||||
pp.current?.contentWindow?.postMessage(
|
||||
`app.activeDocument.name="${
|
||||
currentState.current?.file.name.replace(/"/g, '\\"') ?? ""
|
||||
}"`,
|
||||
`app.activeDocument.name="${currentState.current?.file.name.replace(/"/g, '\\"') ?? ""}"`,
|
||||
"*",
|
||||
);
|
||||
setLoaded(true);
|
||||
|
|
@ -161,11 +141,7 @@ const Photopea = () => {
|
|||
dispatch(closePhotopeaViewer());
|
||||
}, [dispatch]);
|
||||
|
||||
const onLoad = useCallback(
|
||||
(e: React.SyntheticEvent<HTMLIFrameElement>) =>
|
||||
(pp.current = e.currentTarget),
|
||||
[],
|
||||
);
|
||||
const onLoad = useCallback((e: React.SyntheticEvent<HTMLIFrameElement>) => (pp.current = e.currentTarget), []);
|
||||
|
||||
const openMore = useCallback(
|
||||
(e: React.MouseEvent<any>) => {
|
||||
|
|
@ -200,11 +176,7 @@ const Photopea = () => {
|
|||
readOnly={!supportUpdate.current}
|
||||
actions={
|
||||
supportUpdate.current ? (
|
||||
<ButtonGroup
|
||||
disabled={loading || !loaded}
|
||||
disableElevation
|
||||
variant="contained"
|
||||
>
|
||||
<ButtonGroup disabled={loading || !loaded} disableElevation variant="contained">
|
||||
<Button onClick={() => save()} variant={"contained"}>
|
||||
{t("fileManager.save")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ const VideoViewer = () => {
|
|||
}),
|
||||
);
|
||||
|
||||
art.subtitle.switch(subtitleUrl.urls[0], {
|
||||
art.subtitle.switch(subtitleUrl.urls[0].url, {
|
||||
type: fileExtension(subtitle.name) ?? "",
|
||||
});
|
||||
art.subtitle.show = true;
|
||||
|
|
@ -129,7 +129,7 @@ const VideoViewer = () => {
|
|||
)
|
||||
.then((res) => {
|
||||
const current = art.currentTime;
|
||||
currentUrl.current = res.urls[0];
|
||||
currentUrl.current = res.urls[0].url;
|
||||
|
||||
let timeOut = dayjs(res.expires).diff(dayjs(), "millisecond") - srcRefreshMargin;
|
||||
if (timeOut < 0) {
|
||||
|
|
@ -137,7 +137,7 @@ const VideoViewer = () => {
|
|||
}
|
||||
currentExpire.current = setTimeout(refreshSrc, timeOut);
|
||||
|
||||
art.switchUrl(res.urls[0]).then(() => {
|
||||
art.switchUrl(res.urls[0].url).then(() => {
|
||||
art.currentTime = current;
|
||||
});
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ const VideoViewer = () => {
|
|||
const realUrl = base.join(...url.split("/"));
|
||||
try {
|
||||
const res = await dispatch(getFileEntityUrl({ uris: [realUrl.toString()] }));
|
||||
return res.urls[0];
|
||||
return res.urls[0].url;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return url;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import { AppThunk } from "../store.ts";
|
|||
import { promiseId, selectOption } from "./dialog.ts";
|
||||
import { longRunningTaskWithSnackbar, refreshSingleFileSymbolicLinks, walk, walkAll } from "./file.ts";
|
||||
|
||||
const streamSaverParam = "stream_saver";
|
||||
|
||||
enum MultipleDownloadOption {
|
||||
Browser,
|
||||
StreamSaver,
|
||||
|
|
@ -99,7 +97,7 @@ export function backendBatchDownload(files: FileResponse[]): AppThunk {
|
|||
"application:fileManager.preparingBathDownload",
|
||||
);
|
||||
|
||||
window.location.assign(downloadUrl.urls[0]);
|
||||
window.location.assign(downloadUrl.urls[0].url);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +246,7 @@ function startBrowserBatchDownloadTo(
|
|||
|
||||
appendLog(i18next.t("modals.directoryDownloadStarted", { name }));
|
||||
try {
|
||||
const res = await fetch(entityUrls.urls[i], {
|
||||
const res = await fetch(entityUrls.urls[i].url, {
|
||||
signal: cancelSignals[downloadId].signal,
|
||||
});
|
||||
await saveFileToFileSystemDirectory(handle, await res.blob(), name);
|
||||
|
|
@ -333,7 +331,7 @@ export function streamSaverDownload(files: FileResponse[]): AppThunk {
|
|||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
const res = await fetch(url);
|
||||
const res = await fetch(url.url);
|
||||
const stream = () => res.body;
|
||||
ctrl.enqueue({ name: batch[i].relativePath, stream });
|
||||
}
|
||||
|
|
@ -371,13 +369,11 @@ export function downloadSingleFile(file: FileResponse, preferredEntity?: string)
|
|||
"application:fileManager.preparingDownload",
|
||||
);
|
||||
|
||||
const downloadUrl = new URL(urlRes.urls[0]);
|
||||
const streamSaverName = downloadUrl.searchParams.get(streamSaverParam);
|
||||
const streamSaverName = urlRes.urls[0].stream_saver_display_name;
|
||||
if (streamSaverName) {
|
||||
// remove streamSaverParam from query
|
||||
downloadUrl.searchParams.delete(streamSaverParam);
|
||||
const fileStream = streamSaver.createWriteStream(streamSaverName);
|
||||
const res = await fetch(downloadUrl.toString());
|
||||
const res = await fetch(urlRes.urls[0].url);
|
||||
const readableStream = res.body;
|
||||
if (!readableStream) {
|
||||
return;
|
||||
|
|
@ -395,7 +391,7 @@ export function downloadSingleFile(file: FileResponse, preferredEntity?: string)
|
|||
return readableStream.pipeTo(fileStream).finally(() => closeSnackbar(downloadingSnackbar));
|
||||
}
|
||||
} else {
|
||||
window.location.assign(urlRes.urls[0]);
|
||||
window.location.assign(urlRes.urls[0].url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -967,7 +967,7 @@ export function getEntityContent(file: FileResponse, version?: string): AppThunk
|
|||
}),
|
||||
);
|
||||
try {
|
||||
const data = await fetch(urls.urls[0]);
|
||||
const data = await fetch(urls.urls[0].url);
|
||||
if (!data.ok) {
|
||||
throw new Error(`Failed to load file, response code ${data.status}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,8 +263,8 @@ export function openCustomViewer(file: FileResponse, viewer: Viewer, preferredVe
|
|||
const currentUser = SessionManager.currentUser();
|
||||
|
||||
const vars: { [key: string]: string } = {
|
||||
src: encodeURIComponent(entityUrl.urls[0]),
|
||||
src_raw: entityUrl.urls[0],
|
||||
src: encodeURIComponent(entityUrl.urls[0].url),
|
||||
src_raw: entityUrl.urls[0].url,
|
||||
name: encodeURIComponent(file.name),
|
||||
version: preferredVersion ? preferredVersion : "",
|
||||
id: file.id,
|
||||
|
|
|
|||
Loading…
Reference in New Issue