From e44af7ec7a71b20b27f9ff86ad97a559bd736687 Mon Sep 17 00:00:00 2001
From: Scharel
Date: Mon, 12 Apr 2021 18:58:39 +0200
Subject: [PATCH] Worked on Nextcloud API capabilities (not tested\!)
---
qml/harbour-nextcloudnotes.qml | 12 ++--
qml/pages/AboutPage.qml | 2 +-
qml/pages/EditPage.qml | 2 +-
qml/pages/GPLLicense.qml | 2 +-
qml/pages/LoginPage.qml | 4 +-
qml/pages/MITLicense.qml | 2 +-
qml/pages/NotePage.qml | 2 +-
qml/pages/NotesPage.qml | 10 +--
qml/pages/SettingsPage.qml | 2 +-
qml/pages/SyntaxPage.qml | 2 +-
qml/pages/UnencryptedDialog.qml | 2 +-
src/harbour-nextcloudnotes.cpp | 2 +
src/nextcloudapi.cpp | 92 ++++++++++++++++++++++++--
src/nextcloudapi.h | 79 +++++++++++++---------
translations/harbour-nextcloudnotes.ts | 22 +++---
15 files changed, 164 insertions(+), 73 deletions(-)
diff --git a/qml/harbour-nextcloudnotes.qml b/qml/harbour-nextcloudnotes.qml
index 80d6a7c..c761498 100644
--- a/qml/harbour-nextcloudnotes.qml
+++ b/qml/harbour-nextcloudnotes.qml
@@ -30,13 +30,10 @@ ApplicationWindow
}
onSortByChanged: {
- if (sortBy == "none")
- notesProxyModel.invalidate()
- else
- notesProxyModel.sortRole = notesModel.roleFromName(sortBy)
+ if (sortBy == "none") notesModel.invalidate()
}
onFavoritesOnTopChanged: {
- notesProxyModel.favoritesOnTop = favoritesOnTop
+ notesModel.favoritesOnTop = favoritesOnTop
}
function createAccount(username, password, url, name) {
@@ -138,7 +135,7 @@ ApplicationWindow
}
Nextcloud {
- id: nextcloud
+ id: notesApi
server: account.url
username: account.username
password: account.passowrd
@@ -155,5 +152,6 @@ ApplicationWindow
initialPage: Component { NotesPage { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml")
- allowedOrientations: defaultAllowedOrientations
+
+ allowedOrientations: debug ? Orientation.All : defaultAllowedOrientations
}
diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml
index 25830b5..809691c 100644
--- a/qml/pages/AboutPage.qml
+++ b/qml/pages/AboutPage.qml
@@ -89,5 +89,5 @@ Page {
VerticalScrollDecorator {}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/EditPage.qml b/qml/pages/EditPage.qml
index 5a6084e..b0ee9e2 100644
--- a/qml/pages/EditPage.qml
+++ b/qml/pages/EditPage.qml
@@ -179,5 +179,5 @@ Dialog {
VerticalScrollDecorator {}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/GPLLicense.qml b/qml/pages/GPLLicense.qml
index 4db6145..5e31981 100644
--- a/qml/pages/GPLLicense.qml
+++ b/qml/pages/GPLLicense.qml
@@ -111,5 +111,5 @@ NO WARRANTY
}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/LoginPage.qml b/qml/pages/LoginPage.qml
index 76dff4c..8bb3cb6 100644
--- a/qml/pages/LoginPage.qml
+++ b/qml/pages/LoginPage.qml
@@ -1,7 +1,7 @@
import QtQuick 2.2
import Sailfish.Silica 1.0
import Nemo.Configuration 1.0
-import NextcloudNotes 1.0
+import NextcloudApi 1.0
Dialog {
id: loginDialog
@@ -308,5 +308,5 @@ Dialog {
}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/MITLicense.qml b/qml/pages/MITLicense.qml
index 476ff57..67a6343 100644
--- a/qml/pages/MITLicense.qml
+++ b/qml/pages/MITLicense.qml
@@ -44,5 +44,5 @@ SOFTWARE.
"
}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/NotePage.qml b/qml/pages/NotePage.qml
index 85f14f5..cbff9ab 100644
--- a/qml/pages/NotePage.qml
+++ b/qml/pages/NotePage.qml
@@ -261,5 +261,5 @@ Dialog {
VerticalScrollDecorator {}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/NotesPage.qml b/qml/pages/NotesPage.qml
index 1ba99dd..3857a76 100644
--- a/qml/pages/NotesPage.qml
+++ b/qml/pages/NotesPage.qml
@@ -38,7 +38,7 @@ Page {
MenuItem {
text: notesApi.networkAccessible && !notesApi.busy ? qsTr("Reload") : qsTr("Updating...")
enabled: account !== null && notesApi.networkAccessible && !notesApi.busy
- onClicked: notes.getAllNotes()
+ onClicked: notesApi.getAllNotes()
}
MenuLabel {
visible: account !== null
@@ -59,7 +59,7 @@ Page {
EnterKey.iconSource: "image://theme/icon-m-enter-close"
EnterKey.onClicked: focus = false
onTextChanged: {
- notesProxyModel.searchFilter = text
+ notesModel.searchFilter = text
}
}
Label {
@@ -84,7 +84,7 @@ Page {
currentIndex: -1
- model: notesProxyModel
+ model: notesModel
delegate: BackgroundItem {
id: note
@@ -238,7 +238,7 @@ Page {
ViewPlaceholder {
id: noSearchPlaceholder
- enabled: notesList.count === 0 && notesProxyModel.searchFilter !== "" //notesProxyModel.filterRegExp !== ""
+ enabled: notesList.count === 0 && notesModel.searchFilter !== "" //notesModel.filterRegExp !== ""
text: qsTr("No result")
hintText: qsTr("Try another query")
}
@@ -266,5 +266,5 @@ Page {
VerticalScrollDecorator { flickable: notesList }
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml
index 09575ca..d7ef555 100644
--- a/qml/pages/SettingsPage.qml
+++ b/qml/pages/SettingsPage.qml
@@ -231,5 +231,5 @@ Page {
VerticalScrollDecorator {}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/SyntaxPage.qml b/qml/pages/SyntaxPage.qml
index 8b6aba2..3e563a4 100644
--- a/qml/pages/SyntaxPage.qml
+++ b/qml/pages/SyntaxPage.qml
@@ -394,5 +394,5 @@ Use HTML Tags to format your text."
}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/qml/pages/UnencryptedDialog.qml b/qml/pages/UnencryptedDialog.qml
index 20ec87b..95faeac 100644
--- a/qml/pages/UnencryptedDialog.qml
+++ b/qml/pages/UnencryptedDialog.qml
@@ -39,5 +39,5 @@ Dialog {
}
}
- allowedOrientations: defaultAllowedOrientations
+ allowedOrientations: appWindow.allowedOrientations
}
diff --git a/src/harbour-nextcloudnotes.cpp b/src/harbour-nextcloudnotes.cpp
index efb26c1..aa372f0 100644
--- a/src/harbour-nextcloudnotes.cpp
+++ b/src/harbour-nextcloudnotes.cpp
@@ -17,6 +17,7 @@ int main(int argc, char *argv[])
qDebug() << app->applicationDisplayName() << app->applicationVersion();
+ NotesProxyModel* notesProxyModel = new NotesProxyModel;
AccountHash* accountHash = new AccountHash;
qmlRegisterType("NextcloudApi", 1, 0, "Nextcloud");
@@ -26,6 +27,7 @@ int main(int argc, char *argv[])
#else
view->rootContext()->setContextProperty("debug", QVariant(false));
#endif
+ view->rootContext()->setContextProperty("notesModel", notesProxyModel);
view->rootContext()->setContextProperty("accountHash", accountHash);
view->setSource(SailfishApp::pathTo("qml/harbour-nextcloudnotes.qml"));
diff --git a/src/nextcloudapi.cpp b/src/nextcloudapi.cpp
index 5376050..986631c 100644
--- a/src/nextcloudapi.cpp
+++ b/src/nextcloudapi.cpp
@@ -15,6 +15,9 @@ NextcloudApi::NextcloudApi(QObject *parent) : QObject(parent)
m_status_needsDbUpgrade = false;
m_status_extendedSupport = false;
+ // Add capabilities functions for server apps
+ m_appCapabilities["notes"] = &NextcloudApi::updateNoteCapabilities;
+
// Login Flow V2 poll timer
m_loginPollTimer.setInterval(LOGIN_FLOWV2_POLL_INTERVALL);
connect(&m_loginPollTimer, SIGNAL(timeout()), this, SLOT(pollLoginUrl()));
@@ -118,6 +121,15 @@ void NextcloudApi::setPort(int port) {
}
}
+void NextcloudApi::setPath(QString path) {
+ if (path != m_url.path()) {
+ m_url.setPath(path);
+ emit pathChanged(m_url.path());
+ emit serverChanged(server());
+ emit urlChanged(m_url);
+ }
+}
+
void NextcloudApi::setUsername(QString username) {
if (username != m_url.userName()) {
m_url.setUserName(username);
@@ -142,13 +154,23 @@ void NextcloudApi::setPassword(QString password) {
}
}
-void NextcloudApi::setPath(QString path) {
- if (path != m_url.path()) {
- m_url.setPath(path);
- emit pathChanged(m_url.path());
- emit serverChanged(server());
- emit urlChanged(m_url);
+bool NextcloudApi::notesAppInstalled() const {
+ QJsonObject notes = m_capabilities.value("notes").toObject();
+ return !notes.isEmpty();
+}
+
+QStringList NextcloudApi::notesAppApiVersions() const {
+ QStringList versions;
+ QJsonObject notes = m_capabilities.value("notes").toObject();
+ if (!notes.isEmpty()) {
+ QJsonArray apiVersion = notes.value("api_version").toArray();
+ QJsonArray::const_iterator i;
+ for (i = apiVersion.constBegin(); i != apiVersion.constEnd(); ++i) {
+ if (i->isString())
+ versions << i->toString();
+ }
}
+ return versions;
}
const QString NextcloudApi::errorMessage(int error) const {
@@ -288,6 +310,18 @@ bool NextcloudApi::deleteAppPassword() {
return del(DEL_APPPASSWORD_ENDPOINT, true);
}
+bool NextcloudApi::getUserList() {
+ return false; // TODO
+}
+
+bool NextcloudApi::getUserMetaData(const QString& user) {
+ return false; // TODO
+}
+
+bool NextcloudApi::getCapabilities() {
+ return false; // TODO
+}
+
void NextcloudApi::verifyUrl(QUrl url) {
emit urlValidChanged(
url.isValid()&&
@@ -608,9 +642,53 @@ void NextcloudApi::setUserMetaStatus(ApiCallStatus status, bool *changed) {
}
bool NextcloudApi::updateCapabilities(const QJsonObject &json) {
+ QJsonObject ocs = json.value("ocs").toObject();
+ QJsonObject data = ocs.value("data").toObject();
+ QJsonObject preCapabilities = m_capabilities;
+ QJsonObject newCapabilities = data.value("capabilities").toObject();
+ if (!newCapabilities.isEmpty()) {
+ setCababilitiesStatus(ApiSuccess);
+ if (newCapabilities != preCapabilities) {
+ m_capabilities = newCapabilities;
+ QStringList apps = newCapabilities.keys();
+ QStringList::const_iterator app;
+ for (app = apps.constBegin(); app != apps.constEnd(); ++app) {
+ if (m_appCapabilities.contains(*app)) {
+ qDebug() << "Updating \"" << *app << "\" capabilities";
+ (this->*m_appCapabilities[*app])(newCapabilities.value(*app).toObject(), preCapabilities.value(*app).toObject());
+ }
+ else {
+ qDebug() << "Capabilities for " << *app << " not implemented!";
+ }
+ }
+ }
+ return true;
+ }
+ setCababilitiesStatus(ApiFailed);
+ return false;
+}
+
+void NextcloudApi::updateNoteCapabilities(const QJsonObject &newObject, const QJsonObject &preObject) {
+ qDebug() << "Updating \"notes\" capabilities";
+ if (newObject.isEmpty() != preObject.isEmpty())
+ emit notesAppInstalledChanged(notesAppInstalled());
+
+ QStringList preVersions;
+ QJsonArray preApiVersion = preObject.value("api_version").toArray();
+ QJsonArray::const_iterator i;
+ for (i = preApiVersion.constBegin(); i != preApiVersion.constEnd(); ++i) {
+ if (i->isString())
+ preVersions << i->toString();
+ }
+ if (preVersions != notesAppApiVersions())
+ emit notesAppApiVersionsChanged(notesAppApiVersions());
}
void NextcloudApi::setCababilitiesStatus(ApiCallStatus status, bool *changed) {
-
+ if (status != m_capabilitiesStatus) {
+ m_capabilitiesStatus = status;
+ if (changed) *changed = true;
+ emit capabilitiesStatusChanged(m_capabilitiesStatus);
+ }
}
diff --git a/src/nextcloudapi.h b/src/nextcloudapi.h
index 671577e..b3902ce 100644
--- a/src/nextcloudapi.h
+++ b/src/nextcloudapi.h
@@ -16,21 +16,24 @@
// Nextcloud instance information
const QString STATUS_ENDPOINT("/status.php");
-// Capabilites and users
+// OCS APIs endpoints
+// https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html
const QString CAPABILITIES_ENDPOINT("/ocs/v2.php/cloud/capabilities");
const QString LIST_USERS_ENDPOINT("/ocs/v2.php/cloud/users");
const QString USER_METADATA_ENDPOINT("/ocs/v2.php/cloud/users/%1");
-const QString USER_NOTIFICATION_ENDPOINT("/ocs/v2.php/cloud/capabilities");
+const QString USER_NOTIFICATION_ENDPOINT("/ocs/v2.php/apps/notifications/api/v2/notifications");
+const QString DIRECT_DOWNLOAD_ENDPOINT("/ocs/v2.php/apps/dav/api/v1/direct");
-// Login and authentication
+// Login Flow endpoints
+// https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html
const QString GET_APPPASSWORD_ENDPOINT("/ocs/v2.php/core/getapppassword");
const QString DEL_APPPASSWORD_ENDPOINT("/ocs/v2.php/core/apppassword");
const QString LOGIN_FLOWV2_ENDPOINT("/index.php/login/v2");
const int LOGIN_FLOWV2_MIN_VERSION = 16;
const int LOGIN_FLOWV2_POLL_INTERVALL = 5000;
-// Diredct Download
-const QString DIRECT_DOWNLOAD_ENDPOINT("/ocs/v2.php/apps/dav/api/v1/direct");
+class NextcloudApi;
+typedef void (NextcloudApi::*updateAppCapabilities)(const QJsonObject &newObject, const QJsonObject &preObject);
class NextcloudApi : public QObject
{
@@ -38,9 +41,9 @@ class NextcloudApi : public QObject
// Generic API properties
Q_PROPERTY(bool verifySsl READ verifySsl WRITE setVerifySsl NOTIFY verifySslChanged) // to allow selfsigned certificates
- Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // complete API URL = ://:@[:]/
+ Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // complete nextcloud URL = ://:@[:]/
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged) // url without username and password = ://[:]/
- // the following six properties will update the url and server properties and vice versa
+ // the following properties will update the url and server properties and vice versa
Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged)
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
@@ -49,10 +52,10 @@ class NextcloudApi : public QObject
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
// Networking status information
- Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
- Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
- Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged)
- Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
+ Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) // when all needed properties are set
+ Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged) // if the property url is valid
+ Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged) // when the device has connectivity
+ Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) // when an API call is currently running
// Nextcloud status (status.php), these properties will be automatically updated on changes of the generic properties
Q_PROPERTY(ApiCallStatus statusStatus READ statusStatus NOTIFY statusStatusChanged)
@@ -66,17 +69,21 @@ class NextcloudApi : public QObject
Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged)
// Login status
+ Q_PROPERTY(LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
Q_PROPERTY(bool loginFlowV2Possible READ loginFlowV2Possible NOTIFY loginFlowV2PossibleChanged)
- Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged)
- Q_PROPERTY(ApiCallStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
+ Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged) // will be set after initiateFlowV2Login() has been called. The URL needs to be opened in a browser or webview
// User(s) status
Q_PROPERTY(ApiCallStatus userListStatus READ userListStatus NOTIFY userListStatusChanged)
- Q_PROPERTY(QStringList userList READ userList NOTIFY userListChanged)
Q_PROPERTY(ApiCallStatus userMetaStatus READ userMetaStatus NOTIFY userMetaStatusChanged)
+ Q_PROPERTY(QStringList userList READ userList NOTIFY userListChanged)
+ // TODO property for user metadata
// Nextcloud capabilities
Q_PROPERTY(ApiCallStatus capabilitiesStatus READ capabilitiesStatus NOTIFY capabilitiesStatusChanged)
+ Q_PROPERTY(bool notesAppInstalled READ notesAppInstalled NOTIFY notesAppInstalledChanged)
+ Q_PROPERTY(QStringList notesAppApiVersions READ notesAppApiVersions NOTIFY notesAppApiVersionsChanged)
+ // TODO other property for server capabilities
public:
explicit NextcloudApi(QObject *parent = nullptr);
@@ -148,17 +155,19 @@ public:
bool statusExtendedSupport() const { return m_status_extendedSupport; }
// Login status
+ LoginStatus loginStatus() const { return m_loginStatus; }
bool loginFlowV2Possible() const { return QVersionNumber::fromString(statusVersion()) >= QVersionNumber(LOGIN_FLOWV2_MIN_VERSION); }
QUrl loginUrl() const { return m_loginUrl; }
- LoginStatus loginStatus() const { return m_loginStatus; }
// User(s) status
ApiCallStatus userListStatus() const { return m_userListStatus; }
- QStringList userList() const { return m_userList; }
ApiCallStatus userMetaStatus() const { return m_userMetaStatus; }
+ QStringList userList() const { return m_userList; }
// Nextcloud capabilities
ApiCallStatus capabilitiesStatus() const { return m_capabilitiesStatus; }
+ bool notesAppInstalled() const;
+ QStringList notesAppApiVersions() const;
enum ErrorCodes {
NoError,
@@ -229,6 +238,8 @@ signals:
// Nextcloud capabilities
void capabilitiesStatusChanged(ApiCallStatus status);
+ void notesAppInstalledChanged(bool installed);
+ void notesAppApiVersionsChanged(QStringList versions);
// API helper updates
void getFinished(QNetworkReply* reply);
@@ -265,11 +276,18 @@ private:
QString m_status_productname;
bool m_status_extendedSupport;
- // Nextcloud capabilities
- bool updateCapabilities(const QJsonObject &json);
- void setCababilitiesStatus(ApiCallStatus status, bool *changed = NULL);
- ApiCallStatus m_capabilitiesStatus;
- QJsonObject m_capabilities;
+ // Nextcloud Login Flow v2
+ // https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
+ bool updateLoginFlow(const QJsonObject &json);
+ bool updateLoginCredentials(const QJsonObject &json);
+ bool updateAppPassword(const QJsonObject &json);
+ bool deleteAppPassword(const QJsonObject &json);
+ void setLoginStatus(LoginStatus status, bool *changed = NULL);
+ LoginStatus m_loginStatus;
+ QTimer m_loginPollTimer;
+ QUrl m_loginUrl;
+ QUrl m_pollUrl;
+ QString m_pollToken;
// Nextcloud users list
bool updateUserList(const QJsonObject &json);
@@ -283,18 +301,13 @@ private:
ApiCallStatus m_userMetaStatus;
QHash m_userMeta;
- // Nextcloud Login Flow v2
- // https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
- bool updateLoginFlow(const QJsonObject &json);
- bool updateLoginCredentials(const QJsonObject &json);
- bool updateAppPassword(const QJsonObject &json);
- bool deleteAppPassword(const QJsonObject &json);
- void setLoginStatus(LoginStatus status, bool *changed = NULL);
- LoginStatus m_loginStatus;
- QTimer m_loginPollTimer;
- QUrl m_loginUrl;
- QUrl m_pollUrl;
- QString m_pollToken;
+ // Nextcloud capabilities
+ bool updateCapabilities(const QJsonObject &json);
+ QMap m_appCapabilities;
+ void updateNoteCapabilities(const QJsonObject &newObject, const QJsonObject &preObject = QJsonObject());
+ void setCababilitiesStatus(ApiCallStatus status, bool *changed = NULL);
+ ApiCallStatus m_capabilitiesStatus;
+ QJsonObject m_capabilities;
};
#endif // NEXTCLOUDAPI_H
diff --git a/translations/harbour-nextcloudnotes.ts b/translations/harbour-nextcloudnotes.ts
index 5152263..170c864 100644
--- a/translations/harbour-nextcloudnotes.ts
+++ b/translations/harbour-nextcloudnotes.ts
@@ -275,32 +275,32 @@
NextcloudApi
-
+
-
+
-
+
-
+
-
+
-
+
@@ -912,27 +912,27 @@ You can also use other markdown syntax inside them.
harbour-nextcloudnotes
-
+
-
+
-
+
-
+
-
+