You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-09-16 08:56:40 +02:00
Sync in background for RN
This commit is contained in:
@@ -5,11 +5,6 @@ import { _ } from 'lib/locale.js';
|
|||||||
|
|
||||||
class AppNavComponent extends Component {
|
class AppNavComponent extends Component {
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.screenCache_ = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.props.route) throw new Error('Route must not be null');
|
if (!this.props.route) throw new Error('Route must not be null');
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import { ListView, Text, TouchableHighlight, Switch, View } from 'react-native';
|
|||||||
import { Log } from 'lib/log.js';
|
import { Log } from 'lib/log.js';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
import { Checkbox } from 'lib/components/checkbox.js';
|
import { Checkbox } from 'lib/components/checkbox.js';
|
||||||
|
import { reg } from 'lib/registry.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
|
|
||||||
@@ -36,6 +37,8 @@ class ItemListComponent extends Component {
|
|||||||
async todoCheckbox_change(itemId, checked) {
|
async todoCheckbox_change(itemId, checked) {
|
||||||
let note = await Note.load(itemId);
|
let note = await Note.load(itemId);
|
||||||
await Note.save({ id: note.id, todo_completed: checked ? time.unixMs() : 0 });
|
await Note.save({ id: note.id, todo_completed: checked ? time.unixMs() : 0 });
|
||||||
|
reg.scheduleSync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listView_itemLongPress(itemId) {}
|
listView_itemLongPress(itemId) {}
|
||||||
|
@@ -6,6 +6,7 @@ import { ActionButton } from 'lib/components/action-button.js';
|
|||||||
import { Folder } from 'lib/models/folder.js'
|
import { Folder } from 'lib/models/folder.js'
|
||||||
import { BaseModel } from 'lib/base-model.js'
|
import { BaseModel } from 'lib/base-model.js'
|
||||||
import { ScreenHeader } from 'lib/components/screen-header.js';
|
import { ScreenHeader } from 'lib/components/screen-header.js';
|
||||||
|
import { reg } from 'lib/registry.js';
|
||||||
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js'
|
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js'
|
||||||
import { BaseScreenComponent } from 'lib/components/base-screen.js';
|
import { BaseScreenComponent } from 'lib/components/base-screen.js';
|
||||||
import { dialogs } from 'lib/dialogs.js';
|
import { dialogs } from 'lib/dialogs.js';
|
||||||
@@ -69,6 +70,8 @@ class FolderScreenComponent extends BaseScreenComponent {
|
|||||||
duplicateCheck: true,
|
duplicateCheck: true,
|
||||||
reservedTitleCheck: true,
|
reservedTitleCheck: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
reg.scheduleSync();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dialogs.error(this, _('The folder could not be saved: %s', error.message));
|
dialogs.error(this, _('The folder could not be saved: %s', error.message));
|
||||||
return;
|
return;
|
||||||
|
@@ -12,6 +12,7 @@ import { time } from 'lib/time-utils.js';
|
|||||||
import { Checkbox } from 'lib/components/checkbox.js'
|
import { Checkbox } from 'lib/components/checkbox.js'
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
import marked from 'lib/marked.js';
|
import marked from 'lib/marked.js';
|
||||||
|
import { reg } from 'lib/registry.js';
|
||||||
import { BaseScreenComponent } from 'lib/components/base-screen.js';
|
import { BaseScreenComponent } from 'lib/components/base-screen.js';
|
||||||
import { dialogs } from 'lib/dialogs.js';
|
import { dialogs } from 'lib/dialogs.js';
|
||||||
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js'
|
import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js'
|
||||||
@@ -38,7 +39,9 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
showNoteMetadata: false,
|
showNoteMetadata: false,
|
||||||
folder: null,
|
folder: null,
|
||||||
lastSavedNote: null,
|
lastSavedNote: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.saveButtonHasBeenShown_ = false;
|
||||||
|
|
||||||
this.backHandler = () => {
|
this.backHandler = () => {
|
||||||
if (!this.state.note.id) {
|
if (!this.state.note.id) {
|
||||||
@@ -112,11 +115,9 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
noteComponent_change(propName, propValue) {
|
noteComponent_change(propName, propValue) {
|
||||||
this.setState((prevState, props) => {
|
let note = Object.assign({}, this.state.note);
|
||||||
let note = Object.assign({}, prevState.note);
|
note[propName] = propValue;
|
||||||
note[propName] = propValue;
|
this.setState({ note: note });
|
||||||
return { note: note }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshNoteMetadata(force = null) {
|
async refreshNoteMetadata(force = null) {
|
||||||
@@ -155,6 +156,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
if (isNew) Note.updateGeolocation(note.id);
|
if (isNew) Note.updateGeolocation(note.id);
|
||||||
this.refreshNoteMetadata();
|
this.refreshNoteMetadata();
|
||||||
|
|
||||||
|
reg.scheduleSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteNote_onPress() {
|
async deleteNote_onPress() {
|
||||||
@@ -168,6 +171,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
await Note.delete(note.id);
|
await Note.delete(note.id);
|
||||||
await NotesScreenUtils.openNoteList(folderId);
|
await NotesScreenUtils.openNoteList(folderId);
|
||||||
|
|
||||||
|
reg.scheduleSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
attachFile_onPress() {
|
attachFile_onPress() {
|
||||||
@@ -200,6 +205,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
lastSavedNote: Object.assign({}, note),
|
lastSavedNote: Object.assign({}, note),
|
||||||
note: note,
|
note: note,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
reg.scheduleSync();
|
||||||
} else {
|
} else {
|
||||||
note[name] = value;
|
note[name] = value;
|
||||||
this.setState({ note: note });
|
this.setState({ note: note });
|
||||||
@@ -207,7 +214,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async todoCheckbox_change(checked) {
|
async todoCheckbox_change(checked) {
|
||||||
return this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
||||||
|
reg.scheduleSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -307,9 +315,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
const actionButtonComp = renderActionButton();
|
const actionButtonComp = renderActionButton();
|
||||||
|
|
||||||
let showSaveButton = this.state.mode == 'edit';
|
let showSaveButton = this.state.mode == 'edit' || this.isModified() || this.saveButtonHasBeenShown_;
|
||||||
let saveButtonDisabled = !this.isModified();
|
let saveButtonDisabled = !this.isModified();
|
||||||
|
|
||||||
|
if (showSaveButton) this.saveButtonHasBeenShown_ = true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={this.styles().screen}>
|
<View style={this.styles().screen}>
|
||||||
<ScreenHeader
|
<ScreenHeader
|
||||||
@@ -328,6 +338,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
note: note,
|
note: note,
|
||||||
folder: folder,
|
folder: folder,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
reg.scheduleSync();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
navState={this.props.navigation.state}
|
navState={this.props.navigation.state}
|
||||||
|
@@ -40,7 +40,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingRight: 20,
|
paddingRight: 20,
|
||||||
paddingTop: 14,
|
paddingTop: 14,
|
||||||
paddingBottom: 14,
|
paddingBottom: 14,
|
||||||
marginBottom: 5 ,
|
marginBottom: 5,
|
||||||
},
|
},
|
||||||
folderButtonText: {
|
folderButtonText: {
|
||||||
color: "#ffffff",
|
color: "#ffffff",
|
||||||
@@ -65,24 +65,30 @@ class SideMenuContentComponent extends Component {
|
|||||||
NotesScreenUtils.openNoteList(folder.id);
|
NotesScreenUtils.openNoteList(folder.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async synchronizer_progress(report) {
|
||||||
|
const sync = await reg.synchronizer();
|
||||||
|
let lines = sync.reportToLines(report);
|
||||||
|
this.setState({ syncReportText: lines.join("\n") });
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronizer_complete() {
|
||||||
|
FoldersScreenUtils.refreshFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillMount() {
|
||||||
|
reg.dispatcher().on('synchronizer_progress', this.synchronizer_progress.bind(this));
|
||||||
|
reg.dispatcher().on('synchronizer_complete', this.synchronizer_complete.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
reg.dispatcher().off('synchronizer_progress', this.synchronizer_progress.bind(this));
|
||||||
|
reg.dispatcher().off('synchronizer_complete', this.synchronizer_complete.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
async synchronize_press() {
|
async synchronize_press() {
|
||||||
if (reg.oneDriveApi().auth()) {
|
if (reg.oneDriveApi().auth()) {
|
||||||
const sync = await reg.synchronizer()
|
const sync = await reg.synchronizer()
|
||||||
|
sync.start();
|
||||||
let options = {
|
|
||||||
onProgress: (report) => {
|
|
||||||
let lines = sync.reportToLines(report);
|
|
||||||
this.setState({ syncReportText: lines.join("\n") });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
sync.start(options).then(async () => {
|
|
||||||
await FoldersScreenUtils.refreshFolders();
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Log.error(error);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||||
|
|
||||||
@@ -102,7 +108,7 @@ class SideMenuContentComponent extends Component {
|
|||||||
items.push(
|
items.push(
|
||||||
<TouchableOpacity key={f.id} onPress={() => { this.folder_press(f) }}>
|
<TouchableOpacity key={f.id} onPress={() => { this.folder_press(f) }}>
|
||||||
<View style={styles.folderButton}>
|
<View style={styles.folderButton}>
|
||||||
<Text style={styles.folderButtonText}>{title}</Text>
|
<Text numberOfLines={1} style={styles.folderButtonText}>{title}</Text>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
35
ReactNativeClient/lib/event-dispatcher.js
Normal file
35
ReactNativeClient/lib/event-dispatcher.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
class EventDispatcher {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.listeners_ = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(eventName, event = null) {
|
||||||
|
if (!this.listeners_[eventName]) return;
|
||||||
|
|
||||||
|
let ls = this.listeners_[eventName];
|
||||||
|
for (let i = 0; i < ls.length; i++) {
|
||||||
|
ls[i](event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventName, callback) {
|
||||||
|
if (!this.listeners_[eventName]) this.listeners_[eventName] = [];
|
||||||
|
this.listeners_[eventName].push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(eventName, callback) {
|
||||||
|
if (!this.listeners_[eventName]) return;
|
||||||
|
|
||||||
|
let ls = this.listeners_[eventName];
|
||||||
|
for (let i = 0; i < ls.length; i++) {
|
||||||
|
if (ls[i] === callback) {
|
||||||
|
ls.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export { EventDispatcher };
|
@@ -5,9 +5,16 @@ import { parameters } from 'lib/parameters.js';
|
|||||||
import { FileApi } from 'lib/file-api.js';
|
import { FileApi } from 'lib/file-api.js';
|
||||||
import { Synchronizer } from 'lib/synchronizer.js';
|
import { Synchronizer } from 'lib/synchronizer.js';
|
||||||
import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js';
|
import { FileApiDriverOneDrive } from 'lib/file-api-driver-onedrive.js';
|
||||||
|
import { EventDispatcher } from 'lib/event-dispatcher.js';
|
||||||
|
|
||||||
const reg = {};
|
const reg = {};
|
||||||
|
|
||||||
|
reg.dispatcher = () => {
|
||||||
|
if (this.dispatcher_) return this.dispatcher_;
|
||||||
|
this.dispatcher_ = new EventDispatcher();
|
||||||
|
return this.dispatcher_;
|
||||||
|
}
|
||||||
|
|
||||||
reg.logger = () => {
|
reg.logger = () => {
|
||||||
if (!reg.logger_) {
|
if (!reg.logger_) {
|
||||||
console.warn('Calling logger before it is initialized');
|
console.warn('Calling logger before it is initialized');
|
||||||
@@ -70,9 +77,37 @@ reg.synchronizer = async () => {
|
|||||||
let fileApi = await reg.fileApi();
|
let fileApi = await reg.fileApi();
|
||||||
reg.synchronizer_ = new Synchronizer(reg.db(), fileApi, Setting.value('appType'));
|
reg.synchronizer_ = new Synchronizer(reg.db(), fileApi, Setting.value('appType'));
|
||||||
reg.synchronizer_.setLogger(reg.logger());
|
reg.synchronizer_.setLogger(reg.logger());
|
||||||
|
|
||||||
|
reg.synchronizer_.on('progress', (report) => {
|
||||||
|
reg.dispatcher().dispatch('synchronizer_progress', report);
|
||||||
|
});
|
||||||
|
|
||||||
|
reg.synchronizer_.on('complete', () => {
|
||||||
|
reg.dispatcher().dispatch('synchronizer_complete');
|
||||||
|
});
|
||||||
|
|
||||||
return reg.synchronizer_;
|
return reg.synchronizer_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reg.scheduleSync = async () => {
|
||||||
|
if (reg.scheduleSyncId_) return;
|
||||||
|
|
||||||
|
reg.logger().info('Scheduling sync operation...');
|
||||||
|
|
||||||
|
reg.scheduleSyncId_ = setTimeout(async () => {
|
||||||
|
reg.scheduleSyncId_ = null;
|
||||||
|
reg.logger().info('Doing scheduled sync');
|
||||||
|
|
||||||
|
if (!reg.oneDriveApi().auth()) {
|
||||||
|
reg.logger().info('Synchronizer is missing credentials - manual sync required to authenticate.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sync = await reg.synchronizer();
|
||||||
|
sync.start();
|
||||||
|
}, 1000 * 10);
|
||||||
|
}
|
||||||
|
|
||||||
reg.setDb = (v) => {
|
reg.setDb = (v) => {
|
||||||
reg.db_ = v;
|
reg.db_ = v;
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import { sprintf } from 'sprintf-js';
|
|||||||
import { time } from 'lib/time-utils.js';
|
import { time } from 'lib/time-utils.js';
|
||||||
import { Logger } from 'lib/logger.js'
|
import { Logger } from 'lib/logger.js'
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { EventDispatcher } from 'lib/event-dispatcher.js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
class Synchronizer {
|
class Synchronizer {
|
||||||
@@ -23,6 +24,16 @@ class Synchronizer {
|
|||||||
|
|
||||||
this.onProgress_ = function(s) {};
|
this.onProgress_ = function(s) {};
|
||||||
this.progressReport_ = {};
|
this.progressReport_ = {};
|
||||||
|
|
||||||
|
this.dispatcher_ = new EventDispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventName, callback) {
|
||||||
|
return this.dispatcher_.on(eventName, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(eventName, callback) {
|
||||||
|
return this.dispatcher_.off(eventName, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
@@ -55,6 +66,7 @@ class Synchronizer {
|
|||||||
if (report.deleteRemote) lines.push(_('Deleted remote items: %d.', report.deleteRemote));
|
if (report.deleteRemote) lines.push(_('Deleted remote items: %d.', report.deleteRemote));
|
||||||
if (report.state) lines.push(_('State: %s.', report.state.replace(/_/g, ' ')));
|
if (report.state) lines.push(_('State: %s.', report.state.replace(/_/g, ' ')));
|
||||||
if (report.errors && report.errors.length) lines.push(_('Last error: %s (stacktrace in log).', report.errors[report.errors.length-1].message));
|
if (report.errors && report.errors.length) lines.push(_('Last error: %s (stacktrace in log).', report.errors[report.errors.length-1].message));
|
||||||
|
if (report.completedTime) lines.push(_('Completed: %s', time.unixMsToLocalDateTime(report.completedTime)));
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +100,8 @@ class Synchronizer {
|
|||||||
this.progressReport_[action]++;
|
this.progressReport_[action]++;
|
||||||
this.progressReport_.state = this.state();
|
this.progressReport_.state = this.state();
|
||||||
this.onProgress_(this.progressReport_);
|
this.onProgress_(this.progressReport_);
|
||||||
|
|
||||||
|
this.dispatcher_.dispatch('progress', this.progressReport_);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logSyncSummary(report) {
|
async logSyncSummary(report) {
|
||||||
@@ -143,7 +157,7 @@ class Synchronizer {
|
|||||||
const syncTargetId = this.api().driver().syncTargetId();
|
const syncTargetId = this.api().driver().syncTargetId();
|
||||||
|
|
||||||
if (this.state() != 'idle') {
|
if (this.state() != 'idle') {
|
||||||
this.logger().warn('Synchronization is already in progress. State: ' + this.state());
|
this.logger().info('Synchronization is already in progress. State: ' + this.state());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,10 +444,14 @@ class Synchronizer {
|
|||||||
|
|
||||||
this.logSyncOperation('finished', null, null, 'Synchronization finished [' + synchronizationId + ']');
|
this.logSyncOperation('finished', null, null, 'Synchronization finished [' + synchronizationId + ']');
|
||||||
|
|
||||||
|
this.progressReport_.completedTime = time.unixMs();
|
||||||
|
|
||||||
await this.logSyncSummary(this.progressReport_);
|
await this.logSyncSummary(this.progressReport_);
|
||||||
|
|
||||||
this.onProgress_ = function(s) {};
|
this.onProgress_ = function(s) {};
|
||||||
this.progressReport_ = {};
|
this.progressReport_ = {};
|
||||||
|
|
||||||
|
this.dispatcher_.dispatch('complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user