Browse Source

Remove directory trees after a complete preflight scan

master
evgeny 2 days ago
parent
commit
7244f22722
  1. 44
      tools/chatgui/src/member_files/batch.cpp
  2. 4
      tools/chatgui/src/member_files/batch.h

44
tools/chatgui/src/member_files/batch.cpp

@ -87,7 +87,13 @@ void MemberFileBatch::upload(const QStringList& sources, const QString& destinat
// destination — каталог назначения либо родитель временного каталога экспорта.
void MemberFileBatch::start(const QList<MemberFileEntry>& entries, const QString& destination, bool temporary) {
m_queue = entries;
if (m_mode == Remove) { next(); return; }
if (m_mode == Remove) {
for (const auto& entry : entries) {
if (m_seen.contains(entry.remote) || m_seen.size() >= 100000) { finish(text("Invalid directory response")); return; }
m_seen.insert(entry.remote); m_scanning |= entry.directory;
}
next(); return;
}
m_work = true;
m_pool.start(QRunnable::create([this, destination, temporary] {
QString root = destination, error;
@ -120,7 +126,8 @@ void MemberFileBatch::request(uint8_t command) {
q->inst = gui_bridge_get_inst(); q->group = m_group; q->server = m_server;
auto& r = q->message; r.kind = MF_REQUEST; r.command = command; r.owner = m_owner; r.request_id = nextId();
memcpy(r.path, entry.remote.toUtf8().constData(), entry.remote.toUtf8().size());
if ((m_mode == Download || command == MF_REMOVE_PATH) && entry.version.size() == 16) memcpy(r.version, entry.version.constData(), 16);
if ((m_mode == Download || command == MF_REMOVE_PATH || command == MF_LIST) && entry.version.size() == 16)
memcpy(r.version, entry.version.constData(), 16);
r.cursor = command == MF_LIST ? entry.cursor : 0; m_command = command; m_request = r.request_id;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "file batch: request id=%llu command=%s path=%s cursor=%llu",
(unsigned long long)m_request, mf_command_text(command), r.path, (unsigned long long)r.cursor);
@ -130,14 +137,22 @@ void MemberFileBatch::request(uint8_t command) {
void MemberFileBatch::next() {
if (m_finished || m_work || m_request || m_transfer) return;
if (m_cancelled) { finish(); return; }
if (m_queue.isEmpty() && m_scanning) {
m_scanning = false; m_queue.swap(m_remove);
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file batch: removal scan complete entries=%d", int(m_queue.size()));
}
if (m_queue.isEmpty() && !m_verify.isEmpty()) { m_queue = m_verify; m_verify.clear(); }
if (m_queue.isEmpty()) { finish(); return; }
const auto entry = m_queue.first();
if (!pathBytes(entry.remote, true) || (m_mode != Remove && !pathBytes(entry.local, false)) ||
(m_mode == Download && (entry.version.size() != 16 || mf_zero((const uint8_t*)entry.version.constData(), 16)))) {
((m_mode == Download || (m_mode == Remove && !entry.directory)) &&
(entry.version.size() != 16 || mf_zero((const uint8_t*)entry.version.constData(), 16)))) {
finish(text("Cannot copy this path: %1").arg(entry.remote)); return;
}
progress(text("Processed %1, skipped %2, remaining %3: %4").arg(m_done).arg(m_skipped).arg(m_queue.size()).arg(entry.remote), -1);
if (m_scanning) {
progress(text("Preparing deletion: %1 entries found — %2").arg(m_seen.size()).arg(entry.remote), -1);
if (!entry.directory) { advance(); return; }
} else progress(text("Processed %1, skipped %2, remaining %3: %4").arg(m_done).arg(m_skipped).arg(m_queue.size()).arg(entry.remote), -1);
if (m_mode != Download || entry.verify) { request(MF_STAT); return; }
if (entry.directory && entry.cursor) { request(MF_LIST); return; }
m_work = true;
@ -192,7 +207,9 @@ bool MemberFileBatch::response(const mf_message& r) {
(!entry.directory && entry.version != QByteArray((const char*)r.version, 16))) {
finish(text("Source changed: %1").arg(entry.remote)); return true;
}
entry.version = QByteArray((const char*)r.version, 16); request(MF_REMOVE_PATH); return true;
// Удаление дочерних объектов меняет версию папки; перед REMOVE берём свежую.
entry.version = QByteArray((const char*)r.version, 16);
request(m_scanning ? MF_LIST : MF_REMOVE_PATH); return true;
}
if (r.command != MF_LIST) { advance(); return true; }
if (r.type != MF_DIRECTORY || mf_zero(r.version, 16)) { finish(text("Invalid directory response")); return true; }
@ -217,7 +234,7 @@ bool MemberFileBatch::response(const mf_message& r) {
finish(text("Invalid directory response")); return true;
}
entry.version = QByteArray((const char*)r.version, 16); entry.cursor = r.cursor;
if (!r.more) { auto verify = entry; verify.verify = true; m_verify.append(verify); }
if (!r.more && m_mode == Download) { auto verify = entry; verify.verify = true; m_verify.append(verify); }
m_queue.append(children);
if (r.more) request(MF_LIST); else advance();
return true;
@ -251,8 +268,15 @@ bool MemberFileBatch::transfer(const uint8_t* data, int length) {
}
void MemberFileBatch::advance(bool skipped) {
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file batch: entry completed path=%s skipped=%d", qPrintable(m_queue.first().remote), skipped);
if (!m_queue.first().verify) { if (skipped) m_skipped++; else m_done++; }
if (m_scanning) {
// Обратный порядок полного обхода гарантирует удаление детей раньше родителей.
m_remove.prepend(m_queue.first());
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "file batch: removal planned path=%s entries=%d",
qPrintable(m_queue.first().remote), int(m_remove.size()));
} else {
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file batch: entry completed path=%s skipped=%d", qPrintable(m_queue.first().remote), skipped);
if (!m_queue.first().verify) { if (skipped) m_skipped++; else m_done++; }
}
m_queue.removeFirst();
QMetaObject::invokeMethod(this, [this] { next(); }, Qt::QueuedConnection);
}
@ -268,8 +292,8 @@ void MemberFileBatch::finish(const QString& error) {
if (m_finished) return;
m_finished = true;
bool ok = error.isEmpty() && !m_cancelled;
QString summary = text("Completed: %1, skipped: %2, remaining: %3").arg(m_done).arg(m_skipped).arg(m_queue.size());
if (!ok) summary = (m_cancelled ? text("Copy cancelled") : error) + " — " + summary;
QString summary = text("Completed: %1, skipped: %2, remaining: %3").arg(m_done).arg(m_skipped).arg(m_queue.size() + m_remove.size());
if (!ok) summary = (m_cancelled ? (m_mode == Remove ? text("Deletion cancelled") : text("Copy cancelled")) : error) + " — " + summary;
if (ok) DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file batch: %s", qPrintable(summary));
else DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file batch: %s", qPrintable(summary));
if (!ok && !m_temporary.isEmpty()) {

4
tools/chatgui/src/member_files/batch.h

@ -44,13 +44,13 @@ private:
void cancelRequest(quint64 id);
const quint64 m_group, m_server, m_owner;
const Mode m_mode;
QList<MemberFileEntry> m_queue, m_verify;
QList<MemberFileEntry> m_queue, m_verify, m_remove;
QSet<QString> m_seen;
QStringList m_roots;
QString m_temporary;
QThreadPool m_pool;
std::atomic_bool m_cancelled{false};
bool m_work = false, m_finished = false;
bool m_work = false, m_finished = false, m_scanning = false;
quint64 m_request = 0, m_transfer = 0;
uint8_t m_command = 0;
int m_done = 0, m_skipped = 0, m_policy = 0;

Loading…
Cancel
Save