diff --git a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.cpp b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.cpp index 810820b60..63a28c333 100644 --- a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.cpp +++ b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.cpp @@ -145,8 +145,7 @@ public: void setUploadedOnly(bool val) { if (m_uploadedOnly != val) { m_uploadedOnly = val; - // invalidateFilter(); // CRASH FIX: Do not invalidate incrementally. - // The dialog calls model->update() which triggers a full Reset. + invalidateFilter(); } } @@ -229,7 +228,7 @@ SharedFilesDialog::~SharedFilesDialog() * Constructor for the base SharedFilesDialog. */ SharedFilesDialog::SharedFilesDialog(bool remote_mode, QWidget *parent) - : RsAutoUpdatePage(1000,parent), model(NULL), uploadedOnly_CB(NULL) + : RsAutoUpdatePage(1000,parent), model(NULL) { /* Invoke the Qt Designer generated object setup routine */ ui.setupUi(this); @@ -350,17 +349,7 @@ SharedFilesDialog::SharedFilesDialog(bool remote_mode, QWidget *parent) LocalSharedFilesDialog::LocalSharedFilesDialog(QWidget *parent) : SharedFilesDialog(false,parent) { - // Label updated to "Popular files" - uploadedOnly_CB = new QCheckBox(tr("Popular files"), this); - - // Updated tooltip to be more descriptive of the "Popularity" (upload activity) - uploadedOnly_CB->setToolTip(tr("Show only files and folders that have been successfully uploaded to other peers")); - - // Positioning the checkbox next to the view selector - int cbIndex = ui.horizontalLayout_2->indexOf(ui.viewType_CB); - ui.horizontalLayout_2->insertWidget(cbIndex + 1, uploadedOnly_CB); - - connect(uploadedOnly_CB, SIGNAL(toggled(bool)), this, SLOT(filterUploadedOnlyToggled(bool))); + connect(ui.uploadedOnly_CB, SIGNAL(toggled(bool)), this, SLOT(filterUploadedOnlyToggled(bool))); // Ensure proper columns are visible for local sharing ui.dirTreeView->setColumnHidden(SHARED_FILES_DIALOG_COLUMN_WN_VISU_DIR, false) ; @@ -400,6 +389,7 @@ RemoteSharedFilesDialog::RemoteSharedFilesDialog(QWidget *parent) // changeCurrentViewModel(ui.viewType_CB->currentIndex()) ; + ui.uploadedOnly_CB->hide(); ui.addShares_PB->hide() ; } @@ -524,9 +514,9 @@ void SharedFilesDialog::changeCurrentViewModel(int viewTypeIndex) proxyModel = flat_proxyModel ; } - if(uploadedOnly_CB) { - if(tree_proxyModel) tree_proxyModel->setUploadedOnly(uploadedOnly_CB->isChecked()); - if(flat_proxyModel) flat_proxyModel->setUploadedOnly(uploadedOnly_CB->isChecked()); + if(ui.uploadedOnly_CB) { + if(tree_proxyModel) tree_proxyModel->setUploadedOnly(ui.uploadedOnly_CB->isChecked()); + if(flat_proxyModel) flat_proxyModel->setUploadedOnly(ui.uploadedOnly_CB->isChecked()); } showProperColumns() ; @@ -561,7 +551,7 @@ void SharedFilesDialog::changeCurrentViewModel(int viewTypeIndex) FilterItems(); // MODIFICATION: Expand tree if "Uploaded Only" is active, otherwise items remain hidden in collapsed folders. - if(viewTypeIndex==VIEW_TYPE_TREE && uploadedOnly_CB && uploadedOnly_CB->isChecked()) { + if(viewTypeIndex==VIEW_TYPE_TREE && ui.uploadedOnly_CB && ui.uploadedOnly_CB->isChecked()) { expandAll(); } } @@ -1669,12 +1659,16 @@ void SharedFilesDialog::filterUploadedOnlyToggled(bool checked) if (tree_proxyModel) tree_proxyModel->setUploadedOnly(checked); if (flat_proxyModel) flat_proxyModel->setUploadedOnly(checked); - // CRITICAL FIX: Use model->update() instead of proxy->invalidate(). - // model->update() triggers a full reset (beginResetModel), which forces - // the Proxy to drop its dangerous mapping and rebuild it from scratch. + // CRITICAL PERFORMANCE FIX: DO NOT call model->update() here. + // model->update() triggers a full reset and re-crawl of the entire share hierarchy, + // which takes 30+ seconds for large shares. + // Instead, we rely on proxyModel->invalidateFilter() (called above) which + // is near-instant as it uses the existing O(1) cache. + /* if (model) { model->update(); } + */ if(ui.viewType_CB->currentIndex() == VIEW_TYPE_TREE) { if (checked) expandAll(); diff --git a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.h b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.h index 7d41287e6..2f99e465b 100644 --- a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.h +++ b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.h @@ -158,8 +158,7 @@ protected: RsEventsHandlerId_t mEventHandlerId ; - /** Checkbox to filter files with 0 upload */ - QCheckBox *uploadedOnly_CB; + /** Checkbox to filter files with 0 upload - now in ui.uploadedOnly_CB */ }; class LocalSharedFilesDialog : public SharedFilesDialog diff --git a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.ui b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.ui index 4c8f8c38b..e9eb8bdfb 100644 --- a/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.ui +++ b/retroshare-gui/src/gui/FileTransfer/SharedFilesDialog.ui @@ -236,6 +236,16 @@ border-image: url(:/images/closepressed.png) + + + + Show only files and folders that have been successfully uploaded to other peers + + + Popular files + + + diff --git a/retroshare-gui/src/gui/RemoteDirModel.cpp b/retroshare-gui/src/gui/RemoteDirModel.cpp index a17eeaef7..4cd7cca4f 100644 --- a/retroshare-gui/src/gui/RemoteDirModel.cpp +++ b/retroshare-gui/src/gui/RemoteDirModel.cpp @@ -58,7 +58,7 @@ #define REMOTEDIRMODEL_COLUMN_UPLOADED 6 #define REMOTEDIRMODEL_COLUMN_COUNT 7 -static const uint32_t FLAT_VIEW_MAX_REFS_PER_SECOND = 10000 ; +static const uint32_t FLAT_VIEW_MAX_REFS_PER_SECOND = 1000 ; static const size_t FLAT_VIEW_MAX_REFS_TABLE_SIZE = 30000 ; // static const uint32_t FLAT_VIEW_MIN_DELAY_BETWEEN_UPDATES = 120 ; // dont rebuild ref list more than every 2 mins. @@ -151,9 +151,7 @@ void RetroshareDirModel::treeStyle() void TreeStyle_RDM::recalculateDirectoryTotals() { - m_folderUploadTotals.clear(); - m_folderFileTotals.clear(); - m_folderSizeTotals.clear(); + m_folderTotals.clear(); // Stats are primarily calculated for local files. // For remote files, branch stats are limited by what the core provides. @@ -187,9 +185,7 @@ TreeStyle_RDM::FolderStats TreeStyle_RDM::collectStatsRecursive(void* ref) // Special handling for Extra Files (virtual root) if (details.type == DIR_TYPE_EXTRA_FILE) { - m_folderUploadTotals["!!RS_EXTRA_FILES_ROOT!!"] += stats.uploads; - m_folderFileTotals["!!RS_EXTRA_FILES_ROOT!!"] += stats.count; - m_folderSizeTotals["!!RS_EXTRA_FILES_ROOT!!"] += stats.size; + m_folderTotals["!!RS_EXTRA_FILES_ROOT!!"] += stats; } return stats; @@ -200,21 +196,14 @@ TreeStyle_RDM::FolderStats TreeStyle_RDM::collectStatsRecursive(void* ref) { for(const auto& child : details.children) { - FolderStats childStats = collectStatsRecursive(child.ref); - - stats.size += childStats.size; - stats.count += childStats.count; - stats.uploads += childStats.uploads; + stats += collectStatsRecursive(child.ref); } // If this is a real directory, store the aggregated totals if(details.type == DIR_TYPE_DIR) { QString path = QDir::cleanPath(QString::fromUtf8(details.path.c_str())); - - m_folderUploadTotals[path] = stats.uploads; - m_folderFileTotals[path] = stats.count; - m_folderSizeTotals[path] = stats.size; + m_folderTotals[path] = stats; } } @@ -238,12 +227,14 @@ bool TreeStyle_RDM::hasUploads(void *ref) const } if (details.type == DIR_TYPE_PERSON && details.id != rsPeers->getOwnId()) { - return m_folderUploadTotals.value("!!RS_EXTRA_FILES_ROOT!!", 0) > 0; + auto it = m_folderTotals.find("!!RS_EXTRA_FILES_ROOT!!"); + return (it != m_folderTotals.end()) ? (it->second.uploads > 0) : false; } if (details.type == DIR_TYPE_DIR) { QString path = QDir::cleanPath(QString::fromUtf8(details.path.c_str())); - return m_folderUploadTotals.value(path, 0) > 0; + auto it = m_folderTotals.find(path); + return (it != m_folderTotals.end()) ? (it->second.uploads > 0) : false; } return true; @@ -251,11 +242,11 @@ bool TreeStyle_RDM::hasUploads(void *ref) const void TreeStyle_RDM::update() { + preMods() ; // Recalculate totals before notifying view update recalculateDirectoryTotals(); - preMods() ; postMods() ; } void TreeStyle_RDM::updateRef(const QModelIndex& indx) const @@ -588,14 +579,19 @@ void FlatStyle_RDM::update() // Previously, this was guarded by if(_needs_update), preventing filter updates. // if(_needs_update) { - if(mUpdating) return; + if(mUpdating) + { + } - { - RS_STACK_MUTEX(_ref_mutex); - m_cache.clear(); - } + // MODIFICATION: Do NOT clear cache here. + // We want hasUploads() to be fast even during the re-crawl. + // updateRefs will overwrite cache entries as it finds files. + // { + // RS_STACK_MUTEX(_ref_mutex); + // m_cache.clear(); + // } preMods() ; postMods() ; @@ -638,21 +634,35 @@ QVariant FlatStyle_RDM::data(const QModelIndex &index, int role) const if (!index.isValid()) return QVariant(); - if (role == Qt::DisplayRole) + if (role == Qt::DisplayRole || role == RetroshareDirModel::SortRole) { void *ref = index.internalPointer(); - + // Try Lock-less read first? No, use lock for safety matching updateRefs RS_STACK_MUTEX(_ref_mutex); - if (m_cache.contains(ref)) + auto it = m_cache.find(ref); + if (it != m_cache.end()) { - const CachedFileDetails &cfd = m_cache[ref]; - switch(index.column()) + const CachedFileDetails &cfd = it.value(); + if (role == Qt::DisplayRole) { - case REMOTEDIRMODEL_COLUMN_NAME: return cfd.name; - case REMOTEDIRMODEL_COLUMN_SIZE: return cfd.sizeStr; - case REMOTEDIRMODEL_COLUMN_AGE: return cfd.ageStr; - case REMOTEDIRMODEL_COLUMN_UPLOADED: return cfd.uploadStr; + switch(index.column()) + { + case REMOTEDIRMODEL_COLUMN_NAME: return cfd.name; + case REMOTEDIRMODEL_COLUMN_SIZE: return cfd.sizeStr; + case REMOTEDIRMODEL_COLUMN_AGE: return cfd.ageStr; + case REMOTEDIRMODEL_COLUMN_UPLOADED: return cfd.uploadStr; + } + } + else // SortRole + { + switch(index.column()) + { + case REMOTEDIRMODEL_COLUMN_NAME: return cfd.name; + case REMOTEDIRMODEL_COLUMN_SIZE: return (qulonglong)cfd.size; + case REMOTEDIRMODEL_COLUMN_AGE: return (qulonglong)cfd.mtime; + case REMOTEDIRMODEL_COLUMN_UPLOADED: return (qulonglong)cfd.uploads; + } } } } @@ -702,7 +712,10 @@ QVariant TreeStyle_RDM::sortRole(const QModelIndex& /*index*/,const DirDetails& else if(details.id == rsPeers->getOwnId()) rsFiles->getSharedDirStatistics(rsPeers->getOwnId(),stats) ; else - return (qulonglong) m_folderFileTotals.value("!!RS_EXTRA_FILES_ROOT!!", 0); + { + auto it = m_folderTotals.find("!!RS_EXTRA_FILES_ROOT!!"); + return (it != m_folderTotals.end()) ? (qulonglong)it->second.count : (qulonglong)0; + } return (qulonglong) stats.total_number_of_files; } @@ -713,7 +726,10 @@ QVariant TreeStyle_RDM::sortRole(const QModelIndex& /*index*/,const DirDetails& else if(details.id == rsPeers->getOwnId()) rsFiles->getSharedDirStatistics(rsPeers->getOwnId(),stats) ; else - return (qulonglong) m_folderSizeTotals.value("!!RS_EXTRA_FILES_ROOT!!", 0); + { + auto it = m_folderTotals.find("!!RS_EXTRA_FILES_ROOT!!"); + return (it != m_folderTotals.end()) ? (qulonglong)it->second.size : (qulonglong)0; + } return (qulonglong) stats.total_shared_size; } @@ -758,9 +774,15 @@ QVariant TreeStyle_RDM::sortRole(const QModelIndex& /*index*/,const DirDetails& case REMOTEDIRMODEL_COLUMN_NAME: return QString::fromUtf8(details.name.c_str()); case REMOTEDIRMODEL_COLUMN_FILENB: - return (qulonglong) m_folderFileTotals.value(path, 0); + { + auto it = m_folderTotals.find(path); + return (it != m_folderTotals.end()) ? (qulonglong)it->second.count : (qulonglong)0; + } case REMOTEDIRMODEL_COLUMN_SIZE: - return (qulonglong) m_folderSizeTotals.value(path, 0); + { + auto it = m_folderTotals.find(path); + return (it != m_folderTotals.end()) ? (qulonglong)it->second.size : (qulonglong)0; + } case REMOTEDIRMODEL_COLUMN_AGE: return details.max_mtime; case REMOTEDIRMODEL_COLUMN_FRIEND_ACCESS: @@ -1212,9 +1234,17 @@ void FlatStyle_RDM::postMods() { time_t now = time(NULL); - // MODIFICATION: Removed throttling because it caused stuck model state (beginReset without endReset) - // if(_last_update + FLAT_VIEW_MIN_DELAY_BETWEEN_UPDATES > now) - // return ; + // MODIFICATION: Bypass throttle if we are in search mode (mFilteredPointers not empty) + // ALSO bypass if we just cleared the filter (to avoid 120s delay to show all files) + static bool was_filtered = false; + bool is_filtered = !mFilteredPointers.empty(); + bool transition_to_unfiltered = (was_filtered && !is_filtered); + was_filtered = is_filtered; + + if(!is_filtered && !transition_to_unfiltered && _last_update + FLAT_VIEW_MIN_DELAY_BETWEEN_UPDATES > now) + { + return ; + } if(visible()) { @@ -1521,14 +1551,12 @@ void RetroshareDirModel::getFilePaths(const QModelIndexList &list, std::list& keywords, uint32_t& found) { FileSearchFlags flags = RemoteMode ? RS_FILE_HINTS_REMOTE : RS_FILE_HINTS_LOCAL; - - std::list result_list ; found = 0 ; + std::list result_list ; if(keywords.empty()) { mFilteredPointers.clear(); - // MODIFICATION: Call update to refresh the view when the filter is cleared update(); return ; } @@ -1580,9 +1608,6 @@ void RetroshareDirModel::filterItems(const std::list& keywords, uin } } - // MODIFICATION: Restore the update call. - // This triggers beginResetModel/endResetModel which notifies the view - // that row counts and visibility have changed. update(); } @@ -1614,7 +1639,8 @@ QVariant TreeStyle_RDM::displayRole(const DirDetails& details, int coln) const rsFiles->getSharedDirStatistics(rsPeers->getOwnId(), stats) ; else { // Specific handling for "Temporary shared files" node (Extra files root) - uint32_t nb = m_folderFileTotals.value("!!RS_EXTRA_FILES_ROOT!!", 0); + auto it = m_folderTotals.find("!!RS_EXTRA_FILES_ROOT!!"); + uint32_t nb = (it != m_folderTotals.end()) ? it->second.count : 0; if(nb > 1) return QString::number(nb) + " " + tr("Files"); if(nb == 1) return QString::number(nb) + " " + tr("File"); return tr("Empty"); @@ -1634,7 +1660,10 @@ QVariant TreeStyle_RDM::displayRole(const DirDetails& details, int coln) const else if(details.id == rsPeers->getOwnId()) rsFiles->getSharedDirStatistics(rsPeers->getOwnId(),stats) ; else - return misc::friendlyUnit(m_folderSizeTotals.value("!!RS_EXTRA_FILES_ROOT!!", 0)); + { + auto it = m_folderTotals.find("!!RS_EXTRA_FILES_ROOT!!"); + return misc::friendlyUnit((it != m_folderTotals.end()) ? it->second.size : 0); + } if(stats.total_shared_size > 0) return misc::friendlyUnit(stats.total_shared_size) ; @@ -1706,17 +1735,22 @@ QVariant TreeStyle_RDM::displayRole(const DirDetails& details, int coln) const case REMOTEDIRMODEL_COLUMN_NAME: return QString::fromUtf8(details.name.c_str()); case REMOTEDIRMODEL_COLUMN_FILENB: { - uint32_t totalFiles = m_folderFileTotals.value(path, 0); + auto it = m_folderTotals.find(path); + uint32_t totalFiles = (it != m_folderTotals.end()) ? it->second.count : 0; return QString::number(totalFiles) + " " + (totalFiles > 1 ? tr("Files") : tr("File")); } case REMOTEDIRMODEL_COLUMN_SIZE: - return misc::friendlyUnit(m_folderSizeTotals.value(path, 0)); + { + auto it = m_folderTotals.find(path); + return misc::friendlyUnit((it != m_folderTotals.end()) ? it->second.size : 0); + } case REMOTEDIRMODEL_COLUMN_AGE: return misc::timeRelativeToNow(details.max_mtime); case REMOTEDIRMODEL_COLUMN_FRIEND_ACCESS: return getFlagsString(details.flags); case REMOTEDIRMODEL_COLUMN_WN_VISU_DIR: return getGroupsString(details.flags,details.parent_groups) ; case REMOTEDIRMODEL_COLUMN_UPLOADED: { - uint64_t totalUpload = m_folderUploadTotals.value(path, 0); + auto it = m_folderTotals.find(path); + uint64_t totalUpload = (it != m_folderTotals.end()) ? it->second.uploads : 0; return totalUpload > 0 ? misc::friendlyUnit(totalUpload) : ""; } break; @@ -1844,49 +1878,27 @@ void FlatStyle_RDM::updateRefs() RS_STACK_MUTEX(_ref_mutex) ; // MODIFICATION: instant search result display - // If we have a filter active, use the filtered set directly instead of crawling. - if (!mFilteredPointers.empty()) + // If we have a filter active, initialize the batch stack with the results. + if (!mFilteredPointers.empty() && (_ref_stack.empty() || (_ref_stack.size() == 1 && _ref_stack.front() == NULL))) { - _ref_stack.clear(); // Stop any pending crawl - _ref_entries.clear(); - + _ref_stack.clear(); + uint32_t added = 0; for(auto it = mFilteredPointers.begin(); it != mFilteredPointers.end(); ++it) { - void* ref = *it; - DirDetails details; - // Only add files to the flat list - if(requestDirDetails(ref, RemoteMode, details)) + if (*it != NULL && *it != (void*)0x1) { - if(details.type == DIR_TYPE_FILE || details.type == DIR_TYPE_EXTRA_FILE) - { - _ref_entries.push_back(ref); - - CachedFileDetails cfd; - cfd.name = QString::fromUtf8(details.name.c_str()); - cfd.sizeStr = misc::friendlyUnit(details.size); - - if(details.type == DIR_TYPE_FILE) - cfd.ageStr = misc::timeRelativeToNow(details.max_mtime); - else { - FileInfo fi; - if (rsFiles->FileDetails(details.hash, RS_FILE_HINTS_EXTRA , fi)) - cfd.ageStr = misc::timeRelativeToNow((rstime_t)fi.age-(30 * 3600 * 24)); - } - - uint64_t x = rsFiles->getCumulativeUpload(details.hash); - cfd.uploadStr = x ? misc::friendlyUnit(x) : QString(); - - m_cache[ref] = cfd; - } + _ref_stack.push_back(*it); + added++; } } - - _needs_update = false; // We are done } - else - { - // Standard crawling behavior (when no filter or cleared) - while( !_ref_stack.empty() && (_ref_entries.size() <= FLAT_VIEW_MAX_REFS_TABLE_SIZE) ) + } + + { + RS_STACK_MUTEX(_ref_mutex) ; + + // Standard batched behavior (handles both crawling and filtered results) + while( !_ref_stack.empty() && (_ref_entries.size() <= FLAT_VIEW_MAX_REFS_TABLE_SIZE) ) { void *ref = _ref_stack.back() ; #ifdef RDM_DEBUG @@ -1896,6 +1908,16 @@ void FlatStyle_RDM::updateRefs() DirDetails details ; + auto it_cache = m_cache.find(ref); + if (it_cache != m_cache.end()) + { + _ref_entries.push_back(ref); + // We still need to find children if it's a directory, + // but only files are in m_cache. + // So if it's in cache, we KNOW it's a file and has no children. + continue; + } + if (requestDirDetails(ref, RemoteMode,details)) { if(details.type == DIR_TYPE_FILE || details.type == DIR_TYPE_EXTRA_FILE) // only push files, not directories nor persons. @@ -1916,14 +1938,24 @@ void FlatStyle_RDM::updateRefs() uint64_t x = rsFiles->getCumulativeUpload(details.hash); cfd.uploadStr = x ? misc::friendlyUnit(x) : QString(); + cfd.uploads = x; + cfd.hasUploads = (x > 0); + cfd.size = details.size; + cfd.mtime = details.max_mtime; m_cache[ref] = cfd; } #ifdef RDM_DEBUG std::cerr << "FlatStyle_RDM::postMods(): adding ref " << ref << std::endl; #endif - for(uint32_t i=0;i #include +#include struct DirDetails; @@ -223,10 +224,6 @@ class TreeStyle_RDM: public RetroshareDirModel // Helper to calculate total recursive statistics per directory (Files count, Size, Uploads) void recalculateDirectoryTotals(); - QHash m_folderUploadTotals; - QHash m_folderFileTotals; // Total files in branch - QHash m_folderSizeTotals; // Total size of branch - private: struct FolderStats { uint64_t size; @@ -234,8 +231,17 @@ class TreeStyle_RDM: public RetroshareDirModel uint64_t uploads; FolderStats() : size(0), count(0), uploads(0) {} + + FolderStats& operator+=(const FolderStats& s) { + size += s.size; + count += s.count; + uploads += s.uploads; + return *this; + } }; - + + std::map m_folderTotals; // Key: clean path, Val: recursive stats + FolderStats collectStatsRecursive(void* ref); protected: @@ -294,6 +300,10 @@ class FlatStyle_RDM: public RetroshareDirModel QString sizeStr; QString ageStr; QString uploadStr; + uint64_t size; + uint32_t mtime; + uint64_t uploads; + bool hasUploads; }; QHash m_cache;