feat: Automatic plaintext backup option in Preferences > Backups will backup your notes and tags into plaintext, unencrypted folders on your computer. In addition, automatic encrypted text backups preference management has moved from the top-level menu in the desktop app to Preferences > Backups. (#2322)
This commit is contained in:
@@ -17,12 +17,12 @@ import {
|
||||
WebApplicationInterface,
|
||||
MobileDeviceInterface,
|
||||
MobileUnlockTiming,
|
||||
InternalEventBus,
|
||||
DecryptedItem,
|
||||
EditorIdentifier,
|
||||
FeatureIdentifier,
|
||||
Environment,
|
||||
ApplicationOptionsDefaults,
|
||||
BackupServiceInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { makeObservable, observable } from 'mobx'
|
||||
import { startAuthentication, startRegistration } from '@simplewebauthn/browser'
|
||||
@@ -93,27 +93,26 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
})
|
||||
|
||||
deviceInterface.setApplication(this)
|
||||
const internalEventBus = new InternalEventBus()
|
||||
|
||||
this.itemControllerGroup = new ItemGroupController(this)
|
||||
this.routeService = new RouteService(this, internalEventBus)
|
||||
this.routeService = new RouteService(this, this.internalEventBus)
|
||||
|
||||
this.webServices = {} as WebServices
|
||||
this.webServices.keyboardService = new KeyboardService(platform, this.environment)
|
||||
this.webServices.archiveService = new ArchiveManager(this)
|
||||
this.webServices.themeService = new ThemeManager(this, internalEventBus)
|
||||
this.webServices.themeService = new ThemeManager(this, this.internalEventBus)
|
||||
this.webServices.autolockService = this.isNativeMobileWeb()
|
||||
? undefined
|
||||
: new AutolockService(this, internalEventBus)
|
||||
: new AutolockService(this, this.internalEventBus)
|
||||
this.webServices.desktopService = isDesktopDevice(deviceInterface)
|
||||
? new DesktopManager(this, deviceInterface)
|
||||
? new DesktopManager(this, deviceInterface, this.fileBackups as BackupServiceInterface)
|
||||
: undefined
|
||||
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
||||
this.webServices.changelogService = new ChangelogService(this.environment, this.storage)
|
||||
this.webServices.momentsService = new MomentsService(
|
||||
this,
|
||||
this.webServices.viewControllerManager.filesController,
|
||||
internalEventBus,
|
||||
this.internalEventBus,
|
||||
)
|
||||
|
||||
if (this.isNativeMobileWeb()) {
|
||||
@@ -181,6 +180,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
for (const observer of this.webEventObservers) {
|
||||
observer(event, data)
|
||||
}
|
||||
|
||||
this.internalEventBus.publish({ type: event, payload: data })
|
||||
}
|
||||
|
||||
publishPanelDidResizeEvent(name: string, width: number, collapsed: boolean) {
|
||||
@@ -268,16 +269,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
||||
return this.protocolUpgradeAvailable()
|
||||
}
|
||||
|
||||
downloadBackup(): void | Promise<void> {
|
||||
if (isDesktopDevice(this.deviceInterface)) {
|
||||
return this.deviceInterface.downloadBackup()
|
||||
}
|
||||
}
|
||||
|
||||
async signOutAndDeleteLocalBackups(): Promise<void> {
|
||||
isDesktopDevice(this.deviceInterface) && (await this.deviceInterface.deleteLocalBackups())
|
||||
|
||||
return this.user.signOut()
|
||||
performDesktopTextBackup(): void | Promise<void> {
|
||||
return this.getDesktopService()?.saveDesktopBackup()
|
||||
}
|
||||
|
||||
isGlobalSpellcheckEnabled(): boolean {
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
DesktopDeviceInterface,
|
||||
WebApplicationInterface,
|
||||
WebAppEvent,
|
||||
BackupServiceInterface,
|
||||
DesktopWatchedDirectoriesChanges,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
export class DesktopManager
|
||||
@@ -27,10 +29,34 @@ export class DesktopManager
|
||||
dataLoaded = false
|
||||
lastSearchedText?: string
|
||||
|
||||
constructor(application: WebApplicationInterface, private device: DesktopDeviceInterface) {
|
||||
private textBackupsInterval: ReturnType<typeof setInterval> | undefined
|
||||
private needsInitialTextBackup = false
|
||||
|
||||
constructor(
|
||||
application: WebApplicationInterface,
|
||||
private device: DesktopDeviceInterface,
|
||||
private backups: BackupServiceInterface,
|
||||
) {
|
||||
super(application, new InternalEventBus())
|
||||
}
|
||||
|
||||
async handleWatchedDirectoriesChanges(changes: DesktopWatchedDirectoriesChanges): Promise<void> {
|
||||
void this.backups.importWatchedDirectoryChanges(changes)
|
||||
}
|
||||
|
||||
beginTextBackupsTimer() {
|
||||
if (this.textBackupsInterval) {
|
||||
clearInterval(this.textBackupsInterval)
|
||||
}
|
||||
|
||||
this.needsInitialTextBackup = true
|
||||
|
||||
const hoursInterval = 12
|
||||
const seconds = hoursInterval * 60 * 60
|
||||
const milliseconds = seconds * 1000
|
||||
this.textBackupsInterval = setInterval(this.saveDesktopBackup, milliseconds)
|
||||
}
|
||||
|
||||
get webApplication() {
|
||||
return this.application as WebApplicationInterface
|
||||
}
|
||||
@@ -44,14 +70,35 @@ export class DesktopManager
|
||||
super.onAppEvent(eventName).catch(console.error)
|
||||
if (eventName === ApplicationEvent.LocalDataLoaded) {
|
||||
this.dataLoaded = true
|
||||
this.device.onInitialDataLoad()
|
||||
if (this.backups.isTextBackupsEnabled()) {
|
||||
this.beginTextBackupsTimer()
|
||||
}
|
||||
} else if (eventName === ApplicationEvent.MajorDataChange) {
|
||||
this.device.onMajorDataChange()
|
||||
void this.saveDesktopBackup()
|
||||
}
|
||||
}
|
||||
|
||||
saveBackup() {
|
||||
this.device.onMajorDataChange()
|
||||
async saveDesktopBackup() {
|
||||
this.webApplication.notifyWebEvent(WebAppEvent.BeganBackupDownload)
|
||||
|
||||
const data = await this.getBackupFile()
|
||||
if (data) {
|
||||
await this.webApplication.fileBackups?.saveTextBackupData(data)
|
||||
this.webApplication.notifyWebEvent(WebAppEvent.EndedBackupDownload, { success: true })
|
||||
}
|
||||
}
|
||||
|
||||
private async getBackupFile(): Promise<string | undefined> {
|
||||
const encrypted = this.application.hasProtectionSources()
|
||||
const data = encrypted
|
||||
? await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
: await this.application.createDecryptedBackupFile()
|
||||
|
||||
if (data) {
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
getExtServerHost(): string {
|
||||
@@ -111,6 +158,11 @@ export class DesktopManager
|
||||
|
||||
windowLostFocus(): void {
|
||||
this.webApplication.notifyWebEvent(WebAppEvent.WindowDidBlur)
|
||||
|
||||
if (this.needsInitialTextBackup) {
|
||||
this.needsInitialTextBackup = false
|
||||
void this.saveDesktopBackup()
|
||||
}
|
||||
}
|
||||
|
||||
async onComponentInstallationComplete(componentData: DecryptedTransferPayload<ComponentContent>) {
|
||||
@@ -136,25 +188,4 @@ export class DesktopManager
|
||||
observer.callback(updatedComponent as SNComponent)
|
||||
}
|
||||
}
|
||||
|
||||
async requestBackupFile(): Promise<string | undefined> {
|
||||
const encrypted = this.application.hasProtectionSources()
|
||||
const data = encrypted
|
||||
? await this.application.createEncryptedBackupFileForAutomatedDesktopBackups()
|
||||
: await this.application.createDecryptedBackupFile()
|
||||
|
||||
if (data) {
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
didBeginBackup() {
|
||||
this.webApplication.notifyWebEvent(WebAppEvent.BeganBackupDownload)
|
||||
}
|
||||
|
||||
didFinishBackup(success: boolean) {
|
||||
this.webApplication.notifyWebEvent(WebAppEvent.EndedBackupDownload, { success })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,7 @@ export {
|
||||
FileBackupReadToken,
|
||||
FileBackupReadChunkResponse,
|
||||
FileDownloadProgress,
|
||||
PlaintextBackupsMapping,
|
||||
DesktopWatchedDirectoriesChanges,
|
||||
DesktopWatchedDirectoriesChange,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
Reference in New Issue
Block a user