diff --git a/data/filehandler.h b/data/filehandler.h index 6fd6019..c9bb097 100644 --- a/data/filehandler.h +++ b/data/filehandler.h @@ -3,7 +3,7 @@ #include -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; class QJsonDocument; class QString; diff --git a/formats/csvparser.cpp b/formats/csvparser.cpp index 365de1d..85e5bac 100644 --- a/formats/csvparser.cpp +++ b/formats/csvparser.cpp @@ -86,7 +86,7 @@ QHash> CsvParser::extractColumnValues( const rapidcsv::Document& doc) { QHash> columnValueMap; for (const QString& columnName : headerNames) { - // NEXT add support for optional columns + // TODO add support for optional columns // if (optionalCsvHeaderNames.contains(columnName)) { // const std::vector columnNames = doc.GetColumnNames(); // int columnIdx = doc.GetColumnIdx(columnName.toStdString()); @@ -147,7 +147,7 @@ QVariant CsvParser::parseItemValue(const int role, const std::string& valueStrin result = doubleValue; // } else if (typeColumns.contains(columnName)) { - // // NEXT validate string is allowed + // // TODO validate string is allowed // values[role] = QString::fromStdString(columnValueMap.value(columnName).at(row)); } else { /// no type recognized for column diff --git a/formats/csvparser.h b/formats/csvparser.h index 7695156..b1375d9 100644 --- a/formats/csvparser.h +++ b/formats/csvparser.h @@ -3,7 +3,7 @@ #include -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; namespace rapidcsv { class Document; diff --git a/formats/jsonparser.cpp b/formats/jsonparser.cpp index a29f8e7..686f4bc 100644 --- a/formats/jsonparser.cpp +++ b/formats/jsonparser.cpp @@ -6,20 +6,35 @@ #include "../model/metadata.h" QList JsonParser::toItemValuesList(const QByteArray& jsonData, - const QString& objectName) { + const QString& rootValueName) { QList result; if (jsonData.isEmpty()) { return result; } - QJsonArray itemArray = extractItemArray(jsonData, objectName); + // NEXT tidy up the following code and encapsulate into functions; - foreach (QJsonValue value, itemArray) { - QJsonObject itemJsonObject = value.toObject(); + /// check if there is an array or there is only one object inside the root object; + // TODO ? rename objectName into jsonValueName? + if (rootValueName == ITEMS_KEY_STRING) { + QJsonArray itemArray = extractItemArray(jsonData, rootValueName); + + foreach (QJsonValue value, itemArray) { + QJsonObject itemJsonObject = value.toObject(); + ModelItemValues values = jsonObjectToItemValues(itemJsonObject); + result.append(values); + } + } + if (rootValueName == ITEM_KEY_STRING) { + // QJsonArray itemArray = extractItemArray(jsonData, objectName); + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + QJsonObject rootObject = doc.object(); + QJsonObject itemJsonObject = rootObject.value(rootValueName).toObject(); ModelItemValues values = jsonObjectToItemValues(itemJsonObject); result.append(values); } + return result; } @@ -28,7 +43,7 @@ QByteArray JsonParser::itemValuesListToJson(const QList& itemVa QJsonDocument jsonDoc; QJsonObject rootObject; QJsonArray itemArray; - for (const QHash& itemValues : itemValuesList) { + for (const ModelItemValues& itemValues : itemValuesList) { QJsonObject itemObject; QListIterator i(USER_FACING_ROLES); diff --git a/formats/jsonparser.h b/formats/jsonparser.h index be0fc6a..a27d223 100644 --- a/formats/jsonparser.h +++ b/formats/jsonparser.h @@ -8,14 +8,14 @@ class QString; class QByteArray; class QJsonArray; -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; using namespace std; class JsonParser { public: static QList toItemValuesList(const QByteArray& jsonData, - const QString& objectName = ""); + const QString& rootValueName = ""); static QByteArray itemValuesListToJson(const QList& itemValuesList, const QString& objectName = ""); diff --git a/genericcore.cpp b/genericcore.cpp index 707037f..539e6ab 100644 --- a/genericcore.cpp +++ b/genericcore.cpp @@ -105,7 +105,7 @@ void GenericCore::saveItems() { qDebug() << "saving items..."; const QJsonDocument doc = m_mainModel->getAllItemsAsJsonDoc(); - const bool successfulSave = FileHandler::saveToFile(doc, ITEM_FILE_NAME); + const bool successfulSave = FileHandler::saveToFile(doc, ITEMS_FILE_NAME); if (successfulSave) { m_modelUndoStack->setClean(); emit displayStatusMessage(QString("Items saved.")); @@ -118,7 +118,7 @@ void GenericCore::importCSVFile(const QString& filePath) { qInfo() << "importing items from CSV..."; qDebug() << "filePath:" << filePath; const QList itemValuesList = FileHandler::getItemValuesFromCSVFile(filePath); - // NEXT inform UI on errors + // TODO inform UI on errors if (itemValuesList.isEmpty()) { qDebug() << "No items found. Doing nothing..."; return; @@ -149,7 +149,7 @@ void GenericCore::onSendItemTriggered(const QByteArray& jsonData) { void GenericCore::onItemsFetched(const QByteArray jsonData) { emit displayStatusMessage("New items fetched."); // TODO ? check compability of JSON structure beforehand? - // TODO check if item already exists? + // NEXT check if item already exists ? ; m_mainModel->appendItems(jsonData); } @@ -157,8 +157,10 @@ void GenericCore::onItemsFetchFailure(const QString errorString) { emit displayStatusMessage(QString("Error: %1").arg(errorString)); } -void GenericCore::onPostRequestSuccessful(const QString message) { - // NEXT search local item an set server generated UUID +void GenericCore::onPostRequestSuccessful(const QByteArray responseData) { + // NEXT search local item and set server generated UUID; + const QString message = m_mainModel->updateItemsFromJson(responseData); + emit displayStatusMessage(message); } @@ -193,7 +195,7 @@ void GenericCore::setupModels() { */ void GenericCore::initModelData() { qInfo() << "Trying to read model data from file..."; - const QByteArray jsonDoc = FileHandler::loadJSONDataFromFile(ITEM_FILE_NAME); + const QByteArray jsonDoc = FileHandler::loadJSONDataFromFile(ITEMS_FILE_NAME); // qDebug() << "jsonDoc:" << jsonDoc; // TODO decide on lack of file(s) (config, data) if example items should be generated // (see welcome wizard) @@ -228,7 +230,7 @@ void GenericCore::setupServerConfiguration() { /// request connections connect(this, &GenericCore::fetchItemsFromServer, m_serverCommunicator.get(), &ServerCommunicator::fetchItems); - connect(this, &GenericCore::sendItemToServer, this, &GenericCore::onSendItemTriggered); + connect(this, &GenericCore::postItemToServer, this, &GenericCore::onSendItemTriggered); /// response connections connect(m_serverCommunicator.get(), &ServerCommunicator::itemsFetched, this, diff --git a/genericcore.h b/genericcore.h index 9fc1e2a..66877f9 100644 --- a/genericcore.h +++ b/genericcore.h @@ -39,13 +39,13 @@ class GenericCore : public QObject { void onSendItemTriggered(const QByteArray& jsonData); void onItemsFetched(const QByteArray jsonData); void onItemsFetchFailure(const QString errorString); - void onPostRequestSuccessful(const QString message); + void onPostRequestSuccessful(const QByteArray responseData); void onPostRequestFailure(const QString errorString); signals: void displayStatusMessage(QString message); void fetchItemsFromServer(); - void sendItemToServer(const QByteArray& jsonData); + void postItemToServer(const QByteArray& jsonData); private: QUndoStack* m_modelUndoStack; diff --git a/model/commands/edititemcommand.cpp b/model/commands/edititemcommand.cpp index 449272b..f9280f8 100644 --- a/model/commands/edititemcommand.cpp +++ b/model/commands/edititemcommand.cpp @@ -26,6 +26,11 @@ EditItemCommand::EditItemCommand(TableModel* model, .arg(roleName) .arg(index.data(DEFAULT_ROLE).toString()) .arg(value.toString()); + } else if (role == IdRole) { + commandText = QString("Setting '%1' of item '%2' to '%3'") + .arg(roleName) + .arg(index.data(DEFAULT_ROLE).toString()) + .arg(value.toString()); } else { qWarning() << "Role didn't match! Using a generic command text..."; commandText = QString("Edit item '%1'").arg(index.data(DEFAULT_ROLE).toString()); diff --git a/model/commands/insertrowscommand.h b/model/commands/insertrowscommand.h index ddc5617..64dff89 100644 --- a/model/commands/insertrowscommand.h +++ b/model/commands/insertrowscommand.h @@ -3,7 +3,7 @@ #include -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; class TableModel; diff --git a/model/commands/removerowscommand.h b/model/commands/removerowscommand.h index 6703aab..21b314e 100644 --- a/model/commands/removerowscommand.h +++ b/model/commands/removerowscommand.h @@ -5,7 +5,7 @@ class TableModel; -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; class RemoveRowsCommand : public QUndoCommand { public: diff --git a/model/metadata.h b/model/metadata.h index ce42c60..eadb1d0 100644 --- a/model/metadata.h +++ b/model/metadata.h @@ -35,10 +35,11 @@ static QList INT_ROLES = {AmountRole}; static QList DOUBLE_ROLES = {FactorRole}; /// JSON keys -static const QString ITEM_KEY_STRING = "items"; +static const QString ITEMS_KEY_STRING = "items"; +static const QString ITEM_KEY_STRING = "item"; /// file naming -static const QString ITEM_FILE_NAME = ITEM_KEY_STRING + ".json"; +static const QString ITEMS_FILE_NAME = ITEMS_KEY_STRING + ".json"; /// functions static int GET_ROLE_FOR_COLUMN(const int column) { diff --git a/model/modelitem.h b/model/modelitem.h index 5d2e956..1019296 100644 --- a/model/modelitem.h +++ b/model/modelitem.h @@ -3,7 +3,7 @@ #include -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; class ModelItem { public: @@ -18,7 +18,7 @@ class ModelItem { QJsonObject toJsonObject() const; private: - QHash m_values; + ModelItemValues m_values; }; #endif // MODELITEM_H diff --git a/model/tablemodel.cpp b/model/tablemodel.cpp index 6d97e03..0b3460f 100644 --- a/model/tablemodel.cpp +++ b/model/tablemodel.cpp @@ -29,7 +29,7 @@ QByteArray TableModel::generateExampleItems() { array.append(itemObject); } - rootObject.insert(ITEM_KEY_STRING, array); + rootObject.insert(ITEMS_KEY_STRING, array); doc.setObject(rootObject); return doc.toJson(); @@ -83,6 +83,7 @@ QVariant TableModel::data(const QModelIndex& index, int role) const { case InfoRole: case AmountRole: case FactorRole: + case IdRole: return m_items.at(row)->data(role); case ToStringRole: return m_items.at(row)->toString(); @@ -152,7 +153,7 @@ QJsonDocument TableModel::getAllItemsAsJsonDoc() const { QJsonObject itemObject = item->toJsonObject(); array.append(itemObject); } - rootObject.insert(ITEM_KEY_STRING, array); + rootObject.insert(ITEMS_KEY_STRING, array); doc.setObject(rootObject); return doc; @@ -175,11 +176,37 @@ QList TableModel::getItemsAsStringLists() const { QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const { const QJsonObject itemObject = data(currentIndex, ToJsonRole).toJsonObject(); QJsonObject rootObject; - rootObject.insert("item", itemObject); + rootObject.insert(ITEM_KEY_STRING, itemObject); const QJsonDocument jsonDoc(rootObject); return jsonDoc.toJson(QJsonDocument::Compact); } +QString TableModel::updateItemsFromJson(const QByteArray& jsonData) { + /// convert JSON data into a list of item values + const QList valueList = JsonParser::toItemValuesList(jsonData, ITEM_KEY_STRING); + /// for each item values: + // NEXT iterate over all value items in the list (or disable updating multiple items for now) + // for (ModelItemValues itemValues : valueList) { + // } + // NEXT encapsulate into updateItem(const ModelItemValues& itemValues) + QModelIndex foundIndex = searchItemIndex(valueList.first()); + + qDebug() << "Search done!"; + if (foundIndex == QModelIndex()) { + const QString errorMessage = "No matching item found!"; + qWarning() << errorMessage; + return errorMessage; + } else { + qInfo() << "Item found!"; + /// update existing item + QMap roles = valueList.first(); + setItemData(foundIndex, roles); + /// return status what happened in this function (i. e. "Created x items, updated y items.") + // return data(foundIndex, ToStringRole).toString(); + return QString("Item found at row %1.").arg(foundIndex.row()); + } +} + bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) { if (parentIndex != QModelIndex()) { qWarning() << "Removing of child rows is not supported yet!"; @@ -203,7 +230,7 @@ void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDo void TableModel::insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex) { - const QList valueList = JsonParser::toItemValuesList(jsonDoc, ITEM_KEY_STRING); + const QList valueList = JsonParser::toItemValuesList(jsonDoc, ITEMS_KEY_STRING); if (valueList.empty()) { /// don't try inserting if no values to insert @@ -302,3 +329,60 @@ bool TableModel::isEmptyValueEqualToZero(const int role) const { return false; } } + +QModelIndex TableModel::searchItemIndex(const ModelItemValues givenItemValues) const { + // iterate over indexes to search item : see searchItem(...); + // for (const shared_ptr& item : m_items) { + for (int row = 0; row < rowCount(); ++row) { + qDebug() << "Processing item at row" << row << "..."; + QModelIndex itemIndex = index(row, 0); + if (isItemEqualToItemValues(itemIndex, givenItemValues)) { + qInfo() << "Found item at row" << row << "! Returning its index..."; + return itemIndex; + } + } + qDebug() << "No matching item found. Returning empty pointer..."; + return {}; +} + +bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex, + const ModelItemValues givenItemValues) const { + /// do both have a UUID? + QVariant idOfItem = data(itemIndex, IdRole); + QVariant given = givenItemValues.value(IdRole); + if (idOfItem.isValid() && given.isValid()) { + /// are the UUIDs the same? + if (idOfItem.toString() == given.toString()) { + qInfo() << "UUIDs are the same."; + return true; + } else { + qDebug() << "UUIDs are NOT the same."; + return false; + } + } else { + /// are all other values the same? (for now only USER_FACING_ROLES are checked) + QListIterator i(USER_FACING_ROLES); + while (i.hasNext()) { + const UserRoles role = i.next(); + const QString roleName = ROLE_NAMES.value(role); + const QVariant valueOfItem = data(itemIndex, role); + const QVariant givenValue = givenItemValues.value(role); + if (STRING_ROLES.contains(role)) { + if (valueOfItem.toString() != givenValue.toString()) { + return false; + } + } else if (INT_ROLES.contains(role)) { + if (valueOfItem.toInt() != givenValue.toInt()) { + return false; + } + } else if (DOUBLE_ROLES.contains(role)) { + if (valueOfItem.toDouble() != givenValue.toDouble()) { + return false; + } + } else { + qCritical() << QString("Can't find data type for role %1!!!").arg(role); + } + } + return true; + } +} diff --git a/model/tablemodel.h b/model/tablemodel.h index 4017d55..84c592f 100644 --- a/model/tablemodel.h +++ b/model/tablemodel.h @@ -8,7 +8,7 @@ class ModelItem; using namespace std; -typedef QHash ModelItemValues; +typedef QMap ModelItemValues; class TableModel : public QAbstractTableModel { Q_OBJECT @@ -40,6 +40,8 @@ class TableModel : public QAbstractTableModel { QByteArray jsonDataForServer(const QModelIndex& currentIndex) const; + QString updateItemsFromJson(const QByteArray& jsonData); + public slots: // bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex()) // override; @@ -68,6 +70,9 @@ class TableModel : public QAbstractTableModel { QMap onlyChangedValues(const QModelIndex& index, const QMap& roleValueMap) const; bool isEmptyValueEqualToZero(const int role) const; + QModelIndex searchItemIndex(const ModelItemValues givenItemValues) const; + bool isItemEqualToItemValues(const QModelIndex& itemIndex, + const ModelItemValues givenItemValues) const; }; #endif // TABLEMODEL_H diff --git a/network/servercommunicator.cpp b/network/servercommunicator.cpp index feb41ea..a48dae1 100644 --- a/network/servercommunicator.cpp +++ b/network/servercommunicator.cpp @@ -68,7 +68,7 @@ void ServerCommunicator::postItems(const QByteArray& jsonData) { QByteArray responseData = reply->readAll(); const QString message = QString("POST successful! Response: %1").arg(responseData); qInfo() << message; - emit postRequestSuccessful(message); + emit postRequestSuccessful(responseData); } else { const QString message = QString("Error: %1").arg(reply->errorString()); qDebug() << message; diff --git a/network/servercommunicator.h b/network/servercommunicator.h index 257a4c8..6fdb7ba 100644 --- a/network/servercommunicator.h +++ b/network/servercommunicator.h @@ -26,7 +26,7 @@ class ServerCommunicator : public QObject { void itemsFetched(const QByteArray jsonDoc); void itemsFetchFailure(const QString errorString); - void postRequestSuccessful(const QString message); + void postRequestSuccessful(const QByteArray responseData); void postRequestFailure(const QString errorString); private: