diff --git a/syncthingconnector/syncthingconnection.h b/syncthingconnector/syncthingconnection.h index 1fb6ef6..f527d71 100644 --- a/syncthingconnector/syncthingconnection.h +++ b/syncthingconnector/syncthingconnection.h @@ -53,7 +53,7 @@ enum class SyncthingItemType { Unknown, File, Directory }; struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItem { QString name; - CppUtilities::DateTime modificationTime; + CppUtilities::DateTime modificationTime = CppUtilities::DateTime(); std::size_t size = std::size_t(); SyncthingItemType type = SyncthingItemType::Unknown; std::vector children; @@ -254,7 +254,7 @@ public Q_SLOTS: public: // methods to GET or POST information from/to Syncthing (non-slots) QMetaObject::Connection browse( - const QString &dirId, const QString &prefix, int level, std::function &&)> &&callback); + const QString &dirId, const QString &prefix, int level, std::function &&, QString &&error)> &&callback); Q_SIGNALS: void newConfig(const QJsonObject &rawConfig); @@ -369,7 +369,7 @@ private Q_SLOTS: private: // handler to evaluate results from request...() methods - void readBrowse(const QString &dirId, int levels, std::function &&)> &&callback); + void readBrowse(const QString &dirId, int levels, std::function &&, QString &&)> &&callback); // internal helper methods struct Reply { diff --git a/syncthingconnector/syncthingconnection_requests.cpp b/syncthingconnector/syncthingconnection_requests.cpp index 5097c73..fa87090 100644 --- a/syncthingconnector/syncthingconnection_requests.cpp +++ b/syncthingconnector/syncthingconnection_requests.cpp @@ -1590,8 +1590,7 @@ void SyncthingConnection::readRevert() * consume results of a specific request. Errors are still reported via the error() signal so there's no extra error handling * required. Note that \a callback is *not* invoked in the error case. */ -QMetaObject::Connection SyncthingConnection::browse( - const QString &dirId, const QString &prefix, int levels, std::function &&)> &&callback) +QMetaObject::Connection SyncthingConnection::browse(const QString &dirId, const QString &prefix, int levels, std::function &&, QString &&)> &&callback) { auto query = QUrlQuery(); query.addQueryItem(QStringLiteral("folder"), formatQueryItem(dirId)); @@ -1643,9 +1642,10 @@ static void readSyncthingItems(const QJsonArray &array, std::vector &&)> &&callback) +void SyncthingConnection::readBrowse(const QString &dirId, int levels, std::function &&, QString &&)> &&callback) { auto const [reply, response] = prepareReply(); if (!reply) { @@ -1657,18 +1657,25 @@ void SyncthingConnection::readBrowse(const QString &dirId, int levels, std::func auto jsonError = QJsonParseError(); const auto replyDoc = QJsonDocument::fromJson(response, &jsonError); if (jsonError.error != QJsonParseError::NoError) { - emit error(tr("Unable to parse response for browsing \"%1\": ").arg(dirId) + jsonError.errorString(), SyncthingErrorCategory::Parsing, - QNetworkReply::NoError); + auto errorMessage = tr("Unable to parse response for browsing \"%1\": ").arg(dirId) + jsonError.errorString(); + emit error(errorMessage, SyncthingErrorCategory::Parsing, QNetworkReply::NoError); + if (callback) { + callback(std::move(items), std::move(errorMessage)); + } return; } readSyncthingItems(replyDoc.array(), items, 0, levels); if (callback) { - callback(std::move(items)); + callback(std::move(items), QString()); } break; } default: - emitError(tr("Unable to browse \"%1\": ").arg(dirId), SyncthingErrorCategory::SpecificRequest, reply); + auto errorMessage = tr("Unable to browse \"%1\": ").arg(dirId); + emitError(errorMessage, SyncthingErrorCategory::SpecificRequest, reply); + if (callback) { + callback(std::move(items), std::move(errorMessage)); + } } } diff --git a/syncthingmodel/syncthingfilemodel.cpp b/syncthingmodel/syncthingfilemodel.cpp index 566b732..303c0bc 100644 --- a/syncthingmodel/syncthingfilemodel.cpp +++ b/syncthingmodel/syncthingfilemodel.cpp @@ -8,20 +8,25 @@ #include +#include + using namespace std; using namespace CppUtilities; namespace Data { -SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const QString &dirId, QObject *parent) +SyncthingFileModel::SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent) : SyncthingModel(connection, parent) , m_connection(connection) - , m_dirId(dirId) + , m_dirId(dir.id) + , m_root({ .name = dir.displayName(), .modificationTime = dir.lastFileTime, .size = dir.globalStats.bytes, .type = SyncthingItemType::Directory }) { - m_connection.browse(m_dirId, QString(), 1, [this](std::vector &&items) { + m_connection.browse(m_dirId, QString(), 1, [this](std::vector &&items, QString &&errorMessage) { + Q_UNUSED(errorMessage) const auto last = items.size() - 1; - beginInsertRows(QModelIndex(), 0, last < std::numeric_limits::max() ? static_cast(last) : std::numeric_limits::max()); - m_items = std::move(items); + beginInsertRows(index(0, 0), 0, last < std::numeric_limits::max() ? static_cast(last) : std::numeric_limits::max()); + m_root.children = std::move(items); + m_root.childrenPopulated = true; endInsertRows(); }); } @@ -49,15 +54,13 @@ QModelIndex SyncthingFileModel::index(int row, int column, const QModelIndex &pa if (row < 0 || column < 0 || column > 2) { return QModelIndex(); } - if (!parent.isValid()) { - if (static_cast(row) >= m_items.size()) { - return QModelIndex(); - } - return createIndex(row, column, &m_items[static_cast(row)]); + return static_cast(row) ? QModelIndex() : createIndex(row, column, &m_root); } - auto *const parentItem = reinterpret_cast(parent.internalPointer()); + if (!parentItem) { + return QModelIndex(); + } auto &items = parentItem->children; if (static_cast(row) >= items.size()) { return QModelIndex(); @@ -67,6 +70,30 @@ QModelIndex SyncthingFileModel::index(int row, int column, const QModelIndex &pa return createIndex(row, column, &item); } +QModelIndex SyncthingFileModel::index(const QString &path) const +{ + auto parts = path.split(QChar('/'), Qt::SkipEmptyParts); + auto res = index(0, 0); + auto *parent = &m_root; + for (const auto &part : parts) { + auto foundPart = false; + for (const auto &child : parent->children) { + if (child.name == part) { + parent = &child; + res = index(static_cast(child.index), 0, res); + foundPart = true; + break; + } + } + if (!foundPart) { + res = QModelIndex(); + return res; + } + } + std::cerr << "index for path " << path.toStdString() << ": " << this->path(res).toStdString() << '\n'; + return res; +} + QString SyncthingFileModel::path(const QModelIndex &index) const { auto res = QString(); @@ -77,6 +104,10 @@ QString SyncthingFileModel::path(const QModelIndex &index) const auto size = QString::size_type(); parts.reserve(reinterpret_cast(index.internalPointer())->level + 1); for (auto i = index; i.isValid(); i = i.parent()) { + const auto *const item = reinterpret_cast(i.internalPointer()); + if (item == &m_root) { + break; + } parts.append(reinterpret_cast(i.internalPointer())->name); size += parts.back().size(); } @@ -94,10 +125,10 @@ QModelIndex SyncthingFileModel::parent(const QModelIndex &child) const return QModelIndex(); } auto *const childItem = reinterpret_cast(child.internalPointer()); - if (!childItem->parent) { + if (!childItem) { return QModelIndex(); } - return createIndex(static_cast(childItem->index), 0, childItem->parent); + return !childItem->parent ? QModelIndex() : createIndex(static_cast(childItem->index), 0, childItem->parent); } QVariant SyncthingFileModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -192,7 +223,7 @@ int SyncthingFileModel::rowCount(const QModelIndex &parent) const { auto res = std::size_t(); if (!parent.isValid()) { - res = m_items.size(); + res = 1; } else { auto *const parentItem = reinterpret_cast(parent.internalPointer()); res = parentItem->childrenPopulated || parentItem->type != SyncthingItemType::Directory ? parentItem->children.size() : 1; @@ -230,7 +261,7 @@ void SyncthingFileModel::fetchMore(const QModelIndex &parent) if (!parent.isValid()) { return; } - m_fetchQueue.append(parent); + m_fetchQueue.append(path(parent)); if (m_fetchQueue.size() == 1) { processFetchQueue(); } @@ -261,19 +292,32 @@ void SyncthingFileModel::processFetchQueue() if (m_fetchQueue.isEmpty()) { return; } - const auto &parent = m_fetchQueue.front(); - m_pendingRequest = m_connection.browse(m_dirId, path(parent), 1, [this, parent](std::vector &&items) { - auto *const parentItem = reinterpret_cast(parent.internalPointer()); - addLevel(items, parentItem->level); - beginRemoveRows(parent, 0, static_cast(parentItem->children.size() - 1)); - parentItem->children.clear(); - endRemoveRows(); - const auto last = items.size() - 1; - beginInsertRows(parent, 0, last < std::numeric_limits::max() ? static_cast(last) : std::numeric_limits::max()); - parentItem->children = std::move(items); - parentItem->childrenPopulated = true; - endInsertRows(); - m_fetchQueue.removeAll(parent); + const auto &path = m_fetchQueue.front(); + m_pendingRequest = m_connection.browse(m_dirId, path, 1, [this, p = path](std::vector &&items, QString &&errorMessage) { + Q_UNUSED(errorMessage) + m_fetchQueue.removeAll(p); + + const auto refreshedIndex = index(p); + if (!refreshedIndex.isValid()) { + return; + } + auto *const refreshedItem = reinterpret_cast(refreshedIndex.internalPointer()); + if (!refreshedItem->children.empty()) { + beginRemoveRows(refreshedIndex, 0, static_cast(refreshedItem->children.size() - 1)); + refreshedItem->children.clear(); + endRemoveRows(); + } + if (!items.empty()) { + const auto last = items.size() - 1; + addLevel(items, refreshedItem->level); + for (auto &item : items) { + item.parent = refreshedItem; + } + beginInsertRows(refreshedIndex, 0, last < std::numeric_limits::max() ? static_cast(last) : std::numeric_limits::max()); + refreshedItem->children = std::move(items); + refreshedItem->childrenPopulated = true; + endInsertRows(); + } processFetchQueue(); }); } diff --git a/syncthingmodel/syncthingfilemodel.h b/syncthingmodel/syncthingfilemodel.h index d1931cf..e03a031 100644 --- a/syncthingmodel/syncthingfilemodel.h +++ b/syncthingmodel/syncthingfilemodel.h @@ -3,23 +3,24 @@ #include "./syncthingmodel.h" +#include + #include namespace Data { -struct SyncthingItem; - class LIB_SYNCTHING_MODEL_EXPORT SyncthingFileModel : public SyncthingModel { Q_OBJECT public: enum SyncthingFileModelRole { NameRole = SyncthingModelUserRole + 1, SizeRole, ModificationTimeRole, Actions, ActionNames, ActionIcons }; - explicit SyncthingFileModel(SyncthingConnection &connection, const QString &dirId, QObject *parent = nullptr); + explicit SyncthingFileModel(SyncthingConnection &connection, const SyncthingDir &dir, QObject *parent = nullptr); ~SyncthingFileModel() override; public Q_SLOTS: QHash roleNames() const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex index(const QString &path) const; QModelIndex parent(const QModelIndex &child) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -44,9 +45,9 @@ private: private: SyncthingConnection &m_connection; QString m_dirId; - QModelIndexList m_fetchQueue; + QStringList m_fetchQueue; QMetaObject::Connection m_pendingRequest; - mutable std::vector m_items; + SyncthingItem m_root; }; } // namespace Data diff --git a/syncthingmodel/syncthingicons.cpp b/syncthingmodel/syncthingicons.cpp index 919c416..d7ce686 100644 --- a/syncthingmodel/syncthingicons.cpp +++ b/syncthingmodel/syncthingicons.cpp @@ -278,7 +278,7 @@ QString StatusIconSettings::toString() const StatusIcons::StatusIcons(const StatusIconSettings &settings) : disconnected( - QIcon(renderSvgImage(makeSyncthingIcon(settings.disconnectedColor, StatusEmblem::None, settings.strokeWidth), settings.renderSize))) + QIcon(renderSvgImage(makeSyncthingIcon(settings.disconnectedColor, StatusEmblem::None, settings.strokeWidth), settings.renderSize))) , idling(QIcon(renderSvgImage(makeSyncthingIcon(settings.idleColor, StatusEmblem::None, settings.strokeWidth), settings.renderSize))) , scanninig(QIcon(renderSvgImage(makeSyncthingIcon(settings.scanningColor, StatusEmblem::Scanning, settings.strokeWidth), settings.renderSize))) , notify(QIcon(renderSvgImage(makeSyncthingIcon(settings.warningColor, StatusEmblem::Alert, settings.strokeWidth), settings.renderSize))) diff --git a/syncthingwidgets/misc/otherdialogs.cpp b/syncthingwidgets/misc/otherdialogs.cpp index d167ab2..1a0a8f1 100644 --- a/syncthingwidgets/misc/otherdialogs.cpp +++ b/syncthingwidgets/misc/otherdialogs.cpp @@ -88,7 +88,7 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da dlg->setAttribute(Qt::WA_DeleteOnClose); // setup model/view - auto model = new Data::SyncthingFileModel(connection, dir.id, &connection); + auto model = new Data::SyncthingFileModel(connection, dir, &connection); auto view = new QTreeView(dlg); view->setModel(model); view->setContextMenuPolicy(Qt::CustomContextMenu); diff --git a/testhelper/helper.h b/testhelper/helper.h index 0427fb2..2b604de 100644 --- a/testhelper/helper.h +++ b/testhelper/helper.h @@ -178,7 +178,8 @@ public: #endif } // register own handler to detect whether signal has been emitted - m_emittedConnection = QObject::connect(sender, signal, sender, [this] { m_signalEmitted = true; }, Qt::DirectConnection); + m_emittedConnection = QObject::connect( + sender, signal, sender, [this] { m_signalEmitted = true; }, Qt::DirectConnection); #ifndef SYNCTHINGTESTHELPER_FOR_CLI if (!m_emittedConnection) { CPPUNIT_FAIL(argsToString("Unable to connect signal ", signalName().data(), " to check for signal emmitation"));