Пример #1
0
async function setupDatabase(id = null) {
	if (id === null) id = currentClient_;

	Setting.cancelScheduleSave();
	Setting.cache_ = null;

	if (databases_[id]) {
		await clearDatabase(id);
		await Setting.load();
		return;
	}

	const filePath = __dirname + '/data/test-' + id + '.sqlite';

	try {
		await fs.unlink(filePath);
	} catch (error) {
		// Don't care if the file doesn't exist
	};

	databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
	await databases_[id].open({ name: filePath });

	BaseModel.db_ = databases_[id];
	await Setting.load();
}
Пример #2
0
	async refreshNotes(state) {
		let parentType = state.notesParentType;
		let parentId = null;

		if (parentType === 'Folder') {
			parentId = state.selectedFolderId;
			parentType = BaseModel.TYPE_FOLDER;
		} else if (parentType === 'Tag') {
			parentId = state.selectedTagId;
			parentType = BaseModel.TYPE_TAG;
		} else if (parentType === 'Search') {
			parentId = state.selectedSearchId;
			parentType = BaseModel.TYPE_SEARCH;
		}

		this.logger().debug('Refreshing notes:', parentType, parentId);

		let options = {
			order: stateUtils.notesOrder(state.settings),
			uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'),
			showCompletedTodos: Setting.value('showCompletedTodos'),
			caseInsensitive: true,
		};

		const source = JSON.stringify({
			options: options,
			parentId: parentId,
		});

		let notes = [];

		if (parentId) {
			if (parentType === Folder.modelType()) {
				notes = await Note.previews(parentId, options);
			} else if (parentType === Tag.modelType()) {
				notes = await Tag.notes(parentId, options);
			} else if (parentType === BaseModel.TYPE_SEARCH) {
				let fields = Note.previewFields();
				let search = BaseModel.byId(state.searches, parentId);
				notes = await Note.previews(null, {
					fields: fields,
					anywherePattern: '*' + search.query_pattern + '*',
				});
			}
		}

		this.store().dispatch({
			type: 'NOTE_UPDATE_ALL',
			notes: notes,
			notesSource: source,
		});

		this.store().dispatch({
			type: 'NOTE_SELECT',
			id: notes.length ? notes[0].id : null,
		});
	}
Пример #3
0
	async generalMiddleware(store, next, action) {
		if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'locale' || action.type == 'SETTING_UPDATE_ALL') {
			setLocale(Setting.value('locale'));
			// The bridge runs within the main process, with its own instance of locale.js
			// so it needs to be set too here.
			bridge().setLocale(Setting.value('locale'));
			this.refreshMenu();
		}

		if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'showTrayIcon' || action.type == 'SETTING_UPDATE_ALL') {
			this.updateTray();
		}

		if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'style.editor.fontFamily' || action.type == 'SETTING_UPDATE_ALL') {
			this.updateEditorFont();
		}

		if (["NOTE_UPDATE_ONE", "NOTE_DELETE", "FOLDER_UPDATE_ONE", "FOLDER_DELETE"].indexOf(action.type) >= 0) {
			if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(30 * 1000, { syncSteps: ["update_remote", "delete_remote"] });
		}

		if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) {
			await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE');
		}

		const result = await super.generalMiddleware(store, next, action);
		const newState = store.getState();

		if (action.type === 'NAV_GO' || action.type === 'NAV_BACK') {
			app().updateMenu(newState.route.routeName);
		}

		if (['NOTE_VISIBLE_PANES_TOGGLE', 'NOTE_VISIBLE_PANES_SET'].indexOf(action.type) >= 0) {
			Setting.setValue('noteVisiblePanes', newState.noteVisiblePanes);
		}

		if (['SIDEBAR_VISIBILITY_TOGGLE', 'SIDEBAR_VISIBILITY_SET'].indexOf(action.type) >= 0) {
			Setting.setValue('sidebarVisibility', newState.sidebarVisibility);
		}

		if (action.type === 'SYNC_STARTED') {
			if (!this.powerSaveBlockerId_) this.powerSaveBlockerId_ = bridge().powerSaveBlockerStart('prevent-app-suspension');
		}

		if (action.type === 'SYNC_COMPLETED') {
			if (this.powerSaveBlockerId_) {
				bridge().powerSaveBlockerStop(this.powerSaveBlockerId_);
				this.powerSaveBlockerId_ = null;
			}
		}

		return result;
	}
Пример #4
0
		const renderKeyValue = (name) => {
			const md = Setting.settingMetadata(name);
			let value = Setting.value(name);
			if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
			if (md.secure) value = '********';

			if (Setting.isEnum(name)) {
				return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
			} else {
				return _('%s = %s', name, value);
			}
		}
Пример #5
0
testUnits.testConfig = async () => {
	await execCommand(client, 'config editor vim');
	await Setting.load();
	assertEquals('vim', Setting.value('editor'));

	await execCommand(client, 'config editor subl');
	await Setting.load();
	assertEquals('subl', Setting.value('editor'));

	let r = await execCommand(client, 'config');
	assertTrue(r.indexOf('editor') >= 0);
	assertTrue(r.indexOf('subl') >= 0);
}
Пример #6
0
	updateEditorFont() {
		const fontFamilies = [];
		if (Setting.value('style.editor.fontFamily')) fontFamilies.push('"' + Setting.value('style.editor.fontFamily') + '"');
		fontFamilies.push('monospace');

		// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
		// https://github.com/laurent22/joplin/issues/155

		const css = '.ace_editor * { font-family: ' + fontFamilies.join(', ') + ' !important; }';
		const styleTag = document.createElement('style');
		styleTag.type = 'text/css';
		styleTag.appendChild(document.createTextNode(css));
		document.head.appendChild(styleTag);
	}
Пример #7
0
	async enableEncryption(masterKey, password = null) {
		Setting.setValue('encryption.enabled', true);
		Setting.setValue('encryption.activeMasterKeyId', masterKey.id);

		if (password) {
			let passwordCache = Setting.value('encryption.passwordCache');
			passwordCache[masterKey.id] = password;	
			Setting.setValue('encryption.passwordCache', passwordCache);		
		}

		// Mark only the non-encrypted ones for sync since, if there are encrypted ones,
		// it means they come from the sync target and are already encrypted over there.
		await BaseItem.markAllNonEncryptedForSync();
	}
Пример #8
0
	updateTray() {
		const app = bridge().electronApp();

		if (app.trayShown() === Setting.value('showTrayIcon')) return;

		if (!Setting.value('showTrayIcon')) {
			app.destroyTray();
		} else {
			const contextMenu = Menu.buildFromTemplate([
				{ label: _('Open %s', app.electronApp().getName()), click: () => { app.window().show(); } },
				{ type: 'separator' },
				{ label: _('Exit'), click: () => { app.quit() } },
			])
			app.createTray(contextMenu);
		}
	}
Пример #9
0
	// Prepare the resource by encrypting it if needed.
	// The call returns the path to the physical file AND a representation of the resource object
	// as it should be uploaded to the sync target. Note that this may be different from what is stored
	// in the database. In particular, the flag encryption_blob_encrypted might be 1 on the sync target
	// if the resource is encrypted, but will be 0 locally because the device has the decrypted resource.
	static async fullPathForSyncUpload(resource) {
		const plainTextPath = this.fullPath(resource);

		if (!Setting.value('encryption.enabled')) {
			// Normally not possible since itemsThatNeedSync should only return decrypted items
			if (!!resource.encryption_blob_encrypted) throw new Error('Trying to access encrypted resource but encryption is currently disabled');
			return { path: plainTextPath, resource: resource };
		}

		const encryptedPath = this.fullPath(resource, true);
		if (resource.encryption_blob_encrypted) return { path: encryptedPath, resource: resource };

		try {
			// const stat = await this.fsDriver().stat(plainTextPath);
			await this.encryptionService().encryptFile(plainTextPath, encryptedPath, {
				// onProgress: (progress) => {
				// 	console.info(progress.doneSize / stat.size);
				// },
			});
		} catch (error) {
			if (error.code === 'ENOENT') throw new JoplinError('File not found:' + error.toString(), 'fileNotFound');
			throw error;
		}

		const resourceCopy = Object.assign({}, resource);
		resourceCopy.encryption_blob_encrypted = 1;
		return { path: encryptedPath, resource: resourceCopy };
	}
Пример #10
0
	determineProfileDir(initArgs) {
		if (initArgs.profileDir) return initArgs.profileDir;

		if (process && process.env && process.env.PORTABLE_EXECUTABLE_DIR) return process.env.PORTABLE_EXECUTABLE_DIR + '/JoplinProfile';

		return os.homedir() + '/.config/' + Setting.value('appName');
	}
Пример #11
0
shared.checkSyncConfig = async function(comp, settings) {
	const syncTargetId = settings['sync.target'];
	const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId);
	const options = Setting.subValues('sync.' + syncTargetId, settings);
	comp.setState({ checkSyncConfigResult: 'checking' });
	const result = await SyncTargetClass.checkConfig(ObjectUtils.convertValuesToFunctions(options));
	comp.setState({ checkSyncConfigResult: result });
}
Пример #12
0
	async action(args) {
		const verbose = args.options.verbose;

		const renderKeyValue = (name) => {
			const md = Setting.settingMetadata(name);
			let value = Setting.value(name);
			if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
			if (md.secure) value = '********';

			if (Setting.isEnum(name)) {
				return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
			} else {
				return _('%s = %s', name, value);
			}
		}

		if (!args.name && !args.value) {
			let keys = Setting.keys(!verbose, 'cli');
			keys.sort();
			for (let i = 0; i < keys.length; i++) {
				const value = Setting.value(keys[i]);
				if (!verbose && !value) continue;
				this.stdout(renderKeyValue(keys[i]));
			}
			app().gui().showConsole();
			app().gui().maximizeConsole();
			return;
		}

		if (args.name && !args.value) {
			this.stdout(renderKeyValue(args.name));
			app().gui().showConsole();
			app().gui().maximizeConsole();
			return;
		}

		Setting.setValue(args.name, args.value);

		if (args.name == 'locale') {
			setLocale(Setting.value('locale'));
			app().onLocaleChanged();
		}

		await Setting.saveAll();
	}
Пример #13
0
async function switchClient(id) {
	await time.msleep(sleepTime); // Always leave a little time so that updated_time properties don't overlap
	await Setting.saveAll();

	currentClient_ = id;
	BaseModel.db_ = databases_[id];
	Folder.db_ = databases_[id];
	Note.db_ = databases_[id];
	BaseItem.db_ = databases_[id];
	Setting.db_ = databases_[id];

	BaseItem.encryptionService_ = encryptionServices_[id];
	Resource.encryptionService_ = encryptionServices_[id];

	Setting.setConstant('resourceDir', resourceDir(id));

	return Setting.load();
}
Пример #14
0
	async initFileApi() {
		const syncPath = Setting.value('sync.2.path');
		const driver = new FileApiDriverLocal();
		const fileApi = new FileApi(syncPath, driver);
		fileApi.setLogger(this.logger());
		fileApi.setSyncTargetId(SyncTargetFilesystem.id());
		await driver.mkdir(syncPath);
		return fileApi;
	}
Пример #15
0
shared.settingsToComponents = function(comp, device, settings) {
	const keys = Setting.keys(true, device);
	const settingComps = [];

	for (let i = 0; i < keys.length; i++) {
		const key = keys[i];
		if (!Setting.isPublic(key)) continue;

		const md = Setting.settingMetadata(key);
		if (md.show && !md.show(settings)) continue;

		const settingComp = comp.settingToComponent(key, settings[key]);
		if (!settingComp) continue;
		settingComps.push(settingComp);
	}

	return settingComps
}
Пример #16
0
shared.saveSettings = function(comp) {
	for (let key in comp.state.settings) {
		if (!comp.state.settings.hasOwnProperty(key)) continue;
		if (comp.state.changedSettingKeys.indexOf(key) < 0) continue;
		console.info("Saving", key, comp.state.settings[key]);
		Setting.setValue(key, comp.state.settings[key]);
	}

	comp.setState({ changedSettingKeys: [] });
}
Пример #17
0
	switchCurrentFolder(folder) {
		if (!this.hasGui()) {
			this.currentFolder_ = Object.assign({}, folder);
			Setting.setValue('activeFolderId', folder ? folder.id : '');
		} else {
			this.dispatch({
				type: 'FOLDER_SELECT',
				id: folder ? folder.id : '',
			});
		}
	}
Пример #18
0
shared.updateSettingValue = function(comp, key, value) {
	const settings = Object.assign({}, comp.state.settings);
	const changedSettingKeys = comp.state.changedSettingKeys.slice();
	settings[key] = Setting.formatValue(key, value);
	if (changedSettingKeys.indexOf(key) < 0) changedSettingKeys.push(key);

	comp.setState({
		settings: settings,
		changedSettingKeys: changedSettingKeys,
	});
}
Пример #19
0
					click: () => {
						const p = packageInfo;
						let message = [
							p.description,
							'',
							'Copyright © 2016-2018 Laurent Cozic',
							_('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
						];
						bridge().showInfoMessageBox(message.join('\n'), {
							icon: bridge().electronApp().buildDir() + '/icons/32x32.png',
						});
					}
Пример #20
0
	async loadMasterKeysFromSettings() {
		const masterKeys = await MasterKey.all();
		const passwords = Setting.value('encryption.passwordCache');
		const activeMasterKeyId = Setting.value('encryption.activeMasterKeyId');

		this.logger().info('Trying to load ' + masterKeys.length + ' master keys...');

		for (let i = 0; i < masterKeys.length; i++) {
			const mk = masterKeys[i];
			const password = passwords[mk.id];
			if (this.isMasterKeyLoaded(mk.id)) continue;
			if (!password) continue;

			try {
				await this.loadMasterKey(mk, password, activeMasterKeyId === mk.id);
			} catch (error) {
				this.logger().warn('Cannot load master key ' + mk.id + '. Invalid password?', error);
			}
		}

		this.logger().info('Loaded master keys: ' + this.loadedMasterKeysCount());
	}
Пример #21
0
	async disableEncryption() {
		// Allow disabling encryption even if some items are still encrypted, because whether E2EE is enabled or disabled
		// should not affect whether items will enventually be decrypted or not (DecryptionWorker will still work as
		// long as there are encrypted items). Also even if decryption is disabled, it's possible that encrypted items
		// will still be received via synchronisation.

		// const hasEncryptedItems = await BaseItem.hasEncryptedItems();
		// if (hasEncryptedItems) throw new Error(_('Encryption cannot currently be disabled because some items are still encrypted. Please wait for all the items to be decrypted and try again.'));
		
		Setting.setValue('encryption.enabled', false);
		// The only way to make sure everything gets decrypted on the sync target is
		// to re-sync everything.
		await BaseItem.forceSyncAll();
	}
Пример #22
0
	static currentPosition(options = null) {
		if (Setting.value('env') == 'dev') return this.currentPosition_testResponse();

		if (!options) options = {};
		if (!('enableHighAccuracy' in options)) options.enableHighAccuracy = true;
		if (!('timeout' in options)) options.timeout = 10000;

		return new Promise((resolve, reject) => {
			navigator.geolocation.getCurrentPosition((data) => {
				resolve(data);
			}, (error) => {
				reject(error);
			}, options);
		});
	}
Пример #23
0
	async generateMasterKey(password) {
		const bytes = await shim.randomBytes(256);
		const hexaBytes = bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
		const checksum = this.sha256(hexaBytes);
		const encryptionMethod = EncryptionService.METHOD_SJCL_2;
		const cipherText = await this.encrypt(encryptionMethod, password, hexaBytes);
		const now = Date.now();

		return {
			created_time: now,
			updated_time: now,
			source_application: Setting.value('appId'),
			encryption_method: encryptionMethod,
			checksum: checksum,
			content: cipherText,
		};
	}
Пример #24
0
async function main(argv) {
	await fs.remove(baseDir);

	logger.info(await execCommand(client, 'version'));

	await db.open({ name: client.profileDir + '/database.sqlite' });
	BaseModel.db_ = db;
	await Setting.load();

	let onlyThisTest = 'testMv';
	onlyThisTest = '';

	for (let n in testUnits) {
		if (!testUnits.hasOwnProperty(n)) continue;
		if (onlyThisTest && n != onlyThisTest) continue;

		await clearDatabase();
		let testName = n.substr(4).toLowerCase();
		process.stdout.write(testName + ': ');
		await testUnits[n]();
		console.info('');
	}
}
Пример #25
0
			const runAutoUpdateCheck = () => {
				if (Setting.value('autoUpdateEnabled')) {
					bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath());
				}
			}
Пример #26
0
	async exit(code = 0) {
		await Setting.saveAll();
		process.exit(code);
	}
Пример #27
0
	// Handles the initial flags passed to main script and
	// returns the remaining args.
	async handleStartFlags_(argv, setDefaults = true) {
		let matched = {};
		argv = argv.slice(0);
		argv.splice(0, 2); // First arguments are the node executable, and the node JS file

		while (argv.length) {
			let arg = argv[0];
			let nextArg = argv.length >= 2 ? argv[1] : null;

			if (arg == '--profile') {
				if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile <dir-path>'), 'flagError');
				matched.profileDir = nextArg;
				argv.splice(0, 2);
				continue;
			}

			if (arg == '--env') {
				if (!nextArg) throw new JoplinError(_('Usage: %s', '--env <dev|prod>'), 'flagError');
				matched.env = nextArg;
				argv.splice(0, 2);
				continue;
			}

			if (arg == '--is-demo') {
				Setting.setConstant('isDemo', true);
				argv.splice(0, 1);
				continue;
			}

			if (arg == '--open-dev-tools') {
				Setting.setConstant('openDevTools', true);
				argv.splice(0, 1);
				continue;
			}

			if (arg == '--update-geolocation-disabled') {
				Note.updateGeolocationEnabled_ = false;
				argv.splice(0, 1);
				continue;
			}

			if (arg == '--stack-trace-enabled') {
				this.showStackTraces_ = true;
				argv.splice(0, 1);
				continue;
			}

			if (arg == '--log-level') {
				if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level <none|error|warn|info|debug>'), 'flagError');
				matched.logLevel = Logger.levelStringToId(nextArg);
				argv.splice(0, 2);
				continue;
			}

			if (arg.indexOf('-psn') === 0) {
				// Some weird flag passed by macOS - can be ignored.
				// https://github.com/laurent22/joplin/issues/480
				// https://stackoverflow.com/questions/10242115
				argv.splice(0, 1);
				continue;
			}

			if (arg.length && arg[0] == '-') {
				throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
			} else {
				break;
			}
		}

		if (setDefaults) {
			if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO;
			if (!matched.env) matched.env = 'prod';
		}

		return {
			matched: matched,
			argv: argv,
		};
	}
Пример #28
0
	async generalMiddleware(store, next, action) {
		this.logger().debug('Reducer action', this.reducerActionToString(action));

		const result = next(action);
		const newState = store.getState();
		let refreshNotes = false;

		reduxSharedMiddleware(store, next, action);

		if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE' || (action.type === 'SEARCH_UPDATE' && newState.notesParentType === 'Folder')) {
			Setting.setValue('activeFolderId', newState.selectedFolderId);
			this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
			refreshNotes = true;
		}

		if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) {
			refreshNotes = true;
		}

		if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'showCompletedTodos') || action.type == 'SETTING_UPDATE_ALL')) {
			refreshNotes = true;
		}

		if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) {
			refreshNotes = true;
		}

		if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
			refreshNotes = true;
		}

		if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
			refreshNotes = true;
		}

		if (refreshNotes) {
			await this.refreshNotes(newState);
		}

		if ((action.type == 'SETTING_UPDATE_ONE' && (action.key == 'dateFormat' || action.key == 'timeFormat')) || (action.type == 'SETTING_UPDATE_ALL')) {
			time.setDateFormat(Setting.value('dateFormat'));
			time.setTimeFormat(Setting.value('timeFormat'));
		}

		if ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'net.ignoreTlsErrors') || (action.type == 'SETTING_UPDATE_ALL')) {
			// https://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature
			process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = Setting.value('net.ignoreTlsErrors') ? '0' : '1';
		}

		if ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'net.customCertificates') || (action.type == 'SETTING_UPDATE_ALL')) {
			const caPaths = Setting.value('net.customCertificates').split(',');
			for (let i = 0; i < caPaths.length; i++) {
				const f = caPaths[i].trim();
				if (!f) continue;
				syswidecas.addCAs(f);
			}
		}

		if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) {
			if (this.hasGui()) {
				await EncryptionService.instance().loadMasterKeysFromSettings();
				DecryptionWorker.instance().scheduleStart();
				const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds();

				this.dispatch({
					type: 'MASTERKEY_REMOVE_NOT_LOADED',
					ids: loadedMasterKeyIds,
				});

				// Schedule a sync operation so that items that need to be encrypted
				// are sent to sync target.
				reg.scheduleSync();
			}
		}

		if (action.type === 'NOTE_UPDATE_ONE') {
			// If there is a conflict, we refresh the folders so as to display "Conflicts" folder
			if (action.note && action.note.is_conflict) {
				await FoldersScreenUtils.refreshFolders();
			}
		}

		if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
			reg.setupRecurrentSync();
		}

		if (this.hasGui() && action.type === 'SYNC_GOT_ENCRYPTED_ITEM') {
			DecryptionWorker.instance().scheduleStart();
		}

	  	return result;
	}
Пример #29
0
			EncryptionService.instance().randomHexString(64).then((token) => {
				Setting.setValue('api.token', token);
			});
Пример #30
0
	async start(argv) {
		let startFlags = await this.handleStartFlags_(argv);

		argv = startFlags.argv;
		let initArgs = startFlags.matched;
		if (argv.length) this.showPromptString_ = false;

		let appName = initArgs.env == 'dev' ? 'joplindev' : 'joplin';
		if (Setting.value('appId').indexOf('-desktop') >= 0) appName += '-desktop';
		Setting.setConstant('appName', appName);

		const profileDir = this.determineProfileDir(initArgs);
		const resourceDir = profileDir + '/resources';
		const tempDir = profileDir + '/tmp';

		Setting.setConstant('env', initArgs.env);
		Setting.setConstant('profileDir', profileDir);
		Setting.setConstant('resourceDir', resourceDir);
		Setting.setConstant('tempDir', tempDir);

		await shim.fsDriver().remove(tempDir);

		await fs.mkdirp(profileDir, 0o755);
		await fs.mkdirp(resourceDir, 0o755);
		await fs.mkdirp(tempDir, 0o755);

		const extraFlags = await this.readFlagsFromFile(profileDir + '/flags.txt');
		initArgs = Object.assign(initArgs, extraFlags);

		this.logger_.addTarget('file', { path: profileDir + '/log.txt' });
		//this.logger_.addTarget('console');
		this.logger_.setLevel(initArgs.logLevel);

		reg.setLogger(this.logger_);
		reg.dispatch = (o) => {};

		this.dbLogger_.addTarget('file', { path: profileDir + '/log-database.txt' });
		this.dbLogger_.setLevel(initArgs.logLevel);

		if (Setting.value('env') === 'dev') {
			this.dbLogger_.setLevel(Logger.LEVEL_WARN);
		}

		this.logger_.info('Profile directory: ' + profileDir);

		this.database_ = new JoplinDatabase(new DatabaseDriverNode());
		this.database_.setLogExcludedQueryTypes(['SELECT']);
		this.database_.setLogger(this.dbLogger_);
		await this.database_.open({ name: profileDir + '/database.sqlite' });

		reg.setDb(this.database_);
		BaseModel.db_ = this.database_;

		await Setting.load();

		if (Setting.value('firstStart')) {
			const locale = shim.detectAndSetLocale(Setting);
			reg.logger().info('First start: detected locale as ' + locale);

			if (Setting.value('env') === 'dev') {
				Setting.setValue('showTrayIcon', 0);
				Setting.setValue('autoUpdateEnabled', 0);
				Setting.setValue('sync.interval', 3600);
			}

			Setting.setValue('firstStart', 0);
		} else {
			setLocale(Setting.value('locale'));
		}

		if (!Setting.value('api.token')) {
			EncryptionService.instance().randomHexString(64).then((token) => {
				Setting.setValue('api.token', token);
			});
		}

		time.setDateFormat(Setting.value('dateFormat'));
		time.setTimeFormat(Setting.value('timeFormat'));

		BaseService.logger_ = this.logger_;
		EncryptionService.instance().setLogger(this.logger_);
		BaseItem.encryptionService_ = EncryptionService.instance();
		DecryptionWorker.instance().setLogger(this.logger_);
		DecryptionWorker.instance().setEncryptionService(EncryptionService.instance());
		await EncryptionService.instance().loadMasterKeysFromSettings();

		let currentFolderId = Setting.value('activeFolderId');
		let currentFolder = null;
		if (currentFolderId) currentFolder = await Folder.load(currentFolderId);
		if (!currentFolder) currentFolder = await Folder.defaultFolder();
		Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : '');

		// await this.testing();process.exit();

		return argv;
	}