Files
desktop/app/providers/AttachmentProvider/useAttachment.ts
rosetta 83f38dc63f 'init'
2026-01-30 05:01:05 +02:00

198 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useContext, useEffect, useState } from "react";
import { useDownloadStatus } from "../TransportProvider/useDownloadStatus";
import { useUploadStatus } from "../TransportProvider/useUploadStatus";
import { useFileStorage } from "../../hooks/useFileStorage";
import { usePublicKey } from "../AccountProvider/usePublicKey";
import { usePrivatePlain } from "../AccountProvider/usePrivatePlain";
import { decodeWithPassword, encodeWithPassword, generateMd5 } from "../../crypto/crypto";
import { useTransport } from "../TransportProvider/useTransport";
import { useDialogsCache } from "../DialogProvider/useDialogsCache";
import { useConsoleLogger } from "../../hooks/useConsoleLogger";
import { Attachment, AttachmentType } from "../ProtocolProvider/protocol/packets/packet.message";
import { useMemory } from "../MemoryProvider/useMemory";
import { DialogContext } from "../DialogProvider/DialogProvider";
import { useSaveAvatar } from "../AvatarProvider/useSaveAvatar";
import { AVATAR_PASSWORD_TO_ENCODE } from "@/app/constants";
import { useDialog } from "../DialogProvider/useDialog";
export enum DownloadStatus {
DOWNLOADED,
NOT_DOWNLOADED,
PENDING,
DECRYPTING,
DOWNLOADING,
ERROR
}
export function useAttachment(attachment: Attachment, keyPlain: string) {
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
const uploadedPercentage = useUploadStatus(attachment.id);
const downloadPercentage = useDownloadStatus(attachment.id);
const [downloadStatus, setDownloadStatus] = useMemory("attachment-downloaded-status-" + attachment.id, DownloadStatus.PENDING, true);
const [downloadTag, setDownloadTag] = useState("");
const {readFile, writeFile} = useFileStorage();
const { downloadFile } = useTransport();
const publicKey = usePublicKey();
const privatePlain = usePrivatePlain();
const {updateAttachmentInDialogCache} = useDialogsCache();
const {info} = useConsoleLogger('useAttachment');
const {updateAttachmentsInMessagesByAttachmentId} = useDialog();
const context = useContext(DialogContext);
if(!context) {
throw new Error("useAttachment must be used within a DialogProvider");
}
const {dialog} = context;
const saveAvatar = useSaveAvatar();
useEffect(() => {
calcDownloadStatus();
}, []);
const getPreview = () => {
if(attachment.preview.split("::")[0].match(uuidRegex)){
/**
* Это тег загрузки
*/
return attachment.preview.split("::").splice(1).join("::");
}
return attachment.preview;
}
const calcDownloadStatus = async () => {
if(attachment.preview.split("::")[0].match(uuidRegex)){
/**
* Это тег загрузки
*/
setDownloadTag(attachment.preview.split("::")[0]);
}
if(!attachment.preview.split("::")[0].match(uuidRegex)){
/**
* Там не тег загрузки, значит это наш файл
*/
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
if (downloadStatus == DownloadStatus.DOWNLOADED) {
return;
}
if(attachment.type == AttachmentType.FILE){
/**
* Если это файл, то он хранится не в папке медиа,
* а в загрузках
*/
const preview = getPreview();
const filename = preview.split("::")[1];
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
const fileData = await readFile(pathInDownloads, false);
if(fileData){
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
setDownloadStatus(DownloadStatus.NOT_DOWNLOADED);
return;
}
if(attachment.type == AttachmentType.AVATAR){
/**
* Если это аватар, то он хранится не в папке медиа,
* а в папке аватарок
*/
const fileData = await readFile(`a/${await generateMd5(attachment.id + publicKey)}`);
if(fileData){
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
setDownloadStatus(DownloadStatus.NOT_DOWNLOADED);
return;
}
const fileData = await readFile(`m/${await generateMd5(attachment.id + publicKey)}`);
if(fileData){
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
setDownloadStatus(DownloadStatus.NOT_DOWNLOADED);
}
const getBlob = async () => {
if(attachment.blob && attachment.blob != ""){
return attachment.blob;
}
const folder = (attachment.type == AttachmentType.AVATAR) ? "a" : "m";
const fileData = await readFile(`${folder}/${await generateMd5(attachment.id + publicKey)}`);
if (!fileData) {
return "";
}
const password = (attachment.type == AttachmentType.AVATAR) ? AVATAR_PASSWORD_TO_ENCODE : privatePlain;
const decryptedData = await decodeWithPassword(password, Buffer.from(fileData, 'binary').toString());
return decryptedData;
}
const download = async () => {
if(downloadStatus == DownloadStatus.DOWNLOADED){
return;
}
if (downloadTag == "") {
return;
}
setDownloadStatus(DownloadStatus.DOWNLOADING);
info("Downloading attachment: " + downloadTag);
let downloadedBlob = '';
try {
downloadedBlob = await downloadFile(attachment.id,
downloadTag);
} catch (e) {
console.info(e);
info("Error downloading attachment: " + attachment.id);
setDownloadStatus(DownloadStatus.ERROR);
return;
}
setDownloadStatus(DownloadStatus.DECRYPTING);
//console.info("Decrypted attachment ", Buffer.from(keyPlain, 'binary').toString('hex'));
const decrypted = await decodeWithPassword(keyPlain, downloadedBlob);
setDownloadTag("");
if(attachment.type == AttachmentType.FILE) {
/**
* Если это файл то шифрованную копию не пишем,
* пишем его сразу в загрузки
*/
const preview = getPreview();
const filename = preview.split("::")[1];
let buffer = Buffer.from(decrypted.split(",")[1], 'base64');
let pathInDownloads = window.downloadsPath + "/Rosetta Downloads/" + filename;
await writeFile(pathInDownloads, buffer, false);
setDownloadStatus(DownloadStatus.DOWNLOADED);
return;
}
if(attachment.type == AttachmentType.AVATAR) {
/**
* Аватарки, пишем их в папку аватарок
*/
const avatarPath = `a/${await generateMd5(attachment.id + publicKey)}`;
await writeFile(avatarPath,
Buffer.from(await encodeWithPassword(AVATAR_PASSWORD_TO_ENCODE, decrypted)));
setDownloadStatus(DownloadStatus.DOWNLOADED);
saveAvatar(dialog, avatarPath, decrypted);
return;
}
/**
* Если это не файл, то обновляем состояние кэша,
* и пишем шифрованную копию
*/
updateAttachmentInDialogCache(attachment.id, decrypted);
updateAttachmentsInMessagesByAttachmentId(attachment.id, decrypted);
await writeFile(`m/${await generateMd5(attachment.id + publicKey)}`,
Buffer.from(await encodeWithPassword(privatePlain, decrypted)).toString('binary'));
setDownloadStatus(DownloadStatus.DOWNLOADED);
}
return {
uploadedPercentage,
downloadPercentage,
downloadStatus,
getPreview,
getBlob,
download
};
}