|
|
|
|
@ -29,6 +29,7 @@
|
|
|
|
|
#include <functional> |
|
|
|
|
extern "C" { |
|
|
|
|
#include "member_files/member_files.h" |
|
|
|
|
#include "member_files/storage.h" |
|
|
|
|
#include "mem.h" |
|
|
|
|
#include "debug_config.h" |
|
|
|
|
} |
|
|
|
|
@ -261,6 +262,87 @@ private slots:
|
|
|
|
|
requestStarted = {}; transferStarted = {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void recursiveDeletion_data() { |
|
|
|
|
QTest::addColumn<QString>("scenario"); |
|
|
|
|
for (const auto* scenario : {"complete", "cancel-scan", "cancel-remove", "changed-file", "new-child", "changed-page"}) |
|
|
|
|
QTest::newRow(scenario) << QString::fromLatin1(scenario); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void recursiveDeletion() { |
|
|
|
|
QFETCH(QString, scenario); |
|
|
|
|
QTemporaryDir root; QVERIFY(root.isValid()); |
|
|
|
|
QString user = root.path() + "/42/22"; |
|
|
|
|
QVERIFY(QDir().mkpath(user + "/folder/nested/empty")); QVERIFY(QDir().mkpath(user + "/other")); |
|
|
|
|
auto write = [&](const QString& path, const QByteArray& contents) { |
|
|
|
|
QFile file(user + '/' + path); QVERIFY(file.open(QIODevice::WriteOnly)); QCOMPARE(file.write(contents), qint64(contents.size())); |
|
|
|
|
}; |
|
|
|
|
// Длинные имена вынуждают настоящий storage выдавать несколько страниц LIST.
|
|
|
|
|
for (int i = 0; i < 40; i++) write("folder/" + QString::number(i) + QString(220, 'x'), "payload"); |
|
|
|
|
write("folder/nested/file.txt", "nested"); write("loose.txt", "selected"); write("keep.txt", "unselected"); |
|
|
|
|
auto* storage = mf_storage_create(root.path().toUtf8().constData(), 1000000, 200000); |
|
|
|
|
QVERIFY(storage); auto destroy = qScopeGuard([&] { mf_storage_destroy(storage); }); |
|
|
|
|
QCOMPARE(mf_storage_initialize(storage), int(MF_OK)); |
|
|
|
|
QList<MemberFileEntry> entries; |
|
|
|
|
for (const auto* path : {"folder", "loose.txt", "other"}) { |
|
|
|
|
mf_message q{}, r{}; q.kind = MF_REQUEST; q.command = MF_STAT; q.owner = 22; q.request_id = entries.size() + 1; |
|
|
|
|
strcpy(q.path, path); mf_storage_execute(storage, 42, 22, &q, &r); QCOMPARE(r.status, uint16_t(MF_OK)); |
|
|
|
|
entries.append({QString::fromUtf8(path), {}, QByteArray((const char*)r.version, 16), r.type == MF_DIRECTORY}); |
|
|
|
|
} |
|
|
|
|
MemberFileBatch batch(42, 33, 22, MemberFileBatch::Remove, nullptr); |
|
|
|
|
bool done = false, success = false, injected = false; QString summary, changed; |
|
|
|
|
int pages = 0, removed = 0, requests = 0, rootStats = 0; |
|
|
|
|
batch.progress = [](const QString&, int) {}; |
|
|
|
|
batch.finished = [&](bool ok, const QString& status, const QStringList&) { done = true; success = ok; summary = status; }; |
|
|
|
|
auto reset = qScopeGuard([] { requestStarted = {}; }); |
|
|
|
|
requestStarted = [&](mf_gui_request request) { |
|
|
|
|
requests++; |
|
|
|
|
QTimer::singleShot(0, &batch, [&, request] { |
|
|
|
|
const auto& q = request.message; QString path = QString::fromUtf8(q.path); |
|
|
|
|
if (q.command == MF_STAT && path == "folder") { |
|
|
|
|
rootStats++; |
|
|
|
|
if (scenario == "new-child" && rootStats == 2) { write("folder/arrived.txt", "new arrival"); injected = true; } |
|
|
|
|
} |
|
|
|
|
if (q.command == MF_LIST) { |
|
|
|
|
QCOMPARE(removed, 0); pages++; |
|
|
|
|
QVERIFY(!mf_zero(q.version, 16)); |
|
|
|
|
if (scenario == "changed-page" && q.cursor) { write("folder/arrived.txt", "new arrival"); injected = true; } |
|
|
|
|
} |
|
|
|
|
if (q.command == MF_REMOVE_PATH) { |
|
|
|
|
QVERIFY(pages >= 4); // folder: две страницы, nested и empty: по одной.
|
|
|
|
|
if (scenario == "changed-file" && !injected) { changed = path; write(path, "changed contents"); injected = true; } |
|
|
|
|
} |
|
|
|
|
mf_message response{}; mf_storage_execute(storage, request.group, request.message.owner, &q, &response); |
|
|
|
|
if (q.command == MF_REMOVE_PATH && !response.status) { |
|
|
|
|
removed++; QVERIFY(!QFileInfo::exists(user + '/' + path)); |
|
|
|
|
} |
|
|
|
|
if (scenario == "cancel-scan" && response.more) { |
|
|
|
|
batch.cancel(); QVERIFY(!batch.response(response)); return; |
|
|
|
|
} |
|
|
|
|
QVERIFY(batch.response(response)); |
|
|
|
|
if (scenario == "cancel-remove" && removed == 1) { batch.cancel(); QVERIFY(!batch.response(response)); } |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
batch.start(entries); QTRY_VERIFY(done); |
|
|
|
|
int stopped = requests; QCoreApplication::processEvents(); QCOMPARE(requests, stopped); |
|
|
|
|
QFile keep(user + "/keep.txt"); QVERIFY(keep.open(QIODevice::ReadOnly)); QCOMPARE(keep.readAll(), QByteArray("unselected")); |
|
|
|
|
if (scenario == "complete") { |
|
|
|
|
QVERIFY2(success, qPrintable(summary)); QCOMPARE(removed, 46); |
|
|
|
|
QVERIFY(!QFileInfo::exists(user + "/folder")); QVERIFY(!QFileInfo::exists(user + "/other")); |
|
|
|
|
QVERIFY(!QFileInfo::exists(user + "/loose.txt")); QVERIFY(summary.contains("Completed: 46")); |
|
|
|
|
} else { |
|
|
|
|
QVERIFY(!success); QVERIFY(QFileInfo::exists(user + "/folder")); |
|
|
|
|
if (scenario == "cancel-scan" || scenario == "changed-page" || scenario == "changed-file") QCOMPARE(removed, 0); |
|
|
|
|
if (scenario == "cancel-remove") { QCOMPARE(removed, 1); QVERIFY(summary.contains("Completed: 1")); } |
|
|
|
|
if (scenario.startsWith("cancel-")) QVERIFY(summary.contains("Deletion cancelled")); |
|
|
|
|
if (scenario == "changed-file") { QVERIFY(injected); QVERIFY(QFileInfo::exists(user + '/' + changed)); } |
|
|
|
|
if (scenario == "new-child") { |
|
|
|
|
QVERIFY(injected); QVERIFY(QFileInfo::exists(user + "/folder/arrived.txt")); |
|
|
|
|
QVERIFY(summary.contains(QString::fromUtf8(mf_status_text(MF_DIRECTORY_NOT_EMPTY)))); |
|
|
|
|
} |
|
|
|
|
if (scenario == "changed-page") QVERIFY(summary.contains(QString::fromUtf8(mf_status_text(MF_STALE)))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void deleteAndParentShortcuts() { |
|
|
|
|
sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); |
|
|
|
|
QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);" |
|
|
|
|
|