mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-24 23:49:33 +01:00
web: fix attachment caching issues
This commit is contained in:
committed by
Abdullah Atta
parent
89dd8f5b6c
commit
4eabe79e7e
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user