|
|
|
|
@ -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()) { |
|
|
|
|
|