mirror of https://github.com/laurent22/joplin.git
Fixes for release apk
parent
8751aa1a34
commit
216a6780cb
|
@ -350,8 +350,8 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
|
||||||
id: noteResource.id,
|
id: noteResource.id,
|
||||||
data: decodedData,
|
data: decodedData,
|
||||||
mime: noteResource.mime,
|
mime: noteResource.mime,
|
||||||
title: noteResource.filename,
|
title: noteResource.filename ? noteResource.filename : '',
|
||||||
filename: noteResource.filename,
|
filename: noteResource.filename ? noteResource.filename : '',
|
||||||
};
|
};
|
||||||
|
|
||||||
note.resources.push(r);
|
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 { FileApiDriverMemory } from 'lib/file-api-driver-memory.js';
|
||||||
import { FileApiDriverLocal } from 'lib/file-api-driver-local.js';
|
import { FileApiDriverLocal } from 'lib/file-api-driver-local.js';
|
||||||
import { OneDriveApiNodeUtils } from './onedrive-api-node-utils.js';
|
import { OneDriveApiNodeUtils } from './onedrive-api-node-utils.js';
|
||||||
|
import { JoplinDatabase } from 'lib/joplin-database.js';
|
||||||
import { Database } from 'lib/database.js';
|
import { Database } from 'lib/database.js';
|
||||||
import { DatabaseDriverNode } from 'lib/database-driver-node.js';
|
import { DatabaseDriverNode } from 'lib/database-driver-node.js';
|
||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
|
@ -927,7 +928,12 @@ async function main() {
|
||||||
await fs.mkdirp(resourceDir, 0o755);
|
await fs.mkdirp(resourceDir, 0o755);
|
||||||
await fs.mkdirp(tempDir, 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('file', { path: profileDir + '/log.txt' });
|
||||||
|
// logger.addTarget('database', { database: logDatabase, source: 'main' });
|
||||||
logger.setLevel(logLevel);
|
logger.setLevel(logLevel);
|
||||||
|
|
||||||
dbLogger.addTarget('file', { path: profileDir + '/log-database.txt' });
|
dbLogger.addTarget('file', { path: profileDir + '/log-database.txt' });
|
||||||
|
@ -947,7 +953,7 @@ async function main() {
|
||||||
BaseItem.loadClass('Tag', Tag);
|
BaseItem.loadClass('Tag', Tag);
|
||||||
BaseItem.loadClass('NoteTag', NoteTag);
|
BaseItem.loadClass('NoteTag', NoteTag);
|
||||||
|
|
||||||
database_ = new Database(new DatabaseDriverNode());
|
database_ = new JoplinDatabase(new DatabaseDriverNode());
|
||||||
database_.setLogger(dbLogger);
|
database_.setLogger(dbLogger);
|
||||||
await database_.open({ name: profileDir + '/database.sqlite' });
|
await database_.open({ name: profileDir + '/database.sqlite' });
|
||||||
BaseModel.db_ = database_;
|
BaseModel.db_ = database_;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import fs from 'fs-extra';
|
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 { DatabaseDriverNode } from 'lib/database-driver-node.js';
|
||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Folder } from 'lib/models/folder.js';
|
import { Folder } from 'lib/models/folder.js';
|
||||||
|
@ -25,8 +25,11 @@ const fsDriver = new FsDriverNode();
|
||||||
Logger.fsDriver_ = fsDriver;
|
Logger.fsDriver_ = fsDriver;
|
||||||
Resource.fsDriver_ = fsDriver;
|
Resource.fsDriver_ = fsDriver;
|
||||||
|
|
||||||
|
const logDir = __dirname + '/../tests/logs';
|
||||||
|
fs.mkdirpSync(logDir, 0o755);
|
||||||
|
|
||||||
const logger = new Logger();
|
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);
|
logger.setLevel(Logger.LEVEL_DEBUG);
|
||||||
|
|
||||||
BaseItem.loadClass('Note', Note);
|
BaseItem.loadClass('Note', Note);
|
||||||
|
@ -87,7 +90,7 @@ function setupDatabase(id = null) {
|
||||||
return fs.unlink(filePath).catch(() => {
|
return fs.unlink(filePath).catch(() => {
|
||||||
// Don't care if the file doesn't exist
|
// Don't care if the file doesn't exist
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
databases_[id] = new Database(new DatabaseDriverNode());
|
databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
|
||||||
databases_[id].setLogger(logger);
|
databases_[id].setLogger(logger);
|
||||||
return databases_[id].open({ name: filePath }).then(() => {
|
return databases_[id].open({ name: filePath }).then(() => {
|
||||||
BaseModel.db_ = databases_[id];
|
BaseModel.db_ = databases_[id];
|
||||||
|
|
|
@ -83,62 +83,73 @@ def enableSeparateBuildPerCPUArchitecture = false
|
||||||
def enableProguardInReleaseBuilds = false
|
def enableProguardInReleaseBuilds = false
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 23
|
||||||
buildToolsVersion "23.0.1"
|
buildToolsVersion "23.0.1"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.awesomeproject"
|
applicationId "com.awesomeproject"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a", "x86"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
reset()
|
reset()
|
||||||
enable enableSeparateBuildPerCPUArchitecture
|
enable enableSeparateBuildPerCPUArchitecture
|
||||||
universalApk false // If true, also generate a universal APK
|
universalApk false // If true, also generate a universal APK
|
||||||
include "armeabi-v7a", "x86"
|
include "armeabi-v7a", "x86"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
if (project.hasProperty('JOPLIN_RELEASE_STORE_FILE')) {
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
storeFile file(JOPLIN_RELEASE_STORE_FILE)
|
||||||
}
|
storePassword JOPLIN_RELEASE_STORE_PASSWORD
|
||||||
}
|
keyAlias JOPLIN_RELEASE_KEY_ALIAS
|
||||||
// applicationVariants are e.g. debug, release
|
keyPassword JOPLIN_RELEASE_KEY_PASSWORD
|
||||||
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
|
buildTypes {
|
||||||
def versionCodes = ["armeabi-v7a":1, "x86":2]
|
release {
|
||||||
def abi = output.getFilter(OutputFile.ABI)
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
output.versionCodeOverride =
|
signingConfig signingConfigs.release
|
||||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
}
|
||||||
}
|
}
|
||||||
}
|
// 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 {
|
dependencies {
|
||||||
compile project(':react-native-fs')
|
compile project(':react-native-fs')
|
||||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||||
compile "com.android.support:appcompat-v7:23.0.1"
|
compile "com.android.support:appcompat-v7:23.0.1"
|
||||||
compile "com.facebook.react:react-native:+" // From node_modules
|
compile "com.facebook.react:react-native:+" // From node_modules
|
||||||
compile project(':react-native-sqlite-storage')
|
compile project(':react-native-sqlite-storage')
|
||||||
compile project(':react-native-fetch-blob')
|
compile project(':react-native-fetch-blob')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
// puts all compile dependencies into folder libs for BUCK to use
|
// puts all compile dependencies into folder libs for BUCK to use
|
||||||
task copyDownloadableDepsToLibs(type: Copy) {
|
task copyDownloadableDepsToLibs(type: Copy) {
|
||||||
from configurations.compile
|
from configurations.compile
|
||||||
into 'libs'
|
into 'libs'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||||
|
|
|
@ -4,12 +4,12 @@ import com.facebook.react.ReactActivity;
|
||||||
|
|
||||||
public class MainActivity extends ReactActivity {
|
public class MainActivity extends ReactActivity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the main component registered from JavaScript.
|
* Returns the name of the main component registered from JavaScript.
|
||||||
* This is used to schedule rendering of the component.
|
* This is used to schedule rendering of the component.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "AwesomeProject";
|
return "AwesomeProject";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ class ItemListComponent extends Component {
|
||||||
await Note.save({ id: note.id, todo_completed: checked });
|
await Note.save({ id: note.id, todo_completed: checked });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listView_itemLongPress(itemId) {}
|
||||||
listView_itemPress(itemId) {}
|
listView_itemPress(itemId) {}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -50,8 +51,8 @@ class ItemListComponent extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableHighlight onPress={onPress} onLongPress={onLongPress}>
|
<TouchableHighlight onPress={onPress} onLongPress={onLongPress}>
|
||||||
<View style={{flexDirection: 'row'}}>
|
<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} [{item.id}]</Text>
|
{ !!Number(item.is_todo) && <Checkbox checked={!!Number(item.todo_completed)} onChange={(checked) => { this.todoCheckbox_change(item.id, checked) }}/> }<Text>{item.title}</Text>
|
||||||
</View>
|
</View>
|
||||||
</TouchableHighlight>
|
</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() {
|
render() {
|
||||||
let key = 0;
|
let key = 0;
|
||||||
let menuOptionComponents = [];
|
let menuOptionComponents = [];
|
||||||
|
@ -72,15 +56,10 @@ class ScreenHeaderComponent extends Component {
|
||||||
menuOptionComponents.push(<View key={'menuOption_' + key++} style={styles.divider}/>);
|
menuOptionComponents.push(<View key={'menuOption_' + key++} style={styles.divider}/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
menuOptionComponents.push(
|
// menuOptionComponents.push(
|
||||||
<MenuOption value={() => this.menu_synchronize()} key={'menuOption_' + key++}>
|
// <MenuOption value={1} key={'menuOption_' + key++}>
|
||||||
<Text>{_('Synchronize')}</Text>
|
// <Text>{_('Configuration')}</Text>
|
||||||
</MenuOption>);
|
// </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);
|
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' }}>
|
<View style={{ flexDirection: 'row' }}>
|
||||||
{ isTodo && <Checkbox checked={!!Number(note.todo_completed)} /> }<TextInput style={{flex:1}} value={note.title} onChangeText={(text) => this.title_changeText(text)} />
|
{ isTodo && <Checkbox checked={!!Number(note.todo_completed)} /> }<TextInput style={{flex:1}} value={note.title} onChangeText={(text) => this.title_changeText(text)} />
|
||||||
</View>
|
</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 }
|
{ todoComponents }
|
||||||
<Button title="Save note" onPress={() => this.saveNoteButton_press()} />
|
<Button title="Save note" onPress={() => this.saveNoteButton_press()} />
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { WebView, Button } from 'react-native';
|
import { WebView, Button, Text } from 'react-native';
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Log } from 'lib/log.js'
|
import { Log } from 'lib/log.js'
|
||||||
import { Setting } from 'lib/models/setting.js'
|
import { Setting } from 'lib/models/setting.js'
|
||||||
|
@ -22,10 +22,14 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
webviewUrl: reg.oneDriveApi().authCodeUrl(this.redirectUrl()),
|
webviewUrl: this.startUrl(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startUrl() {
|
||||||
|
return reg.oneDriveApi().authCodeUrl(this.redirectUrl());
|
||||||
|
}
|
||||||
|
|
||||||
redirectUrl() {
|
redirectUrl() {
|
||||||
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
|
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
|
||||||
}
|
}
|
||||||
|
@ -37,7 +41,7 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||||
const url = noIdeaWhatThisIs.url;
|
const url = noIdeaWhatThisIs.url;
|
||||||
|
|
||||||
if (!this.authCode_ && url.indexOf(this.redirectUrl() + '?code=') === 0) {
|
if (!this.authCode_ && url.indexOf(this.redirectUrl() + '?code=') === 0) {
|
||||||
console.info('URL: ' + url);
|
Log.info('URL: ' + url);
|
||||||
|
|
||||||
let code = url.split('?code=');
|
let code = url.split('?code=');
|
||||||
this.authCode_ = code[1];
|
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() {
|
render() {
|
||||||
const source = {
|
const source = {
|
||||||
uri: this.state.webviewUrl,
|
uri: this.state.webviewUrl,
|
||||||
|
@ -60,7 +86,10 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||||
source={source}
|
source={source}
|
||||||
style={{marginTop: 20}}
|
style={{marginTop: 20}}
|
||||||
onNavigationStateChange={(o) => { this.webview_load(o); }}
|
onNavigationStateChange={(o) => { this.webview_load(o); }}
|
||||||
|
onError={(error) => { this.webview_error(error); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Button title="Retry" onPress={() => { this.retryButton_click(); }}></Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Button } from 'react-native';
|
import { Button, Text } from 'react-native';
|
||||||
import { Log } from 'lib/log.js';
|
import { Log } from 'lib/log.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.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 React = require('react');
|
||||||
const {
|
const {
|
||||||
|
@ -11,7 +13,6 @@ const {
|
||||||
ScrollView,
|
ScrollView,
|
||||||
View,
|
View,
|
||||||
Image,
|
Image,
|
||||||
Text,
|
|
||||||
} = require('react-native');
|
} = require('react-native');
|
||||||
const { Component } = React;
|
const { Component } = React;
|
||||||
|
|
||||||
|
@ -41,6 +42,11 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
class SideMenuContentComponent extends Component {
|
class SideMenuContentComponent extends Component {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = { syncReportText: '' };
|
||||||
|
}
|
||||||
|
|
||||||
folder_press(folder) {
|
folder_press(folder) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'SIDE_MENU_CLOSE',
|
type: 'SIDE_MENU_CLOSE',
|
||||||
|
@ -49,19 +55,57 @@ class SideMenuContentComponent extends Component {
|
||||||
NotesScreenUtils.openNoteList(folder.id);
|
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() {
|
render() {
|
||||||
let buttons = [];
|
let keyIndex = 0;
|
||||||
|
let key = () => {
|
||||||
|
return 'smitem_' + (keyIndex++);
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = [];
|
||||||
for (let i = 0; i < this.props.folders.length; i++) {
|
for (let i = 0; i < this.props.folders.length; i++) {
|
||||||
let f = this.props.folders[i];
|
let f = this.props.folders[i];
|
||||||
let title = f.title ? f.title : '';
|
let title = f.title ? f.title : '';
|
||||||
buttons.push(
|
items.push(
|
||||||
<Button style={styles.button} title={title} onPress={() => { this.folder_press(f) }} key={f.id} />
|
<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 (
|
return (
|
||||||
<ScrollView scrollsToTop={false} style={styles.menu}>
|
<ScrollView scrollsToTop={false} style={styles.menu}>
|
||||||
{ buttons }
|
{ items }
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,120 +2,16 @@ import { uuid } from 'lib/uuid.js';
|
||||||
import { promiseChain } from 'lib/promise-utils.js';
|
import { promiseChain } from 'lib/promise-utils.js';
|
||||||
import { Logger } from 'lib/logger.js'
|
import { Logger } from 'lib/logger.js'
|
||||||
import { time } from 'lib/time-utils.js'
|
import { time } from 'lib/time-utils.js'
|
||||||
import { _ } from 'lib/locale.js'
|
|
||||||
import { sprintf } from 'sprintf-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 {
|
class Database {
|
||||||
|
|
||||||
constructor(driver) {
|
constructor(driver) {
|
||||||
this.debugMode_ = false;
|
this.debugMode_ = false;
|
||||||
this.initialized_ = false;
|
|
||||||
this.tableFields_ = null;
|
|
||||||
this.driver_ = driver;
|
this.driver_ = driver;
|
||||||
this.inTransaction_ = false;
|
this.inTransaction_ = false;
|
||||||
|
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
this.logger_.addTarget('console');
|
|
||||||
this.logger_.setLevel(Logger.LEVEL_DEBUG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts the SQLite error to a regular JS error
|
// Converts the SQLite error to a regular JS error
|
||||||
|
@ -133,10 +29,6 @@ class Database {
|
||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized() {
|
|
||||||
return this.initialized_;
|
|
||||||
}
|
|
||||||
|
|
||||||
driver() {
|
driver() {
|
||||||
return this.driver_;
|
return this.driver_;
|
||||||
}
|
}
|
||||||
|
@ -144,7 +36,6 @@ class Database {
|
||||||
async open(options) {
|
async open(options) {
|
||||||
await this.driver().open(options);
|
await this.driver().open(options);
|
||||||
this.logger().info('Database was open successfully');
|
this.logger().info('Database was open successfully');
|
||||||
return this.initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
escapeField(field) {
|
escapeField(field) {
|
||||||
|
@ -251,26 +142,12 @@ class Database {
|
||||||
if (s == 'string') return 2;
|
if (s == 'string') return 2;
|
||||||
}
|
}
|
||||||
if (type == 'fieldType') {
|
if (type == 'fieldType') {
|
||||||
|
if (s == 'INTEGER') s = 'INT';
|
||||||
return this['TYPE_' + s];
|
return this['TYPE_' + s];
|
||||||
}
|
}
|
||||||
throw new Error('Unknown enum type or value: ' + 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) {
|
static formatValue(type, value) {
|
||||||
if (value === null || value === undefined) return null;
|
if (value === null || value === undefined) return null;
|
||||||
if (type == this.TYPE_INT) return Number(value);
|
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;
|
Database.TYPE_INT = 1;
|
||||||
|
|
|
@ -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 moment from 'moment';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { time } from 'lib/time-utils.js';
|
||||||
import { FsDriverDummy } from 'lib/fs-driver-dummy.js';
|
import { FsDriverDummy } from 'lib/fs-driver-dummy.js';
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
|
@ -37,6 +38,36 @@ class Logger {
|
||||||
this.targets_.push(target);
|
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) {
|
log(level, object) {
|
||||||
if (this.level() < level || !this.targets_.length) return;
|
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;
|
let line = moment().format('YYYY-MM-DD HH:mm:ss') + ': ' + levelString;
|
||||||
|
|
||||||
for (let i = 0; i < this.targets_.length; i++) {
|
for (let i = 0; i < this.targets_.length; i++) {
|
||||||
let t = this.targets_[i];
|
let target = this.targets_[i];
|
||||||
if (t.type == 'console') {
|
if (target.type == 'console') {
|
||||||
let fn = 'debug';
|
let fn = 'debug';
|
||||||
if (level = Logger.LEVEL_ERROR) fn = 'error';
|
if (level = Logger.LEVEL_ERROR) fn = 'error';
|
||||||
if (level = Logger.LEVEL_WARN) fn = 'warn';
|
if (level = Logger.LEVEL_WARN) fn = 'warn';
|
||||||
|
@ -58,49 +89,18 @@ class Logger {
|
||||||
} else {
|
} else {
|
||||||
console[fn](line + object);
|
console[fn](line + object);
|
||||||
}
|
}
|
||||||
} else if (t.type == 'file') {
|
} else if (target.type == 'file') {
|
||||||
let serializedObject = '';
|
let serializedObject = this.objectToString(object);
|
||||||
|
Logger.fsDriver().appendFileSync(target.path, line + serializedObject + "\n");
|
||||||
if (typeof object === 'object') {
|
} else if (target.type == 'vorpal') {
|
||||||
if (object instanceof Error) {
|
target.vorpal.log(object);
|
||||||
serializedObject = object.toString();
|
} else if (target.type == 'database') {
|
||||||
if (object.stack) serializedObject += "\n" + object.stack;
|
let msg = this.objectToString(object);
|
||||||
} else {
|
target.database.exec('INSERT INTO logs (`source`, `level`, `message`, `timestamp`) VALUES (?, ?, ?, ?)', [target.source, level, msg, time.unixMs()]);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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); }
|
error(object) { return this.log(Logger.LEVEL_ERROR, object); }
|
||||||
warn(object) { return this.log(Logger.LEVEL_WARN, object); }
|
warn(object) { return this.log(Logger.LEVEL_WARN, object); }
|
||||||
info(object) { return this.log(Logger.LEVEL_INFO, 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 { BaseItem } from 'lib/models/base-item.js';
|
||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { BaseModel } from 'lib/base-model.js';
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Database } from 'lib/database.js';
|
|
||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
import { NoteTag } from 'lib/models/note-tag.js';
|
import { NoteTag } from 'lib/models/note-tag.js';
|
||||||
import { Note } from 'lib/models/note.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 { NoteTag } from 'lib/models/note-tag.js'
|
||||||
import { BaseItem } from 'lib/models/base-item.js'
|
import { BaseItem } from 'lib/models/base-item.js'
|
||||||
import { BaseModel } from 'lib/base-model.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 { ItemList } from 'lib/components/item-list.js'
|
||||||
import { NotesScreen } from 'lib/components/screens/notes.js'
|
import { NotesScreen } from 'lib/components/screens/notes.js'
|
||||||
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.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 { SideMenuContent } from 'lib/components/side-menu-content.js';
|
||||||
import { DatabaseDriverReactNative } from 'lib/database-driver-react-native';
|
import { DatabaseDriverReactNative } from 'lib/database-driver-react-native';
|
||||||
import { reg } from 'lib/registry.js';
|
import { reg } from 'lib/registry.js';
|
||||||
|
import RNFetchBlob from 'react-native-fetch-blob';
|
||||||
|
|
||||||
let defaultState = {
|
let defaultState = {
|
||||||
notes: [],
|
notes: [],
|
||||||
|
@ -194,9 +195,6 @@ const AppNavigator = StackNavigator({
|
||||||
OneDriveLogin: { screen: OneDriveLoginScreen },
|
OneDriveLogin: { screen: OneDriveLoginScreen },
|
||||||
});
|
});
|
||||||
|
|
||||||
import RNFetchBlob from 'react-native-fetch-blob'
|
|
||||||
|
|
||||||
|
|
||||||
class AppComponent extends React.Component {
|
class AppComponent extends React.Component {
|
||||||
|
|
||||||
async componentDidMount() {
|
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);
|
reg.setDb(db);
|
||||||
|
|
||||||
BaseModel.dispatch = this.props.dispatch;
|
BaseModel.dispatch = this.props.dispatch;
|
||||||
|
@ -249,7 +247,7 @@ class AppComponent extends React.Component {
|
||||||
BaseItem.loadClass('NoteTag', NoteTag);
|
BaseItem.loadClass('NoteTag', NoteTag);
|
||||||
|
|
||||||
try {
|
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.');
|
Log.info('Database is ready.');
|
||||||
|
|
||||||
//await db.exec('DELETE FROM notes');
|
//await db.exec('DELETE FROM notes');
|
||||||
|
|
|
@ -4,11 +4,6 @@
|
||||||
{
|
{
|
||||||
"path": ".",
|
"path": ".",
|
||||||
"folder_exclude_patterns": [
|
"folder_exclude_patterns": [
|
||||||
"var",
|
|
||||||
"vendor",
|
|
||||||
"QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug",
|
|
||||||
"QtClient/data/resources",
|
|
||||||
"app/data/uploads",
|
|
||||||
"CliClient/node_modules",
|
"CliClient/node_modules",
|
||||||
"CliClient/build",
|
"CliClient/build",
|
||||||
"CliClient/tests-build",
|
"CliClient/tests-build",
|
||||||
|
@ -27,29 +22,11 @@
|
||||||
"_vieux",
|
"_vieux",
|
||||||
],
|
],
|
||||||
"file_exclude_patterns": [
|
"file_exclude_patterns": [
|
||||||
"*.pro.user",
|
|
||||||
"*.pro.user.*",
|
|
||||||
"*.iml",
|
|
||||||
"*.map",
|
"*.map",
|
||||||
"CliClient/app/src",
|
"CliClient/app/src",
|
||||||
"CliClient/app/lib",
|
"CliClient/app/lib",
|
||||||
"*.jar",
|
"*.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"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue