backup second pass (#6388)

* some notes on approach

* some broken fs logic

* some more broke stuff

* fix backup logic

* rename export to backup

* wire up a restore

* restore check

* only update if newer version

* extract to function

* rename export to backup
This commit is contained in:
Jack Kavanagh 2023-08-25 17:35:58 +02:00 committed by GitHub
parent 5407006663
commit 7e3e44c50a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 42 deletions

View File

@ -1,3 +1,4 @@
import electron, { app, ipcMain, session } from 'electron';
import { BrowserWindow } from 'electron';
import contextMenu from 'electron-context-menu';
@ -8,6 +9,7 @@ import { userDataFolder } from '../config/config.json';
import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/constants';
import { database } from './common/database';
import log, { initializeLogging } from './common/log';
import { backupIfNewerVersionAvailable } from './main/backup';
import { registerElectronHandlers } from './main/ipc/electron';
import { registergRPCHandlers } from './main/ipc/grpc';
import { registerMainHandlers } from './main/ipc/main';
@ -228,7 +230,8 @@ async function _trackStats() {
launches: oldStats.launches + 1,
});
ipcMain.once('halfSecondAfterAppStart', () => {
ipcMain.once('halfSecondAfterAppStart', async () => {
backupIfNewerVersionAvailable();
const { currentVersion, launches, lastVersion } = stats;
const firstLaunch = launches === 1;
@ -244,7 +247,7 @@ async function _trackStats() {
message: `Updated to ${currentVersion}`,
};
// Wait a bit before showing the user because the app just launched.
setTimeout(() => {
setTimeout(async () => {
for (const window of BrowserWindow.getAllWindows()) {
// @ts-expect-error -- TSCONVERSION likely needs to be window.webContents.send instead
window.send('show-notification', notification);

View File

@ -0,0 +1,75 @@
import { copyFile, mkdir, readdir } from 'node:fs/promises';
import path from 'node:path';
import electron from 'electron';
import appConfig from '../../config/config.json';
import { version } from '../../package.json';
import * as models from '../models';
import { insomniaFetch } from './insomniaFetch';
export async function backupIfNewerVersionAvailable() {
try {
const settings = await models.settings.getOrCreate();
console.log('[main] Checking for newer version than ', version);
const response = await insomniaFetch<{ url: string }>({
method: 'GET',
origin: 'https://updates.insomnia.rest',
path: `/builds/check/mac?v=${version}&app=${appConfig.appId}&channel=${settings.updateChannel}`,
sessionId: null,
});
if (response) {
console.log('[main] Found newer version', response);
backup();
return;
}
console.log('[main] No newer version');
} catch (err) {
console.log('[main] Error checking for newer version', err);
}
}
export async function backup() {
try {
const dataPath = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
const versionPath = path.join(dataPath, 'backups', version);
await mkdir(versionPath, { recursive: true });
// skip if backup already exists at version path
const backupFiles = await readdir(versionPath);
if (backupFiles.length) {
console.log('Backup found at:', versionPath);
return;
}
const files = await readdir(dataPath);
files.forEach(async (file: string) => {
if (file.endsWith('.db')) {
await copyFile(path.join(dataPath, file), path.join(versionPath, file));
}
});
console.log('Exported backup to:', versionPath);
} catch (err) {
console.log('Error exporting backup:', err);
}
}
export async function restoreBackup(version: string) {
try {
const dataPath = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
const versionPath = path.join(dataPath, 'backups', version);
const files = await readdir(versionPath);
if (!files.length) {
console.log('No backup found at:', versionPath);
return;
}
files.forEach(async (file: string) => {
if (file.endsWith('.db')) {
await copyFile(path.join(versionPath, file), path.join(dataPath, file));
}
});
console.log('Restored backup from:', versionPath);
} catch (err) {
console.log('Error restoring backup:', err);
}
electron.app.relaunch();
electron.app.exit();
}

View File

@ -1,32 +0,0 @@
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import electron from 'electron';
import { version } from '../../package.json';
import { database as db } from '../common/database';
import { exportRequestsData } from '../common/export';
import * as models from '../models';
import { isGrpcRequest } from '../models/grpc-request';
import { isRequest } from '../models/request';
import { isWebSocketRequest } from '../models/websocket-request';
export async function exportAllWorkspaces() {
try {
const projects = await models.project.all();
await Promise.all(projects.map(async project => {
const dataPath = process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData');
const versionPath = path.join(dataPath, 'backups', version);
await mkdir(versionPath, { recursive: true });
const fileName = path.join(versionPath, `${project.name}.insomnia.json`);
const workspaces = await models.workspace.findByParentId(project._id);
const docs = await Promise.all(workspaces.map(parentDoc => db.withDescendants(parentDoc)));
const requests = docs.flat().filter(doc => isRequest(doc) || isGrpcRequest(doc) || isWebSocketRequest(doc));
const stringifiedExport = await exportRequestsData(requests, true, 'json');
await writeFile(fileName, stringifiedExport);
console.log('Exported project backup to:', fileName);
}
));
} catch (err) {
console.log('Error exporting project backup:', err);
}
}

View File

@ -9,7 +9,7 @@ import fs from 'fs';
import { SegmentEvent, trackPageView, trackSegmentEvent } from '../analytics';
import { authorizeUserInWindow } from '../authorizeUserInWindow';
import { exportAllWorkspaces } from '../export';
import { backup, restoreBackup } from '../backup';
import { insomniaFetch } from '../insomniaFetch';
import installPlugin from '../install-plugin';
import { axiosRequest } from '../network/axios-request';
@ -24,7 +24,8 @@ export interface MainBridgeAPI {
restart: () => void;
halfSecondAfterAppStart: () => void;
manualUpdateCheck: () => void;
exportAllWorkspaces: () => Promise<void>;
backup: () => Promise<void>;
restoreBackup: (version: string) => Promise<void>;
spectralRun: (options: { contents: string; rulesetPath: string }) => Promise<ISpectralDiagnostic[]>;
authorizeUserInWindow: typeof authorizeUserInWindow;
setMenuBarVisibility: (visible: boolean) => void;
@ -54,8 +55,11 @@ export function registerMainHandlers() {
w.webContents.send('loggedIn');
});
});
ipcMain.handle('exportAllWorkspaces', async () => {
return exportAllWorkspaces();
ipcMain.handle('backup', async () => {
return backup();
});
ipcMain.handle('restoreBackup', async (_, options: string) => {
return restoreBackup(options);
});
ipcMain.handle('authorizeUserInWindow', (_, options: Parameters<typeof authorizeUserInWindow>[0]) => {
const { url, urlSuccessRegex, urlFailureRegex, sessionId } = options;

View File

@ -10,7 +10,6 @@ import {
import { delay } from '../common/misc';
import * as models from '../models/index';
import { invariant } from '../utils/invariant';
import { exportAllWorkspaces } from './export';
const isUpdateSupported = () => {
if (process.platform === 'linux') {
console.log('[updater] Not supported on this platform', process.platform);
@ -62,7 +61,6 @@ export const init = async () => {
autoUpdater.on('update-downloaded', async (_error, releaseNotes, releaseName) => {
console.log(`[updater] Downloaded ${releaseName}`);
_sendUpdateStatus('Performing backup...');
await exportAllWorkspaces();
_sendUpdateStatus('Updated (Restart Required)');
dialog.showMessageBox({
@ -79,6 +77,7 @@ export const init = async () => {
});
if (isUpdateSupported()) {
// perhaps disable this method of upgrading just incase it trigger before backup is complete
// on app start
const settings = await models.settings.getOrCreate();
const updateUrl = getUpdateUrl(settings.updateChannel);

View File

@ -43,7 +43,8 @@ const main: Window['main'] = {
openInBrowser: options => ipcRenderer.send('openInBrowser', options),
halfSecondAfterAppStart: () => ipcRenderer.send('halfSecondAfterAppStart'),
manualUpdateCheck: () => ipcRenderer.send('manualUpdateCheck'),
exportAllWorkspaces: () => ipcRenderer.invoke('exportAllWorkspaces'),
backup: () => ipcRenderer.invoke('backup'),
restoreBackup: options => ipcRenderer.invoke('restoreBackup', options),
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
spectralRun: options => ipcRenderer.invoke('spectralRun', options),
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),

View File

@ -45,7 +45,9 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
exportAllToFile(projectName, workspacesForActiveProject);
hideSettingsModal();
};
// here we should list all the folders which contain insomnia.*.db files
// and have some big red button to overwrite the current data with the backup
// and once complete trigger an app restart?
return (
<Fragment>
<div data-testid="import-export-tab">