|
|
|
@ -35,6 +35,7 @@ |
|
|
|
#include <QTemporaryDir> |
|
|
|
#include <QTemporaryDir> |
|
|
|
#include <QStandardPaths> |
|
|
|
#include <QStandardPaths> |
|
|
|
#include <QEventLoop> |
|
|
|
#include <QEventLoop> |
|
|
|
|
|
|
|
#include <QCloseEvent> |
|
|
|
#include <functional> |
|
|
|
#include <functional> |
|
|
|
#include <limits> |
|
|
|
#include <limits> |
|
|
|
#include <cstring> |
|
|
|
#include <cstring> |
|
|
|
@ -59,7 +60,7 @@ protected: |
|
|
|
// Системный менеджер может запросить URI после drop. До завершения скачивания путь не выдаётся.
|
|
|
|
// Системный менеджер может запросить URI после drop. До завершения скачивания путь не выдаётся.
|
|
|
|
class DeferredFileMimeData : public QMimeData { |
|
|
|
class DeferredFileMimeData : public QMimeData { |
|
|
|
public: |
|
|
|
public: |
|
|
|
std::function<QString()> localFile; |
|
|
|
std::function<QStringList()> localFiles; |
|
|
|
QStringList formats() const override { return {QStringLiteral("text/uri-list")}; } |
|
|
|
QStringList formats() const override { return {QStringLiteral("text/uri-list")}; } |
|
|
|
bool hasFormat(const QString& format) const override { return format == QStringLiteral("text/uri-list"); } |
|
|
|
bool hasFormat(const QString& format) const override { return format == QStringLiteral("text/uri-list"); } |
|
|
|
protected: |
|
|
|
protected: |
|
|
|
@ -69,9 +70,10 @@ protected: |
|
|
|
QVariant retrieveData(const QString& format, QVariant::Type type) const override { |
|
|
|
QVariant retrieveData(const QString& format, QVariant::Type type) const override { |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
if (!hasFormat(format)) return {}; |
|
|
|
if (!hasFormat(format)) return {}; |
|
|
|
QString local = localFile(); |
|
|
|
QStringList paths = localFiles(); |
|
|
|
if (local.isEmpty()) return {}; |
|
|
|
if (paths.isEmpty()) return {}; |
|
|
|
const_cast<DeferredFileMimeData*>(this)->setUrls({QUrl::fromLocalFile(local)}); |
|
|
|
QList<QUrl> urls; for (const auto& path : paths) urls.append(QUrl::fromLocalFile(path)); |
|
|
|
|
|
|
|
const_cast<DeferredFileMimeData*>(this)->setUrls(urls); |
|
|
|
return QMimeData::retrieveData(format, type); |
|
|
|
return QMimeData::retrieveData(format, type); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
@ -87,7 +89,6 @@ static bool copyPath(char* out, const QString& value) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
MemberFileBrowser::MemberFileBrowser(DbManager* db, QWidget* parent) : QDialog(parent), m_db(db) { |
|
|
|
MemberFileBrowser::MemberFileBrowser(DbManager* db, QWidget* parent) : QDialog(parent), m_db(db) { |
|
|
|
m_uploadPool.setMaxThreadCount(1); |
|
|
|
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(text("Group files")); resize(940, 600); |
|
|
|
setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(text("Group files")); resize(940, 600); |
|
|
|
auto* layout = new QVBoxLayout(this); |
|
|
|
auto* layout = new QVBoxLayout(this); |
|
|
|
auto* selectors = new QHBoxLayout; |
|
|
|
auto* selectors = new QHBoxLayout; |
|
|
|
@ -107,7 +108,7 @@ MemberFileBrowser::MemberFileBrowser(DbManager* db, QWidget* parent) : QDialog(p |
|
|
|
tree->exportRequested = [this] { exportFile(false); }; m_entries->setDragEnabled(true); |
|
|
|
tree->exportRequested = [this] { exportFile(false); }; m_entries->setDragEnabled(true); |
|
|
|
m_entries->setHeaderLabels({text("Name"), text("Type"), text("Size"), text("Modified")}); |
|
|
|
m_entries->setHeaderLabels({text("Name"), text("Type"), text("Size"), text("Modified")}); |
|
|
|
m_entries->header()->setSectionResizeMode(0, QHeaderView::Stretch); |
|
|
|
m_entries->header()->setSectionResizeMode(0, QHeaderView::Stretch); |
|
|
|
m_entries->setSelectionMode(QAbstractItemView::SingleSelection); layout->addWidget(m_entries, 1); |
|
|
|
m_entries->setSelectionMode(QAbstractItemView::ExtendedSelection); layout->addWidget(m_entries, 1); |
|
|
|
auto* actions = new QHBoxLayout; |
|
|
|
auto* actions = new QHBoxLayout; |
|
|
|
m_upload = new QPushButton(text("Upload…"), this); m_download = new QPushButton(text("Download…"), this); |
|
|
|
m_upload = new QPushButton(text("Upload…"), this); m_download = new QPushButton(text("Download…"), this); |
|
|
|
m_mkdir = new QPushButton(text("New folder…"), this); m_rename = new QPushButton(text("Rename…"), this); |
|
|
|
m_mkdir = new QPushButton(text("New folder…"), this); m_rename = new QPushButton(text("Rename…"), this); |
|
|
|
@ -117,7 +118,7 @@ MemberFileBrowser::MemberFileBrowser(DbManager* db, QWidget* parent) : QDialog(p |
|
|
|
m_uploadStatus = new QLabel(this); m_uploadStatus->setObjectName("memberFilesUploadStatus"); |
|
|
|
m_uploadStatus = new QLabel(this); m_uploadStatus->setObjectName("memberFilesUploadStatus"); |
|
|
|
m_uploadStatus->setWordWrap(true); layout->addWidget(m_uploadStatus); |
|
|
|
m_uploadStatus->setWordWrap(true); layout->addWidget(m_uploadStatus); |
|
|
|
m_progress = new QProgressBar(this); m_progress->setRange(0, 1000); m_progress->hide(); layout->addWidget(m_progress); |
|
|
|
m_progress = new QProgressBar(this); m_progress->setRange(0, 1000); m_progress->hide(); layout->addWidget(m_progress); |
|
|
|
m_copyAction = new QAction(text("Copy file"), this); m_copyAction->setObjectName("memberFilesCopy"); |
|
|
|
m_copyAction = new QAction(text("Copy"), this); m_copyAction->setObjectName("memberFilesCopy"); |
|
|
|
m_copyAction->setShortcut(QKeySequence::Copy); m_copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
|
|
|
m_copyAction->setShortcut(QKeySequence::Copy); m_copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
|
|
|
m_entries->addAction(m_copyAction); |
|
|
|
m_entries->addAction(m_copyAction); |
|
|
|
connect(m_copyAction, &QAction::triggered, this, [this] { exportFile(true); }); |
|
|
|
connect(m_copyAction, &QAction::triggered, this, [this] { exportFile(true); }); |
|
|
|
@ -170,8 +171,11 @@ MemberFileBrowser::MemberFileBrowser(DbManager* db, QWidget* parent) : QDialog(p |
|
|
|
m_entries->setContextMenuPolicy(Qt::CustomContextMenu); |
|
|
|
m_entries->setContextMenuPolicy(Qt::CustomContextMenu); |
|
|
|
connect(m_entries, &QWidget::customContextMenuRequested, this, [this](const QPoint& point) { |
|
|
|
connect(m_entries, &QWidget::customContextMenuRequested, this, [this](const QPoint& point) { |
|
|
|
auto* item = m_entries->itemAt(point); if (!item) return; |
|
|
|
auto* item = m_entries->itemAt(point); if (!item) return; |
|
|
|
m_entries->setCurrentItem(item); QMenu menu(this); |
|
|
|
if (!item->isSelected()) m_entries->setCurrentItem(item); |
|
|
|
|
|
|
|
else m_entries->setCurrentItem(item, 0, QItemSelectionModel::NoUpdate); |
|
|
|
|
|
|
|
QMenu menu(this); |
|
|
|
auto* open = menu.addAction(text("Open")); auto* info = menu.addAction(text("Properties")); |
|
|
|
auto* open = menu.addAction(text("Open")); auto* info = menu.addAction(text("Properties")); |
|
|
|
|
|
|
|
info->setEnabled(m_entries->selectedItems().size() == 1); |
|
|
|
menu.addAction(m_copyAction); |
|
|
|
menu.addAction(m_copyAction); |
|
|
|
auto* selected = menu.exec(m_entries->viewport()->mapToGlobal(point)); |
|
|
|
auto* selected = menu.exec(m_entries->viewport()->mapToGlobal(point)); |
|
|
|
if (selected == open) enter(item); |
|
|
|
if (selected == open) enter(item); |
|
|
|
@ -187,7 +191,6 @@ MemberFileBrowser::~MemberFileBrowser() { |
|
|
|
cancel(); cancelRequest(m_listId); |
|
|
|
cancel(); cancelRequest(m_listId); |
|
|
|
cancelRequest(m_quotaId); |
|
|
|
cancelRequest(m_quotaId); |
|
|
|
for (auto id : m_actions.keys()) cancelRequest(id); |
|
|
|
for (auto id : m_actions.keys()) cancelRequest(id); |
|
|
|
m_uploadPool.waitForDone(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool MemberFileBrowser::eventFilter(QObject* target, QEvent* event) { |
|
|
|
bool MemberFileBrowser::eventFilter(QObject* target, QEvent* event) { |
|
|
|
@ -197,7 +200,7 @@ bool MemberFileBrowser::eventFilter(QObject* target, QEvent* event) { |
|
|
|
auto* drop = static_cast<QDropEvent*>(event); |
|
|
|
auto* drop = static_cast<QDropEvent*>(event); |
|
|
|
QString reason; |
|
|
|
QString reason; |
|
|
|
if (drop->source() == m_entries) { m_dropHint->hide(); drop->ignore(); return true; } |
|
|
|
if (drop->source() == m_entries) { m_dropHint->hide(); drop->ignore(); return true; } |
|
|
|
if (m_dropId || m_uploadBatchId || m_transferId || m_exportId) reason = text("Finish or cancel the current transfer first"); |
|
|
|
if (m_dropId || m_batch || m_transferId || m_exportId) reason = text("Finish or cancel the current transfer first"); |
|
|
|
else if (!m_group || !server() || !m_db || !owner() || owner() != m_db->myNodeId() || !gui_bridge_is_uasync_ready()) |
|
|
|
else if (!m_group || !server() || !m_db || !owner() || owner() != m_db->myNodeId() || !gui_bridge_is_uasync_ready()) |
|
|
|
reason = text("Upload unavailable"); |
|
|
|
reason = text("Upload unavailable"); |
|
|
|
else if (!drop->mimeData()->hasUrls() || drop->mimeData()->urls().isEmpty() || !(drop->possibleActions() & Qt::CopyAction)) |
|
|
|
else if (!drop->mimeData()->hasUrls() || drop->mimeData()->urls().isEmpty() || !(drop->possibleActions() & Qt::CopyAction)) |
|
|
|
@ -229,7 +232,7 @@ bool MemberFileBrowser::eventFilter(QObject* target, QEvent* event) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::setChannel(const QString& channel, quint64 selectedOwner) { |
|
|
|
void MemberFileBrowser::setChannel(const QString& channel, quint64 selectedOwner) { |
|
|
|
if (m_dropId || m_uploadBatchId || m_transferId || m_exportId) { |
|
|
|
if (m_dropId || m_batch || m_transferId || m_exportId) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: context change blocked during transfer channel=%s", qPrintable(channel)); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: context change blocked during transfer channel=%s", qPrintable(channel)); |
|
|
|
m_status->setText(text("Finish or cancel the current transfer first")); return; |
|
|
|
m_status->setText(text("Finish or cancel the current transfer first")); return; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -271,13 +274,13 @@ QByteArray MemberFileBrowser::selectedVersion() const { |
|
|
|
void MemberFileBrowser::updateActions() { |
|
|
|
void MemberFileBrowser::updateActions() { |
|
|
|
auto* item = m_entries->currentItem(); bool own = owner() && owner() == m_db->myNodeId(); |
|
|
|
auto* item = m_entries->currentItem(); bool own = owner() && owner() == m_db->myNodeId(); |
|
|
|
bool available = server() && gui_bridge_is_uasync_ready(); |
|
|
|
bool available = server() && gui_bridge_is_uasync_ready(); |
|
|
|
bool file = item && item->data(0, Qt::UserRole + 1).toInt() == MF_FILE; |
|
|
|
int count = m_entries->selectedItems().size(); |
|
|
|
bool busy = m_dropId || m_uploadBatchId || m_transferId || m_exportId; |
|
|
|
bool busy = m_dropId || m_batch || m_transferId || m_exportId; |
|
|
|
m_servers->setEnabled(!busy); m_owners->setEnabled(!busy); m_navigation->setEnabled(!busy); m_entries->setEnabled(!busy); |
|
|
|
m_servers->setEnabled(!busy); m_owners->setEnabled(!busy); m_navigation->setEnabled(!busy); m_entries->setEnabled(!busy); |
|
|
|
m_upload->setEnabled(available && own && !busy); m_mkdir->setEnabled(available && own && !busy); |
|
|
|
m_upload->setEnabled(available && own && !busy); m_mkdir->setEnabled(available && own && !busy); |
|
|
|
m_rename->setEnabled(available && own && item && !busy); m_remove->setEnabled(available && own && item && !busy); |
|
|
|
m_rename->setEnabled(available && own && count == 1 && !busy); m_remove->setEnabled(available && own && count && !busy); |
|
|
|
m_download->setEnabled(available && owner() && file && !busy); m_cancel->setEnabled(busy); |
|
|
|
m_download->setEnabled(available && owner() && count && !busy); m_cancel->setEnabled(busy); |
|
|
|
m_copyAction->setEnabled(available && owner() && file && !busy); |
|
|
|
m_copyAction->setEnabled(available && owner() && count && !busy); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool MemberFileBrowser::post(mf_message& message) { |
|
|
|
bool MemberFileBrowser::post(mf_message& message) { |
|
|
|
@ -359,38 +362,7 @@ void MemberFileBrowser::onResponse(const uint8_t* data, int length) { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: GUI response id=%llu list_id=%llu cmd=%s status=%s bytes=%u owner=%llx/%llx path=%s", |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: GUI response id=%llu list_id=%llu cmd=%s status=%s bytes=%u owner=%llx/%llx path=%s", |
|
|
|
(unsigned long long)r.request_id, (unsigned long long)m_listId, mf_command_text(r.command), mf_status_text(r.status), r.data_len, |
|
|
|
(unsigned long long)r.request_id, (unsigned long long)m_listId, mf_command_text(r.command), mf_status_text(r.status), r.data_len, |
|
|
|
(unsigned long long)r.owner, (unsigned long long)owner(), r.path); |
|
|
|
(unsigned long long)r.owner, (unsigned long long)owner(), r.path); |
|
|
|
if (m_uploadBatchId && r.request_id == m_uploadStatId) { |
|
|
|
if (m_batch && m_batch->response(r)) return; |
|
|
|
m_uploadStatId = 0; |
|
|
|
|
|
|
|
QString local = m_uploadQueue.first(), name = QFileInfo(local).fileName(); |
|
|
|
|
|
|
|
QString remote = m_path.isEmpty() ? name : m_path + '/' + name; |
|
|
|
|
|
|
|
if (r.command != MF_STAT || r.owner != owner() || QString::fromUtf8(r.path) != remote) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file browser: upload STAT context mismatch id=%llu", (unsigned long long)r.request_id); |
|
|
|
|
|
|
|
finishUploads(text("Invalid directory response")); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (r.status != MF_OK && r.status != MF_NOT_FOUND) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: upload STAT failed path=%s status=%s", qPrintable(remote), mf_status_text(r.status)); |
|
|
|
|
|
|
|
finishUploads(text("Upload stopped: %1 — %2").arg(name, QString::fromUtf8(mf_status_text(r.status)))); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
QByteArray version(16, '\0'); |
|
|
|
|
|
|
|
if (r.status == MF_OK) { |
|
|
|
|
|
|
|
if (r.type != MF_FILE) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: upload target is a directory path=%s", qPrintable(remote)); |
|
|
|
|
|
|
|
finishUploads(text("Upload stopped: %1 — %2").arg(name, text("A folder with this name already exists"))); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
version = QByteArray((const char*)r.version, 16); |
|
|
|
|
|
|
|
quint64 batch = m_uploadBatchId; QPointer<MemberFileBrowser> guard(this); |
|
|
|
|
|
|
|
auto answer = QMessageBox::question(this, text("Replace file"), text("Replace %1?").arg(name), |
|
|
|
|
|
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); |
|
|
|
|
|
|
|
if (!guard || m_uploadBatchId != batch) return; |
|
|
|
|
|
|
|
if (answer == QMessageBox::Cancel) { cancel(); return; } |
|
|
|
|
|
|
|
if (answer != QMessageBox::Yes) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: upload skipped path=%s", qPrintable(remote)); |
|
|
|
|
|
|
|
m_uploadSkipped++; m_uploadQueue.removeFirst(); nextUpload(); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!startTransfer(remote, local, true, version)) finishUploads(text("Invalid path or chat is stopped")); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (r.command == MF_QUOTA) { |
|
|
|
if (r.command == MF_QUOTA) { |
|
|
|
if (r.request_id != m_quotaId || r.owner != owner()) { |
|
|
|
if (r.request_id != m_quotaId || r.owner != owner()) { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: GUI stale quota response id=%llu", (unsigned long long)r.request_id); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: GUI stale quota response id=%llu", (unsigned long long)r.request_id); |
|
|
|
@ -484,7 +456,7 @@ void MemberFileBrowser::upload() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::uploadOwnFiles(const QString& channel, const QStringList& paths) { |
|
|
|
void MemberFileBrowser::uploadOwnFiles(const QString& channel, const QStringList& paths) { |
|
|
|
if (m_dropId || m_uploadBatchId || m_transferId || m_exportId) { |
|
|
|
if (m_dropId || m_batch || m_transferId || m_exportId) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: drop rejected while busy files=%d", int(paths.size())); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: drop rejected while busy files=%d", int(paths.size())); |
|
|
|
QMessageBox::warning(this, text("Group files"), text("Finish or cancel the current transfer first")); return; |
|
|
|
QMessageBox::warning(this, text("Group files"), text("Finish or cancel the current transfer first")); return; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -496,97 +468,81 @@ void MemberFileBrowser::uploadOwnFiles(const QString& channel, const QStringList |
|
|
|
beginUploads(paths, true); |
|
|
|
beginUploads(paths, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Сервер выбирается до первого запроса; batch ID защищает вложенные циклы диалогов от отмены/закрытия окна.
|
|
|
|
QList<MemberFileEntry> MemberFileBrowser::selectedEntries() const { |
|
|
|
|
|
|
|
QList<MemberFileEntry> entries; |
|
|
|
|
|
|
|
for (int i = 0; i < m_entries->topLevelItemCount(); ++i) { |
|
|
|
|
|
|
|
auto* item = m_entries->topLevelItem(i); |
|
|
|
|
|
|
|
if (!item->isSelected()) continue; |
|
|
|
|
|
|
|
QString name = item->data(0, Qt::UserRole).toString(); |
|
|
|
|
|
|
|
entries.append({m_path.isEmpty() ? name : m_path + '/' + name, {}, item->data(0, Qt::UserRole + 2).toByteArray(), |
|
|
|
|
|
|
|
item->data(0, Qt::UserRole + 1).toInt() == MF_DIRECTORY}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return entries; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MemberFileBatch* MemberFileBrowser::createBatch(MemberFileBatch::Mode mode) { |
|
|
|
|
|
|
|
auto* batch = new MemberFileBatch(m_group, server(), owner(), mode, this); m_batch = batch; |
|
|
|
|
|
|
|
batch->progress = [this](const QString& status, int value) { |
|
|
|
|
|
|
|
m_uploadStatus->setText(status); m_progress->show(); |
|
|
|
|
|
|
|
m_progress->setRange(0, value < 0 ? 0 : 1000); if (value >= 0) m_progress->setValue(value); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
batch->conflict = [this](const QString& path, bool sameType) { |
|
|
|
|
|
|
|
return int(QMessageBox::question(this, text("Replace file"), |
|
|
|
|
|
|
|
sameType ? text("Replace %1?").arg(path) : text("File and folder types conflict: %1").arg(path), |
|
|
|
|
|
|
|
sameType ? QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel : |
|
|
|
|
|
|
|
QMessageBox::No | QMessageBox::Cancel, QMessageBox::No)); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
updateActions(); return batch; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Сервер выбирается до первого запроса; затем контекст хранит очередь.
|
|
|
|
void MemberFileBrowser::beginUploads(const QStringList& paths, bool chooseServer) { |
|
|
|
void MemberFileBrowser::beginUploads(const QStringList& paths, bool chooseServer) { |
|
|
|
if (paths.isEmpty()) return; |
|
|
|
if (paths.isEmpty()) return; |
|
|
|
if (m_dropId || m_uploadBatchId || m_transferId || m_exportId || !server() || !owner() || |
|
|
|
if (m_dropId || m_batch || m_transferId || m_exportId || !server() || !owner() || |
|
|
|
owner() != m_db->myNodeId() || !gui_bridge_is_uasync_ready()) { |
|
|
|
owner() != m_db->myNodeId() || !gui_bridge_is_uasync_ready()) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: upload unavailable group=%llu server=%llu owner=%llu busy=%d ready=%d", |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: upload unavailable"); |
|
|
|
(unsigned long long)m_group, (unsigned long long)server(), (unsigned long long)owner(), |
|
|
|
QMessageBox::warning(this, text("Group files"), text("Upload unavailable")); return; |
|
|
|
bool(m_dropId || m_uploadBatchId || m_transferId || m_exportId), gui_bridge_is_uasync_ready()); |
|
|
|
|
|
|
|
QMessageBox::warning(this, text("Group files"), !server() ? text("No media server in this group") : text("Upload unavailable")); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
for (const auto& path : paths) { |
|
|
|
|
|
|
|
QFileInfo info(path); QString remote = m_path.isEmpty() ? info.fileName() : m_path + '/' + info.fileName(); |
|
|
|
|
|
|
|
QByteArray localBytes = path.toUtf8(), remoteBytes = remote.toUtf8(); |
|
|
|
|
|
|
|
if (!info.isAbsolute() || info.fileName().isEmpty() || localBytes.contains('\0') || localBytes.size() > MF_PATH_MAX || |
|
|
|
|
|
|
|
remoteBytes.contains('\0') || !mf_path_valid(remoteBytes.constData(), 0)) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: invalid upload path=%s", qPrintable(path)); |
|
|
|
|
|
|
|
QMessageBox::warning(this, text("Group files"), text("Invalid upload path: %1").arg(path)); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
m_uploadQueue = paths; m_uploadQueue.removeDuplicates(); m_uploadTotal = m_uploadQueue.size(); |
|
|
|
|
|
|
|
m_uploadDone = m_uploadSkipped = 0; m_uploadBatchId = requestId(); updateActions(); |
|
|
|
|
|
|
|
if (chooseServer) { |
|
|
|
if (chooseServer) { |
|
|
|
QStringList labels; for (int i = 0; i < m_servers->count(); i++) labels.append(m_servers->itemText(i)); |
|
|
|
QStringList labels; for (int i = 0; i < m_servers->count(); i++) labels.append(m_servers->itemText(i)); |
|
|
|
quint64 batch = m_uploadBatchId; QPointer<MemberFileBrowser> guard(this); bool ok = false; |
|
|
|
QPointer<MemberFileBrowser> guard(this); bool ok = false; |
|
|
|
|
|
|
|
m_dropId = requestId(); updateActions(); |
|
|
|
QString selected = QInputDialog::getItem(this, text("Upload files"), |
|
|
|
QString selected = QInputDialog::getItem(this, text("Upload files"), |
|
|
|
text("Upload %1 file(s) to your root directory in group %2. Select media server:").arg(m_uploadTotal).arg(m_group), |
|
|
|
text("Upload %1 file(s) to your root directory in group %2. Select media server:").arg(paths.size()).arg(m_group), |
|
|
|
labels, m_servers->currentIndex(), false, &ok); |
|
|
|
labels, m_servers->currentIndex(), false, &ok); |
|
|
|
if (!guard || m_uploadBatchId != batch) return; |
|
|
|
if (!guard) return; |
|
|
|
if (!ok) { cancel(); return; } |
|
|
|
bool cancelled = !m_dropId; m_dropId = 0; updateActions(); |
|
|
|
|
|
|
|
if (!ok || cancelled) return; |
|
|
|
m_servers->setCurrentIndex(labels.indexOf(selected)); |
|
|
|
m_servers->setCurrentIndex(labels.indexOf(selected)); |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: upload queue started batch=%llu group=%llu server=%llu owner=%llu path=%s files=%d", |
|
|
|
auto* batch = createBatch(MemberFileBatch::Upload); |
|
|
|
(unsigned long long)m_uploadBatchId, (unsigned long long)m_group, (unsigned long long)server(), |
|
|
|
batch->finished = [this, batch](bool, const QString& summary, const QStringList&) { |
|
|
|
(unsigned long long)owner(), qPrintable(m_path), m_uploadTotal); |
|
|
|
m_batch = nullptr; m_progress->hide(); m_uploadStatus->setText(summary); batch->deleteLater(); updateActions(); refresh(); |
|
|
|
m_uploadStatus->setText(text("Checking selected files…")); |
|
|
|
if (m_closeAfterBatch) close(); |
|
|
|
quint64 batch = m_uploadBatchId; QStringList sources = m_uploadQueue; |
|
|
|
}; |
|
|
|
m_uploadPool.start(QRunnable::create([this, batch, sources] { |
|
|
|
m_uploadStatus->setText(text("Checking selected files…")); batch->upload(paths, m_path); |
|
|
|
QString invalid; |
|
|
|
|
|
|
|
for (const auto& source : sources) { |
|
|
|
|
|
|
|
QFileInfo info(source); |
|
|
|
|
|
|
|
if (!info.isFile() || info.isSymLink() || !info.isReadable()) { invalid = source; break; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, [this, batch, invalid] { |
|
|
|
|
|
|
|
if (m_uploadBatchId != batch) return; |
|
|
|
|
|
|
|
if (!invalid.isEmpty()) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: source rejected path=%s (not a readable regular file)", qPrintable(invalid)); |
|
|
|
|
|
|
|
finishUploads(text("Cannot upload %1. Select readable files, not folders or links.").arg(invalid)); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
nextUpload(); |
|
|
|
|
|
|
|
}, Qt::QueuedConnection); |
|
|
|
|
|
|
|
})); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::nextUpload() { |
|
|
|
|
|
|
|
if (!m_uploadBatchId || m_transferId || m_uploadStatId) return; |
|
|
|
|
|
|
|
if (m_uploadQueue.isEmpty()) { |
|
|
|
|
|
|
|
finishUploads(text("Upload queue finished: %1 uploaded, %2 skipped").arg(m_uploadDone).arg(m_uploadSkipped)); |
|
|
|
|
|
|
|
refresh(); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
QString name = QFileInfo(m_uploadQueue.first()).fileName(); |
|
|
|
|
|
|
|
m_uploadStatus->setText(text("Uploading %1 of %2: %3").arg(m_uploadDone + m_uploadSkipped + 1).arg(m_uploadTotal).arg(name)); |
|
|
|
|
|
|
|
mf_message q{}; q.command = MF_STAT; q.owner = owner(); |
|
|
|
|
|
|
|
if (!copyPath(q.path, m_path.isEmpty() ? name : m_path + '/' + name) || !post(q)) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file browser: upload STAT could not start name=%s", qPrintable(name)); |
|
|
|
|
|
|
|
finishUploads(text("Unable to request directory")); return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
m_uploadStatId = q.request_id; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::finishUploads(const QString& status) { |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: upload queue finished batch=%llu uploaded=%d skipped=%d remaining=%d status=%s", |
|
|
|
|
|
|
|
(unsigned long long)m_uploadBatchId, m_uploadDone, m_uploadSkipped, int(m_uploadQueue.size()), qPrintable(status)); |
|
|
|
|
|
|
|
cancelRequest(m_uploadStatId); m_uploadStatId = 0; m_uploadBatchId = 0; m_uploadQueue.clear(); |
|
|
|
|
|
|
|
m_uploadStatus->setText(status); updateActions(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::download(bool open) { |
|
|
|
void MemberFileBrowser::download(bool open) { |
|
|
|
auto* item = m_entries->currentItem(); |
|
|
|
auto* item = m_entries->currentItem(); |
|
|
|
if (m_transferId || !owner() || !item || item->data(0, Qt::UserRole + 1).toInt() != MF_FILE) return; |
|
|
|
if (m_transferId || m_batch || !owner() || !item) return; |
|
|
|
|
|
|
|
if (!open && (m_entries->selectedItems().size() > 1 || item->data(0, Qt::UserRole + 1).toInt() == MF_DIRECTORY)) { |
|
|
|
|
|
|
|
QPointer<MemberFileBrowser> guard(this); |
|
|
|
|
|
|
|
QString directory = QFileDialog::getExistingDirectory(this, text("Save file")); |
|
|
|
|
|
|
|
if (guard && !directory.isEmpty()) exportFile(false, directory); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (item->data(0, Qt::UserRole + 1).toInt() != MF_FILE) return; |
|
|
|
QString local = QFileDialog::getSaveFileName(this, text("Save file"), item->data(0, Qt::UserRole).toString()); |
|
|
|
QString local = QFileDialog::getSaveFileName(this, text("Save file"), item->data(0, Qt::UserRole).toString()); |
|
|
|
if (local.isEmpty()) return; |
|
|
|
if (local.isEmpty()) return; |
|
|
|
m_openAfter = open; startTransfer(selectedPath(), local, false, selectedVersion()); |
|
|
|
m_openAfter = open; startTransfer(selectedPath(), local, false, selectedVersion()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::publishExport(const QString& local, bool clipboard) { |
|
|
|
void MemberFileBrowser::publishExport(const QStringList& paths, bool clipboard) { |
|
|
|
auto* mime = new QMimeData; QUrl url = QUrl::fromLocalFile(local); mime->setUrls({url}); |
|
|
|
auto* mime = new QMimeData; QList<QUrl> urls; QByteArray gnome("copy"); |
|
|
|
// Nautilus и KDE также получают явное действие copy, без удаления исходного файла.
|
|
|
|
for (const auto& path : paths) { QUrl url = QUrl::fromLocalFile(path); urls.append(url); gnome += '\n' + url.toEncoded(); } |
|
|
|
mime->setData("x-special/gnome-copied-files", QByteArray("copy\n") + url.toEncoded()); |
|
|
|
mime->setUrls(urls); mime->setData("x-special/gnome-copied-files", gnome); mime->setData("application/x-kde-cutselection", "0"); |
|
|
|
mime->setData("application/x-kde-cutselection", "0"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: exporting entries=%d clipboard=%d", int(paths.size()), clipboard); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: exporting local=%s clipboard=%d", qPrintable(local), clipboard); |
|
|
|
|
|
|
|
if (clipboard) { |
|
|
|
if (clipboard) { |
|
|
|
QGuiApplication::clipboard()->setMimeData(mime); |
|
|
|
QGuiApplication::clipboard()->setMimeData(mime); |
|
|
|
m_status->setText(text("File copied. Paste it in your file manager.")); |
|
|
|
m_status->setText(text("File copied. Paste it in your file manager.")); |
|
|
|
@ -595,63 +551,41 @@ void MemberFileBrowser::publishExport(const QString& local, bool clipboard) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Кеш сохраняется после закрытия окна: URI в системном буфере должен оставаться пригодным для вставки.
|
|
|
|
void MemberFileBrowser::exportFile(bool clipboard, const QString& destination) { |
|
|
|
void MemberFileBrowser::exportFile(bool clipboard) { |
|
|
|
|
|
|
|
if (!m_copyAction->isEnabled()) { |
|
|
|
if (!m_copyAction->isEnabled()) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: export unavailable group=%llu owner=%llu busy=%d", |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: export unavailable"); |
|
|
|
(unsigned long long)m_group, (unsigned long long)owner(), bool(m_dropId || m_uploadBatchId || m_transferId || m_exportId)); |
|
|
|
|
|
|
|
m_status->setText(text("Select a file and finish the current transfer first")); return; |
|
|
|
m_status->setText(text("Select a file and finish the current transfer first")); return; |
|
|
|
} |
|
|
|
} |
|
|
|
QString remote = selectedPath(); QByteArray version = selectedVersion(); |
|
|
|
auto entries = selectedEntries(); if (entries.isEmpty()) return; |
|
|
|
if (version.size() != 16 || !mf_path_valid(remote.toUtf8().constData(), 0)) { |
|
|
|
QString base = destination.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/file-exports" : destination; |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file browser: invalid export entry path=%s version_bytes=%d", qPrintable(remote), int(version.size())); |
|
|
|
m_exportId = requestId(); m_exportResult.clear(); m_openAfter = false; |
|
|
|
m_status->setText(text("Invalid directory response")); return; |
|
|
|
quint64 id = m_exportId; |
|
|
|
} |
|
|
|
auto* batch = createBatch(MemberFileBatch::Download); |
|
|
|
QString key = QString("%1:%2:%3:%4:%5").arg(m_group).arg(server()).arg(owner()).arg(remote, QString::fromLatin1(version.toHex())); |
|
|
|
batch->finished = [this, batch, clipboard, destination](bool ok, const QString& summary, const QStringList& roots) { |
|
|
|
if (m_exportCache.contains(key)) { publishExport(m_exportCache.value(key), clipboard); return; } |
|
|
|
m_batch = nullptr; m_exportId = 0; m_progress->hide(); m_uploadStatus->setText(summary); |
|
|
|
QString base = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); |
|
|
|
m_exportResult = ok ? roots : QStringList(); batch->deleteLater(); updateActions(); |
|
|
|
if (base.isEmpty()) { |
|
|
|
if (ok && clipboard) publishExport(roots, true); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file browser: no cache location for export"); |
|
|
|
else m_status->setText(ok ? text("Local copy ready") : summary); |
|
|
|
m_status->setText(text("Cannot prepare a local copy")); return; |
|
|
|
emit exportFinished(); |
|
|
|
} |
|
|
|
if (m_closeAfterBatch) QTimer::singleShot(0, this, &QWidget::close); |
|
|
|
m_exportId = requestId(); m_exportKey = key; m_exportClipboard = clipboard; m_openAfter = false; updateActions(); |
|
|
|
}; |
|
|
|
m_status->setText(text("Downloading a local copy for your file manager…")); |
|
|
|
m_status->setText(text("Downloading a local copy for your file manager…")); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: preparing export group=%llu server=%llu owner=%llu path=%s clipboard=%d", |
|
|
|
batch->start(entries, base, destination.isEmpty()); |
|
|
|
(unsigned long long)m_group, (unsigned long long)server(), (unsigned long long)owner(), qPrintable(remote), clipboard); |
|
|
|
if (!clipboard && destination.isEmpty()) { |
|
|
|
quint64 id = m_exportId; QString name = QFileInfo(remote).fileName(); |
|
|
|
|
|
|
|
m_uploadPool.start(QRunnable::create([this, id, base, name, remote, version] { |
|
|
|
|
|
|
|
QString root = base + "/file-exports", local; |
|
|
|
|
|
|
|
if (QDir().mkpath(root)) { |
|
|
|
|
|
|
|
QTemporaryDir directory(root + "/XXXXXX"); |
|
|
|
|
|
|
|
if (directory.isValid()) { local = directory.filePath(name); directory.setAutoRemove(false); } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, [this, id, local, remote, version] { |
|
|
|
|
|
|
|
if (m_exportId != id) return; |
|
|
|
|
|
|
|
if (local.isEmpty() || !startTransfer(remote, local, false, version)) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file browser: export could not start remote=%s local=%s", qPrintable(remote), qPrintable(local)); |
|
|
|
|
|
|
|
m_exportId = 0; updateActions(); m_status->setText(text("Cannot prepare a local copy")); |
|
|
|
|
|
|
|
emit exportFinished(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, Qt::QueuedConnection); |
|
|
|
|
|
|
|
})); |
|
|
|
|
|
|
|
if (!clipboard) { |
|
|
|
|
|
|
|
QPointer<MemberFileBrowser> guard(this); |
|
|
|
QPointer<MemberFileBrowser> guard(this); |
|
|
|
auto* mime = new DeferredFileMimeData; |
|
|
|
auto* mime = new DeferredFileMimeData; |
|
|
|
mime->localFile = [guard, id, key] { |
|
|
|
mime->localFiles = [guard, id] { |
|
|
|
if (!guard) return QString(); |
|
|
|
if (!guard) return QStringList(); |
|
|
|
if (guard->m_exportId == id) { |
|
|
|
if (guard->m_exportId == id) { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "file browser: drag waiting for local copy id=%llu", (unsigned long long)id); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "file browser: drag waiting for batch id=%llu", (unsigned long long)id); |
|
|
|
QEventLoop wait; |
|
|
|
QEventLoop wait; |
|
|
|
QObject::connect(guard, &MemberFileBrowser::exportFinished, &wait, &QEventLoop::quit); |
|
|
|
QObject::connect(guard, &MemberFileBrowser::exportFinished, &wait, &QEventLoop::quit); |
|
|
|
QObject::connect(guard, &QObject::destroyed, &wait, &QEventLoop::quit); |
|
|
|
QObject::connect(guard, &QObject::destroyed, &wait, &QEventLoop::quit); |
|
|
|
wait.exec(); |
|
|
|
wait.exec(); |
|
|
|
} |
|
|
|
} |
|
|
|
QString local = guard ? guard->m_exportCache.value(key) : QString(); |
|
|
|
return guard ? guard->m_exportResult : QStringList(); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "file browser: drag data ready id=%llu local=%s", (unsigned long long)id, qPrintable(local)); |
|
|
|
|
|
|
|
return local; |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
auto* drag = new QDrag(m_entries); drag->setMimeData(mime); drag->setParent(QCoreApplication::instance()); |
|
|
|
auto* drag = new QDrag(m_entries); drag->setMimeData(mime); drag->setParent(QCoreApplication::instance()); |
|
|
|
// MIME живёт до возврата из native drag, даже если окно файлов закрыли во вложенном цикле событий.
|
|
|
|
|
|
|
|
connect(this, &QObject::destroyed, drag, [] { QDrag::cancel(); }); |
|
|
|
connect(this, &QObject::destroyed, drag, [] { QDrag::cancel(); }); |
|
|
|
Qt::DropAction action = drag->exec(Qt::CopyAction, Qt::CopyAction); |
|
|
|
Qt::DropAction action = drag->exec(Qt::CopyAction, Qt::CopyAction); |
|
|
|
if (guard && m_exportId == id) cancel(); |
|
|
|
if (guard && m_exportId == id) cancel(); |
|
|
|
@ -677,38 +611,13 @@ bool MemberFileBrowser::startTransfer(const QString& remote, const QString& loca |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::onTransfer(const uint8_t* data, int length) { |
|
|
|
void MemberFileBrowser::onTransfer(const uint8_t* data, int length) { |
|
|
|
|
|
|
|
if (m_batch && m_batch->transfer(data, length)) return; |
|
|
|
if (length != 51 || mf_get64(data) != m_transferId) return; |
|
|
|
if (length != 51 || mf_get64(data) != m_transferId) return; |
|
|
|
quint64 done = mf_get64(data + 32), total = mf_get64(data + 40); unsigned status = data[48] | ((unsigned)data[49] << 8); |
|
|
|
quint64 done = mf_get64(data + 32), total = mf_get64(data + 40); unsigned status = data[48] | ((unsigned)data[49] << 8); |
|
|
|
if (total) { m_progress->setRange(0, 1000); m_progress->setValue((int)((long double)done * 1000 / total)); } |
|
|
|
if (total) { m_progress->setRange(0, 1000); m_progress->setValue((int)((long double)done * 1000 / total)); } |
|
|
|
m_status->setText(text("Transferred %1 / %2 bytes").arg(done).arg(total)); |
|
|
|
m_status->setText(text("Transferred %1 / %2 bytes").arg(done).arg(total)); |
|
|
|
if (data[50] != 2) return; |
|
|
|
if (data[50] != 2) return; |
|
|
|
m_transferId = 0; m_progress->hide(); updateActions(); |
|
|
|
m_transferId = 0; m_progress->hide(); updateActions(); |
|
|
|
if (m_exportId) { |
|
|
|
|
|
|
|
m_exportId = 0; updateActions(); |
|
|
|
|
|
|
|
if (status) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: export download failed local=%s status=%s", qPrintable(m_local), mf_status_text(status)); |
|
|
|
|
|
|
|
m_status->setText(QString::fromUtf8(mf_status_text(status))); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
m_exportCache.insert(m_exportKey, m_local); |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: local copy ready path=%s", qPrintable(m_local)); |
|
|
|
|
|
|
|
if (m_exportClipboard) publishExport(m_local, true); |
|
|
|
|
|
|
|
else m_status->setText(text("Local copy ready")); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
emit exportFinished(); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (m_uploadBatchId) { |
|
|
|
|
|
|
|
if (status) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file browser: upload failed local=%s status=%s", qPrintable(m_local), mf_status_text(status)); |
|
|
|
|
|
|
|
QString reason = status == MF_INVALID_PATH ? text("Only regular files with valid paths can be uploaded") : |
|
|
|
|
|
|
|
QString::fromUtf8(mf_status_text(status)); |
|
|
|
|
|
|
|
finishUploads(text("Upload stopped: %1 — %2").arg(QFileInfo(m_local).fileName(), reason)); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
m_uploadDone++; m_uploadQueue.removeFirst(); nextUpload(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!m_quotaId) requestQuota(); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (status) m_status->setText(QString::fromUtf8(mf_status_text((int)status))); |
|
|
|
if (status) m_status->setText(QString::fromUtf8(mf_status_text((int)status))); |
|
|
|
else { |
|
|
|
else { |
|
|
|
m_status->setText(text("Transfer completed")); |
|
|
|
m_status->setText(text("Transfer completed")); |
|
|
|
@ -722,7 +631,21 @@ void MemberFileBrowser::onTransfer(const uint8_t* data, int length) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::mutate(uint8_t command) { |
|
|
|
void MemberFileBrowser::mutate(uint8_t command) { |
|
|
|
if (!owner() || owner() != m_db->myNodeId()) return; |
|
|
|
if (!owner() || owner() != m_db->myNodeId() || m_batch || m_transferId || m_dropId) return; |
|
|
|
|
|
|
|
if (command == MF_REMOVE_PATH) { |
|
|
|
|
|
|
|
auto entries = selectedEntries(); if (entries.isEmpty()) return; |
|
|
|
|
|
|
|
QPointer<MemberFileBrowser> guard(this); |
|
|
|
|
|
|
|
QString prompt = entries.size() == 1 ? text("Delete %1?").arg(entries.first().remote) : |
|
|
|
|
|
|
|
text("Delete %1 selected entries? Non-empty folders will not be removed.").arg(entries.size()); |
|
|
|
|
|
|
|
auto answer = QMessageBox::question(this, text("Delete"), prompt, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); |
|
|
|
|
|
|
|
if (!guard || answer != QMessageBox::Yes) return; |
|
|
|
|
|
|
|
auto* batch = createBatch(MemberFileBatch::Remove); |
|
|
|
|
|
|
|
batch->finished = [this, batch](bool, const QString& summary, const QStringList&) { |
|
|
|
|
|
|
|
m_batch = nullptr; m_progress->hide(); m_uploadStatus->setText(summary); batch->deleteLater(); updateActions(); refresh(); |
|
|
|
|
|
|
|
if (m_closeAfterBatch) close(); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
batch->start(entries); return; |
|
|
|
|
|
|
|
} |
|
|
|
mf_message q{}; q.command = command; q.owner = owner(); |
|
|
|
mf_message q{}; q.command = command; q.owner = owner(); |
|
|
|
QString source = selectedPath(); QByteArray version = selectedVersion(); |
|
|
|
QString source = selectedPath(); QByteArray version = selectedVersion(); |
|
|
|
if (command == MF_MKDIR || command == MF_RENAME) { |
|
|
|
if (command == MF_MKDIR || command == MF_RENAME) { |
|
|
|
@ -742,12 +665,12 @@ void MemberFileBrowser::mutate(uint8_t command) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::cancel() { |
|
|
|
void MemberFileBrowser::cancel() { |
|
|
|
if (m_exportId) { |
|
|
|
if (m_batch) m_batch->cancel(); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file browser: export cancelled id=%llu", (unsigned long long)m_exportId); |
|
|
|
|
|
|
|
m_exportId = 0; m_status->setText(text("Copy cancelled")); updateActions(); |
|
|
|
|
|
|
|
emit exportFinished(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (m_dropId) { m_dropId = 0; m_uploadStatus->setText(text("Upload queue cancelled")); updateActions(); } |
|
|
|
if (m_dropId) { m_dropId = 0; m_uploadStatus->setText(text("Upload queue cancelled")); updateActions(); } |
|
|
|
if (m_uploadBatchId) finishUploads(text("Upload queue cancelled")); |
|
|
|
|
|
|
|
cancelRequest(m_transferId); |
|
|
|
cancelRequest(m_transferId); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MemberFileBrowser::closeEvent(QCloseEvent* event) { |
|
|
|
|
|
|
|
if (m_batch) { m_closeAfterBatch = true; event->ignore(); cancel(); return; } |
|
|
|
|
|
|
|
QDialog::closeEvent(event); |
|
|
|
|
|
|
|
} |
|
|
|
|