mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
web: add options to create backups with attachments
This commit is contained in:
committed by
Abdullah Atta
parent
10e24ae632
commit
759fba06ae
@@ -22,7 +22,11 @@ import { useStore } from "./stores/app-store";
|
||||
import { useStore as useUserStore } from "./stores/user-store";
|
||||
import { useEditorStore } from "./stores/editor-store";
|
||||
import { useStore as useAnnouncementStore } from "./stores/announcement-store";
|
||||
import { resetNotices, scheduleBackups } from "./common/notices";
|
||||
import {
|
||||
resetNotices,
|
||||
scheduleBackups,
|
||||
scheduleFullBackups
|
||||
} from "./common/notices";
|
||||
import { introduceFeatures, showUpgradeReminderDialogs } from "./common";
|
||||
import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { db } from "./common/db";
|
||||
@@ -90,6 +94,7 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
if (onboardingKey) await OnboardingDialog.show({ type: onboardingKey });
|
||||
await FeatureDialog.show({ featureName: "highlights" });
|
||||
await scheduleBackups();
|
||||
await scheduleFullBackups();
|
||||
})();
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -82,9 +82,10 @@ export async function createBackup(
|
||||
options: {
|
||||
rescueMode?: boolean;
|
||||
noVerify?: boolean;
|
||||
} = {}
|
||||
mode?: "full" | "partial";
|
||||
} = { mode: "partial" }
|
||||
) {
|
||||
const { rescueMode, noVerify } = options;
|
||||
const { rescueMode, noVerify, mode } = options;
|
||||
const { isLoggedIn } = useUserStore.getState();
|
||||
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
|
||||
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
|
||||
@@ -103,7 +104,7 @@ export async function createBackup(
|
||||
type: "date-time",
|
||||
dateFormat: "YYYY-MM-DD",
|
||||
timeFormat: "24-hour"
|
||||
})}-${new Date().getSeconds()}`,
|
||||
})}-${new Date().getSeconds()}${mode === "full" ? "-full" : ""}`,
|
||||
{ replacement: "-" }
|
||||
);
|
||||
const directory = Config.get("backupStorageLocation", PATHS.backupsDirectory);
|
||||
@@ -123,10 +124,11 @@ export async function createBackup(
|
||||
start() {},
|
||||
async pull(controller) {
|
||||
const { streamablefs } = await import("../interfaces/fs");
|
||||
for await (const output of db.backup!.export(
|
||||
"web",
|
||||
encryptedBackups
|
||||
)) {
|
||||
for await (const output of db.backup!.export({
|
||||
type: "web",
|
||||
encrypt: encryptedBackups,
|
||||
mode
|
||||
})) {
|
||||
if (output.type === "file") {
|
||||
const file = output;
|
||||
report({
|
||||
|
||||
@@ -38,12 +38,18 @@ export type Notice = {
|
||||
params?: any;
|
||||
};
|
||||
|
||||
export const BACKUP_CRON_EXPRESSIONS = [
|
||||
"",
|
||||
"0 0 8 * * *", // daily at 8 AM
|
||||
"0 0 8 * * 1", // Every monday at 8 AM
|
||||
"0 0 0 1 * *" // 1st day of every month
|
||||
];
|
||||
export const BACKUP_CRON_EXPRESSIONS = {
|
||||
0: "",
|
||||
1: "0 0 8 * * *", // daily at 8 AM
|
||||
2: "0 0 8 * * 1", // Every monday at 8 AM
|
||||
3: "0 0 0 1 * *" // 1st day of every month
|
||||
};
|
||||
|
||||
export const FULL_BACKUP_CRON_EXPRESSIONS = {
|
||||
0: "",
|
||||
1: "0 0 8 * * 1", // Every monday at 8 AM
|
||||
2: "0 0 0 1 * *" // 1st day of every month
|
||||
};
|
||||
|
||||
export async function scheduleBackups() {
|
||||
const backupInterval = Config.get("backupReminderOffset", 0);
|
||||
@@ -62,6 +68,23 @@ export async function scheduleBackups() {
|
||||
);
|
||||
}
|
||||
|
||||
export async function scheduleFullBackups() {
|
||||
const backupInterval = Config.get("fullBackupReminderOffset", 0);
|
||||
|
||||
await TaskScheduler.stop("automatic-full-backups");
|
||||
if (!backupInterval) return false;
|
||||
|
||||
console.log("Scheduling automatic full backups");
|
||||
await TaskScheduler.register(
|
||||
"automatic-full-backups",
|
||||
FULL_BACKUP_CRON_EXPRESSIONS[backupInterval],
|
||||
() => {
|
||||
console.log("Backing up automatically");
|
||||
saveBackup("full");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldAddAutoBackupsDisabledNotice() {
|
||||
const backupInterval = Config.get("backupReminderOffset", 0);
|
||||
if (!isUserPremium() && backupInterval) {
|
||||
@@ -175,9 +198,9 @@ function isIgnored(key: keyof typeof NoticesData) {
|
||||
}
|
||||
|
||||
let openedToast: { hide: () => void } | null = null;
|
||||
async function saveBackup() {
|
||||
async function saveBackup(mode: "full" | "partial" = "partial") {
|
||||
if (IS_DESKTOP_APP) {
|
||||
await createBackup({ noVerify: true });
|
||||
await createBackup({ noVerify: true, mode });
|
||||
} else if (isUserPremium() && !IS_TESTING) {
|
||||
if (openedToast !== null) return;
|
||||
openedToast = showToast(
|
||||
@@ -196,7 +219,7 @@ async function saveBackup() {
|
||||
{
|
||||
text: "Download",
|
||||
onClick: async () => {
|
||||
await createBackup();
|
||||
await createBackup({ mode });
|
||||
openedToast?.hide();
|
||||
openedToast = null;
|
||||
},
|
||||
|
||||
@@ -37,14 +37,24 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
settings: [
|
||||
{
|
||||
key: "create-backup",
|
||||
title: "Backup now",
|
||||
description: "Create a backup file containing all your data",
|
||||
title: "Create backup",
|
||||
description:
|
||||
"Partial backups contain all your data except attachments. They are created from data already available on your device and do not require an Internet connection.",
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
title: "Create backup",
|
||||
action: createBackup,
|
||||
variant: "secondary"
|
||||
type: "dropdown",
|
||||
options: [
|
||||
{ value: "-", title: "Choose backup format" },
|
||||
{ value: "partial", title: "Backup" },
|
||||
{ value: "full", title: "Backup with attachments" }
|
||||
],
|
||||
selectedOption: () => "-",
|
||||
onSelectionChanged: async (value) => {
|
||||
if (value === "-") return;
|
||||
await createBackup({
|
||||
mode: value === "partial" ? "partial" : "full"
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -67,10 +77,10 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
},
|
||||
{
|
||||
key: "auto-backup",
|
||||
title: IS_DESKTOP_APP ? "Automatic backups" : "Backup reminders",
|
||||
description: IS_DESKTOP_APP
|
||||
? "Backup all your data automatically at a set interval."
|
||||
: "You will be shown regular reminders to backup your data.",
|
||||
title: "Automatic backups",
|
||||
description: `Set the interval to create a backup automatically.
|
||||
|
||||
Note: these backups do not contain attachments.`,
|
||||
isHidden: () => !isUserPremium(),
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.backupReminderOffset, listener),
|
||||
@@ -97,6 +107,40 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "auto-backup-with-attachments",
|
||||
title: "Automatic backup with attachments",
|
||||
description: `Set the interval to create a backup (with attachments) automatically.
|
||||
|
||||
NOTE: Creating a backup with attachments can take a while, and also fail completely. The app will try to resume/restart the backup in case of interruptions.`,
|
||||
isHidden: () => !isUserPremium(),
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe(
|
||||
(s) => s.fullBackupReminderOffset,
|
||||
listener
|
||||
),
|
||||
components: [
|
||||
{
|
||||
type: "dropdown",
|
||||
options: [
|
||||
{ value: "0", title: "Never" },
|
||||
{ value: "1", title: "Weekly" },
|
||||
{ value: "2", title: "Monthly" }
|
||||
],
|
||||
selectedOption: () =>
|
||||
useSettingStore.getState().fullBackupReminderOffset.toString(),
|
||||
onSelectionChanged: async (value) => {
|
||||
const verified =
|
||||
useSettingStore.getState().encryptBackups ||
|
||||
(await verifyAccount());
|
||||
if (verified)
|
||||
useSettingStore
|
||||
.getState()
|
||||
.setFullBackupReminderOffset(parseInt(value));
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "encrypt-backups",
|
||||
title: "Backup encryption",
|
||||
|
||||
@@ -32,6 +32,7 @@ import { Profile, TrashCleanupInterval } from "@notesnook/core";
|
||||
class SettingStore extends BaseStore<SettingStore> {
|
||||
encryptBackups = Config.get("encryptBackups", false);
|
||||
backupReminderOffset = Config.get("backupReminderOffset", 0);
|
||||
fullBackupReminderOffset = Config.get("fullBackupReminderOffset", 0);
|
||||
backupStorageLocation = Config.get(
|
||||
"backupStorageLocation",
|
||||
PATHS.backupsDirectory
|
||||
@@ -149,6 +150,11 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
this.set({ backupReminderOffset: offset });
|
||||
};
|
||||
|
||||
setFullBackupReminderOffset = (offset: number) => {
|
||||
Config.set("fullBackupReminderOffset", offset);
|
||||
this.set({ fullBackupReminderOffset: offset });
|
||||
};
|
||||
|
||||
setBackupStorageLocation = (location: string) => {
|
||||
Config.set("backupStorageLocation", location);
|
||||
this.set({ backupStorageLocation: location });
|
||||
|
||||
@@ -455,7 +455,7 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
|
||||
"Please wait while we create a backup file for you to download."
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
await createBackup({ rescueMode: true });
|
||||
await createBackup({ rescueMode: true, mode: "full" });
|
||||
navigate("new");
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -277,10 +277,11 @@ export default class Backup {
|
||||
await this.updateBackupTime();
|
||||
}
|
||||
|
||||
async *export(
|
||||
type: BackupPlatform,
|
||||
encrypt = false
|
||||
): AsyncGenerator<
|
||||
async *export(options: {
|
||||
type: BackupPlatform;
|
||||
encrypt?: boolean;
|
||||
mode?: "full" | "partial";
|
||||
}): AsyncGenerator<
|
||||
| {
|
||||
type: "file";
|
||||
path: string;
|
||||
@@ -296,6 +297,7 @@ export default class Backup {
|
||||
void,
|
||||
unknown
|
||||
> {
|
||||
const { encrypt = false, type, mode = "partial" } = options;
|
||||
if (this.db.migrations.version === 5.9) {
|
||||
yield* this.exportLegacy(type, encrypt);
|
||||
return;
|
||||
@@ -343,6 +345,10 @@ export default class Backup {
|
||||
yield* this.backupCollection(this.db.vaults.collection, backupState);
|
||||
|
||||
if (backupState.buffer.length > 0) yield* this.bufferToFile(backupState);
|
||||
if (mode === "partial") {
|
||||
await this.updateBackupTime();
|
||||
return;
|
||||
}
|
||||
|
||||
const total = await this.db.attachments.all.count();
|
||||
if (total > 0 && user && user.attachmentsKey) {
|
||||
|
||||
Reference in New Issue
Block a user