fix(io): recreate a missing folder on put back and rename kept-both moves

pull/18/head
reionwong 3 weeks ago
parent e7195ec1ad
commit bd3055c539

@ -337,6 +337,16 @@ static void mergeStaged(int source, int target, const QString &path, OperationCo
});
}
// "name (n).ext", the name an item kept beside an existing one takes.
static QByteArray keptName(const QString &destination, int suffix)
{
const QFileInfo info(destination);
const QString extension =
info.isDir() || info.suffix().isEmpty() ? QString() : '.' + info.suffix();
const QString base = extension.isEmpty() ? info.fileName() : info.completeBaseName();
return native(base + QString(" (%1)").arg(suffix) + extension);
}
static QString publish(int parent, const QByteArray &temporary, const QString &source,
const QString &destination, ConflictPolicy policy, OperationContext &ctx)
{
@ -388,11 +398,7 @@ static QString publish(int parent, const QByteArray &temporary, const QString &s
return destination;
}
const QFileInfo info(destination);
const QString extension =
info.isDir() || info.suffix().isEmpty() ? QString() : '.' + info.suffix();
const QString base = extension.isEmpty() ? info.fileName() : info.completeBaseName();
name = native(base + QString(" (%1)").arg(suffix++) + extension);
name = keptName(destination, suffix++);
}
}
@ -477,6 +483,18 @@ QString LocalFileSystem::move(const QString &source, const QString &destination,
if (code == EEXIST && policy == ConflictPolicy::Fail)
throw Failure(code, destination);
if (code == EEXIST && policy == ConflictPolicy::KeepBoth) {
for (int suffix = 1;; ++suffix) {
const QByteArray name = keptName(destination, suffix);
if (::syscall(SYS_renameat2, input.fd.value, input.name.constData(), output.fd.value,
name.constData(), RENAME_NOREPLACE)
== 0)
return QFileInfo(destination).absolutePath() + '/' + QFile::decodeName(name);
if (errno != EEXIST)
break;
}
}
if (code == EEXIST && policy == ConflictPolicy::Replace) {
if (::renameat(input.fd.value, input.name, output.fd.value, output.name) == 0)
return destination;

@ -218,6 +218,13 @@ private slots:
dir.filePath("target (1).txt"));
QCOMPARE(readFile(dir.filePath("target (1).txt")), QByteArray("new"));
auto moved = dir.filePath("moved.txt");
writeFile(moved, "moved");
QCOMPARE(LocalFileSystem::move(moved, target, ConflictPolicy::KeepBoth, ctx),
dir.filePath("target (2).txt"));
QCOMPARE(readFile(dir.filePath("target (2).txt")), QByteArray("moved"));
QVERIFY(!QFileInfo::exists(moved));
QCOMPARE(LocalFileSystem::copy(source, target, ConflictPolicy::Replace, ctx), target);
QCOMPARE(readFile(target), QByteArray("new"));
@ -564,6 +571,20 @@ private slots:
QVERIFY(QDir(FileSystemUrl::trashRoot() + "/info").entryList(QDir::Files).isEmpty());
}
void restoreRecreatesFolder()
{
QTemporaryDir dir;
const auto path = dir.filePath("gone/inner/file");
QVERIFY(QDir().mkpath(dir.filePath("gone/inner")));
writeFile(path);
OperationContext ctx;
const auto trashed = LocalFileSystem::trash(local(path), ctx);
QVERIFY(QDir(dir.filePath("gone")).removeRecursively());
QCOMPARE(LocalFileSystem::restore(trashed, ConflictPolicy::Fail, ctx), local(path));
QVERIFY(QFileInfo::exists(path));
}
void malformedTrashInfo()
{
QTemporaryDir dir;

@ -3,6 +3,8 @@
#include "trashdirectory_p.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QUuid>
#include <optional>
@ -263,6 +265,11 @@ QUrl LocalFileSystem::restore(const QUrl &url, ConflictPolicy policy, OperationC
if (details.path.isEmpty())
throw Failure(EINVAL, info);
// A folder deleted since the item was trashed is made again.
const QString parent = QFileInfo(details.path).absolutePath();
if (!QDir().mkpath(parent))
throw Failure(errno ? errno : EIO, parent);
auto result = move(FileSystemUrl(url).localPath(), details.path, policy, ctx);
if (result.isEmpty())
return {};

Loading…
Cancel
Save