import { WebApplication } from '@/UIModels/Application' import { observer } from 'mobx-react-lite' import { PreferencesGroup, PreferencesSegment, Title, Text, Subtitle, } from '@/Components/Preferences/PreferencesComponents' import { useCallback, useEffect, useMemo, useState } from 'preact/hooks' import { Button } from '@/Components/Button/Button' import { FileBackupMetadataFile, FileBackupsConstantsV1, FileContent, FileHandleRead } from '@standardnotes/snjs' import { Switch } from '@/Components/Switch' import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator' import { EncryptionStatusItem } from '../Security/Encryption' import { Icon } from '@/Components/Icon' import { StreamingFileApi } from '@standardnotes/filepicker' import { FunctionComponent } from 'preact' type Props = { application: WebApplication } export const FileBackups = observer(({ application }: Props) => { const [backupsEnabled, setBackupsEnabled] = useState(false) const [backupsLocation, setBackupsLocation] = useState('') const backupsService = useMemo(() => application.fileBackups, [application.fileBackups]) if (!backupsService) { return ( <> File Backups Automatically save encrypted backups of files uploaded to any device to this computer. To enable file backups, use the Standard Notes desktop application. ) } useEffect(() => { void backupsService.isFilesBackupsEnabled().then(setBackupsEnabled) }, [backupsService]) useEffect(() => { if (backupsEnabled) { void backupsService.getFilesBackupsLocation().then(setBackupsLocation) } }, [backupsService, backupsEnabled]) const changeBackupsLocation = useCallback(async () => { await backupsService.changeFilesBackupsLocation() setBackupsLocation(await backupsService.getFilesBackupsLocation()) }, [backupsService]) const openBackupsLocation = useCallback(async () => { await backupsService.openFilesBackupsLocation() }, [backupsService]) const toggleBackups = useCallback(async () => { if (backupsEnabled) { await backupsService.disableFilesBackups() } else { await backupsService.enableFilesBackups() } setBackupsEnabled(await backupsService.isFilesBackupsEnabled()) }, [backupsService, backupsEnabled]) return ( <> File Backups
Automatically save encrypted backups of files uploaded on any device to this computer.
{!backupsEnabled && ( <> File backups are not enabled. Enable to choose where your files are backed up. )}
{backupsEnabled && ( <> <> Files backups are enabled. When you upload a new file on any device and open this application, files will be backed up in encrypted form to: ]} checkmark={false} />
)}
) }) const isHandlingBackupDrag = (event: DragEvent, application: WebApplication) => { const items = event.dataTransfer?.items if (!items) { return false } return Array.from(items).every((item) => { const isFile = item.kind === 'file' const fileName = item.getAsFile()?.name || '' const isBackupMetadataFile = application.files.isFileNameFileBackupMetadataFile(fileName) return isFile && isBackupMetadataFile }) } export const BackupsDropZone: FunctionComponent = ({ application }) => { const [droppedFile, setDroppedFile] = useState(undefined) const [decryptedFileContent, setDecryptedFileContent] = useState(undefined) const [binaryFile, setBinaryFile] = useState(undefined) const [isSavingAsDecrypted, setIsSavingAsDecrypted] = useState(false) const fileSystem = useMemo(() => new StreamingFileApi(), []) useEffect(() => { if (droppedFile) { void application.files.decryptBackupMetadataFile(droppedFile).then(setDecryptedFileContent) } else { setDecryptedFileContent(undefined) } }, [droppedFile, application]) const chooseRelatedBinaryFile = useCallback(async () => { const selection = await application.files.selectFile(fileSystem) if (selection === 'aborted' || selection === 'failed') { return } setBinaryFile(selection) }, [application, fileSystem]) const downloadBinaryFileAsDecrypted = useCallback(async () => { if (!decryptedFileContent || !binaryFile) { return } setIsSavingAsDecrypted(true) const result = await application.files.readBackupFileAndSaveDecrypted(binaryFile, decryptedFileContent, fileSystem) if (result === 'success') { void application.alertService.alert( `${decryptedFileContent.name} has been successfully decrypted and saved to your chosen directory.`, ) setBinaryFile(undefined) setDecryptedFileContent(undefined) setDroppedFile(undefined) } else if (result === 'failed') { void application.alertService.alert( 'Unable to save file to local directory. This may be caused by failure to decrypt, or failure to save the file locally.', ) } setIsSavingAsDecrypted(false) }, [decryptedFileContent, application, binaryFile, fileSystem]) const handleDrag = useCallback( (event: DragEvent) => { if (isHandlingBackupDrag(event, application)) { event.preventDefault() event.stopPropagation() } }, [application], ) const handleDragIn = useCallback( (event: DragEvent) => { if (!isHandlingBackupDrag(event, application)) { return } event.preventDefault() event.stopPropagation() }, [application], ) const handleDragOut = useCallback( (event: DragEvent) => { if (!isHandlingBackupDrag(event, application)) { return } event.preventDefault() event.stopPropagation() }, [application], ) const handleDrop = useCallback( async (event: DragEvent) => { if (!isHandlingBackupDrag(event, application)) { return } event.preventDefault() event.stopPropagation() const items = event.dataTransfer?.items if (!items || items.length === 0) { return } const item = items[0] const file = item.getAsFile() if (!file) { return } const text = await file.text() try { const metadata = JSON.parse(text) as FileBackupMetadataFile setDroppedFile(metadata) } catch (error) { console.error(error) } event.dataTransfer.clearData() }, [application], ) useEffect(() => { window.addEventListener('dragenter', handleDragIn) window.addEventListener('dragleave', handleDragOut) window.addEventListener('dragover', handleDrag) window.addEventListener('drop', handleDrop) return () => { window.removeEventListener('dragenter', handleDragIn) window.removeEventListener('dragleave', handleDragOut) window.removeEventListener('dragover', handleDrag) window.removeEventListener('drop', handleDrop) } }, [handleDragIn, handleDrop, handleDrag, handleDragOut]) if (!droppedFile) { return ( To decrypt a backup file, drag and drop the file's respective metadata.sn.json file here. ) } return ( <> {!decryptedFileContent && Attempting to decrypt metadata file...} {decryptedFileContent && ( <> Backup Decryption ]} checkmark={true} />
1. Choose related data file {droppedFile.file.uuid}/{FileBackupsConstantsV1.BinaryFileName}
2. Decrypt and save file to your computer
)}
) }