From 77f0b08f984cd01d811134914d8167c910b46a7c Mon Sep 17 00:00:00 2001 From: Zhang Sheng Date: Mon, 13 Jul 2026 14:59:17 +0800 Subject: [PATCH] feat: add dvd+rw-format support for DVD RW erase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Added new DVDRwFormatEngine class to handle DVD RW erasure using dvd+rw-tools 2. Modified control files to add dvd+rw-tools as a dependency 3. Updated DOpticalDiscManager to detect DVD±RW media and use new engine when available 4. Implemented progress parsing for dvd+rw-format's special output format 5. Added fallback to xorriso when dvd+rw-format is not available Log: Added support for DVD±RW erasure using dvd+rw-format tool Influence: 1. Test DVD+RW and DVD-RW erasure with dvd+rw-tools installed 2. Verify fallback to xorriso when dvd+rw-tools is missing 3. Check progress reporting during erase operations 4. Test with different DVD RW media types 5. Verify no regression in CD/DVD non-RW erase functionality feat: 添加对DVD RW擦除的dvd+rw-format支持 1. 新增DVDRwFormatEngine类用于通过dvd+rw-tools处理DVD RW擦除 2. 修改控制文件添加dvd+rw-tools作为依赖项 3. 更新DOpticalDiscManager以检测DVD±RW介质并在可用时使用新引擎 4. 实现针对dvd+rw-format特殊输出格式的进度解析 5. 添加当dvd+rw-format不可用时回退到xorriso的处理 Log: 新增使用dvd+rw-format工具进行DVD±RW擦除的支持 Influence: 1. 测试dvd+rw-tools安装时的DVD+RW和DVD-RW擦除功能 2. 验证dvd+rw-tools缺失时回退到xorriso的功能 3. 检查擦除过程中的进度报告 4. 使用不同类型的DVD RW介质进行测试 5. 验证普通CD/DVD非RW擦除功能无回归 --- debian/control.in | 3 +- .../dfm-burn-lib/dopticaldiscmanager.cpp | 31 ++++ .../private/dvdrwformatengine.cpp | 165 ++++++++++++++++++ .../dfm-burn-lib/private/dvdrwformatengine.h | 37 ++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.cpp create mode 100644 src/dfm-burn/dfm-burn-lib/private/dvdrwformatengine.h 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