From 10c81371a5beee4a12deb870f537e9283492d19a Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Fri, 16 Oct 2026 15:18:02 +0800 Subject: [PATCH] fix: preserve wallpaper FD imports and reuse valid cached images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publish FD imports atomically so repeated imports replace earlier images and failed reads leave published images intact. Validate cache keys, retry interrupted reads, and keep imported sources readable for fallback clients and queued sizes. Reuse complete, readable size caches before consuming the descriptor, regenerate missing entries, and synchronize worker shutdown. Add isolated QtTest coverage and exclude test sources from the production plugin. 原子发布 FD 导入图片,使重复导入正确替换旧图,读取失败时保留已发布图片。 校验缓存键并重试中断读取,保留可读原图供客户端回退和后续尺寸处理使用。 完整有效的尺寸缓存命中时不再读取描述符,补齐缺失缓存,并同步工作线程退出。 增加使用独立缓存目录的 QtTest 回归测试,并从生产插件构建中排除测试源码。 Log: preserve wallpaper FD imports and reuse valid cached images Pms: BUG-335593 Change-Id: I3bfccfee1e4d23ee2c64997fe8f5f81e7de8c697 --- src/plugin-qt/wallpapercache/CMakeLists.txt | 1 + .../wallpapercache/cachedwallpaper.cpp | 34 ++- .../wallpapercache/cachedwallpaper.h | 6 +- .../wallpapercache/scaleimagethread.cpp | 15 +- .../wallpapercache/tests/CMakeLists.txt | 22 ++ .../tests/test_wallpaper_cache_unit.cpp | 196 ++++++++++++++++++ .../wallpapercache/wallpapercacheservice.cpp | 115 ++++++---- 7 files changed, 322 insertions(+), 67 deletions(-) create mode 100644 src/plugin-qt/wallpapercache/tests/test_wallpaper_cache_unit.cpp diff --git a/src/plugin-qt/wallpapercache/CMakeLists.txt b/src/plugin-qt/wallpapercache/CMakeLists.txt index 96c5b3c1..e5275e21 100644 --- a/src/plugin-qt/wallpapercache/CMakeLists.txt +++ b/src/plugin-qt/wallpapercache/CMakeLists.txt @@ -13,6 +13,7 @@ set(CMAKE_AUTORCC ON) include(GNUInstallDirs) file(GLOB_RECURSE SRCS "*.h" "*.cpp") +list(FILTER SRCS EXCLUDE REGEX "/tests/") find_package(PkgConfig REQUIRED) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui DBus) diff --git a/src/plugin-qt/wallpapercache/cachedwallpaper.cpp b/src/plugin-qt/wallpapercache/cachedwallpaper.cpp index 31251f44..a7e7bb32 100644 --- a/src/plugin-qt/wallpapercache/cachedwallpaper.cpp +++ b/src/plugin-qt/wallpapercache/cachedwallpaper.cpp @@ -37,18 +37,13 @@ QStringList CachedWallpaper::getCachedImagePaths(const QString &originalPath, co QString pathMd5 = ScaleImageThread::pathMd5(originalPath, isMd5Path); - if (m_cachedImages.contains(pathMd5)) { - const QMap &map = m_cachedImages[pathMd5]; - for (const QSize &size : sizes) { - QString strSize = ScaleImageThread::sizeToString(size); - if (map.contains(strSize)) { - results.append(map[strSize]); - } else { - noCachedSizes.append(size); - } + for (const QSize &size : sizes) { + const QStringList cached = findCachedImagePaths(pathMd5, {size}); + if (cached.isEmpty()) { + noCachedSizes.append(size); + } else { + results.append(cached); } - } else { - noCachedSizes = sizes; } if (!noCachedSizes.isEmpty()) { @@ -59,6 +54,23 @@ QStringList CachedWallpaper::getCachedImagePaths(const QString &originalPath, co return results; } +QStringList CachedWallpaper::findCachedImagePaths(const QString &pathMd5, const QList &sizes) const +{ + QStringList results; + const auto it = m_cachedImages.constFind(pathMd5); + if (it == m_cachedImages.constEnd()) + return results; + + for (const QSize &size : sizes) { + const QString path = it->value(ScaleImageThread::sizeToString(size)); + const QFileInfo info(path); + if (path.isEmpty() || !info.isFile() || !info.isReadable() || info.size() == 0) + return {}; + results.append(path); + } + return results; +} + void CachedWallpaper::cacheImage(const QString &originalPathMd5, const QString &size, const QString &processedPath) { qDebug() << "cache Image:" << processedPath; diff --git a/src/plugin-qt/wallpapercache/cachedwallpaper.h b/src/plugin-qt/wallpapercache/cachedwallpaper.h index 6bc97fea..383b0ba0 100644 --- a/src/plugin-qt/wallpapercache/cachedwallpaper.h +++ b/src/plugin-qt/wallpapercache/cachedwallpaper.h @@ -12,7 +12,10 @@ #include // Shared cache path constants -inline const QString kWallpaperCacheDir = QStringLiteral("/var/cache/dde-wallpaper-cache"); +#ifndef WALLPAPER_CACHE_DIR +#define WALLPAPER_CACHE_DIR "/var/cache/dde-wallpaper-cache" +#endif +inline const QString kWallpaperCacheDir = QStringLiteral(WALLPAPER_CACHE_DIR); inline const QString kBlurCacheDir = kWallpaperCacheDir + QStringLiteral("/blur"); class CachedWallpaper : public QObject @@ -29,6 +32,7 @@ class CachedWallpaper : public QObject public: static CachedWallpaper *instance(); QStringList getCachedImagePaths(const QString &originalPath, const QList &sizes, bool isMd5Path = false); + QStringList findCachedImagePaths(const QString &pathMd5, const QList &sizes) const; void cacheImage(const QString &originalPathMd5, const QString &size, const QString &processedPath); // Blur wallpaper interfaces diff --git a/src/plugin-qt/wallpapercache/scaleimagethread.cpp b/src/plugin-qt/wallpapercache/scaleimagethread.cpp index f6089d6a..18387f6a 100644 --- a/src/plugin-qt/wallpapercache/scaleimagethread.cpp +++ b/src/plugin-qt/wallpapercache/scaleimagethread.cpp @@ -97,12 +97,14 @@ bool ScaleImageThread::isIdle() void ScaleImageThread::run() { - while (!m_stop) { + for (;;) { QMutexLocker locker(&m_mutex); - if (m_tasks.isEmpty()) { + while (m_tasks.isEmpty() && !m_stop) { m_waitCondition.wait(&m_mutex); } + if (m_stop) + return; if (!m_tasks.isEmpty()) { TaskData task = m_tasks.head(); @@ -128,15 +130,10 @@ void ScaleImageThread::executeTask(const TaskData &task) QString originalPathMd5 = pathMd5(task.originalPath, task.isMd5Path); QString cachedFilePath = cacheImageToDisk(pixmap, task, originalPathMd5); + // Keep the imported source as a cache entry. Other queued sizes and + // clients receiving it as the immediate fallback may still need it. if (!cachedFilePath.isEmpty()) { Q_EMIT imageScaled(originalPathMd5, sizeToString(task.targetSize), cachedFilePath); - - if (task.isMd5Path) { - QFile file(task.originalPath); - if (file.exists()) { - file.remove(); - } - } } } diff --git a/src/plugin-qt/wallpapercache/tests/CMakeLists.txt b/src/plugin-qt/wallpapercache/tests/CMakeLists.txt index 4c073831..f88e2a9a 100644 --- a/src/plugin-qt/wallpapercache/tests/CMakeLists.txt +++ b/src/plugin-qt/wallpapercache/tests/CMakeLists.txt @@ -23,3 +23,25 @@ target_compile_definitions(test_wallpaper_cache PRIVATE QT_MESSAGELOGCONTEXT) message(STATUS "Test build: test_wallpaper_cache") message(STATUS "Run: sudo ./test_wallpaper_cache [wallpaper_path]") + +include(CTest) +if(BUILD_TESTING) + find_package(Qt6 REQUIRED COMPONENTS Test) + find_package(PkgConfig REQUIRED) + pkg_check_modules(DtkGui REQUIRED IMPORTED_TARGET dtk6gui) + set(WALLPAPER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") + add_executable(test_wallpaper_cache_unit + test_wallpaper_cache_unit.cpp + ${WALLPAPER_SOURCE_DIR}/wallpapercacheservice.cpp + ${WALLPAPER_SOURCE_DIR}/cachedwallpaper.cpp + ${WALLPAPER_SOURCE_DIR}/wallpapercachemanager.cpp + ${WALLPAPER_SOURCE_DIR}/scaleimagethread.cpp + ${WALLPAPER_SOURCE_DIR}/imageeffectprocessor.cpp + ) + target_include_directories(test_wallpaper_cache_unit PRIVATE ${WALLPAPER_SOURCE_DIR}) + target_compile_definitions(test_wallpaper_cache_unit PRIVATE + WALLPAPER_CACHE_DIR="${CMAKE_CURRENT_BINARY_DIR}/unit-cache") + target_link_libraries(test_wallpaper_cache_unit PRIVATE + Qt6::Core Qt6::DBus Qt6::Gui Qt6::Test PkgConfig::DtkGui) + add_test(NAME wallpaper-cache-unit COMMAND test_wallpaper_cache_unit) +endif() diff --git a/src/plugin-qt/wallpapercache/tests/test_wallpaper_cache_unit.cpp b/src/plugin-qt/wallpapercache/tests/test_wallpaper_cache_unit.cpp new file mode 100644 index 00000000..0282094e --- /dev/null +++ b/src/plugin-qt/wallpapercache/tests/test_wallpaper_cache_unit.cpp @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "cachedwallpaper.h" +#include "scaleimagethread.h" +#include "wallpapercacheservice.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +class WallpaperCacheTest : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + QVERIFY(kWallpaperCacheDir.contains("/unit-cache")); + QVERIFY(QDir().mkpath(kWallpaperCacheDir)); + } + + void repeatedImportReplacesLongerImage() + { + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString input = dir.filePath("input.bmp"); + const QString key(32, '1'); + WallpaperCacheService service; + QImage large(300, 200, QImage::Format_RGB32); + large.fill(Qt::red); + QVERIFY(large.save(input)); + QFile file(input); + QVERIFY(file.open(QIODevice::ReadOnly)); + const auto first = service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(file.handle()), key, {}); + QCOMPARE(first.size(), 1); + const qint64 largeSize = QFileInfo(first.first()).size(); + file.close(); + + QImage small(30, 20, QImage::Format_RGB32); + small.fill(Qt::blue); + QVERIFY(small.save(input)); + QVERIFY(file.open(QIODevice::ReadOnly)); + const auto second = service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(file.handle()), key, {}); + QCOMPARE(second, first); + QCOMPARE(QFileInfo(second.first()).size(), file.size()); + QVERIFY(QFileInfo(second.first()).size() < largeSize); + QCOMPARE(QImage(second.first()).size(), small.size()); + QCOMPARE(QImage(second.first()).pixelColor(0, 0), QColor(Qt::blue)); + } + + void invalidImportPreservesPublishedImage() + { + QTemporaryDir dir; + const QString input = dir.filePath("input.png"); + QImage image(20, 20, QImage::Format_RGB32); + image.fill(Qt::green); + QVERIFY(image.save(input)); + QFile file(input); + QVERIFY(file.open(QIODevice::ReadOnly)); + WallpaperCacheService service; + const QString key(32, '2'); + const auto first = service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(file.handle()), key, {}); + QCOMPARE(first.size(), 1); + file.close(); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate)); + QCOMPARE(file.write("invalid image"), qint64(13)); + file.close(); + QVERIFY(file.open(QIODevice::ReadOnly)); + QVERIFY(service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(file.handle()), key, {}).isEmpty()); + QCOMPARE(QImage(first.first()).pixelColor(0, 0), QColor(Qt::green)); + QCOMPARE(QDir(kWallpaperCacheDir).entryList({".fd-import-*"}, QDir::Files | QDir::Hidden).size(), 0); + } + + void descriptorReadFailureLeavesNoImport() + { + const int directoryFd = ::open("/tmp", O_RDONLY | O_DIRECTORY); + QVERIFY(directoryFd >= 0); + QDBusUnixFileDescriptor fd; + fd.giveFileDescriptor(directoryFd); + WallpaperCacheService service; + QVERIFY(service.GetProcessedImagePathByFd(fd, QString(32, '3'), {}).isEmpty()); + QVERIFY(!QFile::exists(kWallpaperCacheDir + "/" + QString(32, '3'))); + QCOMPARE(QDir(kWallpaperCacheDir).entryList({".fd-import-*"}, QDir::Files | QDir::Hidden).size(), 0); + } + + void rejectInvalidCacheKey() + { + QFile file("/dev/null"); + QVERIFY(file.open(QIODevice::ReadOnly)); + WallpaperCacheService service; + QVERIFY(service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(file.handle()), "../outside", {}).isEmpty()); + } + + void completeCacheHitDoesNotReadDescriptor() + { + QTemporaryDir dir; + const QString cached = dir.filePath("cached.png"); + QImage image(40, 30, QImage::Format_RGB32); + image.fill(Qt::yellow); + QVERIFY(image.save(cached)); + const QString key(32, '4'); + CachedWallpaper::instance()->cacheImage(key, "40x30", cached); + QFile input(cached); + QVERIFY(input.open(QIODevice::ReadOnly)); + const QVariantList sizes{QVariant::fromValue(QSize(40, 30))}; + WallpaperCacheService service; + QDBusUnixFileDescriptor fd(input.handle()); + QCOMPARE(service.GetProcessedImagePathByFd(fd, key, sizes), QStringList{cached}); + QCOMPARE(::lseek(fd.fileDescriptor(), 0, SEEK_CUR), off_t(0)); + QCOMPARE(service.GetProcessedImagePathByFdWithBlur(fd, key, sizes, false), QStringList{cached}); + QCOMPARE(::lseek(fd.fileDescriptor(), 0, SEEK_CUR), off_t(0)); + QVERIFY(!QFile::exists(kWallpaperCacheDir + "/" + key + ".png")); + } + + void partialCacheHitStillQueuesMissingSizes() + { + QTemporaryDir dir; + const QString original = dir.filePath("input.png"); + QImage image(80, 60, QImage::Format_RGB32); + image.fill(Qt::cyan); + QVERIFY(image.save(original)); + const QString key(32, '5'); + const QString cached = dir.filePath("cached.png"); + QVERIFY(image.save(cached)); + CachedWallpaper::instance()->cacheImage(key, "40x30", cached); + QFile input(original); + QVERIFY(input.open(QIODevice::ReadOnly)); + QSignalSpy pending(CachedWallpaper::instance(), &CachedWallpaper::needHandleImage); + WallpaperCacheService service; + QCOMPARE(service.GetProcessedImagePathByFd(QDBusUnixFileDescriptor(input.handle()), key, + {QVariant::fromValue(QSize(40, 30)), QVariant::fromValue(QSize(20, 15))}), QStringList{cached}); + QCOMPARE(pending.size(), 1); + QCOMPARE(pending.first().at(1).value>(), QList{QSize(20, 15)}); + QVERIFY(QFile::exists(pending.first().at(0).toString())); + } + + void removedCacheEntryIsRegenerated() + { + QTemporaryDir dir; + const QString missing = dir.filePath("removed.png"); + QImage image(20, 20, QImage::Format_RGB32); + QVERIFY(image.save(missing)); + const QString source = dir.filePath("source.png"); + const QString key = ScaleImageThread::pathMd5(source, false); + CachedWallpaper::instance()->cacheImage(key, "20x20", missing); + QVERIFY(QFile::remove(missing)); + QSignalSpy pending(CachedWallpaper::instance(), &CachedWallpaper::needHandleImage); + QVERIFY(CachedWallpaper::instance()->getCachedImagePaths(source, {QSize(20, 20)}).isEmpty()); + QCOMPARE(pending.size(), 1); + } + + void multipleSizesKeepFallbackSourceReadable() + { + QTemporaryDir dir; + const QString source = dir.filePath(QString(32, '6') + ".png"); + QImage image(200, 150, QImage::Format_RGB32); + image.fill(Qt::magenta); + QVERIFY(image.save(source)); + ScaleImageThread worker; + worker.setCachePath(dir.path()); + QSignalSpy scaled(&worker, &ScaleImageThread::imageScaled); + worker.addTasks(source, {QSize(80, 60), QSize(40, 30)}, true); + QTRY_COMPARE_WITH_TIMEOUT(scaled.size(), 2, 5000); + for (const auto &reply : scaled) { + const QString path = reply.at(2).toString(); + const QImage result(path); + QVERIFY(!result.isNull()); + QCOMPARE(ScaleImageThread::sizeToString(result.size()), reply.at(1).toString()); + } + QVERIFY(!QImage(source).isNull()); + worker.addTasks(source, {QSize(20, 15)}, true); + QTRY_COMPARE_WITH_TIMEOUT(scaled.size(), 3, 5000); + QCOMPARE(QImage(scaled.last().at(2).toString()).size(), QSize(20, 15)); + worker.stopThread(); + QVERIFY(worker.wait(5000)); + } + + void idleWorkerStops() + { + ScaleImageThread worker; + worker.start(); + worker.stopThread(); + QVERIFY(worker.wait(5000)); + } +}; + +QTEST_GUILESS_MAIN(WallpaperCacheTest) +#include "test_wallpaper_cache_unit.moc" diff --git a/src/plugin-qt/wallpapercache/wallpapercacheservice.cpp b/src/plugin-qt/wallpapercache/wallpapercacheservice.cpp index 67dd7f3d..7086d6db 100644 --- a/src/plugin-qt/wallpapercache/wallpapercacheservice.cpp +++ b/src/plugin-qt/wallpapercache/wallpapercacheservice.cpp @@ -11,6 +11,12 @@ #include #include #include +#include +#include +#include + +#include +#include #include @@ -23,6 +29,10 @@ QList WallpaperCacheService::parseSizeArray(const QVariantList &sizeArray { QList sizes; for (const QVariant &variant : sizeArray) { + if (variant.canConvert()) { + sizes.append(variant.toSize()); + continue; + } auto arg = variant.value(); QSize size; arg >> size; @@ -50,13 +60,20 @@ QStringList WallpaperCacheService::GetProcessedImagePaths(const QString &origina QStringList WallpaperCacheService::GetProcessedImagePathByFd(const QDBusUnixFileDescriptor &fd, const QString &imagePathMd5, const QVariantList &sizeArray) { + const QList sizes = parseSizeArray(sizeArray); + if (fd.fileDescriptor() < 0) + return {}; + + const QStringList cached = CachedWallpaper::instance()->findCachedImagePaths(imagePathMd5, sizes); + if (!sizes.isEmpty() && cached.size() == sizes.size()) + return cached; + QString destinationPath = saveImageFromFd(fd, imagePathMd5); if (destinationPath.isEmpty()) { return QStringList(); } qDebug() << "get processed image from origin path:" << destinationPath; - QList sizes = parseSizeArray(sizeArray); QStringList results = CachedWallpaper::instance()->getCachedImagePaths(destinationPath, sizes, true); if (results.isEmpty()) { @@ -89,6 +106,9 @@ QStringList WallpaperCacheService::GetProcessedImageWithBlur(const QString &orig QStringList WallpaperCacheService::GetProcessedImagePathByFdWithBlur(const QDBusUnixFileDescriptor &fd, const QString &imagePathMd5, const QVariantList &sizeArray, bool needBlur) { + if (!needBlur) + return GetProcessedImagePathByFd(fd, imagePathMd5, sizeArray); + QString destinationPath = saveImageFromFd(fd, imagePathMd5); if (destinationPath.isEmpty()) { return QStringList(); @@ -97,15 +117,7 @@ QStringList WallpaperCacheService::GetProcessedImagePathByFdWithBlur(const QDBus qDebug() << "get processed image from origin path:" << destinationPath; QList sizes = parseSizeArray(sizeArray); - if (needBlur) { - return CachedWallpaper::instance()->getProcessedImageWithBlur(destinationPath, sizes, needBlur); - } else { - QStringList results = CachedWallpaper::instance()->getCachedImagePaths(destinationPath, sizes, true); - if (results.isEmpty()) { - results << destinationPath; - } - return results; - } + return CachedWallpaper::instance()->getProcessedImageWithBlur(destinationPath, sizes, true); } QStringList WallpaperCacheService::GetWallpaperListForScreen(const QString &originalPath, const QVariantList &sizeArray, bool needBlur) @@ -174,46 +186,57 @@ void WallpaperCacheService::ClearEffectCache(const QString &effect) QString WallpaperCacheService::saveImageFromFd(const QDBusUnixFileDescriptor &fd, const QString &imagePathMd5) { - int fdi = fd.fileDescriptor(); - if (fdi <= 0) { - return QString(); - } - - QString originPath = kWallpaperCacheDir + "/" + imagePathMd5; - QFile originFile(originPath); - if (!originFile.open(QIODevice::ReadWrite)) { - qWarning() << "Failed to open destination file for writing:" << originPath; - return QString(); - } - - char buffer[4096]; - ssize_t bytesRead; - while ((bytesRead = read(fdi, buffer, sizeof(buffer))) > 0) { - if (originFile.write(buffer, bytesRead) != bytesRead) { - qWarning() << "Failed to write data to destination file."; + const int fdi = fd.fileDescriptor(); + static const QRegularExpression md5Pattern(QStringLiteral("\\A[0-9a-fA-F]{32}\\z")); + if (fdi < 0 || !md5Pattern.match(imagePathMd5).hasMatch()) + return {}; + + if (!QDir().mkpath(kWallpaperCacheDir)) + return {}; + + // Import privately: failed or concurrent copies must never expose a partial + // image under the shared source name. + QTemporaryFile originFile(kWallpaperCacheDir + "/.fd-import-XXXXXX"); + if (!originFile.open()) { + qWarning() << "Failed to create imported image:" << originFile.errorString(); + return {}; + } + // Imported sources are returned over D-Bus and read by other clients. + if (!originFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther)) + return {}; + + char buffer[64 * 1024]; + for (;;) { + const ssize_t bytesRead = read(fdi, buffer, sizeof(buffer)); + if (bytesRead < 0) { + if (errno == EINTR) + continue; + qWarning() << "Error reading image descriptor:" << errno; + return {}; + } + if (bytesRead == 0) break; + if (originFile.write(buffer, bytesRead) != bytesRead) { + qWarning() << "Failed to write imported image:" << originFile.errorString(); + return {}; } } - if (bytesRead < 0) { - qWarning() << "Error reading from file descriptor."; - originFile.close(); - return QString(); - } - originFile.close(); + if (!originFile.flush()) + return {}; - // Detect format using static method to avoid loading entire image - QByteArray format = QImageReader::imageFormat(originPath); + const QByteArray format = QImageReader::imageFormat(originFile.fileName()); if (format.isEmpty()) { - qWarning() << "Failed to detect image format:" << originPath; - QFile::remove(originPath); - return QString(); - } - QString destinationPath = originPath + QString(".%1").arg(QString::fromLatin1(format)); - QFile file(originPath); - if (!file.rename(destinationPath)) { - qWarning() << "originPath rename failed"; - return QString(); - } - + qWarning() << "Failed to detect imported image format"; + return {}; + } + const QString destinationPath = kWallpaperCacheDir + "/" + imagePathMd5 + "." + QString::fromLatin1(format); + // POSIX rename atomically replaces an earlier import, including a longer + // image. Existing readers retain their opened file until they finish. + if (::rename(QFile::encodeName(originFile.fileName()).constData(), + QFile::encodeName(destinationPath).constData()) != 0) { + qWarning() << "Failed to publish imported image:" << errno; + return {}; + } + originFile.setAutoRemove(false); return destinationPath; }