|
|
|
|
@ -25,6 +25,7 @@
|
|
|
|
|
#include <QMouseEvent> |
|
|
|
|
#include <QScopeGuard> |
|
|
|
|
#include <QChildEvent> |
|
|
|
|
#include <QMenu> |
|
|
|
|
#include <functional> |
|
|
|
|
extern "C" { |
|
|
|
|
#include "member_files/member_files.h" |
|
|
|
|
@ -104,6 +105,107 @@ private slots:
|
|
|
|
|
} |
|
|
|
|
void init() { transfers.clear(); cancelled.clear(); ready = true; transferStarted = {}; requestStarted = {}; } |
|
|
|
|
|
|
|
|
|
void selectionAndRecursiveClipboard() { |
|
|
|
|
QTemporaryDir cache; QVERIFY(cache.isValid()); QByteArray oldCache = qgetenv("XDG_CACHE_HOME"); |
|
|
|
|
qputenv("XDG_CACHE_HOME", cache.path().toUtf8()); |
|
|
|
|
auto restore = qScopeGuard([oldCache] { qputenv("XDG_CACHE_HOME", oldCache); QApplication::clipboard()->clear(); }); |
|
|
|
|
sqlite3* sql = nullptr; QCOMPARE(sqlite3_open(":memory:", &sql), SQLITE_OK); |
|
|
|
|
QCOMPARE(sqlite3_exec(sql, "CREATE TABLE accounts(node_id INTEGER,display_name TEXT);" |
|
|
|
|
"INSERT INTO accounts VALUES(22,'Me'),(33,'Server');" |
|
|
|
|
"CREATE TABLE peers_42(node_id INTEGER,adm_tags TEXT,adm_tags_sig BLOB,deleted INTEGER,source INTEGER);" |
|
|
|
|
"INSERT INTO peers_42 VALUES(22,'{}',NULL,0,0),(33,'{\"storage\":\"yes\"}',zeroblob(64),0,0);", |
|
|
|
|
nullptr, nullptr, nullptr), SQLITE_OK); |
|
|
|
|
DbManager db; db.setDb(sql); db.setMyNodeId(22); |
|
|
|
|
{ |
|
|
|
|
MemberFileBrowser browser(&db); browser.setChannel("42", 22); browser.show(); browser.activateWindow(); |
|
|
|
|
auto* tree = browser.findChild<QTreeWidget*>(); |
|
|
|
|
for (const auto& name : {"folder", "loose.txt", "skip.txt", "last.txt"}) { |
|
|
|
|
auto* item = new QTreeWidgetItem(tree, {name}); item->setData(0, Qt::UserRole, name); |
|
|
|
|
item->setData(0, Qt::UserRole + 1, QString(name) == "folder" ? MF_DIRECTORY : MF_FILE); |
|
|
|
|
item->setData(0, Qt::UserRole + 2, QByteArray(16, '\7')); |
|
|
|
|
} |
|
|
|
|
tree->setFocus(); QTRY_VERIFY(tree->hasFocus()); |
|
|
|
|
auto click = [tree](int row, Qt::KeyboardModifiers modifiers) { |
|
|
|
|
QTest::mouseClick(tree->viewport(), Qt::LeftButton, modifiers, tree->visualItemRect(tree->topLevelItem(row)).center()); |
|
|
|
|
}; |
|
|
|
|
click(0, Qt::NoModifier); click(2, Qt::ShiftModifier); QCOMPARE(tree->selectedItems().size(), 3); |
|
|
|
|
click(1, Qt::ControlModifier); QCOMPARE(tree->selectedItems().size(), 2); |
|
|
|
|
QTest::keyClick(tree, Qt::Key_A, Qt::ControlModifier); QCOMPARE(tree->selectedItems().size(), 4); |
|
|
|
|
click(0, Qt::NoModifier); click(1, Qt::ShiftModifier); QCOMPARE(tree->selectedItems().size(), 2); |
|
|
|
|
QTimer::singleShot(0, [] { if (auto* menu = qobject_cast<QMenu*>(QApplication::activePopupWidget())) menu->close(); }); |
|
|
|
|
tree->customContextMenuRequested(tree->visualItemRect(tree->topLevelItem(1)).center()); |
|
|
|
|
QCOMPARE(tree->selectedItems().size(), 2); |
|
|
|
|
QMap<QString, QStringList> directories{{"folder", {"empty", "nested", "inside.txt"}}, {"folder/empty", {}}, |
|
|
|
|
{"folder/nested", {"deep.txt"}}}; |
|
|
|
|
int pages = 0, verified = 0; |
|
|
|
|
requestStarted = [&](mf_gui_request request) { |
|
|
|
|
QTimer::singleShot(0, &browser, [&, request] { |
|
|
|
|
mf_message r = request.message; r.kind = MF_RESPONSE; r.status = MF_OK; r.data_len = 0; |
|
|
|
|
QString path = QString::fromUtf8(r.path); r.type = directories.contains(path) ? MF_DIRECTORY : MF_FILE; |
|
|
|
|
memset(r.version, 7, 16); |
|
|
|
|
if (r.command == MF_LIST) { |
|
|
|
|
pages++; auto names = directories.value(path); |
|
|
|
|
if (r.cursor < quint64(names.size())) { |
|
|
|
|
QByteArray name = names[int(r.cursor)].toUtf8(); QString child = path + '/' + QString::fromUtf8(name); |
|
|
|
|
r.data[0] = uint8_t(name.size()); r.data[2] = directories.contains(child) ? MF_DIRECTORY : MF_FILE; |
|
|
|
|
memset(r.data + 19, 7, 16); memcpy(r.data + MF_ENTRY_HEADER, name.constData(), name.size()); |
|
|
|
|
r.data_len = MF_ENTRY_HEADER + name.size(); r.cursor++; r.more = r.cursor < quint64(names.size()); |
|
|
|
|
} |
|
|
|
|
} else if (r.command == MF_STAT) verified++; |
|
|
|
|
else return; |
|
|
|
|
uint8_t bytes[16 + MF_WIRE_MAX]; mf_put64(bytes, request.group); mf_put64(bytes + 8, request.server); |
|
|
|
|
int size = mf_encode(&r, bytes + 16, MF_WIRE_MAX); QVERIFY(size > 0); browser.onResponse(bytes, size + 16); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
transferStarted = [&](mf_transfer_request request) { |
|
|
|
|
QTimer::singleShot(0, &browser, [&, request] { |
|
|
|
|
QFile file(QString::fromUtf8(request.local)); QVERIFY(file.open(QIODevice::WriteOnly)); |
|
|
|
|
QByteArray bytes(request.remote); QCOMPARE(file.write(bytes), qint64(bytes.size())); file.close(); complete(browser, request); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
auto* copy = browser.findChild<QAction*>("memberFilesCopy"); copy->trigger(); |
|
|
|
|
QTRY_VERIFY(copy->isEnabled()); QCOMPARE(pages, 5); QCOMPARE(verified, 3); QCOMPARE(transfers.size(), 3); |
|
|
|
|
auto urls = QApplication::clipboard()->mimeData()->urls(); QCOMPARE(urls.size(), 2); |
|
|
|
|
QString folder = urls[0].toLocalFile(); QVERIFY(QFileInfo(folder + "/empty").isDir()); |
|
|
|
|
QFile deep(folder + "/nested/deep.txt"); QVERIFY(deep.open(QIODevice::ReadOnly)); |
|
|
|
|
QCOMPARE(deep.readAll(), QByteArray("folder/nested/deep.txt")); |
|
|
|
|
QVERIFY(QFileInfo(urls[1].toLocalFile()).isFile()); |
|
|
|
|
requestStarted = {}; transferStarted = {}; |
|
|
|
|
} |
|
|
|
|
db.setDb(nullptr); QCOMPARE(sqlite3_close(sql), SQLITE_OK); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void recursiveUploadAndPreflight() { |
|
|
|
|
QTemporaryDir root; QVERIFY(root.isValid()); QVERIFY(QDir(root.path()).mkpath("folder/empty")); |
|
|
|
|
QVERIFY(QDir(root.path()).mkpath("folder/nested")); |
|
|
|
|
QString source = root.filePath("folder/nested/file.txt"); |
|
|
|
|
{ QFile file(source); QVERIFY(file.open(QIODevice::WriteOnly)); QCOMPARE(file.write("data"), 4LL); } |
|
|
|
|
MemberFileBatch batch(42, 33, 22, MemberFileBatch::Upload, nullptr); |
|
|
|
|
bool done = false, success = false; QString summary; QStringList created; |
|
|
|
|
batch.progress = [](const QString&, int) {}; |
|
|
|
|
batch.conflict = [](const QString&, bool) { return int(QMessageBox::Cancel); }; |
|
|
|
|
batch.finished = [&](bool ok, const QString& status, const QStringList&) { done = true; success = ok; summary = status; }; |
|
|
|
|
requestStarted = [&](mf_gui_request request) { |
|
|
|
|
QTimer::singleShot(0, &batch, [&, request] { |
|
|
|
|
mf_message response = request.message; response.kind = MF_RESPONSE; |
|
|
|
|
response.status = response.command == MF_STAT ? MF_NOT_FOUND : MF_OK; |
|
|
|
|
if (response.command == MF_MKDIR) created.append(QString::fromUtf8(response.path)); |
|
|
|
|
QVERIFY(batch.response(response)); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
transferStarted = [&](mf_transfer_request request) { |
|
|
|
|
QCOMPARE(QString::fromUtf8(request.remote), QString("destination/folder/nested/file.txt")); |
|
|
|
|
QVERIFY(created.contains("destination/folder/nested")); QCOMPARE(QString::fromUtf8(request.local), source); |
|
|
|
|
QTimer::singleShot(0, &batch, [&, request] { |
|
|
|
|
uint8_t bytes[51]{}; mf_put64(bytes, request.id); bytes[50] = 2; QVERIFY(batch.transfer(bytes, sizeof(bytes))); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
batch.upload({root.filePath("folder"), source}, "destination"); |
|
|
|
|
QTRY_VERIFY(done); QVERIFY2(success, qPrintable(summary)); QCOMPARE(transfers.size(), 1); |
|
|
|
|
QVERIFY(created.contains("destination/folder/empty")); QCOMPARE(created.size(), 3); |
|
|
|
|
requestStarted = {}; transferStarted = {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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);" |
|
|
|
|
|