199 lines
8.1 KiB
TypeScript
199 lines
8.1 KiB
TypeScript
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'));
|
||
console.info("KP", keyPlain);
|
||
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
|
||
};
|
||
} |