1
0
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:
Laurent Cozic
2017-07-16 22:17:22 +01:00
parent 576cb345bb
commit 6673baee91
8 changed files with 138 additions and 31 deletions

View File

@@ -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');

View File

@@ -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) {}

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>
); );

View 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 };

View File

@@ -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;
} }

View File

@@ -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');
} }
} }