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 }; }