Commit 5b80e9d1 authored by Alexander Saoutkin's avatar Alexander Saoutkin

Merge branch 'forceDrop2' into 'master'

Automatic node refreshing

See merge request kde/kio-fuse!25
parents 9cd43c67 a294adc1
......@@ -106,10 +106,17 @@ instantiated as one of its subclasses (KIOFuseRemoteCacheBasedNode or
KIOFuseRemoteFileJobBasedNode). The type of class instantiated will depend on
the URL of the file. Please see the "File I/O" section to learn more.
Both kinds of remote nodes (KIOFuseRemoteDirNode, KIOFuseRemoteFileNode) have
an m_overrideUrl member which is used to implement URL mountpoints and
redirections. To get the remote URL of a node, the tree is traversed upwards
until an override is found and the path is appended.
Remote nodes (KIOFuseRemote*Node) are derived from KIOFuseRemoteFileInfo in
addition to the base node type, which contains some members specific for remote
access.
m_overrideUrl is used to implement URL mountpoints and redirections. To get the
remote URL of a node, the tree is traversed upwards until an override is found
and the path is appended.
m_lastStatRefresh stores the time when m_stat was updated last. It's updated
on node construction and by updateNodeFromUDSEntry and queried by the
awaitAttrRefreshed method.
Unlinking a node
----------------
......@@ -162,7 +169,9 @@ inserting it into the tree. This happens when mounting a URL (uses KIO::stat)
and when requesting the children of a URL (using KIO::listDir with details).
The same is true of a symlink's target.
As a result, getattr and readlink are non-blocking.
As a result, getattr and readlink are non-blocking if the node's attributes
have not timed out.
setattr instead does block, it only returns if all of the requested operations
(e.g. SET_ATTR_MTIME, SET_ATTR_MODE, SET_ATTR_UID) completed.
......@@ -174,8 +183,29 @@ API offers, children of KIOFuseRemoteDirNode are loaded lazily. This means
that the full list of children is only requested (using KIO::listDir) if
required, so if lookup on the directory fails or if readdir is executed.
This is implemented using bool m_childrenComplete and the
awaitChildrenComplete method in KIOFuseVFS.
A node's children are considered valid for 30 seconds. The last time a node
was dir listed via KIO::listDir is stored in m_lastChildrenRefreshed.
Each readdir request checks if they have timed out via the
haveChildrenTimedOut() method and updates the children (and consequently, their
attributes) as appropriate. This is implemented in awaitChildrenComplete.
Node Expiration
---------------
Each remote node has a timeout on its attributes and its children, which is
currently set to 30 seconds.
When a node's attributes are requested, the awaitAttrRefreshed method checks
whether the attributes expired and if so, calls mountUrl to refresh it via
updateNodeFromUDSEntry. If the result of KIO::stat indicates that the node does
not exist on the remote side anymore it is (recursively) marked as deleted.
Otherwise, a new node based on the fresh attributes is created and if the type
matches, used to update the existing node. If the type does not match, the old
node is marked as deleted and the new node inserted into the tree.
For directories, awaitChildrenComplete calls KIO::listDir for refreshing the
list of children, either removing vanished nodes, creating new nodes or
updating existing ones using the same method as outlined above.
File IO
-------
......
General:
- Integration into KIO/Plasma
Filesystem features:
- opendir/readdir/releasedir could be more POSIX compliant by increasing the lookup count of
children and storing a copy of childrenInos in fuse_file_info.
......@@ -9,9 +7,6 @@ Filesystem features:
* ACLs (?)
* interrupting requests (just killing the KIO Job would also break other requests for the
same resource)
- Refresh attributes of files/dirs and children of dirs after a while:
Currently directories are only listed once and files only stat'd once.
* Print a warning if a file with dirty cache got deleted? (renames can't be detected)
- Umount support:
Might not even be necessary - except for the nodes in RAM it does not use any resources
KIO Slave support:
......
......@@ -24,6 +24,7 @@
#include <functional>
#include <vector>
#include <chrono>
#include <QObject>
#include <QUrl>
......@@ -88,35 +89,56 @@ public:
NodeType type() const override { return Type; }
};
class KIOFuseRemoteDirNode : public QObject, public KIOFuseDirNode {
// Used for automated testing of expiration.
// Set by KIOFuseServicePrivate::forceNodeTimeout.
extern std::chrono::steady_clock::time_point g_timeoutEpoch;
class KIOFuseRemoteNodeInfo : public QObject {
Q_OBJECT
public:
// Timeout for refreshing of attributes
static const std::chrono::steady_clock::duration ATTR_TIMEOUT;
// Override the URL
QUrl m_overrideUrl;
// Whether a stat was requested. If true, the signal "statRefreshed" will
// be emitted on finish.
bool m_statRequested = false;
// Stores the last time a node's m_stat field was refreshed via KIO::stat or a parent's KIO::listDir.
std::chrono::steady_clock::time_point m_lastStatRefresh = std::chrono::steady_clock::now();
// Returns true if a node is due for a stat refresh, false otherwise.
bool hasStatTimedOut() { return m_lastStatRefresh < g_timeoutEpoch || (std::chrono::steady_clock::now() - m_lastStatRefresh) >= ATTR_TIMEOUT; }
Q_SIGNALS:
// Emitted after finishing (successful or not) a attr refresh on this node
void statRefreshed(int error);
};
class KIOFuseRemoteDirNode : public KIOFuseRemoteNodeInfo, public KIOFuseDirNode {
Q_OBJECT
public:
using KIOFuseDirNode::KIOFuseDirNode;
static const NodeType Type = NodeType::RemoteDirNode;
NodeType type() const override { return Type; }
// Override the URL
QUrl m_overrideUrl;
// Whether the list of children is the result of a successful dirlist
bool m_childrenComplete = false;
// Whether a dirlist was requested. If true, the signal "gotChildren" will
// be emitted on finish.
bool m_childrenRequested = false;
// Stores the last time a node's children were refreshed via KIO::listDir.
std::chrono::steady_clock::time_point m_lastChildrenRefresh;
// Returns true if a node is due for a readdir refresh, false otherwise.
bool haveChildrenTimedOut() { return m_lastChildrenRefresh < g_timeoutEpoch || (std::chrono::steady_clock::now() - m_lastChildrenRefresh) >= ATTR_TIMEOUT; }
Q_SIGNALS:
// Emitted after finishing (successful or not) a distlist on this node
void gotChildren(int error);
};
class KIOFuseRemoteFileNode : public KIOFuseNode {
class KIOFuseRemoteFileNode : public KIOFuseRemoteNodeInfo, public KIOFuseNode {
Q_OBJECT
public:
using KIOFuseNode::KIOFuseNode;
// Override the URL (used for UDS_URL)
QUrl m_overrideUrl;
};
class KIOFuseRemoteCacheBasedFileNode : public QObject, public KIOFuseRemoteFileNode {
class KIOFuseRemoteCacheBasedFileNode : public KIOFuseRemoteFileNode {
Q_OBJECT
public:
using KIOFuseRemoteFileNode::KIOFuseRemoteFileNode;
......@@ -134,7 +156,6 @@ public:
m_cacheDirty = false, // Set on every write to m_localCache, cleared when a flush starts
m_flushRunning = false; // If a flush is currently running
int m_numKilledJobs = 0; // reset on successful flush, incremented every time job is killed because cache is dirty (among other factors)
Q_SIGNALS:
// Emitted when a download operation on this node made progress, finished or failed.
void localCacheChanged(int error);
......@@ -143,7 +164,7 @@ Q_SIGNALS:
};
class KIOFuseRemoteFileJobBasedFileNode : public QObject, public KIOFuseRemoteFileNode {
class KIOFuseRemoteFileJobBasedFileNode : public KIOFuseRemoteFileNode {
Q_OBJECT
public:
using KIOFuseRemoteFileNode::KIOFuseRemoteFileNode;
......@@ -151,8 +172,7 @@ public:
NodeType type() const override { return Type; }
};
class KIOFuseSymLinkNode : public QObject, public KIOFuseNode {
Q_OBJECT
class KIOFuseSymLinkNode : public KIOFuseRemoteNodeInfo, public KIOFuseNode {
public:
using KIOFuseNode::KIOFuseNode;
static const NodeType Type = NodeType::RemoteSymlinkNode;
......
......@@ -78,6 +78,11 @@ bool KIOFuseService::start(struct fuse_args &args, QString mountpoint, bool fore
return registerServiceDaemonized();
}
void KIOFuseServicePrivate::forceNodeTimeout()
{
g_timeoutEpoch = std::chrono::steady_clock::now();
}
QString KIOFuseService::remoteUrl(const QString& localPath)
{
// Massage URL into something KIOFuseVFS may understand.
......@@ -133,7 +138,8 @@ QString KIOFuseService::mountUrl(const QString& remoteUrl, const QDBusMessage& m
bool KIOFuseService::registerService()
{
return QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KIOFuse"), this, QDBusConnection::ExportAllSlots)
return QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KIOFuse"), this,
QDBusConnection::ExportAllSlots | QDBusConnection::ExportAdaptors)
&& QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.KIOFuse"));
}
......
......@@ -27,9 +27,22 @@
#include <QDBusContext>
#include <QTemporaryDir>
#include <QStandardPaths>
#include <QDBusAbstractAdaptor>
#include "kiofusevfs.h"
class KIOFuseServicePrivate : public QDBusAbstractAdaptor {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KIOFuse.Private")
public:
KIOFuseServicePrivate(QObject *obj) : QDBusAbstractAdaptor(obj) {}
public Q_SLOTS:
/** Treat all nodes as expired, to not have to wait in automated testing. */
void forceNodeTimeout();
};
class KIOFuseService : public QObject, protected QDBusContext
{
Q_OBJECT
......@@ -61,4 +74,6 @@ private:
std::optional<QTemporaryDir> m_tempDir;
/** A list of protocols that are blacklisted (for various reasons). */
static const QStringList m_blacklist;
/** DBus Adaptor exported as org.kde.KIOFuse.Private interface. */
KIOFuseServicePrivate m_privateInterface{this};
};
......@@ -24,6 +24,7 @@
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <chrono>
#ifdef Q_OS_LINUX
#include <linux/fs.h>
......@@ -86,6 +87,9 @@ struct KIOFuseVFS::FuseLLOps : public fuse_lowlevel_ops
const struct KIOFuseVFS::FuseLLOps KIOFuseVFS::fuse_ll_ops;
const std::chrono::steady_clock::duration KIOFuseRemoteNodeInfo::ATTR_TIMEOUT = std::chrono::seconds(30);
std::chrono::steady_clock::time_point g_timeoutEpoch = {};
/* Handles partial writes and EINTR.
* Returns true only if count bytes were written successfully. */
static bool sane_write(int fd, const void *buf, size_t count)
......@@ -138,6 +142,11 @@ static bool sane_read(int fd, void *buf, size_t count)
return true;
}
static bool operator <(const struct timespec &a, const struct timespec &b)
{
return (a.tv_sec == b.tv_sec) ? (a.tv_nsec < b.tv_nsec) : (a.tv_sec < b.tv_sec);
}
int KIOFuseVFS::signalFd[2];
KIOFuseVFS::KIOFuseVFS(QObject *parent)
......@@ -291,7 +300,10 @@ void KIOFuseVFS::getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi)
return;
}
that->replyAttr(req, node);
return that->awaitAttrRefreshed(node, [=] (int error) {
Q_UNUSED(error); // Just send the old attr...
replyAttr(req, node);
});
}
void KIOFuseVFS::setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, fuse_file_info *fi)
......@@ -537,13 +549,17 @@ void KIOFuseVFS::readlink(fuse_req_t req, fuse_ino_t ino)
return;
}
if(node->type() != KIOFuseNode::NodeType::RemoteSymlinkNode)
auto symlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node);
if(!symlinkNode)
{
fuse_reply_err(req, EINVAL);
return;
}
fuse_reply_readlink(req, std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node)->m_target.toUtf8().data());
that->awaitAttrRefreshed(node, [=](int error) {
Q_UNUSED(error); // Just send the old target...
fuse_reply_readlink(req, symlinkNode->m_target.toUtf8().data());
});
}
void KIOFuseVFS::mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
......@@ -871,7 +887,9 @@ void KIOFuseVFS::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
std::vector<char> dirbuf;
appendDirentry(dirbuf, req, ".", &node->m_stat);
auto parentNode = that->nodeForIno(node->m_parentIno);
std::shared_ptr<KIOFuseNode> parentNode;
if(node->m_parentIno != KIOFuseIno::DeletedRoot)
parentNode = that->nodeForIno(node->m_parentIno);
if(!parentNode)
parentNode = that->nodeForIno(KIOFuseIno::Root);
if(parentNode)
......@@ -957,6 +975,13 @@ void KIOFuseVFS::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, fu
case KIOFuseNode::NodeType::RemoteFileJobBasedFileNode:
{
qDebug(KIOFUSE_LOG) << "Reading" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
if(node->m_parentIno == KIOFuseIno::DeletedRoot)
{
fuse_reply_err(req, ENOENT);
return;
}
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadOnly);
connect(fileJob, &KIO::FileJob::result, [=] (auto *job) {
......@@ -1075,6 +1100,13 @@ void KIOFuseVFS::write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t s
case KIOFuseNode::NodeType::RemoteFileJobBasedFileNode:
{
qDebug(KIOFUSE_LOG) << "Writing" << size << "byte(s) at offset" << off << "of (FileJob-based) node" << node->m_nodeName;
if(node->m_parentIno == KIOFuseIno::DeletedRoot)
{
fuse_reply_err(req, ENOENT);
return;
}
QByteArray data(buf, size); // Copy data
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteFileJobBasedFileNode>(node);
auto *fileJob = KIO::open(that->remoteUrl(remoteNode), QIODevice::ReadWrite);
......@@ -1261,8 +1293,15 @@ void KIOFuseVFS::lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
QString nodeName = QString::fromUtf8(name);
if(auto child = that->nodeByName(parentNode, nodeName))
return that->replyEntry(req, child);
if(auto child = that->nodeByName(parentNode, nodeName)) // Part of the tree?
{
return that->awaitAttrRefreshed(child, [=](int error) {
Q_UNUSED(error);
// Lookup again, node might've been replaced or deleted
auto child = that->nodeByName(parentNode, nodeName);
that->replyEntry(req, child);
});
}
QUrl url = that->remoteUrl(parentNode);
if(url.isEmpty())
......@@ -1388,17 +1427,10 @@ QUrl KIOFuseVFS::sanitizeNullAuthority(QUrl url) const
QUrl KIOFuseVFS::remoteUrl(const std::shared_ptr<const KIOFuseNode> &node) const
{
// Special handling for KIOFuseRemoteFileNode
if(auto remoteFileNode = std::dynamic_pointer_cast<const KIOFuseRemoteFileNode>(node))
{
if(!remoteFileNode->m_overrideUrl.isEmpty())
return sanitizeNullAuthority(remoteFileNode->m_overrideUrl);
}
QStringList path;
for(const KIOFuseNode *currentNode = node.get(); currentNode != nullptr; currentNode = nodeForIno(currentNode->m_parentIno).get())
{
auto remoteDirNode = dynamic_cast<const KIOFuseRemoteDirNode*>(currentNode);
auto remoteDirNode = dynamic_cast<const KIOFuseRemoteNodeInfo*>(currentNode);
if(remoteDirNode && !remoteDirNode->m_overrideUrl.isEmpty())
{
// Origin found - add path and return
......@@ -1467,7 +1499,17 @@ void KIOFuseVFS::decrementLookupCount(const std::shared_ptr<KIOFuseNode> node, u
}
void KIOFuseVFS::markNodeDeleted(const std::shared_ptr<KIOFuseNode> &node)
{
{
if(auto dirNode = std::dynamic_pointer_cast<KIOFuseDirNode>(node))
{
// Mark all children as deleted first (flatten the hierarchy)
for (auto childIno : std::vector<fuse_ino_t>(dirNode->m_childrenInos))
if(auto childNode = nodeForIno(childIno))
markNodeDeleted(childNode);
}
qDebug(KIOFUSE_LOG) << "Marking node" << node->m_nodeName << "as deleted";
reparentNode(node, KIOFuseIno::DeletedRoot);
decrementLookupCount(node, 0); // Trigger reevaluation
}
......@@ -1478,7 +1520,7 @@ void KIOFuseVFS::replyAttr(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
node->m_stat.st_blocks = (node->m_stat.st_size + node->m_stat.st_blksize - 1) / node->m_stat.st_blksize;
// TODO: Validity timeout?
fuse_reply_attr(req, &node->m_stat, 1);
fuse_reply_attr(req, &node->m_stat, 0);
}
void KIOFuseVFS::replyEntry(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
......@@ -1491,8 +1533,8 @@ void KIOFuseVFS::replyEntry(fuse_req_t req, std::shared_ptr<KIOFuseNode> node)
incrementLookupCount(node);
entry.ino = node->m_stat.st_ino;
entry.attr_timeout = 1.0;
entry.entry_timeout = 1.0;
entry.attr_timeout = 0.0;
entry.entry_timeout = 0.0;
entry.attr = node->m_stat;
}
......@@ -1600,6 +1642,86 @@ std::shared_ptr<KIOFuseNode> KIOFuseVFS::createNodeFromUDSEntry(const KIO::UDSEn
}
}
std::shared_ptr<KIOFuseNode> KIOFuseVFS::updateNodeFromUDSEntry(const std::shared_ptr<KIOFuseNode> &node, const KIO::UDSEntry &entry)
{
qDebug(KIOFUSE_LOG) << "Updating attributes of" << node->m_nodeName;
// Updating a node works by creating a new node and merging their attributes
// (both m_stat and certain other class attributes) together.
// This is broadly done with node->m_stat = newNode->m_stat;
// However, there are some things we may not want to copy straight from the
// new node into the node to be updated and so we update newNode as
// appropriate before merging.
auto newNode = createNodeFromUDSEntry(entry, node->m_parentIno, node->m_nodeName);
if (!newNode)
{
qWarning(KIOFUSE_LOG) << "Could not create new node for" << node->m_nodeName;
return node;
}
if (newNode->type() != node->type())
{
if(node->m_parentIno != KIOFuseIno::DeletedRoot)
{
markNodeDeleted(node);
insertNode(newNode);
return newNode;
}
return node;
}
const bool newMtimNewer = node->m_stat.st_mtim < newNode->m_stat.st_mtim;
// Take the newer time value
if (!newMtimNewer)
newNode->m_stat.st_mtim = node->m_stat.st_mtim;
if (newNode->m_stat.st_atim < node->m_stat.st_atim)
newNode->m_stat.st_atim = node->m_stat.st_atim;
if (auto cacheBasedFileNode = std::dynamic_pointer_cast<KIOFuseRemoteCacheBasedFileNode>(node))
{
if(newMtimNewer || newNode->m_stat.st_size != node->m_stat.st_size)
{
// Someone has changed something server side, lets get those changes.
if(cacheBasedFileNode->m_cacheDirty || cacheBasedFileNode->m_flushRunning)
{
if(newMtimNewer)
qWarning(KIOFUSE_LOG) << cacheBasedFileNode->m_nodeName << "cache is dirty but file has also been changed by something else";
newNode->m_stat.st_size = cacheBasedFileNode->m_stat.st_size;
}
else if(cacheBasedFileNode->m_localCache && cacheBasedFileNode->cacheIsComplete())
{
// Our cache isn't dirty but the file has changed, our current cache is invalid
// and we can safely get rid of it.
fclose(cacheBasedFileNode->m_localCache);
cacheBasedFileNode->m_cacheSize = 0;
cacheBasedFileNode->m_cacheComplete = false;
cacheBasedFileNode->m_localCache = nullptr;
}
}
}
else if (auto oldSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(node))
{
auto newSymlinkNode = std::dynamic_pointer_cast<KIOFuseSymLinkNode>(newNode);
oldSymlinkNode->m_target = newSymlinkNode->m_target;
}
auto oldRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
auto newRemoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(newNode);
oldRemoteNode->m_lastStatRefresh = std::chrono::steady_clock::now();
// oldRemoteNode->m_overrideUrl could've been set by mountUrl, don't clear it
if (!newRemoteNode->m_overrideUrl.isEmpty())
oldRemoteNode->m_overrideUrl = newRemoteNode->m_overrideUrl;
// Preserve the inode number
newNode->m_stat.st_ino = node->m_stat.st_ino;
node->m_stat = newNode->m_stat;
return node;
}
void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBasedFileNode> &node, off_t bytes, std::function<void(int error)> callback)
{
if(bytes < 0)
......@@ -1615,6 +1737,9 @@ void KIOFuseVFS::awaitBytesAvailable(const std::shared_ptr<KIOFuseRemoteCacheBas
if(!node->m_localCache)
{
if(node->m_parentIno == KIOFuseIno::DeletedRoot)
return callback(ENOENT);
// Create a temporary file
node->m_localCache = tmpfile();
......@@ -1709,32 +1834,54 @@ void KIOFuseVFS::awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> &no
if(!remoteNode)
return callback(0); // Not a remote node
if(remoteNode->m_childrenComplete)
return callback(0);
if(!remoteNode->haveChildrenTimedOut())
return callback(0); // Children complete and up to date
if(!remoteNode->m_childrenRequested)
{
if(node->m_parentIno == KIOFuseIno::DeletedRoot)
return callback(0);
auto childrenNotSeen = std::make_shared<std::vector<fuse_ino_t>>(remoteNode->m_childrenInos);
// List the remote dir
auto refreshTime = std::chrono::steady_clock::now();
auto *job = KIO::listDir(remoteUrl(remoteNode));
connect(job, &KIO::ListJob::entries, [=](auto *job, const KIO::UDSEntryList &entries) {
Q_UNUSED(job);
for(auto &entry : entries)
{
// Inside the loop because refreshing "." might drop it
if(remoteNode->m_parentIno == KIOFuseIno::DeletedRoot)
{
job->kill(KJob::EmitResult);
return;
}
QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
// Ignore "." and ".."
if(name == QStringLiteral(".") || name == QStringLiteral(".."))
// Refresh "." and ignore ".."
if(name == QStringLiteral("."))
{
updateNodeFromUDSEntry(remoteNode, entry);
continue;
}
else if(name == QStringLiteral(".."))
continue;
auto childrenNode = nodeByName(remoteNode, name);
if(childrenNode)
// TODO: Verify that the type matches.
// It's possible that something was mounted as a directory,
// but it's actually a symlink :-/
{
auto it = std::find(begin(*childrenNotSeen), end(*childrenNotSeen),
childrenNode->m_stat.st_ino);
if(it != end(*childrenNotSeen))
childrenNotSeen->erase(it);
// Try to update existing node
updateNodeFromUDSEntry(childrenNode, entry);
continue;
}
childrenNode = createNodeFromUDSEntry(entry, remoteNode->m_stat.st_ino);
childrenNode = createNodeFromUDSEntry(entry, remoteNode->m_stat.st_ino, name);
if(!childrenNode)
{
qWarning(KIOFUSE_LOG) << "Could not create node for" << name;
......@@ -1747,13 +1894,22 @@ void KIOFuseVFS::awaitChildrenComplete(const std::shared_ptr<KIOFuseDirNode> &no
connect(job, &KIO::ListJob::result, [=] {
remoteNode->m_childrenRequested = false;
if(job->error())
if(job->error() && job->error() != KJob::KilledJobError)
{
emit remoteNode->gotChildren(kioErrorToFuseError(job->error()));
else
return;
}
for(auto ino : *childrenNotSeen)
{
remoteNode->m_childrenComplete = true;
emit remoteNode->gotChildren(0);
auto childNode = nodeForIno(ino);
// Was not refreshed as part of this listDir operation, drop it
if(childNode && childNode->m_parentIno == node->m_stat.st_ino)
markNodeDeleted(childNode);
}
remoteNode->m_lastChildrenRefresh = refreshTime;
emit remoteNode->gotChildren(0);
});
remoteNode->m_childrenRequested = true;
......@@ -1876,7 +2032,10 @@ void KIOFuseVFS::mountUrl(QUrl url, std::function<void (const std::shared_ptr<KI
// Finally create the last component
auto finalNode = nodeByName(pathNode, pathElements.last());
if(!finalNode)
if(finalNode)
// Note that node may fail to update, but this type of error is ignored.
finalNode = updateNodeFromUDSEntry(finalNode, statJob->statResult());
else
{
// The remote name (statJob->statResult().stringValue(KIO::UDSEntry::UDS_NAME)) has to be
// ignored as it can be different from the path. e.g. tar:/foo.tar/ is "/"
......@@ -2015,7 +2174,46 @@ void KIOFuseVFS::awaitNodeFlushed(const std::shared_ptr<KIOFuseRemoteCacheBasedF
);
}
bool KIOFuseVFS::setupSignalHandlers()
void KIOFuseVFS::awaitAttrRefreshed(const std::shared_ptr<KIOFuseNode> &node, std::function<void (int)> callback)
{
auto remoteNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(node);
if(!remoteNode || !remoteNode->hasStatTimedOut())
return callback(0); // Node not remote, or it hasn't timed out yet
// To do the same request as the initial mount or lookup, build the URL from the parent
auto parentNode = nodeForIno(node->m_parentIno);
auto remoteParentNode = std::dynamic_pointer_cast<KIOFuseRemoteNodeInfo>(parentNode);
QUrl url;
if(!remoteParentNode || (url = remoteUrl(parentNode)).isEmpty())
return callback(0); // Parent not remote
if(!remoteNode->m_statRequested)
{
qDebug(KIOFUSE_LOG) << "Refreshing attributes of node" << node->m_nodeName;
url.setPath(url.path() + QLatin1Char('/') + node->m_nodeName);
remoteNode->m_statRequested = true;
mountUrl(url, [=] (auto mountedNode, int error) {
if(error == ENOENT)