import { createAsyncThunk, Middleware } from '@reduxjs/toolkit'; // import { get, set } from 'idb-keyval'; import { now } from 'lodash'; import { proxyStore as store } from './proxyStore'; export const downloadStateAsync = createAsyncThunk('backup/download', async () => { const state = store.getState(); const json = JSON.stringify(state); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `tabvana-${now()}.json`; a.click(); }); export const loadStateAsync = createAsyncThunk('backup/load', async () => { const file = await window.showOpenFilePicker(); const fileHandle = file[0]; const fileReadable = await fileHandle.getFile(); const text = await fileReadable.text(); const state = JSON.parse(text); return state; }); // This must happen in the foreground page, // because that's the only place that window.showDirectoryPicker() works. export const setBackupDirAsync = createAsyncThunk('prefs/setbackupdir', async () => { const dir = await window.showDirectoryPicker(); console.log('Set Backup Directory: ', dir); await set('backupDirectoryHandle', dir); }); // This must happen in the foreground page, // because that's the only place that window.showDirectoryPicker() works. // export const maybeBackup = createAsyncThunk('prefs/maybeBackup', async () => { // console.log('maybeBackup'); // const state = store.getState(); // const lastBackup = await get('lastBackup'); // if (lastBackup == null || now() - lastBackup > state.prefs.backupEveryNSeconds * 1000) { // await backupAndThin(); // } // }); async function saveState() { const directory = await get('backupDirectoryHandle'); if (directory == null) { throw new Error('No backup directory selected'); } const t = now(); await set('lastBackup', t); // TODO catch permission denied error const file = await directory.getFileHandle(`tabvana-${t}.json`, { create: true }); const state = store.getState(); const json = JSON.stringify(state); const blob = new Blob([json], { type: 'application/json' }); const writable = await file.createWritable(); await writable.write(blob); await writable.close(); } // This should run only in the background thread! async function backupAndThin() { await saveState(); const directory = (await get('backupDirectoryHandle')) as FileSystemDirectoryHandle; const entries = []; for await (const entry of directory.values()) { entries.push(entry); } const promises: Array> = []; for await (const entry of directory.values()) { if (entry.kind == 'file') { promises.push(entry.getFile().then((file) => [file.lastModified, file])); } } const filesByDate = await Promise.all(promises); filesByDate.sort((a, b) => a[0] - b[0]); const toDelete = filesByDate.slice(0, filesByDate.length - 50); for (const file of toDelete) { directory.removeEntry(file[1].name); } } // This should run only in the background thread! export const saveStateAsync = createAsyncThunk('backup/save', async () => { await saveState(); }); // // This should run only in the background thread! // export const backupAndThinAsync = createAsyncThunk('backup/backupAndThin', async () => { // await backupAndThin(); // }); // export const backupAliases = { // 'saveStateAsync-alias': (state: object) => { // saveStateAsync(); // }, // 'backupAndThinAsync-alias': (state: object) => { // backupAndThinAsync(); // }, // }; // var backupTimerId: NodeJS.Timeout; // export function updateBackupTimer(backupEveryNSeconds: number) { // // const backupEveryNSeconds = store.getState().prefs?.backupEveryNSeconds; // console.log('backupEveryNSeconds: ' + backupEveryNSeconds); // if (backupTimerId) { // clearInterval(backupTimerId); // } // if (backupEveryNSeconds != null && backupEveryNSeconds > 0) { // backupTimerId = setInterval(() => { // backupAndThin(); // }, backupEveryNSeconds * 1000); // } // } export async function maybeBackup() { const state = store.getState(); const lastBackup = await get('lastBackup'); if (lastBackup == null || now() - lastBackup > state.prefs.backupEveryNSeconds * 1000) { await backupAndThin(); } } export const backupMiddleware: Middleware = (store) => (next) => (action) => { maybeBackup(); // fire and forget return next(action); };