diff --git a/core/libs/dtrash/dtrash.cpp b/core/libs/dtrash/dtrash.cpp index ee6848874e..b6dfc09298 100644 --- a/core/libs/dtrash/dtrash.cpp +++ b/core/libs/dtrash/dtrash.cpp @@ -1,252 +1,256 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-07-27 * Description : Special digiKam trash implementation * * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dtrash.h" // Qt includes #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "collectionmanager.h" #include "albummanager.h" namespace Digikam { const QString DTrash::TRASH_FOLDER = QLatin1String(".dtrash"); const QString DTrash::FILES_FOLDER = QLatin1String("files"); const QString DTrash::INFO_FOLDER = QLatin1String("info"); const QString DTrash::INFO_FILE_EXTENSION = QLatin1String(".dtrashinfo"); const QString DTrash::PATH_JSON_KEY = QLatin1String("path"); const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp"); const QString DTrash::IMAGEID_JSON_KEY = QLatin1String("imageid"); // ---------------------------------------------- DTrash::DTrash() { } bool DTrash::deleteImage(const QString& imagePath, const QDateTime& deleteTime) { QString collection = CollectionManager::instance()->albumRootPath(imagePath); qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: Image album root path:" << collection; if (!prepareCollectionTrash(collection)) { return false; } QFileInfo imageFileInfo(imagePath); QString fileName = imageFileInfo.fileName(); + // Get the album path, i.e. collection + album. For this, // get the n leftmost characters where n is the complete path without the size of the filename + QString completePath = imageFileInfo.path(); qlonglong imageId = -1; + // Get the album and with this the image id of the image to trash. + PAlbum* const pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath)); if (pAlbum) { imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName); } QString baseNameForMovingIntoTrash = createJsonRecordForFile(imageId, imagePath, deleteTime, collection); - QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER + + QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') + - baseNameForMovingIntoTrash + QLatin1Char('.') + + baseNameForMovingIntoTrash + QLatin1Char('.') + imageFileInfo.completeSuffix(); if (!QFile::rename(imagePath, destinationInTrash)) { return false; } return true; } bool DTrash::deleteDirRecursivley(const QString& dirToDelete, const QDateTime& deleteTime) { QDir srcDir(dirToDelete); foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files)) { if (!deleteImage(fileInfo.filePath(), deleteTime)) { return false; } } foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { if (!deleteDirRecursivley(fileInfo.filePath(), deleteTime)) { return false; } } return srcDir.removeRecursively(); } void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo) { - QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER + + QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER + QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') + baseName + INFO_FILE_EXTENSION; QFile jsonFile(jsonFilePath); if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } QJsonDocument doc = QJsonDocument::fromJson(jsonFile.readAll()); jsonFile.close(); QJsonObject fileInfoObj = doc.object(); itemInfo.jsonFilePath = jsonFilePath; itemInfo.collectionPath = fileInfoObj.value(PATH_JSON_KEY).toString(); itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString() .replace(collPath, QLatin1String("")); itemInfo.deletionTimestamp = QDateTime::fromString( fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString()); QJsonValue imageIdValue = fileInfoObj.value(IMAGEID_JSON_KEY); if (!imageIdValue.isUndefined()) { itemInfo.imageId = imageIdValue.toString().toLongLong(); } else { itemInfo.imageId = -1; } } bool DTrash::prepareCollectionTrash(const QString& collectionPath) { QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER; QDir trashDir(trashFolder); if (!trashDir.exists()) { bool isCreated = true; isCreated &= trashDir.mkpath(trashFolder); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + FILES_FOLDER); isCreated &= trashDir.mkpath(trashFolder + QLatin1Char('/') + INFO_FOLDER); if (!isCreated) { qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection"; return false; } } qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection: " << trashFolder; return true; } QString DTrash::createJsonRecordForFile(qlonglong imageId, const QString& imagePath, const QDateTime& deleteTime, const QString& collectionPath) { QJsonObject jsonObjForImg; QJsonValue pathJsonVal(imagePath); QJsonValue timestampJsonVal(deleteTime.toString()); QJsonValue imageIdJsonVal(QString::number(imageId)); jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal); jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal); jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal); QJsonDocument jsonDocForImg(jsonObjForImg); QFileInfo imgFileInfo(imagePath); QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath, imgFileInfo.baseName()); QFile jsonFileForImg(jsonFileName); QFileInfo jsonFileInfo(jsonFileName); if (!jsonFileForImg.open(QFile::WriteOnly)) { return jsonFileInfo.baseName(); } jsonFileForImg.write(jsonDocForImg.toJson()); jsonFileForImg.close(); return jsonFileInfo.baseName(); } QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath, const QString& baseName, int version) { - QString pathToCreateJsonFile = collectionPath + QLatin1Char('/') + - TRASH_FOLDER + QLatin1Char('/') + - INFO_FOLDER + QLatin1Char('/') + - baseName + QLatin1Char('-') + - QUuid::createUuid().toString().mid(1, 8) + + QString pathToCreateJsonFile = collectionPath + QLatin1Char('/') + + TRASH_FOLDER + QLatin1Char('/') + + INFO_FOLDER + QLatin1Char('/') + + baseName + QLatin1Char('-') + + QUuid::createUuid().toString().mid(1, 8) + (version ? QString::number(version) : QLatin1String("")) + INFO_FILE_EXTENSION; QFileInfo jsonFileInfo(pathToCreateJsonFile); if (jsonFileInfo.exists()) { return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version); } else { return pathToCreateJsonFile; } } } // namespace Digikam diff --git a/core/libs/dtrash/dtrashitemmodel.cpp b/core/libs/dtrash/dtrashitemmodel.cpp index 5b54ddbc16..e61d216600 100644 --- a/core/libs/dtrash/dtrashitemmodel.cpp +++ b/core/libs/dtrash/dtrashitemmodel.cpp @@ -1,420 +1,420 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-08-09 * Description : DTrash item info model * * Copyright (C) 2015 by Mohamed_Anwer * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dtrashitemmodel.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include "klocalizedstring.h" // Local includes #include "digikam_debug.h" #include "thumbnailsize.h" #include "iojobsmanager.h" namespace Digikam { class Q_DECL_HIDDEN DTrashItemModel::Private { public: explicit Private() : thumbSize(ThumbnailSize::Large), sortColumn(2), sortOrder(Qt::DescendingOrder), itemsLoadingThread(nullptr), thumbnailThread(nullptr) { } public: int thumbSize; int sortColumn; Qt::SortOrder sortOrder; IOJobsThread* itemsLoadingThread; ThumbnailLoadThread* thumbnailThread; QList failedThumbnails; DTrashItemInfoList data; }; DTrashItemModel::DTrashItemModel(QObject* const parent) : QAbstractTableModel(parent), d(new Private) { qRegisterMetaType("DTrashItemInfo"); d->thumbnailThread = new ThumbnailLoadThread; d->thumbnailThread->setSendSurrogatePixmap(false); connect(d->thumbnailThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(refreshThumbnails(LoadingDescription,QPixmap))); } DTrashItemModel::~DTrashItemModel() { delete d->thumbnailThread; delete d; } int DTrashItemModel::rowCount(const QModelIndex&) const { return d->data.count(); } int DTrashItemModel::columnCount(const QModelIndex&) const { return 3; } QVariant DTrashItemModel::data(const QModelIndex& index, int role) const { if ( (role != Qt::DisplayRole) && (role != Qt::DecorationRole) && (role != Qt::TextAlignmentRole) && (role != Qt::ToolTipRole) ) { return QVariant(); } const DTrashItemInfo& item = d->data[index.row()]; if (role == Qt::TextAlignmentRole) { return Qt::AlignCenter; } - if (role == Qt::DecorationRole && index.column() == 0) + if ((role == Qt::DecorationRole) && (index.column() == 0)) { QPixmap pix; QString thumbPath; if (!d->failedThumbnails.contains(item.collectionPath)) { d->failedThumbnails << item.collectionPath; thumbPath = item.collectionPath; } else if (!d->failedThumbnails.contains(item.trashPath)) { thumbPath = item.trashPath; } if (thumbPath.isEmpty() || pixmapForItem(thumbPath, pix)) { if (pix.isNull()) { QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item.trashPath); if (mimeType.isValid()) { pix = QIcon::fromTheme(mimeType.genericIconName()).pixmap(128); } } return pix; } else { return QVariant(QVariant::Pixmap); } } if ((role == Qt::ToolTipRole) && (index.column() == 1)) { return item.collectionRelativePath; } switch (index.column()) { case 1: return item.collectionRelativePath; case 2: { QString dateTimeFormat = QLocale().dateTimeFormat(); if (!dateTimeFormat.contains(QLatin1String("yyyy"))) { dateTimeFormat.replace(QLatin1String("yy"), QLatin1String("yyyy")); } return item.deletionTimestamp.toString(dateTimeFormat); } default: return QVariant(); }; } void DTrashItemModel::sort(int column, Qt::SortOrder order) { d->sortColumn = column; d->sortOrder = order; if (d->data.count() < 2) { return; } std::sort(d->data.begin(), d->data.end(), [column, order](const DTrashItemInfo& a, const DTrashItemInfo& b) { if ((column == 2) && (a.deletionTimestamp != b.deletionTimestamp)) { if (order == Qt::DescendingOrder) { return (a.deletionTimestamp > b.deletionTimestamp); } else { return (a.deletionTimestamp < b.deletionTimestamp); } } if (order == Qt::DescendingOrder) { return (a.collectionRelativePath > b.collectionRelativePath); } return (a.collectionRelativePath < b.collectionRelativePath); } ); const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, columnCount(QModelIndex())-1); dataChanged(topLeft, bottomRight); } bool DTrashItemModel::pixmapForItem(const QString& path, QPixmap& pix) const { double ratio = qApp->devicePixelRatio(); int thumbSize = qMin(qRound((double)d->thumbSize * ratio), (int)ThumbnailSize::HD); bool ret = d->thumbnailThread->find(ThumbnailIdentifier(path), pix, thumbSize); pix.setDevicePixelRatio(ratio); return ret; } QVariant DTrashItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } if (role != Qt::DisplayRole) { return QVariant(); } switch (section) { case 0: return i18n("Thumbnail"); case 1: return i18n("Relative Path"); case 2: return i18n("Deletion Time"); default: return QVariant(); } } void DTrashItemModel::append(const DTrashItemInfo& itemInfo) { if (d->itemsLoadingThread != sender()) { return; } beginInsertRows(QModelIndex(), d->data.count(), d->data.count()); d->data.append(itemInfo); endInsertRows(); sort(d->sortColumn, d->sortOrder); emit dataChange(); } void DTrashItemModel::removeItems(const QModelIndexList& indexes) { QList persistentIndexes; foreach (const QModelIndex& index, indexes) { persistentIndexes << index; } layoutAboutToBeChanged(); foreach (const QPersistentModelIndex& index, persistentIndexes) { if (!index.isValid()) { continue; } const DTrashItemInfo& item = d->data[index.row()]; d->failedThumbnails.removeAll(item.collectionPath); d->failedThumbnails.removeAll(item.trashPath); beginRemoveRows(QModelIndex(), index.row(), index.row()); removeRow(index.row()); d->data.removeAt(index.row()); endRemoveRows(); } layoutChanged(); emit dataChange(); } void DTrashItemModel::refreshLayout() { const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, 0); dataChanged(topLeft, bottomRight); layoutAboutToBeChanged(); layoutChanged(); } void DTrashItemModel::refreshThumbnails(const LoadingDescription& desc, const QPixmap& pix) { if (pix.isNull()) { if (!d->failedThumbnails.contains(desc.filePath)) { d->failedThumbnails << desc.filePath; } } const QModelIndex topLeft = index(0, 0); const QModelIndex bottomRight = index(rowCount(QModelIndex())-1, 0); dataChanged(topLeft, bottomRight); } void DTrashItemModel::clearCurrentData() { d->failedThumbnails.clear(); beginResetModel(); d->data.clear(); endResetModel(); emit dataChange(); } void DTrashItemModel::loadItemsForCollection(const QString& colPath) { clearCurrentData(); d->itemsLoadingThread = IOJobsManager::instance()->startDTrashItemsListingForCollection(colPath); connect(d->itemsLoadingThread, SIGNAL(collectionTrashItemInfo(DTrashItemInfo)), this, SLOT(append(DTrashItemInfo)), Qt::QueuedConnection); } DTrashItemInfo DTrashItemModel::itemForIndex(const QModelIndex& index) { if (!index.isValid()) { return DTrashItemInfo(); } return d->data.at(index.row()); } DTrashItemInfoList DTrashItemModel::itemsForIndexes(const QList& indexes) { DTrashItemInfoList items; foreach (const QModelIndex& index, indexes) { if (!index.isValid()) { continue; } items << itemForIndex(index); } return items; } QModelIndex DTrashItemModel::indexForItem(const DTrashItemInfo& itemInfo) const { int index = d->data.indexOf(itemInfo); if (index != -1) { return createIndex(index, 0); } return QModelIndex(); } DTrashItemInfoList DTrashItemModel::allItems() { return d->data; } bool DTrashItemModel::isEmpty() { return d->data.isEmpty(); } void DTrashItemModel::changeThumbSize(int size) { d->thumbSize = size; if (isEmpty()) { return; } QTimer::singleShot(100, this, SLOT(refreshLayout())); } } // namespace Digikam diff --git a/core/libs/iojobs/iojob.cpp b/core/libs/iojobs/iojob.cpp index c28934fc05..1c828671a1 100644 --- a/core/libs/iojobs/iojob.cpp +++ b/core/libs/iojobs/iojob.cpp @@ -1,438 +1,439 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-06-15 * Description : IO Jobs for file systems jobs * * Copyright (C) 2015 by Mohamed_Anwer * Copyright (C) 2018 by Maik Qualmann * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "iojob.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "iteminfo.h" #include "dtrash.h" #include "coredb.h" #include "coredbaccess.h" #include "albummanager.h" #include "dfileoperations.h" #include "coredboperationgroup.h" namespace Digikam { IOJob::IOJob() { } // -------------------------------------------- CopyOrMoveJob::CopyOrMoveJob(IOJobData* const data) : m_data(data) { } void CopyOrMoveJob::run() { while (m_data && !m_cancel) { QUrl srcUrl = m_data->getNextUrl(); if (srcUrl.isEmpty()) { break; } QFileInfo srcInfo(srcUrl.toLocalFile()); QDir dstDir(m_data->destUrl().toLocalFile()); if (!srcInfo.exists()) { emit signalError(i18n("File/Folder %1 does not exist anymore", srcInfo.baseName())); continue; } if (!dstDir.exists()) { emit signalError(i18n("Album %1 does not exist anymore", dstDir.dirName())); continue; } // Checking if there is a file with the same name in destination folder + QString destenationName = srcInfo.isFile() ? srcInfo.fileName() : srcInfo.dir().dirName(); QString destenation = dstDir.path() + QLatin1Char('/') + destenationName; if (QFileInfo::exists(destenation)) { emit signalError(i18n("A file or folder named %1 already exists in %2", srcInfo.baseName(), QDir::toNativeSeparators(dstDir.path()))); continue; } if ((m_data->operation() == IOJobData::MoveAlbum) || (m_data->operation() == IOJobData::MoveImage) || (m_data->operation() == IOJobData::MoveFiles)) { if (srcInfo.isDir()) { QDir srcDir(srcInfo.filePath()); if (!srcDir.rename(srcDir.path(), destenation)) { // If QDir::rename fails, try copy and remove. if (!DFileOperations::copyFolderRecursively(srcDir.path(), dstDir.path(), &m_cancel)) { if (m_cancel) { break; } emit signalError(i18n("Could not move folder %1 to album %2", QDir::toNativeSeparators(srcDir.path()), QDir::toNativeSeparators(dstDir.path()))); continue; } else if (!srcDir.removeRecursively()) { emit signalError(i18n("Could not move folder %1 to album %2. " "The folder %1 was copied as well to album %2", QDir::toNativeSeparators(srcDir.path()), QDir::toNativeSeparators(dstDir.path()))); } } } else { if (!DFileOperations::renameFile(srcInfo.filePath(), destenation)) { emit signalError(i18n("Could not move file %1 to album %2", srcInfo.filePath(), QDir::toNativeSeparators(dstDir.path()))); continue; } } } else { if (srcInfo.isDir()) { QDir srcDir(srcInfo.filePath()); if (!DFileOperations::copyFolderRecursively(srcDir.path(), dstDir.path(), &m_cancel)) { if (m_cancel) { break; } emit signalError(i18n("Could not copy folder %1 to album %2", QDir::toNativeSeparators(srcDir.path()), QDir::toNativeSeparators(dstDir.path()))); continue; } } else { if (!DFileOperations::copyFile(srcInfo.filePath(), destenation)) { emit signalError(i18n("Could not copy file %1 to album %2", QDir::toNativeSeparators(srcInfo.path()), QDir::toNativeSeparators(dstDir.path()))); continue; } } } emit signalOneProccessed(srcUrl); } emit signalDone(); } // -------------------------------------------- DeleteJob::DeleteJob(IOJobData* const data) : m_data(data) { } void DeleteJob::run() { while (m_data && !m_cancel) { QUrl deleteUrl = m_data->getNextUrl(); if (deleteUrl.isEmpty()) { break; } bool useTrash = (m_data->operation() == IOJobData::Trash); QFileInfo fileInfo(deleteUrl.toLocalFile()); qCDebug(DIGIKAM_IOJOB_LOG) << "Deleting: " << fileInfo.filePath(); qCDebug(DIGIKAM_IOJOB_LOG) << "File exists?" << fileInfo.exists(); qCDebug(DIGIKAM_IOJOB_LOG) << "Is to trash?" << useTrash; if (!fileInfo.exists()) { emit signalError(i18n("File/Folder %1 does not exist", QDir::toNativeSeparators(fileInfo.filePath()))); continue; } if (useTrash) { if (fileInfo.isDir()) { if (!DTrash::deleteDirRecursivley(deleteUrl.toLocalFile(), m_data->jobTime())) { emit signalError(i18n("Could not move folder %1 to collection trash", QDir::toNativeSeparators(fileInfo.path()))); continue; } } else { if (!DTrash::deleteImage(deleteUrl.toLocalFile(), m_data->jobTime())) { emit signalError(i18n("Could not move image %1 to collection trash", QDir::toNativeSeparators(fileInfo.filePath()))); continue; } } } else { if (fileInfo.isDir()) { QDir dir(fileInfo.filePath()); if (!dir.removeRecursively()) { emit signalError(i18n("Album %1 could not be removed", QDir::toNativeSeparators(fileInfo.path()))); continue; } } else { QFile file(fileInfo.filePath()); if (!file.remove()) { emit signalError(i18n("Image %1 could not be removed", QDir::toNativeSeparators(fileInfo.filePath()))); continue; } } } emit signalOneProccessed(deleteUrl); } emit signalDone(); } // -------------------------------------------- RenameFileJob::RenameFileJob(IOJobData* const data) : m_data(data) { } void RenameFileJob::run() { while (m_data && !m_cancel) { QUrl renameUrl = m_data->getNextUrl(); if (renameUrl.isEmpty()) { break; } QUrl destUrl = m_data->destUrl(renameUrl); if (QFileInfo::exists(destUrl.toLocalFile())) { if (m_data->overwrite()) { if (!DTrash::deleteImage(destUrl.toLocalFile(), m_data->jobTime())) { emit signalError(i18n("Could not move image %1 to collection trash", QDir::toNativeSeparators(destUrl.toLocalFile()))); emit signalRenameFailed(renameUrl); continue; } } else { qCDebug(DIGIKAM_IOJOB_LOG) << "File with the same name exists!"; emit signalError(i18n("Image with the same name %1 already there", QDir::toNativeSeparators(destUrl.toLocalFile()))); emit signalRenameFailed(renameUrl); continue; } } qCDebug(DIGIKAM_IOJOB_LOG) << "Trying to rename" << renameUrl.toLocalFile() << "to" << destUrl.toLocalFile(); if (!DFileOperations::renameFile(renameUrl.toLocalFile(), destUrl.toLocalFile())) { qCDebug(DIGIKAM_IOJOB_LOG) << "File could not be renamed!"; emit signalError(i18n("Image %1 could not be renamed", QDir::toNativeSeparators(renameUrl.toLocalFile()))); emit signalRenameFailed(renameUrl); continue; } emit signalOneProccessed(renameUrl); } emit signalDone(); } // ---------------------------------------------- DTrashItemsListingJob::DTrashItemsListingJob(const QString& collectionPath) : m_collectionPath(collectionPath) { } void DTrashItemsListingJob::run() { DTrashItemInfo itemInfo; QString collectionTrashFilesPath = m_collectionPath + QLatin1Char('/') + DTrash::TRASH_FOLDER + QLatin1Char('/') + DTrash::FILES_FOLDER; qCDebug(DIGIKAM_IOJOB_LOG) << "Collection trash files path:" << collectionTrashFilesPath; QDir filesDir(collectionTrashFilesPath); foreach (const QFileInfo& fileInfo, filesDir.entryInfoList(QDir::Files)) { qCDebug(DIGIKAM_IOJOB_LOG) << "File in trash:" << fileInfo.filePath(); itemInfo.trashPath = fileInfo.filePath(); DTrash::extractJsonForItem(m_collectionPath, fileInfo.baseName(), itemInfo); emit trashItemInfo(itemInfo); } emit signalDone(); } // ---------------------------------------------- RestoreDTrashItemsJob::RestoreDTrashItemsJob(const DTrashItemInfoList& infos) : m_dtrashItemInfoList(infos) { } void RestoreDTrashItemsJob::run() { foreach (const DTrashItemInfo& item, m_dtrashItemInfoList) { QUrl srcToRename = QUrl::fromLocalFile(item.collectionPath); QUrl newName = DFileOperations::getUniqueFileUrl(srcToRename); QFileInfo fi(item.collectionPath); if (!fi.dir().exists()) { fi.dir().mkpath(fi.dir().path()); } if (!QFile::rename(item.trashPath, newName.toLocalFile())) { qCDebug(DIGIKAM_IOJOB_LOG) << "Trash file could not be renamed!"; } else { QFile::remove(item.jsonFilePath); } } emit signalDone(); } // ---------------------------------------------- DeleteDTrashItemsJob::DeleteDTrashItemsJob(const DTrashItemInfoList& infos) : m_dtrashItemInfoList(infos) { } void DeleteDTrashItemsJob::run() { { CoreDbOperationGroup group; group.setMaximumTime(200); QList albumsFromImages; QList imagesToRemove; foreach (const DTrashItemInfo& item, m_dtrashItemInfoList) { QFile::remove(item.trashPath); QFile::remove(item.jsonFilePath); imagesToRemove << item.imageId; albumsFromImages << ItemInfo(item.imageId).albumId(); CoreDbAccess().db()->removeAllImageRelationsFrom(item.imageId, DatabaseRelation::Grouped); group.allowLift(); } CoreDbAccess().db()->removeItemsPermanently(imagesToRemove, albumsFromImages); } emit signalDone(); } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_data.cpp b/core/libs/metadataengine/engine/metaengine_data.cpp index 69a2a54261..16a4ece7bf 100644 --- a/core/libs/metadataengine/engine/metaengine_data.cpp +++ b/core/libs/metadataengine/engine/metaengine_data.cpp @@ -1,57 +1,56 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Shared data container. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ - #include "metaengine_data_p.h" // Local includes #include "metaengine_p.h" namespace Digikam { MetaEngineData::MetaEngineData() : d(nullptr) { } MetaEngineData::MetaEngineData(const MetaEngineData& other) : d(other.d) { } MetaEngineData::~MetaEngineData() { } MetaEngineData& MetaEngineData::operator=(const MetaEngineData& other) { d = other.d; return *this; } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_data_p.cpp b/core/libs/metadataengine/engine/metaengine_data_p.cpp index eb4bc542c4..fa0b8bea8c 100644 --- a/core/libs/metadataengine/engine/metaengine_data_p.cpp +++ b/core/libs/metadataengine/engine/metaengine_data_p.cpp @@ -1,61 +1,63 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private data container. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metaengine_data_p.h" // Local includes #include "digikam_debug.h" namespace Digikam { void MetaEngineData::Private::clear() { QMutexLocker lock(&s_metaEngineMutex); try { imageComments.clear(); exifMetadata.clear(); iptcMetadata.clear(); + #ifdef _XMP_SUPPORT_ xmpMetadata.clear(); #endif + } catch(Exiv2::AnyError& e) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot clear data container using Exiv2" << " (Error #" << e.code() << ": " << std::string(e.what()).c_str(); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_data_p.h b/core/libs/metadataengine/engine/metaengine_data_p.h index 883f7e1c7d..6c2630329f 100644 --- a/core/libs/metadataengine/engine/metaengine_data_p.h +++ b/core/libs/metadataengine/engine/metaengine_data_p.h @@ -1,55 +1,56 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private data container. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_META_ENGINE_DATA_PRIVATE_H #define DIGIKAM_META_ENGINE_DATA_PRIVATE_H #include "metaengine_p.h" namespace Digikam { class Q_DECL_HIDDEN MetaEngineData::Private : public QSharedData { public: void clear(); public: std::string imageComments; Exiv2::ExifData exifMetadata; Exiv2::IptcData iptcMetadata; #ifdef _XMP_SUPPORT_ Exiv2::XmpData xmpMetadata; #endif + }; } // namespace Digikam #endif // DIGIKAM_META_ENGINE_DATA_PRIVATE_H diff --git a/core/libs/metadataengine/engine/metaengine_exif.cpp b/core/libs/metadataengine/engine/metaengine_exif.cpp index f83e0947f5..0887da6f54 100644 --- a/core/libs/metadataengine/engine/metaengine_exif.cpp +++ b/core/libs/metadataengine/engine/metaengine_exif.cpp @@ -1,1274 +1,1335 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Exif manipulation methods * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metaengine_p.h" // C++ includes #include // Qt includes #include #include // Local includes #include "metaengine_rotation.h" #include "digikam_debug.h" #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { bool MetaEngine::canWriteExif(const QString& filePath) { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*) (QFile::encodeName(filePath).constData())); Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif); - return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite); + return ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)); } catch(Exiv2::AnyError& e) { std::string s(e.what()); qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot check Exif access mode using Exiv2 (Error #" << e.code() << ": " << s.c_str() << ")"; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::hasExif() const { return !d->exifMetadata().empty(); } bool MetaEngine::clearExif() const { QMutexLocker lock(&s_metaEngineMutex); try { d->exifMetadata().clear(); return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot clear Exif data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } QByteArray MetaEngine::getExifEncoded(bool addExifHeader) const { QMutexLocker lock(&s_metaEngineMutex); try { if (!d->exifMetadata().empty()) { QByteArray data; Exiv2::ExifData& exif = d->exifMetadata(); Exiv2::Blob blob; Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif); QByteArray ba((const char*)&blob[0], blob.size()); if (addExifHeader) { const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; data.resize(ba.size() + sizeof(ExifHeader)); memcpy(data.data(), ExifHeader, sizeof(ExifHeader)); memcpy(data.data() + sizeof(ExifHeader), ba.data(), ba.size()); } else { data = ba; } return data; } } catch(Exiv2::AnyError& e) { if (!d->filePath.isEmpty()) qCDebug(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData(); d->printExiv2ExceptionError(QLatin1String("Cannot get Exif data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QByteArray(); } bool MetaEngine::setExif(const QByteArray& data) const { QMutexLocker lock(&s_metaEngineMutex); try { if (!data.isEmpty()) { Exiv2::ExifParser::decode(d->exifMetadata(), (const Exiv2::byte*)data.data(), data.size()); return (!d->exifMetadata().empty()); } } catch(Exiv2::AnyError& e) { if (!d->filePath.isEmpty()) qCCritical(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData(); d->printExiv2ExceptionError(QLatin1String("Cannot set Exif data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } QString MetaEngine::getExifComment(bool readDescription) const { QMutexLocker lock(&s_metaEngineMutex); try { if (!d->exifMetadata().empty()) { Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifKey key("Exif.Photo.UserComment"); Exiv2::ExifData::const_iterator it = exifData.findKey(key); if (it != exifData.end()) { QString exifComment = d->convertCommentValue(*it); // some cameras fill the UserComment with whitespace if (!exifComment.isEmpty() && !exifComment.trimmed().isEmpty()) + { return exifComment; + } } if (readDescription) { Exiv2::ExifKey key2("Exif.Image.ImageDescription"); Exiv2::ExifData::const_iterator it2 = exifData.findKey(key2); if (it2 != exifData.end()) { QString exifComment = d->convertCommentValue(*it2); // Some cameras fill in nonsense default values QStringList blackList; blackList << QLatin1String("SONY DSC"); // + whitespace blackList << QLatin1String("OLYMPUS DIGITAL CAMERA"); blackList << QLatin1String("MINOLTA DIGITAL CAMERA"); QString trimmedComment = exifComment.trimmed(); // some cameras fill the UserComment with whitespace if (!exifComment.isEmpty() && !trimmedComment.isEmpty() && !blackList.contains(trimmedComment)) + { return exifComment; + } } } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot find Exif User Comment using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } static bool is7BitAscii(const QByteArray& s) { const int size = s.size(); for (int i = 0 ; i < size ; ++i) { if (!isascii(s[i])) { return false; } } return true; } bool MetaEngine::setExifComment(const QString& comment, bool writeDescription) const { QMutexLocker lock(&s_metaEngineMutex); try { if (writeDescription) + { removeExifTag("Exif.Image.ImageDescription"); + } removeExifTag("Exif.Photo.UserComment"); if (!comment.isNull()) { if (writeDescription) + { setExifTagString("Exif.Image.ImageDescription", comment); + } // Write as Unicode only when necessary. + QTextCodec* const latin1Codec = QTextCodec::codecForName("iso8859-1"); if (latin1Codec && latin1Codec->canEncode(comment)) { // We know it's in the ISO-8859-1 8bit range. // Check if it's in the ASCII 7bit range + if (is7BitAscii(comment.toLatin1())) { // write as ASCII + std::string exifComment("charset=\"Ascii\" "); exifComment += comment.toLatin1().constData(); d->exifMetadata()["Exif.Photo.UserComment"] = exifComment; return true; } } // write as Unicode (UCS-2) + std::string exifComment("charset=\"Unicode\" "); exifComment += comment.toUtf8().constData(); d->exifMetadata()["Exif.Photo.UserComment"] = exifComment; } return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Comment using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } QString MetaEngine::getExifTagTitle(const char* exifTagName) { QMutexLocker lock(&s_metaEngineMutex); try { std::string exifkey(exifTagName); Exiv2::ExifKey ek(exifkey); return QString::fromLocal8Bit( ek.tagLabel().c_str() ); } catch (Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag title using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } QString MetaEngine::getExifTagDescription(const char* exifTagName) { QMutexLocker lock(&s_metaEngineMutex); try { std::string exifkey(exifTagName); Exiv2::ExifKey ek(exifkey); return QString::fromLocal8Bit( ek.tagDesc().c_str() ); } catch (Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag description using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } bool MetaEngine::removeExifTag(const char* exifTagName) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData::iterator it = d->exifMetadata().findKey(exifKey); if (it != d->exifMetadata().end()) { d->exifMetadata().erase(it); return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif tag using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::getExifTagRational(const char* exifTagName, long int& num, long int& den, int component) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { num = (*it).toRational(component).first; den = (*it).toRational(component).second; return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif Rational value from key '%1' into image using Exiv2 ") .arg(QLatin1String(exifTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::setExifTagLong(const char* exifTagName, long val) const { QMutexLocker lock(&s_metaEngineMutex); try { d->exifMetadata()[exifTagName] = static_cast(val); // krazy:exclude=typedefs return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag long value into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::setExifTagRational(const char* exifTagName, long int num, long int den) const { QMutexLocker lock(&s_metaEngineMutex); try { d->exifMetadata()[exifTagName] = Exiv2::Rational(num, den); return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag rational value into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::setExifTagData(const char* exifTagName, const QByteArray& data) const { if (data.isEmpty()) + { return false; + } QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::DataValue val((Exiv2::byte*)data.data(), data.size()); d->exifMetadata()[exifTagName] = val; return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag data into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::setExifTagVariant(const char* exifTagName, const QVariant& val, bool rationalWantSmallDenominator) const { switch (val.type()) { case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: return setExifTagLong(exifTagName, val.toInt()); case QVariant::Double: { long num, den; if (rationalWantSmallDenominator) + { convertToRationalSmallDenominator(val.toDouble(), &num, &den); + } else + { convertToRational(val.toDouble(), &num, &den, 4); + } return setExifTagRational(exifTagName, num, den); } case QVariant::List: { long num = 0, den = 1; QList list = val.toList(); if (list.size() >= 1) + { num = list[0].toInt(); + } if (list.size() >= 2) + { den = list[1].toInt(); + } return setExifTagRational(exifTagName, num, den); } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = val.toDateTime(); if (!dateTime.isValid()) + { return false; + } try { const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData()); d->exifMetadata()[exifTagName] = exifdatetime; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Date & Time in image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } case QVariant::String: case QVariant::Char: return setExifTagString(exifTagName, val.toString()); case QVariant::ByteArray: return setExifTagData(exifTagName, val.toByteArray()); default: break; } return false; } QString MetaEngine::createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR) { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey key(exifTagName); Exiv2::Exifdatum datum(key); switch (val.type()) { case QVariant::Int: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: datum = (int32_t)val.toInt(); break; case QVariant::UInt: datum = (uint32_t)val.toUInt(); break; case QVariant::Double: { long num, den; convertToRationalSmallDenominator(val.toDouble(), &num, &den); Exiv2::Rational rational; rational.first = num; rational.second = den; - datum = rational; + datum = rational; break; } case QVariant::List: { long num = 0; long den = 1; QList list = val.toList(); if (list.size() >= 1) + { num = list[0].toInt(); + } if (list.size() >= 2) + { den = list[1].toInt(); + } Exiv2::Rational rational; rational.first = num; rational.second = den; datum = rational; break; } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = val.toDateTime(); if (!dateTime.isValid()) + { break; + } const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData()); datum = exifdatetime; break; } case QVariant::ByteArray: case QVariant::String: case QVariant::Char: datum = (std::string)val.toString().toLatin1().constData(); break; default: break; } std::ostringstream os; os << datum; QString tagValue = QString::fromLocal8Bit(os.str().c_str()); if (escapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return tagValue; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Iptc tag string into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } bool MetaEngine::getExifTagLong(const char* exifTagName, long& val) const { return getExifTagLong(exifTagName, val, 0); } bool MetaEngine::getExifTagLong(const char* exifTagName, long& val, int component) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); if (it != exifData.end() && it->count() > 0) { val = it->toLong(component); return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ") .arg(QLatin1String(exifTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } QByteArray MetaEngine::getExifTagData(const char* exifTagName) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { char* const s = new char[(*it).size()]; (*it).copy((Exiv2::byte*)s, Exiv2::bigEndian); QByteArray data(s, (*it).size()); delete[] s; return data; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ") .arg(QLatin1String(exifTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QByteArray(); } QVariant MetaEngine::getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts, bool stringEscapeCR, int component) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { switch (it->typeId()) { case Exiv2::unsignedByte: case Exiv2::unsignedShort: case Exiv2::unsignedLong: case Exiv2::signedShort: case Exiv2::signedLong: { if ((int)it->count() > component) + { return QVariant((int)it->toLong(component)); + } else + { return QVariant(QVariant::Int); + } } case Exiv2::unsignedRational: case Exiv2::signedRational: { if (rationalAsListOfInts) { if ((int)it->count() <= component) + { return QVariant(QVariant::List); + } QList list; list << (*it).toRational(component).first; list << (*it).toRational(component).second; return QVariant(list); } else { if ((int)it->count() <= component) + { return QVariant(QVariant::Double); + } // prefer double precision + double num = (*it).toRational(component).first; double den = (*it).toRational(component).second; if (den == 0.0) + { return QVariant(QVariant::Double); + } return QVariant(num / den); } } case Exiv2::date: case Exiv2::time: { QDateTime dateTime = QDateTime::fromString(QLatin1String(it->toString().c_str()), Qt::ISODate); return QVariant(dateTime); } case Exiv2::asciiString: case Exiv2::comment: case Exiv2::string: { std::ostringstream os; it->write(os, &exifData); QString tagValue = QString::fromLocal8Bit(os.str().c_str()); if (stringEscapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return QVariant(tagValue); } default: break; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' in the image using Exiv2 ") .arg(QLatin1String(exifTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QVariant(); } QString MetaEngine::getExifTagString(const char* exifTagName, bool escapeCR) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { QString tagValue; QString key = QLatin1String(it->key().c_str()); - if (key == QLatin1String("Exif.CanonCs.LensType") && it->toLong() == 65535) + if ((key == QLatin1String("Exif.CanonCs.LensType")) && (it->toLong() == 65535)) { // FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType. + tagValue = QString::fromLocal8Bit(it->toString().c_str()); } else { // See BUG #184156 comment #13 + std::string val = it->print(&exifData); tagValue = QString::fromLocal8Bit(val.c_str()); } if (escapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return tagValue; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ") .arg(QLatin1String(exifTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } bool MetaEngine::setExifTagString(const char* exifTagName, const QString& value) const { QMutexLocker lock(&s_metaEngineMutex); try { d->exifMetadata()[exifTagName] = std::string(value.toLatin1().constData()); return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag string into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } QImage MetaEngine::getExifThumbnail(bool fixOrientation) const { QImage thumbnail; if (d->exifMetadata().empty()) + { return thumbnail; + } QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifThumbC thumb(d->exifMetadata()); Exiv2::DataBuf const c1 = thumb.copy(); thumbnail.loadFromData(c1.pData_, c1.size_); if (!thumbnail.isNull()) { if (fixOrientation) { Exiv2::ExifKey key1("Exif.Thumbnail.Orientation"); Exiv2::ExifKey key2("Exif.Image.Orientation"); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::const_iterator it = exifData.findKey(key1); if (it == exifData.end()) { it = exifData.findKey(key2); } if (it != exifData.end() && it->count()) { long orientation = it->toLong(); + //qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif Thumbnail Orientation: " << (int)orientation; + rotateExifQImage(thumbnail, (ImageOrientation)orientation); } return thumbnail; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return thumbnail; } bool MetaEngine::rotateExifQImage(QImage& image, ImageOrientation orientation) const { QMatrix matrix = MetaEngineRotation::toMatrix(orientation); if ((orientation != ORIENTATION_NORMAL) && (orientation != ORIENTATION_UNSPECIFIED)) { image = image.transformed(matrix); return true; } return false; } bool MetaEngine::setExifThumbnail(const QImage& thumbImage) const { if (thumbImage.isNull()) { return removeExifThumbnail(); } QMutexLocker lock(&s_metaEngineMutex); try { QByteArray data; QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); thumbImage.save(&buffer, "JPEG"); buffer.close(); Exiv2::ExifThumb thumb(d->exifMetadata()); thumb.setJpegThumbnail((Exiv2::byte*)data.data(), data.size()); return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::setTiffThumbnail(const QImage& thumbImage) const { removeExifThumbnail(); QMutexLocker lock(&s_metaEngineMutex); try { // Make sure IFD0 is explicitly marked as a main image + Exiv2::ExifData::const_iterator pos = d->exifMetadata().findKey(Exiv2::ExifKey("Exif.Image.NewSubfileType")); if (pos == d->exifMetadata().end() || pos->count() != 1 || pos->toLong() != 0) { #if EXIV2_TEST_VERSION(0,27,0) throw Exiv2::Error(Exiv2::kerErrorMessage, "Exif.Image.NewSubfileType missing or not set as main image"); #else throw Exiv2::Error(1, "Exif.Image.NewSubfileType missing or not set as main image"); #endif } // Remove sub-IFD tags + std::string subImage1("SubImage1"); for (Exiv2::ExifData::iterator md = d->exifMetadata().begin() ; md != d->exifMetadata().end() ;) { if (md->groupName() == subImage1) { md = d->exifMetadata().erase(md); } else { ++md; } } if (!thumbImage.isNull()) { // Set thumbnail tags + QByteArray data; QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); thumbImage.save(&buffer, "JPEG"); buffer.close(); Exiv2::DataBuf buf((Exiv2::byte*)data.data(), data.size()); Exiv2::ULongValue val; val.read("0"); val.setDataArea(buf.pData_, buf.size_); d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormat"] = val; d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormatLength"] = uint32_t(buf.size_); d->exifMetadata()["Exif.SubImage1.Compression"] = uint16_t(6); // JPEG (old-style) d->exifMetadata()["Exif.SubImage1.NewSubfileType"] = uint32_t(1); // Thumbnail image return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set TIFF Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } bool MetaEngine::removeExifThumbnail() const { QMutexLocker lock(&s_metaEngineMutex); try { // Remove all IFD0 subimages. Exiv2::ExifThumb thumb(d->exifMetadata()); thumb.erase(); return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } MetaEngine::MetaDataMap MetaEngine::getExifTagsDataList(const QStringList& exifKeysFilter, bool invertSelection) const { if (d->exifMetadata().empty()) return MetaDataMap(); QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::ExifData exifData = d->exifMetadata(); exifData.sortByKey(); QString ifDItemName; MetaDataMap metaDataMap; for (Exiv2::ExifData::const_iterator md = exifData.begin() ; md != exifData.end() ; ++md) { QString key = QLatin1String(md->key().c_str()); // Decode the tag value with a user friendly output. + QString tagValue; - if (key == QLatin1String("Exif.Photo.UserComment")) + if (key == QLatin1String("Exif.Photo.UserComment")) { tagValue = d->convertCommentValue(*md); } else if (key == QLatin1String("Exif.Image.0x935c")) { tagValue = QString::number(md->value().size()); } else if (key == QLatin1String("Exif.CanonCs.LensType") && md->toLong() == 65535) { // FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType. tagValue = QString::fromLocal8Bit(md->toString().c_str()); } else { std::ostringstream os; md->write(os, &exifData); // Exif tag contents can be an translated strings, no only simple ascii. tagValue = QString::fromLocal8Bit(os.str().c_str()); } tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); // We apply a filter to get only the Exif tags that we need. if (!exifKeysFilter.isEmpty()) { if (!invertSelection) { if (exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) + { metaDataMap.insert(key, tagValue); + } } else { if (!exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) + { metaDataMap.insert(key, tagValue); + } } } else // else no filter at all. { metaDataMap.insert(key, tagValue); } } return metaDataMap; } catch (Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot parse EXIF metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return MetaDataMap(); } MetaEngine::TagsMap MetaEngine::getStdExifTagsList() const { QMutexLocker lock(&s_metaEngineMutex); try { QList tags; TagsMap tagsMap; const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); while (gi->tagList_ != nullptr) { // NOTE: See BUG #375809 : MPF tags = exception Exiv2 0.26 if (QLatin1String(gi->ifdName_) != QLatin1String("Makernote")) { Exiv2::TagListFct tl = gi->tagList_; const Exiv2::TagInfo* ti = tl(); while (ti->tag_ != 0xFFFF) { tags << ti; ++ti; } } ++gi; } for (QList::iterator it = tags.begin() ; it != tags.end() ; ++it) { do { const Exiv2::TagInfo* const ti = *it; if (ti) { QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); QStringList values; values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_); tagsMap.insert(key, values); } ++(*it); } while((*it)->tag_ != 0xffff); } return tagsMap; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Tags list using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return TagsMap(); } MetaEngine::TagsMap MetaEngine::getMakernoteTagsList() const { QMutexLocker lock(&s_metaEngineMutex); try { QList tags; TagsMap tagsMap; const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); while (gi->tagList_ != nullptr) { if (QLatin1String(gi->ifdName_) == QLatin1String("Makernote")) { Exiv2::TagListFct tl = gi->tagList_; const Exiv2::TagInfo* ti = tl(); while (ti->tag_ != 0xFFFF) { tags << ti; ++ti; } } ++gi; } for (QList::iterator it = tags.begin() ; it != tags.end() ; ++it) { do { const Exiv2::TagInfo* const ti = *it; if (ti) { QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); QStringList values; values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_); tagsMap.insert(key, values); } ++(*it); } - while((*it)->tag_ != 0xffff); + while ((*it)->tag_ != 0xffff); } return tagsMap; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get Makernote Tags list using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return TagsMap(); } } // namespace Digikam #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif