1
0
mirror of https://invent.kde.org/system/dolphin synced 2024-07-02 16:31:23 +00:00

DragAndDropHelper::updateDropAction: use StatJob for remote URLs

When dragging onto tabs/Places from a remote URL, we don't process
the QDropEvent immediately, but start a StatJob and process the
event when it finishes.

Also, the result of the StatJob is cached for 30 seconds, to avoid
starting duplicate jobs.
This commit is contained in:
Jin Liu 2024-02-29 23:13:47 +00:00
parent 992272f8c5
commit dc149ec5e5
6 changed files with 129 additions and 22 deletions

View File

@ -9,7 +9,6 @@
#include "dolphin_generalsettings.h"
#include "dolphintabbar.h"
#include "dolphinviewcontainer.h"
#include "views/draganddrophelper.h"
#include <KAcceleratorManager>
#include <KConfigGroup>
@ -26,6 +25,7 @@ DolphinTabWidget::DolphinTabWidget(DolphinNavigatorsWidgetAction *navigatorsWidg
: QTabWidget(parent)
, m_lastViewedTab(nullptr)
, m_navigatorsWidget{navigatorsWidget}
, m_dragAndDropHelper{this}
{
KAcceleratorManager::setNoAccel(this);
@ -394,7 +394,7 @@ void DolphinTabWidget::tabDragMoveEvent(int index, QDragMoveEvent *event)
{
if (index >= 0) {
DolphinView *view = tabPageAt(index)->activeViewContainer()->view();
DragAndDropHelper::updateDropAction(event, view->url());
m_dragAndDropHelper.updateDropAction(event, view->url());
}
}

View File

@ -9,6 +9,7 @@
#include "dolphinnavigatorswidgetaction.h"
#include "dolphintabpage.h"
#include "views/draganddrophelper.h"
#include <QTabWidget>
#include <QUrl>
@ -276,6 +277,8 @@ private:
*/
const std::optional<const ViewIndex> viewShowingItem(const QUrl &item) const;
DragAndDropHelper m_dragAndDropHelper;
private:
QPointer<DolphinTabPage> m_lastViewedTab;
QPointer<DolphinNavigatorsWidgetAction> m_navigatorsWidget;

View File

@ -15,7 +15,6 @@
#include "dolphin_placespanelsettings.h"
#include "dolphinplacesmodelsingleton.h"
#include "settings/dolphinsettingsdialog.h"
#include "views/draganddrophelper.h"
#include <KFilePlacesModel>
#include <KIO/DropJob>
@ -32,6 +31,7 @@
PlacesPanel::PlacesPanel(QWidget *parent)
: KFilePlacesView(parent)
, m_dragAndDropHelper(this)
{
setDropOnPlaceEnabled(true);
connect(this, &PlacesPanel::urlsDropped, this, &PlacesPanel::slotUrlsDropped);
@ -161,7 +161,7 @@ void PlacesPanel::dragMoveEvent(QDragMoveEvent *event)
if (!url.isValid() || !KProtocolManager::supportsWriting(url)) {
event->setDropAction(Qt::IgnoreAction);
} else {
DragAndDropHelper::updateDropAction(event, url);
m_dragAndDropHelper.updateDropAction(event, url);
}
}
}

View File

@ -10,6 +10,7 @@
#define PLACESPANEL_H
#include "panels/panel.h"
#include "views/draganddrophelper.h"
#include <KFilePlacesView>
#include <QUrl>
@ -78,6 +79,8 @@ private:
QAction *m_configureTrashAction;
QAction *m_openInSplitView;
QAction *m_lockPanelsAction;
DragAndDropHelper m_dragAndDropHelper;
};
#endif // PLACESPANEL_H

View File

@ -73,20 +73,85 @@ bool DragAndDropHelper::supportsDropping(const KFileItem &destItem)
return (destItem.isDir() && destItem.isWritable()) || destItem.isDesktopFile();
}
DragAndDropHelper::DragAndDropHelper(QObject *parent)
: QObject(parent)
{
m_destItemCacheInvalidationTimer.setSingleShot(true);
m_destItemCacheInvalidationTimer.setInterval(30000);
connect(&m_destItemCacheInvalidationTimer, &QTimer::timeout, this, [this]() {
m_destItemCache = KFileItem();
});
}
void DragAndDropHelper::updateDropAction(QDropEvent *event, const QUrl &destUrl)
{
auto processEvent = [this](QDropEvent *event) {
if (supportsDropping(m_destItemCache)) {
event->setDropAction(event->proposedAction());
event->accept();
} else {
event->setDropAction(Qt::IgnoreAction);
event->ignore();
}
};
m_lastUndecidedEvent = nullptr;
if (urlListMatchesUrl(event->mimeData()->urls(), destUrl)) {
event->setDropAction(Qt::IgnoreAction);
event->ignore();
return;
}
KFileItem item(destUrl);
if (!item.isLocalFile() || supportsDropping(item)) {
event->setDropAction(event->proposedAction());
event->accept();
} else {
event->setDropAction(Qt::IgnoreAction);
event->ignore();
if (destUrl == m_destItemCache.url()) {
// We already received events for this URL, and already have the
// stat result cached because:
// 1. it's a local file, and we already called KFileItem(destUrl)
// 2. it's a remote file, and StatJob finished
processEvent(event);
return;
}
if (m_statJob) {
if (destUrl == m_statJobUrl) {
// We already received events for this URL. Still waiting for
// the stat result. StatJob will process the event when it finishes.
m_lastUndecidedEvent = event;
return;
}
// We are waiting for the stat result of a different URL. Cancel.
m_statJob->kill();
m_statJob = nullptr;
m_statJobUrl.clear();
}
if (destUrl.isLocalFile()) {
// New local URL. KFileItem will stat on demand.
m_destItemCache = KFileItem(destUrl);
m_destItemCacheInvalidationTimer.start();
processEvent(event);
return;
}
// New remote URL. Start a StatJob and process the event when it finishes.
m_lastUndecidedEvent = event;
m_statJob = KIO::stat(destUrl, KIO::StatJob::SourceSide, KIO::StatDetail::StatBasic, KIO::JobFlag::HideProgressInfo);
m_statJobUrl = destUrl;
connect(m_statJob, &KIO::StatJob::result, this, [this, processEvent](KJob *job) {
KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
m_destItemCache = KFileItem(statJob->statResult(), m_statJobUrl);
m_destItemCacheInvalidationTimer.start();
if (m_lastUndecidedEvent) {
processEvent(m_lastUndecidedEvent);
m_lastUndecidedEvent = nullptr;
}
m_statJob = nullptr;
m_statJobUrl.clear();
});
}
void DragAndDropHelper::clearUrlListMatchesUrlCache()

View File

@ -11,9 +11,11 @@
#include "dolphin_export.h"
#include <KFileItem>
#include <KIO/StatJob>
#include <QList>
#include <QString>
#include <QTimer>
#include <QUrl>
class QDropEvent;
@ -24,7 +26,7 @@ namespace KIO
class DropJob;
}
class DOLPHIN_EXPORT DragAndDropHelper
class DOLPHIN_EXPORT DragAndDropHelper : public QObject
{
public:
/**
@ -51,16 +53,6 @@ public:
*/
static bool supportsDropping(const KFileItem &destItem);
/**
* Updates the drop action according to whether the destination supports dropping.
* If supportsDropping(destUrl), set dropAction = proposedAction. Otherwise, set
* dropAction = Qt::IgnoreAction.
*
* @param event Drop event.
* @param destUrl Destination URL.
*/
static void updateDropAction(QDropEvent *event, const QUrl &destUrl);
/**
* @return True if destUrl is contained in the urls parameter.
*/
@ -84,11 +76,55 @@ public:
*/
static void clearUrlListMatchesUrlCache();
DragAndDropHelper(QObject *parent);
/**
* Updates the drop action according to whether the destination supports dropping.
* If supportsDropping(destUrl), set dropAction = proposedAction. Otherwise, set
* dropAction = Qt::IgnoreAction.
*
* @param event Drop event.
* @param destUrl Destination URL.
*/
void updateDropAction(QDropEvent *event, const QUrl &destUrl);
private:
/**
* Stores the results of the expensive checks made in urlListMatchesUrl.
*/
static QHash<QUrl, bool> m_urlListMatchesUrlCache;
/**
* When updateDropAction() is called with a remote URL, we create a StatJob to
* check if the destination is a directory or a desktop file. We cache the result
* here to avoid doing the stat again on subsequent calls to updateDropAction().
*/
KFileItem m_destItemCache;
/**
* Only keep the cache for 30 seconds, because the stat of the destUrl might change.
*/
QTimer m_destItemCacheInvalidationTimer;
/**
* A StatJob on-fly to fill the cache for a remote URL. We shouldn't create more
* than one StatJob at a time, so we keep a pointer to the current one.
*/
KIO::StatJob *m_statJob = nullptr;
/**
* The URL for which the StatJob is running.
* Note: We can't use m_statJob->url() because StatJob might resolve the URL to be
* different from what we passed into stat(). E.g. "mtp:<bus-name>" is resolved
* to "mtp:<phone name>"
*/
QUrl m_statJobUrl;
/**
* The last event we received in updateDropAction(), but can't react to yet,
* because a StatJob is on-fly.
*/
QDropEvent *m_lastUndecidedEvent = nullptr;
};
#endif