diff --git a/QtClient/JoplinQtClient/EditableListItem.qml b/QtClient/JoplinQtClient/EditableListItem.qml index e457186a7..d8e55adca 100755 --- a/QtClient/JoplinQtClient/EditableListItem.qml +++ b/QtClient/JoplinQtClient/EditableListItem.qml @@ -5,39 +5,78 @@ Item { id: root width: parent.width height: 25 + property int mouseAreaDefaultWidth + property Menu contextMenu + + 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() + } 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 - MouseArea { - anchors.fill: parent - onClicked: { - root.ListView.view.currentIndex = index - } - onDoubleClicked: { - label.visible = false - textField.visible = true - textField.focus = true - textField.selectAll() - textField.text = display - } - } } TextField { id: textField - text: display visible: false width: parent.width height: parent.height onAccepted: { - root.ListView.view.model.setData(index, text) + root.editingAccepted(index, text); } onEditingFinished: { - label.visible = true - textField.visible = false + 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(); + } + } + } diff --git a/QtClient/JoplinQtClient/FolderList.qml b/QtClient/JoplinQtClient/FolderList.qml index 95fbcae34..eeceecca3 100755 --- a/QtClient/JoplinQtClient/FolderList.qml +++ b/QtClient/JoplinQtClient/FolderList.qml @@ -6,6 +6,20 @@ Item { property alias model: listView.model property alias currentIndex: listView.currentIndex property alias currentItem: listView.currentItem + property string currentItemId + + signal stoppedEditing; + signal editingAccepted(int index, string text); + signal deleteButtonClicked(int index); + + function startEditing(index) { + currentIndex = model.rowCount() - 1; + currentItem.startEditing(); + } + + function stopEditing() { + currentItem.stopEditing(); + } Rectangle { color: "#eeeeff" @@ -14,22 +28,48 @@ Item { } ListView { + + Connections { + target: model + onDataChanged: { + if (currentItemId !== "") { + var newIndex = model.idToIndex(currentItemId); + currentIndex = newIndex + if (newIndex < 0) currentItemId = ""; + } + } + } + + onCurrentItemChanged: { + currentItemId = model.idAtIndex(currentIndex); + } + id: listView + highlightMoveVelocity: -1 + highlightMoveDuration: 100 anchors.fill: parent delegate: folderDelegate ScrollBar.vertical: ScrollBar { } highlight: Rectangle { color: "lightsteelblue"; radius: 5 } focus: true -// onModelChanged: { -//// listView.model.onDataChanged = function() { -//// console.info("testaaaaaaaaaaaaaaaaaaa") -//// } -// console.info("MODEL CHANGAID") -// } } Component { id: folderDelegate - EditableListItem {} + EditableListItem { + contextMenu: + Menu { + MenuItem { + text: "Delete" + onTriggered: deleteButtonClicked(currentIndex); + } + } + onStoppedEditing: { + root.stoppedEditing(); + } + onEditingAccepted: function(index, text) { + root.editingAccepted(index, text); + } + } } } diff --git a/QtClient/JoplinQtClient/JoplinQtClient.pro b/QtClient/JoplinQtClient/JoplinQtClient.pro index 5744be027..a9b892f99 100755 --- a/QtClient/JoplinQtClient/JoplinQtClient.pro +++ b/QtClient/JoplinQtClient/JoplinQtClient.pro @@ -16,7 +16,9 @@ SOURCES += \ webapi.cpp \ synchronizer.cpp \ settings.cpp \ - models/foldercollection.cpp + models/foldercollection.cpp \ + uuid.cpp \ + dispatcher.cpp RESOURCES += qml.qrc \ database.qrc @@ -45,7 +47,9 @@ HEADERS += \ synchronizer.h \ settings.h \ models/foldercollection.h \ - simpletypes.h + simpletypes.h \ + uuid.h \ + dispatcher.h DISTFILES += diff --git a/QtClient/JoplinQtClient/application.cpp b/QtClient/JoplinQtClient/application.cpp index bb64f1ee5..e439319ad 100755 --- a/QtClient/JoplinQtClient/application.cpp +++ b/QtClient/JoplinQtClient/application.cpp @@ -5,6 +5,8 @@ #include "models/foldermodel.h" #include "services/folderservice.h" #include "settings.h" +#include "uuid.h" +#include "dispatcher.h" using namespace jop; @@ -38,21 +40,27 @@ Application::Application(int &argc, char **argv) : connect(rootObject, SIGNAL(currentFolderChanged()), this, SLOT(view_currentFolderChanged())); connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged())); + connect(rootObject, SIGNAL(addFolderButtonClicked()), this, SLOT(view_addFolderButtonClicked())); view_.show(); connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&))); QString sessionId = settings.value("sessionId").toString(); - if (sessionId == "") { + //if (sessionId == "") { QUrlQuery postData; postData.addQueryItem("email", "laurent@cozic.net"); postData.addQueryItem("password", "12345678"); postData.addQueryItem("client_id", "B6E12222B6E12222"); api_.post("sessions", QUrlQuery(), postData, "getSession"); - } else { - afterSessionInitialization(); - } +// } else { +// afterSessionInitialization(); +// } + + + + //emit jop::dispatcher().folderCreated("test"); + //.folderCreated("tes"); } void Application::api_requestDone(const QJsonObject& response, const QString& tag) { @@ -91,7 +99,7 @@ void Application::afterSessionInitialization() { QString sessionId = settings.value("sessionId").toString(); qDebug() << "Session:" << sessionId; api_.setSessionId(sessionId); - //synchronizer_.start(); + synchronizer_.start(); } void Application::view_currentFolderChanged() { @@ -107,9 +115,18 @@ void Application::view_currentNoteChanged() { } void Application::view_addNoteButtonClicked() { - qDebug() << "ici"; + } 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"); + + //qDebug() << "Added" << q.lastInsertId().toString(); } diff --git a/QtClient/JoplinQtClient/database.cpp b/QtClient/JoplinQtClient/database.cpp index 777eaae5d..1b7481a76 100755 --- a/QtClient/JoplinQtClient/database.cpp +++ b/QtClient/JoplinQtClient/database.cpp @@ -5,7 +5,7 @@ using namespace jop; Database::Database(const QString &path) { version_ = -1; - //QFile::remove(path); + // QFile::remove(path); db_ = QSqlDatabase::addDatabase("QSQLITE"); db_.setDatabaseName(path); @@ -84,13 +84,13 @@ QSqlQuery Database::buildSqlQuery(Database::QueryType type, const QString &table } } - qDebug() <<"SQL:"< i(query.boundValues()); - while (i.hasNext()) { - i.next(); - qDebug() << i.key() << ":" << i.value().toString(); - } +// QMapIterator i(query.boundValues()); +// while (i.hasNext()) { +// i.next(); +// qDebug() << i.key() << ":" << i.value().toString(); +// } return query; } diff --git a/QtClient/JoplinQtClient/dispatcher.cpp b/QtClient/JoplinQtClient/dispatcher.cpp new file mode 100755 index 000000000..3cdde6188 --- /dev/null +++ b/QtClient/JoplinQtClient/dispatcher.cpp @@ -0,0 +1,17 @@ +#include "dispatcher.h" + +using namespace jop; + +Dispatcher::Dispatcher() { + +} + +Dispatcher instance_; + +Dispatcher& jop::dispatcher() { + return instance_; +} + +//Dispatcher &Dispatcher::instance() { +// return instance_; +//} diff --git a/QtClient/JoplinQtClient/dispatcher.h b/QtClient/JoplinQtClient/dispatcher.h new file mode 100755 index 000000000..e7dce1e44 --- /dev/null +++ b/QtClient/JoplinQtClient/dispatcher.h @@ -0,0 +1,29 @@ +#ifndef DISPATCHER_H +#define DISPATCHER_H + +namespace jop { + +class Dispatcher : public QObject { + + Q_OBJECT + +public: + + Dispatcher(); + //static Dispatcher& instance(); + +signals: + + void folderCreated(const QString& id); + +private: + + //static Dispatcher& instance_; + +}; + +Dispatcher& dispatcher(); + +} + +#endif // DISPATCHER_H diff --git a/QtClient/JoplinQtClient/main.qml b/QtClient/JoplinQtClient/main.qml index 267ebbe47..26fa340cb 100755 --- a/QtClient/JoplinQtClient/main.qml +++ b/QtClient/JoplinQtClient/main.qml @@ -32,6 +32,24 @@ Item { onCurrentItemChanged: { root.currentFolderChanged() } + + onEditingAccepted: function(index, text) { + if (folderList.model.virtualItemShown()) { + folderList.model.addData(text) + } else { + folderList.model.setData(index, text) + } + } + + onStoppedEditing: { + if (folderList.model.virtualItemShown()) { + folderList.model.hideVirtualItem(); + } + } + + onDeleteButtonClicked: { + folderList.model.deleteData(index) + } } NoteList { @@ -64,7 +82,10 @@ Item { id: addButton anchors.right: parent.right anchors.bottom: parent.bottom - onAddFolderButtonClicked: root.addFolderButtonClicked() + onAddFolderButtonClicked: { + folderList.model.showVirtualItem(); + folderList.startEditing(folderList.model.rowCount() - 1); + } onAddNoteButtonClicked: root.addNoteButtonClicked() } diff --git a/QtClient/JoplinQtClient/models/foldercollection.cpp b/QtClient/JoplinQtClient/models/foldercollection.cpp index ad892b956..888dc5514 100755 --- a/QtClient/JoplinQtClient/models/foldercollection.cpp +++ b/QtClient/JoplinQtClient/models/foldercollection.cpp @@ -1,5 +1,7 @@ #include "foldercollection.h" #include "databaseutils.h" +#include "dispatcher.h" +#include "uuid.h" using namespace jop; @@ -8,24 +10,35 @@ FolderCollection::FolderCollection(Database& db, const QString& parentId, const 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()) return cache_[index]; + if (cache_.size()) { + if (index < 0 || index >= count()) { + qWarning() << "Invalid folder index:" << index; + return Folder(); + } - QSqlQuery q = db_.query("SELECT id, title FROM folders ORDER BY " + orderBy_); + return cache_[index]; + } + + QSqlQuery q = db_.query("SELECT " + Folder::dbFields().join(",") + " FROM folders ORDER BY " + orderBy_); q.exec(); while (q.next()) { Folder folder; - folder.setId(q.value(0).toString()); - folder.setTitle(q.value(1).toString()); - + folder.fromSqlQuery(q); cache_.push_back(folder); } - - return at(index); + if (!cache_.size()) { + qWarning() << "Invalid folder index:" << index; + return Folder(); + } else { + return at(index); + } } // TODO: cache result @@ -37,19 +50,57 @@ int FolderCollection::count() const { } 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 folder; + if (folder.id() == id) return i; } - - qWarning() << "Invalid folder ID:" << id; - return Folder(); + return -1; } -void FolderCollection::update(const QString &id, const QStringList &fields, const VariantVector &values) { +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) { + +} diff --git a/QtClient/JoplinQtClient/models/foldercollection.h b/QtClient/JoplinQtClient/models/foldercollection.h index 7df672c06..e8aeac288 100755 --- a/QtClient/JoplinQtClient/models/foldercollection.h +++ b/QtClient/JoplinQtClient/models/foldercollection.h @@ -22,7 +22,11 @@ public: Folder at(int index) const; int count() const; Folder byId(const QString &id) const; - void update(const QString& id, const QStringList& fields, const VariantVector& values); + 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: @@ -35,6 +39,10 @@ signals: void changed(int from, int to, const QStringList& fields); +public slots: + + void dispatcher_folderCreated(const QString& id); + }; } diff --git a/QtClient/JoplinQtClient/models/foldermodel.cpp b/QtClient/JoplinQtClient/models/foldermodel.cpp index ba8765093..c7e433fc0 100755 --- a/QtClient/JoplinQtClient/models/foldermodel.cpp +++ b/QtClient/JoplinQtClient/models/foldermodel.cpp @@ -2,20 +2,27 @@ using namespace jop; -FolderModel::FolderModel(Database &database) : QAbstractListModel(), folderCollection_(database, 0, "title") { +FolderModel::FolderModel(Database &database) : QAbstractListModel(), folderCollection_(database, 0, "title"), db_(database), orderBy_("title") { + virtualItemShown_ = false; connect(&folderCollection_, SIGNAL(changed(int,int,const QStringList&)), this, SLOT(folderCollection_changed(int,int,const QStringList&))); } int FolderModel::rowCount(const QModelIndex & parent) const { Q_UNUSED(parent); - return folderCollection_.count(); + return folderCollection_.count() + (virtualItemShown_ ? 1 : 0); } // NOTE: to lazy load - send back "Loading..." if item not currently loaded // queue the item for loading. // Then batch load them a bit later. QVariant FolderModel::data(const QModelIndex & index, int role) const { - Folder folder = folderCollection_.at(index.row()); + Folder folder; + + if (virtualItemShown_ && index.row() == rowCount() - 1) { + folder.setTitle("Untitled"); + } else { + folder = folderCollection_.at(index.row()); + } if (role == Qt::DisplayRole) { return QVariant(folder.title()); @@ -32,6 +39,8 @@ bool FolderModel::setData(const QModelIndex &index, const QVariant &value, int r Folder folder = folderCollection_.at(index.row()); if (role == Qt::EditRole) { + emit dataChanging(); + QStringList fields; fields << "title"; VariantVector values; @@ -44,6 +53,30 @@ bool FolderModel::setData(const QModelIndex &index, const QVariant &value, int r return false; } +void FolderModel::showVirtualItem() { + virtualItemShown_ = true; + beginInsertRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1); + endInsertRows(); +} + +void FolderModel::hideVirtualItem() { + beginRemoveRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1); + virtualItemShown_ = false; + endRemoveRows(); +} + +QString FolderModel::idAtIndex(int index) const { + return data(this->index(index), IdRole).toString(); +} + +int FolderModel::idToIndex(const QString &id) const { + return folderCollection_.idToIndex(id); +} + +bool FolderModel::virtualItemShown() const { + return virtualItemShown_; +} + bool FolderModel::setData(int index, const QVariant &value, int role) { return setData(this->index(index), value, role); } @@ -56,8 +89,26 @@ QHash FolderModel::roleNames() const { return roles; } +void FolderModel::addData(const QString &title) { + emit dataChanging(); + + QStringList fields; + fields << "title"; + VariantVector values; + values << QVariant(title); + folderCollection_.add(fields, values); +} + +void FolderModel::deleteData(const int index) { + QString id = folderCollection_.indexToId(index); + folderCollection_.remove(id); +} + void FolderModel::folderCollection_changed(int from, int to, const QStringList& fields) { + beginRemoveRows(QModelIndex(), from, to); QVector roles; roles << Qt::DisplayRole; + qDebug() << "update" << from << to; emit dataChanged(this->index(from), this->index(to), roles); + endRemoveRows(); } diff --git a/QtClient/JoplinQtClient/models/foldermodel.h b/QtClient/JoplinQtClient/models/foldermodel.h index e34348ea1..762407850 100755 --- a/QtClient/JoplinQtClient/models/foldermodel.h +++ b/QtClient/JoplinQtClient/models/foldermodel.h @@ -23,11 +23,8 @@ public: FolderModel(Database& database); void addFolder(Folder* folder); - int rowCount(const QModelIndex & parent = QModelIndex()) const; - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); protected: @@ -38,11 +35,25 @@ private: QList folders_; FolderCollection folderCollection_; + bool virtualItemShown_; + QString orderBy_; + Database& db_; public slots: + void addData(const QString& title); + void deleteData(const int index); bool setData(int index, const QVariant &value, int role = Qt::EditRole); void folderCollection_changed(int from, int to, const QStringList &fields); + void showVirtualItem(); + bool virtualItemShown() const; + void hideVirtualItem(); + QString idAtIndex(int index) const; + int idToIndex(const QString& id) const; + +signals: + + void dataChanging(); }; diff --git a/QtClient/JoplinQtClient/models/item.cpp b/QtClient/JoplinQtClient/models/item.cpp index a611d2e7e..326d160f2 100755 --- a/QtClient/JoplinQtClient/models/item.cpp +++ b/QtClient/JoplinQtClient/models/item.cpp @@ -4,7 +4,6 @@ using namespace jop; Item::Item() { - isPartial_ = true; synced_ = false; } @@ -24,6 +23,10 @@ int Item::updatedTime() const { return updatedTime_; } +bool Item::synced() const { + return synced_; +} + void Item::setId(const QString& v) { id_ = v; } @@ -36,12 +39,8 @@ void Item::setCreatedTime(int v) { createdTime_ = v; } -void Item::setIsPartial(bool v) { - isPartial_ = v; -} - -bool Item::isPartial() const { - return isPartial_; +void Item::setSynced(bool v) { + synced_ = v; } QStringList Item::dbFields() { diff --git a/QtClient/JoplinQtClient/models/item.h b/QtClient/JoplinQtClient/models/item.h index 33b515836..7891b0ba3 100755 --- a/QtClient/JoplinQtClient/models/item.h +++ b/QtClient/JoplinQtClient/models/item.h @@ -15,13 +15,13 @@ public: QString title() const; int createdTime() const; int updatedTime() const; - bool isPartial() const; + bool synced() const; static QStringList dbFields(); void setId(const QString &v); void setTitle(const QString& v); void setCreatedTime(int v); - void setIsPartial(bool v); + void setSynced(bool v); void fromSqlQuery(const QSqlQuery& query); @@ -33,8 +33,6 @@ private: time_t updatedTime_; bool synced_; - bool isPartial_; - }; } diff --git a/QtClient/JoplinQtClient/models/notecollection.cpp b/QtClient/JoplinQtClient/models/notecollection.cpp index f339e1e0c..8c2e4a510 100755 --- a/QtClient/JoplinQtClient/models/notecollection.cpp +++ b/QtClient/JoplinQtClient/models/notecollection.cpp @@ -24,8 +24,6 @@ Note NoteCollection::at(int index) const { int from = indexes[0]; int to = indexes[indexes.size() - 1]; - qDebug() << "Getting from" << from << "to" << to; - 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(); @@ -36,7 +34,6 @@ Note NoteCollection::at(int index) const { note.setId(q.value(0).toString()); note.setTitle(q.value(1).toString()); note.setBody(q.value(2).toString()); - note.setIsPartial(true); cache_.set(noteIndex, note); diff --git a/QtClient/JoplinQtClient/schema.sql b/QtClient/JoplinQtClient/schema.sql index d88c659e6..629829ceb 100755 --- a/QtClient/JoplinQtClient/schema.sql +++ b/QtClient/JoplinQtClient/schema.sql @@ -64,6 +64,13 @@ CREATE TABLE version ( version INT ); +CREATE TABLE changes ( + id INTEGER PRIMARY KEY, + item_id TEXT, + item_type INT, + item_property TEXT +); + --CREATE TABLE mimetypes ( -- id INT, -- mime TEXT diff --git a/QtClient/JoplinQtClient/synchronizer.cpp b/QtClient/JoplinQtClient/synchronizer.cpp index 3f3c54ff1..10e5e2b03 100755 --- a/QtClient/JoplinQtClient/synchronizer.cpp +++ b/QtClient/JoplinQtClient/synchronizer.cpp @@ -44,6 +44,8 @@ void Synchronizer::start() { api_.put("folders/" + folder.id(), QUrlQuery(), data, "putFolder:" + folder.id()); } + return; + for (int i = 0; i < notes.size(); i++) { Note note = notes[i]; QUrlQuery data; @@ -68,10 +70,10 @@ void Synchronizer::api_requestDone(const QJsonObject& response, const QString& t } if (action == "putFolder") { - // qDebug() << "Done folder" << id; -// query = db_.query("UPDATE folders SET synced = 1 WHERE id = ?"); -// query.addBindValue(id); -// query.exec(); + qDebug() << "Synced folder" << id; + query = db_.query("UPDATE folders SET synced = 1 WHERE id = ?"); + query.addBindValue(id); + query.exec(); } if (action == "putNote") { diff --git a/QtClient/JoplinQtClient/uuid.cpp b/QtClient/JoplinQtClient/uuid.cpp index 5e3e79944..1162b363c 100755 --- a/QtClient/JoplinQtClient/uuid.cpp +++ b/QtClient/JoplinQtClient/uuid.cpp @@ -4,19 +4,11 @@ namespace jop { namespace uuid { -//QUuid fromString(const QString& s) { -// // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} -// QString mod = s; -// mod.insert(8, '-'); -// mod.insert(13, '-'); -// mod.insert(18, '-'); -// mod.insert(23, '-'); -// mod = "{" + mod + "}"; - -// //qDebug() << mod; - -// return QUuid(mod); -//} +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); +} } } diff --git a/QtClient/JoplinQtClient/uuid.h b/QtClient/JoplinQtClient/uuid.h index a5c55a6ff..fa5aba021 100755 --- a/QtClient/JoplinQtClient/uuid.h +++ b/QtClient/JoplinQtClient/uuid.h @@ -6,7 +6,7 @@ namespace jop { namespace uuid { -//QUuid fromString(const QString& s); +QString createUuid(QString s = ""); } } diff --git a/QtClient/JoplinQtClient/webapi.cpp b/QtClient/JoplinQtClient/webapi.cpp index 203cea511..b3c869c79 100755 --- a/QtClient/JoplinQtClient/webapi.cpp +++ b/QtClient/JoplinQtClient/webapi.cpp @@ -40,6 +40,13 @@ void WebApi::processQueue() { 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"); @@ -72,11 +79,12 @@ void WebApi::processQueue() { cmd << "curl"; if (r.method == QNetworkAccessManager::PutOperation) { cmd << "-X" << "PUT"; - cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'"; - cmd << url; } - //qDebug().noquote() << cmd.join(" "); + cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'"; + cmd << url; + + qDebug().noquote() << cmd.join(" "); inProgressRequests_.push_back(r); } diff --git a/src/AppBundle/Controller/ApiController.php b/src/AppBundle/Controller/ApiController.php index 0612fa69d..3d1d988e7 100755 --- a/src/AppBundle/Controller/ApiController.php +++ b/src/AppBundle/Controller/ApiController.php @@ -20,7 +20,7 @@ abstract class ApiController extends Controller { protected $session = null; protected $user = null; - private $useTestUserAndSession = true; + private $useTestUserAndSession = false; private $testClientNum = 1; public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null) { @@ -40,16 +40,21 @@ abstract class ApiController extends Controller { echo "\n"; } }); - + // HACK: get connection once here so that it's initialized and can // be accessed from models. $this->db = $this->get('app.eloquent')->connection(); $s = $this->session(); + // TODO: find less hacky way to get request path + $requestPath = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + $requestPath = ltrim($requestPath, '/'); + $requestPath = rtrim($requestPath, '?'); + // TODO: to keep it simple, only respond to logged in users, but in theory some data // could be public. - if (!$s || !$this->user()) throw new UnauthorizedException('A session and user are required'); + if ($requestPath != 'sessions' && (!$s || !$this->user())) throw new UnauthorizedException('A session and user are required'); BaseModel::setClientId($s ? $s->client_id : 0); } diff --git a/web/client.php b/web/client.php new file mode 100755 index 000000000..8c7dde000 --- /dev/null +++ b/web/client.php @@ -0,0 +1,38 @@ + 'http://joplin.local', + ); + if (isset($config[$name])) { + return $config[$name]; + } + throw new Exception('Unknown config: ' . $name); +} + +function execRequest($method, $path, $query = null, $data = null) { + $url = config('baseUrl') . '/' . $path; + if ($query) $url .= '?' . http_build_query($query); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + $response = curl_exec($ch); + curl_close($ch); + + $output = json_decode($response, true); + if ($output === null) { + return array('error' => 'Cannot decode JSON', 'body' => $response); + } + + return $output; +} + +$session = execRequest('POST', 'session', null, array( + 'email' => 'laurent@cozic.net', + 'password' => '12345678', +)); + +var_dump($session); + +die(); \ No newline at end of file