Try another factoring

This commit is contained in:
Manav Rathi 2024-04-23 14:16:41 +05:30
parent 6ca3eb55af
commit 6ff41db939
No known key found for this signature in database
4 changed files with 152 additions and 11 deletions

View file

@ -994,6 +994,7 @@ class ExportService {
file,
);
await writeStream(
electron,
`${collectionExportPath}/${fileExportName}`,
updatedFileStream,
);
@ -1047,6 +1048,7 @@ class ExportService {
file,
);
await writeStream(
electron,
`${collectionExportPath}/${imageExportName}`,
imageStream,
);
@ -1061,6 +1063,7 @@ class ExportService {
);
try {
await writeStream(
electron,
`${collectionExportPath}/${videoExportName}`,
videoStream,
);

View file

@ -430,10 +430,122 @@ const moduleState = new ModuleState();
const readFileOrPath = async (
fileOrPath: File | string,
fileTypeInfo: FileTypeInfo,
): Promise<FileInMemory> =>
fileOrPath instanceof File
? _readFile(fileOrPath, fileTypeInfo)
: _readPath(fileOrPath, fileTypeInfo);
): Promise<FileInMemory> => {
let file: File | undefined;
let dataOrStream: Uint8Array | DataStream;
let fileSize: number;
if (fileOrPath instanceof File) {
file = fileOrPath;
fileSize = file.size;
dataOrStream =
fileSize > MULTIPART_PART_SIZE
? getFileStream(file, FILE_READER_CHUNK_SIZE)
: new Uint8Array(await file.arrayBuffer());
} else {
const path = fileOrPath;
const { response, size } = await readStream(ensureElectron(), path);
fileSize = size;
if (size > MULTIPART_PART_SIZE) {
const chunkCount = Math.ceil(size / FILE_READER_CHUNK_SIZE);
dataOrStream = { stream: response.body, chunkCount };
} else {
dataOrStream = new Uint8Array(await response.arrayBuffer());
}
}
let thumbnail: Uint8Array | undefined;
let hasStaticThumbnail = false;
const electron = globalThis.electron;
if (electron) {
// On Windows native thumbnail creation for images is not yet implemented.
const notAvailable =
fileTypeInfo.fileType == FILE_TYPE.IMAGE &&
moduleState.isNativeImageThumbnailCreationNotAvailable;
try {
if (!notAvailable) {
if (fileOrPath instanceof File) {
if (dataOrStream instanceof Uint8Array) {
thumbnail = await generateThumbnailNative(
electron,
dataOrStream,
fileTypeInfo,
);
}
} else {
thumbnail = await generateThumbnailNative(
electron,
fileOrPath,
fileTypeInfo,
);
}
}
} catch (e) {
if (e.message == CustomErrorMessage.NotAvailable) {
moduleState.isNativeImageThumbnailCreationNotAvailable = true;
} else {
log.error("Native thumbnail creation failed", e);
}
}
}
// If needed, fallback to browser based thumbnail generation. First, see if
// we already have a file (which is also a blob).
if (!thumbnail && file) {
try {
thumbnail = await generateThumbnailWeb(file, fileTypeInfo);
} catch (e) {
log.error(
`Failed to generate ${fileTypeInfo.exactType} thumbnail`,
e,
);
}
}
// Otherwise see if the data is small enough to read in memory.
if (!thumbnail) {
let data: Uint8Array | undefined;
if (dataOrStream instanceof Uint8Array) {
data = dataOrStream;
} else {
// Read the stream into memory, since the our web based thumbnail
// generation methods need the entire file in memory. Don't try this
// fallback for huge files though lest we run out of memory.
if (fileSize < 100 * 1024 * 1024 /* 100 MB */) {
data = new Uint8Array(
await new Response(dataOrStream.stream).arrayBuffer(),
);
// The Readable stream cannot be read twice, so also overwrite
// the stream with the data we read.
dataOrStream = data;
}
}
if (data) {
const blob = new Blob([data]);
try {
thumbnail = await generateThumbnailWeb(blob, fileTypeInfo);
} catch (e) {
log.error(
`Failed to generate ${fileTypeInfo.exactType} thumbnail`,
e,
);
}
}
}
if (!thumbnail) {
thumbnail = fallbackThumbnail();
hasStaticThumbnail = true;
}
return {
filedata: dataOrStream,
thumbnail,
hasStaticThumbnail,
};
};
const _readFile = async (file: File, fileTypeInfo: FileTypeInfo) => {
const dataOrStream =

View file

@ -646,7 +646,11 @@ async function downloadFileDesktop(
fs.exists,
);
const imageStream = generateStreamFromArrayBuffer(imageData);
await writeStream(`${downloadDir}/${imageExportName}`, imageStream);
await writeStream(
electron,
`${downloadDir}/${imageExportName}`,
imageStream,
);
try {
const videoExportName = await safeFileName(
downloadDir,
@ -654,7 +658,11 @@ async function downloadFileDesktop(
fs.exists,
);
const videoStream = generateStreamFromArrayBuffer(videoData);
await writeStream(`${downloadDir}/${videoExportName}`, videoStream);
await writeStream(
electron,
`${downloadDir}/${videoExportName}`,
videoStream,
);
} catch (e) {
await fs.rm(`${downloadDir}/${imageExportName}`);
throw e;
@ -665,7 +673,11 @@ async function downloadFileDesktop(
file.metadata.title,
fs.exists,
);
await writeStream(`${downloadDir}/${fileExportName}`, updatedStream);
await writeStream(
electron,
`${downloadDir}/${fileExportName}`,
updatedStream,
);
}
}

View file

@ -4,13 +4,18 @@
* NOTE: These functions only work when we're running in our desktop app.
*/
import type { Electron } from "@/next/types/ipc";
/**
* Stream the given file from the user's local filesystem.
*
* **This only works when we're running in our desktop app**. It uses the
* This only works when we're running in our desktop app since it uses the
* "stream://" protocol handler exposed by our custom code in the Node.js layer.
* See: [Note: IPC streams].
*
* To avoid accidentally invoking it in a non-desktop app context, it requires
* the {@link Electron} object as a parameter (even though it doesn't use it).
*
* @param path The path on the file on the user's local filesystem whose
* contents we want to stream.
*
@ -23,6 +28,7 @@
* * The size is the size of the file that we'll be reading from disk.
*/
export const readStream = async (
_: Electron,
path: string,
): Promise<{ response: Response; size: number }> => {
const req = new Request(`stream://read${path}`, {
@ -47,14 +53,22 @@ export const readStream = async (
/**
* Write the given stream to a file on the local machine.
*
* **This only works when we're running in our desktop app**. It uses the
* This only works when we're running in our desktop app since it uses the
* "stream://" protocol handler exposed by our custom code in the Node.js layer.
* See: [Note: IPC streams].
*
* To avoid accidentally invoking it in a non-desktop app context, it requires
* the {@link Electron} object as a parameter (even though it doesn't use it).
*
* @param path The path on the local machine where to write the file to.
*
* @param stream The stream which should be written into the file.
* */
export const writeStream = async (path: string, stream: ReadableStream) => {
*/
export const writeStream = async (
_: Electron,
path: string,
stream: ReadableStream,
) => {
// TODO(MR): This doesn't currently work.
//
// Not sure what I'm doing wrong here; I've opened an issue upstream