You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Removed old files
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -30,4 +30,5 @@ sparse_test.php | ||||
| INFO.md | ||||
| /web/env.php | ||||
| sync_staging.sh | ||||
| *.swp | ||||
| *.swp | ||||
| _vieux/ | ||||
| @@ -1,42 +0,0 @@ | ||||
| import QtQuick 2.7 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Layouts 1.1 | ||||
|  | ||||
| Item { | ||||
|  | ||||
| 	id: root | ||||
| 	width: 120 | ||||
| 	height: 100 | ||||
| 	signal addNoteButtonClicked | ||||
| 	signal addFolderButtonClicked | ||||
|  | ||||
| 	ColumnLayout { | ||||
|  | ||||
| 		anchors.fill: parent | ||||
| 		spacing: 2 | ||||
|  | ||||
| 		Button { | ||||
| 			id: addNoteButton | ||||
| 			text: "Add note" | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.fillHeight: true | ||||
| 			onClicked: root.addNoteButtonClicked() | ||||
| 		} | ||||
|  | ||||
| 		Button { | ||||
| 			id: addFolderButton | ||||
| 			text: "Add folder" | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.fillHeight: true | ||||
| 			onClicked: root.addFolderButtonClicked() | ||||
| 		} | ||||
|  | ||||
| 		Button { | ||||
| 			text: "ADD" | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.fillHeight: true | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,80 +0,0 @@ | ||||
| <?xml version="1.0"?> | ||||
| <manifest package="net.cozic.joplin" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto"> | ||||
|     <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Joplin"> | ||||
|         <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="Joplin" android:screenOrientation="unspecified" android:launchMode="singleTop"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN"/> | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|             </intent-filter> | ||||
|  | ||||
|             <!-- Application arguments --> | ||||
|             <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ --> | ||||
|             <!-- Application arguments --> | ||||
|  | ||||
|             <meta-data android:name="android.app.lib_name" android:value="Joplin"/> | ||||
|             <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> | ||||
|             <meta-data android:name="android.app.repository" android:value="default"/> | ||||
|             <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> | ||||
|             <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> | ||||
|             <!-- Deploy Qt libs as part of package --> | ||||
|             <meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/> | ||||
|             <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/> | ||||
|             <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/> | ||||
|             <!-- Run with local libs --> | ||||
|             <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/> | ||||
|             <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> | ||||
|             <meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/> | ||||
|             <meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroid-bundled.jar:jar/QtAndroidBearer.jar:jar/QtAndroidBearer-bundled.jar"/> | ||||
|             <meta-data android:name="android.app.static_init_classes" android:value=""/> | ||||
|             <!--  Messages maps --> | ||||
|             <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/> | ||||
|             <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/> | ||||
|             <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/> | ||||
|             <!--  Messages maps --> | ||||
|  | ||||
|             <!-- Splash screen --> | ||||
|             <!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ --> | ||||
|             <!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ --> | ||||
|             <!-- Splash screen --> | ||||
|  | ||||
|             <!-- Background running --> | ||||
|             <!-- Warning: changing this value to true may cause unexpected crashes if the | ||||
|                           application still try to draw after | ||||
|                           "applicationStateChanged(Qt::ApplicationSuspended)" | ||||
|                           signal is sent! --> | ||||
|             <meta-data android:name="android.app.background_running" android:value="false"/> | ||||
|             <!-- Background running --> | ||||
|  | ||||
|             <!-- auto screen scale factor --> | ||||
|             <meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/> | ||||
|             <!-- auto screen scale factor --> | ||||
|  | ||||
|             <!-- extract android style --> | ||||
|             <!-- available android:values : | ||||
|                 * full - useful QWidget & Quick Controls 1 apps | ||||
|                 * minimal - useful for Quick Controls 2 apps, it is much faster than "full" | ||||
|                 * none - useful for apps that don't use any of the above Qt modules | ||||
|                 --> | ||||
|             <meta-data android:name="android.app.extract_android_style" android:value="full"/> | ||||
|             <!-- extract android style --> | ||||
|     </activity> | ||||
|  | ||||
|     <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> | ||||
|  | ||||
|     </application> | ||||
|  | ||||
|     <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/> | ||||
|     <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> | ||||
|  | ||||
|     <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application. | ||||
|          Remove the comment if you do not require these default permissions. --> | ||||
|         <uses-permission android:name="android.permission.INTERNET"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> | ||||
|  | ||||
|  | ||||
|     <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application. | ||||
|          Remove the comment if you do not require these default features. --> | ||||
|         <uses-feature android:glEsVersion="0x00020000" android:required="true"/> | ||||
|  | ||||
| </manifest> | ||||
| @@ -1,39 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
|  | ||||
| Component { | ||||
| 	Item { | ||||
| 		width: parent.width | ||||
| 		height: 25 | ||||
| 		Text { | ||||
| 			id: label | ||||
| 			text: display | ||||
| 			anchors.fill: parent | ||||
| 			MouseArea { | ||||
| 				anchors.fill: parent | ||||
| 				onClicked: { | ||||
| 					listView.currentIndex = index | ||||
| 				} | ||||
| 				onDoubleClicked: { | ||||
| 					label.visible = false | ||||
| 					textField.visible = true | ||||
| 					textField.focus = true | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		TextField { | ||||
| 			id: textField | ||||
| 			text: display | ||||
| 			visible: false | ||||
| 			width: parent.width | ||||
| 			height: parent.height | ||||
| 			onAccepted: { | ||||
| 				 | ||||
| 			} | ||||
| 			onEditingFinished: { | ||||
| 				label.visible = true | ||||
| 				textField.visible = false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,84 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
|  | ||||
| Item { | ||||
| 	id: root | ||||
| 	width: parent.width | ||||
| 	height: 25 | ||||
| 	property int mouseAreaDefaultWidth | ||||
| 	property Menu contextMenu | ||||
|  | ||||
| 	signal startedEditing; | ||||
| 	signal stoppedEditing; | ||||
| 	signal editingAccepted(int index, string text); | ||||
|  | ||||
| 	function makeEditable(editable) { | ||||
| 		if (typeof editable === 'undefined') editable = true; | ||||
|  | ||||
| 		if (editable === isEditable()) return; // Nothing to do | ||||
|  | ||||
| 		if (editable) { | ||||
| 			label.visible = false | ||||
| 			mouseArea.anchors.rightMargin = 10000; // Hack because `mouseArea.visible = false` makes the MouseArea ignore the next click event | ||||
| 			textField.visible = true | ||||
| 			textField.focus = true | ||||
| 			textField.text = display | ||||
| 			root.ListView.view.focus = true; | ||||
| 			textField.selectAll() | ||||
| 			root.startedEditing(); | ||||
| 		} else { | ||||
| 			mouseArea.anchors.rightMargin = 0; | ||||
| 			label.visible = true | ||||
| 			textField.visible = false | ||||
| 			root.stoppedEditing(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function startEditing() { | ||||
| 		makeEditable(true); | ||||
| 	} | ||||
|  | ||||
| 	function stopEditing() { | ||||
| 		makeEditable(false); | ||||
| 	} | ||||
|  | ||||
| 	function isEditable() { | ||||
| 		return textField.visible; | ||||
| 	} | ||||
|  | ||||
| 	Text { | ||||
| 		id: label | ||||
| 		text: display | ||||
| 		anchors.fill: parent | ||||
| 		verticalAlignment: Text.AlignVCenter | ||||
| 	} | ||||
|  | ||||
| 	TextField { | ||||
| 		id: textField | ||||
| 		visible: false | ||||
| 		width: parent.width | ||||
| 		height: parent.height | ||||
| 		onAccepted: { | ||||
| 			root.editingAccepted(index, text); | ||||
| 		} | ||||
| 		onEditingFinished: { | ||||
| 			stopEditing(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	MouseArea { | ||||
| 		id: mouseArea | ||||
| 		anchors.fill: parent | ||||
| 		acceptedButtons: Qt.LeftButton | Qt.RightButton | ||||
| 		onClicked: { | ||||
| 			root.ListView.view.currentIndex = index | ||||
| 			if (mouse.button === Qt.RightButton) { | ||||
| 				contextMenu.open(); | ||||
| 			} | ||||
| 		} | ||||
| 		onDoubleClicked: { | ||||
| 			startEditing(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
|  | ||||
| Item { | ||||
| 	id: folderDelegateRoot | ||||
| 	width: 100//parent.width | ||||
| 	height: 25 | ||||
| 	Text { | ||||
| 		id: label | ||||
| 		text: display | ||||
| 		anchors.fill: parent | ||||
| 		MouseArea { | ||||
| 			anchors.fill: parent | ||||
| 			onClicked: { | ||||
| 				listView.currentIndex = index | ||||
| 			} | ||||
| 			onDoubleClicked: { | ||||
| 				label.visible = false | ||||
| 				textField.visible = true | ||||
| 				textField.focus = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	TextField { | ||||
| 		id: textField | ||||
| 		text: display | ||||
| 		visible: false | ||||
| 		width: parent.width | ||||
| 		height: parent.height | ||||
| 		onAccepted: { | ||||
|  | ||||
| 		} | ||||
| 		onEditingFinished: { | ||||
| 			label.visible = true | ||||
| 			textField.visible = false | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,104 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
|  | ||||
| Item { | ||||
| 	id: root | ||||
| 	property alias model: listView.model | ||||
| 	property alias currentIndex: listView.currentIndex | ||||
| 	property alias currentItem: listView.currentItem | ||||
| 	property string currentItemId | ||||
|  | ||||
| 	signal startedEditing; | ||||
| 	signal stoppedEditing; | ||||
| 	signal editingAccepted(int index, string text); | ||||
| 	signal deleteButtonClicked(int index); | ||||
|  | ||||
| 	// While an item is being edited, this property hold the item ID. | ||||
| 	// It is then used, once the model is updated, to restore the selection. | ||||
| 	property string editedItemId; | ||||
|  | ||||
| 	function startEditing(index) { | ||||
| 		root.editedItemId = listView.model.indexToId(index); | ||||
| 		currentIndex = model.rowCount() - 1; | ||||
| 		currentItem.startEditing(); | ||||
| 		print("Start editing", root.editedItemId); | ||||
| 	} | ||||
|  | ||||
| 	function stopEditing() { | ||||
| 		currentItem.stopEditing(); | ||||
| 		print("Stop editing", root.editedItemId); | ||||
| 		//print(root.editedItemId, listView.model.idToIndex(root.editedItemId)); | ||||
| 		//currentIndex = listView.model.idToIndex(root.editedItemId); | ||||
| 	} | ||||
|  | ||||
| 	function selectItemById(id) { | ||||
| 		print("selectItemBy()", id); | ||||
| 		currentItemId = id | ||||
| 		var newIndex = listView.model.idToIndex(currentItemId); | ||||
| 		print("newIndex", newIndex); | ||||
| 		currentIndex = newIndex | ||||
| 		if (newIndex < 0) currentItemId = ""; | ||||
| 		print("currentItemId", currentItemId); | ||||
| 	} | ||||
|  | ||||
| 	Rectangle { | ||||
| 		color: "#eeeeff" | ||||
| 		border.color: "#ff0000" | ||||
| 		anchors.fill: parent | ||||
| 	} | ||||
|  | ||||
| 	ListView { | ||||
|  | ||||
| 		Connections { | ||||
| 			target: model | ||||
| 			onDataChanged: { | ||||
| 				print("Connection.onDataChanged", root.editedItemId); | ||||
| 				if (root.editedItemId !== "") { | ||||
| 					selectItemById(root.editedItemId); | ||||
| 					root.editedItemId = ""; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| //		onCurrentItemChanged: { | ||||
| //			print("onCurrentItemChanged avant", currentItemId); | ||||
| //			currentItemId = model.indexToId(currentIndex); | ||||
| //			print("onCurrentItemChanged apres", currentItemId); | ||||
| //		} | ||||
|  | ||||
| 		id: listView | ||||
| 		highlightMoveVelocity: -1 | ||||
| 		highlightMoveDuration: 100 | ||||
| 		anchors.fill: parent | ||||
| 		delegate: itemListDelegate | ||||
| 		ScrollBar.vertical: ScrollBar {  } | ||||
| 		highlight: Rectangle { color: "lightsteelblue"; radius: 5 } | ||||
| 		focus: true | ||||
| 	} | ||||
|  | ||||
| 	Component { | ||||
| 		id: itemListDelegate | ||||
| 		EditableListItem { | ||||
| 			contextMenu: | ||||
| 				Menu { | ||||
| 				    MenuItem { | ||||
| 						text: "Delete" | ||||
| 						onTriggered: deleteButtonClicked(currentIndex); | ||||
| 					} | ||||
| 			    } | ||||
| 			onStartedEditing: { | ||||
| 				print("onStartedEditing()"); | ||||
| 				root.editedItemId = listView.model.indexToId(index); | ||||
| 				root.startedEditing(); | ||||
| 			} | ||||
| 			onStoppedEditing: { | ||||
| 				print("onStoppedEditing()"); | ||||
| 				root.stoppedEditing(); | ||||
| 			} | ||||
| 			onEditingAccepted: function(index, text) { | ||||
| 				print("onEditingAccepted()"); | ||||
| 				root.editingAccepted(index, text); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,142 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
|  | ||||
| Item { | ||||
|  | ||||
| 	id: root | ||||
| 	signal rowsRequested(int fromRowIndex, int toRowIndex) | ||||
|  | ||||
| 	property variant items: []; | ||||
| 	property int itemCount_: 0; | ||||
| 	property int itemHeight_: 0; | ||||
| 	property bool needToRequestRows_: false; | ||||
|  | ||||
| 	function itemHeight() { | ||||
| 		if (root.itemHeight_) return root.itemHeight_; | ||||
| 		var item = itemComponent.createObject(root) | ||||
| 		item.content = { title: "dummy", id: "" }; | ||||
| 		item.updateDisplay(); | ||||
| 		item.visible = false; | ||||
| 		root.itemHeight_ = item.height; | ||||
| 		return root.itemHeight_; | ||||
| 	} | ||||
|  | ||||
| 	function itemCount() { | ||||
| 		return itemCount_; | ||||
| 	} | ||||
|  | ||||
| 	function setItem(index, itemContent) { | ||||
| 		if (index < 0 || index >= itemCount) { | ||||
| 			console.error("ItemList::setItem: index out of bounds:", index); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		var contentTitle = itemContent.title; | ||||
|  | ||||
| 		var item = itemComponent.createObject(scrollArea.contentItem) | ||||
| 		item.content = { | ||||
| 			id: itemContent.id, | ||||
| 			title: itemContent.title | ||||
| 		}; | ||||
| 		item.invalidateDisplay(); | ||||
|  | ||||
| 		items[index] = item; | ||||
|  | ||||
| 		root.invalidateDisplay(); | ||||
| 	} | ||||
|  | ||||
| 	function setItems(fromIndex, itemContents) { | ||||
| 		for (var i = 0; i < itemContents.length; i++) { | ||||
| 			setItem(fromIndex + i, itemContents[i]); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function addItem(title) { | ||||
| 		var item = itemComponent.createObject(scrollArea.contentItem) | ||||
| 		item.title = title; | ||||
| 		item.updateDisplay(); | ||||
|  | ||||
| 		items.push(item); | ||||
| 		if (!itemHeight) itemHeight = item.height; | ||||
|  | ||||
| 		root.invalidateDisplay(); | ||||
| 	} | ||||
|  | ||||
| 	function setItemCount(count) { | ||||
| 		if (count === root.itemCount_) return; | ||||
| 		root.itemCount_ = count; | ||||
| 		root.needToRequestRows_ = true; | ||||
| 		root.invalidateDisplay(); | ||||
| 	} | ||||
|  | ||||
| 	function invalidateDisplay() { | ||||
| 		root.updateDisplay(); | ||||
| 	} | ||||
|  | ||||
| 	function updateDisplay() { | ||||
| 		var itemY = 0; | ||||
| 		for (var i = 0; i < items.length; i++) { | ||||
| 			var item = items[i]; | ||||
| 			if (item) item.y = itemY; | ||||
| 			itemY += itemHeight() | ||||
| 		} | ||||
|  | ||||
| 		scrollArea.contentHeight = itemCount() * itemHeight(); | ||||
|  | ||||
| 		if (root.needToRequestRows_) { | ||||
| 			root.needToRequestRows_ = false; | ||||
| 			var indexes = itemIndexesInView(); | ||||
| 			root.rowsRequested(indexes[0], indexes[1]); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	function itemIndexesInView() { | ||||
| 		var maxVisibleItems = Math.ceil(scrollArea.height / itemHeight()); | ||||
|  | ||||
| 		var fromIndex = Math.max(0, Math.floor(scrollArea.contentY / itemHeight())); | ||||
| 		var toIndex = fromIndex + maxVisibleItems; | ||||
| 		var maxIndex = itemCount() - 1; | ||||
|  | ||||
| 		return [Math.min(fromIndex, maxIndex), Math.min(toIndex, maxIndex)]; | ||||
| 	} | ||||
|  | ||||
| 	Component { | ||||
| 		id: itemComponent | ||||
| 		Item { | ||||
| 			id: container | ||||
| 			//property alias title: label.text | ||||
| 			property variant content; | ||||
|  | ||||
| 			function invalidateDisplay() { | ||||
| 				container.updateDisplay(); | ||||
| 			} | ||||
|  | ||||
| 			function updateDisplay() { | ||||
| 				label.text = content.title; | ||||
| 				container.height = label.height | ||||
| 			} | ||||
|  | ||||
| 			Text { | ||||
| 				id: label | ||||
| 				anchors.left: parent.left | ||||
| 				anchors.right: parent.right | ||||
| 				verticalAlignment: Text.AlignVCenter | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	Flickable { | ||||
| 		id: scrollArea | ||||
| 		anchors.fill: parent | ||||
| 		contentWidth: 800 | ||||
| 		contentHeight: 5000 | ||||
|  | ||||
| //		Rectangle { | ||||
| //			id: background | ||||
| //			color: "#ffffff" | ||||
| //			border.color: "#0000ff" | ||||
| //			width: 800 | ||||
| //			height: 500 | ||||
| //		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,106 +0,0 @@ | ||||
| # To enable CLI or GUI, add either of these: | ||||
| # "JOP_FRONT_END_CLI=1" | ||||
| # "JOP_FRONT_END_GUI=1" | ||||
| # to the qmake command. So that it looks like this: | ||||
| # qmake JoplinQtClient.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug "JOP_FRONT_END_CLI=1" && /usr/bin/make qmake_all | ||||
|  | ||||
| QT += qml quick sql quickcontrols2 network | ||||
|  | ||||
| CONFIG += c++11 | ||||
|  | ||||
| defined(JOP_FRONT_END_CLI, var) { | ||||
|     message(Building CLI client) | ||||
|     DEFINES += "JOP_FRONT_END_CLI=$$JOP_FRONT_END_CLI" | ||||
| } | ||||
|  | ||||
| defined(JOP_FRONT_END_GUI, var) { | ||||
|     message(Building GUI client) | ||||
|     DEFINES += "JOP_FRONT_END_GUI=$$JOP_FRONT_END_GUI" | ||||
| } | ||||
|  | ||||
| defined(JOP_FRONT_END_CLI, var) { | ||||
|     QT -= gui | ||||
|     CONFIG += console | ||||
|     CONFIG -= app_bundle | ||||
| } | ||||
|  | ||||
| SOURCES += \ | ||||
|     main.cpp \ | ||||
|     models/item.cpp \ | ||||
|     models/folder.cpp \ | ||||
|     database.cpp \ | ||||
|     models/foldermodel.cpp \ | ||||
|     models/notemodel.cpp \ | ||||
|     models/note.cpp \ | ||||
|     webapi.cpp \ | ||||
|     synchronizer.cpp \ | ||||
|     settings.cpp \ | ||||
|     uuid.cpp \ | ||||
|     dispatcher.cpp \ | ||||
|     models/change.cpp \ | ||||
|     models/basemodel.cpp \ | ||||
|     models/setting.cpp \ | ||||
|     paths.cpp \ | ||||
|     window.cpp \ | ||||
|     filters.cpp \ | ||||
|     models/abstractlistmodel.cpp \ | ||||
|     cliapplication.cpp \ | ||||
|     command.cpp \ | ||||
|     qmlutils.cpp \ | ||||
|     baseitemlistcontroller.cpp \ | ||||
|     folderlistcontroller.cpp | ||||
|  | ||||
| RESOURCES += qml.qrc \ | ||||
|     database.qrc | ||||
|  | ||||
| # Additional import path used to resolve QML modules in Qt Creator's code model | ||||
| QML_IMPORT_PATH = | ||||
|  | ||||
| # Default rules for deployment. | ||||
| qnx: target.path = /tmp/$${TARGET}/bin | ||||
| else: unix:!android: target.path = /opt/$${TARGET}/bin | ||||
| !isEmpty(target.path): INSTALLS += target | ||||
|  | ||||
| HEADERS += \ | ||||
|     stable.h \ | ||||
|     models/folder.h \ | ||||
|     models/item.h \ | ||||
|     database.h \ | ||||
|     models/foldermodel.h \ | ||||
|     models/notemodel.h \ | ||||
|     models/note.h \ | ||||
|     sparsevector.hpp \ | ||||
|     webapi.h \ | ||||
|     synchronizer.h \ | ||||
|     settings.h \ | ||||
|     simpletypes.h \ | ||||
|     uuid.h \ | ||||
|     dispatcher.h \ | ||||
|     models/change.h \ | ||||
|     models/basemodel.h \ | ||||
|     enum.h \ | ||||
|     models/setting.h \ | ||||
|     paths.h \ | ||||
|     constants.h \ | ||||
|     window.h \ | ||||
|     filters.h \ | ||||
|     models/abstractlistmodel.h \ | ||||
|     cliapplication.h \ | ||||
|     command.h \ | ||||
|     qmlutils.h \ | ||||
|     baseitemlistcontroller.h \ | ||||
|     folderlistcontroller.h | ||||
|  | ||||
| defined(JOP_FRONT_END_GUI, var) { | ||||
|     SOURCES += application.cpp | ||||
|     HEADERS += application.h | ||||
| } | ||||
|  | ||||
| DISTFILES += \ | ||||
|     AndroidManifest.xml | ||||
|  | ||||
| PRECOMPILED_HEADER = stable.h | ||||
|  | ||||
| # INCLUDEPATH += "C:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt" | ||||
|  | ||||
| # LIBS += -L"C:/Program Files (x86)/Windows Kits/10/Lib/10.0.10240.0/ucrt/x86" | ||||
| @@ -1,336 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE QtCreatorProject> | ||||
| <!-- Written by QtCreator 4.2.0, 2017-01-27T17:22:29. --> | ||||
| <qtcreator> | ||||
|  <data> | ||||
|   <variable>EnvironmentId</variable> | ||||
|   <value type="QByteArray">{13661a96-7123-4040-a8b0-364538c1219d}</value> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.ActiveTarget</variable> | ||||
|   <value type="int">0</value> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.EditorSettings</variable> | ||||
|   <valuemap type="QVariantMap"> | ||||
|    <value type="bool" key="EditorConfiguration.AutoIndent">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value> | ||||
|    <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value> | ||||
|    <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0"> | ||||
|     <value type="QString" key="language">Cpp</value> | ||||
|     <valuemap type="QVariantMap" key="value"> | ||||
|      <value type="QByteArray" key="CurrentPreferences">CppGlobal</value> | ||||
|     </valuemap> | ||||
|    </valuemap> | ||||
|    <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1"> | ||||
|     <value type="QString" key="language">QmlJS</value> | ||||
|     <valuemap type="QVariantMap" key="value"> | ||||
|      <value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value> | ||||
|     </valuemap> | ||||
|    </valuemap> | ||||
|    <value type="int" key="EditorConfiguration.CodeStyle.Count">2</value> | ||||
|    <value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value> | ||||
|    <value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value> | ||||
|    <value type="int" key="EditorConfiguration.IndentSize">4</value> | ||||
|    <value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value> | ||||
|    <value type="int" key="EditorConfiguration.MarginColumn">80</value> | ||||
|    <value type="bool" key="EditorConfiguration.MouseHiding">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.MouseNavigation">true</value> | ||||
|    <value type="int" key="EditorConfiguration.PaddingMode">1</value> | ||||
|    <value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.ShowMargin">false</value> | ||||
|    <value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value> | ||||
|    <value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.SpacesForTabs">true</value> | ||||
|    <value type="int" key="EditorConfiguration.TabKeyBehavior">0</value> | ||||
|    <value type="int" key="EditorConfiguration.TabSize">8</value> | ||||
|    <value type="bool" key="EditorConfiguration.UseGlobal">true</value> | ||||
|    <value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value> | ||||
|    <value type="bool" key="EditorConfiguration.addFinalNewLine">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.cleanIndentation">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.cleanWhitespace">true</value> | ||||
|    <value type="bool" key="EditorConfiguration.inEntireDocument">false</value> | ||||
|   </valuemap> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.PluginSettings</variable> | ||||
|   <valuemap type="QVariantMap"/> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.Target.0</variable> | ||||
|   <valuemap type="QVariantMap"> | ||||
|    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.7.1 GCC 64bit</value> | ||||
|    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.7.1 GCC 64bit</value> | ||||
|    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.57.gcc_64_kit</value> | ||||
|    <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value> | ||||
|    <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value> | ||||
|    <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0"> | ||||
|     <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Debug</value> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | ||||
|       <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments">"JOP_FRONT_END_CLI=1"</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value> | ||||
|      </valuemap> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> | ||||
|     </valuemap> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> | ||||
|     </valuemap> | ||||
|     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> | ||||
|     <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value> | ||||
|     <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Debug</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | ||||
|     <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value> | ||||
|     <value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value> | ||||
|    </valuemap> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1"> | ||||
|     <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Release</value> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value> | ||||
|       <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value> | ||||
|      </valuemap> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> | ||||
|     </valuemap> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> | ||||
|     </valuemap> | ||||
|     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> | ||||
|     <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value> | ||||
|     <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Release</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | ||||
|     <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | ||||
|     <value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value> | ||||
|    </valuemap> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2"> | ||||
|     <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Profile</value> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value> | ||||
|       <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value> | ||||
|       <value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value> | ||||
|      </valuemap> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> | ||||
|     </valuemap> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> | ||||
|      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> | ||||
|       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value> | ||||
|       <valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.AutomaticallyAddedMakeArguments"> | ||||
|        <value type="QString">-w</value> | ||||
|        <value type="QString">-r</value> | ||||
|       </valuelist> | ||||
|       <value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value> | ||||
|       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value> | ||||
|      </valuemap> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> | ||||
|     </valuemap> | ||||
|     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> | ||||
|     <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value> | ||||
|     <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Profile</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value> | ||||
|     <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value> | ||||
|     <value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value> | ||||
|    </valuemap> | ||||
|    <value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> | ||||
|     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> | ||||
|      <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> | ||||
|     </valuemap> | ||||
|     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy locally</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value> | ||||
|    </valuemap> | ||||
|    <value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/> | ||||
|    <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0"> | ||||
|     <value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value> | ||||
|     <value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value> | ||||
|     <value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value> | ||||
|     <value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value> | ||||
|     <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value> | ||||
|     <valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value> | ||||
|     <value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value> | ||||
|     <value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value> | ||||
|     <value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value> | ||||
|     <value type="int" key="Analyzer.Valgrind.NumCallers">25</value> | ||||
|     <valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/> | ||||
|     <value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value> | ||||
|     <value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value> | ||||
|     <value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value> | ||||
|     <valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds"> | ||||
|      <value type="int">0</value> | ||||
|      <value type="int">1</value> | ||||
|      <value type="int">2</value> | ||||
|      <value type="int">3</value> | ||||
|      <value type="int">4</value> | ||||
|      <value type="int">5</value> | ||||
|      <value type="int">6</value> | ||||
|      <value type="int">7</value> | ||||
|      <value type="int">8</value> | ||||
|      <value type="int">9</value> | ||||
|      <value type="int">10</value> | ||||
|      <value type="int">11</value> | ||||
|      <value type="int">12</value> | ||||
|      <value type="int">13</value> | ||||
|      <value type="int">14</value> | ||||
|     </valuelist> | ||||
|     <value type="int" key="PE.EnvironmentAspect.Base">2</value> | ||||
|     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">JoplinQtClient</value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value> | ||||
|     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/home/laurent/src/notes/QtClient/JoplinQtClient/JoplinQtClient.pro</value> | ||||
|     <value type="bool" key="QmakeProjectManager.QmakeRunConfiguration.UseLibrarySearchPath">true</value> | ||||
|     <value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.CommandLineArguments"></value> | ||||
|     <value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.ProFile">JoplinQtClient.pro</value> | ||||
|     <value type="bool" key="Qt4ProjectManager.Qt4RunConfiguration.UseDyldImageSuffix">false</value> | ||||
|     <value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory"></value> | ||||
|     <value type="QString" key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory.default"></value> | ||||
|     <value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value> | ||||
|     <value type="bool" key="RunConfiguration.UseCppDebugger">false</value> | ||||
|     <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> | ||||
|     <value type="bool" key="RunConfiguration.UseMultiProcess">false</value> | ||||
|     <value type="bool" key="RunConfiguration.UseQmlDebugger">false</value> | ||||
|     <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> | ||||
|    </valuemap> | ||||
|    <value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value> | ||||
|   </valuemap> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.TargetCount</variable> | ||||
|   <value type="int">1</value> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>ProjectExplorer.Project.Updater.FileVersion</variable> | ||||
|   <value type="int">18</value> | ||||
|  </data> | ||||
|  <data> | ||||
|   <variable>Version</variable> | ||||
|   <value type="int">18</value> | ||||
|  </data> | ||||
| </qtcreator> | ||||
| @@ -1,35 +0,0 @@ | ||||
| import QtQuick 2.4 | ||||
|  | ||||
| LoginPageForm { | ||||
|  | ||||
| 	property Item appRoot | ||||
|  | ||||
| 	id: root | ||||
|  | ||||
| 	function onShown() { | ||||
| 		root.apiBaseUrl = settings.valueString("api.baseUrl"); | ||||
| 		root.email = settings.valueString("user.email"); | ||||
| 		root.password = ""; | ||||
| 	} | ||||
|  | ||||
| 	Connections { | ||||
| 		target: root | ||||
| 		onLoginButtonClicked: { | ||||
| 			appRoot.emitLoginClicked(root.apiBaseUrl, root.email, root.password); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	Connections { | ||||
| 		target: appRoot | ||||
| 		onLoginStarted: { | ||||
| 			root.enabled = false; | ||||
| 		} | ||||
| 		onLoginFailed: { | ||||
| 			root.enabled = true; | ||||
| 		} | ||||
| 		onLoginSuccess: { | ||||
| 			root.enabled = true; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,103 +0,0 @@ | ||||
| import QtQuick 2.4 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Layouts 1.3 | ||||
|  | ||||
| Item { | ||||
| 	id: root | ||||
| 	width: 400 | ||||
| 	height: 400 | ||||
| 	signal loginButtonClicked() | ||||
| 	property alias apiBaseUrl: apiBaseUrlTF.text | ||||
| 	property alias email: emailTF.text | ||||
| 	property alias password: passwordTF.text | ||||
|  | ||||
| 	Rectangle { | ||||
| 		id: rectangle2 | ||||
| 		color: "#ffffff" | ||||
| 		anchors.fill: parent | ||||
| 	} | ||||
|  | ||||
| 	GridLayout { | ||||
| 		id: gridLayout1 | ||||
| 		flow: GridLayout.LeftToRight | ||||
| 		rows: 6 | ||||
| 		columns: 2 | ||||
| 		anchors.fill: parent | ||||
|  | ||||
| 		Label { | ||||
| 			id: label1 | ||||
| 			text: qsTr("API base URL") | ||||
| 		} | ||||
|  | ||||
| 		TextField { | ||||
| 			id: apiBaseUrlTF | ||||
| 			text: "http://joplin.local" | ||||
| 			Layout.fillWidth: true | ||||
| 		} | ||||
|  | ||||
| 		Label { | ||||
| 			id: label2 | ||||
| 			text: qsTr("Email") | ||||
| 		} | ||||
|  | ||||
| 		TextField { | ||||
| 			id: emailTF | ||||
| 			text: "laurent@cozic.net" | ||||
| 			Layout.fillWidth: true | ||||
| 		} | ||||
|  | ||||
| 		Label { | ||||
| 			id: label3 | ||||
| 			text: qsTr("Password") | ||||
| 		} | ||||
|  | ||||
| 		TextField { | ||||
| 			id: passwordTF | ||||
| 			text: "12345678" | ||||
| 			Layout.fillWidth: true | ||||
| 		} | ||||
|  | ||||
| 		Button { | ||||
| 			id: loginButton | ||||
| 			text: qsTr("Login") | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.columnSpan: 2 | ||||
| 		} | ||||
|  | ||||
| 		Rectangle { | ||||
| 			id: rectangle1 | ||||
| 			width: 200 | ||||
| 			height: 200 | ||||
| 			color: "#ffffff" | ||||
| 			Layout.columnSpan: 2 | ||||
| 			Layout.rowSpan: 1 | ||||
| 			Layout.fillHeight: true | ||||
| 			Layout.fillWidth: true | ||||
| 		} | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 	} | ||||
|  | ||||
|  Connections { | ||||
| 	 target: loginButton | ||||
| 	 onClicked: root.loginButtonClicked() | ||||
|  } | ||||
|  | ||||
|  Connections { | ||||
| 	 target: apiBaseUrlTF | ||||
| 	 onAccepted: root.loginButtonClicked() | ||||
|  } | ||||
|  | ||||
|  Connections { | ||||
| 	 target: emailTF | ||||
| 	 onAccepted: root.loginButtonClicked() | ||||
|  } | ||||
|  | ||||
|  Connections { | ||||
| 	 target: passwordTF | ||||
| 	 onAccepted: root.loginButtonClicked() | ||||
|  } | ||||
|  | ||||
| } | ||||
| @@ -1,264 +0,0 @@ | ||||
| import QtQuick 2.7 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Layouts 1.0 | ||||
|  | ||||
| Item { | ||||
|  | ||||
| 	property Item appRoot | ||||
| 	property alias itemList: itemList | ||||
|  | ||||
| //	Component { | ||||
| //		id: rectangleComponent | ||||
| //		Rectangle { width: 80; height: 50; color: "red" } | ||||
| //	} | ||||
|  | ||||
|  | ||||
| //	function createRectangle() { | ||||
| //		var rect = rectangleComponent.createObject(parent); | ||||
| //		rect.x = 200; | ||||
| //		//console.info("aAAAAAAAAAAAAAAAAAAAAAAAA"); | ||||
| //	} | ||||
|  | ||||
| 	ItemList2 { | ||||
| 		id: itemList | ||||
| 		width: 800 | ||||
| 		height: 500 | ||||
| 	} | ||||
|  | ||||
|  | ||||
|  | ||||
| //	RowLayout { | ||||
| //		id: layout | ||||
| //		anchors.fill: parent | ||||
| //		spacing: 0 | ||||
|  | ||||
| //		ItemList { | ||||
| //			id: folderList | ||||
| //			model: folderListModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 50 | ||||
| //			Layout.preferredWidth: 100 | ||||
| //			Layout.maximumWidth: 200 | ||||
| //			Layout.minimumHeight: 150 | ||||
|  | ||||
| //			onCurrentItemChanged: { | ||||
| //				appRoot.currentFolderChanged() | ||||
| //			} | ||||
|  | ||||
| //			onEditingAccepted: function(index, text) { | ||||
| //				handleItemListEditingAccepted(folderList, index, text); | ||||
| //			} | ||||
|  | ||||
| //			onStoppedEditing: { | ||||
| //				handleItemListStoppedEditing(folderList); | ||||
| //			} | ||||
|  | ||||
| //			onDeleteButtonClicked: { | ||||
| //				handleItemListAction(folderList, "delete"); | ||||
| //			} | ||||
| //		} | ||||
|  | ||||
| //		ItemList { | ||||
| //			id: noteList | ||||
| //			model: noteListModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 100 | ||||
| //			Layout.maximumWidth: 200 | ||||
| //			Layout.preferredWidth: 200 | ||||
| //			Layout.preferredHeight: 100 | ||||
|  | ||||
| //			onCurrentItemChanged: { | ||||
| //				appRoot.currentNoteChanged() | ||||
| //			} | ||||
|  | ||||
| //			onEditingAccepted: function(index, text) { | ||||
| //				handleItemListEditingAccepted(noteList, index, text); | ||||
| //			} | ||||
|  | ||||
| //			onStoppedEditing: { | ||||
| //				handleItemListStoppedEditing(noteList); | ||||
| //			} | ||||
|  | ||||
| //			onDeleteButtonClicked: { | ||||
| //				handleItemListAction(noteList, "delete"); | ||||
| //			} | ||||
| //		} | ||||
|  | ||||
| //		NoteEditor { | ||||
| //			id: noteEditor | ||||
| //			model: noteModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 100 | ||||
| //			Layout.preferredHeight: 100 | ||||
| //		} | ||||
|  | ||||
| //	} | ||||
|  | ||||
| //	AddButton { | ||||
| //		id: addButton | ||||
| //		anchors.right: parent.right | ||||
| //		anchors.bottom: parent.bottom | ||||
| //		onAddFolderButtonClicked: handleAddItem(folderList) | ||||
| //		onAddNoteButtonClicked: handleAddItem(noteList) | ||||
| //	} | ||||
|  | ||||
| //	Button { | ||||
| //		id: syncButton | ||||
| //		text: "Sync" | ||||
| //		anchors.right: parent.right | ||||
| //		anchors.top: parent.top | ||||
| //		onClicked: appRoot.syncButtonClicked() | ||||
| //	} | ||||
|  | ||||
| //	Button { | ||||
| //		id: logoutButton | ||||
| //		text: "Logout" | ||||
| //		anchors.right: syncButton.left | ||||
| //		anchors.top: parent.top | ||||
| //		onClicked: appRoot.logoutClicked() | ||||
| //	} | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| //import QtQuick 2.7 | ||||
| //import QtQuick.Controls 2.0 | ||||
| //import QtQuick.Layouts 1.0 | ||||
|  | ||||
| //Item { | ||||
|  | ||||
| //	property Item appRoot | ||||
| //	property alias currentFolderIndex: folderList.currentIndex | ||||
| //	property alias currentNoteIndex: noteList.currentIndex | ||||
|  | ||||
| //	function onShown() {} | ||||
|  | ||||
| //	function handleAddItem(list) { | ||||
| //		list.model.showVirtualItem(); | ||||
| //		list.startEditing(list.model.rowCount() - 1); | ||||
| //	} | ||||
|  | ||||
| //	function handleItemListEditingAccepted(list, index, text) { | ||||
| //		if (list.model.virtualItemShown()) { | ||||
| //			list.model.hideVirtualItem(); | ||||
| //			list.model.addData(text) | ||||
| //			print("handleItemListEditingAccepted"); | ||||
| //			list.selectItemById(list.model.lastInsertId()); | ||||
| //		} else { | ||||
| //			list.model.setData(index, text, "title") | ||||
| //		} | ||||
| //	} | ||||
|  | ||||
| //	function handleItemListStoppedEditing(list) { | ||||
| //		if (list.model.virtualItemShown()) { | ||||
| //			list.model.hideVirtualItem(); | ||||
| //		} | ||||
| //	} | ||||
|  | ||||
| //	function handleItemListAction(list, action) { | ||||
| //		if (action === "delete") { | ||||
| //			if (list.currentIndex === undefined) return; | ||||
| //			list.model.deleteData(list.currentIndex) | ||||
| //		} | ||||
| //	} | ||||
|  | ||||
| //	RowLayout { | ||||
| //		id: layout | ||||
| //		anchors.fill: parent | ||||
| //		spacing: 0 | ||||
|  | ||||
| //		ItemList { | ||||
| //			id: folderList | ||||
| //			model: folderListModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 50 | ||||
| //			Layout.preferredWidth: 100 | ||||
| //			Layout.maximumWidth: 200 | ||||
| //			Layout.minimumHeight: 150 | ||||
|  | ||||
| //			onCurrentItemChanged: { | ||||
| //				appRoot.currentFolderChanged() | ||||
| //			} | ||||
|  | ||||
| //			onEditingAccepted: function(index, text) { | ||||
| //				handleItemListEditingAccepted(folderList, index, text); | ||||
| //			} | ||||
|  | ||||
| //			onStoppedEditing: { | ||||
| //				handleItemListStoppedEditing(folderList); | ||||
| //			} | ||||
|  | ||||
| //			onDeleteButtonClicked: { | ||||
| //				handleItemListAction(folderList, "delete"); | ||||
| //			} | ||||
| //		} | ||||
|  | ||||
| //		ItemList { | ||||
| //			id: noteList | ||||
| //			model: noteListModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 100 | ||||
| //			Layout.maximumWidth: 200 | ||||
| //			Layout.preferredWidth: 200 | ||||
| //			Layout.preferredHeight: 100 | ||||
|  | ||||
| //			onCurrentItemChanged: { | ||||
| //				appRoot.currentNoteChanged() | ||||
| //			} | ||||
|  | ||||
| //			onEditingAccepted: function(index, text) { | ||||
| //				handleItemListEditingAccepted(noteList, index, text); | ||||
| //			} | ||||
|  | ||||
| //			onStoppedEditing: { | ||||
| //				handleItemListStoppedEditing(noteList); | ||||
| //			} | ||||
|  | ||||
| //			onDeleteButtonClicked: { | ||||
| //				handleItemListAction(noteList, "delete"); | ||||
| //			} | ||||
| //		} | ||||
|  | ||||
| //		NoteEditor { | ||||
| //			id: noteEditor | ||||
| //			model: noteModel | ||||
| //			Layout.fillWidth: true | ||||
| //			Layout.fillHeight: true | ||||
| //			Layout.minimumWidth: 100 | ||||
| //			Layout.preferredHeight: 100 | ||||
| //		} | ||||
|  | ||||
| //	} | ||||
|  | ||||
| //	AddButton { | ||||
| //		id: addButton | ||||
| //		anchors.right: parent.right | ||||
| //		anchors.bottom: parent.bottom | ||||
| //		onAddFolderButtonClicked: handleAddItem(folderList) | ||||
| //		onAddNoteButtonClicked: handleAddItem(noteList) | ||||
| //	} | ||||
|  | ||||
| //	Button { | ||||
| //		id: syncButton | ||||
| //		text: "Sync" | ||||
| //		anchors.right: parent.right | ||||
| //		anchors.top: parent.top | ||||
| //		onClicked: appRoot.syncButtonClicked() | ||||
| //	} | ||||
|  | ||||
| //	Button { | ||||
| //		id: logoutButton | ||||
| //		text: "Logout" | ||||
| //		anchors.right: syncButton.left | ||||
| //		anchors.top: parent.top | ||||
| //		onClicked: appRoot.logoutClicked() | ||||
| //	} | ||||
|  | ||||
| //} | ||||
| @@ -1,51 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Layouts 1.1 | ||||
|  | ||||
| Item { | ||||
|  | ||||
| 	property QtObject model | ||||
|  | ||||
| 	Connections { | ||||
| 		target: model | ||||
| 		onChanged: { | ||||
| 			if (!model) { | ||||
| 				titleField.text = "" | ||||
| 				bodyField.text = "" | ||||
| 			} else { | ||||
| 				titleField.text = model.title | ||||
| 				bodyField.text = model.body | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	Rectangle { | ||||
| 		color: "#eeeeee" | ||||
| 		border.color: "#0000ff" | ||||
| 		anchors.fill: parent | ||||
| 	} | ||||
|  | ||||
| 	ColumnLayout { | ||||
|  | ||||
| 		anchors.fill: parent | ||||
| 		spacing: 2 | ||||
|  | ||||
| 		TextField { | ||||
| 			id: titleField | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.minimumWidth: 50 | ||||
| 			Layout.preferredWidth: 100 | ||||
| 		} | ||||
|  | ||||
| 		TextArea { | ||||
| 			id: bodyField | ||||
| 			Layout.fillWidth: true | ||||
| 			Layout.fillHeight: true | ||||
| 			Layout.minimumWidth: 50 | ||||
| 			Layout.preferredWidth: 100 | ||||
| 			Layout.minimumHeight: 150 | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -1,46 +0,0 @@ | ||||
| import QtQuick 2.0 | ||||
| import QtQuick.Controls 2.0 | ||||
|  | ||||
| Item { | ||||
| 	id: root | ||||
| 	property alias model: listView.model | ||||
| 	property alias currentIndex: listView.currentIndex | ||||
| 	property alias currentItem: listView.currentItem | ||||
|  | ||||
| 	Rectangle { | ||||
| 		color: "#ffeeee" | ||||
| 		border.color: "#00ff00" | ||||
| 		anchors.fill: parent | ||||
| 	} | ||||
|  | ||||
| 	Component { | ||||
| 		id: noteDelegate | ||||
| 		Item { | ||||
| 			width: parent.width | ||||
| 			height: 25 | ||||
| 			Text { | ||||
| 				text: display | ||||
| 			} | ||||
| 			MouseArea { | ||||
| 				anchors.fill: parent | ||||
| 				onClicked: { | ||||
| 					listView.currentIndex = index | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ListView { | ||||
| 		id: listView | ||||
| 		anchors.fill: parent | ||||
| 		delegate: noteDelegate | ||||
| 		highlightMoveVelocity: -1 | ||||
| 		highlightMoveDuration: 100 | ||||
| 		ScrollBar.vertical: ScrollBar {  } | ||||
| 		highlight: Rectangle { color: "lightsteelblue"; radius: 5 } | ||||
| 		focus: true | ||||
| 		onCurrentItemChanged: { | ||||
| 			root.currentItemChanged() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| import QtQuick 2.7 | ||||
|  | ||||
| Page1Form { | ||||
|     button1.onClicked: { | ||||
|         console.log("Button Pressed. Entered text: " + textField1.text); | ||||
|     } | ||||
| } | ||||
| @@ -1,73 +0,0 @@ | ||||
| import QtQuick 2.7 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Layouts 1.0 | ||||
|  | ||||
| Item { | ||||
|     property alias textField1: textField1 | ||||
|     property alias button1: button1 | ||||
|  | ||||
|     RowLayout { | ||||
|         anchors.horizontalCenter: parent.horizontalCenter | ||||
|         anchors.topMargin: 20 | ||||
|         anchors.top: parent.top | ||||
|  | ||||
|         TextField { | ||||
|             id: textField1 | ||||
|             placeholderText: qsTr("Text Field") | ||||
|         } | ||||
|  | ||||
|         Button { | ||||
|             id: button1 | ||||
|             text: qsTr("Press Me") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ListView { | ||||
|         id: listView1 | ||||
|         x: 62 | ||||
|         y: 143 | ||||
|         width: 410 | ||||
|         height: 199 | ||||
|         model: ListModel { | ||||
|             ListElement { | ||||
|                 name: "Grey" | ||||
|                 colorCode: "grey" | ||||
|             } | ||||
|  | ||||
|             ListElement { | ||||
|                 name: "Red" | ||||
|                 colorCode: "red" | ||||
|             } | ||||
|  | ||||
|             ListElement { | ||||
|                 name: "Blue" | ||||
|                 colorCode: "blue" | ||||
|             } | ||||
|  | ||||
|             ListElement { | ||||
|                 name: "Green" | ||||
|                 colorCode: "green" | ||||
|             } | ||||
|         } | ||||
|         delegate: Item { | ||||
|             x: 5 | ||||
|             width: 80 | ||||
|             height: 40 | ||||
|             Row { | ||||
|                 id: row1 | ||||
|                 Rectangle { | ||||
|                     width: 40 | ||||
|                     height: 40 | ||||
|                     color: colorCode | ||||
|                 } | ||||
|  | ||||
|                 Text { | ||||
|                     text: name | ||||
|                     font.bold: true | ||||
|                     anchors.verticalCenter: parent.verticalCenter | ||||
|                 } | ||||
|                 spacing: 10 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| import QtQuick 2.4 | ||||
|  | ||||
| TestForm { | ||||
| } | ||||
| @@ -1,26 +0,0 @@ | ||||
| import QtQuick 2.4 | ||||
|  | ||||
| Item { | ||||
| 	id: item1 | ||||
| 	width: 400 | ||||
| 	height: 400 | ||||
|  | ||||
| 	AddButton { | ||||
| 		id: addButton1 | ||||
| 		x: 232 | ||||
| 		y: 294 | ||||
| 		width: 100 | ||||
| 		height: 50 | ||||
| 		anchors.rightMargin: 0 | ||||
| 		anchors.bottom: parent.bottom | ||||
| 		anchors.right: parent.right | ||||
| 	} | ||||
|  | ||||
|  FolderList { | ||||
| 	 id: folderList1 | ||||
| 	 width: 107 | ||||
| 	 anchors.bottom: parent.bottom | ||||
| 	 anchors.top: parent.top | ||||
| 	 anchors.left: parent.left | ||||
|  } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| import QtQuick.Controls 1.4 | ||||
|  | ||||
| TreeView { | ||||
| 	TableViewColumn { | ||||
| 		title: "Name" | ||||
| 		role: "fileName" | ||||
| 		width: 300 | ||||
| 	} | ||||
| 	TableViewColumn { | ||||
| 		title: "Permissions" | ||||
| 		role: "filePermissions" | ||||
| 		width: 100 | ||||
| 	} | ||||
| 	model: fileSystemModel | ||||
| } | ||||
| @@ -1,128 +0,0 @@ | ||||
| import QtQuick 2.7 | ||||
| import QtQuick.Controls 2.0 | ||||
| import QtQuick.Controls 1.4 | ||||
| import QtQuick.Layouts 1.0 | ||||
|  | ||||
| Item { | ||||
| 	id: root | ||||
| 	width: 800 | ||||
| 	height: 600 | ||||
|  | ||||
| 	property alias itemList : mainPage.itemList | ||||
|  | ||||
| 	function testing() { | ||||
| 		var itemList = mainPage.itemList; | ||||
| 		itemList.setItemCount(100); | ||||
|  | ||||
| 		var items = []; | ||||
| 		for (var i = 0; i < 100; i++) { | ||||
| 			items.push({ title: "Item " + i }); | ||||
| 		} | ||||
|  | ||||
| 		itemList.setItems(0, items); | ||||
| 	} | ||||
|  | ||||
| 	MainPage { | ||||
| 		id: mainPage | ||||
| 		anchors.fill: parent | ||||
| 		appRoot: root | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| //import QtQuick 2.7 | ||||
| //import QtQuick.Controls 2.0 | ||||
| //import QtQuick.Controls 1.4 | ||||
| //import QtQuick.Layouts 1.0 | ||||
|  | ||||
| //Item { | ||||
| //	id: root | ||||
| //	width: 800 | ||||
| //	height: 600 | ||||
| //	signal currentFolderChanged() | ||||
| //	signal currentNoteChanged() | ||||
| //	signal addNoteButtonClicked() | ||||
| //	signal addFolderButtonClicked() | ||||
| //	signal syncButtonClicked() | ||||
| //	signal loginButtonClicked() | ||||
| //	signal loginClicked(string apiBaseUrl, string email, string password) | ||||
| //	signal loginStarted() | ||||
| //	signal loginFailed() | ||||
| //	signal loginSuccess() | ||||
| //	signal logoutClicked() | ||||
| //	property alias currentFolderIndex: mainPage.currentFolderIndex | ||||
| //	property alias currentNoteIndex: mainPage.currentNoteIndex | ||||
|  | ||||
| //	property var pages : ({}) | ||||
|  | ||||
| //	function pageByName(pageName) { | ||||
| //		if (root.pages[pageName]) return root.pages[pageName]; | ||||
|  | ||||
| //		var page = null; | ||||
| //		if (pageName === "main") { | ||||
| //			page = mainPage | ||||
| //		} else if (pageName === "login") { | ||||
| //			var s = ' | ||||
| //                LoginPage { | ||||
| //                    id: loginPage | ||||
| //                    anchors.fill: parent | ||||
| //                    visible: false | ||||
| //                    appRoot: root | ||||
| //                }'; | ||||
| //			page = Qt.createQmlObject(s, root); | ||||
| //		} | ||||
|  | ||||
| //		root.pages[pageName] = page; | ||||
|  | ||||
| //		return page; | ||||
| //	} | ||||
|  | ||||
| //	function showPage(pageName) { | ||||
| //		for (var n in root.pages) { | ||||
| //			root.pages[n].visible = false; | ||||
| //		} | ||||
|  | ||||
| //		print("Switching to page: " + pageName); | ||||
| //		var page = pageByName(pageName); | ||||
| //		page.visible = true; | ||||
|  | ||||
| //		page.onShown(); | ||||
| //	} | ||||
|  | ||||
| //	function selectFolderbyId(id) { | ||||
| //		mainPage.folderList.selectItemById(id); | ||||
| //	} | ||||
|  | ||||
| //	function selectNoteById(id) { | ||||
| //		mainPage.noteList.selectItemById(id); | ||||
| //	} | ||||
|  | ||||
| //	function emitLoginStarted() { | ||||
| //		root.loginStarted(); | ||||
| //	} | ||||
|  | ||||
| //	function emitLoginFailed() { | ||||
| //		root.loginFailed(); | ||||
| //	} | ||||
|  | ||||
| //	function emitLoginSuccess() { | ||||
| //		root.loginSuccess(); | ||||
| //	} | ||||
|  | ||||
| //	function emitLoginClicked(apiBaseUrl, email, password) { | ||||
| //		root.loginClicked(apiBaseUrl, email, password); | ||||
| //	} | ||||
|  | ||||
| //	function emitLogoutClicked() { | ||||
| //		root.logoutClicked(); | ||||
| //	} | ||||
|  | ||||
| //	MainPage { | ||||
| //		id: mainPage | ||||
| //		anchors.fill: parent | ||||
| //		appRoot: root | ||||
| //		visible: false | ||||
| //	} | ||||
|  | ||||
| //} | ||||
| @@ -1,244 +0,0 @@ | ||||
| #include "application.h" | ||||
|  | ||||
| #include "models/folder.h" | ||||
| #include "database.h" | ||||
| #include "models/foldermodel.h" | ||||
| #include "models/change.h" | ||||
| #include "services/folderservice.h" | ||||
| #include "settings.h" | ||||
| #include "uuid.h" | ||||
| #include "dispatcher.h" | ||||
| #include "paths.h" | ||||
| #include "constants.h" | ||||
| #include "filters.h" | ||||
|  | ||||
|  | ||||
|  | ||||
| #include "qmlutils.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Application::Application(int &argc, char **argv) : | ||||
|     QGuiApplication(argc, argv) | ||||
|  | ||||
|     { | ||||
|  | ||||
| 	// This is linked to where the QSettings will be saved. In other words, | ||||
| 	// if these values are changed, the settings will be reset and saved | ||||
| 	// somewhere else. | ||||
| 	QCoreApplication::setOrganizationName(jop::ORG_NAME); | ||||
| 	QCoreApplication::setOrganizationDomain(jop::ORG_DOMAIN); | ||||
| 	QCoreApplication::setApplicationName(jop::APP_NAME); | ||||
|  | ||||
| 	qInfo() << "Config dir:" << paths::configDir(); | ||||
| 	qInfo() << "Database file:" << paths::databaseFile(); | ||||
| 	qInfo() << "SSL:" << QSslSocket::sslLibraryBuildVersionString() << QSslSocket::sslLibraryVersionNumber(); | ||||
|  | ||||
| 	jop::db().initialize(paths::databaseFile()); | ||||
|  | ||||
| 	Settings::initialize(); | ||||
|  | ||||
| 	Settings settings; | ||||
|  | ||||
| 	if (!settings.contains("clientId")) { | ||||
| 		// Client ID should be unique per instance of a program | ||||
| 		settings.setValue("clientId", uuid::createUuid()); | ||||
| 	} | ||||
|  | ||||
| 	Settings* qmlSettings = new Settings(); | ||||
|  | ||||
| 	view_.setResizeMode(QQuickView::SizeRootObjectToView); | ||||
| 	QQmlContext *ctxt = view_.rootContext(); | ||||
| 	ctxt->setContextProperty("folderListModel", &folderModel_); | ||||
| 	ctxt->setContextProperty("noteListModel", ¬eModel_); | ||||
| 	//ctxt->setContextProperty("noteModel", &selectedQmlNote_); | ||||
| 	ctxt->setContextProperty("settings", qmlSettings); | ||||
|  | ||||
| 	view_.setSource(QUrl("qrc:/app.qml")); | ||||
|  | ||||
| 	QObject* rootObject = (QObject*)view_.rootObject(); | ||||
|  | ||||
| 	QObject* itemList = qmlUtils::childFromProperty(rootObject, "itemList"); | ||||
|  | ||||
| 	itemListController_.setItemList(itemList); | ||||
| 	itemListController_.setParentId(QString("")); | ||||
|  | ||||
|  | ||||
| 	//qmlUtils::callQml(itemList, "testing"); | ||||
|  | ||||
|  | ||||
|  | ||||
| 	//qDebug() << itemList; | ||||
|  | ||||
|  | ||||
| //	QObject* itemList = rootObject->findChild<QObject*>("itemList"); | ||||
| //	qDebug() << "WWWWWWWWWW" << itemList; | ||||
|  | ||||
| 	//view_.callQml("testing"); | ||||
|  | ||||
|  | ||||
|  | ||||
| //	connect(rootObject, SIGNAL(currentFolderChanged()), this, SLOT(view_currentFolderChanged())); | ||||
| //	connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged())); | ||||
| //	connect(rootObject, SIGNAL(addFolderButtonClicked()), this, SLOT(view_addFolderButtonClicked())); | ||||
| //	connect(rootObject, SIGNAL(addNoteButtonClicked()), this, SLOT(view_addNoteButtonClicked())); | ||||
| //	connect(rootObject, SIGNAL(syncButtonClicked()), this, SLOT(view_syncButtonClicked())); | ||||
| //	connect(rootObject, SIGNAL(loginClicked(QString,QString,QString)), this, SLOT(dispatcher_loginClicked(QString,QString,QString))); | ||||
| //	connect(rootObject, SIGNAL(logoutClicked()), this, SLOT(dispatcher_logoutClicked())); | ||||
|  | ||||
| 	view_.show(); | ||||
|  | ||||
| 	synchronizerTimer_.setInterval(1000 * 120); | ||||
| 	synchronizerTimer_.start(); | ||||
|  | ||||
| 	connect(&synchronizerTimer_, SIGNAL(timeout()), this, SLOT(synchronizerTimer_timeout())); | ||||
|  | ||||
| 	connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&))); | ||||
|  | ||||
| 	if (!settings.contains("user.email") || !settings.contains("session.id") || !settings.contains("api.baseUrl")) { | ||||
| 		synchronizer_.freeze(); | ||||
| 		view_.showPage("login"); | ||||
| 	} else { | ||||
| 		afterSessionInitialization(); | ||||
| 		view_.showPage("main"); | ||||
| 		view_currentFolderChanged(); // Make sure the note list shows the right notes | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Application::~Application() { | ||||
| 	jop::db().close(); | ||||
| } | ||||
|  | ||||
| void Application::login(const QString &email, const QString &password) { | ||||
| 	Settings settings; | ||||
| 	QUrlQuery postData; | ||||
| 	postData.addQueryItem("email", email); | ||||
| 	postData.addQueryItem("password", password); | ||||
| 	postData.addQueryItem("client_id", settings.value("clientId").toString()); | ||||
| 	api_.post("sessions", QUrlQuery(), postData, "getSession"); | ||||
| } | ||||
|  | ||||
| void Application::api_requestDone(const QJsonObject& response, const QString& tag) { | ||||
| 	// TODO: handle errors | ||||
| 	// Handle expired sessions | ||||
|  | ||||
| 	if (tag == "getSession") { | ||||
| 		if (response.contains("error")) { | ||||
| 			qWarning() << "Could not get session:" << response.value("error").toString(); | ||||
| 			view_.emitSignal("loginFailed"); | ||||
| 			view_.showPage("login"); | ||||
| 		} else { | ||||
| 			QString sessionId = response.value("id").toString(); | ||||
| 			qInfo() << "Got session" << sessionId; | ||||
| 			Settings settings; | ||||
| 			settings.setValue("session.id", sessionId); | ||||
| 			afterSessionInitialization(); | ||||
| 			view_.emitSignal("loginSuccess"); | ||||
| 			view_.showPage("main"); | ||||
| 		} | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Application::dispatcher_loginClicked(const QString &apiBaseUrl, const QString &email, const QString &password) { | ||||
| 	view_.emitSignal("loginStarted"); | ||||
|  | ||||
| 	QString newBaseUrl = filters::apiBaseUrl(apiBaseUrl); | ||||
|  | ||||
| 	Settings settings; | ||||
|  | ||||
| 	if (newBaseUrl != settings.value("api.baseUrl").toString()) { | ||||
| 		// TODO: add confirmation dialog | ||||
| 		qDebug() << "Base URL has changed from" << settings.value("api.baseUrl").toString() << "to" << newBaseUrl; | ||||
| 		BaseModel::deleteAll(jop::FoldersTable); | ||||
| 		BaseModel::deleteAll(jop::ChangesTable); | ||||
| 		settings.remove("lastRevId"); | ||||
| 		settings.setValue("clientId", uuid::createUuid()); | ||||
| 	} | ||||
|  | ||||
| 	settings.setValue("user.email", filters::email(email)); | ||||
| 	settings.setValue("api.baseUrl", newBaseUrl); | ||||
|  | ||||
| 	api_.setBaseUrl(apiBaseUrl); | ||||
|  | ||||
| 	login(email, password); | ||||
| } | ||||
|  | ||||
| void Application::dispatcher_logoutClicked() { | ||||
| 	api_.abortAll(); | ||||
| 	synchronizer_.abort(); | ||||
| 	synchronizer_.freeze(); | ||||
|  | ||||
| 	Settings settings; | ||||
| 	settings.remove("session.id"); | ||||
| 	api_.setSessionId(""); | ||||
| 	synchronizer_.setSessionId(""); | ||||
|  | ||||
| 	view_.showPage("login"); | ||||
| } | ||||
|  | ||||
| void Application::synchronizerTimer_timeout() { | ||||
| 	//synchronizerTimer_.start(1000 * 10); | ||||
| 	synchronizer_.start(); | ||||
| } | ||||
|  | ||||
| QString Application::selectedFolderId() const { | ||||
| 	QObject* rootObject = (QObject*)view_.rootObject(); | ||||
| 	if (!rootObject) { | ||||
| 		qCritical() << "Calling selectedFolderId() when root is null"; | ||||
| 		return ""; | ||||
| 	} | ||||
|  | ||||
| 	int index = rootObject->property("currentFolderIndex").toInt(); | ||||
| 	QModelIndex modelIndex = folderModel_.index(index); | ||||
| 	return folderModel_.data(modelIndex, FolderModel::IdRole).toString(); | ||||
| } | ||||
|  | ||||
| QString Application::selectedNoteId() const { | ||||
| 	QObject* rootObject = (QObject*)view_.rootObject(); | ||||
|  | ||||
| 	int index = rootObject->property("currentNoteIndex").toInt(); | ||||
| 	QModelIndex modelIndex = noteModel_.index(index); | ||||
| 	return noteModel_.data(modelIndex, NoteModel::IdRole).toString(); | ||||
| } | ||||
|  | ||||
| void Application::afterSessionInitialization() { | ||||
| 	Settings settings; | ||||
| 	QString sessionId = settings.value("session.id").toString(); | ||||
| 	api_.setBaseUrl(settings.value("api.baseUrl").toString()); | ||||
| 	api_.setSessionId(sessionId); | ||||
| 	synchronizer_.api().setBaseUrl(settings.value("api.baseUrl").toString()); | ||||
| 	synchronizer_.setSessionId(sessionId); | ||||
| 	synchronizer_.unfreeze(); | ||||
| 	synchronizer_.start(); | ||||
| } | ||||
|  | ||||
| void Application::view_currentFolderChanged() { | ||||
| 	QString folderId = selectedFolderId(); | ||||
| 	noteModel_.setFolderId(folderId); | ||||
| } | ||||
|  | ||||
| void Application::view_currentNoteChanged() { | ||||
| //	QString noteId = selectedNoteId(); | ||||
| //	Note note = noteCollection_.byId(noteId); | ||||
| //	selectedQmlNote_.setNote(note); | ||||
| } | ||||
|  | ||||
| void Application::view_addNoteButtonClicked() { | ||||
| 	qDebug() <<"ADDNOTE"; | ||||
| } | ||||
|  | ||||
| void Application::view_addFolderButtonClicked() { | ||||
| //	QStringList fields; | ||||
| //	fields << "id"; | ||||
| //	VariantVector values; | ||||
| //	values << uuid::createUuid(); | ||||
| //	QSqlQuery q = db_.buildSqlQuery(Database::Insert, "folders", fields, values); | ||||
| //	q.exec(); | ||||
|  | ||||
| //	emit jop::dispatcher().folderCreated("test"); | ||||
| } | ||||
|  | ||||
| void Application::view_syncButtonClicked() { | ||||
| 	synchronizer_.start(); | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| #ifndef APPLICATION_H | ||||
| #define APPLICATION_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "database.h" | ||||
| #include "models/foldermodel.h" | ||||
| #include "models/notemodel.h" | ||||
| #include "webapi.h" | ||||
| #include "synchronizer.h" | ||||
| #include "window.h" | ||||
| #include "folderlistcontroller.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Application : public QGuiApplication { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Application(int &argc, char **argv); | ||||
| 	~Application(); | ||||
| 	void login(const QString& email, const QString& password); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	Window view_; | ||||
| 	FolderModel folderModel_; | ||||
| 	NoteModel noteModel_; | ||||
| 	QString selectedFolderId() const; | ||||
| 	QString selectedNoteId() const; | ||||
| 	WebApi api_; | ||||
| 	Synchronizer synchronizer_; | ||||
| 	QTimer synchronizerTimer_; | ||||
| 	FolderListController itemListController_; | ||||
|  | ||||
| 	void afterSessionInitialization(); | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void view_currentFolderChanged(); | ||||
| 	void view_currentNoteChanged(); | ||||
| 	void view_addNoteButtonClicked(); | ||||
| 	void view_addFolderButtonClicked(); | ||||
| 	void view_syncButtonClicked(); | ||||
|  | ||||
| 	void api_requestDone(const QJsonObject& response, const QString& tag); | ||||
|  | ||||
| 	void dispatcher_loginClicked(const QString &domain, const QString &email, const QString &password); | ||||
| 	void dispatcher_logoutClicked(); | ||||
|  | ||||
| 	void synchronizerTimer_timeout(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // APPLICATION_H | ||||
| @@ -1,73 +0,0 @@ | ||||
| #include "baseitemlistcontroller.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| BaseItemListController::BaseItemListController() : | ||||
|     parentId_(QString("")), | ||||
|     itemList_(NULL), | ||||
|     orderBy_("title") { | ||||
| } | ||||
|  | ||||
| void BaseItemListController::setItemList(QObject *itemList) { | ||||
| 	if (itemList_) { | ||||
| 		qFatal("Cannot reset itemList - create a new ItemListController instead"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	itemList_ = itemList; | ||||
|  | ||||
| 	connect(itemList, SIGNAL(rowsRequested(int,int)), this, SLOT(itemList_rowsRequested(int,int))); | ||||
| } | ||||
|  | ||||
| void BaseItemListController::setParentId(const QString &parentId) { | ||||
| 	parentId_= parentId; | ||||
| 	updateItemCount(); | ||||
| } | ||||
|  | ||||
| QString BaseItemListController::parentId() const { | ||||
| 	return parentId_; | ||||
| } | ||||
|  | ||||
| QObject *BaseItemListController::itemList() const { | ||||
| 	return itemList_; | ||||
| } | ||||
|  | ||||
| void BaseItemListController::setOrderBy(const QString &v) { | ||||
| 	orderBy_ = v; | ||||
| } | ||||
|  | ||||
| QString BaseItemListController::orderBy() const { | ||||
| 	return orderBy_; | ||||
| } | ||||
|  | ||||
| void BaseItemListController::updateItemCount() { | ||||
| 	qFatal("BaseItemListController::updateItemCount() must be implemented by child class"); | ||||
| } | ||||
|  | ||||
| void BaseItemListController::itemList_rowsRequested(int fromIndex, int toIndex) { | ||||
| 	Q_UNUSED(fromIndex); Q_UNUSED(toIndex); | ||||
| 	qFatal("BaseItemListController::itemList_rowsRequested() must be implemented by child class"); | ||||
| } | ||||
|  | ||||
| const BaseModel *BaseItemListController::cacheGet(int index) const { | ||||
| 	Q_UNUSED(index); | ||||
| 	qFatal("BaseItemListController::cacheGet() not implemented"); | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| void BaseItemListController::cacheSet(int index, BaseModel* baseModel) const { | ||||
| 	Q_UNUSED(index); Q_UNUSED(baseModel); | ||||
| 	qFatal("BaseItemListController::cacheSet() not implemented"); | ||||
| } | ||||
|  | ||||
| bool BaseItemListController::cacheIsset(int index) const { | ||||
| 	Q_UNUSED(index); | ||||
| 	qFatal("BaseItemListController::cacheIsset() not implemented"); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void BaseItemListController::cacheClear() const { | ||||
| 	qFatal("BaseItemListController::cacheClear() not implemented"); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| #ifndef BASEITEMLISTCONTROLLER_H | ||||
| #define BASEITEMLISTCONTROLLER_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class BaseItemListController : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	BaseItemListController(); | ||||
| 	void setItemList(QObject* itemList); | ||||
| 	void setParentId(const QString& parentId); | ||||
| 	QString parentId() const; | ||||
| 	QObject* itemList() const; | ||||
| 	void setOrderBy(const QString& v); | ||||
| 	QString orderBy() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QString parentId_; | ||||
| 	QObject* itemList_; | ||||
| 	QString orderBy_; | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	virtual void updateItemCount(); | ||||
|  | ||||
| 	// All these methods are const because we want to be able to clear the | ||||
| 	// cache or set values from any method including const ones. | ||||
| 	// http://stackoverflow.com/a/4248661/561309 | ||||
| 	virtual const BaseModel* cacheGet(int index) const; | ||||
| 	virtual void cacheSet(int index, BaseModel* baseModel) const; | ||||
| 	virtual bool cacheIsset(int index) const; | ||||
| 	virtual void cacheClear() const; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	virtual void itemList_rowsRequested(int fromIndex, int toIndex); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // BASEITEMLISTCONTROLLER_H | ||||
| @@ -1,11 +0,0 @@ | ||||
| @echo off | ||||
| D: | ||||
| mkdir "D:\Web\www\joplin\QtClient\build-JoplinQtClient-Visual_C_32_bits-Debug\" | ||||
| cd "D:\Web\www\joplin\QtClient\build-JoplinQtClient-Visual_C_32_bits-Debug\" | ||||
| "C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\JoplinQtClient\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" "JOP_FRONT_END_GUI=1" | ||||
| "C:\Qt\Tools\QtCreator\bin\jom.exe" qmake_all | ||||
| "C:\Qt\Tools\QtCreator\bin\jom.exe" | ||||
|  | ||||
| rem "C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\JoplinQtClient\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" | ||||
| rem "C:\Qt\Tools\QtCreator\bin\jom.exe" qmake_all | ||||
| rem "C:\Qt\Tools\QtCreator\bin\jom.exe" | ||||
| @@ -1,24 +0,0 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| set -e | ||||
|  | ||||
| # mkdir -p /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug | ||||
| # cd /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug | ||||
| # rm -rf debug/ release/ Makefile* | ||||
| # export PATH="/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin":$PATH | ||||
| # export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Windows Kits/8.1/bin/x86" | ||||
| # export PATH=$PATH:"/cygdrive/c/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" | ||||
| # "/cygdrive/c/Qt/5.7/msvc2015/bin/qmake.exe" D:\\Web\\www\\joplin\\QtClient\\JoplinQtClient\\JoplinQtClient.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" "JOP_FRONT_END_GUI=1" | ||||
| # "/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe" qmake_all | ||||
| # "/cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe" | ||||
| # rsync -a /cygdrive/d/Web/www/joplin/QtClient/dependencies/dll-debug/ /cygdrive/d/Web/www/joplin/QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug/debug | ||||
| # cd - | ||||
|  | ||||
|  | ||||
|  | ||||
| BUILD_DIR=/home/laurent/src/notes/QtClient/build-JoplinQtClient-Desktop_Qt_5_7_1_GCC_64bit-Debug | ||||
| mkdir -p "$BUILD_DIR" | ||||
| cd "$BUILD_DIR" | ||||
| /opt/Qt/5.7/gcc_64/bin/qmake /home/laurent/src/notes/QtClient/JoplinQtClient/JoplinQtClient.pro -spec linux-g++ CONFIG+=debug CONFIG+=qml_debug JOP_FRONT_END_CLI=1 | ||||
| /usr/bin/make qmake_all | ||||
| /usr/bin/make | ||||
| @@ -1,486 +0,0 @@ | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "cliapplication.h" | ||||
| #include "constants.h" | ||||
| #include "database.h" | ||||
| #include "paths.h" | ||||
| #include "uuid.h" | ||||
| #include "settings.h" | ||||
| #include "models/folder.h" | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| #include <signal.h> | ||||
|  | ||||
|  | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| StdoutHandler::StdoutHandler() : QTextStream(stdout) {} | ||||
| StderrHandler::StderrHandler() : QTextStream(stderr) {} | ||||
|  | ||||
| CliApplication::CliApplication(int &argc, char **argv) : QCoreApplication(argc, argv) { | ||||
| 	// This is linked to where the QSettings will be saved. In other words, | ||||
| 	// if these values are changed, the settings will be reset and saved | ||||
| 	// somewhere else. | ||||
| 	QCoreApplication::setOrganizationName(jop::ORG_NAME); | ||||
| 	QCoreApplication::setOrganizationDomain(jop::ORG_DOMAIN); | ||||
| 	QCoreApplication::setApplicationName(jop::APP_NAME); | ||||
|  | ||||
| 	qInfo() << "Config dir:" << paths::configDir(); | ||||
| 	qInfo() << "Database file:" << paths::databaseFile(); | ||||
| 	qInfo() << "SSL:" << QSslSocket::sslLibraryBuildVersionString() << QSslSocket::sslLibraryVersionNumber(); | ||||
|  | ||||
| 	jop::db().initialize(paths::databaseFile()); | ||||
|  | ||||
| 	Settings::initialize(); | ||||
|  | ||||
| 	Settings settings; | ||||
|  | ||||
| 	if (!settings.contains("clientId")) { | ||||
| 		// Client ID should be unique per instance of a program | ||||
| 		settings.setValue("clientId", uuid::createUuid()); | ||||
| 	} | ||||
|  | ||||
| 	connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&))); | ||||
| 	connect(&synchronizer_, SIGNAL(started()), this, SLOT(synchronizer_started())); | ||||
| 	connect(&synchronizer_, SIGNAL(finished()), this, SLOT(synchronizer_finished())); | ||||
| } | ||||
|  | ||||
| CliApplication::~CliApplication() { | ||||
| 	jop::db().close(); | ||||
| } | ||||
|  | ||||
| void CliApplication::api_requestDone(const QJsonObject& response, const QString& tag) { | ||||
| 	// TODO: handle errors | ||||
| 	// Handle expired sessions | ||||
|  | ||||
| 	if (tag == "getSession") { | ||||
| 		if (response.contains("error")) { | ||||
| 			qStderr() << "Could not login: " << response.value("error").toString() << endl; | ||||
| 			emit synchronizationDone(); | ||||
| 		} else { | ||||
| 			QString sessionId = response.value("id").toString(); | ||||
| 			Settings settings; | ||||
| 			settings.setValue("session.id", sessionId); | ||||
| 			startSynchronization(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Call this only once the API base URL has been defined and the session has been set. | ||||
| void CliApplication::startSynchronization() { | ||||
| 	Settings settings; | ||||
| 	synchronizer_.api().setBaseUrl(api_.baseUrl()); | ||||
| 	synchronizer_.setSessionId(settings.value("session.id").toString()); | ||||
| 	synchronizer_.unfreeze(); | ||||
| 	synchronizer_.start(); | ||||
| } | ||||
|  | ||||
| void CliApplication::synchronizer_started() { | ||||
| 	qDebug() << "Synchronization started..."; | ||||
| } | ||||
|  | ||||
| void CliApplication::synchronizer_finished() { | ||||
| 	qDebug() << "Synchronization finished..."; | ||||
| 	emit synchronizationDone(); | ||||
| } | ||||
|  | ||||
| bool CliApplication::filePutContents(const QString& filePath, const QString& content) const { | ||||
| 	QFile file(filePath); | ||||
| 	if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false; | ||||
|  | ||||
| 	QTextStream out(&file); | ||||
| 	out << content; | ||||
| 	out.flush(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QString CliApplication::fileGetContents(const QString& filePath) const { | ||||
| 	QFile file(filePath); | ||||
| 	if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(""); | ||||
|  | ||||
| 	QTextStream in(&file); | ||||
| 	return in.readAll(); | ||||
| } | ||||
|  | ||||
| void CliApplication::saveNoteIfFileChanged(Note& note, const QDateTime& originalLastModified, const QString& noteFilePath) { | ||||
| 	if (originalLastModified == QFileInfo(noteFilePath).lastModified()) return; | ||||
|  | ||||
| 	QString content = fileGetContents(noteFilePath); | ||||
| 	if (content.isEmpty()) return; | ||||
|  | ||||
| 	note.patchFriendlyString(content); | ||||
| 	note.save(); | ||||
| } | ||||
|  | ||||
| // int CliApplication::execCommandConfig(QCommandLineParser& parser) { | ||||
| // 	parser.addPositionalArgument("key", "Key of the config property."); | ||||
| // 	parser.addPositionalArgument("value", "Value of the config property."); | ||||
|  | ||||
| // 	QCommandLineOption unsetOption(QStringList() << "unset", "Unset the given <key>.", "key"); | ||||
| // 	parser.addOption(unsetOption); | ||||
|  | ||||
| // 	QStringList args = parser.positionalArguments(); | ||||
| // 	Settings settings; | ||||
|  | ||||
| // 	QString propKey = args.size() >= 1 ? args[0] : ""; | ||||
| // 	QString propValue = args.size() >= 2 ? args[1] : ""; | ||||
| // 	if (propKey.isEmpty()) { | ||||
| // 		QStringList propKeys = settings.allKeys(); | ||||
| // 		for (int i = 0; i < propKeys.size(); i++) { | ||||
| // 			qStdout() << settings.keyValueserialize(propKeys[i]) << endl; | ||||
| // 		} | ||||
| // 		return 0; | ||||
| // 	} | ||||
|  | ||||
| // 	if (propValue.isEmpty()) { | ||||
| // 		qStdout() << settings.keyValueserialize(propKey) << endl; | ||||
| // 		return 0; | ||||
| // 	} | ||||
|  | ||||
| // 	settings.setValue(propKey, propValue); | ||||
|  | ||||
| // 	return 0; | ||||
| // } | ||||
|  | ||||
| QStringList CliApplication::parseCommandLinePath(const QString& commandLine) const { | ||||
| 	QStringList output; | ||||
| 	int state = 0; // 0 = "outside quotes", 1 = "inside quotes" | ||||
| 	QString current(""); | ||||
| 	for (int i = 0; i < commandLine.length(); i++) { | ||||
| 		QChar c = commandLine[i]; | ||||
| 		 | ||||
| 		// End quote | ||||
| 		if (c == '"' && state == 1) { | ||||
| 			output << current; | ||||
| 			current = ""; | ||||
| 			state = 0; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		// Start quote | ||||
| 		if (c == '"' && state == 0) { | ||||
| 			state = 1; | ||||
| 			current = current.trimmed(); | ||||
| 			if (current != "") output << current; | ||||
| 			current = ""; | ||||
| 			state = 1; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		// A space when not inside a quoted string | ||||
| 		if (c == ' ' && state == 0) { | ||||
| 			current = current.trimmed(); | ||||
| 			if (current != "") output << current; | ||||
| 			current = ""; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		current += c; | ||||
| 	} | ||||
|  | ||||
| 	if (state == 0) current = current.trimmed(); | ||||
| 	if (current != "") output << current; | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| QString CliApplication::commandLineArgsToString(const QStringList& args) const { | ||||
| 	QString output; | ||||
| 	for (int i = 0; i < args.size(); i++) { | ||||
| 		if (output != "") output += " "; | ||||
| 		QString arg = args[i]; | ||||
| 		if (arg.contains(' ')) { | ||||
| 			output += QString("\"%1\"").arg(arg); | ||||
| 		} else { | ||||
| 			output += arg; | ||||
| 		} | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| int CliApplication::exec() { | ||||
| 	qDebug() << "==========================================="; | ||||
|  | ||||
| 	Settings settings; | ||||
|  | ||||
| 	QString command = "help"; | ||||
| 	QStringList args = arguments(); | ||||
|  | ||||
| 	if (args.size() >= 2) { | ||||
| 		command = args[1]; | ||||
| 		args.erase(args.begin() + 1); | ||||
| 	} | ||||
|  | ||||
| 	QCommandLineParser parser; | ||||
| 	QCommandLineOption helpOption(QStringList() << "h" << "help", "Display usage information."); | ||||
| 	parser.addOption(helpOption); | ||||
| 	parser.addVersionOption(); | ||||
|  | ||||
| 	// mkdir "new_folder" | ||||
| 	// rm "new_folder" | ||||
| 	// ls | ||||
| 	// ls new_folder | ||||
| 	// touch new_folder/new_note | ||||
| 	// edit new_folder/new_note | ||||
| 	// config editor "subl -w %1" | ||||
| 	// sync | ||||
|  | ||||
| 	// TODO: implement mv "new_folder" | ||||
|  | ||||
| 	if (command == "mkdir") { | ||||
| 		parser.addPositionalArgument("path", "Folder path."); | ||||
| 	} else if (command == "rm") { | ||||
| 		parser.addPositionalArgument("path", "Folder path."); | ||||
| 	} else if (command == "ls") { | ||||
| 		parser.addPositionalArgument("path", "Folder path."); | ||||
| 	} else if (command == "touch") { | ||||
| 		parser.addPositionalArgument("path", "Note path."); | ||||
| 	} else if (command == "edit") { | ||||
| 		parser.addPositionalArgument("path", "Note path."); | ||||
| 	} else if (command == "config") { | ||||
| 		parser.addPositionalArgument("key", "Key of the config property."); | ||||
| 		parser.addPositionalArgument("value", "Value of the config property."); | ||||
| 		parser.addOption(QCommandLineOption(QStringList() << "unset", "Unset the given <key>.", "key")); | ||||
| 	} else if (command == "sync") { | ||||
|  | ||||
| 	} else if (command == "help") { | ||||
|  | ||||
| 	} else { | ||||
| 		qStderr() << parser.helpText() << endl; | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	parser.process(args); | ||||
|  | ||||
| 	if (parser.isSet(helpOption) || command == "help") { | ||||
| 		qStdout() << parser.helpText(); | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	args = parser.positionalArguments(); | ||||
|  | ||||
| 	int errorCode = 0; | ||||
|  | ||||
| 	if (command == "mkdir") { | ||||
| 		QString path = args.size() ? args[0] : QString(); | ||||
|  | ||||
| 		if (path.isEmpty()) { | ||||
| 			qStderr() << "Please provide a path or name for the folder."; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode); | ||||
| 		if (errorCode) { | ||||
| 			qStderr() << "Invalid path: " << path << endl; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		Folder folder; | ||||
| 		folder.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : ""); | ||||
| 		folder.setValue("title", Folder::pathBaseName(path)); | ||||
| 		folder.save(); | ||||
| 	} | ||||
|  | ||||
| 	if (command == "rm") { | ||||
| 		QString path = args.size() ? args[0] : QString(); | ||||
|  | ||||
| 		if (path.isEmpty()) { | ||||
| 			qStderr() << "Please provide a path or name for the folder."; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, true, errorCode); | ||||
| 		if (errorCode || !folders.size()) { | ||||
| 			qStderr() << "Invalid path: " << path << endl; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		folders[folders.size() - 1]->dispose(); | ||||
| 	} | ||||
|  | ||||
| 	if (command == "ls") { | ||||
| 		QString path = args.size() ? args[0] : QString(); | ||||
| 		std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, true, errorCode); | ||||
|  | ||||
| 		if (errorCode) { | ||||
| 			qStderr() << "Invalid path: " << path << endl; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<BaseModel>> children; | ||||
| 		if (folders.size()) { | ||||
| 			children = folders[folders.size() - 1]->children(); | ||||
| 		} else { | ||||
| 			std::unique_ptr<Folder> root = Folder::root(); | ||||
| 			children = root->children(); | ||||
| 		} | ||||
|  | ||||
| 		qStdout() << QString("Total: %1 items").arg(children.size()) << endl; | ||||
| 		for (size_t i = 0; i < children.size(); i++) { | ||||
| 			qStdout() << children[i]->displayTitle() << endl; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (command == "touch") { | ||||
| 		QString path = args.size() ? args[0] : QString(); | ||||
|  | ||||
| 		if (path.isEmpty()) { | ||||
| 			qStderr() << "Please provide a path or name for the note."; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode); | ||||
|  | ||||
| 		if (errorCode) { | ||||
| 			qStderr() << "Invalid path: " << path << endl; | ||||
| 		} else { | ||||
| 			QString noteTitle = Folder::pathBaseName(path); | ||||
|  | ||||
| 			Note note; | ||||
| 			note.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : ""); | ||||
| 			note.setValue("title", noteTitle); | ||||
| 			note.save(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (command == "edit") { | ||||
| 		QString path = args.size() ? args[0] : QString(); | ||||
|  | ||||
| 		if (path.isEmpty()) { | ||||
| 			qStderr() << "Please provide a path or name for the note."; | ||||
| 			return 1; | ||||
| 		} | ||||
|  | ||||
| 		std::vector<std::unique_ptr<Folder>> folders = Folder::pathToFolders(path, false, errorCode); | ||||
|  | ||||
| 		if (errorCode) { | ||||
| 			qStderr() << "Invalid path: " << path << endl; | ||||
| 		} else { | ||||
| 			// TODO: handle case where two notes with the same title exist | ||||
|  | ||||
| 			QString editorCommandString = settings.value("editor").toString().trimmed(); | ||||
| 			if (editorCommandString.isEmpty()) { | ||||
| 				qStderr() << "No editor is defined. Please define one using the \"config editor\" command." << endl; | ||||
| 				return 1; | ||||
| 			} | ||||
|  | ||||
| 			QStringList editorCommand = parseCommandLinePath(editorCommandString); | ||||
|  | ||||
| 			QString parentId = folders.size() ? folders[folders.size() - 1]->idString() : QString(""); | ||||
| 			QString noteTitle = Folder::pathBaseName(path); | ||||
| 			Note note; | ||||
| 			if (!note.loadByField(parentId, QString("title"), noteTitle)) { | ||||
| 				note.setValue("parent_id", folders.size() ? folders[folders.size() - 1]->idString() : ""); | ||||
| 				note.setValue("title", noteTitle); | ||||
| 				note.save(); | ||||
| 				note.reload(); // To ensure that all fields are populated with the default values | ||||
| 			} | ||||
|  | ||||
| 			QString noteFilePath = QString("%1/%2.txt").arg(paths::noteDraftsDir()).arg(note.idString()); | ||||
|  | ||||
| 			if (!filePutContents(noteFilePath, note.serialize())) { | ||||
| 				qStderr() << QString("Cannot open %1 for writing").arg(noteFilePath) << endl; | ||||
| 				return 1; | ||||
| 			} | ||||
|  | ||||
| 			QFileInfo fileInfo(noteFilePath); | ||||
| 			QDateTime originalLastModified = fileInfo.lastModified(); | ||||
|  | ||||
| 			qStdout() << QString("Editing note \"%1\" (Either close the editor or press Ctrl+C when done)").arg(path) << endl; | ||||
| 			qDebug() << "File:" << noteFilePath; | ||||
| 			QProcess* process = new QProcess(); | ||||
| 			qint64 processId = 0; | ||||
|  | ||||
| 			QString editorCommandPath = editorCommand.takeFirst(); | ||||
| 			editorCommand << noteFilePath; | ||||
| 			if (!process->startDetached(editorCommandPath, editorCommand, QString(), &processId)) { | ||||
| 				qStderr() << QString("Could not start command: %1").arg(editorCommandPath + " " + commandLineArgsToString(editorCommand)) << endl; | ||||
| 				return 1; | ||||
| 			} | ||||
|  | ||||
| 			while (kill(processId, 0) == 0) { // While the process still exist | ||||
| 				QThread::sleep(2); | ||||
| 				saveNoteIfFileChanged(note, originalLastModified, noteFilePath); | ||||
| 			} | ||||
|  | ||||
| 			saveNoteIfFileChanged(note, originalLastModified, noteFilePath); | ||||
|  | ||||
| 			delete process; process = NULL; | ||||
|  | ||||
| 			QFile::remove(noteFilePath); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (command == "config") { | ||||
| 		if (parser.isSet("unset")) { | ||||
| 			QString key = parser.value("unset").trimmed(); | ||||
| 			settings.remove(key); | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		QString propKey = args.size() >= 1 ? args[0] : ""; | ||||
| 		QString propValue = args.size() >= 2 ? args[1] : ""; | ||||
| 		if (propKey.isEmpty()) { | ||||
| 			QStringList propKeys = settings.allKeys(); | ||||
| 			for (int i = 0; i < propKeys.size(); i++) { | ||||
| 				qStdout() << settings.keyValueserialize(propKeys[i]) << endl; | ||||
| 			} | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		if (propValue.isEmpty()) { | ||||
| 			qStdout() << settings.keyValueserialize(propKey) << endl; | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		settings.setValue(propKey, propValue); | ||||
| 	} | ||||
|  | ||||
| 	if (command == "sync") { | ||||
| 		QString sessionId = settings.value("session.id").toString(); | ||||
| 		qDebug() << "Session ID:" << sessionId; | ||||
|  | ||||
| 		// TODO: ask user | ||||
| 		api_.setBaseUrl("http://127.0.0.1:8000"); | ||||
|  | ||||
| 		QEventLoop loop; | ||||
| 		connect(this, SIGNAL(synchronizationDone()), &loop, SLOT(quit())); | ||||
|  | ||||
| 		if (sessionId == "") { | ||||
| 			QTextStream qtin(stdin);  | ||||
| 			qStdout() << "Enter email:" << endl; | ||||
| 			QString email = qtin.readLine(); | ||||
| 			qStdout() << "Enter password:" << endl; | ||||
| 			QString password = qtin.readLine(); | ||||
|  | ||||
| 			qDebug() << email << password; | ||||
|  | ||||
| 			Settings settings; | ||||
| 			QUrlQuery postData; | ||||
| 			postData.addQueryItem("email", email); | ||||
| 			postData.addQueryItem("password", password); | ||||
| 			postData.addQueryItem("client_id", settings.value("clientId").toString()); | ||||
| 			api_.post("sessions", QUrlQuery(), postData, "getSession"); | ||||
| 		} else { | ||||
| 			startSynchronization(); | ||||
| 		} | ||||
|  | ||||
| 		loop.exec(); | ||||
|  | ||||
| 		qDebug() << "Synchronization done"; | ||||
| 	} | ||||
|  | ||||
| 	qDebug() << "=========================================== END"; | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,75 +0,0 @@ | ||||
| #ifndef CLIAPPLICATION_H | ||||
| #define CLIAPPLICATION_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "command.h" | ||||
| #include "models/note.h" | ||||
| #include "webapi.h" | ||||
| #include "synchronizer.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class StdoutHandler : public QTextStream { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	StdoutHandler(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| class StderrHandler : public QTextStream { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	StderrHandler(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| inline StdoutHandler& qStdout() { | ||||
| 	static StdoutHandler r; | ||||
| 	return r; | ||||
| } | ||||
|  | ||||
| inline StderrHandler& qStderr() { | ||||
| 	static StderrHandler r; | ||||
| 	return r; | ||||
| } | ||||
|  | ||||
| class CliApplication : public QCoreApplication { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	CliApplication(int &argc, char **argv); | ||||
| 	~CliApplication(); | ||||
| 	void processCommand(const Command &command); | ||||
| 	int exec(); | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void api_requestDone(const QJsonObject& response, const QString& tag); | ||||
| 	void synchronizer_started(); | ||||
| 	void synchronizer_finished(); | ||||
|  | ||||
| signals: | ||||
|  | ||||
| 	void synchronizationDone(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	bool filePutContents(const QString& filePath, const QString& content) const; | ||||
| 	void startSynchronization(); | ||||
| 	QString fileGetContents(const QString& filePath) const; | ||||
| 	void saveNoteIfFileChanged(Note& note, const QDateTime& originalLastModified, const QString& noteFilePath); | ||||
| 	QStringList parseCommandLinePath(const QString& commandLine) const; | ||||
| 	QString commandLineArgsToString(const QStringList& args) const; | ||||
| 	WebApi api_; | ||||
| 	Synchronizer synchronizer_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // CLIAPPLICATION_H | ||||
| @@ -1,24 +0,0 @@ | ||||
| #include "command.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| Command::Command(const QStringList &arguments) : name_("help"), args_(arguments) { | ||||
| 	args_.removeFirst(); | ||||
| 	if (args_.size() >= 1) { | ||||
| 		name_ = args_.takeFirst(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QString Command::name() const { | ||||
| 	return name_; | ||||
| } | ||||
|  | ||||
| std::map<QString, QString> Command::flags() const { | ||||
| 	return flags_; | ||||
| } | ||||
|  | ||||
| QStringList Command::args() const { | ||||
| 	return args_; | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| #ifndef COMMAND_H | ||||
| #define COMMAND_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Command { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Command(const QStringList& arguments); | ||||
| 	QString name() const; | ||||
| 	std::map<QString, QString> flags() const; | ||||
| 	QStringList args() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QString name_; | ||||
| 	std::map<QString, QString> flags_; | ||||
| 	QStringList args_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // COMMAND_H | ||||
| @@ -1,26 +0,0 @@ | ||||
| #ifndef CONSTANTS_H | ||||
| #define CONSTANTS_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| const QString ORG_NAME = "Cozic"; | ||||
| const QString ORG_DOMAIN = "cozic.net"; | ||||
| const QString APP_NAME = "Joplin"; | ||||
|  | ||||
| #ifdef Q_WS_WIN | ||||
| const QString NEW_LINE = "\r\n"; | ||||
| #else // Q_WS_WIN | ||||
| const QString NEW_LINE = "\n"; | ||||
| #endif // Q_WS_WIN | ||||
|  | ||||
| #if defined(JOP_FRONT_END_CLI) | ||||
| const QString FRONT_END = "cli"; | ||||
| #elif defined(JOP_FRONT_END_GUI) | ||||
| const QString FRONT_END = "gui"; | ||||
| #endif // JOP_FRONT_END_GUI | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // CONSTANTS_H | ||||
| @@ -1,270 +0,0 @@ | ||||
| #include "database.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Database::Database() : db_(NULL), isClosed_(true) {} | ||||
|  | ||||
| Database::~Database() { | ||||
| 	if (!isClosed()) qWarning() << "Database::close() should be called explicitely"; | ||||
| } | ||||
|  | ||||
| void Database::initialize(const QString &path) { | ||||
| 	version_ = -1; | ||||
| 	transactionCount_ = 0; | ||||
| 	logQueries_ = true; | ||||
|  | ||||
| 	// QFile::remove(path); | ||||
|  | ||||
| 	db_ = new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE")); | ||||
| 	db_->setDatabaseName(path); | ||||
|  | ||||
| 	if  (!db_->open()) { | ||||
| 		qFatal("Error: connection with database fail"); | ||||
| 	} else { | ||||
| 		qInfo() << "Database: connection ok"; | ||||
| 		isClosed_ = false; | ||||
| 	} | ||||
|  | ||||
| 	upgrade(); | ||||
| } | ||||
|  | ||||
| // See https://bugreports.qt.io/browse/QTBUG-35977 for the reason why it's necessary | ||||
| // to manually destroy the QSqlDatabase instance (i.e. it cannot be done in the | ||||
| // Database::~Database). | ||||
| void Database::close() { | ||||
| 	if (db_ && db_->open()) db_->close(); | ||||
| 	delete db_; | ||||
| 	db_ = NULL; | ||||
| 	isClosed_ = true; | ||||
| } | ||||
|  | ||||
| bool Database::isClosed() const { | ||||
| 	return isClosed_; | ||||
| } | ||||
|  | ||||
| QSqlDatabase* Database::database() const { | ||||
| 	if (isClosed_) qFatal("Database::database: Database is closed"); | ||||
| 	return db_; | ||||
| } | ||||
|  | ||||
| QSqlQuery Database::buildSqlQuery(Database::QueryType type, const QString &tableName, const QStringList &fields, const VariantVector &values, const QString &whereCondition) { | ||||
| 	QString sql; | ||||
|  | ||||
| 	if (type == Insert) { | ||||
| 		QString fieldString = ""; | ||||
| 		QString valueString = ""; | ||||
| 		for (int i = 0; i < fields.length(); i++) { | ||||
| 			QString f = fields[i]; | ||||
| 			if (fieldString != "") fieldString += ", "; | ||||
| 			if (valueString != "") valueString += ", "; | ||||
| 			fieldString += QString("`%1`").arg(f); | ||||
| 			valueString += ":" + f; | ||||
| 		} | ||||
|  | ||||
| 		sql = QString("INSERT INTO `%1` (%2) VALUES (%3)").arg(tableName).arg(fieldString).arg(valueString); | ||||
| 	} else if (type == Update) { | ||||
| 		QString fieldString = ""; | ||||
| 		for (int i = 0; i < fields.length(); i++) { | ||||
| 			QString f = fields[i]; | ||||
| 			if (fieldString != "") fieldString += ", "; | ||||
| 			fieldString += QString("`%1`=:%1").arg(f); | ||||
| 		} | ||||
|  | ||||
| 		sql = QString("UPDATE `%1` SET %2").arg(tableName).arg(fieldString); | ||||
| 		if (whereCondition != "") sql += " WHERE " + whereCondition; | ||||
| 	} | ||||
|  | ||||
| 	QSqlQuery query(*db_); | ||||
| 	bool ok = query.prepare(sql); | ||||
| 	if (!ok) { | ||||
| 		printError(query); | ||||
| 		return query; | ||||
| 	} | ||||
|  | ||||
| 	for (int i = 0; i < values.size(); i++) { | ||||
| 		QVariant v = values[i]; | ||||
| 		QString fieldName = ":" + fields[i]; | ||||
| 		if (v.type() == QVariant::String) { | ||||
| 			query.bindValue(fieldName, v.toString()); | ||||
| 		} else if (v.type() == QVariant::Int) { | ||||
| 			query.bindValue(fieldName, v.toInt()); | ||||
| 		} else if (v.isNull()) { | ||||
| 			query.bindValue(fieldName, (int)NULL); | ||||
| 		} else if (v.type() == QVariant::Double) { | ||||
| 			query.bindValue(fieldName, v.toDouble()); | ||||
| 		} else if (v.type() == (QVariant::Type)QMetaType::Float) { | ||||
| 			query.bindValue(fieldName, v.toFloat()); | ||||
| 		} else if (v.type() == QVariant::LongLong) { | ||||
| 			query.bindValue(fieldName, v.toLongLong()); | ||||
| 		} else if (v.type() == QVariant::UInt) { | ||||
| 			query.bindValue(fieldName, v.toUInt()); | ||||
| 		} else if (v.type() == QVariant::Char) { | ||||
| 			query.bindValue(fieldName, v.toChar()); | ||||
| 		} else { | ||||
| 			qWarning() << Q_FUNC_INFO << "Unsupported variant type:" << v.type(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return query; | ||||
| } | ||||
|  | ||||
| QSqlQuery Database::buildSqlQuery(Database::QueryType type, const QString &tableName, const QMap<QString, QVariant> &values, const QString &whereCondition) { | ||||
| 	QStringList fields; | ||||
| 	VariantVector fieldValues; | ||||
| 	for (QMap<QString, QVariant>::const_iterator it = values.begin(); it != values.end(); ++it) { | ||||
| 		fields.push_back(it.key()); | ||||
| 		fieldValues.push_back(it.value()); | ||||
| 	} | ||||
| 	return buildSqlQuery(type, tableName, fields, fieldValues, whereCondition); | ||||
| } | ||||
|  | ||||
| void Database::printError(const QSqlQuery& query) const { | ||||
| 	if (query.lastError().isValid()) { | ||||
| 		qCritical().noquote() << "SQL error: " << query.lastError().text().trimmed() << ". Query was: " << query.lastQuery(); | ||||
| 		QMapIterator<QString, QVariant> i(query.boundValues()); | ||||
| 		while (i.hasNext()) { | ||||
| 			i.next(); | ||||
| 			qCritical() << i.key() << "=" << i.value().toString(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool Database::errorCheck(const QSqlQuery& query) { | ||||
| 	if (query.lastError().isValid()) { | ||||
| 		printError(query); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool Database::transaction() { | ||||
| 	transactionCount_++; | ||||
| 	if (transactionCount_ > 1) return true; | ||||
| 	return db_->transaction(); | ||||
| } | ||||
|  | ||||
| bool Database::commit() { | ||||
| 	transactionCount_--; | ||||
|  | ||||
| 	if (transactionCount_ < 0) { | ||||
| 		transactionCount_ = 0; | ||||
| 		qCritical() << "Attempting commit on a database that is not in transaction mode"; | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (transactionCount_ <= 0) { | ||||
| 		return db_->commit(); | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool Database::execQuery(QSqlQuery &query) { | ||||
| 	if (logQueries_) { | ||||
| 		QString sql = query.lastQuery(); | ||||
| 		qDebug().noquote() << "SQL:" << sql; | ||||
|  | ||||
| 		QMapIterator<QString, QVariant> i(query.boundValues()); | ||||
| 		while (i.hasNext()) { | ||||
| 			i.next(); | ||||
| 			qDebug().noquote() << "SQL:" << i.key() << "=" << i.value().toString(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return query.exec(); | ||||
| } | ||||
|  | ||||
| bool Database::execQuery(const QString &sql) { | ||||
| 	QSqlQuery query(sql, *db_); | ||||
| 	return execQuery(query); | ||||
| } | ||||
|  | ||||
| QSqlQuery Database::prepare(const QString &sql) { | ||||
| 	QSqlQuery query(*db_); | ||||
| 	query.prepare(sql); | ||||
| 	return query; | ||||
| } | ||||
|  | ||||
| int Database::version() const { | ||||
| 	if (version_ >= 0) return version_; | ||||
|  | ||||
| 	QSqlQuery query = db_->exec("SELECT * FROM version"); | ||||
| 	bool result = query.next(); | ||||
| 	if (!result) return 0; | ||||
|  | ||||
| 	QSqlRecord r = query.record(); | ||||
| 	int i_version = r.indexOf("version"); | ||||
|  | ||||
| 	version_ = query.value(i_version).toInt(); | ||||
| 	return version_; | ||||
| } | ||||
|  | ||||
| QStringList Database::sqlStringToLines(const QString& sql) { | ||||
| 	QStringList statements; | ||||
| 	QStringList lines = sql.split("\n"); | ||||
| 	QString statement; | ||||
| 	foreach (QString line, lines) { | ||||
| 		line = line.trimmed(); | ||||
| 		if (line == "") continue; | ||||
| 		if (line.left(2) == "--") continue; | ||||
| 		statement += line; | ||||
| 		if (line[line.length() - 1] == ';') { | ||||
| 			statements.append(statement); | ||||
| 			statement = ""; | ||||
| 		} | ||||
| 	} | ||||
| 	return statements; | ||||
| } | ||||
|  | ||||
| void Database::upgrade() { | ||||
| 	// INSTRUCTIONS TO UPGRADE THE DATABASE: | ||||
| 	// | ||||
| 	// 1. Add the new version number to the existingDatabaseVersions array | ||||
| 	// 2. Add the upgrade logic to the "switch (targetVersion)" statement below | ||||
|  | ||||
| 	QList<int> existingVersions; | ||||
| 	existingVersions << 1; | ||||
|  | ||||
| 	int versionIndex = existingVersions.indexOf(version()); | ||||
| 	if (versionIndex == existingVersions.length() - 1) return; | ||||
|  | ||||
| 	while (versionIndex < existingVersions.length() - 1) { | ||||
| 		int targetVersion = existingVersions[versionIndex + 1]; | ||||
|  | ||||
| 		qDebug() << "Upgrading database to version " << targetVersion; | ||||
|  | ||||
| 		db_->transaction(); | ||||
|  | ||||
| 		switch (targetVersion) { | ||||
|  | ||||
| 		    case 1: | ||||
|  | ||||
| 			    QFile f(":/schema.sql"); | ||||
| 				if  (!f.open(QFile::ReadOnly | QFile::Text)) { | ||||
| 					qFatal("Cannot open database schema file"); | ||||
| 					return; | ||||
| 				} | ||||
| 				QTextStream in(&f); | ||||
| 				QString schemaSql = in.readAll(); | ||||
|  | ||||
| 				QStringList lines = sqlStringToLines(schemaSql); | ||||
| 				foreach (const QString& line, lines) { | ||||
| 					db_->exec(line); | ||||
| 				} | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		db_->exec(QString("UPDATE version SET version = %1").arg(targetVersion)); | ||||
| 		db_->commit(); | ||||
|  | ||||
| 		versionIndex++; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Database databaseInstance_; | ||||
|  | ||||
| Database& jop::db() { | ||||
| 	return databaseInstance_; | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| #ifndef DATABASE_H | ||||
| #define DATABASE_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "enum.h" | ||||
| #include "simpletypes.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Database { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	enum QueryType { Select, Insert, Update, Delete }; | ||||
|  | ||||
| 	Database(); | ||||
| 	~Database(); | ||||
| 	void initialize(const QString& path); | ||||
| 	void close(); | ||||
| 	bool isClosed() const; | ||||
| 	QSqlDatabase* database() const; | ||||
| 	QSqlQuery buildSqlQuery(Database::QueryType type, const QString& tableName, const QStringList& fields, const VariantVector& values, const QString& whereCondition = ""); | ||||
| 	QSqlQuery buildSqlQuery(Database::QueryType type, const QString& tableName, const QMap<QString, QVariant>& values, const QString& whereCondition = ""); | ||||
| 	bool errorCheck(const QSqlQuery& query); | ||||
| 	bool transaction(); | ||||
| 	bool commit(); | ||||
| 	bool execQuery(QSqlQuery &query); | ||||
| 	bool execQuery(const QString &query); | ||||
| 	QSqlQuery prepare(const QString& sql); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QSqlDatabase* db_; | ||||
| 	void upgrade(); | ||||
| 	int version() const; | ||||
| 	mutable int version_; | ||||
| 	QStringList sqlStringToLines(const QString& sql); | ||||
| 	void printError(const QSqlQuery& query) const; | ||||
| 	int transactionCount_; | ||||
| 	bool logQueries_; | ||||
| 	bool isClosed_; | ||||
|  | ||||
| }; | ||||
|  | ||||
|  | ||||
| Database& db(); | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // DATABASE_H | ||||
| @@ -1,5 +0,0 @@ | ||||
| <RCC> | ||||
|     <qresource prefix="/"> | ||||
|         <file>schema.sql</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
| @@ -1,69 +0,0 @@ | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "databaseutils.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| QSqlQuery dbUtils::buildSqlQuery(QSqlDatabase* db, const QString& type, const QString& tableName, const QStringList& fields, const VariantVector& values, const QString& whereCondition) { | ||||
| 	QString sql; | ||||
|  | ||||
| 	if (type.toLower() == "insert") { | ||||
| 		QString fieldString = ""; | ||||
| 		QString valueString = ""; | ||||
| 		for (int i = 0; i < fields.length(); i++) { | ||||
| 			QString f = fields[i]; | ||||
| 			if (fieldString != "") fieldString += ", "; | ||||
| 			if (valueString != "") valueString += ", "; | ||||
| 			fieldString += f; | ||||
| 			valueString += ":" + f; | ||||
| 		} | ||||
|  | ||||
| 		sql = QString("INSERT INTO %1 (%2) VALUES (%3)").arg(tableName).arg(fieldString).arg(valueString); | ||||
| 	} else if (type.toLower() == "update") { | ||||
| 		QString fieldString = ""; | ||||
| 		for (int i = 0; i < fields.length(); i++) { | ||||
| 			QString f = fields[i]; | ||||
| 			if (fieldString != "") fieldString += ", "; | ||||
| 			fieldString += f + " = :" + f; | ||||
| 		} | ||||
|  | ||||
| 		sql = QString("UPDATE %1 SET %2").arg(tableName).arg(fieldString); | ||||
| 		if (whereCondition != "") sql += " WHERE " + whereCondition; | ||||
| 	} | ||||
|  | ||||
| 	QSqlQuery query(*db); | ||||
| 	query.prepare(sql); | ||||
| 	for (int i = 0; i < values.size(); i++) { | ||||
| 		QVariant v = values[i]; | ||||
| 		QString fieldName = ":" + fields[i]; | ||||
| 		if (v.type() == QVariant::String) { | ||||
| 			query.bindValue(fieldName, v.toString()); | ||||
| 		} else if (v.type() == QVariant::Int) { | ||||
| 			query.bindValue(fieldName, v.toInt()); | ||||
| 		} else if (v.isNull()) { | ||||
| 			query.bindValue(fieldName, (int)NULL); | ||||
| 		} else if (v.type() == QVariant::Double) { | ||||
| 			query.bindValue(fieldName, v.toDouble()); | ||||
| 		} else if (v.type() == (QVariant::Type)QMetaType::Float) { | ||||
| 			query.bindValue(fieldName, v.toFloat()); | ||||
| 		} else if (v.type() == QVariant::LongLong) { | ||||
| 			query.bindValue(fieldName, v.toLongLong()); | ||||
| 		} else if (v.type() == QVariant::UInt) { | ||||
| 			query.bindValue(fieldName, v.toUInt()); | ||||
| 		} else if (v.type() == QVariant::Char) { | ||||
| 			query.bindValue(fieldName, v.toChar()); | ||||
| 		} else { | ||||
| 			qWarning() << Q_FUNC_INFO << "Unsupported variant type:" << v.type(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	qDebug() <<"SQL:"<<sql; | ||||
|  | ||||
| 	QMapIterator<QString, QVariant> i(query.boundValues()); | ||||
| 	while (i.hasNext()) { | ||||
| 		i.next(); | ||||
| 		qDebug() << i.key() << ":" << i.value().toString(); | ||||
| 	} | ||||
|  | ||||
| 	return query; | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| #ifndef DATABASEUTILS_H | ||||
| #define DATABASEUTILS_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "simpletypes.h" | ||||
|  | ||||
| namespace jop { | ||||
| namespace dbUtils { | ||||
|  | ||||
| QSqlQuery buildSqlQuery(QSqlDatabase* db, const QString& type, const QString& tableName, const QStringList& fields, const VariantVector& values, const QString& whereCondition = ""); | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // DATABASEUTILS_H | ||||
| @@ -1,59 +0,0 @@ | ||||
| #include "dispatcher.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Dispatcher::Dispatcher() {} | ||||
|  | ||||
| void Dispatcher::emitFolderCreated(const QString &folderId) { | ||||
| 	emit folderCreated(folderId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitFolderUpdated(const QString &folderId) { | ||||
| 	emit folderUpdated(folderId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitFolderDeleted(const QString &folderId) { | ||||
| 	emit folderDeleted(folderId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitAllFoldersDeleted() { | ||||
| 	emit allFoldersDeleted(); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitNoteCreated(const QString ¬eId) { | ||||
| 	emit noteCreated(noteId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitNoteUpdated(const QString ¬eId) { | ||||
| 	emit noteUpdated(noteId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitNoteDeleted(const QString ¬eId) { | ||||
| 	emit noteDeleted(noteId); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitLoginClicked(const QString &apiBaseUrl, const QString &email, const QString &password) { | ||||
| 	emit loginClicked(apiBaseUrl, email, password); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitLogoutClicked() { | ||||
| 	emit logoutClicked(); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitLoginStarted() { | ||||
| 	emit loginStarted(); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitLoginFailed() { | ||||
| 	emit loginFailed(); | ||||
| } | ||||
|  | ||||
| void Dispatcher::emitLoginSuccess() { | ||||
| 	emit loginSuccess(); | ||||
| } | ||||
|  | ||||
| Dispatcher dispatcherInstance_; | ||||
|  | ||||
| Dispatcher& jop::dispatcher() { | ||||
| 	return dispatcherInstance_; | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| #ifndef DISPATCHER_H | ||||
| #define DISPATCHER_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Dispatcher : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Dispatcher(); | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void emitFolderCreated(const QString& folderId); | ||||
| 	void emitFolderUpdated(const QString& folderId); | ||||
| 	void emitFolderDeleted(const QString& folderId); | ||||
| 	void emitAllFoldersDeleted(); | ||||
| 	void emitNoteCreated(const QString& noteId); | ||||
| 	void emitNoteUpdated(const QString& noteId); | ||||
| 	void emitNoteDeleted(const QString& noteId); | ||||
| 	void emitLoginClicked(const QString& domain, const QString& email, const QString &password); | ||||
| 	void emitLogoutClicked(); | ||||
| 	void emitLoginStarted(); | ||||
| 	void emitLoginFailed(); | ||||
| 	void emitLoginSuccess(); | ||||
|  | ||||
| signals: | ||||
|  | ||||
| 	void folderCreated(const QString& folderId); | ||||
| 	void folderUpdated(const QString& folderId); | ||||
| 	void folderDeleted(const QString& folderId); | ||||
| 	void allFoldersDeleted(); | ||||
| 	void noteCreated(const QString& noteId); | ||||
| 	void noteUpdated(const QString& noteId); | ||||
| 	void noteDeleted(const QString& noteId); | ||||
| 	void loginClicked(const QString& domain, const QString& email, const QString& password); | ||||
| 	void logoutClicked(); | ||||
| 	void loginStarted(); | ||||
| 	void loginFailed(); | ||||
| 	void loginSuccess(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| Dispatcher& dispatcher(); | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // DISPATCHER_H | ||||
| @@ -1,6 +0,0 @@ | ||||
| #include "enum.h" | ||||
|  | ||||
| enum::enum() | ||||
| { | ||||
|  | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| #ifndef ENUM_H | ||||
| #define ENUM_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| enum Table { UndefinedTable, FoldersTable, NotesTable, ChangesTable }; | ||||
|  | ||||
| // Note "DELETE" is a reserved keyword so we need to use "DEL" | ||||
| enum HttpMethod { UndefinedMethod, HEAD, GET, PUT, POST, DEL, PATCH }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // ENUM_H | ||||
| @@ -1,19 +0,0 @@ | ||||
| #include "filters.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| QString filters::apiBaseUrl(const QString &baseUrl) { | ||||
| 	QString output(baseUrl.trimmed()); | ||||
| 	if (!output.startsWith("http://") && !output.startsWith("https://")) { | ||||
| 		output = "http://" + output; | ||||
| 	} | ||||
| 	while (output.endsWith("/")) { | ||||
| 		output = output.left(output.length() - 1); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| QString filters::email(const QString &email) { | ||||
| 	QString output(email.trimmed()); | ||||
| 	return output; | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| #ifndef FILTERS_H | ||||
| #define FILTERS_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
| namespace filters { | ||||
|  | ||||
| QString apiBaseUrl(const QString& apiBaseUrl); | ||||
| QString email(const QString& email); | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // FILTERS_H | ||||
| @@ -1,62 +0,0 @@ | ||||
| #include "folderlistcontroller.h" | ||||
| #include "qmlutils.h" | ||||
|  | ||||
| #include "models/folder.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| FolderListController::FolderListController() : BaseItemListController() {} | ||||
|  | ||||
| void FolderListController::updateItemCount() { | ||||
| 	int itemCount = Folder::count(parentId()); | ||||
| 	qmlUtils::callQml(itemList(), "setItemCount", QVariantList() << itemCount); | ||||
| } | ||||
|  | ||||
| const BaseModel *FolderListController::cacheGet(int index) const { | ||||
| 	return cache_[index].get(); | ||||
| } | ||||
|  | ||||
| void FolderListController::cacheSet(int index, BaseModel *baseModel) const { | ||||
| 	Folder* folder = static_cast<Folder*>(baseModel); | ||||
| 	cache_[index] = std::unique_ptr<Folder>(folder); | ||||
| } | ||||
|  | ||||
| bool FolderListController::cacheIsset(int index) const { | ||||
| 	return index > 0 && cache_.size() > (size_t)index; | ||||
| } | ||||
|  | ||||
| void FolderListController::cacheClear() const { | ||||
| 	cache_.clear(); | ||||
| } | ||||
|  | ||||
| void FolderListController::itemList_rowsRequested(int fromIndex, int toIndex) { | ||||
| 	if (!cache_.size()) { | ||||
| 		qFatal("TODO: replace with root::children()"); | ||||
| 		//cache_ = Folder::all(parentId(), orderBy()); | ||||
| 	} | ||||
|  | ||||
| 	//qDebug() << cache_.size(); | ||||
|  | ||||
| 	if (fromIndex < 0 || (size_t)toIndex >= cache_.size() || !cache_.size()) { | ||||
| 		qWarning() << "Invalid folder indexes" << fromIndex << toIndex; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	QVariantList output; | ||||
| 	for (int i = fromIndex; i <= toIndex; i++) { | ||||
| 		const BaseModel* model = cacheGet(i); | ||||
| 		//qDebug() << model; | ||||
| 		//QVariant v(cacheGet(i)); | ||||
| 		QVariant v = QVariant::fromValue((QObject*)model); | ||||
| 		//qDebug() << v; | ||||
| 		output.push_back(v); | ||||
| 	} | ||||
|  | ||||
| 	QVariantList args; | ||||
| 	args.push_back(fromIndex); | ||||
| 	args.push_back(output); | ||||
|  | ||||
| 	qmlUtils::callQml(itemList(), "setItems", args); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| #ifndef ITEMLISTCONTROLLER_H | ||||
| #define ITEMLISTCONTROLLER_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/folder.h" | ||||
| #include "baseitemlistcontroller.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class FolderListController : public BaseItemListController { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	FolderListController(); | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	void updateItemCount(); | ||||
| 	const BaseModel* cacheGet(int index) const; | ||||
| 	void cacheSet(int index, BaseModel* baseModel) const; | ||||
| 	bool cacheIsset(int index) const; | ||||
| 	void cacheClear() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	mutable std::vector<std::unique_ptr<Folder>> cache_; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void itemList_rowsRequested(int fromIndex, int toIndex); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // ITEMLISTCONTROLLER_H | ||||
| @@ -1,46 +0,0 @@ | ||||
| #include "itemlist2.h" | ||||
|  | ||||
| ItemList2::ItemList2(QObject *parent) | ||||
|     : QAbstractItemModel(parent) | ||||
| { | ||||
| } | ||||
|  | ||||
| QVariant ItemList2::headerData(int section, Qt::Orientation orientation, int role) const | ||||
| { | ||||
| 	// FIXME: Implement me! | ||||
| } | ||||
|  | ||||
| QModelIndex ItemList2::index(int row, int column, const QModelIndex &parent) const | ||||
| { | ||||
| 	// FIXME: Implement me! | ||||
| } | ||||
|  | ||||
| QModelIndex ItemList2::parent(const QModelIndex &index) const | ||||
| { | ||||
| 	// FIXME: Implement me! | ||||
| } | ||||
|  | ||||
| int ItemList2::rowCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	if (!parent.isValid()) | ||||
| 		return 0; | ||||
|  | ||||
| 	// FIXME: Implement me! | ||||
| } | ||||
|  | ||||
| int ItemList2::columnCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	if (!parent.isValid()) | ||||
| 		return 0; | ||||
|  | ||||
| 	// FIXME: Implement me! | ||||
| } | ||||
|  | ||||
| QVariant ItemList2::data(const QModelIndex &index, int role) const | ||||
| { | ||||
| 	if (!index.isValid()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	// FIXME: Implement me! | ||||
| 	return QVariant(); | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| #ifndef ITEMLIST2_H | ||||
| #define ITEMLIST2_H | ||||
|  | ||||
| #include <QAbstractItemModel> | ||||
|  | ||||
| class ItemList2 : public QAbstractItemModel | ||||
| { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	explicit ItemList2(QObject *parent = 0); | ||||
|  | ||||
| 	// Header: | ||||
| 	QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; | ||||
|  | ||||
| 	// Basic functionality: | ||||
| 	QModelIndex index(int row, int column, | ||||
| 	                  const QModelIndex &parent = QModelIndex()) const override; | ||||
| 	QModelIndex parent(const QModelIndex &index) const override; | ||||
|  | ||||
| 	int rowCount(const QModelIndex &parent = QModelIndex()) const override; | ||||
| 	int columnCount(const QModelIndex &parent = QModelIndex()) const override; | ||||
|  | ||||
| 	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; | ||||
|  | ||||
| private: | ||||
| }; | ||||
|  | ||||
| #endif // ITEMLIST2_H | ||||
| @@ -1,43 +0,0 @@ | ||||
| #include <stable.h> | ||||
|  | ||||
| #if defined(JOP_FRONT_END_CLI) | ||||
| #include "cliapplication.h" | ||||
| #elif defined(JOP_FRONT_END_GUI) | ||||
| #include "application.h" | ||||
| #endif | ||||
|  | ||||
| #include "models/folder.h" | ||||
| #include "database.h" | ||||
| #include "models/foldermodel.h" | ||||
| #include "services/folderservice.h" | ||||
|  | ||||
| int main(int argc, char *argv[]) {	 | ||||
|  | ||||
| #if (!defined(JOP_FRONT_END_GUI) && !defined(JOP_FRONT_END_CLI)) | ||||
|     qFatal("Either JOP_FRONT_END_GUI or JOP_FRONT_END_CLI must be defined!"); | ||||
|     return 1; | ||||
| #endif | ||||
|  | ||||
| #if (defined(JOP_FRONT_END_GUI) && defined(JOP_FRONT_END_CLI)) | ||||
|     qFatal("JOP_FRONT_END_GUI and JOP_FRONT_END_CLI cannot both be defined!"); | ||||
|     return 1; | ||||
| #endif | ||||
|  | ||||
| #ifdef JOP_FRONT_END_GUI | ||||
| 	qDebug() << "Front end: GUI"; | ||||
| 	QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); | ||||
| 	jop::Application* app = new jop::Application(argc, argv); | ||||
| #endif | ||||
|  | ||||
| #ifdef JOP_FRONT_END_CLI | ||||
| 	qDebug() << "Front end: CLI"; | ||||
| 	jop::CliApplication* app = new jop::CliApplication(argc, argv); | ||||
| #endif | ||||
|  | ||||
|     int errorCode = app->exec(); | ||||
|  | ||||
|     delete app; | ||||
|     app = NULL; | ||||
|      | ||||
|     return errorCode; | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| SET PATH=%PATH%;"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" | ||||
|  | ||||
| cd "D:\Web\www\joplin\QtClient\build-evernote-import-qt-Visual_C_32_bites-Debug" | ||||
| if %errorlevel% neq 0 exit /b %errorlevel% | ||||
|  | ||||
| "C:\Qt\5.7\msvc2015\bin\qmake.exe" D:\Web\www\joplin\QtClient\evernote-import\evernote-import-qt.pro -spec win32-msvc2015 "CONFIG+=debug" "CONFIG+=qml_debug" | ||||
| if %errorlevel% neq 0 exit /b %errorlevel% | ||||
|  | ||||
| "C:\Qt\Tools\QtCreator\bin\jom.exe" qmake_all | ||||
| if %errorlevel% neq 0 exit /b %errorlevel% | ||||
|  | ||||
| "C:\Qt\Tools\QtCreator\bin\jom.exe"  | ||||
| if %errorlevel% neq 0 exit /b %errorlevel% | ||||
|  | ||||
|  | ||||
|  | ||||
| /cygdrive/c/Qt/Tools/QtCreator/bin/jom.exe | ||||
| @@ -1,140 +0,0 @@ | ||||
| #include "abstractlistmodel.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| AbstractListModel::AbstractListModel() : QAbstractListModel() { | ||||
| 	virtualItemShown_ = false; | ||||
| } | ||||
|  | ||||
| int AbstractListModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); | ||||
| 	return baseModelCount() + (virtualItemShown() ? 1 : 0); | ||||
| } | ||||
|  | ||||
| QVariant AbstractListModel::data(const QModelIndex & index, int role) const { | ||||
| 	const BaseModel* model = NULL; | ||||
|  | ||||
| 	if (virtualItemShown() && index.row() == rowCount() - 1) { | ||||
| 		if (role == Qt::DisplayRole) return "Untitled"; | ||||
| 		return ""; | ||||
| 	} else { | ||||
| 		model = atIndex(index.row()); | ||||
| 	} | ||||
|  | ||||
| 	if (role == Qt::DisplayRole) { | ||||
| 		return model->value("title").toQVariant(); | ||||
| 	} | ||||
|  | ||||
| 	if (role == IdRole) { | ||||
| 		return model->id().toQVariant(); | ||||
| 	} | ||||
|  | ||||
| 	return QVariant(); | ||||
| } | ||||
|  | ||||
| const BaseModel *AbstractListModel::atIndex(int index) const { | ||||
| 	Q_UNUSED(index); | ||||
| 	qFatal("AbstractListModel::atIndex() not implemented"); | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| const BaseModel* AbstractListModel::atIndex(const QModelIndex &index) const { | ||||
| 	return atIndex(index.row()); | ||||
| } | ||||
|  | ||||
| bool AbstractListModel::setData(const QModelIndex &index, const QVariant &value, int role) { | ||||
| 	const BaseModel* model = atIndex(index.row()); | ||||
| 	if (!model) return false; | ||||
|  | ||||
| 	if (role == TitleRole) { | ||||
| 		BaseModel temp; | ||||
| 		temp.clone(*model); | ||||
| 		temp.setValue("title", value.toString()); | ||||
| 		if (!temp.save()) return false; | ||||
| 		cacheClear(); | ||||
| 		return true; | ||||
|  | ||||
| //		model->setValue("title", value.toString()); | ||||
| //		if (!model->save()) return false; | ||||
| //		cacheClear(); | ||||
| //		return true; | ||||
| 	} | ||||
|  | ||||
| 	qWarning() << "Unsupported role" << role; | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool AbstractListModel::setData(int index, const QVariant &value, const QString& role) { | ||||
| 	return setData(this->index(index), value, roleNameToId(role)); | ||||
| } | ||||
|  | ||||
| int AbstractListModel::baseModelCount() const { | ||||
| 	qFatal("AbstractListModel::baseModelCount() not implemented"); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| const BaseModel *AbstractListModel::cacheGet(int index) const { | ||||
| 	Q_UNUSED(index); | ||||
| 	qFatal("AbstractListModel::cacheGet() not implemented"); | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| void AbstractListModel::cacheSet(int index, BaseModel* baseModel) const { | ||||
| 	Q_UNUSED(index); Q_UNUSED(baseModel); | ||||
| 	qFatal("AbstractListModel::cacheSet() not implemented"); | ||||
| } | ||||
|  | ||||
| bool AbstractListModel::cacheIsset(int index) const { | ||||
| 	Q_UNUSED(index); | ||||
| 	qFatal("AbstractListModel::cacheIsset() not implemented"); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void AbstractListModel::cacheClear() const { | ||||
| 	qFatal("AbstractListModel::cacheClear() not implemented"); | ||||
| } | ||||
|  | ||||
| void AbstractListModel::showVirtualItem() { | ||||
| 	virtualItemShown_ = true; | ||||
| 	beginInsertRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1); | ||||
| 	endInsertRows(); | ||||
| } | ||||
|  | ||||
| void AbstractListModel::hideVirtualItem() { | ||||
| 	beginRemoveRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1); | ||||
| 	virtualItemShown_ = false; | ||||
| 	endRemoveRows(); | ||||
| } | ||||
|  | ||||
| bool AbstractListModel::virtualItemShown() const { | ||||
| 	return virtualItemShown_; | ||||
| } | ||||
|  | ||||
| QHash<int, QByteArray> AbstractListModel::roleNames() const { | ||||
| 	QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); | ||||
| 	roles[TitleRole] = "title"; | ||||
| 	roles[IdRole] = "id"; | ||||
| 	return roles; | ||||
| } | ||||
|  | ||||
| int AbstractListModel::roleNameToId(const QString &name) const { | ||||
| 	QHash<int, QByteArray> roles = roleNames(); | ||||
| 	for (QHash<int, QByteArray>::const_iterator it = roles.begin(); it != roles.end(); ++it) { | ||||
| 		if (it.value() == name) return it.key(); | ||||
| 	} | ||||
| 	qCritical() << "Unknown role" << name; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| QString AbstractListModel::indexToId(int index) const { | ||||
| 	return data(this->index(index), IdRole).toString(); | ||||
| } | ||||
|  | ||||
| int AbstractListModel::idToIndex(const QString &id) const { | ||||
| 	Q_UNUSED(id); | ||||
| 	qFatal("AbstractListModel::idToIndex() not implemented"); | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| QString AbstractListModel::lastInsertId() const { | ||||
| 	return lastInsertId_; | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| #ifndef ABSTRACTLISTMODEL_H | ||||
| #define ABSTRACTLISTMODEL_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class AbstractListModel : public QAbstractListModel { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	enum ModelRoles { | ||||
| 		IdRole = Qt::UserRole + 1, | ||||
| 		TitleRole | ||||
| 	}; | ||||
|  | ||||
| 	AbstractListModel(); | ||||
| 	int rowCount(const QModelIndex & parent = QModelIndex()) const; | ||||
| 	QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; | ||||
| 	virtual const BaseModel* atIndex(int index) const; | ||||
| 	const BaseModel* atIndex(const QModelIndex &index) const; | ||||
| 	bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	QString lastInsertId_; | ||||
|  | ||||
| 	virtual int baseModelCount() const; | ||||
|  | ||||
| 	// All these methods are const because we want to be able to clear the | ||||
| 	// cache or set values from any method including const ones. | ||||
| 	// http://stackoverflow.com/a/4248661/561309 | ||||
| 	virtual const BaseModel* cacheGet(int index) const; | ||||
| 	virtual void cacheSet(int index, BaseModel* baseModel) const; | ||||
| 	virtual bool cacheIsset(int index) const; | ||||
| 	virtual void cacheClear() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	bool virtualItemShown_; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void showVirtualItem(); | ||||
| 	bool virtualItemShown() const; | ||||
| 	void hideVirtualItem(); | ||||
| 	QHash<int, QByteArray> roleNames() const; | ||||
| 	int roleNameToId(const QString& name) const; | ||||
| 	QString indexToId(int index) const; | ||||
| 	virtual int idToIndex(const QString& id) const; | ||||
| 	QString lastInsertId() const; | ||||
| 	bool setData(int index, const QVariant &value, const QString& role = "edit"); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // ABSTRACTLISTMODEL_H | ||||
| @@ -1,538 +0,0 @@ | ||||
| #include "basemodel.h" | ||||
|  | ||||
| #include "dispatcher.h" | ||||
| #include "models/change.h" | ||||
| #include "database.h" | ||||
| #include "uuid.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| QMap<int, QVector<BaseModel::Field>> BaseModel::tableFields_; | ||||
| QHash<QString, QVariant> BaseModel::cache_; | ||||
|  | ||||
| BaseModel::BaseModel() : isNew_(-1), table_(jop::UndefinedTable) {} | ||||
|  | ||||
| QStringList BaseModel::changedFields() const { | ||||
| 	QStringList output; | ||||
| 	for (QHash<QString, bool>::const_iterator it = changedFields_.begin(); it != changedFields_.end(); ++it) { | ||||
| 		output.push_back(it.key()); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| int BaseModel::count(Table table, const QString &parentId) { | ||||
| 	QString t = BaseModel::tableName(table); | ||||
| 	QString k = QString("%1:count").arg(t); | ||||
| 	QVariant r = BaseModel::cacheGet(k); | ||||
| 	if (r.isValid()) return r.toInt(); | ||||
|  | ||||
| 	QSqlQuery q = jop::db().prepare("SELECT count(*) AS row_count FROM " + t + " WHERE parent_id = :parent_id"); | ||||
| 	q.bindValue(":parent_id", parentId); | ||||
| 	jop::db().execQuery(q); | ||||
| 	q.next(); | ||||
| 	if (!jop::db().errorCheck(q)) return 0; | ||||
| 	int output = q.value(0).toInt(); | ||||
| 	BaseModel::cacheSet(k, QVariant(output)); | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| bool BaseModel::load(const QString &id) { | ||||
| 	QSqlQuery q(*jop::db().database()); | ||||
| 	q.prepare("SELECT " + BaseModel::sqlTableFields(table()) + " FROM " + BaseModel::tableName(table()) + " WHERE id = :id"); | ||||
| 	q.bindValue(":id", id); | ||||
| 	jop::db().execQuery(q); | ||||
| 	q.next(); | ||||
| 	if (!jop::db().errorCheck(q)) return false; | ||||
| 	if (!q.isValid()) return false; | ||||
|  | ||||
| 	loadSqlQuery(q); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BaseModel::reload() { | ||||
| 	return load(idString()); | ||||
| } | ||||
|  | ||||
| bool BaseModel::loadByField(const QString& parentId, const QString& field, const QString& fieldValue) { | ||||
| 	QSqlQuery q(*jop::db().database()); | ||||
| 	QString sql = QString("SELECT %1 FROM %2 WHERE `%3` = :field_value AND parent_id = :parent_id LIMIT 1") | ||||
| 	                     .arg(BaseModel::sqlTableFields(table())) | ||||
| 	                     .arg(BaseModel::tableName(table())) | ||||
| 	                     .arg(field); | ||||
| 	q.prepare(sql); | ||||
| 	q.bindValue(":parent_id", parentId); | ||||
| 	q.bindValue(":field_value", fieldValue); | ||||
| 	jop::db().execQuery(q); | ||||
| 	q.next(); | ||||
| 	if (!jop::db().errorCheck(q)) return false; | ||||
| 	if (!q.isValid()) return false; | ||||
|  | ||||
| 	loadSqlQuery(q); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BaseModel::save(bool trackChanges) { | ||||
| 	bool isNew = this->isNew(); | ||||
|  | ||||
| 	if (!changedFields_.size() && !isNew) return true; | ||||
|  | ||||
| 	QStringList fields = changedFields(); | ||||
|  | ||||
| 	QMap<QString, QVariant> values; | ||||
|  | ||||
| 	foreach (QString field, fields) { | ||||
| 		values[field] = value(field).toQVariant(); | ||||
| 	} | ||||
|  | ||||
| 	// If it's a new entry and the ID is a UUID, we need to create this | ||||
| 	// ID now. If the ID is an INT, it will be automatically set by | ||||
| 	// SQLite. | ||||
| 	if (isNew && primaryKeyIsUuid() && !valueIsSet(primaryKey())) { | ||||
| 		values[primaryKey()] = uuid::createUuid(); | ||||
| 	} | ||||
|  | ||||
| 	// Update created_time and updated_time if needed. If updated_time | ||||
| 	// has already been updated (maybe manually by the user), don't | ||||
| 	// automatically update it. | ||||
| 	if (isNew) { | ||||
| 		if (BaseModel::hasField(table(), "created_time")) { | ||||
| 			values["created_time"] = (int)(QDateTime::currentMSecsSinceEpoch() / 1000); | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (!values.contains("updated_time")) { | ||||
| 			if (BaseModel::hasField(table(), "updated_time")) { | ||||
| 				values["updated_time"] = (int)(QDateTime::currentMSecsSinceEpoch() / 1000); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	changedFields_.clear(); | ||||
|  | ||||
| 	const QString& tableName = BaseModel::tableName(table()); | ||||
|  | ||||
| 	if (isNew) { | ||||
| 		cacheDelete(QString("%1:count").arg(tableName)); | ||||
| 	} | ||||
|  | ||||
| 	bool isSaved = false; | ||||
|  | ||||
| 	jop::db().transaction(); | ||||
|  | ||||
| 	if (isNew) { | ||||
| 		QSqlQuery q = jop::db().buildSqlQuery(Database::Insert, tableName, values); | ||||
| 		jop::db().execQuery(q); | ||||
| 		isSaved = jop::db().errorCheck(q); | ||||
| 		if (isSaved) setValue("id", values["id"]); | ||||
| 	} else { | ||||
| 		QSqlQuery q = jop::db().buildSqlQuery(Database::Update, tableName, values, QString("%1 = '%2'").arg(primaryKey()).arg(value("id").toString())); | ||||
| 		jop::db().execQuery(q); | ||||
| 		isSaved = jop::db().errorCheck(q); | ||||
| 	} | ||||
|  | ||||
| 	if (isSaved && this->trackChanges() && trackChanges) { | ||||
| 		if (isNew) { | ||||
| 			Change change; | ||||
| 			change.setValue("item_id", id()); | ||||
| 			change.setValue("item_type", table()); | ||||
| 			change.setValue("type", Change::Create); | ||||
| 			change.save(); | ||||
| 		} else { | ||||
| 			for (QMap<QString, QVariant>::const_iterator it = values.begin(); it != values.end(); ++it) { | ||||
| 				Change change; | ||||
| 				change.setValue("item_id", id()); | ||||
| 				change.setValue("item_type", table()); | ||||
| 				change.setValue("type", Change::Update); | ||||
| 				change.setValue("item_field", it.key()); | ||||
| 				change.save(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	jop::db().commit(); | ||||
|  | ||||
| 	if (isSaved) { | ||||
| 		if (table() == jop::FoldersTable) { | ||||
| 			if (isNew) { | ||||
| 				dispatcher().emitFolderCreated(idString()); | ||||
| 			} else { | ||||
| 				dispatcher().emitFolderUpdated(idString()); | ||||
| 			} | ||||
| 		} | ||||
| 		if (table() == jop::NotesTable) { | ||||
| 			if (isNew) { | ||||
| 				dispatcher().emitNoteCreated(idString()); | ||||
| 			} else { | ||||
| 				dispatcher().emitNoteUpdated(idString()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	isNew_ = -1; | ||||
|  | ||||
| 	return isSaved; | ||||
| } | ||||
|  | ||||
| bool BaseModel::dispose() { | ||||
| 	const QString& tableName = BaseModel::tableName(table()); | ||||
| 	QSqlQuery q(*jop::db().database()); | ||||
| 	q.prepare("DELETE FROM " + tableName + " WHERE " + primaryKey() + " = :id"); | ||||
| 	q.bindValue(":id", id().toString()); | ||||
| 	jop::db().execQuery(q); | ||||
|  | ||||
| 	bool isDeleted = jop::db().errorCheck(q); | ||||
|  | ||||
| 	if (isDeleted) cacheDelete(QString("%1:count").arg(tableName)); | ||||
|  | ||||
| 	if (isDeleted && trackChanges()) { | ||||
| 		Change change; | ||||
| 		change.setValue("item_id", id()); | ||||
| 		change.setValue("item_type", table()); | ||||
| 		change.setValue("type", Change::Delete); | ||||
| 		change.save(); | ||||
| 	} | ||||
|  | ||||
| 	if (isDeleted) { | ||||
| 		if (table() == jop::FoldersTable) dispatcher().emitFolderDeleted(idString()); | ||||
| 		if (table() == jop::NotesTable) dispatcher().emitNoteDeleted(idString()); | ||||
| 	} | ||||
|  | ||||
| 	return isDeleted; | ||||
| } | ||||
|  | ||||
| Table BaseModel::table() const { | ||||
| 	return table_; | ||||
| } | ||||
|  | ||||
| QString BaseModel::primaryKey() const { | ||||
| 	return "id"; | ||||
| } | ||||
|  | ||||
| bool BaseModel::primaryKeyIsUuid() const { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool BaseModel::trackChanges() const { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| QString BaseModel::displayTitle() const { | ||||
| 	return value("title").toString(); | ||||
| } | ||||
|  | ||||
| bool BaseModel::isNew() const { | ||||
| 	if (isNew_ == 0) return false; | ||||
| 	if (isNew_ == 1) return true; | ||||
| 	return !valueIsSet(primaryKey()); | ||||
| } | ||||
|  | ||||
| BaseModel::Field createField(const QString& name, QMetaType::Type type) { | ||||
| 	BaseModel::Field c; | ||||
| 	c.name = name; | ||||
| 	c.type = type; | ||||
| 	return c; | ||||
| } | ||||
|  | ||||
| QVector<BaseModel::Field> BaseModel::tableFields(jop::Table table) { | ||||
| 	if (BaseModel::tableFields_.contains(table)) return BaseModel::tableFields_[table]; | ||||
|  | ||||
| 	QVector<BaseModel::Field> output; | ||||
|  | ||||
| 	// TODO: ideally that should be auto-generated based on schema.sql | ||||
|  | ||||
| 	if (table == jop::FoldersTable) { | ||||
| 		output.push_back(createField("id", QMetaType::QString )); | ||||
| 		output.push_back(createField("title", QMetaType::QString )); | ||||
| 		output.push_back(createField("created_time", QMetaType::Int )); | ||||
| 		output.push_back(createField("updated_time", QMetaType::Int )); | ||||
| 	} else if (table == jop::NotesTable) { | ||||
| 		output.push_back(createField("id", QMetaType::QString )); | ||||
| 		output.push_back(createField("title", QMetaType::QString )); | ||||
| 		output.push_back(createField("body", QMetaType::QString )); | ||||
| 		output.push_back(createField("parent_id", QMetaType::QString )); | ||||
| 		output.push_back(createField("created_time", QMetaType::Int )); | ||||
| 		output.push_back(createField("updated_time", QMetaType::Int )); | ||||
| 		output.push_back(createField("latitude", QMetaType::QString )); | ||||
| 		output.push_back(createField("longitude", QMetaType::QString )); | ||||
| 		output.push_back(createField("altitude", QMetaType::QString )); | ||||
| 		output.push_back(createField("source", QMetaType::QString )); | ||||
| 		output.push_back(createField("author", QMetaType::QString )); | ||||
| 		output.push_back(createField("source_url", QMetaType::QString )); | ||||
| 		output.push_back(createField("is_todo", QMetaType::Int )); | ||||
| 		output.push_back(createField("todo_due", QMetaType::Int )); | ||||
| 		output.push_back(createField("todo_completed", QMetaType::Int )); | ||||
| 		output.push_back(createField("source_application", QMetaType::QString )); | ||||
| 		output.push_back(createField("application_data", QMetaType::QString )); | ||||
| 		output.push_back(createField("order", QMetaType::Int )); | ||||
| 	} else if (table == jop::ChangesTable) { | ||||
| 		output.push_back(createField("id", QMetaType::Int )); | ||||
| 		output.push_back(createField("type", QMetaType::Int )); | ||||
| 		output.push_back(createField("item_id", QMetaType::QString )); | ||||
| 		output.push_back(createField("item_type", QMetaType::Int )); | ||||
| 		output.push_back(createField("item_field", QMetaType::QString )); | ||||
| 	} else { | ||||
| 		qFatal("Field not defined for table %d", table); | ||||
| 	} | ||||
|  | ||||
| 	BaseModel::tableFields_[table] = output; | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| bool BaseModel::hasField(jop::Table table, const QString &name) { | ||||
| 	QVector<BaseModel::Field> fields = tableFields(table); | ||||
| 	foreach (Field field, fields) { | ||||
| 		if (field.name == name) return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| QStringList BaseModel::tableFieldNames(Table table) { | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table); | ||||
| 	QStringList output; | ||||
| 	foreach (BaseModel::Field field, fields) { | ||||
| 		output.push_back(field.name); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| QString BaseModel::sqlTableFields(Table table) { | ||||
| 	QString output = ""; | ||||
| 	QStringList fields = BaseModel::tableFieldNames(table); | ||||
| 	for (int i = 0; i < fields.size(); i++) { | ||||
| 		if (output != "") output += ","; | ||||
| 		output += QString("`%1`").arg(fields[i]); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| bool BaseModel::isValidFieldName(Table table, const QString &name) { | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table); | ||||
| 	foreach (BaseModel::Field col, fields) { | ||||
| 		if (col.name == name) return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void BaseModel::deleteAll(Table table) { | ||||
| 	QString tableName = BaseModel::tableName(table); | ||||
| 	jop::db().execQuery("DELETE FROM " + tableName); | ||||
| 	BaseModel::cache_.clear(); | ||||
|  | ||||
| 	if (table == jop::FoldersTable) { | ||||
| 		dispatcher().emitAllFoldersDeleted(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // When loading a QSqlQuery, all the values are cleared and replaced by those | ||||
| // from the QSqlQuery. All the fields are marked as NOT changed as it's assumed | ||||
| // the object is already in the database (since loaded from there). | ||||
| void BaseModel::loadSqlQuery(const QSqlQuery &query) { | ||||
| 	values_.clear(); | ||||
| 	QSqlRecord record = query.record(); | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table()); | ||||
|  | ||||
| 	foreach (BaseModel::Field field, fields) { | ||||
| 		int idx = record.indexOf(field.name); | ||||
| 		if (idx < 0) { | ||||
| 			qCritical() << "Cannot find field" << field.name; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if (field.type == QMetaType::QString) { | ||||
| 			setValue(field.name, query.value(idx).toString()); | ||||
| 		} else if (field.type == QMetaType::Int) { | ||||
| 			setValue(field.name, query.value(idx).toInt()); | ||||
| 		} else { | ||||
| 			qCritical() << "Unsupported value type" << field.name; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	isNew_ = -1; | ||||
|  | ||||
| 	changedFields_.clear(); | ||||
| } | ||||
|  | ||||
| // When loading a QJsonObject, all the values are cleared and replaced by those | ||||
| // from the QJsonObject. All the fields are marked as changed since it's | ||||
| // assumed that the object comes from the web service. | ||||
| void BaseModel::loadJsonObject(const QJsonObject &jsonObject) { | ||||
| 	values_.clear(); | ||||
| 	changedFields_.clear(); | ||||
|  | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table()); | ||||
|  | ||||
| 	foreach (BaseModel::Field field, fields) { | ||||
| 		setValue(field.name, jsonObject[field.name], field.type); | ||||
| 	} | ||||
|  | ||||
| 	isNew_ = 1; | ||||
| } | ||||
|  | ||||
| void BaseModel::patchJsonObject(const QJsonObject &jsonObject) { | ||||
| 	QVector<BaseModel::Field> fields = BaseModel::tableFields(table()); | ||||
|  | ||||
| 	foreach (BaseModel::Field field, fields) { | ||||
| 		if (!jsonObject.contains(field.name)) continue; | ||||
| 		setValue(field.name, jsonObject[field.name], field.type); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QHash<QString, BaseModel::Value> BaseModel::values() const { | ||||
| 	return values_; | ||||
| } | ||||
|  | ||||
| BaseModel::Value BaseModel::value(const QString &name) const { | ||||
| 	if (!valueIsSet(name)) { | ||||
| 		qCritical() << "Value does not exist" << name; | ||||
| 		return Value(); | ||||
| 	} | ||||
| 	return values_[name]; | ||||
| } | ||||
|  | ||||
| bool BaseModel::valueIsSet(const QString &name) const { | ||||
| 	return values_.contains(name); | ||||
| } | ||||
|  | ||||
| void BaseModel::setValue(const QString &name, const BaseModel::Value &value) { | ||||
| 	if (!values_.contains(name)) { | ||||
| 		values_.insert(name, value); | ||||
| 		changedFields_.insert(name, true); | ||||
| 	} else { | ||||
| 		Value& v = values_[name]; | ||||
| 		if (v.isEqual(value)) return; | ||||
| 		values_.insert(name, value); | ||||
| 		changedFields_.insert(name, true); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void BaseModel::setValue(const QString &name, int value) { | ||||
| 	setValue(name, Value(value)); | ||||
| } | ||||
|  | ||||
| void BaseModel::setValue(const QString &name, const QJsonValue &value, QMetaType::Type type) { | ||||
| 	if (type == QMetaType::QString) { | ||||
| 		setValue(name, value.toString()); | ||||
| 	} else if (type == QMetaType::Int) { | ||||
| 		setValue(name, value.toInt()); | ||||
| 	} else { | ||||
| 		qFatal("Unsupported value type %s %d", name.toStdString().c_str(), type); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //void BaseModel::setValues(const QHash<QString, BaseModel::Value> values) { | ||||
| //	values_ = values; | ||||
| //} | ||||
|  | ||||
| BaseModel::Value BaseModel::id() const { | ||||
| 	if (!valueIsSet(primaryKey())) return QVariant(); | ||||
| 	return value(primaryKey()); | ||||
| } | ||||
|  | ||||
| QString BaseModel::idString() const { | ||||
| 	return id().toString(); | ||||
| } | ||||
|  | ||||
| QString BaseModel::valuesToString() const { | ||||
| 	QString s; | ||||
| 	for (QHash<QString, Value>::const_iterator it = values_.begin(); it != values_.end(); ++it) { | ||||
| 		if (s != "") s += "\n"; | ||||
| 		s += it.key() + " = " + it.value().toString(); | ||||
| 	} | ||||
| 	return s; | ||||
| } | ||||
|  | ||||
| void BaseModel::clone(const BaseModel &baseModel) { | ||||
| 	values_ = baseModel.values_; | ||||
| 	changedFields_.clear(); | ||||
| 	isNew_ = false; | ||||
| 	table_ = baseModel.table_; | ||||
| } | ||||
|  | ||||
| QString BaseModel::tableName(Table t) { | ||||
| 	if (t == jop::FoldersTable) return "folders"; | ||||
| 	if (t == jop::NotesTable) return "notes"; | ||||
| 	if (t == jop::ChangesTable) return "changes"; | ||||
| 	qFatal("Unknown table %d", t); | ||||
| } | ||||
|  | ||||
| QVariant BaseModel::cacheGet(const QString &key) { | ||||
| 	if (!BaseModel::cache_.contains(key)) return QVariant(); | ||||
| 	return cache_[key]; | ||||
| } | ||||
|  | ||||
| void BaseModel::cacheSet(const QString &key, const QVariant &value) { | ||||
| 	BaseModel::cache_[key] = value; | ||||
| } | ||||
|  | ||||
| void BaseModel::cacheDelete(const QString &key) { | ||||
| 	BaseModel::cache_.remove(key); | ||||
| } | ||||
|  | ||||
| QString BaseModel::title() const { | ||||
| 	return value("title").toString(); | ||||
| } | ||||
|  | ||||
| void BaseModel::setValue(const QString &name, const QString &value) { | ||||
| 	setValue(name, Value(value)); | ||||
| } | ||||
|  | ||||
| void BaseModel::setValue(const QString& name, const QVariant& value) { | ||||
| 	setValue(name, Value(value)); | ||||
| } | ||||
|  | ||||
| BaseModel::Value::Value() {} | ||||
|  | ||||
| BaseModel::Value::Value(const QString &v) { | ||||
| 	type_ = QMetaType::QString; | ||||
| 	stringValue_ = v; | ||||
| } | ||||
|  | ||||
| BaseModel::Value::Value(int v) { | ||||
| 	type_ = QMetaType::Int; | ||||
| 	intValue_ = v; | ||||
| } | ||||
|  | ||||
| BaseModel::Value::Value(const QVariant &v) { | ||||
| 	type_ = (QMetaType::Type)v.type(); | ||||
| 	if (type_ == QMetaType::QString) { | ||||
| 		stringValue_ = v.toString(); | ||||
| 	} else if (type_ == QMetaType::Int) { | ||||
| 		intValue_ = v.toInt(); | ||||
| 	} else { | ||||
| 		// Creates an invalid Value, which is what we want | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int BaseModel::Value::toInt() const { | ||||
| 	return intValue_; | ||||
| } | ||||
|  | ||||
| QString BaseModel::Value::toString() const { | ||||
| 	if (type_ == QMetaType::QString) return stringValue_; | ||||
| 	if (type_ == QMetaType::Int) return QString::number(intValue_); | ||||
| 	return QString(""); | ||||
| } | ||||
|  | ||||
| QVariant BaseModel::Value::toQVariant() const { | ||||
| 	QMetaType::Type t = type(); | ||||
| 	if (t == QMetaType::QString) return QVariant(toString()); | ||||
| 	if (t == QMetaType::Int) return QVariant(toInt()); | ||||
| 	return QVariant(); | ||||
| } | ||||
|  | ||||
| QMetaType::Type BaseModel::Value::type() const { | ||||
| 	return type_; | ||||
| } | ||||
|  | ||||
| bool BaseModel::Value::isValid() const { | ||||
| 	return type_ > 0; | ||||
| } | ||||
|  | ||||
| bool BaseModel::Value::isEqual(const BaseModel::Value &v) const { | ||||
| 	QMetaType::Type type = v.type(); | ||||
| 	if (this->type() != type) return false; | ||||
| 	if (type == QMetaType::QString) return toString() == v.toString(); | ||||
| 	if (type == QMetaType::Int) return toInt() == v.toInt(); | ||||
|  | ||||
| 	qCritical() << "Unreachable"; | ||||
| 	return false; | ||||
| } | ||||
| @@ -1,112 +0,0 @@ | ||||
| #ifndef BASEMODEL_H | ||||
| #define BASEMODEL_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "enum.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class BaseModel : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| 	Q_PROPERTY(QString title READ title) | ||||
| 	Q_PROPERTY(QString id READ idString) | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	struct Field { | ||||
| 		QString name; | ||||
| 		QMetaType::Type type; | ||||
| 	}; | ||||
|  | ||||
| 	class Value { | ||||
|  | ||||
| 	public: | ||||
|  | ||||
| 		Value(); | ||||
| 		Value(const QString& v); | ||||
| 		Value(int v); | ||||
| 		Value(const QVariant& v); | ||||
| 		int toInt() const; | ||||
| 		QString toString() const; | ||||
| 		QVariant toQVariant() const; | ||||
| 		QMetaType::Type type() const; | ||||
| 		bool isValid() const; | ||||
| 		bool isEqual(const Value& v) const; | ||||
|  | ||||
| 	private: | ||||
|  | ||||
| 		QMetaType::Type type_; | ||||
| 		QString stringValue_; | ||||
| 		int intValue_; | ||||
|  | ||||
| 	}; | ||||
|  | ||||
| 	BaseModel(); | ||||
| 	QStringList changedFields() const; | ||||
| 	static int count(jop::Table table, const QString &parentId); | ||||
| 	bool load(const QString& id); | ||||
| 	bool loadByField(const QString& parentId, const QString& field, const QString& fieldValue); | ||||
| 	bool reload(); | ||||
| 	virtual bool save(bool trackChanges = true); | ||||
| 	virtual bool dispose(); | ||||
|  | ||||
| 	Table table() const; | ||||
| 	virtual QString primaryKey() const; | ||||
| 	virtual bool primaryKeyIsUuid() const; | ||||
| 	virtual bool trackChanges() const; | ||||
| 	virtual QString displayTitle() const; | ||||
|  | ||||
| 	bool isNew() const; | ||||
|  | ||||
| 	static QVector<BaseModel::Field> tableFields(Table table); | ||||
| 	static bool hasField(jop::Table table, const QString& name); | ||||
| 	static QStringList tableFieldNames(Table table); | ||||
| 	static QString sqlTableFields(Table table); | ||||
| 	static bool isValidFieldName(Table table, const QString& name); | ||||
| 	static void deleteAll(Table table); | ||||
|  | ||||
| 	void loadSqlQuery(const QSqlQuery& query); | ||||
| 	void loadJsonObject(const QJsonObject& jsonObject); | ||||
| 	void patchJsonObject(const QJsonObject& jsonObject); | ||||
| 	QHash<QString, Value> values() const; | ||||
| 	Value value(const QString& name) const; | ||||
| 	bool valueIsSet(const QString& name) const; | ||||
| 	void setValue(const QString& name, const Value& value); | ||||
| 	void setValue(const QString& name, const QVariant& value); | ||||
| 	void setValue(const QString& name, const QString& value); | ||||
| 	void setValue(const QString& name, int value); | ||||
| 	void setValue(const QString& name, const QJsonValue& value, QMetaType::Type type); | ||||
| 	//void setValues(const QHash<QString, Value> values); | ||||
| 	Value id() const; | ||||
| 	QString valuesToString() const; | ||||
| 	void clone(const BaseModel& baseModel); | ||||
|  | ||||
| 	static QString tableName(Table t); | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	QHash<QString, bool> changedFields_; | ||||
| 	QHash<QString, Value> values_; | ||||
| 	int isNew_; | ||||
| 	jop::Table table_; | ||||
|  | ||||
| 	static QVariant cacheGet(const QString& key); | ||||
| 	static void cacheSet(const QString& key, const QVariant& value); | ||||
| 	static void cacheDelete(const QString& key); | ||||
| 	static QMap<int, QVector<BaseModel::Field>> tableFields_; | ||||
| 	static QHash<QString, QVariant> cache_; | ||||
|  | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	QString title() const; | ||||
| 	QString idString() const; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // BASEMODEL_H | ||||
| @@ -1,120 +0,0 @@ | ||||
| #include "change.h" | ||||
| #include "database.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| //Table Change::table() const { | ||||
| //	return jop::ChangesTable; | ||||
| //} | ||||
|  | ||||
| Change::Change() : BaseModel() { | ||||
| 	table_ = jop::ChangesTable; | ||||
| } | ||||
|  | ||||
| std::vector<Change*> Change::all(int limit) { | ||||
| 	QString sql = QString("SELECT %1 FROM %2 ORDER BY id ASC LIMIT %3") | ||||
| 	        .arg(BaseModel::tableFieldNames(jop::ChangesTable).join(",")) | ||||
| 	        .arg(BaseModel::tableName(jop::ChangesTable)) | ||||
| 	        .arg(QString::number(limit)); | ||||
|  | ||||
| 	QSqlQuery q(sql); | ||||
| 	jop::db().execQuery(q); | ||||
|  | ||||
| 	std::vector<Change*> output; | ||||
|  | ||||
| 	while (q.next()) { | ||||
| 		Change* change(new Change()); | ||||
| 		change->loadSqlQuery(q); | ||||
| 		output.push_back(change); | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| void Change::mergedChanges(std::vector<Change*>& changes) { | ||||
| 	QStringList createdItems; | ||||
| 	QStringList deletedItems; | ||||
| 	QHash<QString, Change*> itemChanges; | ||||
|  | ||||
| 	for (size_t i = 0; i < changes.size(); i++) { | ||||
| 		Change* change = changes[i]; | ||||
|  | ||||
| 		QString itemId = change->value("item_id").toString(); | ||||
| 		Change::Type type = (Change::Type)change->value("type").toInt(); | ||||
|  | ||||
| 		if (type == Change::Create) { | ||||
| 			createdItems.push_back(itemId); | ||||
| 		} else if (type == Change::Delete) { | ||||
| 			deletedItems.push_back(itemId); | ||||
| 		} | ||||
|  | ||||
| 		if (itemChanges.contains(itemId) && type == Change::Update) { | ||||
| 			// Merge all the "Update" event into one. | ||||
| 			Change* existingChange = itemChanges[itemId]; | ||||
| 			existingChange->addMergedField(change->value("item_field").toString()); | ||||
| 		} else { | ||||
| 			itemChanges[itemId] = change; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::vector<Change*> output; | ||||
|  | ||||
| 	for (QHash<QString, Change*>::iterator it = itemChanges.begin(); it != itemChanges.end(); ++it) { | ||||
| 		QString itemId = it.key(); | ||||
| 		Change* change = it.value(); | ||||
|  | ||||
| 		if (createdItems.contains(itemId) && deletedItems.contains(itemId)) { | ||||
| 			// Item both created then deleted - skip | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if (deletedItems.contains(itemId)) { | ||||
| 			// Item was deleted at some point - just return one 'delete' event | ||||
| 			change->setValue("type", Change::Delete); | ||||
| 		} else if (createdItems.contains(itemId)) { | ||||
| 			// Item was created then updated - just return one 'create' event with the latest changes | ||||
| 			change->setValue("type", Change::Create); | ||||
| 		} | ||||
|  | ||||
| 		output.push_back(change); | ||||
| 	} | ||||
|  | ||||
| 	// Delete the changes that are now longer needed (have been merged) | ||||
| 	for (size_t i = 0; i < changes.size(); i++) { | ||||
| 		Change* c1 = changes[i]; | ||||
| 		bool found = false; | ||||
| 		for (size_t j = 0; j < output.size(); j++) { | ||||
| 			Change* c2 = output[i]; | ||||
| 			if (c1 == c2) { | ||||
| 				found = true; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if (!found) { | ||||
| 			delete c1; c1 = NULL; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	changes = output; | ||||
| } | ||||
|  | ||||
| void Change::addMergedField(const QString &name) { | ||||
| 	if (mergedFields_.contains(name)) return; | ||||
| 	mergedFields_.push_back(name); | ||||
| } | ||||
|  | ||||
| QStringList Change::mergedFields() const { | ||||
| 	QStringList output(mergedFields_); | ||||
| 	QString itemField = value("item_field").toString(); | ||||
| 	if (!mergedFields_.contains(itemField)) { | ||||
| 		output.push_back(itemField); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| void Change::disposeByItemId(const QString &itemId) { | ||||
| 	QString sql = QString("DELETE FROM %1 WHERE item_id = :item_id").arg(BaseModel::tableName(jop::ChangesTable)); | ||||
| 	QSqlQuery q = jop::db().prepare(sql); | ||||
| 	q.bindValue(":item_id", itemId); | ||||
| 	jop::db().execQuery(q); | ||||
| } | ||||
| @@ -1,32 +0,0 @@ | ||||
| #ifndef CHANGE_H | ||||
| #define CHANGE_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Change : public BaseModel { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	enum Type { Undefined, Create, Update, Delete }; | ||||
|  | ||||
| 	Change(); | ||||
| 	static std::vector<Change*> all(int limit = 100); | ||||
| 	static void mergedChanges(std::vector<Change*> &changes); | ||||
| 	static void disposeByItemId(const QString& itemId); | ||||
|  | ||||
| 	void addMergedField(const QString& name); | ||||
| 	QStringList mergedFields() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QStringList mergedFields_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // CHANGE_H | ||||
| @@ -1,171 +0,0 @@ | ||||
| #include "models/folder.h" | ||||
| #include "database.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| Folder::Folder() : Item() { | ||||
| 	table_ = jop::FoldersTable; | ||||
| } | ||||
|  | ||||
| //Table Folder::table() const { | ||||
| //	return jop::FoldersTable; | ||||
| //} | ||||
|  | ||||
| bool Folder::primaryKeyIsUuid() const { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool Folder::trackChanges() const { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| int Folder::noteCount() const { | ||||
| 	QSqlQuery q = jop::db().prepare(QString("SELECT count(*) AS row_count FROM %1 WHERE parent_id = :parent_id").arg(BaseModel::tableName(jop::NotesTable))); | ||||
| 	q.bindValue(":parent_id", id().toString()); | ||||
| 	jop::db().execQuery(q); | ||||
| 	q.next(); | ||||
| 	return q.value(0).toInt(); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Folder> Folder::root() { | ||||
| 	std::unique_ptr<Folder> folder(new Folder()); | ||||
| 	return std::move(folder); | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<BaseModel>> Folder::children(const QString &orderBy, int limit, int offset) const { | ||||
| 	std::vector<std::unique_ptr<BaseModel>> output; | ||||
|  | ||||
| 	std::vector<jop::Table> tables; | ||||
| 	tables.push_back(jop::FoldersTable); | ||||
| 	tables.push_back(jop::NotesTable); | ||||
| 	for (size_t tableIndex = 0; tableIndex < tables.size(); tableIndex++) { | ||||
| 		jop::Table table = tables[tableIndex]; | ||||
|  | ||||
| 		QString sql = QString("SELECT %1 FROM %2 WHERE parent_id = :parent_id ORDER BY %3 %4 %5") | ||||
|                             .arg(BaseModel::sqlTableFields(table)) | ||||
| 	                        .arg(BaseModel::tableName(table)) | ||||
| 	                        .arg(orderBy) | ||||
| 	                        .arg(limit ? QString("LIMIT %1").arg(limit) : "") | ||||
| 	                        .arg(limit && offset ? QString("OFFSET %1").arg(offset) : ""); | ||||
|  | ||||
| 		QSqlQuery q = jop::db().prepare(sql); | ||||
|  | ||||
| 		q.bindValue(":parent_id", idString()); | ||||
| 		jop::db().execQuery(q); | ||||
| 		if (!jop::db().errorCheck(q)) return output; | ||||
|  | ||||
| 		while (q.next()) { | ||||
| 			if (table == jop::FoldersTable) { | ||||
| 				std::unique_ptr<BaseModel> folder(new Folder()); | ||||
| 				folder->loadSqlQuery(q); | ||||
| 				output.push_back(std::move(folder)); | ||||
| 			} else if (table == jop::NotesTable) { | ||||
| 				std::unique_ptr<BaseModel> note(new Note()); | ||||
| 				note->loadSqlQuery(q); | ||||
| 				output.push_back(std::move(note)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Note>> Folder::notes(const QString &orderBy, int limit, int offset) const { | ||||
| 	std::vector<std::unique_ptr<Note>> output; | ||||
|  | ||||
| 	QSqlQuery q = jop::db().prepare(QString("SELECT %1 FROM %2 WHERE parent_id = :parent_id ORDER BY %3 LIMIT %4 OFFSET %5") | ||||
| 	                        .arg(BaseModel::sqlTableFields(jop::NotesTable)) | ||||
| 	                        .arg(BaseModel::tableName(jop::NotesTable)) | ||||
| 	                        .arg(orderBy) | ||||
| 	                        .arg(limit) | ||||
| 	                        .arg(offset)); | ||||
| 	q.bindValue(":parent_id", id().toString()); | ||||
| 	jop::db().execQuery(q); | ||||
| 	if (!jop::db().errorCheck(q)) return output; | ||||
|  | ||||
| 	while (q.next()) { | ||||
| 		std::unique_ptr<Note> note(new Note()); | ||||
| 		note->loadSqlQuery(q); | ||||
| 		output.push_back(std::move(note)); | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Folder>> Folder::pathToFolders(const QString& path, bool returnLast, int& errorCode) { | ||||
| 	std::vector<std::unique_ptr<Folder>> output; | ||||
| 	if (!path.length()) return output; | ||||
|  | ||||
| 	QStringList parts = path.split('/'); | ||||
| 	QString parentId(""); | ||||
| 	int toIndex = returnLast ? parts.size() : parts.size() - 1; | ||||
| 	for (int i = 0; i < toIndex; i++) { | ||||
| 		std::unique_ptr<Folder> folder(new Folder()); | ||||
| 		bool ok = folder->loadByField(parentId, "title", parts[i]); | ||||
| 		if (!ok) { | ||||
| 			// qWarning() << "Folder does not exist" << parts[i]; | ||||
| 			errorCode = 1; | ||||
| 			return output; | ||||
| 		} | ||||
| 		output.push_back(std::move(folder)); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| QString Folder::pathBaseName(const QString& path) { | ||||
| 	QStringList parts = path.split('/'); | ||||
| 	return parts[parts.size() - 1]; | ||||
| } | ||||
|  | ||||
| int Folder::noteIndexById(const QString &orderBy, const QString& id) const { | ||||
| 	qDebug() << "Folder::noteIndexById" << orderBy << id; | ||||
|  | ||||
| 	QSqlQuery q = jop::db().prepare(QString("SELECT id, %2 FROM %1 WHERE parent_id = :parent_id ORDER BY %2") | ||||
| 	                        .arg(BaseModel::tableName(jop::NotesTable)) | ||||
| 	                        .arg(orderBy)); | ||||
| 	q.bindValue(":parent_id", idString()); | ||||
| 	jop::db().execQuery(q); | ||||
| 	if (!jop::db().errorCheck(q)) return -1; | ||||
|  | ||||
| 	int index = 0; | ||||
| 	while (q.next()) { | ||||
| 		QString qId = q.value(0).toString(); | ||||
| 		QString qTitle = q.value(1).toString(); | ||||
| 		qDebug() << "CURRENT" << qId << qTitle; | ||||
| 		if (qId == id) return index; | ||||
| 		index++; | ||||
| 	} | ||||
|  | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| int Folder::count(const QString &parentId) { | ||||
| 	return BaseModel::count(jop::FoldersTable, parentId); | ||||
| } | ||||
|  | ||||
| // std::vector<std::unique_ptr<Folder>> Folder::all(const QString& parentId, const QString &orderBy) { | ||||
| // 	QSqlQuery q = jop::db().prepare(QString("SELECT %1 FROM %2 WHERE parent_id = :parent_id ORDER BY %3") | ||||
| // 	        .arg(BaseModel::tableFieldNames(jop::FoldersTable).join(",")) | ||||
| // 	        .arg(BaseModel::tableName(jop::FoldersTable)) | ||||
| // 	        .arg(orderBy)); | ||||
| // 	q.bindValue(":parent_id", parentId); | ||||
| // 	jop::db().execQuery(q); | ||||
|  | ||||
| // 	std::vector<std::unique_ptr<Folder>> output; | ||||
|  | ||||
| // 	//if (!jop::db().errorCheck(q)) return output; | ||||
|  | ||||
| // 	while (q.next()) { | ||||
| // 		std::unique_ptr<Folder> folder(new Folder()); | ||||
| // 		folder->loadSqlQuery(q); | ||||
| // 		output.push_back(std::move(folder)); | ||||
| // 	} | ||||
|  | ||||
| // 	return output; | ||||
| // } | ||||
|  | ||||
| QString Folder::displayTitle() const { | ||||
| 	return QString("%1/").arg(value("title").toString()); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| #ifndef FOLDER_H | ||||
| #define FOLDER_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "models/item.h" | ||||
| #include "models/note.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Folder : public Item { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Folder(); | ||||
|  | ||||
| 	static int count(const QString& parentId); | ||||
| 	static std::vector<std::unique_ptr<Folder>> pathToFolders(const QString& path, bool returnLast, int& errorCode); | ||||
| 	static QString pathBaseName(const QString& path); | ||||
| 	static std::unique_ptr<Folder> root(); | ||||
|  | ||||
| 	bool primaryKeyIsUuid() const; | ||||
| 	bool trackChanges() const; | ||||
| 	int noteCount() const; | ||||
| 	std::vector<std::unique_ptr<Note>> notes(const QString& orderBy, int limit, int offset = 0) const; | ||||
| 	std::vector<std::unique_ptr<BaseModel>> children(const QString &orderBy = QString("title"), int limit = 0, int offset = 0) const; | ||||
| 	int noteIndexById(const QString& orderBy, const QString &id) const; | ||||
| 	QString displayTitle() const; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // FOLDER_H | ||||
| @@ -1,106 +0,0 @@ | ||||
| #include "foldercollection.h" | ||||
| #include "databaseutils.h" | ||||
| #include "dispatcher.h" | ||||
| #include "uuid.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| // Note: although parentId is supplied, it is currently not being used. | ||||
| FolderCollection::FolderCollection(Database& db, const QString& parentId, const QString& orderBy) { | ||||
| 	db_ = db; | ||||
| 	parentId_ = parentId; | ||||
| 	orderBy_ = orderBy; | ||||
|  | ||||
| 	connect(&jop::dispatcher(), SIGNAL(folderCreated(const QString&)), this, SLOT(dispatcher_folderCreated(QString))); | ||||
| } | ||||
|  | ||||
| Folder FolderCollection::at(int index) const { | ||||
| 	if (cache_.size()) { | ||||
| 		if (index < 0 || index >= cache_.size()) { | ||||
| 			qWarning() << "Invalid folder index:" << index; | ||||
| 			return Folder(); | ||||
| 		} | ||||
|  | ||||
| 		return cache_[index]; | ||||
| 	} | ||||
|  | ||||
| 	QSqlQuery q = db_.query("SELECT " + Folder::dbFields().join(",") + " FROM folders ORDER BY " + orderBy_); | ||||
| 	q.exec(); | ||||
|  | ||||
| 	while (q.next()) { | ||||
| 		Folder folder; | ||||
| 		folder.fromSqlQuery(q); | ||||
| 		cache_.push_back(folder); | ||||
| 	} | ||||
|  | ||||
| 	if (!cache_.size()) { | ||||
| 		qWarning() << "Invalid folder index:" << index; | ||||
| 		return Folder(); | ||||
| 	} else { | ||||
| 		return at(index); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TODO: cache result | ||||
| int FolderCollection::count() const { | ||||
| 	QSqlQuery q = db_.query("SELECT count(*) as row_count FROM folders"); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
| 	return q.value(0).toInt(); | ||||
| } | ||||
|  | ||||
| Folder FolderCollection::byId(const QString& id) const { | ||||
| 	int index = idToIndex(id); | ||||
| 	return at(index); | ||||
| } | ||||
|  | ||||
| int FolderCollection::idToIndex(const QString &id) const { | ||||
| 	int count = this->count(); | ||||
| 	for (int i = 0; i < count; i++) { | ||||
| 		Folder folder = at(i); | ||||
| 		if (folder.id() == id) return i; | ||||
| 	} | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| QString FolderCollection::indexToId(int index) const { | ||||
| 	Folder folder = at(index); | ||||
| 	return folder.id(); | ||||
| } | ||||
|  | ||||
| void FolderCollection::update(const QString &id, QStringList fields, VariantVector values) { | ||||
| 	if (!fields.contains("synced")) { | ||||
| 		fields.push_back("synced"); | ||||
| 		values.push_back(QVariant(0)); | ||||
| 	} | ||||
| 	QSqlQuery q = db_.buildSqlQuery(Database::Update, "folders", fields, values, "id = \"" + id + "\""); | ||||
| 	q.exec(); | ||||
| 	cache_.clear(); | ||||
| 	emit changed(0, count() - 1, fields); | ||||
| } | ||||
|  | ||||
| void FolderCollection::add(QStringList fields, VariantVector values) { | ||||
| 	fields.push_back("synced"); | ||||
| 	values.push_back(QVariant(0)); | ||||
|  | ||||
| 	fields.push_back("id"); | ||||
| 	values.push_back(uuid::createUuid()); | ||||
|  | ||||
| 	QSqlQuery q = db_.buildSqlQuery(Database::Insert, "folders", fields, values); | ||||
| 	q.exec(); | ||||
| 	cache_.clear(); | ||||
| 	emit changed(0, count() - 1, fields); | ||||
| } | ||||
|  | ||||
| void FolderCollection::remove(const QString& id) { | ||||
| 	QSqlQuery q(db_.database()); | ||||
| 	q.prepare("DELETE FROM folders WHERE id = :id"); | ||||
| 	q.bindValue(":id", id); | ||||
| 	q.exec(); | ||||
| 	cache_.clear(); | ||||
| 	emit changed(0, count(), QStringList()); | ||||
| } | ||||
|  | ||||
| void FolderCollection::dispatcher_folderCreated(const QString &id) { | ||||
|  | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| #ifndef FOLDERCOLLECTION_H | ||||
| #define FOLDERCOLLECTION_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "database.h" | ||||
| #include "models/note.h" | ||||
| #include "models/folder.h" | ||||
| #include "sparsevector.hpp" | ||||
| #include "simpletypes.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class FolderCollection : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	//FolderCollection(); | ||||
| 	FolderCollection(Database& db, const QString &parentId, const QString& orderBy); | ||||
| 	Folder at(int index) const; | ||||
| 	int count() const; | ||||
| 	Folder byId(const QString &id) const; | ||||
| 	int idToIndex(const QString& id) const; | ||||
| 	QString indexToId(int index) const; | ||||
| 	void update(const QString& id, QStringList fields, VariantVector values); | ||||
| 	void add(QStringList fields, VariantVector values); | ||||
| 	void remove(const QString &id); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QString parentId_; | ||||
| 	QString orderBy_; | ||||
| 	Database db_; | ||||
| 	mutable QVector<Folder> cache_; | ||||
|  | ||||
| signals: | ||||
|  | ||||
| 	void changed(int from, int to, const QStringList& fields); | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void dispatcher_folderCreated(const QString& id); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // FOLDERCOLLECTION_H | ||||
| @@ -1,139 +0,0 @@ | ||||
| #include "foldermodel.h" | ||||
| #include "uuid.h" | ||||
| #include "dispatcher.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| FolderModel::FolderModel() : AbstractListModel(), orderBy_("title") { | ||||
| 	connect(&dispatcher(), SIGNAL(folderCreated(QString)), this, SLOT(dispatcher_folderCreated(QString))); | ||||
| 	connect(&dispatcher(), SIGNAL(folderUpdated(QString)), this, SLOT(dispatcher_folderUpdated(QString))); | ||||
| 	connect(&dispatcher(), SIGNAL(folderDeleted(QString)), this, SLOT(dispatcher_folderDeleted(QString))); | ||||
| 	connect(&dispatcher(), SIGNAL(allFoldersDeleted()), this, SLOT(dispatcher_allFoldersDeleted())); | ||||
| } | ||||
|  | ||||
| const BaseModel *FolderModel::atIndex(int index) const { | ||||
| 	if (cache_.size()) { | ||||
| 		if (index < 0 || index >= (int)cache_.size()) { | ||||
| 			qWarning() << "Invalid folder index:" << index; | ||||
| 			return NULL; | ||||
| 		} | ||||
|  | ||||
| 		return cacheGet(index); | ||||
| 	} | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	qFatal("TODO: replace with root::children()"); | ||||
| 	//cache_ = Folder::all(orderBy_); | ||||
|  | ||||
| 	if (!cache_.size()) { | ||||
| 		qWarning() << "Invalid folder index:" << index; | ||||
| 		return NULL; | ||||
| 	} else { | ||||
| 		return atIndex(index); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int FolderModel::idToIndex(const QString &id) const { | ||||
| 	int count = this->rowCount(); | ||||
| 	for (int i = 0; i < count; i++) { | ||||
| 		Folder* folder = (Folder*)atIndex(i); | ||||
| 		if (!folder) return -1; | ||||
| 		if (folder->idString() == id) return i; | ||||
| 	} | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| //bool FolderModel::setTitle(int index, const QVariant &value, int role) { | ||||
| //	return setData(this->index(index), value, role); | ||||
| //} | ||||
|  | ||||
| //bool FolderModel::setData(int index, const QVariant &value, int role) { | ||||
| //	return BaseModel::setData(this->index(index), value, role); | ||||
| //} | ||||
|  | ||||
| void FolderModel::addData(const QString &title) { | ||||
| 	Folder folder; | ||||
| 	folder.setValue("title", title); | ||||
| 	if (!folder.save()) return; | ||||
|  | ||||
| 	lastInsertId_ = folder.id().toString(); | ||||
| } | ||||
|  | ||||
| void FolderModel::deleteData(const int index) { | ||||
| 	Folder* folder = (Folder*)atIndex(index); | ||||
| 	if (!folder) return; | ||||
| 	folder->dispose(); | ||||
| } | ||||
|  | ||||
| int FolderModel::baseModelCount() const { | ||||
| 	return Folder::count(""); | ||||
| } | ||||
|  | ||||
| const BaseModel *FolderModel::cacheGet(int index) const { | ||||
| 	return cache_[index].get(); | ||||
| } | ||||
|  | ||||
| void FolderModel::cacheSet(int index, BaseModel* baseModel) const { | ||||
| 	Folder* folder = static_cast<Folder*>(baseModel); | ||||
| 	cache_[index] = std::unique_ptr<Folder>(folder); | ||||
| } | ||||
|  | ||||
| bool FolderModel::cacheIsset(int index) const { | ||||
| 	return index > 0 && (int)cache_.size() > index; | ||||
| } | ||||
|  | ||||
| void FolderModel::cacheClear() const { | ||||
| 	cache_.clear(); | ||||
| } | ||||
|  | ||||
| // TODO: instead of clearing the whole cache every time, the individual items | ||||
| // could be created/updated/deleted | ||||
|  | ||||
| void FolderModel::dispatcher_folderCreated(const QString &folderId) { | ||||
| 	qDebug() << "FolderModel Folder created" << folderId; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	int from = 0; | ||||
| 	int to = rowCount() - 1; | ||||
|  | ||||
| 	QVector<int> roles; | ||||
| 	roles << Qt::DisplayRole; | ||||
|  | ||||
| 	// Necessary to make sure a new item is added to the view, even | ||||
| 	// though it might not be positioned there due to sorting | ||||
| 	beginInsertRows(QModelIndex(), to, to); | ||||
| 	endInsertRows(); | ||||
|  | ||||
| 	emit dataChanged(this->index(from), this->index(to), roles); | ||||
| } | ||||
|  | ||||
| void FolderModel::dispatcher_folderUpdated(const QString &folderId) { | ||||
| 	qDebug() << "FolderModel Folder udpated" << folderId; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	QVector<int> roles; | ||||
| 	roles << Qt::DisplayRole; | ||||
| 	emit dataChanged(this->index(0), this->index(rowCount() - 1), roles); | ||||
| } | ||||
|  | ||||
| void FolderModel::dispatcher_folderDeleted(const QString &folderId) { | ||||
| 	qDebug() << "FolderModel Folder deleted" << folderId; | ||||
|  | ||||
| 	int index = idToIndex(folderId); | ||||
| 	if (index < 0) return; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	beginRemoveRows(QModelIndex(), index, index); | ||||
| 	endRemoveRows(); | ||||
| } | ||||
|  | ||||
| void FolderModel::dispatcher_allFoldersDeleted() { | ||||
| 	qDebug() << "FolderModel All folders deleted"; | ||||
| 	cacheClear(); | ||||
| 	beginResetModel(); | ||||
| 	endResetModel(); | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| #ifndef FOLDERMODEL_H | ||||
| #define FOLDERMODEL_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/folder.h" | ||||
| #include "models/abstractlistmodel.h" | ||||
| #include "database.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class FolderModel : public AbstractListModel { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	FolderModel(); | ||||
| 	void addFolder(Folder* folder); | ||||
| 	const BaseModel* atIndex(int index) const; | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	int baseModelCount() const; | ||||
| 	const BaseModel *cacheGet(int index) const; | ||||
| 	void cacheSet(int index, BaseModel *baseModel) const; | ||||
| 	bool cacheIsset(int index) const; | ||||
| 	void cacheClear() const; | ||||
| 	int cacheSize() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QList<Folder> folders_; | ||||
| 	QString orderBy_; | ||||
| 	mutable std::vector<std::unique_ptr<Folder>> cache_; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void addData(const QString& title); | ||||
| 	void deleteData(const int index); | ||||
| 	int idToIndex(const QString& id) const; | ||||
|  | ||||
| 	void dispatcher_folderCreated(const QString& folderId); | ||||
| 	void dispatcher_folderUpdated(const QString& folderId); | ||||
| 	void dispatcher_folderDeleted(const QString& folderId); | ||||
| 	void dispatcher_allFoldersDeleted(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // FOLDERMODEL_H | ||||
| @@ -1,61 +0,0 @@ | ||||
| #include "models/item.h" | ||||
| #include "constants.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| Item::Item() {} | ||||
|  | ||||
| QString Item::serialize() const { | ||||
| 	QStringList shownKeys; | ||||
| 	shownKeys << "author" << "longitude" << "latitude" << "is_todo" << "todo_due" << "todo_completed"; | ||||
|  | ||||
| 	QStringList output; | ||||
| 	output << value("title").toString(); | ||||
| 	output << ""; | ||||
| 	output << value("body").toString(); | ||||
| 	output << "================================================================================"; | ||||
| 	QHash<QString, Value> values = this->values(); | ||||
| 	for (int i = 0; i < shownKeys.size(); i++) { | ||||
| 		QString key = shownKeys[i]; | ||||
| 		if (!values.contains(key)) continue; | ||||
| 		output << QString("%1: %2").arg(key).arg(values[key].toString()); | ||||
| 	} | ||||
| 	return output.join(NEW_LINE); | ||||
| } | ||||
|  | ||||
| void Item::patchFriendlyString(const QString& patch) { | ||||
| 	QStringList lines = patch.split(jop::NEW_LINE); | ||||
|  | ||||
| 	QString title(""); | ||||
| 	if (lines.size() >= 1) { | ||||
| 		title = lines[0]; | ||||
| 	} | ||||
|  | ||||
| 	bool foundDelimiter = false; | ||||
| 	QString body(""); | ||||
| 	for (int i = 1; i < lines.size(); i++) { | ||||
| 		QString line = lines[i]; | ||||
|  | ||||
| 		if (line.indexOf("================================================================================") == 0) { | ||||
| 			foundDelimiter = true; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if (!foundDelimiter && line.trimmed() == "" && i == 1) continue; // Skip the first \n | ||||
|  | ||||
| 		if (!foundDelimiter) { | ||||
| 			if (!body.isEmpty()) body += "\n"; | ||||
| 			body += line; | ||||
| 		} else { | ||||
| 			int colonIndex = line.indexOf(':'); | ||||
| 			QString propName = line.left(colonIndex).trimmed(); | ||||
| 			QString propValue = line.right(line.length() - colonIndex - 1).trimmed(); | ||||
| 			setValue(propName, propValue); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	setValue("title", title); | ||||
| 	setValue("body", body); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| #ifndef ITEM_H | ||||
| #define ITEM_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Item : public BaseModel { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Item(); | ||||
| 	QString serialize() const; | ||||
| 	void patchFriendlyString(const QString& patch); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // ITEM_H | ||||
| @@ -1,19 +0,0 @@ | ||||
| #include "note.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Note::Note() : Item() { | ||||
| 	table_ = jop::NotesTable; | ||||
| } | ||||
|  | ||||
| //Table Note::table() const { | ||||
| //	return jop::NotesTable; | ||||
| //} | ||||
|  | ||||
| bool Note::primaryKeyIsUuid() const { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool Note::trackChanges() const { | ||||
| 	return true; | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| #ifndef NOTE_H | ||||
| #define NOTE_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "models/item.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Note : public Item { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Note(); | ||||
| 	bool primaryKeyIsUuid() const; | ||||
| 	bool trackChanges() const; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // NOTE_H | ||||
| @@ -1,83 +0,0 @@ | ||||
| #include "notecollection.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| NoteCollection::NoteCollection() {} | ||||
|  | ||||
| NoteCollection::NoteCollection(Database& db, const QString& parentId, const QString& orderBy) { | ||||
| 	db_ = db; | ||||
| 	parentId_ = parentId; | ||||
| 	orderBy_ = orderBy; | ||||
| } | ||||
|  | ||||
| Note NoteCollection::at(int index) const { | ||||
| 	return Note(); | ||||
| //	if (parentId_ == "") return Note(); | ||||
|  | ||||
| //	if (cache_.isset(index)) return cache_.get(index); | ||||
|  | ||||
| //	std::vector<int> indexes = cache_.availableBufferAround(index, 32); | ||||
| //	if (!indexes.size()) { | ||||
| //		qWarning() << "Couldn't acquire buffer"; // "Cannot happen" | ||||
| //		return Note(); | ||||
| //	} | ||||
|  | ||||
| //	int from = indexes[0]; | ||||
| //	int to = indexes[indexes.size() - 1]; | ||||
|  | ||||
| //	QSqlQuery q = db_.query("SELECT id, title, body FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy_ + " LIMIT " + QString::number(to - from + 1) + " OFFSET " + QString::number(from)); | ||||
| //	q.bindValue(":parent_id", parentId_); | ||||
| //	q.exec(); | ||||
|  | ||||
| //	int noteIndex = from; | ||||
| //	while (q.next()) { | ||||
| //		Note note; | ||||
| //		note.setId(q.value(0).toString()); | ||||
| //		note.setTitle(q.value(1).toString()); | ||||
| //		note.setBody(q.value(2).toString()); | ||||
|  | ||||
| //		cache_.set(noteIndex, note); | ||||
|  | ||||
| //		noteIndex++; | ||||
| //	} | ||||
|  | ||||
| //	return cache_.get(index); | ||||
| } | ||||
|  | ||||
| // TODO: cache result | ||||
| int NoteCollection::count() const { | ||||
| 	return 0; | ||||
| //	if (parentId_ == "") return 0; | ||||
|  | ||||
| //	QSqlQuery q = db_.query("SELECT count(*) as row_count FROM notes WHERE parent_id = :parent_id"); | ||||
| //	q.bindValue(":parent_id", parentId_); | ||||
| //	q.exec(); | ||||
| //	q.next(); | ||||
| //	return q.value(0).toInt(); | ||||
| } | ||||
|  | ||||
| Note NoteCollection::byId(const QString& id) const { | ||||
| 	return Note(); | ||||
| //	std::vector<int> indexes = cache_.indexes(); | ||||
| //	for (size_t i = 0; i < indexes.size(); i++) { | ||||
| //		Note note = cache_.get(indexes[i]); | ||||
| //		if (note.id() == id) return note; | ||||
| //	} | ||||
|  | ||||
| //	QSqlQuery q = db_.query("SELECT id, title, body FROM notes WHERE id = :id"); | ||||
| //	q.bindValue(":id", id); | ||||
| //	q.exec(); | ||||
| //	q.next(); | ||||
| //	if (!q.isValid()) { | ||||
| //		qWarning() << "Invalid note ID:" << id; | ||||
| //		return Note(); | ||||
| //	} | ||||
|  | ||||
| //	// TODO: refactor creation of note from SQL query object | ||||
| //	Note note; | ||||
| //	note.setId(q.value(0).toString()); | ||||
| //	note.setTitle(q.value(1).toString()); | ||||
| //	note.setBody(q.value(2).toString()); | ||||
| 	//	return note; | ||||
| } | ||||
|  | ||||
| @@ -1,33 +0,0 @@ | ||||
| #ifndef NOTECOLLECTION_H | ||||
| #define NOTECOLLECTION_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "database.h" | ||||
| #include "models/note.h" | ||||
| #include "sparsevector.hpp" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class NoteCollection { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	NoteCollection(); | ||||
| 	NoteCollection(Database& db, const QString &parentId, const QString& orderBy); | ||||
| 	Note at(int index) const; | ||||
| 	int count() const; | ||||
| 	Note byId(const QString &id) const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QString parentId_; | ||||
| 	QString orderBy_; | ||||
| 	Database db_; | ||||
| 	mutable SparseVector<Note> cache_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // NOTECOLLECTION_H | ||||
| @@ -1,149 +0,0 @@ | ||||
| #include "notemodel.h" | ||||
| #include "dispatcher.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| NoteModel::NoteModel() : AbstractListModel() { | ||||
| 	folderId_ = ""; | ||||
| 	orderBy_ = "title"; | ||||
|  | ||||
| 	connect(&dispatcher(), SIGNAL(noteCreated(QString)), this, SLOT(dispatcher_noteCreated(QString)), Qt::QueuedConnection); | ||||
| 	connect(&dispatcher(), SIGNAL(noteUpdated(QString)), this, SLOT(dispatcher_noteUpdated(QString)), Qt::QueuedConnection); | ||||
| 	connect(&dispatcher(), SIGNAL(noteDeleted(QString)), this, SLOT(dispatcher_noteDeleted(QString)), Qt::QueuedConnection); | ||||
| } | ||||
|  | ||||
| const Note *NoteModel::atIndex(int index) const { | ||||
| 	if (folderId_ == "") return NULL; | ||||
| 	if (index < 0 || index >= rowCount()) return NULL; | ||||
| 	if (cache_.isset(index)) return cache_.get(index); | ||||
|  | ||||
| 	std::vector<int> indexes = cache_.availableBufferAround(index, 32); | ||||
| 	if (!indexes.size()) { | ||||
| 		qCritical() << "Couldn't acquire buffer"; // "Cannot happen" | ||||
| 		return NULL; | ||||
| 	} | ||||
|  | ||||
| 	int from = indexes[0]; | ||||
| 	int to = indexes[indexes.size() - 1]; | ||||
|  | ||||
| //	Folder folder = this->folder(); | ||||
|  | ||||
| //	qDebug() << "NoteModel: cache recreated"; | ||||
| //	std::vector<std::unique_ptr<Note>> notes = folder.notes(orderBy_, to - from + 1, from); | ||||
| //	int noteIndex = from; | ||||
| //	for (int i = 0; i < notes.size(); i++) { | ||||
| //		cache_.set(noteIndex, notes[i].release()); | ||||
| //		noteIndex++; | ||||
| //	} | ||||
|  | ||||
| 	return cache_.get(index); | ||||
| } | ||||
|  | ||||
| void NoteModel::setFolderId(const QString &v) { | ||||
| 	if (v == folderId_) return; | ||||
| 	beginResetModel(); | ||||
| 	cache_.clear(); | ||||
| 	folderId_ = v; | ||||
| 	endResetModel(); | ||||
| } | ||||
|  | ||||
| //Folder NoteModel::folder() const { | ||||
| //	Folder folder; | ||||
| //	//if (folderId_ == "") return folder; | ||||
| //	folder.load(folderId_); | ||||
| //	return folder; | ||||
| //} | ||||
|  | ||||
| int NoteModel::idToIndex(const QString &id) const { | ||||
| 	std::vector<int> indexes = cache_.indexes(); | ||||
| 	for (size_t i = 0; i < indexes.size(); i++) { | ||||
| 		Note* note = cache_.get(indexes[i]); | ||||
| 		if (note->idString() == id) return indexes[i]; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
|  | ||||
| 	//Folder f = this->folder(); | ||||
| 	//return f.noteIndexById(orderBy_, id); | ||||
| } | ||||
|  | ||||
| void NoteModel::addData(const QString &title) { | ||||
| 	Note note; | ||||
| 	note.setValue("title", title); | ||||
| 	note.setValue("parent_id", folderId_); | ||||
| 	if (!note.save()) return; | ||||
|  | ||||
| 	lastInsertId_ = note.idString(); | ||||
| } | ||||
|  | ||||
| void NoteModel::deleteData(int index) { | ||||
| 	Note* note = (Note*)atIndex(index); | ||||
| 	if (!note) return; | ||||
| 	note->dispose(); | ||||
| } | ||||
|  | ||||
| int NoteModel::baseModelCount() const { | ||||
| 	return 0; | ||||
| 	//return folder().noteCount(); | ||||
| } | ||||
|  | ||||
| const BaseModel *NoteModel::cacheGet(int index) const { | ||||
| 	return static_cast<BaseModel*>(cache_.get(index)); | ||||
| } | ||||
|  | ||||
| void NoteModel::cacheSet(int index, BaseModel *baseModel) const { | ||||
| 	cache_.set(index, static_cast<Note*>(baseModel)); | ||||
| } | ||||
|  | ||||
| bool NoteModel::cacheIsset(int index) const { | ||||
| 	return cache_.isset(index); | ||||
| } | ||||
|  | ||||
| void NoteModel::cacheClear() const { | ||||
| 	qDebug() << "NoteModel::cacheClear()"; | ||||
| 	cache_.clear(); | ||||
| } | ||||
|  | ||||
| void NoteModel::dispatcher_noteCreated(const QString ¬eId) { | ||||
| 	qDebug() << "NoteModel note created" << noteId; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	int from = 0; | ||||
| 	int to = rowCount() - 1; | ||||
|  | ||||
| 	QVector<int> roles; | ||||
| 	roles << Qt::DisplayRole; | ||||
|  | ||||
| 	// Necessary to make sure a new item is added to the view, even | ||||
| 	// though it might not be positioned there due to sorting | ||||
| 	beginInsertRows(QModelIndex(), to, to); | ||||
| 	endInsertRows(); | ||||
|  | ||||
| 	emit dataChanged(this->index(from), this->index(to), roles); | ||||
| } | ||||
|  | ||||
| void NoteModel::dispatcher_noteUpdated(const QString ¬eId) { | ||||
| 	qDebug() << "NoteModel note udpated" << noteId; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	QVector<int> roles; | ||||
| 	roles << Qt::DisplayRole; | ||||
| 	emit dataChanged(this->index(0), this->index(rowCount() - 1), roles); | ||||
| } | ||||
|  | ||||
| void NoteModel::dispatcher_noteDeleted(const QString ¬eId) { | ||||
| 	qDebug() << "NoteModel note deleted" << noteId; | ||||
|  | ||||
| 	int index = idToIndex(noteId); | ||||
| 	qDebug() << "index" << index; | ||||
| 	if (index < 0) return; | ||||
|  | ||||
| 	cacheClear(); | ||||
|  | ||||
| 	beginRemoveRows(QModelIndex(), index, index); | ||||
| 	endRemoveRows(); | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,51 +0,0 @@ | ||||
| #ifndef NOTEMODEL_H | ||||
| #define NOTEMODEL_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/folder.h" | ||||
| #include "sparsevector.hpp" | ||||
| #include "models/abstractlistmodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class NoteModel : public AbstractListModel { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	NoteModel(); | ||||
| 	const Note* atIndex(int index) const; | ||||
| 	void setFolderId(const QString& v); | ||||
| 	//Folder folder() const; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	int idToIndex(const QString& id) const; | ||||
| 	void addData(const QString& title); | ||||
| 	void deleteData(int index); | ||||
| 	void dispatcher_noteCreated(const QString& noteId); | ||||
| 	void dispatcher_noteUpdated(const QString& noteId); | ||||
| 	void dispatcher_noteDeleted(const QString& noteId); | ||||
|  | ||||
| protected: | ||||
|  | ||||
| 	int baseModelCount() const; | ||||
| 	const BaseModel* cacheGet(int index) const; | ||||
| 	void cacheSet(int index, BaseModel *baseModel) const; | ||||
| 	bool cacheIsset(int index) const; | ||||
| 	void cacheClear() const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QList<Note> notes_; | ||||
| 	QString folderId_; | ||||
| 	QString orderBy_; | ||||
| 	mutable SparseVector<Note> cache_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // NOTEMODEL_H | ||||
| @@ -1,38 +0,0 @@ | ||||
| #include "setting.h" | ||||
|  | ||||
| #include "database.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| void Setting::setSettings(const QSettings::SettingsMap &map) { | ||||
| 	jop::db().transaction(); | ||||
| 	jop::db().execQuery("DELETE FROM settings"); | ||||
| 	QString sql = "INSERT INTO settings (`key`, `value`, `type`) VALUES (:key, :value, :type)"; | ||||
| 	QSqlQuery query = jop::db().prepare(sql); | ||||
| 	for (QSettings::SettingsMap::const_iterator it = map.begin(); it != map.end(); ++it) { | ||||
| 		query.bindValue(":key", it.key()); | ||||
| 		query.bindValue(":value", it.value()); | ||||
| 		query.bindValue(":type", (int)it.value().type()); | ||||
| 		jop::db().execQuery(query); | ||||
| 	} | ||||
| 	jop::db().commit(); | ||||
| } | ||||
|  | ||||
| QSettings::SettingsMap Setting::settings() { | ||||
| 	QSettings::SettingsMap output; | ||||
| 	QSqlQuery query("SELECT key, value, type FROM settings"); | ||||
| 	jop::db().execQuery(query); | ||||
| 	while (query.next()) { | ||||
| 		QString key = query.value(0).toString(); | ||||
| 		QVariant val = query.value(1); | ||||
| 		QMetaType::Type type = (QMetaType::Type)query.value(2).toInt(); | ||||
| 		if (type == QMetaType::Int) { | ||||
| 			output[key] = QVariant(val.toInt()); | ||||
| 		} else if (type == QMetaType::QString) { | ||||
| 			output[key] = QVariant(val.toString()); | ||||
| 		} else { | ||||
| 			qCritical() << "Unsupported setting type" << key << val << type; | ||||
| 		} | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| #ifndef SETTING_H | ||||
| #define SETTING_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "models/basemodel.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Setting : public BaseModel { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	static void setSettings(const QSettings::SettingsMap &map); | ||||
| 	static QSettings::SettingsMap settings(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // SETTING_H | ||||
| @@ -1,31 +0,0 @@ | ||||
| #include "paths.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| QString configDir_ = ""; | ||||
|  | ||||
| QString paths::configDir() { | ||||
| 	if (configDir_ != "") return configDir_; | ||||
|  | ||||
| 	configDir_ = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + QCoreApplication::applicationName(); | ||||
| 	QDir d(configDir_); | ||||
| 	if (!d.exists()) { | ||||
| 		bool dirCreated = d.mkpath("."); | ||||
| 		if (!dirCreated) qFatal("Cannot create config directory: %s", configDir_.toStdString().c_str()); | ||||
| 	} | ||||
| 	return configDir_; | ||||
| } | ||||
|  | ||||
| QString paths::databaseFile() { | ||||
| 	return QString("%1/%2.sqlite").arg(configDir()).arg(QCoreApplication::applicationName()); | ||||
| } | ||||
|  | ||||
| QString paths::noteDraftsDir() { | ||||
| 	QString output = QString("%1/note_drafts").arg(paths::configDir()); | ||||
| 	QDir d(output); | ||||
| 	if (!d.exists()) { | ||||
| 		bool dirCreated = d.mkpath("."); | ||||
| 		if (!dirCreated) qFatal("Cannot create note draft directory: %s", output.toStdString().c_str()); | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| #ifndef PATHS_H | ||||
| #define PATHS_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
| namespace paths { | ||||
|  | ||||
| QString configDir(); | ||||
| QString databaseFile(); | ||||
| QString noteDraftsDir(); | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // PATHS_H | ||||
| @@ -1,13 +0,0 @@ | ||||
| <RCC> | ||||
|     <qresource prefix="/"> | ||||
|         <file>app.qml</file> | ||||
|         <file>ItemList.qml</file> | ||||
|         <file>NoteEditor.qml</file> | ||||
|         <file>AddButton.qml</file> | ||||
|         <file>EditableListItem.qml</file> | ||||
|         <file>LoginPage.qml</file> | ||||
|         <file>LoginPageForm.ui.qml</file> | ||||
|         <file>MainPage.qml</file> | ||||
|         <file>ItemList2.qml</file> | ||||
|     </qresource> | ||||
| </RCC> | ||||
| @@ -1,33 +0,0 @@ | ||||
| #include "qmlutils.h" | ||||
|  | ||||
| namespace jop { | ||||
| namespace qmlUtils { | ||||
|  | ||||
| QVariant callQml(QObject* o, const QString &name, const QVariantList &args) { | ||||
| 	QVariant returnedValue; | ||||
| 	//qDebug() << "Going to call QML:" << name << args; | ||||
| 	if (args.size() == 0) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue)); | ||||
| 	} else if (args.size() == 1) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0])); | ||||
| 	} else if (args.size() == 2) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1])); | ||||
| 	} else if (args.size() == 3) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1]), Q_ARG(QVariant, args[2])); | ||||
| 	} else { | ||||
| 		qFatal("qmlUtils::callQml: add support for more args!"); | ||||
| 	} | ||||
| 	return returnedValue; | ||||
| } | ||||
|  | ||||
| QObject* childFromProperty(QObject *o, const QString &propertyName) { | ||||
| 	QVariant p = QQmlProperty(o, propertyName).read(); | ||||
| 	if (!p.isValid()) { | ||||
| 		qCritical() << "Invalid QML property" << propertyName; | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return qvariant_cast<QObject*>(p); | ||||
| } | ||||
|  | ||||
| } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| #ifndef QMLUTILS_H | ||||
| #define QMLUTILS_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
| namespace qmlUtils { | ||||
|  | ||||
| QVariant callQml(QObject* o, const QString &name, const QVariantList &args = QVariantList()); | ||||
| QObject* childFromProperty(QObject* o, const QString& propertyName); | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // QMLUTILS_H | ||||
| @@ -1,87 +0,0 @@ | ||||
| CREATE TABLE folders ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL DEFAULT 0, | ||||
| 	updated_time INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE TABLE notes ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	parent_id TEXT NOT NULL DEFAULT "", | ||||
| 	title TEXT NOT NULL DEFAULT "", | ||||
| 	body TEXT NOT NULL DEFAULT "", | ||||
| 	created_time INT NOT NULL DEFAULT 0, | ||||
| 	updated_time INT NOT NULL DEFAULT 0, | ||||
| 	latitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	longitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	altitude NUMERIC NOT NULL DEFAULT 0, | ||||
| 	source TEXT NOT NULL DEFAULT "", | ||||
| 	author TEXT NOT NULL DEFAULT "", | ||||
| 	source_url TEXT NOT NULL DEFAULT "", | ||||
| 	is_todo BOOLEAN NOT NULL DEFAULT 0, | ||||
| 	todo_due INT NOT NULL DEFAULT "", | ||||
| 	todo_completed INT NOT NULL DEFAULT "", | ||||
| 	source_application TEXT NOT NULL DEFAULT "", | ||||
| 	application_data TEXT NOT NULL DEFAULT "", | ||||
| 	`order` INT NOT NULL DEFAULT 0 | ||||
| ); | ||||
|  | ||||
| CREATE TABLE tags ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT, | ||||
| 	created_time INT, | ||||
| 	updated_time INT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE note_tags ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	note_id TEXT, | ||||
| 	tag_id TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE resources ( | ||||
| 	id TEXT PRIMARY KEY, | ||||
| 	title TEXT, | ||||
| 	mime TEXT, | ||||
| 	filename TEXT, | ||||
| 	created_time INT, | ||||
| 	updated_time INT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE note_resources ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	note_id TEXT, | ||||
| 	resource_id TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE version ( | ||||
| 	version INT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE changes ( | ||||
|     id INTEGER PRIMARY KEY, | ||||
| 	`type` INT, | ||||
| 	item_id TEXT, | ||||
| 	item_type INT, | ||||
| 	item_field TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE settings ( | ||||
|     `key` TEXT PRIMARY KEY, | ||||
| 	`value` TEXT, | ||||
| 	`type` INT | ||||
| ); | ||||
|  | ||||
| --CREATE TABLE mimetypes ( | ||||
| --    id INT, | ||||
| --	mime TEXT | ||||
| --); | ||||
|  | ||||
| --CREATE TABLE mimetype_extensions ( | ||||
| --    id INTEGER PRIMARY KEY, | ||||
| --	mimetype_id, | ||||
| --	extension TEXT | ||||
| --); | ||||
|  | ||||
| INSERT INTO version (version) VALUES (1); | ||||
| @@ -1,53 +0,0 @@ | ||||
| #include "folderservice.h" | ||||
| #include "uuid.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| FolderService::FolderService() {} | ||||
|  | ||||
| FolderService::FolderService(Database &database) { | ||||
| 	database_ = database; | ||||
| } | ||||
|  | ||||
| int FolderService::count() const { | ||||
| 	QSqlQuery q = database_.query("SELECT count(*) as row_count FROM folders"); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
| 	return q.value(0).toInt(); | ||||
| } | ||||
|  | ||||
| Folder FolderService::byId(const QString& id) const { | ||||
| 	QSqlQuery q = database_.query("SELECT title, created_time FROM folders WHERE id = :id"); | ||||
| 	q.bindValue(":id", id); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
|  | ||||
| 	Folder output; | ||||
| 	output.setId(id); | ||||
| 	output.setTitle(q.value(0).toString()); | ||||
| 	output.setCreatedTime(q.value(1).toInt()); | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| const QList<Folder> FolderService::overviewList() const { | ||||
| 	if (cache_.size()) return cache_; | ||||
|  | ||||
| 	QList<Folder> output; | ||||
| 	QSqlQuery q = database_.query("SELECT id, title FROM folders ORDER BY created_time DESC"); | ||||
| 	q.exec(); | ||||
| 	while (q.next()) { | ||||
| 		Folder f; | ||||
| 		f.setId(q.value(0).toString()); | ||||
| 		f.setTitle(q.value(1).toString()); | ||||
| 		f.setIsPartial(true); | ||||
| 		output << f; | ||||
| 	} | ||||
|  | ||||
| 	cache_ = output; | ||||
|  | ||||
| 	return cache_; | ||||
| } | ||||
|  | ||||
| void FolderService::clearCache() { | ||||
| 	cache_.clear(); | ||||
| } | ||||
| @@ -1,31 +0,0 @@ | ||||
| #ifndef FOLDERSERVICE_H | ||||
| #define FOLDERSERVICE_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "database.h" | ||||
| #include "models/folder.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class FolderService { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	FolderService(); | ||||
| 	FolderService(Database& database); | ||||
| 	int count() const; | ||||
| 	Folder byId(const QString &id) const; | ||||
| 	//Folder partialAt(int index) const; | ||||
| 	const QList<Folder> overviewList() const; | ||||
| 	void clearCache(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	Database database_; | ||||
| 	mutable QList<Folder> cache_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // FOLDERSERVICE_H | ||||
| @@ -1,18 +0,0 @@ | ||||
| #include "notecache.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| NoteCache::NoteCache() { | ||||
|  | ||||
| } | ||||
|  | ||||
| void NoteCache::add(QList<Note> notes) { | ||||
| 	foreach (Note note, notes) { | ||||
| 		//cache_[note.id()] = note; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::pair<Note, bool> NoteCache::get(int id) const { | ||||
| 	if (cache_.contains(id)) return std::make_pair(cache_[id], true); | ||||
| 	return std::make_pair(Note(), true); | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| #ifndef NOTECACHE_H | ||||
| #define NOTECACHE_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "models/note.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class NoteCache { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	NoteCache(); | ||||
| 	void add(QList<Note> notes); | ||||
| 	std::pair<Note, bool> get(int id) const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QMap<int, Note> cache_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // NOTECACHE_H | ||||
| @@ -1,54 +0,0 @@ | ||||
| #include "noteservice.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| NoteService::NoteService() {} | ||||
|  | ||||
| NoteService::NoteService(jop::Database &database) { | ||||
| 	database_ = database; | ||||
| } | ||||
|  | ||||
| int NoteService::count(const QString &parentFolderId) const { | ||||
| 	QSqlQuery q = database_.query("SELECT count(*) as row_count FROM notes WHERE parent_id = :parent_id"); | ||||
| 	q.bindValue(":parent_id", parentFolderId); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
| 	return q.value(0).toInt(); | ||||
| } | ||||
|  | ||||
| Note NoteService::byId(const QString &id) const { | ||||
| 	Note n; | ||||
| 	return n; | ||||
| } | ||||
|  | ||||
| const QList<Note> NoteService::overviewList(const QString& folderId, int from, int to, const QString &orderBy) const { | ||||
| 	QList<Note> output; | ||||
| 	QSqlQuery q = database_.query("SELECT id, title FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy + " LIMIT " + QString::number(to - from) + " OFFSET " + QString::number(from)); | ||||
| 	q.bindValue(":parent_id", folderId); | ||||
| 	q.exec(); | ||||
|  | ||||
| 	while (q.next()) { | ||||
| 		Note f; | ||||
| 		f.setId(q.value(0).toString()); | ||||
| 		f.setTitle(q.value(1).toString()); | ||||
| 		f.setIsPartial(true); | ||||
| 		output << f; | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| std::pair<const Note &, bool> NoteService::overviewAt(const QString &folderId, int index, const QString &orderBy) const { | ||||
| 	QSqlQuery q = database_.query("SELECT id, title FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy + " LIMIT 1 OFFSET " + QString::number(index)); | ||||
| 	q.bindValue(":parent_id", folderId); | ||||
| 	q.exec(); | ||||
| 	q.next(); | ||||
| 	if (!q.isValid()) return std::make_pair(Note(), false); | ||||
|  | ||||
| 	Note f; | ||||
| 	f.setId(q.value(0).toString()); | ||||
| 	f.setTitle(q.value(1).toString()); | ||||
| 	f.setIsPartial(true); | ||||
|  | ||||
| 	return std::make_pair(f, true); | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| #ifndef NOTESERVICE_H | ||||
| #define NOTESERVICE_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "database.h" | ||||
| #include "models/note.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class NoteService { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	NoteService(); | ||||
| 	NoteService(Database& database); | ||||
| 	int count(const QString& parentFolderId) const; | ||||
| 	Note byId(const QString& id) const; | ||||
| 	const QList<Note> overviewList(const QString &folderId, int from, int to, const QString& orderBy) const; | ||||
| 	std::pair<const Note&, bool> overviewAt(const QString& folderId, int index, const QString& orderBy) const; | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	Database database_; | ||||
| 	mutable QList<Note> cache_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // NOTESERVICE_H | ||||
| @@ -1,40 +0,0 @@ | ||||
| #include "settings.h" | ||||
| #include "models/setting.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Settings::Settings() : QSettings() {} | ||||
|  | ||||
| bool readSqlite(QIODevice &device, QSettings::SettingsMap &map) { | ||||
| 	Q_UNUSED(device); | ||||
| 	map = Setting::settings(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool writeSqlite(QIODevice &device, const QSettings::SettingsMap &map) { | ||||
| 	// HACK: QSettings requires a readable/writable file to be present | ||||
| 	// for the custom handler to work. However, we don't need such a | ||||
| 	// file since we write to the db. So to simulate it, we write once | ||||
| 	// to that file. Without this, readSqlite in particular will never | ||||
| 	// get called. | ||||
| 	device.write("X", 1); | ||||
| 	Setting::setSettings(map); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void Settings::initialize() { | ||||
| 	const QSettings::Format SqliteFormat = QSettings::registerFormat("sqlite", &readSqlite, &writeSqlite); | ||||
| 	QSettings::setDefaultFormat(SqliteFormat); | ||||
| } | ||||
|  | ||||
| QString Settings::valueString(const QString &name, const QString &defaultValue) { | ||||
| 	return value(name, defaultValue).toString(); | ||||
| } | ||||
|  | ||||
| int Settings::valueInt(const QString &name, int defaultValue) { | ||||
| 	return value(name, defaultValue).toInt(); | ||||
| } | ||||
|  | ||||
| QString Settings::keyValueserialize(const QString& key) const { | ||||
| 	return QString("%1 = %2").arg(key).arg(value(key).toString()); | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| #ifndef SETTINGS_H | ||||
| #define SETTINGS_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "database.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Settings : public QSettings { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Settings(); | ||||
|  | ||||
| 	static void initialize(); | ||||
| 	QString keyValueserialize(const QString& key) const; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	QString valueString(const QString& name, const QString& defaultValue = ""); | ||||
| 	int valueInt(const QString& name, int defaultValue = 0); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // SETTINGS_H | ||||
| @@ -1,12 +0,0 @@ | ||||
| #ifndef SIMPLETYPES_H | ||||
| #define SIMPLETYPES_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| typedef QVector<QVariant> VariantVector; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // SIMPLETYPES_H | ||||
| @@ -1,215 +0,0 @@ | ||||
| #ifndef SPARSEARRAY_HPP | ||||
| #define SPARSEARRAY_HPP | ||||
|  | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include <math.h> | ||||
|  | ||||
| template <class ClassType> class SparseVector { | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	SparseVector() { | ||||
| 		counter_ = -1; | ||||
| 		count_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	ClassType* get(int index) const { | ||||
| 		if (index > count()) return NULL; | ||||
| 		if (!isset(index)) return NULL; | ||||
|  | ||||
| 		typename IndexMap::const_iterator pos = indexes_.find(index); | ||||
| 		int valueIndex = pos->second.valueIndex; | ||||
|  | ||||
| 		typename std::map<int, std::unique_ptr<ClassType>>::const_iterator pos2 = values_.find(valueIndex); | ||||
| 		return pos2->second.get(); | ||||
| 	} | ||||
|  | ||||
| 	void set(int index, ClassType* value) { | ||||
| 		unset(index); | ||||
| 		int valueIndex = ++counter_; | ||||
| 		std::unique_ptr<ClassType> ptr(value); | ||||
| 		values_[valueIndex] = std::move(ptr); | ||||
| 		IndexRecord r; | ||||
| 		r.valueIndex = valueIndex; | ||||
| 		r.time = 0; // Disabled / not needed | ||||
| 		//r.time = time(0); | ||||
| 		indexes_[index] = r; | ||||
| 		count_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	void push(const ClassType& value) { | ||||
| 		set(count(), value); | ||||
| 	} | ||||
|  | ||||
| 	bool isset(int index) const { | ||||
| 		return indexes_.find(index) != indexes_.end(); | ||||
| 	} | ||||
|  | ||||
| 	std::vector<int> indexes() const { | ||||
| 		std::vector<int> output; | ||||
| 		for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) { | ||||
| 			output.push_back(it->first); | ||||
| 		} | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	// Unsets that particular index, but without shifting the following indexes | ||||
| 	void unset(int index) { | ||||
| 		if (!isset(index)) return; | ||||
| 		IndexRecord r = indexes_[index]; | ||||
| 		values_.erase(r.valueIndex); | ||||
| 		indexes_.erase(index); | ||||
| 	} | ||||
|  | ||||
| 	void insert(int index, const ClassType& value) { | ||||
| 		IndexMap newIndexes; | ||||
| 		for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) { | ||||
| 			int key = it->first; | ||||
| 			if (key > index) key++; | ||||
| 			newIndexes[key] = it->second; | ||||
| 		} | ||||
| 		indexes_ = newIndexes; | ||||
| 		set(index, value); | ||||
| 	} | ||||
|  | ||||
| 	// Removes the element at that particular index, and shift all the following elements | ||||
| 	void remove(int index) { | ||||
| 		if (index > count()) return; | ||||
|  | ||||
| 		if (isset(index)) { | ||||
| 			int valueIndex = indexes_[index].valueIndex; | ||||
| 			values_.erase(valueIndex); | ||||
| 		} | ||||
|  | ||||
| 		IndexMap newIndexes; | ||||
| 		for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) { | ||||
| 			int key = it->first; | ||||
| 			if (key == index) continue; | ||||
| 			if (key > index) key--; | ||||
| 			newIndexes[key] = it->second; | ||||
| 		} | ||||
| 		indexes_ = newIndexes; | ||||
| 		count_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	// Returns a vector containing the indexes that are not currently set around | ||||
| 	// the given index, up to bufferSize indexes. | ||||
| 	std::vector<int> availableBufferAround(int index, size_t bufferSize) const { | ||||
| 		std::vector<int> temp; | ||||
|  | ||||
| 		// Doesn't make sense to search for an empty buffer around | ||||
| 		// an index that is already set. | ||||
| 		if (isset(index)) return temp; | ||||
|  | ||||
| 		temp.push_back(index); | ||||
|  | ||||
| 		// Probably not the most efficient algorithm but it works: | ||||
| 		// First search 1 position to the left, then 1 position to the right, | ||||
| 		// then 2 to the left, etc. If encountering an unavailable index on one | ||||
| 		// of the side, the path is "blocked" and searching is now done in only | ||||
| 		// one direction. If both sides are blocked, the algorithm exit. | ||||
|  | ||||
| 		int inc = 1; | ||||
| 		int sign = -1; | ||||
| 		bool leftBlocked = false; | ||||
| 		bool rightBlocked = false; | ||||
| 		while (temp.size() < bufferSize) { | ||||
| 			int bufferIndex = index + (inc * sign); | ||||
|  | ||||
| 			bool blocked = isset(bufferIndex) || bufferIndex < 0; | ||||
| 			if (blocked) { | ||||
| 				if (sign < 0) { | ||||
| 					leftBlocked = true; | ||||
| 				} else { | ||||
| 					rightBlocked = true; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (leftBlocked && rightBlocked) break; | ||||
|  | ||||
| 			if (!blocked) temp.push_back(bufferIndex); | ||||
|  | ||||
| 			sign = -sign; | ||||
| 			if (sign < 0) inc++; | ||||
| 			if (leftBlocked && sign < 0) sign = 1; | ||||
| 			if (rightBlocked && sign > 0) sign = -1; | ||||
| 		} | ||||
|  | ||||
| 		std::sort(temp.begin(), temp.end()); | ||||
|  | ||||
| 		return temp; | ||||
| 	} | ||||
|  | ||||
| 	int count() const { | ||||
| 		if (count_ >= 0) return count_; | ||||
| 		int maxKey = -1; | ||||
| 		for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) { | ||||
| 			const int& key = it->first; | ||||
| 			if (key > maxKey) maxKey = key; | ||||
| 		} | ||||
| 		count_ = maxKey + 1; | ||||
| 		return count_; | ||||
| 	} | ||||
|  | ||||
| 	void clearOlderThan(time_t time) { | ||||
| 		IndexMap newIndexes; | ||||
| 		for (typename IndexMap::const_iterator it = indexes_.begin(); it != indexes_.end(); ++it) { | ||||
| 			const IndexRecord& r = it->second; | ||||
| 			if (r.time > time) { | ||||
| 				newIndexes[it->first] = r; | ||||
| 			} else { | ||||
| 				values_.erase(r.valueIndex); | ||||
| 			} | ||||
| 		} | ||||
| 		indexes_ = newIndexes; | ||||
| 		count_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	// Unset all values outside of this interval | ||||
| 	void unsetAllButInterval(int intervalFrom, int intervalTo) { | ||||
| 		int count = this->count(); | ||||
| 		for (int i = 0; i < count; i++) { | ||||
| 			if (i >= intervalFrom && i <= intervalTo) continue; | ||||
| 			unset(i); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void clear() { | ||||
| 		indexes_.clear(); | ||||
| 		values_.clear(); | ||||
| 		counter_ = 0; | ||||
| 		count_ = -1; | ||||
| 	} | ||||
|  | ||||
| 	void print() const { | ||||
| 		for (int i = 0; i < count(); i++) { | ||||
| 			std::cout << "|"; | ||||
| 			std::cout << " " << get(i) << " "; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	struct IndexRecord { | ||||
| 		int valueIndex; | ||||
| 		time_t time; | ||||
| 	}; | ||||
|  | ||||
| 	typedef std::map<int, IndexRecord> IndexMap; | ||||
|  | ||||
| 	int counter_; | ||||
| 	IndexMap indexes_; | ||||
| 	std::map<int, std::unique_ptr<ClassType>> values_; | ||||
|  | ||||
| 	// This is used to cache the result of ::count(). | ||||
| 	// Don't forget to set it to -1 whenever the list | ||||
| 	// size changes, so that it can be recalculated. | ||||
| 	mutable int count_; | ||||
|  | ||||
| }; | ||||
|  | ||||
| #endif // SPARSEARRAY_HPP | ||||
| @@ -1,44 +0,0 @@ | ||||
| #ifndef STABLE_H | ||||
| #define STABLE_H | ||||
|  | ||||
| #if defined __cplusplus | ||||
|  | ||||
| #include <memory> | ||||
| #include <limits> | ||||
|  | ||||
| #include <QAbstractListModel> | ||||
| #include <QCoreApplication> | ||||
| #include <QGuiApplication> | ||||
| #include <QDebug> | ||||
| #include <QFileInfo> | ||||
| #include <QSqlDatabase> | ||||
| #include <QSqlQuery> | ||||
| #include <QSqlRecord> | ||||
| #include <QList> | ||||
| #include <QGuiApplication> | ||||
| #include <QQmlApplicationEngine> | ||||
| #include <QSqlDatabase> | ||||
| #include <QQuickView> | ||||
| #include <QQmlContext> | ||||
| #include <QQmlProperty> | ||||
| #include <QSqlError> | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QUrlQuery> | ||||
| #include <QNetworkRequest> | ||||
| #include <QNetworkReply> | ||||
| #include <QSettings> | ||||
| #include <QJsonObject> | ||||
| #include <QJsonParseError> | ||||
| #include <QBuffer> | ||||
| #include <QJsonArray> | ||||
| #include <QTimer> | ||||
| #include <QStandardPaths> | ||||
| #include <QDir> | ||||
| #include <QCommandLineParser> | ||||
| #include <QProcess> | ||||
| #include <QQmlProperty> | ||||
| #include <QThread> | ||||
|  | ||||
| #endif // __cplusplus | ||||
|  | ||||
| #endif // STABLE_H | ||||
| @@ -1,321 +0,0 @@ | ||||
| #include "synchronizer.h" | ||||
| #include "models/folder.h" | ||||
| #include "models/note.h" | ||||
| #include "settings.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Synchronizer::Synchronizer() { | ||||
| 	state_ = Idle; | ||||
| 	uploadsRemaining_ = 0; | ||||
| 	connect(&api_, SIGNAL(requestDone(QJsonObject,QString)), this, SLOT(api_requestDone(QJsonObject,QString))); | ||||
| } | ||||
|  | ||||
| void Synchronizer::start() { | ||||
| 	if (state_ == Frozen) { | ||||
| 		qWarning() << "Cannot start synchronizer while frozen"; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (state_ != Idle) { | ||||
| 		qWarning() << "Cannot start synchronizer because synchronization already in progress. State: " << state_; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	emit started(); | ||||
|  | ||||
| 	qInfo() << "Starting synchronizer..."; | ||||
|  | ||||
| 	switchState(UploadingChanges); | ||||
| } | ||||
|  | ||||
| void Synchronizer::setSessionId(const QString &v) { | ||||
| 	api_.setSessionId(v); | ||||
| } | ||||
|  | ||||
| void Synchronizer::abort() { | ||||
| 	switchState(Aborting); | ||||
| } | ||||
|  | ||||
| void Synchronizer::freeze() { | ||||
| 	switchState(Frozen); | ||||
| } | ||||
|  | ||||
| void Synchronizer::unfreeze() { | ||||
| 	switchState(Idle); | ||||
| } | ||||
|  | ||||
| WebApi &Synchronizer::api() { | ||||
| 	return api_; | ||||
| } | ||||
|  | ||||
| QUrlQuery Synchronizer::valuesToUrlQuery(const QHash<QString, Change::Value>& values) const { | ||||
| 	QUrlQuery query; | ||||
| 	for (QHash<QString, Change::Value>::const_iterator it = values.begin(); it != values.end(); ++it) { | ||||
| 		if (it.key() == "id") continue; | ||||
| 		query.addQueryItem(it.key(), it.value().toString()); | ||||
| 	} | ||||
| 	return query; | ||||
| } | ||||
|  | ||||
| void Synchronizer::checkNextState() { | ||||
| 	qDebug() << "Synchronizer::checkNextState from state" << state_; | ||||
|  | ||||
| 	switch (state_) { | ||||
|  | ||||
| 		case UploadingChanges: | ||||
|  | ||||
| 			if (uploadsRemaining_ < 0) qCritical() << "Mismatch on upload operations done" << uploadsRemaining_; | ||||
|  | ||||
| 			if (uploadsRemaining_ <= 0) { | ||||
| 				uploadsRemaining_ = 0; | ||||
| 				switchState(DownloadingChanges); | ||||
| 			} | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		case DownloadingChanges: | ||||
|  | ||||
| 			switchState(Idle); | ||||
| 			emit finished(); | ||||
| 			break; | ||||
|  | ||||
| 		case Idle: | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		case Aborting: | ||||
|  | ||||
| 			switchState(Idle); | ||||
| 			emit finished(); | ||||
| 			break; | ||||
|  | ||||
| 		case Frozen: | ||||
|  | ||||
| 			break; | ||||
|  | ||||
| 		default: | ||||
|  | ||||
| 			qCritical() << "Synchronizer has invalid state" << state_; | ||||
| 			break; | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Synchronizer::switchState(Synchronizer::SynchronizationState state) { | ||||
| 	if (state_ == state) { | ||||
| 		qCritical() << "Trying to switch synchronizer to its current state" << state; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	state_ = state; | ||||
|  | ||||
| 	qInfo() << "Switching synchronizer state to" << state; | ||||
|  | ||||
| 	if (state == Idle) { | ||||
|  | ||||
| 		// ============================================================================================= | ||||
| 		// IDLE STATE | ||||
| 		// ============================================================================================= | ||||
|  | ||||
| 	} else if (state == UploadingChanges) { | ||||
|  | ||||
| 		// ============================================================================================= | ||||
| 		// UPLOADING STATE | ||||
| 		// ============================================================================================= | ||||
|  | ||||
| 		std::vector<Change*> changes = Change::all(); | ||||
| 		Change::mergedChanges(changes); | ||||
|  | ||||
| 		uploadsRemaining_ = changes.size(); | ||||
|  | ||||
| 		for (size_t i = 0; i < changes.size(); i++) { | ||||
| 			Change* change = changes[i]; | ||||
|  | ||||
| 			jop::Table itemType = (jop::Table)change->value("item_type").toInt(); | ||||
| 			QString itemId = change->value("item_id").toString(); | ||||
| 			Change::Type type = (Change::Type)change->value("type").toInt(); | ||||
|  | ||||
| 			qDebug() << "Change" << change->idString() << itemId << itemType; | ||||
|  | ||||
| 			if (itemType == jop::FoldersTable) { | ||||
|  | ||||
| 				if (type == Change::Create) { | ||||
|  | ||||
| 					Folder folder; | ||||
| 					folder.load(itemId); | ||||
| 					QUrlQuery data = valuesToUrlQuery(folder.values()); | ||||
| 					api_.put("folders/" + folder.idString(), QUrlQuery(), data, "upload:putFolder:" + folder.idString()); | ||||
|  | ||||
| 				} else if (type == Change::Update) { | ||||
|  | ||||
| 					Folder folder; | ||||
| 					folder.load(itemId); | ||||
| 					QStringList mergedFields = change->mergedFields(); | ||||
| 					QUrlQuery data; | ||||
| 					foreach (QString field, mergedFields) { | ||||
| 						data.addQueryItem(field, folder.value(field).toString()); | ||||
| 					} | ||||
| 					api_.patch("folders/" + folder.idString(), QUrlQuery(), data, "upload:patchFolder:" + folder.idString()); | ||||
|  | ||||
| 				} else if (type == Change::Delete) { | ||||
|  | ||||
| 					api_.del("folders/" + itemId, QUrlQuery(), QUrlQuery(), "upload:deleteFolder:" + itemId); | ||||
|  | ||||
| 				} | ||||
| 			} else { | ||||
|  | ||||
| 				qFatal("Unsupported item type: %d", itemType); | ||||
|  | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for (size_t i = 0; i < changes.size(); i++) { | ||||
| 			delete changes[i]; | ||||
| 		} | ||||
| 		changes.clear(); | ||||
|  | ||||
| 		checkNextState(); | ||||
|  | ||||
| 	} else if (state_ == DownloadingChanges) { | ||||
|  | ||||
| 		// ============================================================================================= | ||||
| 		// DOWNLOADING STATE | ||||
| 		// ============================================================================================= | ||||
|  | ||||
| 		Settings settings; | ||||
| 		QString lastRevId = settings.value("lastRevId", "0").toString(); | ||||
|  | ||||
| 		QUrlQuery query; | ||||
| 		query.addQueryItem("rev_id", lastRevId); | ||||
| 		api_.get("synchronizer", query, QUrlQuery(), "download:getSynchronizer"); | ||||
|  | ||||
| 	} else if (state == Aborting) { | ||||
|  | ||||
| 		// ============================================================================================= | ||||
| 		// ABORTING STATE | ||||
| 		// ============================================================================================= | ||||
|  | ||||
| 		uploadsRemaining_ = 0; | ||||
| 		api_.abortAll(); | ||||
| 		checkNextState(); | ||||
|  | ||||
| 	} else if (state == Frozen) { | ||||
|  | ||||
| 		// ============================================================================================= | ||||
| 		// FROZEN STATE | ||||
| 		// ============================================================================================= | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| void Synchronizer::api_requestDone(const QJsonObject& response, const QString& tag) { | ||||
| 	if (state_ == Frozen) { | ||||
| 		qWarning() << "Receiving response while synchronizer is frozen"; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	QStringList parts = tag.split(':'); | ||||
| 	QString category = parts[0]; | ||||
| 	QString action = parts[1]; | ||||
| 	QString arg1 = ""; | ||||
| 	QString arg2 = ""; | ||||
|  | ||||
| 	if (parts.size() == 3) arg1 = parts[2]; | ||||
| 	if (parts.size() == 4) arg2 = parts[3]; | ||||
|  | ||||
| 	qInfo() << "WebApi: done" << category << action << arg1 << arg2; | ||||
|  | ||||
| 	QString error = ""; | ||||
|  | ||||
| 	if (response.contains("error")) { | ||||
| 		error = response.value("error").toString(); | ||||
| 		qCritical().noquote() << "Sync error:" << error; | ||||
| 		// Each action might handle errors differently so let it proceed below | ||||
| 	} | ||||
|  | ||||
| 	// ============================================================================================= | ||||
| 	// HANDLE UPLOAD RESPONSE | ||||
| 	// ============================================================================================= | ||||
|  | ||||
| 	if (state_ == UploadingChanges) { | ||||
| 		uploadsRemaining_--; | ||||
|  | ||||
| 		if (error == "") { | ||||
| 			qInfo() << "Synced folder" << arg1; | ||||
|  | ||||
| 			if (action == "putFolder") { | ||||
| 				Change::disposeByItemId(arg1); | ||||
| 			} | ||||
|  | ||||
| 			if (action == "patchFolder") { | ||||
| 				Change::disposeByItemId(arg1); | ||||
| 			} | ||||
|  | ||||
| 			if (action == "deleteFolder") { | ||||
| 				Change::disposeByItemId(arg1); | ||||
| 			} | ||||
|  | ||||
| 			if (uploadsRemaining_ < 0) { | ||||
| 				qWarning() << "Mismatch on operations done:" << uploadsRemaining_; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		checkNextState(); | ||||
|  | ||||
| 	// ============================================================================================= | ||||
| 	// HANDLE DOWNLOAD RESPONSE | ||||
| 	// ============================================================================================= | ||||
|  | ||||
| 	} else if (state_ == DownloadingChanges) { | ||||
| 		if (error != "") { | ||||
| 			checkNextState(); | ||||
| 		} else { | ||||
| 			if (action == "getSynchronizer") { | ||||
| 				QJsonArray items = response["items"].toArray(); | ||||
| 				QString maxRevId = ""; | ||||
| 				foreach (QJsonValue it, items) { | ||||
| 					QJsonObject obj = it.toObject(); | ||||
| 					QString itemId = obj["item_id"].toString(); | ||||
| 					QString itemType = obj["item_type"].toString(); | ||||
| 					QString operationType = obj["type"].toString(); | ||||
| 					QString revId = obj["id"].toString(); | ||||
| 					QJsonObject item = obj["item"].toObject(); | ||||
|  | ||||
| 					if (itemType == "folder") { | ||||
| 						if (operationType == "create") { | ||||
| 							Folder folder; | ||||
| 							folder.loadJsonObject(item); | ||||
| 							folder.save(false); | ||||
| 						} | ||||
|  | ||||
| 						if (operationType == "update") { | ||||
| 							Folder folder; | ||||
| 							folder.load(itemId); | ||||
| 							folder.patchJsonObject(item); | ||||
| 							folder.save(false); | ||||
| 						} | ||||
|  | ||||
| 						if (operationType == "delete") { | ||||
| 							Folder folder; | ||||
| 							folder.load(itemId); | ||||
| 							folder.dispose(); | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					if (revId > maxRevId) maxRevId = revId; | ||||
| 				} | ||||
|  | ||||
| 				if (maxRevId != "") { | ||||
| 					Settings settings; | ||||
| 					settings.setValue("lastRevId", maxRevId); | ||||
| 				} | ||||
|  | ||||
| 				checkNextState(); | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		qCritical() << "Invalid category" << category; | ||||
| 	} | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| #ifndef SYNCHRONIZER_H | ||||
| #define SYNCHRONIZER_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "webapi.h" | ||||
| #include "database.h" | ||||
| #include "models/change.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Synchronizer : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	enum SynchronizationState { Idle, UploadingChanges, DownloadingChanges, Aborting, Frozen }; | ||||
|  | ||||
| 	Synchronizer(); | ||||
| 	void start(); | ||||
| 	void setSessionId(const QString& v); | ||||
| 	void abort(); | ||||
| 	void freeze(); | ||||
| 	void unfreeze(); | ||||
| 	WebApi& api(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QUrlQuery valuesToUrlQuery(const QHash<QString, BaseModel::Value> &values) const; | ||||
| 	WebApi api_; | ||||
| 	SynchronizationState state_; | ||||
| 	int uploadsRemaining_; | ||||
| 	void checkNextState(); | ||||
| 	void switchState(SynchronizationState state); | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void api_requestDone(const QJsonObject& response, const QString& tag); | ||||
|  | ||||
| signals: | ||||
|  | ||||
| 	void started(); | ||||
| 	void finished(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // SYNCHRONIZER_H | ||||
| @@ -1,14 +0,0 @@ | ||||
| #include <stable.h> | ||||
| #include "uuid.h" | ||||
|  | ||||
| namespace jop { | ||||
| namespace uuid { | ||||
|  | ||||
| QString createUuid(QString s) { | ||||
| 	if (s == "") s = QString("%1%2").arg(qrand()).arg(QDateTime::currentMSecsSinceEpoch()); | ||||
| 	QString hash = QString(QCryptographicHash::hash(s.toUtf8(), QCryptographicHash::Sha256).toHex()); | ||||
| 	return hash.left(32); | ||||
| } | ||||
|  | ||||
| } | ||||
| } | ||||
| @@ -1,14 +0,0 @@ | ||||
| #ifndef UUID_H | ||||
| #define UUID_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
| namespace uuid { | ||||
|  | ||||
| QString createUuid(QString s = ""); | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| #endif // UUID_H | ||||
| @@ -1,7 +0,0 @@ | ||||
| #include "uuid_utils.h" | ||||
|  | ||||
|  | ||||
| QString testtest() | ||||
| { | ||||
| 	return "con"; | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| #ifndef UUID_UTILS_H | ||||
| #define UUID_UTILS_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| QString testtest(); | ||||
|  | ||||
| #endif // UUID_UTILS_H | ||||
| @@ -1,171 +0,0 @@ | ||||
| #include <stable.h> | ||||
|  | ||||
| #include "webapi.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| WebApi::WebApi() { | ||||
| 	baseUrl_ = ""; | ||||
| 	sessionId_ = ""; | ||||
| 	connect(&manager_, SIGNAL(finished(QNetworkReply*)), this, SLOT(request_finished(QNetworkReply*))); | ||||
| } | ||||
|  | ||||
| QString WebApi::baseUrl() const { | ||||
| 	return baseUrl_; | ||||
| } | ||||
|  | ||||
| void WebApi::execRequest(HttpMethod method, const QString &path, const QUrlQuery &query, const QUrlQuery &data, const QString& tag) { | ||||
| 	if (baseUrl() == "") { | ||||
| 		qCritical() << "Trying to execute request before base URL has been set"; | ||||
| 		QJsonObject obj; | ||||
| 		obj["error"] = "Trying to execute request before base URL has been set"; | ||||
| 		emit requestDone(obj, tag); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	QueuedRequest r; | ||||
| 	r.method = method; | ||||
| 	r.path = path; | ||||
| 	r.query = query; | ||||
| 	r.data = data; | ||||
| 	r.tag = tag; | ||||
| 	r.buffer = NULL; | ||||
| 	r.reply = NULL; | ||||
| 	queuedRequests_ << r; | ||||
|  | ||||
| 	processQueue(); | ||||
| } | ||||
|  | ||||
| void WebApi::post(const QString& path,const QUrlQuery& query, const QUrlQuery& data, const QString& tag) { execRequest(HttpMethod::POST, path, query, data, tag); } | ||||
| void WebApi::get(const QString& path,const QUrlQuery& query, const QUrlQuery& data, const QString& tag) { execRequest(HttpMethod::GET, path, query, data, tag); } | ||||
| void WebApi::put(const QString& path,const QUrlQuery& query, const QUrlQuery& data, const QString& tag) { execRequest(HttpMethod::PUT, path, query, data, tag); } | ||||
| void WebApi::del(const QString &path, const QUrlQuery &query, const QUrlQuery &data, const QString &tag) { execRequest(HttpMethod::DEL, path, query, data, tag); } | ||||
| void WebApi::patch(const QString &path, const QUrlQuery &query, const QUrlQuery &data, const QString &tag) { execRequest(HttpMethod::PATCH, path, query, data, tag); } | ||||
|  | ||||
| void WebApi::setSessionId(const QString &v) { | ||||
| 	sessionId_ = v; | ||||
| } | ||||
|  | ||||
| void WebApi::abortAll() { | ||||
| 	for (int i = 0; i < inProgressRequests_.size(); i++) { | ||||
| 		QueuedRequest r = inProgressRequests_[i]; | ||||
| 		if (r.reply) { | ||||
| 			r.reply->abort(); | ||||
| 			// TODO: Delete r.reply? | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (int i = 0; i < queuedRequests_.size(); i++) { | ||||
| 		QueuedRequest r = queuedRequests_[i]; | ||||
| 		if (r.reply) { | ||||
| 			r.reply->abort(); | ||||
| 			// TODO: Delete r.reply? | ||||
| 		} | ||||
| 	} | ||||
| 	queuedRequests_.size(); | ||||
| } | ||||
|  | ||||
| void WebApi::processQueue() { | ||||
| 	if (!queuedRequests_.size() || inProgressRequests_.size() >= 50) return; | ||||
| 	QueuedRequest r = queuedRequests_.takeFirst(); | ||||
|  | ||||
| 	QString url = baseUrl_ + "/" + r.path; | ||||
| 	QUrlQuery query = r.query; | ||||
|  | ||||
| 	if (sessionId_ != "") { | ||||
| 		query.addQueryItem("session", sessionId_); | ||||
| 	} | ||||
|  | ||||
| 	url += "?" + query.toString(QUrl::FullyEncoded); | ||||
|  | ||||
| 	QNetworkRequest* request = new QNetworkRequest(url); | ||||
| 	request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); | ||||
|  | ||||
| 	QNetworkReply* reply = NULL; | ||||
|  | ||||
| 	if (r.method == jop::PATCH) { | ||||
| 		// TODO: Delete buffer when done | ||||
| 		QBuffer* buffer = new QBuffer(); | ||||
| 		buffer->open(QBuffer::ReadWrite); | ||||
| 		buffer->write(r.data.toString(QUrl::FullyEncoded).toUtf8()); | ||||
| 		buffer->seek(0); | ||||
| 		r.buffer = buffer; | ||||
| 		reply = manager_.sendCustomRequest(*request, "PATCH", buffer); | ||||
| 	} | ||||
|  | ||||
| 	if (r.method == jop::GET) { | ||||
| 	   reply = manager_.get(*request); | ||||
| 	} | ||||
|  | ||||
| 	if (r.method == jop::POST) { | ||||
| 		reply = manager_.post(*request, r.data.toString(QUrl::FullyEncoded).toUtf8()); | ||||
| 	} | ||||
|  | ||||
| 	if (r.method == jop::PUT) { | ||||
| 		reply = manager_.put(*request, r.data.toString(QUrl::FullyEncoded).toUtf8()); | ||||
| 	} | ||||
|  | ||||
| 	if (r.method == jop::DEL) { | ||||
| 		reply = manager_.deleteResource(*request); | ||||
| 	} | ||||
|  | ||||
| 	if (!reply) { | ||||
| 		qWarning() << "WebApi::processQueue(): reply object was not created - invalid request method"; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	r.reply = reply; | ||||
| 	r.request = request; | ||||
| 	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(request_error(QNetworkReply::NetworkError))); | ||||
|  | ||||
| 	QStringList cmd; | ||||
| 	cmd << "curl"; | ||||
| 	if (r.method == jop::PUT) cmd << "-X" << "PUT"; | ||||
| 	if (r.method == jop::PATCH) cmd << "-X" << "PATCH"; | ||||
| 	if (r.method == jop::DEL) cmd << "-X" << "DELETE"; | ||||
|  | ||||
| 	if (r.method != jop::GET && r.method != jop::DEL) { | ||||
| 		cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'"; | ||||
| 	} | ||||
| 	cmd << "'" + url + "'"; | ||||
|  | ||||
| 	qDebug().noquote() << cmd.join(" "); | ||||
|  | ||||
| 	inProgressRequests_.push_back(r); | ||||
| } | ||||
|  | ||||
| void WebApi::request_finished(QNetworkReply *reply) { | ||||
| 	QByteArray responseBodyBA = reply->readAll(); | ||||
| 	QJsonObject response; | ||||
| 	QJsonParseError err; | ||||
| 	QJsonDocument doc = QJsonDocument::fromJson(responseBodyBA, &err); | ||||
| 	if (err.error != QJsonParseError::NoError) { | ||||
| 		QString errorMessage = "Could not parse JSON: " + err.errorString() + "\n" + QString(responseBodyBA); | ||||
| 		qWarning().noquote() << errorMessage; | ||||
| 		response["error"] = errorMessage; | ||||
| 	} else { | ||||
| 		response = doc.object(); | ||||
| 		if (response.contains("error") && !response["error"].isNull()) { | ||||
| 			qWarning().noquote() << "API error:" << QString(responseBodyBA); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (int i = 0; i < inProgressRequests_.size(); i++) { | ||||
| 		QueuedRequest r = inProgressRequests_[i]; | ||||
| 		if (r.reply == reply) { | ||||
| 			inProgressRequests_.erase(inProgressRequests_.begin() + i); | ||||
| 			emit requestDone(response, r.tag); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	processQueue(); | ||||
| } | ||||
|  | ||||
| void WebApi::request_error(QNetworkReply::NetworkError e) { | ||||
| 	qWarning() << "Network error" << e; | ||||
| } | ||||
|  | ||||
| void jop::WebApi::setBaseUrl(const QString &v) { | ||||
| 	baseUrl_ = v; | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| #ifndef WEBAPI_H | ||||
| #define WEBAPI_H | ||||
|  | ||||
| #include <stable.h> | ||||
| #include "enum.h" | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class WebApi : public QObject { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	struct QueuedRequest { | ||||
| 		HttpMethod method; | ||||
| 		QString path; | ||||
| 		QUrlQuery query; | ||||
| 		QUrlQuery data; | ||||
| 		QNetworkReply* reply; | ||||
| 		QNetworkRequest* request; | ||||
| 		QString tag; | ||||
| 		QBuffer* buffer; | ||||
| 	}; | ||||
|  | ||||
| 	WebApi(); | ||||
| 	void setBaseUrl(const QString& v); | ||||
| 	QString baseUrl() const; | ||||
| 	void execRequest(HttpMethod method, const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void post(const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void get(const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void put(const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void del(const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void patch(const QString& path,const QUrlQuery& query = QUrlQuery(), const QUrlQuery& data = QUrlQuery(), const QString& tag = ""); | ||||
| 	void setSessionId(const QString& v); | ||||
| 	void abortAll(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	QString baseUrl_; | ||||
| 	QList<QueuedRequest> queuedRequests_; | ||||
| 	QList<QueuedRequest> inProgressRequests_; | ||||
| 	void processQueue(); | ||||
| 	QString sessionId_; | ||||
| 	QNetworkAccessManager manager_; | ||||
|  | ||||
| public slots: | ||||
|  | ||||
| 	void request_finished(QNetworkReply* reply); | ||||
| 	void request_error(QNetworkReply::NetworkError e); | ||||
|  | ||||
| signals: | ||||
|  | ||||
| 	void requestDone(const QJsonObject& response, const QString& tag); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // WEBAPI_H | ||||
| @@ -1,52 +0,0 @@ | ||||
| #include "window.h" | ||||
|  | ||||
| using namespace jop; | ||||
|  | ||||
| Window::Window() : QQuickView() {} | ||||
|  | ||||
| void Window::showPage(const QString &pageName) { | ||||
| 	qWarning() << "Window::showPage() disabled"; | ||||
| 	return; | ||||
|  | ||||
| 	QVariant pageNameV(pageName); | ||||
| 	QVariant returnedValue; | ||||
| 	QMetaObject::invokeMethod((QObject*)rootObject(), "showPage", Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, pageNameV)); | ||||
| } | ||||
|  | ||||
| QVariant Window::callQml(const QString &name, const QVariantList &args) { | ||||
| 	QVariant returnedValue; | ||||
| 	qDebug() << "Going to call QML:" << name; | ||||
| 	QObject* o = (QObject*)rootObject(); | ||||
| 	if (args.size() == 0) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue)); | ||||
| 	} else if (args.size() == 1) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0])); | ||||
| 	} else if (args.size() == 2) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1])); | ||||
| 	} else if (args.size() == 3) { | ||||
| 		QMetaObject::invokeMethod(o, name.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1]), Q_ARG(QVariant, args[2])); | ||||
| 	} else { | ||||
| 		qFatal("Window::emitSignal: add support for more args!"); | ||||
| 	} | ||||
| 	return returnedValue; | ||||
| } | ||||
|  | ||||
| void Window::emitSignal(const QString &name, const QVariantList &args) { | ||||
| 	QString nameCopy(name); | ||||
| 	nameCopy = nameCopy.left(1).toUpper() + nameCopy.right(nameCopy.length() - 1); | ||||
| 	nameCopy = "emit" + nameCopy; | ||||
| 	callQml(nameCopy, args); | ||||
| //	qDebug() << "Going to call QML:" << nameCopy; | ||||
| //	QObject* o = (QObject*)rootObject(); | ||||
| //	if (args.size() == 0) { | ||||
| //		QMetaObject::invokeMethod(o, nameCopy.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue)); | ||||
| //	} else if (args.size() == 1) { | ||||
| //		QMetaObject::invokeMethod(o, nameCopy.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0])); | ||||
| //	} else if (args.size() == 2) { | ||||
| //		QMetaObject::invokeMethod(o, nameCopy.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1])); | ||||
| //	} else if (args.size() == 3) { | ||||
| //		QMetaObject::invokeMethod(o, nameCopy.toStdString().c_str(), Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, args[0]), Q_ARG(QVariant, args[1]), Q_ARG(QVariant, args[2])); | ||||
| //	} else { | ||||
| //		qFatal("Window::emitSignal: add support for more args!"); | ||||
| //	} | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| #ifndef WINDOW_H | ||||
| #define WINDOW_H | ||||
|  | ||||
| #include <stable.h> | ||||
|  | ||||
| namespace jop { | ||||
|  | ||||
| class Window : public QQuickView { | ||||
|  | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
|  | ||||
| 	Window(); | ||||
| 	void showPage(const QString& pageName); | ||||
| 	QVariant callQml(const QString& name, const QVariantList& args = QVariantList()); | ||||
| 	void emitSignal(const QString& name, const QVariantList& args = QVariantList()); | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|  | ||||
| #endif // WINDOW_H | ||||
| @@ -1,23 +0,0 @@ | ||||
| CREATE TABLE folders ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	title TEXT, | ||||
| 	created_time INT, | ||||
| 	updated_time INT, | ||||
| 	remote_id TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE notes ( | ||||
| 	id INTEGER PRIMARY KEY, | ||||
| 	title TEXT, | ||||
| 	body TEXT, | ||||
| 	parent_id INT, | ||||
| 	created_time INT, | ||||
| 	updated_time INT, | ||||
| 	remote_id TEXT | ||||
| ); | ||||
|  | ||||
| CREATE TABLE version ( | ||||
| 	version INT | ||||
| ); | ||||
|  | ||||
| INSERT INTO version (version) VALUES (1); | ||||
										
											Binary file not shown.
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user