You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Fixes for release apk
This commit is contained in:
		| @@ -350,8 +350,8 @@ function importEnex(parentFolderId, filePath, importOptions = null) { | ||||
| 					id: noteResource.id, | ||||
| 					data: decodedData, | ||||
| 					mime: noteResource.mime, | ||||
| 					title: noteResource.filename, | ||||
| 					filename: noteResource.filename, | ||||
| 					title: noteResource.filename ? noteResource.filename : '', | ||||
| 					filename: noteResource.filename ? noteResource.filename : '', | ||||
| 				}; | ||||
|  | ||||
| 				note.resources.push(r); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js'; | ||||
| import { FileApiDriverMemory } from 'lib/file-api-driver-memory.js'; | ||||
| import { FileApiDriverLocal } from 'lib/file-api-driver-local.js'; | ||||
| import { OneDriveApiNodeUtils } from './onedrive-api-node-utils.js'; | ||||
| import { JoplinDatabase } from 'lib/joplin-database.js'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { DatabaseDriverNode } from 'lib/database-driver-node.js'; | ||||
| import { BaseModel } from 'lib/base-model.js'; | ||||
| @@ -927,7 +928,12 @@ async function main() { | ||||
| 	await fs.mkdirp(resourceDir, 0o755); | ||||
| 	await fs.mkdirp(tempDir, 0o755); | ||||
|  | ||||
| 	// let logDatabase = new Database(new DatabaseDriverNode()); | ||||
| 	// await logDatabase.open({ name: profileDir + '/database-log.sqlite' }); | ||||
| 	// await logDatabase.exec(Logger.databaseCreateTableSql()); | ||||
|  | ||||
| 	logger.addTarget('file', { path: profileDir + '/log.txt' }); | ||||
| 	// logger.addTarget('database', { database: logDatabase, source: 'main' }); | ||||
| 	logger.setLevel(logLevel); | ||||
|  | ||||
| 	dbLogger.addTarget('file', { path: profileDir + '/log-database.txt' }); | ||||
| @@ -947,7 +953,7 @@ async function main() { | ||||
| 	BaseItem.loadClass('Tag', Tag); | ||||
| 	BaseItem.loadClass('NoteTag', NoteTag); | ||||
|  | ||||
| 	database_ = new Database(new DatabaseDriverNode()); | ||||
| 	database_ = new JoplinDatabase(new DatabaseDriverNode()); | ||||
| 	database_.setLogger(dbLogger); | ||||
| 	await database_.open({ name: profileDir + '/database.sqlite' }); | ||||
| 	BaseModel.db_ = database_; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import fs from 'fs-extra'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { JoplinDatabase } from 'lib/joplin-database.js'; | ||||
| import { DatabaseDriverNode } from 'lib/database-driver-node.js'; | ||||
| import { BaseModel } from 'lib/base-model.js'; | ||||
| import { Folder } from 'lib/models/folder.js'; | ||||
| @@ -25,8 +25,11 @@ const fsDriver = new FsDriverNode(); | ||||
| Logger.fsDriver_ = fsDriver; | ||||
| Resource.fsDriver_ = fsDriver; | ||||
|  | ||||
| const logDir = __dirname + '/../tests/logs'; | ||||
| fs.mkdirpSync(logDir, 0o755); | ||||
|  | ||||
| const logger = new Logger(); | ||||
| logger.addTarget('file', { path: __dirname + '/../tests/logs/log.txt' }); | ||||
| logger.addTarget('file', { path: logDir + '/log.txt' }); | ||||
| logger.setLevel(Logger.LEVEL_DEBUG); | ||||
|  | ||||
| BaseItem.loadClass('Note', Note); | ||||
| @@ -87,7 +90,7 @@ function setupDatabase(id = null) { | ||||
| 	return fs.unlink(filePath).catch(() => { | ||||
| 		// Don't care if the file doesn't exist | ||||
| 	}).then(() => { | ||||
| 		databases_[id] = new Database(new DatabaseDriverNode()); | ||||
| 		databases_[id] = new JoplinDatabase(new DatabaseDriverNode()); | ||||
| 		databases_[id].setLogger(logger); | ||||
| 		return databases_[id].open({ name: filePath }).then(() => { | ||||
| 			BaseModel.db_ = databases_[id]; | ||||
|   | ||||
| @@ -83,62 +83,73 @@ def enableSeparateBuildPerCPUArchitecture = false | ||||
| def enableProguardInReleaseBuilds = false | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 23 | ||||
|     buildToolsVersion "23.0.1" | ||||
| 	compileSdkVersion 23 | ||||
| 	buildToolsVersion "23.0.1" | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "com.awesomeproject" | ||||
|         minSdkVersion 16 | ||||
|         targetSdkVersion 22 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         ndk { | ||||
|             abiFilters "armeabi-v7a", "x86" | ||||
|         } | ||||
|     } | ||||
|     splits { | ||||
|         abi { | ||||
|             reset() | ||||
|             enable enableSeparateBuildPerCPUArchitecture | ||||
|             universalApk false  // If true, also generate a universal APK | ||||
|             include "armeabi-v7a", "x86" | ||||
|         } | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled enableProguardInReleaseBuilds | ||||
|             proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" | ||||
|         } | ||||
|     } | ||||
|     // applicationVariants are e.g. debug, release | ||||
|     applicationVariants.all { variant -> | ||||
|         variant.outputs.each { output -> | ||||
|             // For each separate APK per architecture, set a unique version code as described here: | ||||
|             // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits | ||||
|             def versionCodes = ["armeabi-v7a":1, "x86":2] | ||||
|             def abi = output.getFilter(OutputFile.ABI) | ||||
|             if (abi != null) {  // null for the universal-debug, universal-release variants | ||||
|                 output.versionCodeOverride = | ||||
|                         versionCodes.get(abi) * 1048576 + defaultConfig.versionCode | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 	defaultConfig { | ||||
| 		applicationId "com.awesomeproject" | ||||
| 		minSdkVersion 16 | ||||
| 		targetSdkVersion 22 | ||||
| 		versionCode 1 | ||||
| 		versionName "1.0" | ||||
| 		ndk { | ||||
| 			abiFilters "armeabi-v7a", "x86" | ||||
| 		} | ||||
| 	} | ||||
| 	splits { | ||||
| 		abi { | ||||
| 			reset() | ||||
| 			enable enableSeparateBuildPerCPUArchitecture | ||||
| 			universalApk false  // If true, also generate a universal APK | ||||
| 			include "armeabi-v7a", "x86" | ||||
| 		} | ||||
| 	} | ||||
| 	signingConfigs { | ||||
| 		release { | ||||
| 			if (project.hasProperty('JOPLIN_RELEASE_STORE_FILE')) { | ||||
| 				storeFile file(JOPLIN_RELEASE_STORE_FILE) | ||||
| 				storePassword JOPLIN_RELEASE_STORE_PASSWORD | ||||
| 				keyAlias JOPLIN_RELEASE_KEY_ALIAS | ||||
| 				keyPassword JOPLIN_RELEASE_KEY_PASSWORD | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	buildTypes { | ||||
| 		release { | ||||
| 			minifyEnabled enableProguardInReleaseBuilds | ||||
| 			proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" | ||||
| 			signingConfig signingConfigs.release | ||||
| 		} | ||||
| 	} | ||||
| 	// applicationVariants are e.g. debug, release | ||||
| 	applicationVariants.all { variant -> | ||||
| 		variant.outputs.each { output -> | ||||
| 			// For each separate APK per architecture, set a unique version code as described here: | ||||
| 			// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits | ||||
| 			def versionCodes = ["armeabi-v7a":1, "x86":2] | ||||
| 			def abi = output.getFilter(OutputFile.ABI) | ||||
| 			if (abi != null) {  // null for the universal-debug, universal-release variants | ||||
| 				output.versionCodeOverride = | ||||
| 						versionCodes.get(abi) * 1048576 + defaultConfig.versionCode | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     compile project(':react-native-fs') | ||||
|     compile fileTree(dir: "libs", include: ["*.jar"]) | ||||
|     compile "com.android.support:appcompat-v7:23.0.1" | ||||
|     compile "com.facebook.react:react-native:+"  // From node_modules | ||||
|     compile project(':react-native-sqlite-storage') | ||||
|     compile project(':react-native-fetch-blob') | ||||
| 	compile project(':react-native-fs') | ||||
| 	compile fileTree(dir: "libs", include: ["*.jar"]) | ||||
| 	compile "com.android.support:appcompat-v7:23.0.1" | ||||
| 	compile "com.facebook.react:react-native:+"  // From node_modules | ||||
| 	compile project(':react-native-sqlite-storage') | ||||
| 	compile project(':react-native-fetch-blob') | ||||
| } | ||||
|  | ||||
| // Run this once to be able to run the application with BUCK | ||||
| // puts all compile dependencies into folder libs for BUCK to use | ||||
| task copyDownloadableDepsToLibs(type: Copy) { | ||||
|     from configurations.compile | ||||
|     into 'libs' | ||||
| 	from configurations.compile | ||||
| 	into 'libs' | ||||
| } | ||||
|  | ||||
| apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" | ||||
|   | ||||
| @@ -4,12 +4,12 @@ import com.facebook.react.ReactActivity; | ||||
|  | ||||
| public class MainActivity extends ReactActivity { | ||||
|  | ||||
|     /** | ||||
|      * Returns the name of the main component registered from JavaScript. | ||||
|      * This is used to schedule rendering of the component. | ||||
|      */ | ||||
|     @Override | ||||
|     protected String getMainComponentName() { | ||||
|         return "AwesomeProject"; | ||||
|     } | ||||
| 	/** | ||||
| 	 * Returns the name of the main component registered from JavaScript. | ||||
| 	 * This is used to schedule rendering of the component. | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	protected String getMainComponentName() { | ||||
| 		return "AwesomeProject"; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -37,6 +37,7 @@ class ItemListComponent extends Component { | ||||
| 		await Note.save({ id: note.id, todo_completed: checked }); | ||||
| 	} | ||||
|  | ||||
| 	listView_itemLongPress(itemId) {} | ||||
| 	listView_itemPress(itemId) {} | ||||
|  | ||||
| 	render() { | ||||
| @@ -50,8 +51,8 @@ class ItemListComponent extends Component { | ||||
|  | ||||
| 			return ( | ||||
| 				<TouchableHighlight onPress={onPress} onLongPress={onLongPress}> | ||||
| 					<View style={{flexDirection: 'row'}}> | ||||
| 						{ !!Number(item.is_todo) && <Checkbox checked={!!Number(item.todo_completed)} onChange={(checked) => { this.todoCheckbox_change(item.id, checked) }}/> }<Text>{item.title} [{item.id}]</Text> | ||||
| 					<View style={{flexDirection: 'row', paddingLeft: 10, paddingTop:5, paddingBottom:5 }}> | ||||
| 						{ !!Number(item.is_todo) && <Checkbox checked={!!Number(item.todo_completed)} onChange={(checked) => { this.todoCheckbox_change(item.id, checked) }}/> }<Text>{item.title}</Text> | ||||
| 					</View> | ||||
| 				</TouchableHighlight> | ||||
| 			); | ||||
|   | ||||
| @@ -41,22 +41,6 @@ class ScreenHeaderComponent extends Component { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async menu_synchronize() { | ||||
| 		if (reg.oneDriveApi().auth()) { | ||||
| 			const sync = await reg.synchronizer(); | ||||
| 			try { | ||||
| 				sync.start(); | ||||
| 			} catch (error) { | ||||
| 				Log.error(error); | ||||
| 			} | ||||
| 		} else { | ||||
| 			this.props.dispatch({ | ||||
| 				type: 'Navigation/NAVIGATE', | ||||
| 				routeName: 'OneDriveLogin', | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let key = 0; | ||||
| 		let menuOptionComponents = []; | ||||
| @@ -72,15 +56,10 @@ class ScreenHeaderComponent extends Component { | ||||
| 			menuOptionComponents.push(<View key={'menuOption_' + key++} style={styles.divider}/>); | ||||
| 		} | ||||
|  | ||||
| 		menuOptionComponents.push( | ||||
| 			<MenuOption value={() => this.menu_synchronize()} key={'menuOption_' + key++}> | ||||
| 				<Text>{_('Synchronize')}</Text> | ||||
| 			</MenuOption>); | ||||
|  | ||||
| 		menuOptionComponents.push( | ||||
| 			<MenuOption value={1} key={'menuOption_' + key++}> | ||||
| 				<Text>{_('Configuration')}</Text> | ||||
| 			</MenuOption>); | ||||
| 		// menuOptionComponents.push( | ||||
| 		// 	<MenuOption value={1} key={'menuOption_' + key++}> | ||||
| 		// 		<Text>{_('Configuration')}</Text> | ||||
| 		// 	</MenuOption>); | ||||
|  | ||||
| 		let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName); | ||||
|  | ||||
|   | ||||
| @@ -85,7 +85,7 @@ class NoteScreenComponent extends React.Component { | ||||
| 				<View style={{ flexDirection: 'row' }}> | ||||
| 					{ isTodo && <Checkbox checked={!!Number(note.todo_completed)} /> }<TextInput style={{flex:1}} value={note.title} onChangeText={(text) => this.title_changeText(text)} /> | ||||
| 				</View> | ||||
| 				<TextInput style={{flex: 1, textAlignVertical: 'top'}} multiline={true} value={note.body} onChangeText={(text) => this.body_changeText(text)} /> | ||||
| 				<TextInput style={{flex: 1, textAlignVertical: 'top', fontFamily: 'monospace'}} multiline={true} value={note.body} onChangeText={(text) => this.body_changeText(text)} /> | ||||
| 				{ todoComponents } | ||||
| 				<Button title="Save note" onPress={() => this.saveNoteButton_press()} /> | ||||
| 			</View> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import { View } from 'react-native'; | ||||
| import { WebView, Button } from 'react-native'; | ||||
| import { WebView, Button, Text } from 'react-native'; | ||||
| import { connect } from 'react-redux' | ||||
| import { Log } from 'lib/log.js' | ||||
| import { Setting } from 'lib/models/setting.js' | ||||
| @@ -22,10 +22,14 @@ class OneDriveLoginScreenComponent extends React.Component { | ||||
|  | ||||
| 	componentWillMount() { | ||||
| 		this.setState({ | ||||
| 			webviewUrl: reg.oneDriveApi().authCodeUrl(this.redirectUrl()), | ||||
| 			webviewUrl: this.startUrl(), | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	startUrl() { | ||||
| 		return reg.oneDriveApi().authCodeUrl(this.redirectUrl()); | ||||
| 	} | ||||
|  | ||||
| 	redirectUrl() { | ||||
| 		return 'https://login.microsoftonline.com/common/oauth2/nativeclient'; | ||||
| 	} | ||||
| @@ -37,7 +41,7 @@ class OneDriveLoginScreenComponent extends React.Component { | ||||
| 		const url = noIdeaWhatThisIs.url; | ||||
|  | ||||
| 		if (!this.authCode_ && url.indexOf(this.redirectUrl() + '?code=') === 0) { | ||||
| 			console.info('URL: ' + url); | ||||
| 			Log.info('URL: ' + url); | ||||
|  | ||||
| 			let code = url.split('?code='); | ||||
| 			this.authCode_ = code[1]; | ||||
| @@ -48,6 +52,28 @@ class OneDriveLoginScreenComponent extends React.Component { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async webview_error(error) { | ||||
| 		Log.error(error); | ||||
| 	} | ||||
|  | ||||
| 	retryButton_click() { | ||||
| 		// It seems the only way it would reload the page is by loading an unrelated | ||||
| 		// URL, waiting a bit, and then loading the actual URL. There's probably | ||||
| 		// a better way to do this. | ||||
|  | ||||
| 		this.setState({ | ||||
| 			webviewUrl: 'https://microsoft.com', | ||||
| 		}); | ||||
| 		this.forceUpdate(); | ||||
|  | ||||
| 		setTimeout(() => { | ||||
| 			this.setState({ | ||||
| 				webviewUrl: this.startUrl(), | ||||
| 			}); | ||||
| 			this.forceUpdate(); | ||||
| 		}, 1000); | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		const source = { | ||||
| 			uri: this.state.webviewUrl, | ||||
| @@ -60,7 +86,10 @@ class OneDriveLoginScreenComponent extends React.Component { | ||||
| 					source={source} | ||||
| 					style={{marginTop: 20}} | ||||
| 					onNavigationStateChange={(o) => { this.webview_load(o); }} | ||||
| 					onError={(error) => { this.webview_error(error); }} | ||||
| 				/> | ||||
|  | ||||
| 				<Button title="Retry" onPress={() => { this.retryButton_click(); }}></Button> | ||||
| 			</View> | ||||
| 		); | ||||
| 	} | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import { connect } from 'react-redux' | ||||
| import { Button } from 'react-native'; | ||||
| import { Button, Text } from 'react-native'; | ||||
| import { Log } from 'lib/log.js'; | ||||
| import { Note } from 'lib/models/note.js'; | ||||
| import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js' | ||||
| import { reg } from 'lib/registry.js'; | ||||
| import { _ } from 'lib/locale.js'; | ||||
|  | ||||
| const React = require('react'); | ||||
| const { | ||||
| @@ -11,7 +13,6 @@ const { | ||||
| 	ScrollView, | ||||
| 	View, | ||||
| 	Image, | ||||
| 	Text, | ||||
| } = require('react-native'); | ||||
| const { Component } = React; | ||||
|  | ||||
| @@ -41,6 +42,11 @@ const styles = StyleSheet.create({ | ||||
|  | ||||
| class SideMenuContentComponent extends Component { | ||||
|  | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		this.state = { syncReportText: '' }; | ||||
| 	} | ||||
|  | ||||
| 	folder_press(folder) { | ||||
| 		this.props.dispatch({ | ||||
| 			type: 'SIDE_MENU_CLOSE', | ||||
| @@ -49,19 +55,57 @@ class SideMenuContentComponent extends Component { | ||||
| 		NotesScreenUtils.openNoteList(folder.id); | ||||
| 	} | ||||
|  | ||||
| 	async synchronize_press() { | ||||
| 		if (reg.oneDriveApi().auth()) { | ||||
| 			let options = { | ||||
| 				onProgress: (report) => { | ||||
| 					let line = []; | ||||
| 					line.push(_('Items to upload: %d/%d.', report.createRemote + report.updateRemote, report.remotesToUpdate)); | ||||
| 					line.push(_('Remote items to delete: %d/%d.', report.deleteRemote, report.remotesToDelete)); | ||||
| 					line.push(_('Items to download: %d/%d.', report.createLocal + report.updateLocal, report.localsToUdpate)); | ||||
| 					line.push(_('Local items to delete: %d/%d.', report.deleteLocal, report.localsToDelete)); | ||||
| 					this.setState({	syncReportText: line.join("\n") }); | ||||
| 				}, | ||||
| 			}; | ||||
|  | ||||
| 			try { | ||||
| 				const sync = await reg.synchronizer() | ||||
| 				sync.start(options); | ||||
| 			} catch (error) { | ||||
| 				Log.error(error); | ||||
| 			} | ||||
| 		} else { | ||||
| 			this.props.dispatch({ | ||||
| 				type: 'Navigation/NAVIGATE', | ||||
| 				routeName: 'OneDriveLogin', | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let buttons = []; | ||||
| 		let keyIndex = 0; | ||||
| 		let key = () => { | ||||
| 			return 'smitem_' + (keyIndex++); | ||||
| 		} | ||||
|  | ||||
| 		let items = []; | ||||
| 		for (let i = 0; i < this.props.folders.length; i++) { | ||||
| 			let f = this.props.folders[i]; | ||||
| 			let title = f.title ? f.title : ''; | ||||
| 			buttons.push( | ||||
| 				<Button style={styles.button} title={title} onPress={() => { this.folder_press(f) }} key={f.id} /> | ||||
| 			items.push( | ||||
| 				<Button style={styles.button} title={title} onPress={() => { this.folder_press(f) }} key={key()} /> | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		items.push(<Text key={key()}></Text>); // DIVIDER | ||||
|  | ||||
| 		items.push(<Button style={styles.button} title="Synchronize" onPress={() => { this.synchronize_press() }} key={key()} />); | ||||
|  | ||||
| 		items.push(<Text key={key()}>{this.state.syncReportText}</Text>); | ||||
|  | ||||
| 		return ( | ||||
| 			<ScrollView scrollsToTop={false} style={styles.menu}> | ||||
| 				{ buttons } | ||||
| 				{ items } | ||||
| 			</ScrollView> | ||||
| 		); | ||||
| 	} | ||||
|   | ||||
| @@ -2,120 +2,16 @@ import { uuid } from 'lib/uuid.js'; | ||||
| import { promiseChain } from 'lib/promise-utils.js'; | ||||
| import { Logger } from 'lib/logger.js' | ||||
| import { time } from 'lib/time-utils.js' | ||||
| import { _ } from 'lib/locale.js' | ||||
| import { sprintf } from 'sprintf-js'; | ||||
|  | ||||
| const structureSql = ` | ||||
| CREATE TABLE folders ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX folders_title ON folders (title); | ||||
| CREATE INDEX folders_updated_time ON folders (updated_time); | ||||
| CREATE INDEX folders_sync_time ON folders (sync_time); | ||||
|  | ||||
| CREATE TABLE notes ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	body TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0, | ||||
| 	is_conflict INT NOT NULL DEFAULT 0, | ||||
| 	latitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	longitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	altitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	author TEXT NOT NULL DEFAULT "", | ||||
| 	source_url TEXT NOT NULL DEFAULT "", | ||||
| 	is_todo INT NOT NULL DEFAULT 0, | ||||
| 	todo_due INT NOT NULL DEFAULT 0, | ||||
| 	todo_completed INT NOT NULL DEFAULT 0, | ||||
| 	source TEXT NOT NULL DEFAULT "", | ||||
| 	source_application TEXT NOT NULL DEFAULT "", | ||||
| 	application_data TEXT NOT NULL DEFAULT "", | ||||
| 	\`order\` INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX notes_title ON notes (title); | ||||
| CREATE INDEX notes_updated_time ON notes (updated_time); | ||||
| CREATE INDEX notes_sync_time ON notes (sync_time); | ||||
| CREATE INDEX notes_is_conflict ON notes (is_conflict); | ||||
| CREATE INDEX notes_is_todo ON notes (is_todo); | ||||
| CREATE INDEX notes_order ON notes (\`order\`); | ||||
|  | ||||
| CREATE TABLE deleted_items ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	item_type INT NOT NULL, | ||||
| 	item_id TEXT NOT NULL, | ||||
| 	deleted_time INT NOT NULL | ||||
| ); | ||||
|  | ||||
| CREATE TABLE tags ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE TABLE note_tags ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	note_id TEXT NOT NULL, | ||||
| 	tag_id TEXT NOT NULL, | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE TABLE resources ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	mime TEXT NOT NULL, | ||||
| 	filename TEXT NOT NULL, | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE TABLE settings ( | ||||
| 	\`key\` TEXT PRIMARY KEY, | ||||
| 	\`value\` TEXT, | ||||
| 	\`type\` INT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE table_fields ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	table_name TEXT, | ||||
| 	field_name TEXT, | ||||
| 	field_type INT, | ||||
| 	field_default TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE version ( | ||||
| 	version INT | ||||
| ); | ||||
|  | ||||
| INSERT INTO version (version) VALUES (1); | ||||
| `; | ||||
|  | ||||
| class Database { | ||||
|  | ||||
| 	constructor(driver) { | ||||
| 		this.debugMode_ = false; | ||||
| 		this.initialized_ = false; | ||||
| 		this.tableFields_ = null; | ||||
| 		this.driver_ = driver; | ||||
| 		this.inTransaction_ = false; | ||||
|  | ||||
| 		this.logger_ = new Logger(); | ||||
| 		this.logger_.addTarget('console'); | ||||
| 		this.logger_.setLevel(Logger.LEVEL_DEBUG); | ||||
| 	} | ||||
|  | ||||
| 	// Converts the SQLite error to a regular JS error | ||||
| @@ -133,10 +29,6 @@ class Database { | ||||
| 		return this.logger_; | ||||
| 	} | ||||
|  | ||||
| 	initialized() { | ||||
| 		return this.initialized_; | ||||
| 	} | ||||
|  | ||||
| 	driver() { | ||||
| 		return this.driver_; | ||||
| 	} | ||||
| @@ -144,7 +36,6 @@ class Database { | ||||
| 	async open(options) { | ||||
| 		await this.driver().open(options); | ||||
| 		this.logger().info('Database was open successfully'); | ||||
| 		return this.initialize(); | ||||
| 	} | ||||
|  | ||||
| 	escapeField(field) { | ||||
| @@ -251,26 +142,12 @@ class Database { | ||||
| 			if (s == 'string') return 2; | ||||
| 		} | ||||
| 		if (type == 'fieldType') { | ||||
| 			if (s == 'INTEGER') s = 'INT'; | ||||
| 			return this['TYPE_' + s]; | ||||
| 		} | ||||
| 		throw new Error('Unknown enum type or value: ' + type + ', ' + s); | ||||
| 	} | ||||
|  | ||||
| 	tableFieldNames(tableName) { | ||||
| 		let tf = this.tableFields(tableName); | ||||
| 		let output = []; | ||||
| 		for (let i = 0; i < tf.length; i++) { | ||||
| 			output.push(tf[i].name); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	tableFields(tableName) { | ||||
| 		if (!this.tableFields_) throw new Error('Fields have not been loaded yet'); | ||||
| 		if (!this.tableFields_[tableName]) throw new Error('Unknown table: ' + tableName); | ||||
| 		return this.tableFields_[tableName]; | ||||
| 	} | ||||
|  | ||||
| 	static formatValue(type, value) { | ||||
| 		if (value === null || value === undefined) return null; | ||||
| 		if (type == this.TYPE_INT) return Number(value); | ||||
| @@ -369,98 +246,6 @@ class Database { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	refreshTableFields() { | ||||
| 		this.logger().info('Initializing tables...'); | ||||
| 		let queries = []; | ||||
| 		queries.push(this.wrapQuery('DELETE FROM table_fields')); | ||||
|  | ||||
| 		return this.selectAll('SELECT name FROM sqlite_master WHERE type="table"').then((tableRows) => { | ||||
| 			let chain = []; | ||||
| 			for (let i = 0; i < tableRows.length; i++) { | ||||
| 				let tableName = tableRows[i].name; | ||||
| 				if (tableName == 'android_metadata') continue; | ||||
| 				if (tableName == 'table_fields') continue; | ||||
| 				chain.push(() => { | ||||
| 					return this.selectAll('PRAGMA table_info("' + tableName + '")').then((pragmas) => { | ||||
| 						for (let i = 0; i < pragmas.length; i++) { | ||||
| 							let item = pragmas[i]; | ||||
| 							// In SQLite, if the default value is a string it has double quotes around it, so remove them here | ||||
| 							let defaultValue = item.dflt_value; | ||||
| 							if (typeof defaultValue == 'string' && defaultValue.length >= 2 && defaultValue[0] == '"' && defaultValue[defaultValue.length - 1] == '"') { | ||||
| 								defaultValue = defaultValue.substr(1, defaultValue.length - 2); | ||||
| 							} | ||||
| 							let q = Database.insertQuery('table_fields', { | ||||
| 								table_name: tableName, | ||||
| 								field_name: item.name, | ||||
| 								field_type: Database.enumId('fieldType', item.type), | ||||
| 								field_default: defaultValue, | ||||
| 							}); | ||||
| 							queries.push(q); | ||||
| 						} | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			return promiseChain(chain); | ||||
| 		}).then(() => { | ||||
| 			return this.transactionExecBatch(queries); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	async initialize() { | ||||
| 		this.logger().info('Checking for database schema update...'); | ||||
|  | ||||
| 		for (let initLoopCount = 1; initLoopCount <= 2; initLoopCount++) { | ||||
| 			try { | ||||
| 				let row = await this.selectOne('SELECT * FROM version LIMIT 1'); | ||||
| 				this.logger().info('Current database version', row); | ||||
|  | ||||
| 				// TODO: version update logic | ||||
| 				// TODO: only do this if db has been updated: | ||||
| 				// return this.refreshTableFields(); | ||||
| 			} catch (error) { | ||||
| 				if (error && error.code != 0 && error.code != 'SQLITE_ERROR') throw this.sqliteErrorToJsError(error); | ||||
| 		 | ||||
| 				// Assume that error was: | ||||
| 				// { message: 'no such table: version (code 1): , while compiling: SELECT * FROM version', code: 0 } | ||||
| 				// which means the database is empty and the tables need to be created. | ||||
| 				// If it's any other error there's nothing we can do anyway. | ||||
|  | ||||
| 				this.logger().info('Database is new - creating the schema...'); | ||||
|  | ||||
| 				let queries = this.wrapQueries(this.sqlStringToLines(structureSql)); | ||||
| 				queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")')); | ||||
|  | ||||
| 				try { | ||||
| 					await this.transactionExecBatch(queries); | ||||
| 					this.logger().info('Database schema created successfully'); | ||||
| 					await this.refreshTableFields(); | ||||
| 				} catch (error) { | ||||
| 					throw this.sqliteErrorToJsError(error); | ||||
| 				} | ||||
|  | ||||
| 				// Now that the database has been created, go through the normal initialisation process | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			this.tableFields_ = {}; | ||||
|  | ||||
| 			let rows = await this.selectAll('SELECT * FROM table_fields'); | ||||
|  | ||||
| 			for (let i = 0; i < rows.length; i++) { | ||||
| 				let row = rows[i]; | ||||
| 				if (!this.tableFields_[row.table_name]) this.tableFields_[row.table_name] = []; | ||||
| 				this.tableFields_[row.table_name].push({ | ||||
| 					name: row.field_name, | ||||
| 					type: row.field_type, | ||||
| 					default: Database.formatValue(row.field_type, row.field_default), | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| Database.TYPE_INT = 1; | ||||
|   | ||||
							
								
								
									
										248
									
								
								ReactNativeClient/lib/joplin-database.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								ReactNativeClient/lib/joplin-database.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,248 @@ | ||||
| import { uuid } from 'lib/uuid.js'; | ||||
| import { promiseChain } from 'lib/promise-utils.js'; | ||||
| import { time } from 'lib/time-utils.js' | ||||
| import { Database } from 'lib/database.js' | ||||
|  | ||||
| const structureSql = ` | ||||
| CREATE TABLE folders ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX folders_title ON folders (title); | ||||
| CREATE INDEX folders_updated_time ON folders (updated_time); | ||||
| CREATE INDEX folders_sync_time ON folders (sync_time); | ||||
|  | ||||
| CREATE TABLE notes ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	body TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0, | ||||
| 	is_conflict INT NOT NULL DEFAULT 0, | ||||
| 	latitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	longitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	altitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	author TEXT NOT NULL DEFAULT "", | ||||
| 	source_url TEXT NOT NULL DEFAULT "", | ||||
| 	is_todo INT NOT NULL DEFAULT 0, | ||||
| 	todo_due INT NOT NULL DEFAULT 0, | ||||
| 	todo_completed INT NOT NULL DEFAULT 0, | ||||
| 	source TEXT NOT NULL DEFAULT "", | ||||
| 	source_application TEXT NOT NULL DEFAULT "", | ||||
| 	application_data TEXT NOT NULL DEFAULT "", | ||||
| 	\`order\` INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX notes_title ON notes (title); | ||||
| CREATE INDEX notes_updated_time ON notes (updated_time); | ||||
| CREATE INDEX notes_sync_time ON notes (sync_time); | ||||
| CREATE INDEX notes_is_conflict ON notes (is_conflict); | ||||
| CREATE INDEX notes_is_todo ON notes (is_todo); | ||||
| CREATE INDEX notes_order ON notes (\`order\`); | ||||
|  | ||||
| CREATE TABLE deleted_items ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	item_type INT NOT NULL, | ||||
| 	item_id TEXT NOT NULL, | ||||
| 	deleted_time INT NOT NULL | ||||
| ); | ||||
|  | ||||
| CREATE TABLE tags ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX tags_title ON tags (title); | ||||
| CREATE INDEX tags_updated_time ON tags (updated_time); | ||||
| CREATE INDEX tags_sync_time ON tags (sync_time); | ||||
|  | ||||
| CREATE TABLE note_tags ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	note_id TEXT NOT NULL, | ||||
| 	tag_id TEXT NOT NULL, | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX note_tags_note_id ON note_tags (note_id); | ||||
| CREATE INDEX note_tags_tag_id ON note_tags (tag_id); | ||||
| CREATE INDEX note_tags_updated_time ON note_tags (updated_time); | ||||
| CREATE INDEX note_tags_sync_time ON note_tags (sync_time); | ||||
|  | ||||
| CREATE TABLE resources ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	mime TEXT NOT NULL, | ||||
| 	filename TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL, | ||||
| 	updated_time INT NOT NULL, | ||||
| 	sync_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE INDEX resources_title ON resources (title); | ||||
| CREATE INDEX resources_updated_time ON resources (updated_time); | ||||
| CREATE INDEX resources_sync_time ON resources (sync_time); | ||||
|  | ||||
| CREATE TABLE settings ( | ||||
| 	\`key\` TEXT PRIMARY KEY, | ||||
| 	\`value\` TEXT, | ||||
| 	\`type\` INT NOT NULL | ||||
| ); | ||||
|  | ||||
| CREATE TABLE table_fields ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	table_name TEXT NOT NULL, | ||||
| 	field_name TEXT NOT NULL, | ||||
| 	field_type INT NOT NULL, | ||||
| 	field_default TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE version ( | ||||
| 	version INT NOT NULL | ||||
| ); | ||||
|  | ||||
| INSERT INTO version (version) VALUES (1); | ||||
| `; | ||||
|  | ||||
| class JoplinDatabase extends Database { | ||||
|  | ||||
| 	constructor(driver) { | ||||
| 		super(driver); | ||||
| 		this.initialized_ = false; | ||||
| 		this.tableFields_ = null; | ||||
| 	} | ||||
|  | ||||
| 	initialized() { | ||||
| 		return this.initialized_; | ||||
| 	} | ||||
|  | ||||
| 	async open(options) { | ||||
| 		await super.open(options); | ||||
| 		return this.initialize(); | ||||
| 	} | ||||
|  | ||||
| 	tableFieldNames(tableName) { | ||||
| 		let tf = this.tableFields(tableName); | ||||
| 		let output = []; | ||||
| 		for (let i = 0; i < tf.length; i++) { | ||||
| 			output.push(tf[i].name); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	tableFields(tableName) { | ||||
| 		if (!this.tableFields_) throw new Error('Fields have not been loaded yet'); | ||||
| 		if (!this.tableFields_[tableName]) throw new Error('Unknown table: ' + tableName); | ||||
| 		return this.tableFields_[tableName]; | ||||
| 	} | ||||
|  | ||||
| 	refreshTableFields() { | ||||
| 		this.logger().info('Initializing tables...'); | ||||
| 		let queries = []; | ||||
| 		queries.push(this.wrapQuery('DELETE FROM table_fields')); | ||||
|  | ||||
| 		return this.selectAll('SELECT name FROM sqlite_master WHERE type="table"').then((tableRows) => { | ||||
| 			let chain = []; | ||||
| 			for (let i = 0; i < tableRows.length; i++) { | ||||
| 				let tableName = tableRows[i].name; | ||||
| 				if (tableName == 'android_metadata') continue; | ||||
| 				if (tableName == 'table_fields') continue; | ||||
| 				chain.push(() => { | ||||
| 					return this.selectAll('PRAGMA table_info("' + tableName + '")').then((pragmas) => { | ||||
| 						for (let i = 0; i < pragmas.length; i++) { | ||||
| 							let item = pragmas[i]; | ||||
| 							// In SQLite, if the default value is a string it has double quotes around it, so remove them here | ||||
| 							let defaultValue = item.dflt_value; | ||||
| 							if (typeof defaultValue == 'string' && defaultValue.length >= 2 && defaultValue[0] == '"' && defaultValue[defaultValue.length - 1] == '"') { | ||||
| 								defaultValue = defaultValue.substr(1, defaultValue.length - 2); | ||||
| 							} | ||||
| 							let q = Database.insertQuery('table_fields', { | ||||
| 								table_name: tableName, | ||||
| 								field_name: item.name, | ||||
| 								field_type: Database.enumId('fieldType', item.type), | ||||
| 								field_default: defaultValue, | ||||
| 							}); | ||||
| 							queries.push(q); | ||||
| 						} | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			return promiseChain(chain); | ||||
| 		}).then(() => { | ||||
| 			return this.transactionExecBatch(queries); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	async initialize() { | ||||
| 		this.logger().info('Checking for database schema update...'); | ||||
|  | ||||
| 		for (let initLoopCount = 1; initLoopCount <= 2; initLoopCount++) { | ||||
| 			try { | ||||
| 				let row = await this.selectOne('SELECT * FROM version LIMIT 1'); | ||||
| 				this.logger().info('Current database version', row); | ||||
|  | ||||
| 				// TODO: version update logic | ||||
| 				// TODO: only do this if db has been updated: | ||||
| 				// return this.refreshTableFields(); | ||||
| 			} catch (error) { | ||||
| 				if (error && error.code != 0 && error.code != 'SQLITE_ERROR') throw this.sqliteErrorToJsError(error); | ||||
| 		 | ||||
| 				// Assume that error was: | ||||
| 				// { message: 'no such table: version (code 1): , while compiling: SELECT * FROM version', code: 0 } | ||||
| 				// which means the database is empty and the tables need to be created. | ||||
| 				// If it's any other error there's nothing we can do anyway. | ||||
|  | ||||
| 				this.logger().info('Database is new - creating the schema...'); | ||||
|  | ||||
| 				let queries = this.wrapQueries(this.sqlStringToLines(structureSql)); | ||||
| 				queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")')); | ||||
|  | ||||
| 				try { | ||||
| 					await this.transactionExecBatch(queries); | ||||
| 					this.logger().info('Database schema created successfully'); | ||||
| 					await this.refreshTableFields(); | ||||
| 				} catch (error) { | ||||
| 					throw this.sqliteErrorToJsError(error); | ||||
| 				} | ||||
|  | ||||
| 				// Now that the database has been created, go through the normal initialisation process | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			this.tableFields_ = {}; | ||||
|  | ||||
| 			let rows = await this.selectAll('SELECT * FROM table_fields'); | ||||
|  | ||||
| 			for (let i = 0; i < rows.length; i++) { | ||||
| 				let row = rows[i]; | ||||
| 				if (!this.tableFields_[row.table_name]) this.tableFields_[row.table_name] = []; | ||||
| 				this.tableFields_[row.table_name].push({ | ||||
| 					name: row.field_name, | ||||
| 					type: row.field_type, | ||||
| 					default: Database.formatValue(row.field_type, row.field_default), | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| Database.TYPE_INT = 1; | ||||
| Database.TYPE_TEXT = 2; | ||||
| Database.TYPE_NUMERIC = 3; | ||||
|  | ||||
| export { JoplinDatabase }; | ||||
| @@ -1,5 +1,6 @@ | ||||
| import moment from 'moment'; | ||||
| import { _ } from 'lib/locale.js'; | ||||
| import { time } from 'lib/time-utils.js'; | ||||
| import { FsDriverDummy } from 'lib/fs-driver-dummy.js'; | ||||
|  | ||||
| class Logger { | ||||
| @@ -37,6 +38,36 @@ class Logger { | ||||
| 		this.targets_.push(target); | ||||
| 	} | ||||
|  | ||||
| 	objectToString(object) { | ||||
| 		let output = ''; | ||||
|  | ||||
| 		if (typeof object === 'object') { | ||||
| 			if (object instanceof Error) { | ||||
| 				output = object.toString(); | ||||
| 				if (object.stack) output += "\n" + object.stack; | ||||
| 			} else { | ||||
| 				output = JSON.stringify(object); | ||||
| 			} | ||||
| 		} else { | ||||
| 			output = object; | ||||
| 		} | ||||
|  | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	static databaseCreateTableSql() { | ||||
| 		let output = ` | ||||
| 		CREATE TABLE logs ( | ||||
| 			id INTEGER PRIMARY KEY, | ||||
| 			source TEXT, | ||||
| 			level INT NOT NULL, | ||||
| 			message TEXT NOT NULL, | ||||
| 			\`timestamp\` INT NOT NULL | ||||
| 		); | ||||
| 		`; | ||||
| 		return output.split("\n").join(' '); | ||||
| 	} | ||||
|  | ||||
| 	log(level, object) { | ||||
| 		if (this.level() < level || !this.targets_.length) return; | ||||
|  | ||||
| @@ -47,8 +78,8 @@ class Logger { | ||||
| 		let line = moment().format('YYYY-MM-DD HH:mm:ss') + ': ' + levelString; | ||||
|  | ||||
| 		for (let i = 0; i < this.targets_.length; i++) { | ||||
| 			let t = this.targets_[i]; | ||||
| 			if (t.type == 'console') { | ||||
| 			let target = this.targets_[i]; | ||||
| 			if (target.type == 'console') { | ||||
| 				let fn = 'debug'; | ||||
| 				if (level = Logger.LEVEL_ERROR) fn = 'error'; | ||||
| 				if (level = Logger.LEVEL_WARN) fn = 'warn'; | ||||
| @@ -58,49 +89,18 @@ class Logger { | ||||
| 				} else { | ||||
| 					console[fn](line + object); | ||||
| 				} | ||||
| 			} else if (t.type == 'file') { | ||||
| 				let serializedObject = ''; | ||||
|  | ||||
| 				if (typeof object === 'object') { | ||||
| 					if (object instanceof Error) { | ||||
| 						serializedObject = object.toString(); | ||||
| 						if (object.stack) serializedObject += "\n" + object.stack; | ||||
| 					} else { | ||||
| 						serializedObject = JSON.stringify(object); | ||||
| 					} | ||||
| 				} else { | ||||
| 					serializedObject = object; | ||||
| 				} | ||||
|  | ||||
| 				Logger.fsDriver().appendFileSync(t.path, line + serializedObject + "\n"); | ||||
|  | ||||
| 				// this.fileAppendQueue_.push({ | ||||
| 				// 	path: t.path, | ||||
| 				// 	line: line + serializedObject + "\n", | ||||
| 				// }); | ||||
|  | ||||
| 				// this.scheduleFileAppendQueueProcessing_(); | ||||
| 			} else if (t.type == 'vorpal') { | ||||
| 				t.vorpal.log(object); | ||||
| 			} else if (target.type == 'file') { | ||||
| 				let serializedObject = this.objectToString(object); | ||||
| 				Logger.fsDriver().appendFileSync(target.path, line + serializedObject + "\n"); | ||||
| 			} else if (target.type == 'vorpal') { | ||||
| 				target.vorpal.log(object); | ||||
| 			} else if (target.type == 'database') { | ||||
| 				let msg = this.objectToString(object); | ||||
| 				target.database.exec('INSERT INTO logs (`source`, `level`, `message`, `timestamp`) VALUES (?, ?, ?, ?)', [target.source, level, msg, time.unixMs()]); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// scheduleFileAppendQueueProcessing_() { | ||||
| 	// 	if (this.fileAppendQueueTID_) return; | ||||
|  | ||||
| 	// 	this.fileAppendQueueTID_ = setTimeout(async () => { | ||||
| 	// 		this.fileAppendQueueTID_ = null; | ||||
|  | ||||
| 	// 		let queue = this.fileAppendQueue_.slice(0); | ||||
| 	// 		for (let i = 0; i < queue.length; i++) { | ||||
| 	// 			let t = queue[i]; | ||||
| 	// 			await fs.appendFile(t.path, t.line); | ||||
| 	// 		} | ||||
| 	// 		this.fileAppendQueue_.splice(0, queue.length); | ||||
| 	// 	}, 1); | ||||
| 	// } | ||||
|  | ||||
| 	error(object) { return this.log(Logger.LEVEL_ERROR, object); } | ||||
| 	warn(object)  { return this.log(Logger.LEVEL_WARN, object); } | ||||
| 	info(object)  { return this.log(Logger.LEVEL_INFO, object); } | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { BaseItem } from 'lib/models/base-item.js'; | ||||
| import { BaseModel } from 'lib/base-model.js'; | ||||
| import lodash  from 'lodash'; | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { BaseModel } from 'lib/base-model.js'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { BaseItem } from 'lib/models/base-item.js'; | ||||
| import { NoteTag } from 'lib/models/note-tag.js'; | ||||
| import { Note } from 'lib/models/note.js'; | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import { Tag } from 'lib/models/tag.js' | ||||
| import { NoteTag } from 'lib/models/note-tag.js' | ||||
| import { BaseItem } from 'lib/models/base-item.js' | ||||
| import { BaseModel } from 'lib/base-model.js' | ||||
| import { Database } from 'lib/database.js' | ||||
| import { JoplinDatabase } from 'lib/joplin-database.js' | ||||
| import { ItemList } from 'lib/components/item-list.js' | ||||
| import { NotesScreen } from 'lib/components/screens/notes.js' | ||||
| import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js' | ||||
| @@ -32,6 +32,7 @@ import { SideMenu } from 'lib/components/side-menu.js'; | ||||
| import { SideMenuContent } from 'lib/components/side-menu-content.js'; | ||||
| import { DatabaseDriverReactNative } from 'lib/database-driver-react-native'; | ||||
| import { reg } from 'lib/registry.js'; | ||||
| import RNFetchBlob from 'react-native-fetch-blob'; | ||||
|  | ||||
| let defaultState = { | ||||
| 	notes: [], | ||||
| @@ -194,9 +195,6 @@ const AppNavigator = StackNavigator({ | ||||
| 	OneDriveLogin: { screen: OneDriveLoginScreen }, | ||||
| }); | ||||
|  | ||||
| import RNFetchBlob from 'react-native-fetch-blob' | ||||
|  | ||||
|  | ||||
| class AppComponent extends React.Component { | ||||
|  | ||||
| 	async componentDidMount() { | ||||
| @@ -235,7 +233,7 @@ class AppComponent extends React.Component { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		let db = new Database(new DatabaseDriverReactNative()); | ||||
| 		let db = new JoplinDatabase(new DatabaseDriverReactNative()); | ||||
| 		reg.setDb(db); | ||||
|  | ||||
| 		BaseModel.dispatch = this.props.dispatch; | ||||
| @@ -249,7 +247,7 @@ class AppComponent extends React.Component { | ||||
| 		BaseItem.loadClass('NoteTag', NoteTag); | ||||
|  | ||||
| 		try { | ||||
| 			await db.open({ name: '/storage/emulated/0/Download/joplin-44.sqlite' }) | ||||
| 			await db.open({ name: '/storage/emulated/0/Download/joplin-48.sqlite' }) | ||||
| 			Log.info('Database is ready.'); | ||||
|  | ||||
| 			//await db.exec('DELETE FROM notes'); | ||||
|   | ||||
| @@ -4,11 +4,6 @@ | ||||
| 		{ | ||||
| 			"path": ".", | ||||
| 			"folder_exclude_patterns": [ | ||||
| 				"var", | ||||
| 				"vendor", | ||||
| 				"QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug", | ||||
| 				"QtClient/data/resources", | ||||
| 				"app/data/uploads", | ||||
| 				"CliClient/node_modules", | ||||
| 				"CliClient/build", | ||||
| 				"CliClient/tests-build", | ||||
| @@ -27,29 +22,11 @@ | ||||
| 				"_vieux", | ||||
| 			], | ||||
| 			"file_exclude_patterns": [ | ||||
| 				"*.pro.user", | ||||
| 				"*.pro.user.*", | ||||
| 				"*.iml", | ||||
| 				"*.map", | ||||
| 				"CliClient/app/src", | ||||
| 				"CliClient/app/lib", | ||||
| 				"*.jar", | ||||
| 			] | ||||
| 		} | ||||
| 	], | ||||
| 	"build_systems": | ||||
| 	[ | ||||
| 		{ | ||||
| 			"name": "Build evernote-import", | ||||
| 			"shell_cmd": "D:\\Programmes\\cygwin\\bin\\bash.exe --login D:\\Web\\www\\joplin\\QtClient\\evernote-import\\build.sh" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"name": "Qt Creator - Build and run", | ||||
| 			"shell_cmd": "D:\\NonPortableApps\\AutoHotkey\\AutoHotkey.exe D:\\Docs\\PROGS\\AutoHotKey\\QtRun\\QtRun.ahk" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"name": "Build QtClient CLI - Linux", | ||||
| 			"shell_cmd": "/home/laurent/src/notes/QtClient/JoplinQtClient/build.sh" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user