ApplicationManager and better memory management

This commit is contained in:
Mo Bitar
2020-03-23 19:59:55 -05:00
parent 7dc3dab90b
commit ee7cb1fce6
55 changed files with 36786 additions and 3046 deletions

View File

@@ -1,16 +1,10 @@
import { EncryptionIntents, ProtectedActions } from 'snjs';
export class ArchiveManager {
/* @ngInject */
constructor(lockManager, application, godService) {
this.lockManager = lockManager;
constructor(application) {
this.application = application;
this.godService = godService;
}
/*
Public
*/
/** @public */
async downloadBackup(encrypted) {
return this.downloadBackupOfItems(this.application.modelManager.allItems, encrypted);
@@ -37,7 +31,7 @@ export class ArchiveManager {
};
if (await this.application.privilegesService.actionRequiresPrivilege(ProtectedActions.ManageBackups)) {
this.godService.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
this.application.presentPrivilegesModal(ProtectedActions.ManageBackups, () => {
run();
});
} else {

View File

@@ -9,23 +9,25 @@ const COMPONENT_CONTENT_KEY_PACKAGE_INFO = 'package_info';
const COMPONENT_CONTENT_KEY_LOCAL_URL = 'local_url';
export class DesktopManager extends ApplicationService {
/* @ngInject */
constructor(
$rootScope,
$timeout,
application,
appState,
application
) {
super(application);
this.$rootScope = $rootScope;
this.$timeout = $timeout;
this.appState = appState;
this.application = application;
this.componentActivationObservers = [];
this.updateObservers = [];
this.isDesktop = isDesktopApplication();
}
deinit() {
this.componentActivationObservers.length = 0;
this.updateObservers.length = 0;
super.deinit();
}
/** @override */
onAppEvent(eventName) {
super.onAppEvent(eventName);
@@ -177,7 +179,7 @@ export class DesktopManager extends ApplicationService {
/* Used to resolve 'sn://' */
desktop_setExtServerHost(host) {
this.extServerHost = host;
this.appState.desktopExtensionsReady();
this.application.getAppState().desktopExtensionsReady();
}
desktop_setComponentInstallationSyncHandler(handler) {
@@ -207,11 +209,11 @@ export class DesktopManager extends ApplicationService {
}
desktop_didBeginBackup() {
this.appState.beganBackupDownload();
this.application.getAppState().beganBackupDownload();
}
desktop_didFinishBackup(success) {
this.appState.endedBackupDownload({
this.application.getAppState().endedBackupDownload({
success: success
});
}

View File

@@ -1,114 +0,0 @@
import angular from 'angular';
export class GodService {
/* @ngInject */
constructor(
$rootScope,
$compile,
application
) {
this.$rootScope = $rootScope;
this.$compile = $compile;
this.application = application;
}
async checkForSecurityUpdate() {
return this.application.protocolUpgradeAvailable();
}
presentPasswordWizard(type) {
const scope = this.$rootScope.$new(true);
scope.type = type;
const el = this.$compile("<password-wizard type='type'></password-wizard>")(scope);
angular.element(document.body).append(el);
}
promptForChallenge(challenge, orchestrator) {
const scope = this.$rootScope.$new(true);
scope.challenge = challenge;
scope.orchestrator = orchestrator;
const el = this.$compile(
"<challenge-modal " +
"class='sk-modal' challenge='challenge' orchestrator='orchestrator'>" +
"</challenge-modal>"
)(scope);
angular.element(document.body).append(el);
}
async performProtocolUpgrade() {
const errors = await this.application.upgradeProtocolVersion();
if (errors.length === 0) {
this.application.alertService.alert({
text: "Success! Your encryption version has been upgraded." +
" You'll be asked to enter your credentials again on other devices you're signed into."
});
} else {
this.application.alertService.alert({
text: "Unable to upgrade encryption version. Please try again."
});
}
}
async presentPrivilegesModal(action, onSuccess, onCancel) {
if (this.authenticationInProgress()) {
onCancel && onCancel();
return;
}
const customSuccess = async () => {
onSuccess && await onSuccess();
this.currentAuthenticationElement = null;
};
const customCancel = async () => {
onCancel && await onCancel();
this.currentAuthenticationElement = null;
};
const scope = this.$rootScope.$new(true);
scope.action = action;
scope.onSuccess = customSuccess;
scope.onCancel = customCancel;
const el = this.$compile(`
<privileges-auth-modal action='action' on-success='onSuccess'
on-cancel='onCancel' class='sk-modal'></privileges-auth-modal>
`)(scope);
angular.element(document.body).append(el);
this.currentAuthenticationElement = el;
}
presentPrivilegesManagementModal() {
var scope = this.$rootScope.$new(true);
var el = this.$compile("<privileges-management-modal class='sk-modal'></privileges-management-modal>")(scope);
angular.element(document.body).append(el);
}
authenticationInProgress() {
return this.currentAuthenticationElement != null;
}
presentPasswordModal(callback) {
const scope = this.$rootScope.$new(true);
scope.type = "password";
scope.title = "Decryption Assistance";
scope.message = `Unable to decrypt this item with your current keys.
Please enter your account password at the time of this revision.`;
scope.callback = callback;
const el = this.$compile(
`<input-modal type='type' message='message'
title='title' callback='callback'></input-modal>`
)(scope);
angular.element(document.body).append(el);
}
presentRevisionPreviewModal(uuid, content) {
const scope = this.$rootScope.$new(true);
scope.uuid = uuid;
scope.content = content;
const el = this.$compile(
`<revision-preview-modal uuid='uuid' content='content'
class='sk-modal'></revision-preview-modal>`
)(scope);
angular.element(document.body).append(el);
}
}

View File

@@ -1,10 +1,10 @@
export { AlertService } from './alertService';
export { ArchiveManager } from './archiveManager';
export { DesktopManager } from './desktopManager';
export { GodService } from './godService';
export { KeyboardManager } from './keyboardManager';
export { LockManager } from './lockManager';
export { NativeExtManager } from './nativeExtManager';
export { PreferencesManager } from './preferencesManager';
export { StatusManager } from './statusManager';
export { ThemeManager } from './themeManager';
export { AppState } from './state';

View File

@@ -22,8 +22,19 @@ const KeyboardKeyEvents = {
export class KeyboardManager {
constructor() {
this.observers = [];
window.addEventListener('keydown', this.handleKeyDown.bind(this));
window.addEventListener('keyup', this.handleKeyUp.bind(this));
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
/** @access public */
deinit() {
this.observers.length = 0;
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
this.handleKeyDown = null;
this.handleKeyUp = null;
}
modifiersForEvent(event) {

View File

@@ -1,5 +1,5 @@
import { isDesktopApplication } from '@/utils';
import { AppStateEvents } from '../state';
import { AppStateEvents } from '@/services/state';
const MILLISECONDS_PER_SECOND = 1000;
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND;
@@ -12,16 +12,15 @@ const LOCK_INTERVAL_ONE_HOUR= 3600 * MILLISECONDS_PER_SECOND;
const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
export class LockManager {
/* @ngInject */
constructor($rootScope, application, appState) {
this.$rootScope = $rootScope;
constructor(application) {
this.application = application;
this.appState = appState;
this.observeVisibility();
setImmediate(() => {
this.observeVisibility();
});
}
observeVisibility() {
this.appState.addObserver((eventName, data) => {
this.unsubState = this.application.getAppState().addObserver((eventName) => {
if(eventName === AppStateEvents.WindowDidBlur) {
this.documentVisibilityChanged(false);
} else if(eventName === AppStateEvents.WindowDidFocus) {
@@ -33,6 +32,13 @@ export class LockManager {
}
}
deinit() {
this.unsubState();
if (this.pollFocusInterval) {
clearInterval(this.pollFocusInterval);
}
}
async setAutoLockInterval(interval) {
return this.application.setValue(STORAGE_KEY_AUTOLOCK_INTERVAL, interval);
}
@@ -53,7 +59,7 @@ export class LockManager {
* not triggered on a typical window blur event but rather on tab changes.
*/
beginWebFocusPolling() {
this.pollFocusTimeout = setInterval(() => {
this.pollFocusInterval = setInterval(() => {
const hasFocus = document.hasFocus();
if(hasFocus && this.lastFocusState === 'hidden') {
this.documentVisibilityChanged(true);

View File

@@ -13,7 +13,6 @@ export class NativeExtManager extends ApplicationService {
/* @ngInject */
constructor(application) {
super(application);
this.application = application;
this.extManagerId = 'org.standardnotes.extensions-manager';
this.batchManagerId = 'org.standardnotes.batch-manager';
}

View File

@@ -1,5 +1,4 @@
import {
ApplicationEvents,
SNPredicate,
ContentTypes,
CreateMaxPayloadFromAnyObject,
@@ -24,15 +23,7 @@ export const PrefKeys = {
};
export class PreferencesManager extends ApplicationService {
/* @ngInject */
constructor(
appState,
application
) {
super(application);
this.appState = appState;
}
/** @override */
onAppLaunch() {
super.onAppLaunch();
@@ -65,7 +56,7 @@ export class PreferencesManager extends ApplicationService {
}
preferencesDidChange() {
this.appState.setUserPreferences(this.userPreferences);
this.application.getAppState().setUserPreferences(this.userPreferences);
}
syncUserPreferences() {

View File

@@ -0,0 +1,204 @@
import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull';
import { ProtectedActions, ApplicationEvents } from 'snjs';
export const AppStateEvents = {
TagChanged: 1,
NoteChanged: 2,
PreferencesChanged: 3,
PanelResized: 4,
EditorFocused: 5,
BeganBackupDownload: 6,
EndedBackupDownload: 7,
DesktopExtsReady: 8,
WindowDidFocus: 9,
WindowDidBlur: 10,
};
export const EventSources = {
UserInteraction: 1,
Script: 2
};
export class AppState {
/* @ngInject */
constructor(
$rootScope,
$timeout,
application
) {
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.application = application;
this.observers = [];
this.locked = true;
this.registerVisibilityObservers();
this.addAppEventObserver();
}
deinit() {
this.unsubApp();
this.unsubApp = null;
this.observers.length = 0;
if(this.rootScopeCleanup1) {
this.rootScopeCleanup1();
this.rootScopeCleanup2();
this.rootScopeCleanup1 = null;
this.rootScopeCleanup2 = null;
}
document.removeEventListener('visibilitychange', this.onVisibilityChange);
this.onVisibilityChange = null;
}
addAppEventObserver() {
this.unsubApp = this.application.addEventObserver(async (eventName) => {
if (eventName === ApplicationEvents.Started) {
this.locked = true;
} else if (eventName === ApplicationEvents.Launched) {
this.locked = false;
}
});
}
isLocked() {
return this.locked;
}
registerVisibilityObservers() {
if (isDesktopApplication()) {
this.rootScopeCleanup1 = this.$rootScope.$on('window-lost-focus', () => {
this.notifyEvent(AppStateEvents.WindowDidBlur);
});
this.rootScopeCleanup2 = this.$rootScope.$on('window-gained-focus', () => {
this.notifyEvent(AppStateEvents.WindowDidFocus);
});
} else {
/* Tab visibility listener, web only */
this.onVisibilityChange = this.onVisibilityChange.bind(this);
document.addEventListener('visibilitychange', this.onVisibilityChange);
}
}
onVisibilityChange() {
const visible = document.visibilityState === "visible";
const event = visible
? AppStateEvents.WindowDidFocus
: AppStateEvents.WindowDidBlur;
this.notifyEvent(event);
}
/** @returns A function that unregisters this observer */
addObserver(callback) {
this.observers.push(callback);
return () => {
pull(this.observers, callback);
};
}
async notifyEvent(eventName, data) {
/**
* Timeout is particullary important so we can give all initial
* controllers a chance to construct before propogting any events *
*/
return new Promise((resolve) => {
this.$timeout(async () => {
for (const callback of this.observers) {
await callback(eventName, data);
}
resolve();
});
});
}
setSelectedTag(tag) {
if (this.selectedTag === tag) {
return;
}
const previousTag = this.selectedTag;
this.selectedTag = tag;
this.notifyEvent(
AppStateEvents.TagChanged,
{
tag: tag,
previousTag: previousTag
}
);
}
async setSelectedNote(note) {
const run = async () => {
const previousNote = this.selectedNote;
this.selectedNote = note;
await this.notifyEvent(
AppStateEvents.NoteChanged,
{ previousNote: previousNote }
);
};
if (note && note.content.protected &&
await this.application.application.privilegesService.actionRequiresPrivilege(
ProtectedActions.ViewProtectedNotes
)) {
this.application.presentPrivilegesModal(
ProtectedActions.ViewProtectedNotes,
run
);
} else {
run();
}
}
getSelectedTag() {
return this.selectedTag;
}
getSelectedNote() {
return this.selectedNote;
}
setUserPreferences(preferences) {
this.userPreferences = preferences;
this.notifyEvent(
AppStateEvents.PreferencesChanged
);
}
panelDidResize({ name, collapsed }) {
this.notifyEvent(
AppStateEvents.PanelResized,
{
panel: name,
collapsed: collapsed
}
);
}
editorDidFocus(eventSource) {
this.notifyEvent(
AppStateEvents.EditorFocused,
{ eventSource: eventSource }
);
}
beganBackupDownload() {
this.notifyEvent(
AppStateEvents.BeganBackupDownload
);
}
endedBackupDownload({ success }) {
this.notifyEvent(
AppStateEvents.EndedBackupDownload,
{ success: success }
);
}
/**
* When the desktop appplication extension server is ready.
*/
desktopExtensionsReady() {
this.notifyEvent(
AppStateEvents.DesktopExtsReady
);
}
}

View File

@@ -5,33 +5,35 @@ import {
EncryptionIntents,
ApplicationService,
} from 'snjs';
import { AppStateEvents } from '@/state';
import { AppStateEvents } from '@/services/state';
const CACHED_THEMES_KEY = 'cachedThemes';
export class ThemeManager extends ApplicationService {
/* @ngInject */
constructor(
application,
appState,
desktopManager,
) {
constructor(application) {
super(application);
this.appState = appState;
this.desktopManager = desktopManager;
this.activeThemes = [];
this.unsubState = appState.addObserver((eventName, data) => {
if (eventName === AppStateEvents.DesktopExtsReady) {
this.activateCachedThemes();
}
setImmediate(() => {
this.unsubState = this.application.getAppState().addObserver((eventName, data) => {
if (eventName === AppStateEvents.DesktopExtsReady) {
this.activateCachedThemes();
}
});
});
}
deinit() {
this.unsubState();
this.unsubState = null;
this.activeThemes.length = 0;
super.deinit();
}
/** @override */
onAppStart() {
super.onAppStart();
this.registerObservers();
if (!this.desktopManager.isDesktop) {
if (!this.application.getDesktopService().isDesktop) {
this.activateCachedThemes();
}
}
@@ -52,7 +54,7 @@ export class ThemeManager extends ApplicationService {
}
registerObservers() {
this.desktopManager.registerUpdateObserver((component) => {
this.application.getDesktopService().registerUpdateObserver((component) => {
if (component.active && component.isTheme()) {
this.deactivateTheme(component);
setTimeout(() => {