You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Started synchronizer
This commit is contained in:
		| @@ -5,6 +5,11 @@ import { uuid } from 'src/uuid.js'; | ||||
|  | ||||
| class BaseModel { | ||||
|  | ||||
| 	static ITEM_TYPE_NOTE = 1; | ||||
| 	static ITEM_TYPE_FOLDER = 2; | ||||
| 	static tableInfo_ = null; | ||||
| 	static tableKeys_ = null; | ||||
|  | ||||
| 	static tableName() { | ||||
| 		throw new Error('Must be overriden'); | ||||
| 	} | ||||
| @@ -13,6 +18,14 @@ class BaseModel { | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	static itemType() { | ||||
| 		throw new Error('Must be overriden'); | ||||
| 	} | ||||
|  | ||||
| 	static trackChanges() { | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	static byId(items, id) { | ||||
| 		for (let i = 0; i < items.length; i++) { | ||||
| 			if (items[i].id == id) return items[i]; | ||||
| @@ -20,12 +33,31 @@ class BaseModel { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	static save(o) { | ||||
| 		let isNew = !o.id; | ||||
| 	static fieldNames() { | ||||
| 		return this.db().tableFieldNames(this.tableName()); | ||||
| 	} | ||||
|  | ||||
| 	static fromApiResult(apiResult) { | ||||
| 		let fieldNames = this.fieldNames(); | ||||
| 		let output = {}; | ||||
| 		for (let i = 0; i < fieldNames.length; i++) { | ||||
| 			let f = fieldNames[i]; | ||||
| 			output[f] = f in apiResult ? apiResult[f] : null; | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	static saveQuery(o, isNew = 'auto') { | ||||
| 		if (isNew == 'auto') isNew = !o.id;		 | ||||
| 		let query = ''; | ||||
| 		let itemId = o.id; | ||||
|  | ||||
| 		if (isNew) { | ||||
| 			if (this.useUuid()) o.id = uuid.create(); | ||||
| 			if (this.useUuid()) { | ||||
| 				o = Object.assign({}, o); | ||||
| 				itemId = uuid.create(); | ||||
| 				o.id = itemId; | ||||
| 			} | ||||
| 			query = Database.insertQuery(this.tableName(), o); | ||||
| 		} else { | ||||
| 			let where = { id: o.id }; | ||||
| @@ -34,7 +66,44 @@ class BaseModel { | ||||
| 			query = Database.updateQuery(this.tableName(), temp, where); | ||||
| 		} | ||||
|  | ||||
| 		return this.db().exec(query.sql, query.params).then(() => { return o; }); | ||||
| 		query.id = itemId; | ||||
|  | ||||
| 		return query; | ||||
| 	} | ||||
|  | ||||
| 	static save(o, trackChanges = true, isNew = 'auto') { | ||||
| 		if (isNew == 'auto') isNew = !o.id; | ||||
| 		let query = this.saveQuery(o, isNew); | ||||
|  | ||||
| 		return this.db().transaction((tx) => { | ||||
| 			tx.executeSql(query.sql, query.params); | ||||
|  | ||||
| 			if (trackChanges && this.trackChanges()) { | ||||
| 				// Cannot import this class the normal way due to cyclical dependencies between Change and BaseModel | ||||
| 				// which are not handled by React Native. | ||||
| 				const { Change } = require('src/models/change.js'); | ||||
|  | ||||
| 				let change = Change.newChange(); | ||||
| 				change.type = isNew ? Change.TYPE_CREATE : Change.TYPE_UPDATE; | ||||
| 				change.item_id = query.id; | ||||
| 				change.item_type = this.itemType(); | ||||
|  | ||||
| 				let changeQuery = Change.saveQuery(change); | ||||
| 				tx.executeSql(changeQuery.sql, changeQuery.params); | ||||
|  | ||||
| 				// TODO: item field for UPDATE | ||||
| 			} | ||||
| 		}).then(() => { | ||||
| 			o = Object.assign({}, o); | ||||
| 			o.id = query.id; | ||||
|  | ||||
| 			this.dispatch({ | ||||
| 				type: 'FOLDERS_UPDATE_ONE', | ||||
| 				folder: o, | ||||
| 			}); | ||||
|  | ||||
| 			return o; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	static delete(id) { | ||||
|   | ||||
| @@ -29,17 +29,6 @@ class NotesScreenComponent extends React.Component { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	loginButton_press = () => { | ||||
| 		this.props.dispatch({ | ||||
| 			type: 'Navigation/NAVIGATE', | ||||
| 			routeName: 'Login', | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	syncButton_press = () => { | ||||
| 		Log.info('SYNC'); | ||||
| 	} | ||||
|  | ||||
| 	deleteFolder_onPress = (folderId) => { | ||||
| 		Folder.delete(folderId).then(() => { | ||||
| 			this.props.dispatch({ | ||||
| @@ -77,10 +66,6 @@ class NotesScreenComponent extends React.Component { | ||||
| 			<View style={{flex: 1}}> | ||||
| 				<ScreenHeader title={title} navState={this.props.navigation.state} menuOptions={this.menuOptions()} /> | ||||
| 				<NoteList style={{flex: 1}}/> | ||||
| 				<View style={{flexDirection: 'row'}}> | ||||
| 					<Button title="Login" onPress={this.loginButton_press} /> | ||||
| 					<Button title="Sync" onPress={this.syncButton_press} /> | ||||
| 				</View> | ||||
| 				<ActionButton parentFolderId={this.props.selectedFolderId}></ActionButton> | ||||
| 			</View> | ||||
| 		); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import SQLite from 'react-native-sqlite-storage'; | ||||
| import { Log } from 'src/log.js'; | ||||
| import { uuid } from 'src/uuid.js'; | ||||
| import { PromiseChain } from 'src/promise-chain.js'; | ||||
|  | ||||
| const structureSql = ` | ||||
| CREATE TABLE folders ( | ||||
| @@ -78,6 +79,12 @@ CREATE TABLE settings ( | ||||
| 	\`type\` INT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE table_fields ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	table_name TEXT, | ||||
| 	field_name TEXT | ||||
| ); | ||||
|  | ||||
| INSERT INTO version (version) VALUES (1); | ||||
| `; | ||||
|  | ||||
| @@ -86,6 +93,7 @@ class Database { | ||||
| 	constructor() { | ||||
| 		this.debugMode_ = false; | ||||
| 		this.initialized_ = false; | ||||
| 		this.tableFields_ = null; | ||||
| 	} | ||||
|  | ||||
| 	setDebugEnabled(v) { | ||||
| @@ -102,13 +110,13 @@ class Database { | ||||
| 	} | ||||
|  | ||||
| 	open() { | ||||
| 		this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-7.sqlite' }, (db) => { | ||||
| 		this.db_ = SQLite.openDatabase({ name: '/storage/emulated/0/Download/joplin-10.sqlite' }, (db) => { | ||||
| 			Log.info('Database was open successfully'); | ||||
| 		}, (error) => { | ||||
| 			Log.error('Cannot open database: ', error); | ||||
| 		}); | ||||
|  | ||||
| 		return this.updateSchema(); | ||||
| 		return this.initialize(); | ||||
| 	} | ||||
|  | ||||
| 	static enumToId(type, s) { | ||||
| @@ -119,6 +127,12 @@ class Database { | ||||
| 		throw new Error('Unknown enum type or value: ' + type + ', ' + s); | ||||
| 	} | ||||
|  | ||||
| 	tableFieldNames(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]; | ||||
| 	} | ||||
|  | ||||
| 	sqlStringToLines(sql) { | ||||
| 		let output = []; | ||||
| 		let lines = sql.split("\n"); | ||||
| @@ -138,7 +152,7 @@ class Database { | ||||
|  | ||||
| 	logQuery(sql, params = null) { | ||||
| 		if (!this.debugMode()) return; | ||||
| 		Log.debug('DB: ' + sql, params); | ||||
| 		//Log.debug('DB: ' + sql, params); | ||||
| 	} | ||||
|  | ||||
| 	selectOne(sql, params = null) { | ||||
| @@ -220,34 +234,119 @@ class Database { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	updateSchema() { | ||||
| 	refreshTableFields() { | ||||
| 		return this.exec('SELECT name FROM sqlite_master WHERE type="table"').then((tableResults) => { | ||||
| 			let chain = []; | ||||
| 			for (let i = 0; i < tableResults.rows.length; i++) { | ||||
| 				let row = tableResults.rows.item(i); | ||||
| 				let tableName = row.name; | ||||
| 				if (tableName == 'android_metadata') continue; | ||||
| 				if (tableName == 'table_fields') continue; | ||||
|  | ||||
| 				chain.push((queries) => { | ||||
| 					if (!queries) queries = []; | ||||
| 					return this.exec('PRAGMA table_info("' + tableName + '")').then((pragmaResult) => { | ||||
| 						for (let i = 0; i < pragmaResult.rows.length; i++) { | ||||
| 							let q = Database.insertQuery('table_fields', { | ||||
| 								table_name: tableName, | ||||
| 								field_name: pragmaResult.rows.item(i).name, | ||||
| 							}); | ||||
| 							queries.push(q); | ||||
| 						} | ||||
| 						return queries; | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			return PromiseChain.exec(chain).then((queries) => { | ||||
| 				return this.transaction((tx) => { | ||||
| 					tx.executeSql('DELETE FROM table_fields'); | ||||
| 					for (let i = 0; i < queries.length; i++) { | ||||
| 						tx.executeSql(queries[i].sql, queries[i].params); | ||||
| 					} | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	initialize() { | ||||
| 		Log.info('Checking for database schema update...'); | ||||
|  | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			this.selectOne('SELECT * FROM version LIMIT 1').then((row) => { | ||||
| 		return this.selectOne('SELECT * FROM version LIMIT 1').then((row) => { | ||||
| 			Log.info('Current database version', row); | ||||
| 				resolve(); | ||||
| 			// TODO: version update logic | ||||
|  | ||||
| 			// TODO: only do this if db has been updated: | ||||
| 			return this.refreshTableFields(); | ||||
| 		}).then(() => { | ||||
| 			return this.exec('SELECT * FROM table_fields').then((r) => { | ||||
| 				this.tableFields_ = {}; | ||||
| 				for (let i = 0; i < r.rows.length; i++) { | ||||
| 					let row = r.rows.item(i); | ||||
| 					if (!this.tableFields_[row.table_name]) this.tableFields_[row.table_name] = []; | ||||
| 					this.tableFields_[row.table_name].push(row.field_name); | ||||
| 				} | ||||
| 			}); | ||||
| 		}).catch((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. | ||||
|  | ||||
| 			Log.info('Database is new - creating the schema...'); | ||||
|  | ||||
| 			let statements = this.sqlStringToLines(structureSql) | ||||
| 				this.transaction((tx) => { | ||||
| 			return this.transaction((tx) => { | ||||
| 				for (let i = 0; i < statements.length; i++) { | ||||
| 					tx.executeSql(statements[i]); | ||||
| 				} | ||||
| 				tx.executeSql('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumToId('settings', 'string') + '")'); | ||||
| 			}).then(() => { | ||||
| 					resolve('Database schema created successfully'); | ||||
| 				}).catch((error) => { | ||||
| 					reject(error); | ||||
| 				}); | ||||
| 			}); | ||||
| 				Log.info('Database schema created successfully'); | ||||
| 				// Calling initialize() now that the db has been created will make it go through | ||||
| 				// the normal db update process (applying any additional patch). | ||||
| 				return this.initialize(); | ||||
| 			}) | ||||
| 		}); | ||||
|  | ||||
| 		// return new Promise((resolve, reject) => { | ||||
| 		// 	this.selectOne('SELECT * FROM version LIMIT 1').then((row) => { | ||||
| 		// 		Log.info('Current database version', row); | ||||
| 		// 		// TODO: version update logic | ||||
| 		// 		// TODO: only do this if db has been updated | ||||
| 		// 		return this.refreshTableFields(); | ||||
| 		// 	}).then(() => { | ||||
| 		// 		return this.exec('SELECT * FROM table_fields').then((r) => { | ||||
| 		// 			this.tableFields_ = {}; | ||||
| 		// 			for (let i = 0; i < r.rows.length; i++) { | ||||
| 		// 				let row = r.rows.item(i); | ||||
| 		// 				if (!this.tableFields_[row.table_name]) this.tableFields_[row.table_name] = []; | ||||
| 		// 				this.tableFields_[row.table_name].push(row.field_name); | ||||
| 		// 			} | ||||
| 		// 		}); | ||||
| 		// 	}).catch((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. | ||||
|  | ||||
| 		// 		Log.info('Database is new - creating the schema...'); | ||||
|  | ||||
| 		// 		let statements = this.sqlStringToLines(structureSql) | ||||
| 		// 		this.transaction((tx) => { | ||||
| 		// 			for (let i = 0; i < statements.length; i++) { | ||||
| 		// 				tx.executeSql(statements[i]); | ||||
| 		// 			} | ||||
| 		// 			tx.executeSql('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumToId('settings', 'string') + '")'); | ||||
| 		// 		}).then(() => { | ||||
| 		// 			Log.info('Database schema created successfully'); | ||||
| 		// 			// Calling initialize() now that the db has been created will make it go through | ||||
| 		// 			// the normal db update process (applying any additional patch). | ||||
| 		// 			return this.initialize(); | ||||
| 		// 		}).catch((error) => { | ||||
| 		// 			reject(error); | ||||
| 		// 		}); | ||||
| 		// 	}); | ||||
| 		// }); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										37
									
								
								ReactNativeClient/src/models/change.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								ReactNativeClient/src/models/change.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import { BaseModel } from 'src/base-model.js'; | ||||
| import { Log } from 'src/log.js'; | ||||
|  | ||||
| class Change extends BaseModel { | ||||
|  | ||||
| 	static TYPE_UNKNOWN = 0; | ||||
| 	static TYPE_CREATE = 1; | ||||
| 	static TYPE_UPDATE = 2; | ||||
| 	static TYPE_DELETE = 3; | ||||
|  | ||||
| 	static tableName() { | ||||
| 		return 'changes'; | ||||
| 	} | ||||
|  | ||||
| 	static newChange() { | ||||
| 		return { | ||||
| 			id: null, | ||||
| 			type: null, | ||||
| 			item_id: null, | ||||
| 			item_type: null, | ||||
| 			item_field: null, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	// static all() { | ||||
| 	// 	return this.db().selectAll('SELECT * FROM folders').then((r) => { | ||||
| 	// 		let output = []; | ||||
| 	// 		for (let i = 0; i < r.rows.length; i++) { | ||||
| 	// 			output.push(r.rows.item(i)); | ||||
| 	// 		} | ||||
| 	// 		return output; | ||||
| 	// 	}); | ||||
| 	// } | ||||
|  | ||||
| } | ||||
|  | ||||
| export { Change }; | ||||
| @@ -11,6 +11,14 @@ class Folder extends BaseModel { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	static itemType() { | ||||
| 		return BaseModel.ITEM_TYPE_FOLDER; | ||||
| 	} | ||||
|  | ||||
| 	static trackChanges() { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	static newFolder() { | ||||
| 		return { | ||||
| 			id: null, | ||||
|   | ||||
| @@ -11,6 +11,14 @@ class Note extends BaseModel { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	static itemType() { | ||||
| 		return BaseModel.ITEM_TYPE_NOTE; | ||||
| 	} | ||||
|  | ||||
| 	static trackChanges() { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	static newNote(parentId = null) { | ||||
| 		return { | ||||
| 			id: null, | ||||
|   | ||||
| @@ -7,9 +7,9 @@ class Setting extends BaseModel { | ||||
| 	static defaults_ = { | ||||
| 		'clientId': { value: '', type: 'string' }, | ||||
| 		'sessionId': { value: '', type: 'string' }, | ||||
| 		'lastUpdateTime': { value: '', type: 'int' }, | ||||
| 		'user.email': { value: '', type: 'string' }, | ||||
| 		'user.session': { value: '', type: 'string' }, | ||||
| 		'sync.lastRevId': { value: 0, type: 'int' }, | ||||
| 	}; | ||||
|  | ||||
| 	static tableName() { | ||||
|   | ||||
							
								
								
									
										14
									
								
								ReactNativeClient/src/promise-chain.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ReactNativeClient/src/promise-chain.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| class PromiseChain { | ||||
|  | ||||
| 	static exec(chain) { | ||||
| 		let output = new Promise((resolve, reject) => { resolve(); }); | ||||
| 		for (let i = 0; i < chain.length; i++) { | ||||
| 			let f = chain[i]; | ||||
| 			output = output.then(f); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| export { PromiseChain }; | ||||
| @@ -9,6 +9,7 @@ import { addNavigationHelpers } from 'react-navigation'; | ||||
| import { Log } from 'src/log.js' | ||||
| import { Note } from 'src/models/note.js' | ||||
| import { Folder } from 'src/models/folder.js' | ||||
| import { BaseModel } from 'src/base-model.js' | ||||
| import { Database } from 'src/database.js' | ||||
| import { Registry } from 'src/registry.js' | ||||
| import { ItemList } from 'src/components/item-list.js' | ||||
| @@ -18,6 +19,7 @@ import { FolderScreen } from 'src/components/screens/folder.js' | ||||
| import { FoldersScreen } from 'src/components/screens/folders.js' | ||||
| import { LoginScreen } from 'src/components/screens/login.js' | ||||
| import { Setting } from 'src/models/setting.js' | ||||
| import { Synchronizer } from 'src/synchronizer.js' | ||||
| import { MenuContext } from 'react-native-popup-menu'; | ||||
|  | ||||
| let defaultState = { | ||||
| @@ -163,6 +165,8 @@ class AppComponent extends React.Component { | ||||
| 		let db = new Database(); | ||||
| 		db.setDebugEnabled(Registry.debugMode()); | ||||
|  | ||||
| 		BaseModel.dispatch = this.props.dispatch; | ||||
|  | ||||
| 		db.open().then(() => { | ||||
| 			Log.info('Database is ready.'); | ||||
| 			Registry.setDb(db); | ||||
| @@ -174,6 +178,8 @@ class AppComponent extends React.Component { | ||||
| 			Log.info('Client ID', Setting.value('clientId')); | ||||
| 			Log.info('User', user); | ||||
|  | ||||
| 			Registry.api().setSession(user.session); | ||||
|  | ||||
| 			this.props.dispatch({ | ||||
| 				type: 'USER_SET', | ||||
| 				user: user, | ||||
| @@ -189,8 +195,11 @@ class AppComponent extends React.Component { | ||||
| 			}).catch((error) => { | ||||
| 				Log.warn('Cannot load folders', error); | ||||
| 			}); | ||||
| 		}).then(() => { | ||||
| 			let synchronizer = new Synchronizer(); | ||||
| 			synchronizer.start(); | ||||
| 		}).catch((error) => { | ||||
| 			Log.error('Cannot initialize database:', error); | ||||
| 			Log.error('Initialization error:', error); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										70
									
								
								ReactNativeClient/src/synchronizer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								ReactNativeClient/src/synchronizer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| import { Registry } from 'src/registry.js'; | ||||
| import { Log } from 'src/log.js'; | ||||
| import { Setting } from 'src/models/setting.js'; | ||||
| import { Change } from 'src/models/change.js'; | ||||
| import { Folder } from 'src/models/folder.js'; | ||||
|  | ||||
| class Synchronizer { | ||||
|  | ||||
| 	constructor() { | ||||
| 		this.state_ = 'idle'; | ||||
| 	} | ||||
|  | ||||
| 	state() { | ||||
| 		return this.state_; | ||||
| 	} | ||||
|  | ||||
| 	db() { | ||||
| 		return Registry.db(); | ||||
| 	} | ||||
|  | ||||
| 	api() { | ||||
| 		return Registry.api(); | ||||
| 	} | ||||
|  | ||||
| 	switchState(state) { | ||||
| 		Log.info('Sync: switching state to: ' + state); | ||||
|  | ||||
| 		if (state == 'downloadChanges') { | ||||
| 			this.api().get('synchronizer', { last_id: Setting.value('sync.lastRevId') }).then((syncOperations) => { | ||||
| 				let promise = new Promise((resolve, reject) => { resolve(); }); | ||||
| 				for (let i = 0; i < syncOperations.items.length; i++) { | ||||
| 					let syncOp = syncOperations.items[i]; | ||||
| 					if (syncOp.item_type == 'folder') { | ||||
| 						if (syncOp.type == 'create') { | ||||
| 							promise = promise.then(() => { | ||||
| 								let folder = Folder.fromApiResult(syncOp.item); | ||||
| 								// TODO: automatically handle NULL fields by checking type and default value of field | ||||
| 								if (!folder.parent_id) folder.parent_id = ''; | ||||
| 								return Folder.save(folder, true, true); | ||||
| 							}); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				promise.then(() => { | ||||
| 					Log.info('All items synced.'); | ||||
| 				}).catch((error) => { | ||||
| 					Log.warn('Sync error', error); | ||||
| 				}); | ||||
| 			}); | ||||
| 		} else { | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	start() { | ||||
|  | ||||
| 		if (this.state() != 'idle') { | ||||
| 			Log.info("Sync: cannot start synchronizer because synchronization already in progress. State: " + this.state()); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		Log.info('Sync: start'); | ||||
|  | ||||
| 		this.switchState('downloadChanges'); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| export { Synchronizer }; | ||||
| @@ -5,6 +5,15 @@ class WebApi { | ||||
|  | ||||
| 	constructor(baseUrl) { | ||||
| 		this.baseUrl_ = baseUrl; | ||||
| 		this.session_ = null; | ||||
| 	} | ||||
|  | ||||
| 	setSession(v) { | ||||
| 		this.session_ = v; | ||||
| 	} | ||||
|  | ||||
| 	session() { | ||||
| 		return this.session_; | ||||
| 	} | ||||
|  | ||||
| 	makeRequest(method, path, query, data) { | ||||
| @@ -38,14 +47,18 @@ class WebApi { | ||||
| 		if (o.method != 'GET' && o.method != 'DELETE') { | ||||
| 			cmd.push("--data '" + stringify(data) + "'"); | ||||
| 		} | ||||
| 		cmd.push(r.url); | ||||
| 		cmd.push("'" + r.url + "'"); | ||||
| 		return cmd.join(' '); | ||||
| 	} | ||||
|  | ||||
| 	exec(method, path, query, data) { | ||||
| 		let that = this; | ||||
| 		return new Promise(function(resolve, reject) { | ||||
| 			let r = that.makeRequest(method, path, query, data); | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			if (this.session_) { | ||||
| 				query = query ? Object.assign({}, query) : {}; | ||||
| 				if (!query.session) query.session = this.session_; | ||||
| 			} | ||||
|  | ||||
| 			let r = this.makeRequest(method, path, query, data); | ||||
|  | ||||
| 			Log.debug(WebApi.toCurl(r, data)); | ||||
|  | ||||
|   | ||||
| @@ -26,7 +26,7 @@ function config($name) { | ||||
| 		'baseUrl' => $baseUrl, | ||||
| 		'clientId' => 'E3E3E3E3E3E3E3E3E3E3E3E3E3E3E3E3', | ||||
| 		'email' => 'laurent@cozic.net', | ||||
| 		'password' => '123456789', | ||||
| 		'password' => '12345678', | ||||
| 	); | ||||
| 	if (isset($config[$name])) return $config[$name]; | ||||
| 	throw new Exception('Unknown config: ' . $name); | ||||
|   | ||||
| @@ -381,7 +381,7 @@ class BaseModel extends \Illuminate\Database\Eloquent\Model { | ||||
|  | ||||
| 		if ($this->isVersioned) { | ||||
| 			if (count($changedFields)) { | ||||
| 				$this->recordChanges($isNew ? 'create' : 'update', $changedFields); | ||||
| 				$this->trackChanges($isNew ? 'create' : 'update', $changedFields); | ||||
| 			} | ||||
| 			$this->changedDiffableFields = array(); | ||||
| 		} | ||||
| @@ -395,13 +395,13 @@ class BaseModel extends \Illuminate\Database\Eloquent\Model { | ||||
| 		$output = parent::delete(); | ||||
|  | ||||
| 		if (count($this->isVersioned)) { | ||||
| 			$this->recordChanges('delete'); | ||||
| 			$this->trackChanges('delete'); | ||||
| 		} | ||||
|  | ||||
| 		return $output; | ||||
| 	} | ||||
|  | ||||
| 	protected function recordChanges($type, $changedFields = array()) { | ||||
| 	protected function trackChanges($type, $changedFields = array()) { | ||||
| 		if ($type == 'delete') { | ||||
| 			$change = $this->newChange($type); | ||||
| 			$change->save(); | ||||
|   | ||||
| @@ -46,6 +46,7 @@ class Change extends BaseModel { | ||||
| 			$itemIdToChange[$change->item_id] = $change; | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		$output = array(); | ||||
| 		foreach ($itemIdToChange as $itemId => $change) { | ||||
| 			if (in_array($itemId, $createdItems) && in_array($itemId, $deletedItems)) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user