diff --git a/debian/control b/debian/control index d382dd2c..cb33ae56 100644 --- a/debian/control +++ b/debian/control @@ -35,7 +35,12 @@ Build-Depends: libopencv-dev, libmediainfo-dev, libffmpegthumbnailer-dev, - libtiff-dev + libtiff-dev, +# Enable use dfm io to copy MTP mount file, Use `|`(or) relationship to +# compatible different environments, qttools5-private-dev will not be used. +# WARNING: control file changes may cause qttools5-private-dev to be installed +# instead of libdfm-io-dev. + libdfm-io-dev | qttools5-private-dev Standards-Version: 3.9.8 Homepage: http://www.deepin.org @@ -57,6 +62,6 @@ Conflicts: libimage-viewer-dev Replaces: libimage-viewer-dev -Recommends: libqt5libqgtk2, kimageformat-plugins ,deepin-ocr +Recommends: libqt5libqgtk2, kimageformat-plugins, deepin-ocr Description: Image Viewer library development headers. Deepin Image Viewer library development headers. diff --git a/libimageviewer/CMakeLists.txt b/libimageviewer/CMakeLists.txt index 8cff4703..579e258b 100644 --- a/libimageviewer/CMakeLists.txt +++ b/libimageviewer/CMakeLists.txt @@ -47,6 +47,15 @@ pkg_check_modules(3rd_lib REQUIRED libmediainfo ) +# 查找 dfm-io +pkg_check_modules(dfm-io_lib dfm-io) +if(${dfm-io_lib_FOUND}) + message("Found dfm-io, enable MTP file copy optimization.") + add_definitions(-DUSE_DFM_IO) +else() + message("Not found dfm-io, use base api copy MTP file.") +endif() + #需要打开的头文件 FILE (GLOB allHeaders "*.h" "*/*.h" "*/*/*.h") #需要打开的代码文件 @@ -98,8 +107,8 @@ set(${TARGET_NAME} ${CMAKE_INSTALL_LIBDIR}) set_target_properties(${TARGET_NAME} PROPERTIES VERSION 0.1.0 SOVERSION 0.1) -target_include_directories(${CMD_NAME} PUBLIC ${3rd_lib_INCLUDE_DIRS} ${TIFF_INCLUDE_DIRS}) -target_link_libraries(imageviewer ${3rd_lib_LIBRARIES} freeimage ${TIFF_LIBRARIES} dl) +target_include_directories(${CMD_NAME} PUBLIC ${3rd_lib_INCLUDE_DIRS} ${TIFF_INCLUDE_DIRS} ${dfm-io_lib_INCLUDE_DIRS}) +target_link_libraries(imageviewer ${3rd_lib_LIBRARIES} freeimage ${TIFF_LIBRARIES} ${dfm-io_lib_LIBRARIES} dl) include(GNUInstallDirs) configure_file(libimageviewer.pc.in ${PROJECT_BINARY_DIR}/libimageviewer.pc @ONLY) diff --git a/libimageviewer/imageviewer.cpp b/libimageviewer/imageviewer.cpp index 2b4994ca..8ba095ce 100644 --- a/libimageviewer/imageviewer.cpp +++ b/libimageviewer/imageviewer.cpp @@ -17,9 +17,9 @@ #include "imageengine.h" #include "viewpanel/viewpanel.h" #include "service/commonservice.h" +#include "service/mtpfileproxy.h" #include "unionimage/imageutils.h" #include "unionimage/baseutils.h" -//#include "widgets/toptoolbar.h" #define PLUGINTRANSPATH "/usr/share/libimageviewer/translations" class ImageViewerPrivate @@ -115,18 +115,24 @@ bool ImageViewer::startdragImageWithUID(const QStringList &paths, const QString return d->m_panel->startdragImage(paths, firstPath); } -void ImageViewer::startImgView(QString currentPath, QStringList paths) +void ImageViewer::startImgView(const QString ¤tPath, const QStringList &paths) { Q_D(ImageViewer); + + QStringList realPaths = paths; + QString realPath = currentPath; + MtpFileProxy::instance()->checkAndCreateProxyFile(realPaths, realPath); + //展示当前图片 - d->m_panel->loadImage(currentPath, paths); + d->m_panel->loadImage(realPath, realPaths); + //启动线程制作缩略图 if (LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeLocal || LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeNull) { //首先生成当前图片的缓存 - ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), QStringList(currentPath), 1); + ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), QStringList(realPath), 1); //看图制作全部缩略图 - ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), paths, paths.size()); + ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), realPaths, realPaths.size()); } } @@ -139,11 +145,14 @@ void ImageViewer::switchFullScreen() void ImageViewer::startSlideShow(const QStringList &paths, const QString &firstPath) { Q_D(ImageViewer); + ViewInfo info; info.fullScreen = window()->isFullScreen(); info.lastPanel = this; info.path = firstPath; info.paths = paths; + MtpFileProxy::instance()->checkAndCreateProxyFile(info.paths, info.path); + info.viewMainWindowID = 0; d->m_panel->startSlideShow(info); } diff --git a/libimageviewer/imageviewer.h b/libimageviewer/imageviewer.h index af2a65af..d6f028d8 100644 --- a/libimageviewer/imageviewer.h +++ b/libimageviewer/imageviewer.h @@ -34,7 +34,7 @@ class IMAGEVIEWERSHARED_EXPORT ImageViewer : public DWidget bool startdragImageWithUID(const QStringList &paths, const QString &firstPath = "", bool isCustom = false, const QString &album = "", int UID = -1); //启动图片展示入口 - void startImgView(QString currentPath, QStringList paths = QStringList()); + void startImgView(const QString ¤tPath, const QStringList &paths = QStringList()); //设置topbar的显示和隐藏 void setTopBarVisible(bool visible); diff --git a/libimageviewer/service/mtpfileproxy.cpp b/libimageviewer/service/mtpfileproxy.cpp new file mode 100644 index 00000000..ae12a861 --- /dev/null +++ b/libimageviewer/service/mtpfileproxy.cpp @@ -0,0 +1,291 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mtpfileproxy.h" +#include "imageengine.h" + +#include +#include + +#include + +#ifdef USE_DFM_IO +#include +USING_IO_NAMESPACE +#endif + +static QString realPath(const QStringList &paths, const QString &filePath) +{ + return filePath.isEmpty() && !paths.isEmpty() ? paths.first() : filePath; +} + +/** + @brief 接收拷贝完成信号 + */ +static void fileOperateCallbackFunc(bool ret, void *data) +{ + if (data) { + MtpFileProxy::instance()->loadFinished(*reinterpret_cast(data), ret); + } +} + +MtpFileProxy::MtpFileProxy() +{ +#ifdef USE_DFM_IO + qInfo() << qPrintable("Use dfm-io copy MTP file."); +#else + qInfo() << qPrintable("Use QFile copy MTP file."); +#endif +} + +MtpFileProxy::~MtpFileProxy() {} + +MtpFileProxy *MtpFileProxy::instance() +{ + static MtpFileProxy ins; + return &ins; +} + +/** + @return 返回当前是否存在代理文件 + */ +bool MtpFileProxy::isValid() const +{ + return !proxyCache.isEmpty(); +} + +/** + @brief 检测传入的文件 `firstPath` 和列表 `paths` 是否为MTP文件。 + 1. 若为,则创建代理文件,并将 `firstPath` `paths` 替换为代理文件路径,返回 true ; + 2. 若非MTP文件,则不修改传入参数,返回 false . + */ +bool MtpFileProxy::checkAndCreateProxyFile(QStringList &paths, QString &firstPath) +{ + firstPath = realPath(paths, firstPath); + if (MtpFileProxy::instance()->checkFileDeviceIsMtp(firstPath)) { + firstPath = MtpFileProxy::instance()->createPorxyFile(firstPath); + paths.clear(); + paths.append(firstPath); + + qInfo() << qPrintable("Detect MTP mount file."); + return true; + } + + return false; +} + +/** + @return 返回文件 `filePath` 是否为MTP挂载图片文件 + 目前可能采用 gvfs/cifs 进行挂载。 + */ +bool MtpFileProxy::checkFileDeviceIsMtp(const QString &filePath) +{ + QStorageInfo storage(filePath); + if (storage.device().startsWith("gvfs") || storage.device().startsWith("cifs")) { + // /run/user/1000/gvfs/mtp: /run/user/1000/gvfs/gphoto2: + QString absoluteFilePath = QFileInfo(filePath).absoluteFilePath(); + if (absoluteFilePath.contains(QRegExp("fs/(mtp|gphoto2):"))) { + // 判断是否为图片文件 + if (ImageEngine::instance()->isImage(filePath)) { + return true; + } + } + } + + return false; +} + +/** + @brief 提交代理文件 `proxyFile` 变更(图片旋转)到 MTP 挂载文件并返回是否成功。 + */ +bool MtpFileProxy::submitChangesToMTP(const QString &proxyFile) +{ + if (isValid() && proxyCache.contains(proxyFile)) { + auto infoPtr = proxyCache.value(proxyFile); + + // 提交临时文件变更到 MTP 原始文件目录 +#ifdef USE_DFM_IO + DOperator copyOpt(QUrl::fromLocalFile(proxyFile)); + if (!copyOpt.copyFile(QUrl::fromLocalFile(infoPtr->originFileName), DFile::CopyFlag::kOverwrite)) { + qWarning() + << QString("Submit changes to MTP mount file failed! DOperator error:%!").arg(copyOpt.lastError().errorMsg()); + return false; + } +#else + QFile copyFile(proxyFile); + if (!copyFile.copy(infoPtr->originFileName)) { + qWarning() << QString("Submit changes to MTP mount file failed! QFile error:%!").arg(copyFile.errorString()); + return false; + } +#endif + + return true; + } + + return false; +} + +/** + @return 返回缓存是否包含 `proxyFile` 代理文件路径 + */ +bool MtpFileProxy::contains(const QString &proxyFile) const +{ + return proxyCache.contains(proxyFile); +} + +/** + @return 返回代理文件当前状态。 + @li None 初始状态 + @li Loading 加载中 + @li LoadSucc 加载成功 + @li LoadFailed 加载失败 + @li FileDelete 文件删除 + */ +MtpFileProxy::FileState MtpFileProxy::state(const QString &proxyFile) const +{ + if (proxyCache.contains(proxyFile)) { + return proxyCache.value(proxyFile)->fileState; + } + + return None; +} + +/** + @brief 根据原始文件路径 `filePath` 创建代理文件并返回代理文件路径。 + 如果已存在 `filePath` 的代理文件,将判断文件是否变更。 + 创建并非立即完成,创建完成信号通过 `createProxyFileFinished` 发出。 + */ +QString MtpFileProxy::createPorxyFile(const QString &filePath) +{ + auto findItr = std::find_if(proxyCache.begin(), proxyCache.end(), [&](const QSharedPointer &info) { + return info->originFileName == filePath; + }); + + if (findItr != proxyCache.end()) { + // 同一路径文件已变更,移除之前缓存信息 + QFileInfo current(filePath); + if (findItr.value()->lastModified == current.lastModified()) { + return findItr.key(); + } + proxyCache.erase(findItr); + } + + QSharedPointer infoPtr = QSharedPointer(new ProxyInfo); + if (!infoPtr->tempDir.isValid()) { + qWarning() << qPrintable("Cannot create temporary dir for MTP mount device."); + return filePath; + } + + QFileInfo info(filePath); + QString proxyFile = infoPtr->tempDir.filePath(info.fileName()); + infoPtr->fileState = Loading; + infoPtr->proxyFileName = proxyFile; + infoPtr->originFileName = filePath; + infoPtr->lastModified = info.lastModified(); + proxyCache.insert(proxyFile, infoPtr); + + // 异步拷贝文件到临时目录 + copyFileFromMtpAsync(infoPtr); + + return proxyFile; +} + +/** + @return 返回代理文件 `proxyFile` 所指向的原始文件路径。 + */ +QString MtpFileProxy::mapToOriginFile(const QString &proxyFile) const +{ + if (proxyCache.contains(proxyFile)) { + return proxyCache.value(proxyFile)->originFileName; + } + + // 返回传入文件路径 + return proxyFile; +} + +/** + @brief 接收 dfm-io 异步拷贝文件 `proxyFile` 的结果 `ret`,将通过 `createProxyFileFinished` 信号通知 + */ +void MtpFileProxy::loadFinished(const QString &proxyFile, bool ret) +{ + if (proxyCache.contains(proxyFile)) { + if (!ret) { + qWarning() << qPrintable("Copy MTP mount file to tmp folder failed!"); + } + + proxyCache.value(proxyFile)->fileState = ret ? LoadSucc : LoadFailed; + Q_EMIT createProxyFileFinished(proxyFile, ret); + } +} + +/** + @brief 触发原始文件 `originFile` 变更操作,将根据原始文件状态更新代理文件状态。 + */ +void MtpFileProxy::triggerOriginFileChanged(const QString &originFile) +{ + auto findItr = std::find_if(proxyCache.begin(), proxyCache.end(), [&](const QSharedPointer &info) { + return info->originFileName == originFile; + }); + + if (findItr == proxyCache.end()) { + return; + } + + QFileInfo info(originFile); + auto proxyPtr = findItr.value(); + + if (!info.exists()) { + // 文件已被移除 + if (QFile::rename(proxyPtr->proxyFileName, proxyPtr->proxyFileName + ".delete")) { + proxyPtr->fileState = FileDelete; + } else { + qWarning() << qPrintable("For delete, rename MTP cached file failed!"); + } + } else if (FileDelete == findItr.value()->fileState) { + // 文件恢复 + if (QFile::rename(proxyPtr->proxyFileName + ".delete", proxyPtr->proxyFileName)) { + proxyPtr->fileState = LoadSucc; + } else { + qWarning() << qPrintable("For restore, rename MTP cached file failed!"); + } + } else if (info.lastModified() != findItr.value()->lastModified) { + // 文件变更 + copyFileFromMtpAsync(proxyPtr); + proxyPtr->lastModified = info.lastModified(); + } +} + +/** + @brief 根据给出的代理信息 `proxyPtr` ,将原始文件拷贝至临时文件目录 + */ +void MtpFileProxy::copyFileFromMtpAsync(const QSharedPointer &proxyPtr) +{ +#ifdef USE_DFM_IO + proxyPtr->fileState = Loading; + // dfm-io (gio) + DOperator copyOpt(QUrl::fromLocalFile(proxyPtr->originFileName)); + copyOpt.copyFileAsync(QUrl::fromLocalFile(proxyPtr->proxyFileName), + DFile::CopyFlag::kOverwrite, + nullptr, + nullptr, + 0, + fileOperateCallbackFunc, + reinterpret_cast(&proxyPtr->proxyFileName)); + +#else + // 使用 Qt Api + QFile copyFile(proxyPtr->originFileName); + // 移除原有代理文件 QFile::copy() 默认不会覆盖文件 + if (QFile::exists(proxyPtr->proxyFileName)) { + QFile::remove(proxyPtr->proxyFileName); + } + + bool ret = copyFile.copy(proxyPtr->proxyFileName); + if (!ret) { + qWarning() << QString("Copy from MTP mount file failed! QFile error:%!").arg(copyFile.errorString()); + } + + loadFinished(proxyPtr->proxyFileName, ret); +#endif +} diff --git a/libimageviewer/service/mtpfileproxy.h b/libimageviewer/service/mtpfileproxy.h new file mode 100644 index 00000000..ca1fdb0b --- /dev/null +++ b/libimageviewer/service/mtpfileproxy.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MTPFILEPROXY_H +#define MTPFILEPROXY_H + +#include +#include +#include +#include +#include + +// MTP挂载设备文件代理 +class MtpFileProxy : public QObject +{ + Q_OBJECT + + MtpFileProxy(); + ~MtpFileProxy(); + +public: + static MtpFileProxy *instance(); + + bool isValid() const; + bool contains(const QString &proxyFile) const; + bool checkAndCreateProxyFile(QStringList &paths, QString &firstPath); + bool checkFileDeviceIsMtp(const QString &filePath); + bool submitChangesToMTP(const QString &proxyFile); + + // 文件状态 + enum FileState { None, Loading, LoadSucc, LoadFailed, FileDelete }; + FileState state(const QString &proxyFile) const; + + QString createPorxyFile(const QString &filePath); + QString mapToOriginFile(const QString &proxyFile) const; + void loadFinished(const QString &proxyFile, bool ret); + Q_SIGNAL void createProxyFileFinished(const QString &proxyFile, bool ret); + void triggerOriginFileChanged(const QString &originFile); + +private: + struct ProxyInfo + { + FileState fileState = None; // 异步处理,加载成功标识 + QString proxyFileName; + QString originFileName; + QDateTime lastModified; + QTemporaryDir tempDir; // 缓存文件目录,自动移除 + }; + void copyFileFromMtpAsync(const QSharedPointer &proxyPtr); + + QHash> proxyCache; // 文件缓存,程序消耗时移除 +}; + +#endif // MTPFILEPROXY_H diff --git a/libimageviewer/viewpanel/contents/imageinfowidget.cpp b/libimageviewer/viewpanel/contents/imageinfowidget.cpp index 6effe79a..b16f84b1 100644 --- a/libimageviewer/viewpanel/contents/imageinfowidget.cpp +++ b/libimageviewer/viewpanel/contents/imageinfowidget.cpp @@ -8,6 +8,7 @@ #include "widgets/formlabel.h" #include "accessibility/ac-desktop-define.h" #include "service/commonservice.h" +#include "service/mtpfileproxy.h" #include #include @@ -252,17 +253,40 @@ LibImageInfoWidget::~LibImageInfoWidget() clearLayout(m_exifLayout_details); } +void updateFileTime(QMap &data, const QString &path) +{ + QFileInfo info(path); + if (data.contains("DateTime")) { + QDateTime time = QDateTime::fromString(data["DateTime"], "yyyy:MM:dd hh:mm:ss"); + data["DateTimeOriginal"] = time.toString("yyyy/MM/dd hh:mm"); + } else { + data.insert("DateTimeOriginal", info.lastModified().toString("yyyy/MM/dd HH:mm")); + } + data.insert("DateTimeDigitized", info.lastModified().toString("yyyy/MM/dd HH:mm")); +} + void LibImageInfoWidget::setImagePath(const QString &path, bool forceUpdate) { + // MTP文件需调整文件路径 + bool needMtpProxy = MtpFileProxy::instance()->contains(path); + // 根据forceUpdate标志判断使用本函数进行整个image info弹窗的整体强制重刷 if (!forceUpdate && m_path == path) { + auto metaData = Libutils::image::getAllMetaData(path); + if (needMtpProxy) { + updateFileTime(metaData, MtpFileProxy::instance()->mapToOriginFile(path)); + } + // 检测数据是否存在变更 - if (m_metaData == Libutils::image::getAllMetaData(path)) { + if (m_metaData == metaData) { return; } } else { m_path = path; m_metaData = Libutils::image::getAllMetaData(path); + if (needMtpProxy) { + updateFileTime(m_metaData, MtpFileProxy::instance()->mapToOriginFile(path)); + } } m_isBaseInfo = false; diff --git a/libimageviewer/viewpanel/scen/imagegraphicsview.cpp b/libimageviewer/viewpanel/scen/imagegraphicsview.cpp index 7b8df19a..a1047dab 100644 --- a/libimageviewer/viewpanel/scen/imagegraphicsview.cpp +++ b/libimageviewer/viewpanel/scen/imagegraphicsview.cpp @@ -35,6 +35,7 @@ #include "accessibility/ac-desktop-define.h" #include "../contents/morepicfloatwidget.h" #include "imageengine.h" +#include "service/mtpfileproxy.h" #include #include @@ -128,6 +129,24 @@ LibImageGraphicsView::LibImageGraphicsView(QWidget *parent) m_isChangedTimer = new QTimer(this); QObject::connect(m_isChangedTimer, &QTimer::timeout, this, &LibImageGraphicsView::onIsChangedTimerTimeout); + // MTP文件加载完成通知, 使用 QueuedConnection 确保不在 setImage 时触发,保证先后顺序。 + QObject::connect(MtpFileProxy::instance(), &MtpFileProxy::createProxyFileFinished, this, [ this ](const QString &proxyFile, bool){ + if (proxyFile == m_loadPath) { + // SVG/TIF/GIF 需重新加载 + imageViewerSpace::ImageType Type = LibUnionImage_NameSpace::getImageType(m_loadPath); + if (imageViewerSpace::ImageTypeDynamic == Type + || imageViewerSpace::ImageTypeSvg == Type + || imageViewerSpace::ImageTypeMulti == Type) { + setImage(m_loadPath); + } else { + this->onLoadTimerTimeout(); + } + + m_imgFileWatcher->addPath(m_path); + m_imgFileWatcher->addPath(MtpFileProxy::instance()->mapToOriginFile(m_path)); + } + }, Qt::QueuedConnection); + //让默认的快捷键失效,默认会滑动窗口 connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Up), this), &QShortcut::activated, this, &LibImageGraphicsView::slotsUp); connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Down), this), &QShortcut::activated, this, &LibImageGraphicsView::slotsDown); @@ -228,6 +247,7 @@ void LibImageGraphicsView::clear() void LibImageGraphicsView::setImage(const QString &path, const QImage &image) { + // m_spinner 生命周期由 scene() 管理 m_spinner = nullptr; //默认多页图的按钮显示为false @@ -239,10 +259,16 @@ void LibImageGraphicsView::setImage(const QString &path, const QImage &image) delete m_imageReader; m_imageReader = nullptr; } - //重新生成数据缓存 -// ImageEngine::instance()->makeImgThumbnail(CommonService::instance()->getImgSavePath(), QStringList(path), 1, true); + + // 判断是否需要等待 MTP 代理文件加载完成,失败同样无需等待加载 + MtpFileProxy::FileState state = MtpFileProxy::instance()->state(path); + bool needProxyLoad = MtpFileProxy::instance()->contains(path) && (MtpFileProxy::Loading == state); + //检测数据缓存,如果存在,则使用缓存 - imageViewerSpace::ItemInfo info = LibCommonService::instance()->getImgInfoByPath(path); + imageViewerSpace::ItemInfo info; + if (!needProxyLoad) { + info = LibCommonService::instance()->getImgInfoByPath(path); + } m_bRoate = ImageEngine::instance()->isRotatable(path); //是否可旋转 m_loadPath = path; @@ -250,9 +276,16 @@ void LibImageGraphicsView::setImage(const QString &path, const QImage &image) if (path.isEmpty()) { return; } + + // 同时移除 MTP 代理文件的观察 + m_imgFileWatcher->removePath(MtpFileProxy::instance()->mapToOriginFile(m_path)); m_imgFileWatcher->removePath(m_path); m_path = path; - m_imgFileWatcher->addPath(m_path); + if (!needProxyLoad) { + // MTP 代理文件等待创建完成后再添加观察 + m_imgFileWatcher->addPath(m_path); + } + QString strfixL = QFileInfo(path).suffix().toUpper(); QGraphicsScene *s = scene(); QFileInfo fi(path); @@ -261,9 +294,13 @@ void LibImageGraphicsView::setImage(const QString &path, const QImage &image) //the judge way to solve the problem imageViewerSpace::ImageType Type = info.imageType; - if (Type == imageViewerSpace::ImageTypeBlank) { + if (needProxyLoad) { + // MTP代理文件默认为空,后续加载处理 + Type = imageViewerSpace::ImageTypeBlank; + } else if (Type == imageViewerSpace::ImageTypeBlank) { Type = LibUnionImage_NameSpace::getImageType(path); } + //ImageTypeDynamic if (Type == imageViewerSpace::ImageTypeDynamic) { m_pixmapItem = nullptr; @@ -403,13 +440,18 @@ void LibImageGraphicsView::setImage(const QString &path, const QImage &image) if (!pix.isNull()) { setSceneRect(m_pixmapItem->boundingRect()); } + + // 使用 MTP 代理文件时,需等待代理文件创建完成 createProxyFileFinished() ,完成后调用 onLoadTimerTimeout() //第一次打开直接启动,不使用延时300ms - if (m_isFistOpen) { - onLoadTimerTimeout(); - m_isFistOpen = false; - } else { - m_loadTimer->start(); + if (!needProxyLoad) { + if (m_isFistOpen) { + onLoadTimerTimeout(); + m_isFistOpen = false; + } else { + m_loadTimer->start(); + } } + scene()->addItem(m_pixmapItem); emit imageChanged(path); QMetaObject::invokeMethod(this, [ = ]() { @@ -727,7 +769,9 @@ void LibImageGraphicsView::slotSavePic() void LibImageGraphicsView::onImgFileChanged(const QString &ddfFile) { - Q_UNUSED(ddfFile) + // 判断是否为MTP原始路径文件,若为则同步更新代理文件状态 + MtpFileProxy::instance()->triggerOriginFileChanged(ddfFile); + m_isChangedTimer->start(200); } @@ -936,6 +980,10 @@ void LibImageGraphicsView::slotRotatePixCurrent() disconnect(m_imgFileWatcher, &QFileSystemWatcher::fileChanged, this, &LibImageGraphicsView::onImgFileChanged); Libutils::image::rotate(m_path, m_rotateAngel); + + // 如果是 MTP 文件,则将缓存文件的变更提交到 MTP 目录下 + MtpFileProxy::instance()->submitChangesToMTP(m_path); + //如果是相册调用,则告知刷新 if (LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeAlbum) { emit ImageEngine::instance()->sigRotatePic(m_path); diff --git a/libimageviewer/viewpanel/viewpanel.cpp b/libimageviewer/viewpanel/viewpanel.cpp index ba08e482..1be8e0cf 100644 --- a/libimageviewer/viewpanel/viewpanel.cpp +++ b/libimageviewer/viewpanel/viewpanel.cpp @@ -37,6 +37,7 @@ #include "slideshow/slideshowpanel.h" #include "service/configsetter.h" #include "service/imagedataservice.h" +#include "service/mtpfileproxy.h" #include "unionimage/imageutils.h" const QString IMAGE_TMPPATH = QDir::homePath() + @@ -452,12 +453,17 @@ void LibViewPanel::updateMenuContent(const QString &path) QFileInfo info(currentPath); bool isReadable = info.isReadable() ; //是否可读 - qDebug() << QFileInfo(info.dir(), info.dir().path()).isWritable(); - //判断文件是否可写和文件目录是否可写 + // 判断文件是否可写和文件目录是否可写 bool isWritable = info.isWritable() && QFileInfo(info.dir(), info.dir().path()).isWritable(); //是否可写 -// bool isFile = info.isFile(); //是否存在 bool isRotatable = ImageEngine::instance()->isRotatable(currentPath);//是否可旋转 - imageViewerSpace::PathType pathType = LibUnionImage_NameSpace::getPathType(currentPath);//路径类型 + imageViewerSpace::PathType pathType; // 路径类型 + // 判断是否为MTP文件,现MTP文件将使用代理文件加载 + if (MtpFileProxy::instance()->contains(currentPath)) { + pathType = imageViewerSpace::PathType::PathTypeMTP; + } else { + pathType = LibUnionImage_NameSpace::getPathType(currentPath); + } + imageViewerSpace::ImageType imageType = LibUnionImage_NameSpace::getImageType(currentPath);//图片类型 //判断是否是损坏图片 @@ -866,8 +872,13 @@ void LibViewPanel::setWallpaper(const QString &imgPath) bool LibViewPanel::startdragImage(const QStringList &paths, const QString &firstPath) { + // 若为 MTP 挂载文件,转换为目录加载 + QStringList realPaths = paths; + QString realPath = firstPath; + bool isMtpProxy = MtpFileProxy::instance()->checkAndCreateProxyFile(realPaths, realPath); + bool bRet = false; - QStringList image_list = paths; + QStringList image_list = realPaths; if (image_list.isEmpty()) return false; @@ -879,6 +890,8 @@ bool LibViewPanel::startdragImage(const QStringList &paths, const QString &first if (ImageEngine::instance()->isImage(path)) { image_list << path; } + } else if (isMtpProxy) { + // 无需处理,使用默认 image_list } else { QString DirPath = image_list.first().left(image_list.first().lastIndexOf("/")); QDir _dirinit(DirPath); @@ -927,14 +940,14 @@ bool LibViewPanel::startdragImage(const QStringList &paths, const QString &first //stack设置正确位置 m_stack->setCurrentWidget(m_view); //展示当前图片 - loadImage(firstPath, paths); - LibCommonService::instance()->m_listAllPath = paths; - LibCommonService::instance()->m_noLoadingPath = paths; + loadImage(realPath, realPaths); + LibCommonService::instance()->m_listAllPath = realPaths; + LibCommonService::instance()->m_noLoadingPath = realPaths; LibCommonService::instance()->m_listLoaded.clear(); //看图首先制作显示的图片的缩略图 - ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), QStringList(firstPath), 1); + ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), QStringList(realPath), 1); - loadThumbnails(firstPath); + loadThumbnails(realPath); } m_bottomToolbar->thumbnailMoveCenterWidget(); return bRet; @@ -1088,6 +1101,11 @@ void LibViewPanel::loadThumbnails(const QString &path) void LibViewPanel::setCurrentWidget(const QString &path) { + // MTP 挂载文件且在加载中则不进入此判断(加载完成后调用) + if (MtpFileProxy::Loading == MtpFileProxy::instance()->state(path)) { + return; + } + //存在切换到幻灯片被切换回去的情况,所以如果是当前界面为幻灯片,则不切换为其他的页面 if (m_stack->currentWidget() != m_sliderPanel) { QFileInfo info(path); @@ -1208,12 +1226,17 @@ bool LibViewPanel::startChooseFileDialog() QFileInfo firstFileInfo(path); LibConfigSetter::instance()->setValue(cfgGroupName, cfgLastOpenPath, firstFileInfo.path()); + // 若为MTP挂载图片,将使用临时目录,需在记录打开目录后执行 + bool isMtpProxy = MtpFileProxy::instance()->checkAndCreateProxyFile(image_list, path); + if ((path.indexOf("smb-share:server=") != -1 || path.indexOf("mtp:host=") != -1 || path.indexOf("gphoto2:host=") != -1)) { image_list.clear(); //判断是否图片格式 if (ImageEngine::instance()->isImage(path)) { image_list << path; } + } else if (isMtpProxy) { + // 无需处理,使用默认 image_list } else { QString DirPath = image_list.first().left(image_list.first().lastIndexOf("/")); QDir _dirinit(DirPath); @@ -1750,8 +1773,12 @@ void LibViewPanel::onMenuItemClicked(QAction *action) if (m_view) { m_view->slotRotatePixCurrent(); } + QString path = m_bottomToolbar->getCurrentItemInfo().path; + // MTP文件需调整文件路径 + path = MtpFileProxy::instance()->mapToOriginFile(path); + //todo显示在文管 - Libutils::base::showInFileManager(m_bottomToolbar->getCurrentItemInfo().path); + Libutils::base::showInFileManager(path); break; } case IdImageInfo: { @@ -1767,6 +1794,7 @@ void LibViewPanel::onMenuItemClicked(QAction *action) if (path.isEmpty()) { path = m_currentPath; } + m_info->setImagePath(path); //执行强制重刷 m_info->show(); m_extensionPanel->setContent(m_info);