diff --git a/.github/workflows/call-update-changelog.yml b/.github/workflows/call-update-changelog.yml new file mode 100644 index 00000000..b76b9b3f --- /dev/null +++ b/.github/workflows/call-update-changelog.yml @@ -0,0 +1,53 @@ +name: Call update changelog + +on: + workflow_dispatch: + inputs: + version: + description: "Target version; leave empty to auto bump" + required: false + type: string + name: + description: "Maintainer name used in debian/changelog" + required: true + type: string + email: + description: "Maintainer email used in debian/changelog" + required: true + type: string + base_branch: + description: "Base branch to read and target" + required: false + default: master + type: string + distribution: + description: "Changelog distribution" + required: false + default: unstable + type: string + create_pr: + description: "Create a pull request after generating changelog" + required: false + default: true + type: boolean + +jobs: + update_changelog: + uses: linuxdeepin/.github/.github/workflows/update-changelog.yml@master + with: + version: ${{ inputs.version }} + name: ${{ inputs.name }} + email: ${{ inputs.email }} + base_branch: ${{ inputs.base_branch }} + distribution: ${{ inputs.distribution }} + create_pr: ${{ inputs.create_pr }} + secrets: inherit + + show_result: + runs-on: ubuntu-latest + needs: update_changelog + steps: + - name: Show update changelog result + run: | + echo "Version: ${{ needs.update_changelog.outputs.version }}" + echo "Pull Request URL: ${{ needs.update_changelog.outputs.pr_url }}" diff --git a/debian/changelog b/debian/changelog index 067b8746..3c5dce85 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,23 @@ +util-dfm (1.3.72) unstable; urgency=medium + + * fix: include share name in SMB keyring credentials + + -- Zhang Sheng Fri, 28 Aug 2026 14:21:23 +0800 + +util-dfm (1.3.71) unstable; urgency=medium + + * fix: restrict path traversal check to file scheme in DEnumerator buildUrl + + -- zhangsheng Thu, 06 Aug 2026 10:24:21 +0800 + +util-dfm (1.3.70) unstable; urgency=medium + + * perf(dfm-io): cache statx results in DFileInfoPrivate::attributesBySelf + * fix: prevent BOM character loss in path concatenation + * feat: add dvd+rw-format support for DVD RW erase + + -- Zhang Sheng Fri, 24 Jul 2026 09:51:44 +0800 + util-dfm (1.3.59) unstable; urgency=medium * fix: improve symlink handling in filename search diff --git a/debian/control.in b/debian/control.in index 6335b292..21dcfec9 100644 --- a/debian/control.in +++ b/debian/control.in @@ -76,7 +76,8 @@ Section: libdevel Multi-Arch: same Depends: ${shlibs:Depends}, - ${misc:Depends} + ${misc:Depends}, + dvd+rw-tools Recommends: libudfburn Description: A library about burning CD. diff --git a/src/dfm-burn/dfm-burn-lib/dopticaldiscmanager.cpp b/src/dfm-burn/dfm-burn-lib/dopticaldiscmanager.cpp index 270f06de..6860f76f 100644 --- a/src/dfm-burn/dfm-burn-lib/dopticaldiscmanager.cpp +++ b/src/dfm-burn/dfm-burn-lib/dopticaldiscmanager.cpp @@ -8,6 +8,7 @@ #include "private/dopticaldiscmanager_p.h" #include "private/dxorrisoengine.h" #include "private/dudfburnengine.h" +#include "private/dvdrwformatengine.h" #include "private/dsm3hash.h" #include @@ -144,6 +145,36 @@ bool DOpticalDiscManager::commit(const BurnOptions &opts, int speed, const QStri bool DOpticalDiscManager::erase() { bool ret { false }; + + // 查询介质类型 + MediaType mediaType { MediaType::kNoMedia }; + { + QScopedPointer info { DOpticalDiscManager::createOpticalInfo(dptr->curDev) }; + if (info) + mediaType = info->mediaType(); + } + + // DVD±RW 且 dvd+rw-format 可用时走新引擎 + bool isDvdRw = (mediaType == MediaType::kDVD_PLUS_RW || mediaType == MediaType::kDVD_RW); + QString dvdRwFormat = QStandardPaths::findExecutable("dvd+rw-format"); + + if (isDvdRw && !dvdRwFormat.isEmpty()) { + QScopedPointer engine { new DVDRwFormatEngine }; + connect( + engine.data(), &DVDRwFormatEngine::jobStatusChanged, this, + [this](JobStatus status, int progress) { + Q_EMIT jobStatusChanged(status, progress, {}, {}); + }, + Qt::DirectConnection); + + ret = engine->doErase(dptr->curDev, mediaType); + return ret; + } + + // 降级:走 xorriso 原路径 + if (isDvdRw) + qWarning() << "[dfm-burn] dvd+rw-format not found, falling back to xorriso for DVD±RW erase"; + QScopedPointer engine { new DXorrisoEngine }; connect( engine.data(), &DXorrisoEngine::jobStatusChanged, this, diff --git a/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.cpp b/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.cpp new file mode 100644 index 00000000..004fa275 --- /dev/null +++ b/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.cpp @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dvdrwformatengine.h" + +#include +#include +#include +#include +#include + +DFM_BURN_USE_NS + +DVDRwFormatEngine::DVDRwFormatEngine(QObject *parent) + : QObject(parent) +{ +} + +DVDRwFormatEngine::~DVDRwFormatEngine() +{ +} + +bool DVDRwFormatEngine::doErase(const QString &dev, MediaType type) +{ + phase = 0; + lastRawProgress = 0; + + // 1. 校验设备路径 + QString cleanDev = QDir::cleanPath(dev); + if (!cleanDev.startsWith("/dev/sr")) { + qWarning() << "[dfm-burn] Invalid device path:" << dev; + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } + + // 2. 查找 dvd+rw-format 二进制 + QString binary = QStandardPaths::findExecutable("dvd+rw-format"); + if (binary.isEmpty()) { + qWarning() << "[dfm-burn] dvd+rw-format not found"; + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } + + // 3. 根据介质类型选择命令参数 + QStringList args; + if (type == MediaType::kDVD_PLUS_RW) { + args << "-force" + << "-gui" + << cleanDev; + } else if (type == MediaType::kDVD_RW) { + args << "-blank=full" + << "-gui" + << cleanDev; + } else { + qWarning() << "[dfm-burn] Unsupported media type for dvd+rw-format:" << static_cast(type); + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } + + Q_EMIT jobStatusChanged(JobStatus::kRunning, 0); + + // dvd+rw-format 进度输出机制分析(源码 dvd+rw-format.cpp): + // + // 1. fork() 后子进程 setsid() 执行 SCSI 操作,父进程安装 SIGALRM + // 定时轮询共享内存中的 progress 值(0~65536),用退格符覆盖式 + // 输出百分比到 stderr。 + // + // 2. -gui 标志的 gui_action 赋值在 fork 之后的子进程中(727 行), + // 父进程的 gui_action 始终为 NULL(129 行),所以 -gui 对父进程 + // 的进度输出格式无影响——父进程始终用 \b 退格覆盖方式。 + // + // 3. DVD+RW -force 路径有 3 个 wait_for_unit(progress) 调用 + // (FORMAT UNIT → STOP DE-ICING → CLOSE SESSION), + // 每个 SCSI 操作的进度指示器独立从 0 开始, + // 导致原始输出出现 3 个 0→100% 周期。 + // DVD-RW -blank 路径只有 1 个 wait_for_unit,单周期。 + // + // 4. 进度数据通过 stderr 管道传输。QProcess::readyReadStandardOutput + // 信号在 QEventLoop + MergedChannels 下可能不能及时触发(尤其在 + // DVD-RW 单阶段输出稀疏时)。改用 waitForReadyRead 主动轮询 + // 管道——其内部调用 processEvents 处理 Qt 信号,进度可实时传递。 + + int totalPhases = (type == MediaType::kDVD_PLUS_RW) ? 3 : 1; + + QProcess proc; + proc.setProcessChannelMode(QProcess::MergedChannels); + + QByteArray buffer; + + proc.start(binary, args); + if (!proc.waitForStarted()) { + qWarning() << "[dfm-burn] Failed to start dvd+rw-format"; + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } + + // 主动轮询管道,不依赖 readyReadStandardOutput 信号。 + // waitForReadyRead 内部调用 processEvents,emit 的信号能被上层接收。 + const int pollInterval { 200 }; // ms + + while (proc.state() == QProcess::Running) { + proc.waitForReadyRead(pollInterval); + + if (proc.bytesAvailable() > 0) { + buffer += proc.readAllStandardOutput(); + parseProgress(buffer, totalPhases); + if (buffer.size() > 1024) + buffer = buffer.right(512); + } + } + + // 读取进程退出后管道中的剩余数据 + if (proc.bytesAvailable() > 0) { + buffer += proc.readAllStandardOutput(); + parseProgress(buffer, totalPhases); + } + + // 检查退出结果 + if (proc.exitStatus() == QProcess::CrashExit) { + qWarning() << "[dfm-burn] dvd+rw-format crashed"; + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } + + int exitCode = proc.exitCode(); + if (exitCode == 0) { + Q_EMIT jobStatusChanged(JobStatus::kFinished, 100); + return true; + } else { + qWarning() << "[dfm-burn] dvd+rw-format exited with code" << exitCode; + Q_EMIT jobStatusChanged(JobStatus::kFailed, -1); + return false; + } +} + +void DVDRwFormatEngine::parseProgress(const QByteArray &data, int totalPhases) +{ + // dvd+rw-format 父进程用退格符覆盖式输出进度,格式如: + // 0.0%\b\b\b\b0.1%\b|\b/\b\b0.2% ... + // 也有部分行带换行符(子进程的诊断输出、父进程结束时的 \n)。 + // 在整个缓冲区中搜索最后一个 XX.X% 模式即可获取当前进度。 + static const QRegularExpression re("([0-9]+(?:\\.[0-9]+)?)%"); + auto matches = re.globalMatch(QString::fromLocal8Bit(data)); + QString lastPct; + while (matches.hasNext()) + lastPct = matches.next().captured(1); + if (lastPct.isEmpty()) + return; + + int rawProgress = static_cast(lastPct.toDouble()); + + // 检测相位切换:进度显著回退说明进入了下一个 SCSI 操作 + if (rawProgress < lastRawProgress - 5) { + phase++; + if (phase >= totalPhases) + phase = totalPhases - 1; + } + lastRawProgress = rawProgress; + + // 加权计算总体进度:(phase * 100 + rawProgress) / totalPhases + int overall = (phase * 100 + rawProgress) / totalPhases; + if (overall < 100) + Q_EMIT jobStatusChanged(JobStatus::kRunning, overall); +} diff --git a/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.h b/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.h new file mode 100644 index 00000000..40c76c47 --- /dev/null +++ b/src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DVDRWFORMATENGINE_H +#define DVDRWFORMATENGINE_H + +#include + +#include + +DFM_BURN_BEGIN_NS + +class DVDRwFormatEngine : public QObject +{ + Q_OBJECT + +public: + explicit DVDRwFormatEngine(QObject *parent = nullptr); + ~DVDRwFormatEngine() override; + + bool doErase(const QString &dev, MediaType type); + +Q_SIGNALS: + void jobStatusChanged(JobStatus status, int progress); + +private: + void parseProgress(const QByteArray &data, int totalPhases); + +private: + int phase { 0 }; + int lastRawProgress { 0 }; +}; + +DFM_BURN_END_NS + +#endif // DVDRWFORMATENGINE_H diff --git a/src/dfm-io/dfm-io/denumerator.cpp b/src/dfm-io/dfm-io/denumerator.cpp index 6c129566..ae8cf75e 100644 --- a/src/dfm-io/dfm-io/denumerator.cpp +++ b/src/dfm-io/dfm-io/denumerator.cpp @@ -446,12 +446,36 @@ char *DEnumeratorPrivate::filePath(const QUrl &url) QUrl DEnumeratorPrivate::buildUrl(const QUrl &url, const char *fileName) { - QString path; - if (url.path() == "/") { - path = "/" + QString(fileName); + // 防御空指针,避免std::string或QByteArray构造时崩溃 + if (!fileName) { + return QUrl(); + } + + QByteArray fileNameBa(fileName); + + // 路径遍历检查仅对本地文件系统 (file:/// 或无 scheme) 生效 + // gio 的 trash:/// 后端对非用户主目录挂载点的回收站文件,使用反斜杠分隔的扁平路径 + // 作为 GFileInfo 的 standard::name(例如 "\media\user\dev\.Trash-1000\files\x"), + // 这是合法的 trash 文件名而非恶意路径,故不应对其做路径遍历拦截。 + const QString scheme = url.scheme(); + if (scheme.isEmpty() || scheme == QLatin1String("file")) { + if (fileNameBa.contains("../") || fileNameBa.contains("..\\") || fileNameBa.startsWith("..")) { + return QUrl(); + } + } + + QByteArray path; + QString urlPath = url.path(); + + if (urlPath == "/" || urlPath.isEmpty()) { + path = QByteArray("/") + fileNameBa; } else { - QString dirPath = url.path(); - path = dirPath.endsWith('/') ? dirPath + QString(fileName) : dirPath + "/" + QString(fileName); + QByteArray dirPath = urlPath.toUtf8(); + if (!dirPath.endsWith('/')) { + dirPath.append('/'); + } + // 使用QByteArray进行底层字节数组拼接,避免QString剥离BOM (efbbbf) + path = dirPath + fileNameBa; } // 保留原始 URL 的 scheme 和 host,而不是假定为本地文件 diff --git a/src/dfm-io/dfm-io/dfileinfo.cpp b/src/dfm-io/dfm-io/dfileinfo.cpp index 25533406..ceb1e97c 100644 --- a/src/dfm-io/dfm-io/dfileinfo.cpp +++ b/src/dfm-io/dfm-io/dfileinfo.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -281,6 +280,24 @@ void DFileInfoPrivate::queryInfoAsync(int ioPriority, DFileInfo::InitQuerierAsyn g_file_query_info_async(this->gfile, attributes, GFileQueryInfoFlags(flag), ioPriority, gcancellable, queryInfoAsyncCallback, dataOp); } +bool DFileInfoPrivate::ensureStatxCached() const +{ + if (statxCached) + return statxValid; + + statxCached = true; + + const QUrl &url = q->uri(); + if (!url.isLocalFile()) + return false; + + unsigned mask = STATX_BASIC_STATS | STATX_BTIME; + int ret = statx(AT_FDCWD, url.path().toStdString().data(), + AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuf); + statxValid = (ret == 0); + return statxValid; +} + QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) { QVariant retValue; @@ -295,16 +312,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_btime.tv_sec); + return quint64(statxBuf.stx_btime.tv_sec); } return qulonglong(ret); } @@ -314,16 +325,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_btime.tv_nsec / 1000000; + return statxBuf.stx_btime.tv_nsec / 1000000; } return QVariant(ret); } @@ -333,16 +338,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_mtime.tv_sec); + return quint64(statxBuf.stx_mtime.tv_sec); } return qulonglong(ret); } @@ -352,16 +351,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_mtime.tv_nsec / 1000000; + return statxBuf.stx_mtime.tv_nsec / 1000000; } return QVariant(ret); } @@ -371,16 +364,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return quint64(statxBuffer.stx_atime.tv_sec); + return quint64(statxBuf.stx_atime.tv_sec); } return qulonglong(ret); } @@ -390,16 +377,10 @@ QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) return QVariant(); uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key.c_str()); if (ret == 0) { - struct statx statxBuffer; - unsigned mask = STATX_BASIC_STATS | STATX_BTIME; - const QUrl &url = q->uri(); - if (!url.isLocalFile()) - return QVariant(); - int ret = statx(AT_FDCWD, url.path().toStdString().data(), AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, mask, &statxBuffer); - if (ret != 0) + if (!ensureStatxCached()) return QVariant(); - return statxBuffer.stx_atime.tv_nsec / 1000000; + return statxBuf.stx_atime.tv_nsec / 1000000; } return QVariant(ret); } diff --git a/src/dfm-io/dfm-io/private/dfileinfo_p.h b/src/dfm-io/dfm-io/private/dfileinfo_p.h index 348ed6df..1547a6b9 100644 --- a/src/dfm-io/dfm-io/private/dfileinfo_p.h +++ b/src/dfm-io/dfm-io/private/dfileinfo_p.h @@ -19,6 +19,7 @@ #include #include +#include BEGIN_IO_NAMESPACE @@ -55,6 +56,7 @@ class DFileInfoPrivate : public QObject, public QSharedData QVariant attributesBySelf(DFileInfo::AttributeID id); QVariant attributesFromUrl(DFileInfo::AttributeID id); void checkAndResetCancel(); + [[nodiscard]] bool ensureStatxCached() const; [[nodiscard]] DFileFuture *initQuerierAsync(int ioPriority, QObject *parent = nullptr) const; [[nodiscard]] QFuture refreshAsync(); @@ -94,6 +96,11 @@ class DFileInfoPrivate : public QObject, public QSharedData std::atomic_bool stoped { false }; std::atomic_bool fileExists { false }; QMap caches; + + // statx 缓存:6 个时间属性 case 共享一次 statx 调用,避免重复系统调用 + mutable struct statx statxBuf {}; + mutable bool statxCached { false }; + mutable bool statxValid { false }; std::atomic_bool cacheing { false }; std::atomic_bool refreshing { false }; QMutex mutex; diff --git a/src/dfm-mount/lib/dprotocoldevice.cpp b/src/dfm-mount/lib/dprotocoldevice.cpp index b15c9c64..56d570ab 100644 --- a/src/dfm-mount/lib/dprotocoldevice.cpp +++ b/src/dfm-mount/lib/dprotocoldevice.cpp @@ -21,6 +21,8 @@ DFM_MOUNT_USE_NS namespace { +static constexpr int kUnmountAsyncTimeoutMs { 5000 }; + struct CallbackProxyWithData { CallbackProxyWithData() = delete; @@ -31,6 +33,12 @@ struct CallbackProxyWithData CallbackProxy caller; QPointer data; DProtocolDevicePrivate *d { nullptr }; + + QScopedPointer unmountTimer; + GCancellable *unmountCancellable { nullptr }; + bool ownsCancellable { false }; + bool unmountTimedOut { false }; + bool forceUnmountIssued { false }; }; } @@ -347,19 +355,36 @@ void DProtocolDevicePrivate::unmountAsync(const QVariantMap &opts, DeviceOperate if (opts.contains(ParamMountOperation)) operation = reinterpret_cast((opts.value(ParamMountOperation).value())); + bool ownsCancellable = false; + if (!cancellable) { + cancellable = g_cancellable_new(); + ownsCancellable = true; + } + GMountUnmountFlags flag; if (opts.contains(ParamForce) && opts.value(ParamForce).toBool()) flag = GMountUnmountFlags::G_MOUNT_UNMOUNT_FORCE; else flag = GMountUnmountFlags::G_MOUNT_UNMOUNT_NONE; - // qInfo() << "mutexForMount prelock" << __FUNCTION__; - // QMutexLocker locker(&mutexForMount); - // qInfo() << "mutexForMount locked" << __FUNCTION__; - CallbackProxyWithData *proxy = new CallbackProxyWithData(cb); proxy->data = qobject_cast(q); proxy->d = this; + proxy->unmountCancellable = cancellable; + proxy->ownsCancellable = ownsCancellable; + + if (flag == G_MOUNT_UNMOUNT_NONE) { + proxy->unmountTimer.reset(new QTimer()); + proxy->unmountTimer->setInterval(kUnmountAsyncTimeoutMs); + proxy->unmountTimer->setSingleShot(true); + QObject::connect(proxy->unmountTimer.data(), &QTimer::timeout, [proxy]() { + proxy->unmountTimedOut = true; + if (proxy->unmountCancellable) + g_cancellable_cancel(proxy->unmountCancellable); + }); + proxy->unmountTimer->start(); + } + g_mount_unmount_with_operation(mountHandler, flag, operation, cancellable, unmountWithCallback, proxy); } } @@ -613,12 +638,27 @@ void DProtocolDevicePrivate::unmountWithCallback(GObject *sourceObj, GAsyncResul auto proxy = static_cast(cbProxy); if (proxy) { + if (proxy->unmountTimedOut && !proxy->forceUnmountIssued && !ret && proxy->data) { + proxy->forceUnmountIssued = true; + g_mount_unmount_with_operation(mnt, G_MOUNT_UNMOUNT_FORCE, + nullptr, nullptr, + unmountWithCallback, proxy); + return; + } + + if (proxy->unmountTimer) + proxy->unmountTimer->stop(); + if (proxy->data) { proxy->d->mountHandler = nullptr; } if (proxy->caller.cb) { proxy->caller.cb(ret, derr); } + + if (proxy->ownsCancellable && proxy->unmountCancellable) + g_object_unref(proxy->unmountCancellable); + delete proxy; } } diff --git a/src/dfm-mount/private/dnetworkmounter.cpp b/src/dfm-mount/private/dnetworkmounter.cpp index 6ad2fca7..610a9df1 100644 --- a/src/dfm-mount/private/dnetworkmounter.cpp +++ b/src/dfm-mount/private/dnetworkmounter.cpp @@ -43,6 +43,7 @@ static constexpr char kSchemaUser[] { "user" }; static constexpr char kSchemaProtocol[] { "protocol" }; static constexpr char kSchemaDomain[] { "domain" }; static constexpr char kSchemaServer[] { "server" }; +static constexpr char kSchemaObject[] { "object" }; static constexpr char kLoginUser[] { "user" }; static constexpr char kLoginDomain[] { "domain" }; @@ -114,10 +115,12 @@ QList DNetworkMounter::loginPasswd(const QString &address) QUrl u(address); QString protocol = u.scheme(); QString host = u.host(); + QString share = u.path().remove("/"); GHashTable_autoptr query = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(query, strdup(kSchemaServer), strdup(host.toStdString().c_str())); g_hash_table_insert(query, strdup(kSchemaProtocol), strdup(protocol.toStdString().c_str())); + g_hash_table_insert(query, strdup(kSchemaObject), strdup(share.toStdString().c_str())); QList passwds; GError_autoptr err { nullptr }; @@ -164,10 +167,11 @@ QList DNetworkMounter::loginPasswd(const QString &address) std::string domain = passwd.value(kSchemaDomain).toString().toStdString(); GError_autoptr err { nullptr }; + std::string object = passwd.value(kSchemaObject).toString().toStdString(); g_autofree char *pwd = secret_password_lookup_sync( smbSchema(), nullptr, &err, kSchemaServer, server.c_str(), kSchemaProtocol, protocol.c_str(), kSchemaUser, user.c_str(), kSchemaDomain, domain.c_str(), - nullptr); + kSchemaObject, object.c_str(), nullptr); if (err) qDebug() << "query password failed: " << passwd << err->message; else { @@ -182,6 +186,7 @@ void DNetworkMounter::savePasswd(const QString &address, const MountPassInfo &in QUrl u(address); QString protocol = u.scheme(); QString server = u.host(); + QString share = u.path().remove("/"); const char *collection = info.savePasswd == NetworkMountPasswdSaveMode::kSaveBeforeLogout ? SECRET_COLLECTION_SESSION : SECRET_COLLECTION_DEFAULT; @@ -196,7 +201,8 @@ void DNetworkMounter::savePasswd(const QString &address, const MountPassInfo &in info.domain.toStdString().c_str(), kSchemaProtocol, protocol.toStdString().c_str(), kSchemaServer, server.toStdString().c_str(), kSchemaUser, - info.userName.toStdString().c_str(), nullptr); + info.userName.toStdString().c_str(), kSchemaObject, + share.toStdString().c_str(), nullptr); if (err) qWarning() << "save passwd failed: " << err->message; } @@ -211,6 +217,7 @@ SecretSchema *DNetworkMounter::smbSchema() sche.attributes[1] = { kSchemaDomain, SECRET_SCHEMA_ATTRIBUTE_STRING }; sche.attributes[2] = { kSchemaServer, SECRET_SCHEMA_ATTRIBUTE_STRING }; sche.attributes[3] = { kSchemaProtocol, SECRET_SCHEMA_ATTRIBUTE_STRING }; + sche.attributes[4] = { kSchemaObject, SECRET_SCHEMA_ATTRIBUTE_STRING }; return &sche; }