Fix lookup of local items in filesystem model so it works on all levels

The local lookup needs to be done using the same depth as the database
lookup; otherwise not all levels are covered.
This commit is contained in:
Martchus 2024-06-22 21:59:52 +02:00
parent 5084a9a59e
commit 4bb23c84c3
2 changed files with 108 additions and 48 deletions

View File

@ -18,6 +18,7 @@
#include <QStringBuilder>
#include <QtConcurrent>
#include <cassert>
#include <limits>
using namespace std;
@ -794,6 +795,31 @@ void SyncthingFileModel::handleBrightColorsChanged()
invalidateAllIndicies(QVector<int>({ Qt::ForegroundRole }));
}
void SyncthingFileModel::lookupDirLocally(const QDir &dir, SyncthingFileModel::LocalItemMap &items, int depth)
{
const auto entries = dir.entryInfoList(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
for (const auto &entry : entries) {
const auto entryName = entry.fileName();
auto &item = items[entryName];
item.name = entryName;
item.existsInDb = false;
item.existsLocally = true;
item.size = static_cast<std::size_t>(entry.size());
item.modificationTime = DateTime::unixEpochStart() + TimeSpan(TimeSpan::ticksPerMillisecond * entry.lastModified().toMSecsSinceEpoch());
if (entry.isSymbolicLink()) {
item.type = SyncthingItemType::Symlink;
} else if (entry.isDir()) {
item.type = SyncthingItemType::Directory;
if (depth > 0) {
lookupDirLocally(QDir(entry.absoluteFilePath()), item.localChildren, depth - 1);
item.localChildrenPopulated = true;
}
} else {
item.type = SyncthingItemType::File;
}
}
}
void SyncthingFileModel::processFetchQueue(const QString &lastItemPath)
{
if (!lastItemPath.isNull()) {
@ -824,8 +850,8 @@ void SyncthingFileModel::processFetchQueue(const QString &lastItemPath)
= m_connection.browse(m_dirId, path, 1, [this](std::vector<std::unique_ptr<SyncthingItem>> &&items, QString &&errorMessage) mutable {
m_pendingRequest.reply = nullptr;
addErrorItem(items, std::move(errorMessage));
const auto refreshedIndex = index(m_pendingRequest.forPath);
if (!refreshedIndex.isValid()) {
processFetchQueue(m_pendingRequest.forPath);
return;
@ -879,24 +905,8 @@ void SyncthingFileModel::processFetchQueue(const QString &lastItemPath)
return;
}
m_pendingRequest.localLookup = QtConcurrent::run([dir = QDir(m_localPath % m_pathSeparator % path)] {
auto items = std::make_shared<std::map<QString, SyncthingItem>>();
auto entries = dir.entryInfoList(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
for (const auto &entry : entries) {
const auto entryName = entry.fileName();
auto &item = (*items)[entryName];
item.name = entryName;
item.existsInDb = false;
item.existsLocally = true;
item.size = static_cast<std::size_t>(entry.size());
item.modificationTime = DateTime::unixEpochStart() + TimeSpan(TimeSpan::ticksPerMillisecond * entry.lastModified().toMSecsSinceEpoch());
if (entry.isSymbolicLink()) {
item.type = SyncthingItemType::Symlink;
} else if (entry.isDir()) {
item.type = SyncthingItemType::Directory;
} else {
item.type = SyncthingItemType::File;
}
}
auto items = std::make_shared<LocalItemMap>();
lookupDirLocally(dir, *items);
return items;
});
if (!rootItem->existsInDb) {
@ -949,18 +959,36 @@ void SyncthingFileModel::matchItemAgainstIgnorePatterns(SyncthingItem &item) con
}
}
void SyncthingFileModel::handleLocalLookupFinished()
/*!
* \brief Marks items from the database query as locally existing if they do; marks items from local lookup as existing in the db if they do.
*/
void SyncthingFileModel::markItemsFromDatabaseAsLocallyExisting(
std::vector<std::unique_ptr<SyncthingItem>> &items, SyncthingFileModel::LocalItemMap &localItems)
{
for (auto &child : items) {
auto localItemIter = localItems.find(child->name);
if (localItemIter == localItems.end()) {
continue;
}
child->existsLocally = true;
localItemIter->second.existsInDb = true;
localItemIter->second.index = child->index;
markItemsFromDatabaseAsLocallyExisting(child->children, localItemIter->second.localChildren);
}
}
/*!
* \brief Inserts items from local lookup that are not already present via the database query (usually ignored files).
*/
void SyncthingFileModel::insertLocalItems(const QModelIndex &refreshedIndex, SyncthingFileModel::LocalItemMap &localItems)
{
// get refreshed index/item
const auto &refreshedIndex = m_pendingRequest.refreshedIndex;
if (!refreshedIndex.isValid()) {
processFetchQueue(m_pendingRequest.forPath);
return;
}
auto *const refreshedItem = reinterpret_cast<SyncthingItem *>(refreshedIndex.internalPointer());
auto &items = refreshedItem->children;
const auto previousChildCount = items.size();
refreshedItem->childrenPopulated = true;
if (!refreshedItem->existsInDb) {
refreshedItem->childrenPopulated = true;
}
// clear loading item
if (!refreshedItem->existsInDb && !items.empty()) {
@ -970,35 +998,23 @@ void SyncthingFileModel::handleLocalLookupFinished()
endRemoveRows();
}
// get result from local lookup
auto res = m_pendingRequest.localLookup.result();
if (!res || res->empty()) {
processFetchQueue(m_pendingRequest.forPath);
// skip if there are no local items to insert
if (localItems.empty()) {
return;
}
// mark items from the database query as locally existing if they do; mark items from local lookup as existing in the db if they do
auto &localItems = *res;
for (auto &child : items) {
auto localItemIter = localItems.find(child->name);
if (localItemIter == localItems.end()) {
continue;
}
child->existsLocally = true;
localItemIter->second.existsInDb = true;
}
// insert items from local lookup that are not already present via the database query (probably ignored files)
const auto last = items.size();
const auto firstRow = last < std::numeric_limits<int>::max() ? static_cast<int>(last) : std::numeric_limits<int>::max();
auto index = items.size();
auto row = index < std::numeric_limits<int>::max() ? static_cast<int>(index) : std::numeric_limits<int>::max();
auto firstRow = row;
for (auto &[localItemName, localItem] : localItems) {
if (localItem.existsInDb) {
continue;
}
beginInsertRows(refreshedIndex, firstRow, firstRow);
beginInsertRows(refreshedIndex, row, row);
auto &item = items.emplace_back(std::make_unique<SyncthingItem>(std::move(localItem)));
item->parent = refreshedItem;
item->index = last;
item->index = localItem.index = index++;
switch (refreshedItem->checked) {
case Qt::Checked:
setChildrenChecked(item.get(), item->checked = Qt::Checked);
@ -1010,15 +1026,49 @@ void SyncthingFileModel::handleLocalLookupFinished()
}
populatePath(item->path = refreshedItem->path % m_pathSeparator % item->name, m_pathSeparator, item->children);
endInsertRows();
++row;
}
if (refreshedItem->children.size() != previousChildCount) {
const auto sizeIndex = refreshedIndex.sibling(refreshedIndex.row(), 1);
emit dataChanged(sizeIndex, sizeIndex, QVector<int>{ Qt::DisplayRole });
}
if (firstRow < 0) {
emit dataChanged(index(0, 4, refreshedIndex), index(firstRow - 1, 4, refreshedIndex), QVector<int>{ Qt::DecorationRole });
// insert local child items recursively
for (auto &[localItemName, localItem] : localItems) {
if (!localItem.localChildrenPopulated) {
continue;
}
if (const auto childIndex = this->index(static_cast<int>(localItem.index), 0, refreshedIndex); childIndex.isValid()) {
assert(childIndex.data() == localItemName);
insertLocalItems(childIndex, localItem.localChildren);
}
}
// update global/local icons of items that were already present (at they exist in the database)
if (firstRow > 0) {
emit dataChanged(
this->index(0, 4, refreshedIndex), this->index(firstRow - 1, 4, refreshedIndex), QVector<int>{ Qt::DecorationRole, Qt::ToolTipRole });
}
}
/*!
* \brief Incorporates data found by the local lookup into the item-tree.
*/
void SyncthingFileModel::handleLocalLookupFinished()
{
// get refreshed index/item and result from local lookup
const auto &refreshedIndex = m_pendingRequest.refreshedIndex;
const auto res = m_pendingRequest.localLookup.result();
if (!refreshedIndex.isValid() || !res) {
processFetchQueue(m_pendingRequest.forPath);
return;
}
// update items
auto &items = reinterpret_cast<SyncthingItem *>(refreshedIndex.internalPointer())->children;
auto &localItems = *res;
markItemsFromDatabaseAsLocallyExisting(items, localItems);
insertLocalItems(refreshedIndex, localItems);
processFetchQueue(m_pendingRequest.forPath);
}

View File

@ -86,7 +86,17 @@ private:
private:
using SyncthingItems = std::vector<std::unique_ptr<SyncthingItem>>;
using LocalLookupRes = std::shared_ptr<std::map<QString, SyncthingItem>>;
struct LocalItem;
using LocalItemMap = std::map<QString, LocalItem>;
struct LocalItem : public SyncthingItem {
LocalItemMap localChildren;
bool localChildrenPopulated = false;
};
using LocalLookupRes = std::shared_ptr<LocalItemMap>;
static void lookupDirLocally(const QDir &dir, SyncthingFileModel::LocalItemMap &items, int depth = 1);
static void markItemsFromDatabaseAsLocallyExisting(
std::vector<std::unique_ptr<SyncthingItem>> &items, SyncthingFileModel::LocalItemMap &localItems);
void insertLocalItems(const QModelIndex &refreshedIndex, SyncthingFileModel::LocalItemMap &localItems);
struct QueryResult : SyncthingConnection::QueryResult {
QString forPath;
QFuture<LocalLookupRes> localLookup;