You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	refresh OneDrive token
This commit is contained in:
		| @@ -1,657 +0,0 @@ | ||||
| require('source-map-support').install(); | ||||
| require('babel-plugin-transform-runtime'); | ||||
|  | ||||
| import { FileApi } from 'src/file-api.js'; | ||||
| import { FileApiDriverLocal } from 'src/file-api-driver-local.js'; | ||||
| import { FileApiDriverMemory } from 'src/file-api-driver-memory.js'; | ||||
| import { Database } from 'src/database.js'; | ||||
| import { DatabaseDriverNode } from 'src/database-driver-node.js'; | ||||
| import { BaseModel } from 'src/base-model.js'; | ||||
| import { Folder } from 'src/models/folder.js'; | ||||
| import { Note } from 'src/models/note.js'; | ||||
| import { Setting } from 'src/models/setting.js'; | ||||
| import { Synchronizer } from 'src/synchronizer.js'; | ||||
| import { uuid } from 'src/uuid.js'; | ||||
| import { sprintf } from 'sprintf-js'; | ||||
| import { _ } from 'src/locale.js'; | ||||
|  | ||||
| let db = new Database(new DatabaseDriverNode()); | ||||
| let fileDriver = new FileApiDriverLocal(); | ||||
| let fileApi = new FileApi('/home/laurent/Temp/TestImport', fileDriver); | ||||
| let synchronizer = new Synchronizer(db, fileApi); | ||||
| const vorpal = require('vorpal')(); | ||||
|  | ||||
| async function main() { | ||||
| 	await db.open({ name: '/home/laurent/Temp/test.sqlite3' }); | ||||
| 	BaseModel.db_ = db; | ||||
| 	await Setting.load(); | ||||
|  | ||||
| 	let commands = []; | ||||
| 	let currentFolder = null; | ||||
|  | ||||
| 	function switchCurrentFolder(folder) { | ||||
| 		currentFolder = folder; | ||||
| 		updatePrompt(); | ||||
| 	} | ||||
|  | ||||
| 	function promptString() { | ||||
| 		let path = '~'; | ||||
| 		if (currentFolder) { | ||||
| 			path += '/' + currentFolder.title; | ||||
| 		} | ||||
| 		return 'joplin:' + path + '$ '; | ||||
| 	} | ||||
|  | ||||
| 	function updatePrompt() { | ||||
| 		vorpal.delimiter(promptString()); | ||||
| 	} | ||||
|  | ||||
| 	// For now, to go around this issue: https://github.com/dthree/vorpal/issues/114 | ||||
| 	function quotePromptArg(s) { | ||||
| 		if (s.indexOf(' ') >= 0) { | ||||
| 			return '"' + s + '"'; | ||||
| 		} | ||||
| 		return s; | ||||
| 	} | ||||
|  | ||||
| 	function autocompleteFolders() { | ||||
| 		return Folder.all().then((folders) => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < folders.length; i++) { | ||||
| 				output.push(quotePromptArg(folders[i].title)); | ||||
| 			} | ||||
| 			output.push('..'); | ||||
| 			output.push('.'); | ||||
| 			return output; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function autocompleteItems() { | ||||
| 		let promise = null; | ||||
| 		if (!currentFolder) { | ||||
| 			promise = Folder.all(); | ||||
| 		} else { | ||||
| 			promise = Note.previews(currentFolder.id); | ||||
| 		} | ||||
|  | ||||
| 		return promise.then((items) => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < items.length; i++) { | ||||
| 				output.push(quotePromptArg(items[i].title)); | ||||
| 			} | ||||
| 			return output;			 | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	process.stdin.on('keypress', (_, key) => { | ||||
| 		if (key && key.name === 'return') { | ||||
| 			updatePrompt(); | ||||
| 		} | ||||
|  | ||||
| 		if (key.name === 'tab') { | ||||
| 			vorpal.ui.imprint(); | ||||
| 			vorpal.log(vorpal.ui.input()); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'cd <list-title>', | ||||
| 		description: 'Moved to [list-title] - all further operations will happen within this list. Use `cd ..` to go back one level.', | ||||
| 		action: function (args, end) { | ||||
| 			let folderTitle = args['list-title']; | ||||
|  | ||||
| 			if (folderTitle == '..') { | ||||
| 				switchCurrentFolder(null); | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if (folderTitle == '.') { | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			Folder.loadByField('title', folderTitle).then((folder) => { | ||||
| 				switchCurrentFolder(folder); | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteFolders, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'mklist <list-title>', | ||||
| 		alias: 'mkdir', | ||||
| 		description: 'Creates a new list', | ||||
| 		action: function (args, end) { | ||||
| 			Folder.save({ title: args['list-title'] }).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then((folder) => { | ||||
| 				switchCurrentFolder(folder); | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'mknote <note-title>', | ||||
| 		alias: 'touch', | ||||
| 		description: 'Creates a new note', | ||||
| 		action: function (args, end) { | ||||
| 			if (!currentFolder) { | ||||
| 				this.log('Notes can only be created within a list.'); | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			let note = { | ||||
| 				title: args['note-title'], | ||||
| 				parent_id: currentFolder.id, | ||||
| 			}; | ||||
| 			Note.save(note).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then((note) => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'set <item-title> <prop-name> [prop-value]', | ||||
| 		description: 'Sets the given <prop-name> of the given item.', | ||||
| 		action: function (args, end) { | ||||
| 			let promise = null; | ||||
| 			let title = args['item-title']; | ||||
| 			let propName = args['prop-name']; | ||||
| 			let propValue = args['prop-value']; | ||||
| 			if (!propValue) propValue = ''; | ||||
|  | ||||
| 			if (!currentFolder) { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				let newItem = { | ||||
| 					id: item.id, | ||||
| 					type_: item.type_, | ||||
| 				}; | ||||
| 				newItem[propName] = propValue; | ||||
| 				let ItemClass = BaseItem.itemClass(); | ||||
| 				return ItemClass.save(newItem); | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'cat <item-title>', | ||||
| 		description: 'Displays the given item data.', | ||||
| 		action: function (args, end) { | ||||
| 			let title = args['item-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
| 			if (!currentFolder) { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				if (!currentFolder) { | ||||
| 					this.log(Folder.serialize(item)); | ||||
| 				} else { | ||||
| 					this.log(Note.serialize(item)); | ||||
| 				} | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'rm <item-title>', | ||||
| 		description: 'Deletes the given item. For a list, all the notes within that list will be deleted.', | ||||
| 		action: function (args, end) { | ||||
| 			let title = args['item-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
| 			let itemType = currentFolder ? 'note' : 'folder'; | ||||
| 			if (itemType == 'folder') { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				if (itemType == 'folder') { | ||||
| 					return Folder.delete(item.id); | ||||
| 				} else { | ||||
| 					return Note.delete(item.id); | ||||
| 				} | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'ls [list-title]', | ||||
| 		alias: 'll', | ||||
| 		description: 'Lists items in [list-title].', | ||||
| 		action: function (args, end) { | ||||
| 			let folderTitle = args['list-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
|  | ||||
| 			if (folderTitle == '..') { | ||||
| 				promise = Promise.resolve('root'); | ||||
| 			} else if (folderTitle && folderTitle != '.') { | ||||
| 				promise = Folder.loadByField('title', folderTitle); | ||||
| 			} else if (currentFolder) { | ||||
| 				promise = Promise.resolve(currentFolder); | ||||
| 			} else { | ||||
| 				promise = Promise.resolve('root'); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((folder) => { | ||||
| 				let p = null | ||||
| 				let postfix = ''; | ||||
| 				if (folder === 'root') { | ||||
| 					p = Folder.all(); | ||||
| 					postfix = '/'; | ||||
| 				} else if (!folder) { | ||||
| 					throw new Error(_('Unknown list: "%s"', folderTitle)); | ||||
| 				} else { | ||||
| 					p = Note.previews(folder.id); | ||||
| 				} | ||||
|  | ||||
| 				return p.then((previews) => { | ||||
| 					for (let i = 0; i < previews.length; i++) { | ||||
| 						this.log(previews[i].title + postfix); | ||||
| 					} | ||||
| 				}); | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteFolders, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'sync', | ||||
| 		description: 'Synchronizes with remote storage.', | ||||
| 		action: function (args, end) { | ||||
| 			synchronizer.start().catch((error) => { | ||||
| 				console.error(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	for (let i = 0; i < commands.length; i++) { | ||||
| 		let c = commands[i]; | ||||
| 		let o = vorpal.command(c.usage, c.description); | ||||
| 		if (c.alias) { | ||||
| 			o.alias(c.alias); | ||||
| 		} | ||||
| 		if (c.autocomplete) { | ||||
| 			o.autocomplete({ | ||||
| 				data: c.autocomplete, | ||||
| 			}); | ||||
| 		} | ||||
| 		o.action(c.action); | ||||
| 	} | ||||
|  | ||||
| 	vorpal.delimiter(promptString()).show(); | ||||
| } | ||||
|  | ||||
| main(); | ||||
|  | ||||
| // 	BaseModel.db_ = db; | ||||
| // }).then(() => { | ||||
| // 	return Setting.load(); | ||||
| // }).then(() => { | ||||
| // 	let commands = []; | ||||
| // 	let currentFolder = null; | ||||
|  | ||||
| // 	function switchCurrentFolder(folder) { | ||||
| // 		currentFolder = folder; | ||||
| // 		updatePrompt(); | ||||
| // 	} | ||||
|  | ||||
| // 	function promptString() { | ||||
| // 		let path = '~'; | ||||
| // 		if (currentFolder) { | ||||
| // 			path += '/' + currentFolder.title; | ||||
| // 		} | ||||
| // 		return 'joplin:' + path + '$ '; | ||||
| // 	} | ||||
|  | ||||
| // 	function updatePrompt() { | ||||
| // 		vorpal.delimiter(promptString()); | ||||
| // 	} | ||||
|  | ||||
| // 	// For now, to go around this issue: https://github.com/dthree/vorpal/issues/114 | ||||
| // 	function quotePromptArg(s) { | ||||
| // 		if (s.indexOf(' ') >= 0) { | ||||
| // 			return '"' + s + '"'; | ||||
| // 		} | ||||
| // 		return s; | ||||
| // 	} | ||||
|  | ||||
| // 	function autocompleteFolders() { | ||||
| // 		return Folder.all().then((folders) => { | ||||
| // 			let output = []; | ||||
| // 			for (let i = 0; i < folders.length; i++) { | ||||
| // 				output.push(quotePromptArg(folders[i].title)); | ||||
| // 			} | ||||
| // 			output.push('..'); | ||||
| // 			output.push('.'); | ||||
| // 			return output; | ||||
| // 		}); | ||||
| // 	} | ||||
|  | ||||
| // 	function autocompleteItems() { | ||||
| // 		let promise = null; | ||||
| // 		if (!currentFolder) { | ||||
| // 			promise = Folder.all(); | ||||
| // 		} else { | ||||
| // 			promise = Note.previews(currentFolder.id); | ||||
| // 		} | ||||
|  | ||||
| // 		return promise.then((items) => { | ||||
| // 			let output = []; | ||||
| // 			for (let i = 0; i < items.length; i++) { | ||||
| // 				output.push(quotePromptArg(items[i].title)); | ||||
| // 			} | ||||
| // 			return output;			 | ||||
| // 		}); | ||||
| // 	} | ||||
|  | ||||
| // 	process.stdin.on('keypress', (_, key) => { | ||||
| // 		if (key && key.name === 'return') { | ||||
| // 			updatePrompt(); | ||||
| // 		} | ||||
|  | ||||
| // 		if (key.name === 'tab') { | ||||
| // 			vorpal.ui.imprint(); | ||||
| // 			vorpal.log(vorpal.ui.input()); | ||||
| // 		} | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'cd <list-title>', | ||||
| // 		description: 'Moved to [list-title] - all further operations will happen within this list. Use `cd ..` to go back one level.', | ||||
| // 		action: function (args, end) { | ||||
| // 			let folderTitle = args['list-title']; | ||||
|  | ||||
| // 			if (folderTitle == '..') { | ||||
| // 				switchCurrentFolder(null); | ||||
| // 				end(); | ||||
| // 				return; | ||||
| // 			} | ||||
|  | ||||
| // 			if (folderTitle == '.') { | ||||
| // 				end(); | ||||
| // 				return; | ||||
| // 			} | ||||
|  | ||||
| // 			Folder.loadByField('title', folderTitle).then((folder) => { | ||||
| // 				switchCurrentFolder(folder); | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 		autocomplete: autocompleteFolders, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'mklist <list-title>', | ||||
| // 		alias: 'mkdir', | ||||
| // 		description: 'Creates a new list', | ||||
| // 		action: function (args, end) { | ||||
| // 			Folder.save({ title: args['list-title'] }).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then((folder) => { | ||||
| // 				switchCurrentFolder(folder); | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'mknote <note-title>', | ||||
| // 		alias: 'touch', | ||||
| // 		description: 'Creates a new note', | ||||
| // 		action: function (args, end) { | ||||
| // 			if (!currentFolder) { | ||||
| // 				this.log('Notes can only be created within a list.'); | ||||
| // 				end(); | ||||
| // 				return; | ||||
| // 			} | ||||
|  | ||||
| // 			let note = { | ||||
| // 				title: args['note-title'], | ||||
| // 				parent_id: currentFolder.id, | ||||
| // 			}; | ||||
| // 			Note.save(note).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then((note) => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'set <item-title> <prop-name> [prop-value]', | ||||
| // 		description: 'Sets the given <prop-name> of the given item.', | ||||
| // 		action: function (args, end) { | ||||
| // 			let promise = null; | ||||
| // 			let title = args['item-title']; | ||||
| // 			let propName = args['prop-name']; | ||||
| // 			let propValue = args['prop-value']; | ||||
| // 			if (!propValue) propValue = ''; | ||||
|  | ||||
| // 			if (!currentFolder) { | ||||
| // 				promise = Folder.loadByField('title', title); | ||||
| // 			} else { | ||||
| // 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| // 			} | ||||
|  | ||||
| // 			promise.then((item) => { | ||||
| // 				if (!item) { | ||||
| // 					this.log(_('No item with title "%s" found.', title)); | ||||
| // 					end(); | ||||
| // 					return; | ||||
| // 				} | ||||
|  | ||||
| // 				let newItem = { | ||||
| // 					id: item.id, | ||||
| // 					type_: item.type_, | ||||
| // 				}; | ||||
| // 				newItem[propName] = propValue; | ||||
| // 				let ItemClass = BaseItem.itemClass(); | ||||
| // 				return ItemClass.save(newItem); | ||||
| // 			}).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then(() => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 		autocomplete: autocompleteItems, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'cat <item-title>', | ||||
| // 		description: 'Displays the given item data.', | ||||
| // 		action: function (args, end) { | ||||
| // 			let title = args['item-title']; | ||||
|  | ||||
| // 			let promise = null; | ||||
| // 			if (!currentFolder) { | ||||
| // 				promise = Folder.loadByField('title', title); | ||||
| // 			} else { | ||||
| // 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| // 			} | ||||
|  | ||||
| // 			promise.then((item) => { | ||||
| // 				if (!item) { | ||||
| // 					this.log(_('No item with title "%s" found.', title)); | ||||
| // 					end(); | ||||
| // 					return; | ||||
| // 				} | ||||
|  | ||||
| // 				if (!currentFolder) { | ||||
| // 					this.log(Folder.serialize(item)); | ||||
| // 				} else { | ||||
| // 					this.log(Note.serialize(item)); | ||||
| // 				} | ||||
| // 			}).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then(() => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 		autocomplete: autocompleteItems, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'rm <item-title>', | ||||
| // 		description: 'Deletes the given item. For a list, all the notes within that list will be deleted.', | ||||
| // 		action: function (args, end) { | ||||
| // 			let title = args['item-title']; | ||||
|  | ||||
| // 			let promise = null; | ||||
| // 			let itemType = currentFolder ? 'note' : 'folder'; | ||||
| // 			if (itemType == 'folder') { | ||||
| // 				promise = Folder.loadByField('title', title); | ||||
| // 			} else { | ||||
| // 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| // 			} | ||||
|  | ||||
| // 			promise.then((item) => { | ||||
| // 				if (!item) { | ||||
| // 					this.log(_('No item with title "%s" found.', title)); | ||||
| // 					end(); | ||||
| // 					return; | ||||
| // 				} | ||||
|  | ||||
| // 				if (itemType == 'folder') { | ||||
| // 					return Folder.delete(item.id); | ||||
| // 				} else { | ||||
| // 					return Note.delete(item.id); | ||||
| // 				} | ||||
| // 			}).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then(() => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 		autocomplete: autocompleteItems, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'ls [list-title]', | ||||
| // 		alias: 'll', | ||||
| // 		description: 'Lists items in [list-title].', | ||||
| // 		action: function (args, end) { | ||||
| // 			let folderTitle = args['list-title']; | ||||
|  | ||||
| // 			let promise = null; | ||||
|  | ||||
| // 			if (folderTitle == '..') { | ||||
| // 				promise = Promise.resolve('root'); | ||||
| // 			} else if (folderTitle && folderTitle != '.') { | ||||
| // 				promise = Folder.loadByField('title', folderTitle); | ||||
| // 			} else if (currentFolder) { | ||||
| // 				promise = Promise.resolve(currentFolder); | ||||
| // 			} else { | ||||
| // 				promise = Promise.resolve('root'); | ||||
| // 			} | ||||
|  | ||||
| // 			promise.then((folder) => { | ||||
| // 				let p = null | ||||
| // 				let postfix = ''; | ||||
| // 				if (folder === 'root') { | ||||
| // 					p = Folder.all(); | ||||
| // 					postfix = '/'; | ||||
| // 				} else if (!folder) { | ||||
| // 					throw new Error(_('Unknown list: "%s"', folderTitle)); | ||||
| // 				} else { | ||||
| // 					p = Note.previews(folder.id); | ||||
| // 				} | ||||
|  | ||||
| // 				return p.then((previews) => { | ||||
| // 					for (let i = 0; i < previews.length; i++) { | ||||
| // 						this.log(previews[i].title + postfix); | ||||
| // 					} | ||||
| // 				}); | ||||
| // 			}).catch((error) => { | ||||
| // 				this.log(error); | ||||
| // 			}).then(() => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 		autocomplete: autocompleteFolders, | ||||
| // 	}); | ||||
|  | ||||
| // 	commands.push({ | ||||
| // 		usage: 'sync', | ||||
| // 		description: 'Synchronizes with remote storage.', | ||||
| // 		action: function (args, end) { | ||||
| // 			synchronizer.start().catch((error) => { | ||||
| // 				console.error(error); | ||||
| // 			}).then(() => { | ||||
| // 				end(); | ||||
| // 			}); | ||||
| // 		}, | ||||
| // 	}); | ||||
|  | ||||
| // 	for (let i = 0; i < commands.length; i++) { | ||||
| // 		let c = commands[i]; | ||||
| // 		let o = vorpal.command(c.usage, c.description); | ||||
| // 		if (c.alias) { | ||||
| // 			o.alias(c.alias); | ||||
| // 		} | ||||
| // 		if (c.autocomplete) { | ||||
| // 			o.autocomplete({ | ||||
| // 				data: c.autocomplete, | ||||
| // 			}); | ||||
| // 		} | ||||
| // 		o.action(c.action); | ||||
| // 	} | ||||
|  | ||||
| // 	vorpal.delimiter(promptString()).show(); | ||||
| // }); | ||||
							
								
								
									
										384
									
								
								CliClient/app/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								CliClient/app/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,384 @@ | ||||
| require('source-map-support').install(); | ||||
| require('babel-plugin-transform-runtime'); | ||||
|  | ||||
| import { FileApi } from 'src/file-api.js'; | ||||
| import { FileApiDriverOneDrive } from 'src/file-api-driver-onedrive.js'; | ||||
| import { Database } from 'src/database.js'; | ||||
| import { DatabaseDriverNode } from 'src/database-driver-node.js'; | ||||
| import { BaseModel } from 'src/base-model.js'; | ||||
| import { Folder } from 'src/models/folder.js'; | ||||
| import { Note } from 'src/models/note.js'; | ||||
| import { Setting } from 'src/models/setting.js'; | ||||
| import { Synchronizer } from 'src/synchronizer.js'; | ||||
| import { uuid } from 'src/uuid.js'; | ||||
| import { sprintf } from 'sprintf-js'; | ||||
| import { _ } from 'src/locale.js'; | ||||
| import os from 'os'; | ||||
| import fs from 'fs-extra'; | ||||
|  | ||||
|  | ||||
| let db = new Database(new DatabaseDriverNode()); | ||||
| let synchronizer_ = null; | ||||
| const vorpal = require('vorpal')(); | ||||
| const APPNAME = 'joplin'; | ||||
|  | ||||
| async function main() { | ||||
| 	let dataDir = os.homedir() + '/.local/share/' + APPNAME; | ||||
| 	await fs.mkdirp(dataDir, 0o755); | ||||
|  | ||||
| 	await db.open({ name: dataDir + '/database.sqlite' }); | ||||
| 	BaseModel.db_ = db; | ||||
| 	await Setting.load(); | ||||
|  | ||||
| 	let commands = []; | ||||
| 	let currentFolder = null; | ||||
|  | ||||
| 	async function synchronizer(remoteBackend) { | ||||
| 		if (synchronizer_) return synchronizer_; | ||||
|  | ||||
| 		let fileApi = null; | ||||
|  | ||||
| 		if (remoteBackend == 'onedrive') { | ||||
| 			const CLIENT_ID = 'e09fc0de-c958-424f-83a2-e56a721d331b'; | ||||
| 			const CLIENT_SECRET = 'JA3cwsqSGHFtjMwd5XoF5L5'; | ||||
|  | ||||
| 			let driver = new FileApiDriverOneDrive(CLIENT_ID, CLIENT_SECRET); | ||||
| 			let auth = Setting.value('sync.onedrive.auth'); | ||||
| 			 | ||||
| 			if (auth) { | ||||
| 				auth = JSON.parse(auth); | ||||
| 			} else { | ||||
| 				auth = await driver.api().oauthDance(); | ||||
| 				Setting.setValue('sync.onedrive.auth', JSON.stringify(auth)); | ||||
| 			} | ||||
|  | ||||
| 			driver.api().setAuth(auth); | ||||
|  | ||||
| 			let appDir = await driver.api().appDirectory(); | ||||
| 			fileApi = new FileApi(appDir, driver); | ||||
| 		} else { | ||||
| 			throw new Error('Unknown backend: ' . remoteBackend); | ||||
| 		} | ||||
|  | ||||
| 		synchronizer_ = new Synchronizer(db, fileApi); | ||||
|  | ||||
| 		return synchronizer_; | ||||
| 	} | ||||
|  | ||||
| 	let s = await synchronizer(); | ||||
| 	return; | ||||
|  | ||||
| 	function switchCurrentFolder(folder) { | ||||
| 		currentFolder = folder; | ||||
| 		updatePrompt(); | ||||
| 	} | ||||
|  | ||||
| 	function promptString() { | ||||
| 		let path = '~'; | ||||
| 		if (currentFolder) { | ||||
| 			path += '/' + currentFolder.title; | ||||
| 		} | ||||
| 		return 'joplin:' + path + '$ '; | ||||
| 	} | ||||
|  | ||||
| 	function updatePrompt() { | ||||
| 		vorpal.delimiter(promptString()); | ||||
| 	} | ||||
|  | ||||
| 	// For now, to go around this issue: https://github.com/dthree/vorpal/issues/114 | ||||
| 	function quotePromptArg(s) { | ||||
| 		if (s.indexOf(' ') >= 0) { | ||||
| 			return '"' + s + '"'; | ||||
| 		} | ||||
| 		return s; | ||||
| 	} | ||||
|  | ||||
| 	function autocompleteFolders() { | ||||
| 		return Folder.all().then((folders) => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < folders.length; i++) { | ||||
| 				output.push(quotePromptArg(folders[i].title)); | ||||
| 			} | ||||
| 			output.push('..'); | ||||
| 			output.push('.'); | ||||
| 			return output; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	function autocompleteItems() { | ||||
| 		let promise = null; | ||||
| 		if (!currentFolder) { | ||||
| 			promise = Folder.all(); | ||||
| 		} else { | ||||
| 			promise = Note.previews(currentFolder.id); | ||||
| 		} | ||||
|  | ||||
| 		return promise.then((items) => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < items.length; i++) { | ||||
| 				output.push(quotePromptArg(items[i].title)); | ||||
| 			} | ||||
| 			return output;			 | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	process.stdin.on('keypress', (_, key) => { | ||||
| 		if (key && key.name === 'return') { | ||||
| 			updatePrompt(); | ||||
| 		} | ||||
|  | ||||
| 		if (key.name === 'tab') { | ||||
| 			vorpal.ui.imprint(); | ||||
| 			vorpal.log(vorpal.ui.input()); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'cd <list-title>', | ||||
| 		description: 'Moved to [list-title] - all further operations will happen within this list. Use `cd ..` to go back one level.', | ||||
| 		action: function (args, end) { | ||||
| 			let folderTitle = args['list-title']; | ||||
|  | ||||
| 			if (folderTitle == '..') { | ||||
| 				switchCurrentFolder(null); | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			if (folderTitle == '.') { | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			Folder.loadByField('title', folderTitle).then((folder) => { | ||||
| 				switchCurrentFolder(folder); | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteFolders, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'mklist <list-title>', | ||||
| 		alias: 'mkdir', | ||||
| 		description: 'Creates a new list', | ||||
| 		action: function (args, end) { | ||||
| 			Folder.save({ title: args['list-title'] }).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then((folder) => { | ||||
| 				switchCurrentFolder(folder); | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'mknote <note-title>', | ||||
| 		alias: 'touch', | ||||
| 		description: 'Creates a new note', | ||||
| 		action: function (args, end) { | ||||
| 			if (!currentFolder) { | ||||
| 				this.log('Notes can only be created within a list.'); | ||||
| 				end(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			let note = { | ||||
| 				title: args['note-title'], | ||||
| 				parent_id: currentFolder.id, | ||||
| 			}; | ||||
| 			Note.save(note).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then((note) => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'set <item-title> <prop-name> [prop-value]', | ||||
| 		description: 'Sets the given <prop-name> of the given item.', | ||||
| 		action: function (args, end) { | ||||
| 			let promise = null; | ||||
| 			let title = args['item-title']; | ||||
| 			let propName = args['prop-name']; | ||||
| 			let propValue = args['prop-value']; | ||||
| 			if (!propValue) propValue = ''; | ||||
|  | ||||
| 			if (!currentFolder) { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				let newItem = { | ||||
| 					id: item.id, | ||||
| 					type_: item.type_, | ||||
| 				}; | ||||
| 				newItem[propName] = propValue; | ||||
| 				let ItemClass = BaseItem.itemClass(); | ||||
| 				return ItemClass.save(newItem); | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'cat <item-title>', | ||||
| 		description: 'Displays the given item data.', | ||||
| 		action: function (args, end) { | ||||
| 			let title = args['item-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
| 			if (!currentFolder) { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				if (!currentFolder) { | ||||
| 					this.log(Folder.serialize(item)); | ||||
| 				} else { | ||||
| 					this.log(Note.serialize(item)); | ||||
| 				} | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'rm <item-title>', | ||||
| 		description: 'Deletes the given item. For a list, all the notes within that list will be deleted.', | ||||
| 		action: function (args, end) { | ||||
| 			let title = args['item-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
| 			let itemType = currentFolder ? 'note' : 'folder'; | ||||
| 			if (itemType == 'folder') { | ||||
| 				promise = Folder.loadByField('title', title); | ||||
| 			} else { | ||||
| 				promise = Folder.loadNoteByField(currentFolder.id, 'title', title); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((item) => { | ||||
| 				if (!item) { | ||||
| 					this.log(_('No item with title "%s" found.', title)); | ||||
| 					end(); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				if (itemType == 'folder') { | ||||
| 					return Folder.delete(item.id); | ||||
| 				} else { | ||||
| 					return Note.delete(item.id); | ||||
| 				} | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteItems, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'ls [list-title]', | ||||
| 		alias: 'll', | ||||
| 		description: 'Lists items in [list-title].', | ||||
| 		action: function (args, end) { | ||||
| 			let folderTitle = args['list-title']; | ||||
|  | ||||
| 			let promise = null; | ||||
|  | ||||
| 			if (folderTitle == '..') { | ||||
| 				promise = Promise.resolve('root'); | ||||
| 			} else if (folderTitle && folderTitle != '.') { | ||||
| 				promise = Folder.loadByField('title', folderTitle); | ||||
| 			} else if (currentFolder) { | ||||
| 				promise = Promise.resolve(currentFolder); | ||||
| 			} else { | ||||
| 				promise = Promise.resolve('root'); | ||||
| 			} | ||||
|  | ||||
| 			promise.then((folder) => { | ||||
| 				let p = null | ||||
| 				let postfix = ''; | ||||
| 				if (folder === 'root') { | ||||
| 					p = Folder.all(); | ||||
| 					postfix = '/'; | ||||
| 				} else if (!folder) { | ||||
| 					throw new Error(_('Unknown list: "%s"', folderTitle)); | ||||
| 				} else { | ||||
| 					p = Note.previews(folder.id); | ||||
| 				} | ||||
|  | ||||
| 				return p.then((previews) => { | ||||
| 					for (let i = 0; i < previews.length; i++) { | ||||
| 						this.log(previews[i].title + postfix); | ||||
| 					} | ||||
| 				}); | ||||
| 			}).catch((error) => { | ||||
| 				this.log(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 		autocomplete: autocompleteFolders, | ||||
| 	}); | ||||
|  | ||||
| 	commands.push({ | ||||
| 		usage: 'sync', | ||||
| 		description: 'Synchronizes with remote storage.', | ||||
| 		action: function (args, end) { | ||||
| 			synchronizer('onedrive').then((s) => { | ||||
| 				return s.start(); | ||||
| 			}).catch((error) => { | ||||
| 				console.error(error); | ||||
| 			}).then(() => { | ||||
| 				end(); | ||||
| 			}); | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	for (let i = 0; i < commands.length; i++) { | ||||
| 		let c = commands[i]; | ||||
| 		let o = vorpal.command(c.usage, c.description); | ||||
| 		if (c.alias) { | ||||
| 			o.alias(c.alias); | ||||
| 		} | ||||
| 		if (c.autocomplete) { | ||||
| 			o.autocomplete({ | ||||
| 				data: c.autocomplete, | ||||
| 			}); | ||||
| 		} | ||||
| 		o.action(c.action); | ||||
| 	} | ||||
|  | ||||
| 	vorpal.delimiter(promptString()).show(); | ||||
| } | ||||
|  | ||||
| main().catch((error) => { | ||||
| 	console.error('Fatal error: ', error); | ||||
| }); | ||||
| @@ -1,5 +1,6 @@ | ||||
| require('source-map-support').install(); | ||||
| require('babel-plugin-transform-runtime'); | ||||
|  | ||||
| import { OneDriveApi } from 'src/onedrive-api.js'; | ||||
|  | ||||
| const fetch = require('node-fetch'); | ||||
|   | ||||
| @@ -50,10 +50,19 @@ async function main() { | ||||
|  | ||||
|  | ||||
|  | ||||
| 	let driver = new FileApiDriverOneDrive(config.oneDriveToken); | ||||
| 	let driver = new FileApiDriverOneDrive('e09fc0de-c958-424f-83a2-e56a721d331b', 'JA3cwsqSGHFtjMwd5XoF5L5'); | ||||
| 	driver.api().setToken(config.oneDriveToken); | ||||
|  | ||||
| 	//config.oneDriveToken); | ||||
| 	let api = new FileApi('/joplin', driver); | ||||
|  | ||||
| 	await api.delete('eraseme.txt'); | ||||
| 	let appDir = await driver.api().execJson('GET', '/drive/special/approot'); | ||||
|  | ||||
| 	console.info(appDir); | ||||
|  | ||||
| 	// /drive/special/approot | ||||
|  | ||||
| 	// await api.delete('eraseme.txt'); | ||||
|  | ||||
| 	// let result = await api.list(); | ||||
| 	// console.info(result); | ||||
| @@ -63,8 +72,8 @@ async function main() { | ||||
|  | ||||
|  | ||||
|  | ||||
| 	let content = await api.get('aaaaaaaaaaaaaaaaa.txt'); | ||||
| 	console.info(content); | ||||
| 	// let content = await api.get('aaaaaaaaaaaaaaaaa.txt'); | ||||
| 	// console.info(content); | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
|     "query-string": "4.3.4", | ||||
|     "react": "16.0.0-alpha.6", | ||||
|     "sax": "^1.2.2", | ||||
|     "server-destroy": "^1.0.1", | ||||
|     "source-map-support": "^0.4.15", | ||||
|     "sprintf-js": "^1.1.1", | ||||
|     "sqlite3": "^3.1.8", | ||||
|   | ||||
| @@ -4,6 +4,6 @@ CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | ||||
| rm -f "$CLIENT_DIR/app/src" | ||||
| ln -s "$CLIENT_DIR/../ReactNativeClient/src" "$CLIENT_DIR/app" | ||||
|  | ||||
| npm run build && NODE_PATH="$CLIENT_DIR/build/" node build/cmd.js | ||||
| npm run build && NODE_PATH="$CLIENT_DIR/build/" node build/main.js | ||||
| #npm run build && NODE_PATH="$CLIENT_DIR/build/" node build/test-onedrive.js | ||||
| #npm run build && NODE_PATH="$CLIENT_DIR/build/" node build/onedrive-server.js | ||||
| @@ -4,9 +4,12 @@ import { OneDriveApi } from 'src/onedrive-api.js'; | ||||
|  | ||||
| class FileApiDriverOneDrive { | ||||
|  | ||||
| 	constructor(token) { | ||||
| 		this.api_ = new OneDriveApi('e09fc0de-c958-424f-83a2-e56a721d331b'); | ||||
| 		this.api_.setToken(token); | ||||
| 	constructor(clientId, clientSecret) { | ||||
| 		this.api_ = new OneDriveApi(clientId, clientSecret); | ||||
| 	} | ||||
|  | ||||
| 	api() { | ||||
| 		return this.api_; | ||||
| 	} | ||||
|  | ||||
| 	listReturnsFullPath() { | ||||
| @@ -20,7 +23,7 @@ class FileApiDriverOneDrive { | ||||
| 	} | ||||
|  | ||||
| 	makePath_(path) { | ||||
| 		return '/drive/root:' + path; | ||||
| 		return path; | ||||
| 	} | ||||
|  | ||||
| 	makeItems_(odItems) { | ||||
| @@ -41,7 +44,12 @@ class FileApiDriverOneDrive { | ||||
| 	} | ||||
|  | ||||
| 	async stat(path) { | ||||
| 		let item = await this.api_.execJson('GET', this.makePath_(path), this.itemFilter_()); | ||||
| 		try { | ||||
| 			let item = await this.api_.execJson('GET', this.makePath_(path), this.itemFilter_()); | ||||
| 		} catch (error) { | ||||
| 			if (error.error.code == 'itemNotFound') return null; | ||||
| 			throw error; | ||||
| 		} | ||||
| 		return this.makeItem_(item); | ||||
| 	} | ||||
|  | ||||
| @@ -59,8 +67,14 @@ class FileApiDriverOneDrive { | ||||
| 		return this.makeItems_(items.value); | ||||
| 	} | ||||
|  | ||||
| 	get(path) { | ||||
| 		return this.api_.execText('GET', this.makePath_(path) + ':/content'); | ||||
| 	async get(path) { | ||||
| 		try { | ||||
| 			let content = await this.api_.execText('GET', this.makePath_(path) + ':/content'); | ||||
| 		} catch (error) { | ||||
| 			if (error.error.code == 'itemNotFound') return null; | ||||
| 			throw error; | ||||
| 		} | ||||
| 		return content; | ||||
| 	} | ||||
|  | ||||
| 	mkdir(path) { | ||||
|   | ||||
| @@ -7,6 +7,10 @@ class FileApi { | ||||
| 		this.driver_ = driver; | ||||
| 	} | ||||
|  | ||||
| 	dlog(s) { | ||||
| 		console.info('FileApi: ' + s); | ||||
| 	} | ||||
|  | ||||
| 	fullPath_(path) { | ||||
| 		let output = this.baseDir_; | ||||
| 		if (path != '') output += '/' + path; | ||||
| @@ -31,32 +35,35 @@ class FileApi { | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	listDirectories() { | ||||
| 		return this.driver_.list(this.fullPath_('')).then((items) => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < items.length; i++) { | ||||
| 				if (items[i].isDir) output.push(this.scopeItemToBaseDir_(items[i])); | ||||
| 			} | ||||
| 			return output; | ||||
| 		}); | ||||
| 	} | ||||
| 	// listDirectories() { | ||||
| 	// 	return this.driver_.list(this.fullPath_('')).then((items) => { | ||||
| 	// 		let output = []; | ||||
| 	// 		for (let i = 0; i < items.length; i++) { | ||||
| 	// 			if (items[i].isDir) output.push(this.scopeItemToBaseDir_(items[i])); | ||||
| 	// 		} | ||||
| 	// 		return output; | ||||
| 	// 	}); | ||||
| 	// } | ||||
|  | ||||
| 	list() { | ||||
| 		this.dlog('list'); | ||||
| 		return this.driver_.list(this.baseDir_).then((items) => { | ||||
| 			return this.scopeItemsToBaseDir_(items); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	setTimestamp(path, timestamp) { | ||||
| 		this.dlog('setTimestamp ' + path); | ||||
| 		return this.driver_.setTimestamp(this.fullPath_(path), timestamp); | ||||
| 	} | ||||
|  | ||||
| 	mkdir(path) { | ||||
| 		console.info('mkdir ' + path); | ||||
| 		return this.driver_.mkdir(this.fullPath_(path)); | ||||
| 	} | ||||
| 	// mkdir(path) { | ||||
| 	// 	this.dlog('delete ' + path); | ||||
| 	// 	return this.driver_.mkdir(this.fullPath_(path)); | ||||
| 	// } | ||||
|  | ||||
| 	stat(path) { | ||||
| 		this.dlog('stat ' + path); | ||||
| 		return this.driver_.stat(this.fullPath_(path)).then((output) => { | ||||
| 			if (!output) return output; | ||||
| 			output.path = path; | ||||
| @@ -65,20 +72,24 @@ class FileApi { | ||||
| 	} | ||||
|  | ||||
| 	get(path) { | ||||
| 		this.dlog('get ' + path); | ||||
| 		return this.driver_.get(this.fullPath_(path)); | ||||
| 	} | ||||
|  | ||||
| 	put(path, content) { | ||||
| 		this.dlog('put ' + path); | ||||
| 		return this.driver_.put(this.fullPath_(path), content); | ||||
| 	} | ||||
|  | ||||
| 	delete(path) { | ||||
| 		this.dlog('delete ' + path); | ||||
| 		return this.driver_.delete(this.fullPath_(path)); | ||||
| 	} | ||||
|  | ||||
| 	move(oldPath, newPath) { | ||||
| 		return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath)); | ||||
| 	} | ||||
| 	// move(oldPath, newPath) { | ||||
| 	// 	this.dlog('move ' + path); | ||||
| 	// 	return this.driver_.move(this.fullPath_(oldPath), this.fullPath_(newPath)); | ||||
| 	// } | ||||
|  | ||||
| 	format() { | ||||
| 		return this.driver_.format(); | ||||
|   | ||||
| @@ -133,6 +133,7 @@ Setting.defaults_ = { | ||||
| 	'sync.lastRevId': { value: 0, type: 'int' }, // DEPRECATED | ||||
| 	'sync.lastUpdateTime': { value: 0, type: 'int' }, | ||||
| 	'sync.conflictFolderId': { value: '', type: 'string' }, | ||||
| 	'sync.onedrive.auth': { value: '', type: 'string' },	 | ||||
| }; | ||||
|  | ||||
| export { Setting }; | ||||
| @@ -1,4 +1,9 @@ | ||||
| const fetch = require('node-fetch'); | ||||
| const tcpPortUsed = require('tcp-port-used'); | ||||
| const http = require("http"); | ||||
| const urlParser = require("url"); | ||||
| const FormData = require('form-data'); | ||||
| const enableServerDestroy = require('server-destroy'); | ||||
| import { stringify } from 'query-string'; | ||||
|  | ||||
| class OneDriveApi { | ||||
| @@ -6,10 +11,19 @@ class OneDriveApi { | ||||
| 	constructor(clientId, clientSecret) { | ||||
| 		this.clientId_ = clientId; | ||||
| 		this.clientSecret_ = clientSecret; | ||||
| 		this.auth_ = null; | ||||
| 	} | ||||
|  | ||||
| 	setToken(token) { | ||||
| 		this.token_ = token; | ||||
| 	tokenBaseUrl() { | ||||
| 		return 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; | ||||
| 	} | ||||
|  | ||||
| 	setAuth(auth) { | ||||
| 		this.auth_ = auth; | ||||
| 	} | ||||
|  | ||||
| 	token() { | ||||
| 		return this.auth_ ? this.auth_.access_token : null; | ||||
| 	} | ||||
|  | ||||
| 	clientId() { | ||||
| @@ -20,10 +34,15 @@ class OneDriveApi { | ||||
| 		return this.clientSecret_; | ||||
| 	} | ||||
|  | ||||
| 	possibleOAuthFlowPorts() { | ||||
| 	possibleOAuthDancePorts() { | ||||
| 		return [1917, 9917, 8917]; | ||||
| 	} | ||||
|  | ||||
| 	async appDirectory() { | ||||
| 		let r = await this.execJson('GET', '/drive/special/approot'); | ||||
| 		return r.parentReference.path + '/' + r.name; | ||||
| 	} | ||||
|  | ||||
| 	authCodeUrl(redirectUri) { | ||||
| 		let query = { | ||||
| 			client_id: this.clientId_, | ||||
| @@ -40,10 +59,6 @@ class OneDriveApi { | ||||
| 		if (!options) options = {}; | ||||
| 		if (!options.headers) options.headers = {}; | ||||
|  | ||||
| 		if (this.token_) { | ||||
| 			options.headers['Authorization'] = 'bearer ' + this.token_; | ||||
| 		} | ||||
|  | ||||
| 		if (method != 'GET') { | ||||
| 			options.method = method; | ||||
| 		} | ||||
| @@ -62,13 +77,23 @@ class OneDriveApi { | ||||
| 		console.info(method + ' ' + url); | ||||
| 		console.info(data); | ||||
|  | ||||
| 		let response = await fetch(url, options); | ||||
| 		if (!response.ok) { | ||||
| 			let error = await response.json(); | ||||
| 			throw error; | ||||
| 		} | ||||
| 		while (true) { | ||||
| 			options.headers['Authorization'] = 'bearer ' + this.token(); | ||||
|  | ||||
| 		return response; | ||||
| 			let response = await fetch(url, options); | ||||
| 			if (!response.ok) { | ||||
| 				let error = await response.json(); | ||||
|  | ||||
| 				if (error && error.error && error.error.code == 'InvalidAuthenticationToken') { | ||||
| 					await this.refreshAccessToken(); | ||||
| 					continue; | ||||
| 				} else { | ||||
| 					throw error; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return response; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async execJson(method, path, query, data) { | ||||
| @@ -83,6 +108,114 @@ class OneDriveApi { | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	async refreshAccessToken() { | ||||
| 		if (!this.auth_) throw new Error('Cannot refresh token: authentication data is missing'); | ||||
|  | ||||
| 		let body = new FormData(); | ||||
| 		body.append('client_id', this.clientId()); | ||||
| 		body.append('client_secret', this.clientSecret()); | ||||
| 		body.append('refresh_token', this.auth_.refresh_token); | ||||
| 		body.append('redirect_uri', 'http://localhost:1917'); | ||||
| 		body.append('grant_type', 'refresh_token'); | ||||
|  | ||||
| 		let options = { | ||||
| 			method: 'POST', | ||||
| 			body: body, | ||||
| 		}; | ||||
|  | ||||
| 		this.auth_ = null; | ||||
|  | ||||
| 		let response = await fetch(this.tokenBaseUrl(), options); | ||||
| 		if (!response.ok) { | ||||
| 			let msg = await response.text(); | ||||
| 			throw new Error(msg); | ||||
| 		} | ||||
|  | ||||
| 		this.auth_ = await response.json(); | ||||
|  | ||||
| 		// POST https://login.microsoftonline.com/common/oauth2/v2.0/token | ||||
| 		// Content-Type: application/x-www-form-urlencoded | ||||
|  | ||||
| 		// client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret} | ||||
| 		// &refresh_token={refresh_token}&grant_type=refresh_token | ||||
| 	} | ||||
|  | ||||
| 	async oauthDance() { | ||||
| 		this.auth_ = null; | ||||
|  | ||||
| 		let ports = this.possibleOAuthDancePorts(); | ||||
| 		let port = null; | ||||
| 		for (let i = 0; i < ports.length; i++) { | ||||
| 			let inUse = await tcpPortUsed.check(ports[i]); | ||||
| 			if (!inUse) { | ||||
| 				port = ports[i]; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (!port) throw new Error('All potential ports are in use - please report the issue at https://github.com/laurent22/joplin'); | ||||
|  | ||||
| 		let authCodeUrl = this.authCodeUrl('http://localhost:' + port); | ||||
|  | ||||
| 		return new Promise((resolve, reject) => {			 | ||||
| 			let server = http.createServer(); | ||||
| 			let errorMessage = null; | ||||
|  | ||||
| 			server.on('request', (request, response) => { | ||||
| 				const query = urlParser.parse(request.url, true).query; | ||||
|  | ||||
| 				function writeResponse(code, message) { | ||||
| 					response.writeHead(code, {"Content-Type": "text/html"}); | ||||
| 					response.write(message); | ||||
| 					response.end(); | ||||
| 				} | ||||
|  | ||||
| 				if (!query.code) return writeResponse(400, '"code" query parameter is missing'); | ||||
|  | ||||
| 				let body = new FormData(); | ||||
| 				body.append('client_id', this.clientId()); | ||||
| 				body.append('client_secret', this.clientSecret()); | ||||
| 				body.append('code', query.code ? query.code : ''); | ||||
| 				body.append('redirect_uri', 'http://localhost:' + port.toString()); | ||||
| 				body.append('grant_type', 'authorization_code'); | ||||
|  | ||||
| 				let options = { | ||||
| 					method: 'POST', | ||||
| 					body: body, | ||||
| 				}; | ||||
|  | ||||
| 				fetch(this.tokenBaseUrl(), options).then((r) => { | ||||
| 					if (!r.ok) { | ||||
| 						errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText; | ||||
| 						writeResponse(400, errorMessage); | ||||
| 						server.destroy(); | ||||
| 						return; | ||||
| 					} | ||||
|  | ||||
| 					return r.json().then((json) => { | ||||
| 						this.auth_ = json; | ||||
| 						writeResponse(200, 'The application has been authorised - you may now close this browser tab.'); | ||||
| 						server.destroy(); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}); | ||||
|  | ||||
| 			server.on('close', () => { | ||||
| 				if (errorMessage) { | ||||
| 					reject(new Error(errorMessage)); | ||||
| 				} else { | ||||
| 					resolve(this.auth_); | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			server.listen(port); | ||||
|  | ||||
| 			enableServerDestroy(server); | ||||
|  | ||||
| 			console.info('Please open this URL in your browser to authentify the application: ' + authCodeUrl); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| export { OneDriveApi }; | ||||
		Reference in New Issue
	
	Block a user