web: fix attachment caching issues

This commit is contained in:
Abdullah Atta
2023-05-27 15:06:43 +05:00
committed by Abdullah Atta
parent 89dd8f5b6c
commit 4eabe79e7e
11 changed files with 88 additions and 51 deletions

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import FS from "../interfaces/fs";
import { db } from "./db";
export async function downloadAttachment(hash) {
const attachment = db.attachments.attachment(hash);
async function download(hash: string) {
const attachment = db.attachments?.attachment(hash);
if (!attachment) return;
const downloadResult = await db.fs.downloadFile(
attachment.metadata.hash,
@@ -31,9 +31,17 @@ export async function downloadAttachment(hash) {
);
if (!downloadResult) throw new Error("Failed to download file.");
const key = await db.attachments.decryptKey(attachment.key);
const key = await db.attachments?.decryptKey(attachment.key);
if (!key) throw new Error("Invalid key for attachment.");
return { key, attachment };
}
export async function saveAttachment(hash: string) {
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;
await FS.saveFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
@@ -43,46 +51,51 @@ export async function downloadAttachment(hash) {
});
}
export async function downloadAttachmentForPreview(hash) {
const attachment = db.attachments.attachment(hash);
if (!attachment) return;
const downloadResult = await db.fs.downloadFile(
attachment.metadata.hash,
attachment.metadata.hash,
attachment.chunkSize,
attachment.metadata
);
if (!downloadResult) throw new Error("Failed to download file.");
type OutputTypeToReturnType = {
blob: Blob;
base64: string;
text: string;
};
export async function downloadAttachment<
TType extends "blob" | "base64" | "text",
TOutputType = OutputTypeToReturnType[TType]
>(hash: string, type: TType): Promise<TOutputType | undefined> {
const response = await download(hash);
if (!response) return;
const { attachment, key } = response;
const key = await db.attachments.decryptKey(attachment.key);
if (!key) throw new Error("Invalid key for attachment.");
if (type === "base64" || type === "text")
return (await db.attachments?.read(hash, type)) as TOutputType;
return await FS.decryptFile(attachment.metadata.hash, {
const blob = await FS.decryptFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
name: attachment.metadata.filename,
type: attachment.metadata.type,
isUploaded: !!attachment.dateUploaded
});
if (!blob) return;
return blob as TOutputType;
}
export async function checkAttachment(hash) {
const attachment = db.attachments.attachment(hash);
export async function checkAttachment(hash: string) {
const attachment = db.attachments?.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {
const size = await FS.getUploadedFileSize(hash);
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e.message };
return { failed: e instanceof Error ? e.message : "Unknown error." };
}
return { success: true };
}
const ABYTES = 17;
export function getTotalSize(attachments) {
export function getTotalSize(attachments: any[]) {
let size = 0;
for (let attachment of attachments) {
for (const attachment of attachments) {
size += attachment.length + ABYTES;
}
return size;

View File

@@ -47,7 +47,7 @@ import {
} from "../../common/dialog-controller";
import { store } from "../../stores/attachment-store";
import { db } from "../../common/db";
import { downloadAttachment } from "../../common/attachments";
import { saveAttachment } from "../../common/attachments";
import { reuploadAttachment } from "../editor/picker";
import { Multiselect } from "../../common/multi-select";
import { Menu } from "../../hooks/use-menu";
@@ -303,7 +303,7 @@ const AttachmentMenuItems: MenuItem[] = [
const isDownloading = status?.type === "download";
if (isDownloading) {
await db.fs.cancel(attachment.metadata.hash, "download");
} else await downloadAttachment(attachment.metadata.hash);
} else await saveAttachment(attachment.metadata.hash);
}
},
{

View File

@@ -41,10 +41,7 @@ import Header from "./header";
import { Attachment } from "../icons";
import { useEditorInstance } from "./context";
import { attachFile, AttachmentProgress, insertAttachment } from "./picker";
import {
downloadAttachment,
downloadAttachmentForPreview
} from "../../common/attachments";
import { saveAttachment, downloadAttachment } from "../../common/attachments";
import { EV, EVENTS } from "@notesnook/core/common";
import { db } from "../../common/db";
import useMobile from "../../hooks/use-mobile";
@@ -57,6 +54,7 @@ import { Lightbox } from "../lightbox";
import ThemeProviderWrapper from "../theme-provider";
import { Allotment } from "allotment";
import { PdfPreview } from "../pdf-preview";
import { showToast } from "../../utils/toast";
type PreviewSession = {
content: { data: string; type: string };
@@ -408,14 +406,17 @@ export function Editor(props: EditorProps) {
}}
onContentChange={onContentChange}
onChange={onEditorChange}
onDownloadAttachment={(attachment) =>
downloadAttachment(attachment.hash)
}
onDownloadAttachment={(attachment) => saveAttachment(attachment.hash)}
onPreviewAttachment={async ({ hash, dataurl }) => {
const attachment = db.attachments?.attachment(hash);
if (attachment && attachment.metadata.type.startsWith("image/")) {
const container = document.getElementById("dialogContainer");
if (!(container instanceof HTMLElement)) return;
dataurl = dataurl || (await downloadAttachment(hash, "base64"));
if (!dataurl)
return showToast("error", "This image cannot be previewed.");
ReactDOM.render(
<ThemeProviderWrapper>
<Lightbox
@@ -429,7 +430,7 @@ export function Editor(props: EditorProps) {
);
} else if (attachment && onPreviewDocument) {
onPreviewDocument({ hash });
const blob = await downloadAttachmentForPreview(hash);
const blob = await downloadAttachment(hash, "blob");
if (!blob) return;
onPreviewDocument({ url: URL.createObjectURL(blob), hash });
}

View File

@@ -155,7 +155,7 @@ export type AttachmentProgress = {
export type Attachment = {
hash: string;
filename: string;
type: string;
mime: string;
size: number;
dataurl?: string;
};
@@ -206,7 +206,7 @@ async function addAttachment(
return {
hash: hash,
filename: file.name,
type: file.type,
mime: file.type,
size: file.size,
dataurl
};

View File

@@ -111,8 +111,10 @@ async function writeEncryptedBase64(metadata: {
const { hash, type: hashType } = await hashBuffer(bytes);
const attachment = db.attachments?.attachment(hash);
const file = new File([bytes.buffer], hash, {
type: mimeType || "application/octet-stream"
type: attachment?.metadata.type || mimeType || "application/octet-stream"
});
const result = await writeEncryptedFile(file, key, hash);
@@ -337,9 +339,15 @@ function reportProgress(
async function downloadFile(filename: string, requestOptions: RequestOptions) {
const { url, headers, chunkSize, signal } = requestOptions;
const handle = await streamablefs.readFile(filename);
if (handle && handle.file.size === (await handle.size())) return true;
if (
handle &&
handle.file.size === (await handle.size()) - handle.file.chunks * ABYTES
)
return true;
else if (handle) await handle.delete();
const attachment = db.attachments?.attachment(filename);
try {
reportProgress(
{ total: 100, loaded: 0 },
@@ -379,10 +387,18 @@ async function downloadFile(filename: string, requestOptions: RequestOptions) {
throw new Error(error);
}
const totalChunks = Math.ceil(contentLength / chunkSize);
const decryptedLength = contentLength - totalChunks * ABYTES;
if (attachment && attachment.length !== decryptedLength) {
const error = `File length mismatch. Please upload this file again from the attachment manager. (File hash: ${filename})`;
await db.attachments?.markAsFailed(filename, error);
throw new Error(error);
}
const fileHandle = await streamablefs.createFile(
filename,
contentLength,
"application/octet-stream"
decryptedLength,
attachment?.metadata.type || "application/octet-stream"
);
await response.body

View File

@@ -85,7 +85,7 @@ export class WebExtensionServer implements Server {
clipContent += h("iframe", [], {
"data-hash": attachment.hash,
"data-mime": attachment.type,
"data-mime": attachment.mime,
src: clip.url,
title: clip.pageTitle || clip.title,
width: clip.width ? `${clip.width}` : undefined,

View File

@@ -35,7 +35,7 @@ export type AttachmentWithProgress = AttachmentProgress & Attachment;
export type Attachment = {
hash: string;
filename: string;
type: string;
mime: string;
size: number;
};
@@ -88,7 +88,7 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
},
hash: getDataAttribute("hash"),
filename: getDataAttribute("filename"),
type: getDataAttribute("mime"),
mime: getDataAttribute("mime"),
size: getDataAttribute("size")
};
},

View File

@@ -83,7 +83,7 @@ export function ImageComponent(
setSource(url);
editor.current?.commands.updateImage(
{ src },
{ src: await toDataURL(blob), size, type }
{ src: await toDataURL(blob), size, mime: type }
);
}
} catch (e) {

View File

@@ -138,7 +138,7 @@ export const ImageNode = Node.create<ImageOptions>({
hash: getDataAttribute("hash"),
filename: getDataAttribute("filename"),
type: getDataAttribute("mime"),
mime: getDataAttribute("mime"),
size: getDataAttribute("size"),
aspectRatio: {
default: undefined,
@@ -209,6 +209,7 @@ export const ImageNode = Node.create<ImageOptions>({
insertImage:
(options) =>
({ commands }) => {
console.log(options);
return commands.insertContent({
type: this.name,
attrs: options

View File

@@ -53,7 +53,7 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) {
url,
downloadOptions
);
onInsert({ src: await toDataURL(blob), size, type });
onInsert({ src: await toDataURL(blob), size, mime: type });
} catch (e) {
if (e instanceof Error) setError(e.message);
} finally {

View File

@@ -61,15 +61,7 @@ export function DownloadAttachment(props: ToolProps) {
/>
);
}
const previewableFileExtensions = ["pdf"];
function canPreviewAttachment(attachment: Attachment) {
if (!attachment) return false;
const extension = attachment.filename?.split(".").pop();
if (!extension) return false;
return previewableFileExtensions.indexOf(extension) > -1;
}
export function PreviewAttachment(props: ToolProps) {
const { editor } = props;
const attachmentNode =
@@ -105,3 +97,17 @@ export function RemoveAttachment(props: ToolProps) {
/>
);
}
const previewableFileExtensions = ["pdf"];
const previewableMimeTypes = ["application/pdf"];
function canPreviewAttachment(attachment: Attachment) {
if (!attachment) return false;
if (previewableMimeTypes.some((mime) => attachment.mime.startsWith(mime)))
return true;
const extension = attachment.filename?.split(".").pop();
if (!extension) return false;
return previewableFileExtensions.indexOf(extension) > -1;
}