diff --git a/libs/database/utils/dio.cpp b/libs/database/utils/dio.cpp index eb1ce219d3..2faa0d8f86 100644 --- a/libs/database/utils/dio.cpp +++ b/libs/database/utils/dio.cpp @@ -1,545 +1,546 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg * 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 "dio.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "imageinfo.h" #include "albummanager.h" #include "coredb.h" #include "coredbaccess.h" #include "album.h" #include "dmetadata.h" #include "loadingcacheinterface.h" #include "metadatasettings.h" #include "scancontroller.h" #include "thumbnailloadthread.h" #include "iojobsmanager.h" #include "collectionmanager.h" #include "dnotificationwrapper.h" #include "progressmanager.h" #include "digikamapp.h" #include "iojobdata.h" namespace Digikam { SidecarFinder::SidecarFinder(const QList& files) { process(files); } SidecarFinder::SidecarFinder(const QUrl& file) { process(QList() << file); } void SidecarFinder::process(const QList& files) { foreach(const QUrl& url, files) { if (DMetadata::hasSidecar(url.toLocalFile())) { localFiles << DMetadata::sidecarUrl(url); localFileSuffixes << QLatin1String(".xmp"); qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } foreach(QString suffix, MetadataSettings::instance()->settings().sidecarExtensions) { suffix = QLatin1String(".") + suffix; QString sidecarName = url.toLocalFile() + suffix; if (QFileInfo::exists(sidecarName) && !localFiles.contains(QUrl::fromLocalFile(sidecarName))) { localFiles << QUrl::fromLocalFile(sidecarName); localFileSuffixes << suffix; qCDebug(DIGIKAM_DATABASE_LOG) << "Detected a sidecar" << localFiles.last(); } } localFiles << url; localFileSuffixes << QString(); } } // ------------------------------------------------------------------------------------------------ // TODO // Groups should not be resolved in dio, it should be handled in views. // This is already done for most things except for drag&drop, which is hard :) GroupedImagesFinder::GroupedImagesFinder(const QList& source) { process(source); } void GroupedImagesFinder::process(const QList& source) { QSet ids; foreach(const ImageInfo& info, source) { ids << info.id(); } infos.reserve(source.size()); foreach(const ImageInfo& info, source) { infos << info; if (info.hasGroupedImages()) { foreach(const ImageInfo& groupedImage, info.groupedImages()) { if (ids.contains(groupedImage.id())) { continue; } infos << groupedImage; ids << groupedImage.id(); } } } } // ------------------------------------------------------------------------------------------------ class DIOCreator { public: DIO object; }; Q_GLOBAL_STATIC(DIOCreator, creator) // ------------------------------------------------------------------------------------------------ DIO* DIO::instance() { return &creator->object; } DIO::DIO() { } DIO::~DIO() { } void DIO::cleanUp() { } // Album -> Album ----------------------------------------------------- void DIO::copy(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest)); } void DIO::move(PAlbum* const src, PAlbum* const dest) { if (!src || !dest) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(src); #endif instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest)); } // Images -> Album ---------------------------------------------------- void DIO::copy(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest)); } void DIO::move(const QList& infos, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest)); } // External files -> album -------------------------------------------- void DIO::copy(const QUrl& src, PAlbum* const dest) { copy(QList() << src, dest); } void DIO::copy(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest)); } void DIO::move(const QUrl& src, PAlbum* const dest) { move(QList() << src, dest); } void DIO::move(const QList& srcList, PAlbum* const dest) { if (!dest) { return; } instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest)); } // Rename -------------------------------------------------------------- void DIO::rename(const ImageInfo& info, const QString& newName) { instance()->processJob(new IOJobData(IOJobData::Rename, info, newName)); } // Delete -------------------------------------------------------------- void DIO::del(const QList& infos, bool useTrash) { instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, infos)); } void DIO::del(const ImageInfo& info, bool useTrash) { del(QList() << info, useTrash); } void DIO::del(PAlbum* const album, bool useTrash) { if (!album) { return; } #ifdef Q_OS_WIN AlbumManager::instance()->removeWatchedPAlbums(album); #endif instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash : IOJobData::Delete, QList() << album->fileUrl())); } // ------------------------------------------------------------------------------------------------ void DIO::processJob(IOJobData* const data) { const int operation = data->operation(); if (operation == IOJobData::CopyImage || operation == IOJobData::MoveImage) { // this is a fast db operation, do here GroupedImagesFinder finder(data->imageInfos()); data->setImageInfos(finder.infos); QStringList filenames; QList ids; foreach(const ImageInfo& info, data->imageInfos()) { filenames << info.name(); ids << info.id(); } ScanController::instance()->hintAtMoveOrCopyOfItems(ids, data->destAlbum(), filenames); } else if (operation == IOJobData::CopyAlbum || operation == IOJobData::MoveAlbum) { ScanController::instance()->hintAtMoveOrCopyOfAlbum(data->srcAlbum(), data->destAlbum()); createJob(data); return; } else if (operation == IOJobData::Delete || operation == IOJobData::Trash) { qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count(); } else if (operation == IOJobData::Rename) { PAlbum* const album = AlbumManager::instance()->findPAlbum(data->imageInfo().albumId()); if (album) { ScanController::instance()->hintAtMoveOrCopyOfItem(data->imageInfo().id(), album, data->destUrl().fileName()); } SidecarFinder finder(data->srcUrl()); data->setSourceUrls(finder.localFiles); for (int i = 0 ; i < finder.localFiles.length() ; ++i) { data->setDestUrl(finder.localFiles.at(i), QUrl::fromLocalFile(data->destUrl().toLocalFile() + finder.localFileSuffixes.at(i))); } createJob(data); return; } SidecarFinder finder(data->sourceUrls()); data->setSourceUrls(finder.localFiles); createJob(data); } void DIO::createJob(IOJobData* const data) { if (data->sourceUrls().isEmpty()) { delete data; return; } ProgressItem* item = 0; IOJobsThread* jobThread = 0; const int operation = data->operation(); if (operation == IOJobData::CopyAlbum || operation == IOJobData::CopyImage || operation == IOJobData::CopyFiles) { item = getProgressItem(operation); if (!item || item->totalCompleted()) { item = ProgressManager::instance()->createProgressItem(QLatin1String("DIOCopy"), i18n("Copy"), QString(), true, false); } item->setTotalItems(item->totalItems() + data->sourceUrls().count()); jobThread = IOJobsManager::instance()->startCopy(data); } else if (operation == IOJobData::MoveAlbum || operation == IOJobData::MoveImage || operation == IOJobData::MoveFiles) { item = getProgressItem(operation); if (!item || item->totalCompleted()) { item = ProgressManager::instance()->createProgressItem(QLatin1String("DIOMove"), i18n("Move"), QString(), true, false); } item->setTotalItems(item->totalItems() + data->sourceUrls().count()); jobThread = IOJobsManager::instance()->startMove(data); } else if (operation == IOJobData::Rename) { jobThread = IOJobsManager::instance()->startRenameFile(data); connect(jobThread, SIGNAL(signalRenamed(QUrl)), this, SIGNAL(signalRenameSucceeded(QUrl))); connect(jobThread, SIGNAL(signalRenameFailed(QUrl)), this, SIGNAL(signalRenameFailed(QUrl))); } else if (operation == IOJobData::Delete || operation == IOJobData::DFiles) { item = getProgressItem(operation); if (!item || item->totalCompleted()) { item = ProgressManager::instance()->createProgressItem(QLatin1String("DIODelete"), i18n("Delete"), QString(), true, false); } item->setTotalItems(item->totalItems() + data->sourceUrls().count()); jobThread = IOJobsManager::instance()->startDelete(data); } else if (operation == IOJobData::Trash) { item = getProgressItem(operation); if (!item || item->totalCompleted()) { item = ProgressManager::instance()->createProgressItem(QLatin1String("DIOTrash"), i18n("Trash"), QString(), true, false); } item->setTotalItems(item->totalItems() + data->sourceUrls().count()); jobThread = IOJobsManager::instance()->startDelete(data); } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Unknown IOJob operation:" << operation; return; } connect(jobThread, SIGNAL(signalOneProccessed(int)), this, SLOT(slotOneProccessed(int))); connect(jobThread, SIGNAL(finished()), this, SLOT(slotResult())); if (item) { connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), jobThread, SLOT(slotCancel())); connect(item, SIGNAL(progressItemCanceled(ProgressItem*)), this, SLOT(slotCancel(ProgressItem*))); } } void DIO::slotResult() { IOJobsThread* const jobThread = dynamic_cast(sender()); if (!jobThread || !jobThread->jobData()) { return; } IOJobData* const data = jobThread->jobData(); const int operation = data->operation(); if (operation == IOJobData::MoveImage) { // update the image infos CoreDbAccess access; foreach(const ImageInfo& info, data->imageInfos()) { if (data->processedUrls().contains(info.fileUrl())) { access.db()->moveItem(info.albumId(), info.name(), data->destAlbum()->id(), info.name()); } } } else if (operation == IOJobData::Rename) { // If we rename a file, the name changes. This is equivalent to a move. // Do this in database, too. if (data->processedUrls().contains(data->srcUrl())) { CoreDbAccess().db()->moveItem(data->imageInfo().albumId(), data->srcUrl().fileName(), data->imageInfo().albumId(), data->destUrl(data->srcUrl()).fileName()); // delete thumbnail ThumbnailLoadThread::deleteThumbnail(data->srcUrl().toLocalFile()); LoadingCacheInterface::fileChanged(data->destUrl(data->srcUrl()).toLocalFile()); } } if (jobThread->hasErrors() && operation != IOJobData::Rename) { // Pop-up a message about the error. QString errors = QStringList(jobThread->errorsList()).join(QLatin1String("\n")); DNotificationWrapper(QString(), errors, DigikamApp::instance(), DigikamApp::instance()->windowTitle()); } ProgressItem* const item = getProgressItem(operation); slotCancel(item); } void DIO::slotCancel(ProgressItem* item) { if (item) { item->setComplete(); } } void DIO::slotOneProccessed(int operation) { ProgressItem* const item = getProgressItem(operation); if (item) { item->advance(1); } } ProgressItem* DIO::getProgressItem(int operation) { ProgressItem* item = 0; switch (operation) { case IOJobData::CopyAlbum: case IOJobData::CopyImage: case IOJobData::CopyFiles: item = ProgressManager::instance()->findItembyId(QLatin1String("DIOCopy")); break; case IOJobData::MoveAlbum: case IOJobData::MoveImage: case IOJobData::MoveFiles: item = ProgressManager::instance()->findItembyId(QLatin1String("DIOMove")); break; case IOJobData::Trash: item = ProgressManager::instance()->findItembyId(QLatin1String("DIOTrash")); break; case IOJobData::Delete: item = ProgressManager::instance()->findItembyId(QLatin1String("DIODelete")); break; default: break; } return item; } } // namespace Digikam diff --git a/libs/database/utils/dio.h b/libs/database/utils/dio.h index 4c0f978d0a..fb1c53f8ba 100644 --- a/libs/database/utils/dio.h +++ b/libs/database/utils/dio.h @@ -1,152 +1,153 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-05-17 * Description : low level files management interface. * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2012-2013 by Marcel Wiesweg + * 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. * * ============================================================ */ #ifndef _DIGIKAM_IO_H_ #define _DIGIKAM_IO_H_ // Qt includes #include // Local includes #include "digikam_export.h" class QUrl; namespace Digikam { class PAlbum; class ImageInfo; class IOJobData; class ProgressItem; class DIGIKAM_EXPORT DIO : public QObject { Q_OBJECT public: static void cleanUp(); /** * All DIO methods will take care for sidecar files, if they exist */ /// Copy an album to another album static void copy(PAlbum* const src, PAlbum* const dest); /// Copy items to another album static void copy(const QList& infos, PAlbum* const dest); /// Copy an external file to another album static void copy(const QUrl& src, PAlbum* const dest); /// Copy external files to another album static void copy(const QList& srcList, PAlbum* const dest); /// Move an album into another album static void move(PAlbum* const src, PAlbum* const dest); /// Move items to another album static void move(const QList& infos, PAlbum* const dest); /// Move external files another album static void move(const QUrl& src, PAlbum* const dest); /// Move external files into another album static void move(const QList& srcList, PAlbum* const dest); static void del(const QList& infos, bool useTrash); static void del(const ImageInfo& info, bool useTrash); static void del(PAlbum* const album, bool useTrash); /// Rename item to new name static void rename(const ImageInfo& info, const QString& newName); static DIO* instance(); Q_SIGNALS: void signalRenameSucceeded(const QUrl&); void signalRenameFailed(const QUrl&); private: DIO(); ~DIO(); void processJob(IOJobData* const data); void createJob(IOJobData* const data); ProgressItem* getProgressItem(int operation); private Q_SLOTS: void slotResult(); void slotCancel(ProgressItem* item); void slotOneProccessed(int operation); private: friend class DIOCreator; }; // ----------------------------------------------------------------------------------------- class SidecarFinder { public: explicit SidecarFinder(const QList& files); explicit SidecarFinder(const QUrl& file); QList localFiles; QList localFileSuffixes; private: void process(const QList&); }; // ----------------------------------------------------------------------------------------- class GroupedImagesFinder { public: explicit GroupedImagesFinder(const QList& source); QList infos; private: void process(const QList& source); }; } // namespace Digikam #endif // _DIGIKAM_IO_H_ diff --git a/libs/iojobs/iojob.cpp b/libs/iojobs/iojob.cpp index b0349c534c..6193c3aac8 100644 --- a/libs/iojobs/iojob.cpp +++ b/libs/iojobs/iojob.cpp @@ -1,490 +1,491 @@ /* ============================================================ * * This file is a part of digiKam project * http://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 "dtrash.h" #include "coredb.h" #include "coredbaccess.h" #include "collectionmanager.h" #include "albummanager.h" #include "dfileoperations.h" namespace Digikam { IOJob::IOJob() { } // -------------------------------------------- CopyJob::CopyJob(IOJobData* const data) { m_data = data; } void CopyJob::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 error(i18n("File/Folder %1 does not exist anymore", srcInfo.baseName())); emit signalOneProccessed(m_data->operation()); continue; } if (!dstDir.exists()) { emit error(i18n("Album %1 does not exist anymore", dstDir.dirName())); emit signalOneProccessed(m_data->operation()); 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; QFileInfo fileInfoForDestination(destenation); if (fileInfoForDestination.exists()) { emit error(i18n("A file or folder named %1 already exists in %2", srcInfo.baseName(), QDir::toNativeSeparators(dstDir.path()))); emit signalOneProccessed(m_data->operation()); 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())) { emit error(i18n("Could not move folder %1 to album %2", QDir::toNativeSeparators(srcDir.path()), QDir::toNativeSeparators(dstDir.path()))); emit signalOneProccessed(m_data->operation()); continue; } else if (!srcDir.removeRecursively()) { emit error(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 { QFile srcFile(srcInfo.filePath()); if (!srcFile.rename(destenation)) { emit error(i18n("Could not move file %1 to album %2", srcInfo.filePath(), QDir::toNativeSeparators(dstDir.path()))); emit signalOneProccessed(m_data->operation()); continue; } } } else { if (srcInfo.isDir()) { QDir srcDir(srcInfo.filePath()); if (!DFileOperations::copyFolderRecursively(srcDir.path(), dstDir.path())) { emit error(i18n("Could not copy folder %1 to album %2", QDir::toNativeSeparators(srcDir.path()), QDir::toNativeSeparators(dstDir.path()))); emit signalOneProccessed(m_data->operation()); continue; } } else { if (!QFile::copy(srcInfo.filePath(), destenation)) { emit error(i18n("Could not copy file %1 to album %2", QDir::toNativeSeparators(srcInfo.path()), QDir::toNativeSeparators(dstDir.path()))); emit signalOneProccessed(m_data->operation()); continue; } } } emit signalOneProccessed(m_data->operation()); m_data->addProcessedUrl(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 error(i18n("File/Folder %1 does not exist", QDir::toNativeSeparators(fileInfo.filePath()))); emit signalOneProccessed(m_data->operation()); continue; } if (useTrash) { if (fileInfo.isDir()) { if (!DTrash::deleteDirRecursivley(deleteUrl.toLocalFile())) { emit error(i18n("Couldn't move folder %1 to collection trash", QDir::toNativeSeparators(fileInfo.path()))); emit signalOneProccessed(m_data->operation()); continue; } } else { if (!DTrash::deleteImage(deleteUrl.toLocalFile())) { emit error(i18n("Couldn't move image %1 to collection trash", QDir::toNativeSeparators(fileInfo.filePath()))); emit signalOneProccessed(m_data->operation()); continue; } } } else { if (fileInfo.isDir()) { QDir dir(fileInfo.filePath()); if (!dir.removeRecursively()) { emit error(i18n("Album %1 could not be removed", QDir::toNativeSeparators(fileInfo.path()))); emit signalOneProccessed(m_data->operation()); continue; } if (m_data->operation() == IOJobData::Delete) { CoreDbAccess access; // If the images in the directory should be marked as obsolete // get all files recursively and remove all image information // for which the file path leads to an image id. QList imageIds; QDirIterator iter(dir); while (iter.hasNext()) { // get the next path and advance the iterator QString filePath = iter.next(); // Use the file info to get the file type QFileInfo info = iter.fileInfo(); if (info.isFile()) { qlonglong imageId = getItemFromUrl(QUrl::fromLocalFile(filePath)); if (imageId != -1) { imageIds << imageId; } } } // Mark all image ids as obsolete. foreach(qlonglong imageId, imageIds) { access.db()->setItemStatus(imageId, DatabaseItem::Status::Obsolete); } } } else { QFile file(fileInfo.filePath()); if (!file.remove()) { emit error(i18n("Image %1 could not be removed", QDir::toNativeSeparators(fileInfo.filePath()))); emit signalOneProccessed(m_data->operation()); continue; } if (m_data->operation() == IOJobData::Delete) { CoreDbAccess access; // Mark the image info of the removed file as obsolete qlonglong imageId = getItemFromUrl(QUrl::fromLocalFile(fileInfo.filePath())); if (imageId != -1) { access.db()->setItemStatus(imageId, DatabaseItem::Status::Obsolete); } } } } emit signalOneProccessed(m_data->operation()); m_data->addProcessedUrl(deleteUrl); } emit signalDone(); } qlonglong DeleteJob::getItemFromUrl(const QUrl& url) { QString fileName = url.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 = url.toLocalFile(); QString albumPath = CollectionManager::instance()->album(completePath); 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 = CoreDbAccess().db()->getItemFromAlbum(pAlbum->id(), fileName); } return imageId; } // -------------------------------------------- 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); qCDebug(DIGIKAM_IOJOB_LOG) << "Destination Url:" << destUrl; if (QFileInfo(destUrl.toLocalFile()).exists()) { qCDebug(DIGIKAM_IOJOB_LOG) << "File with the same name exists!"; emit error(i18n("Image with the same name %1 already there", QDir::toNativeSeparators(destUrl.toLocalFile()))); emit signalOneProccessed(m_data->operation()); emit signalRenameFailed(renameUrl); continue; } QFile file(renameUrl.toLocalFile()); qCDebug(DIGIKAM_IOJOB_LOG) << "Trying to rename" << renameUrl.toLocalFile() << "to" << destUrl.toLocalFile(); if (!file.rename(destUrl.toLocalFile())) { qCDebug(DIGIKAM_IOJOB_LOG) << "File couldn't be renamed!"; emit error(i18n("Image %1 could not be renamed", QDir::toNativeSeparators(renameUrl.toLocalFile()))); emit signalOneProccessed(m_data->operation()); emit signalRenameFailed(renameUrl); continue; } emit signalOneProccessed(m_data->operation()); m_data->addProcessedUrl(renameUrl); emit signalRenamed(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 couldn't be renamed!"; } else { QFile::remove(item.jsonFilePath); } } emit signalDone(); } // ---------------------------------------------- DeleteDTrashItemsJob::DeleteDTrashItemsJob(const DTrashItemInfoList& infos) { m_dtrashItemInfoList = infos; } void DeleteDTrashItemsJob::run() { CoreDbAccess access; foreach (const DTrashItemInfo& item, m_dtrashItemInfoList) { QFile::remove(item.trashPath); QFile::remove(item.jsonFilePath); // Set the status of the image id to obsolete, i.e. to remove. access.db()->setItemStatus(item.imageId, DatabaseItem::Status::Obsolete); } emit signalDone(); } } // namespace Digikam diff --git a/libs/iojobs/iojob.h b/libs/iojobs/iojob.h index 9f669b7ced..839742cb70 100644 --- a/libs/iojobs/iojob.h +++ b/libs/iojobs/iojob.h @@ -1,186 +1,187 @@ /* ============================================================ * * This file is a part of digiKam project * http://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. * * ============================================================ */ #ifndef IOJOB_H #define IOJOB_H // Qt includes #include // Local includes #include "actionthreadbase.h" #include "dtrashiteminfo.h" #include "digikam_export.h" #include "iojobdata.h" namespace Digikam { class ImageInfo; class DIGIKAM_EXPORT IOJob : public ActionJob { Q_OBJECT protected: IOJob(); Q_SIGNALS: void signalOneProccessed(int operation); void error(const QString& errMsg); }; // --------------------------------------- class DIGIKAM_EXPORT CopyJob : public IOJob { Q_OBJECT public: CopyJob(IOJobData* const data); protected: void run(); private: IOJobData* m_data; }; // --------------------------------------- class DIGIKAM_EXPORT DeleteJob : public IOJob { Q_OBJECT public: DeleteJob(IOJobData* const data); protected: void run(); private: qlonglong getItemFromUrl(const QUrl& url); private: IOJobData* m_data; }; // --------------------------------------- class DIGIKAM_EXPORT RenameFileJob : public IOJob { Q_OBJECT public: RenameFileJob(IOJobData* const data); Q_SIGNALS: void signalRenamed(const QUrl& oldUrl); void signalRenameFailed(const QUrl& oldUrl); protected: void run(); private: IOJobData* m_data; }; // ---------------------------------------------- class DIGIKAM_EXPORT DTrashItemsListingJob : public IOJob { Q_OBJECT public: DTrashItemsListingJob(const QString& collectionPath); Q_SIGNALS: void trashItemInfo(const DTrashItemInfo& info); protected: void run(); private: QString m_collectionPath; }; // ---------------------------------------------- class DIGIKAM_EXPORT RestoreDTrashItemsJob : public IOJob { Q_OBJECT public: RestoreDTrashItemsJob(const DTrashItemInfoList& infos); protected: void run(); private: DTrashItemInfoList m_dtrashItemInfoList; }; // ---------------------------------------------- class DIGIKAM_EXPORT DeleteDTrashItemsJob : public IOJob { Q_OBJECT public: DeleteDTrashItemsJob(const DTrashItemInfoList& infos); protected: void run(); private: DTrashItemInfoList m_dtrashItemInfoList; }; } // namespace Digikam #endif // IOJOB_H diff --git a/libs/iojobs/iojobsmanager.cpp b/libs/iojobs/iojobsmanager.cpp index c906afa9ba..b77c5b6ca9 100644 --- a/libs/iojobs/iojobsmanager.cpp +++ b/libs/iojobs/iojobsmanager.cpp @@ -1,151 +1,152 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-15 * Description : Manager for creating and starting IO jobs threads * * 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 "iojobsmanager.h" // Local includes #include "iojobdata.h" namespace Digikam { class IOJobsManagerCreator { public: IOJobsManager object; }; Q_GLOBAL_STATIC(IOJobsManagerCreator, creator) // ---------------------------------------------- IOJobsManager::IOJobsManager() { } IOJobsManager* IOJobsManager::instance() { return& creator->object; } IOJobsThread* IOJobsManager::startCopy(IOJobData* const data) { IOJobsThread* const thread = new IOJobsThread(this); thread->copy(data); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread* IOJobsManager::startMove(IOJobData* const data) { IOJobsThread* const thread = new IOJobsThread(this); thread->move(data); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread* IOJobsManager::startDelete(IOJobData* const data) { IOJobsThread* const thread = new IOJobsThread(this); thread->deleteFiles(data); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread* IOJobsManager::startRenameFile(IOJobData* const data) { IOJobsThread* const thread = new IOJobsThread(this); thread->renameFile(data); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread *IOJobsManager::startDTrashItemsListingForCollection(const QString& collectionPath) { IOJobsThread* const thread = new IOJobsThread(this); thread->listDTrashItems(collectionPath); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread* IOJobsManager::startRestoringDTrashItems(const DTrashItemInfoList& trashItemsList) { IOJobsThread* const thread = new IOJobsThread(this); thread->restoreDTrashItems(trashItemsList); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } IOJobsThread* IOJobsManager::startDeletingDTrashItems(const DTrashItemInfoList& trashItemsList) { IOJobsThread* const thread = new IOJobsThread(this); thread->deleteDTrashItems(trashItemsList); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()), Qt::QueuedConnection); thread->start(); return thread; } } // namespace Digikam diff --git a/libs/iojobs/iojobsmanager.h b/libs/iojobs/iojobsmanager.h index 9ad72f9727..9d7672b26c 100644 --- a/libs/iojobs/iojobsmanager.h +++ b/libs/iojobs/iojobsmanager.h @@ -1,111 +1,112 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-15 * Description : Manager for creating and starting IO jobs threads * * 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. * * ============================================================ */ #ifndef IOJOBSMANAGER_H #define IOJOBSMANAGER_H // Qt includes #include #include // Local includes #include "digikam_export.h" #include "dtrashiteminfo.h" #include "iojobsthread.h" namespace Digikam { class IOJobData; class DIGIKAM_EXPORT IOJobsManager : public QObject { public: /** * @brief instance: returns the singleton of IO Jobs Manager * @return IOJobsManager global instance */ static IOJobsManager* instance(); /** * @brief startCopy: Starts a thread to copy items to destination * @param data: IOJobData container with source and destination url * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startCopy(IOJobData* const data); /** * @brief startMove: Starts a thread to move items to destination * @param data: IOJobData container with source and destination url * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startMove(IOJobData* const data); /** * @brief startDelete: Starts a thread to delete items * @param data: IOJobData container with source and destination url * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startDelete(IOJobData* const data); /** * @brief startRenameFile: Starts a thread to rename a single file * @param data: IOJobData container with source and destination url * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startRenameFile(IOJobData* const data); /** * @brief Starts a thread for listing items inside trash for specific collection * @param collectionPath: the path for collection to list items for it's trash * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startDTrashItemsListingForCollection(const QString& collectionPath); /** * @brief Starts a thread to restore mutiple trash items * @param trashItemsList: list of selected trash items to restore * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startRestoringDTrashItems(const DTrashItemInfoList& trashItemsList); /** * @brief Starts a thread to delete mutiple trash items * @param trashItemsList: list of selected trash items to delete * @return IOJobsThread pointer for signal/slot connection */ IOJobsThread* startDeletingDTrashItems(const DTrashItemInfoList& trashItemsList); private: friend class IOJobsManagerCreator; IOJobsManager(); }; } // namespace Digikam #endif // FILESYSTEMJOBSMANAGER_H diff --git a/libs/iojobs/iojobsthread.cpp b/libs/iojobs/iojobsthread.cpp index e4dc8706ef..9c10551bdd 100644 --- a/libs/iojobs/iojobsthread.cpp +++ b/libs/iojobs/iojobsthread.cpp @@ -1,254 +1,255 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-15 * Description : IO Jobs thread for file system 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 "iojobsthread.h" // Qt includes #include #include // Local includes #include "iojob.h" #include "iojobdata.h" #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" namespace Digikam { class IOJobsThread::Private { public: Private() : jobsCount(0), isCanceled(false), jobData(0) { } int jobsCount; bool isCanceled; IOJobData* jobData; QList errorsList; }; IOJobsThread::IOJobsThread(QObject* const parent) : ActionThreadBase(parent), d(new Private) { } IOJobsThread::~IOJobsThread() { delete d->jobData; delete d; } void IOJobsThread::copy(IOJobData* const data) { d->jobData = data; ActionJobCollection collection; for (int i = 0; i < maximumNumberOfThreads(); i++) { CopyJob* const j = new CopyJob(data); connectOneJob(j); collection.insert(j, 0); d->jobsCount++; } appendJobs(collection); } void IOJobsThread::move(IOJobData* const data) { d->jobData = data; ActionJobCollection collection; for (int i = 0; i < maximumNumberOfThreads(); i++) { CopyJob* const j = new CopyJob(data); connectOneJob(j); collection.insert(j, 0); d->jobsCount++; } appendJobs(collection); } void IOJobsThread::deleteFiles(IOJobData* const data) { d->jobData = data; ActionJobCollection collection; for (int i = 0; i < maximumNumberOfThreads(); i++) { DeleteJob* const j = new DeleteJob(data); connectOneJob(j); collection.insert(j, 0); d->jobsCount++; } appendJobs(collection); } void IOJobsThread::renameFile(IOJobData* const data) { d->jobData = data; ActionJobCollection collection; RenameFileJob* const j = new RenameFileJob(data); connectOneJob(j); connect(j, SIGNAL(signalRenamed(QUrl)), this, SIGNAL(signalRenamed(QUrl))); connect(j, SIGNAL(signalRenameFailed(QUrl)), this, SIGNAL(signalRenameFailed(QUrl))); collection.insert(j, 0); d->jobsCount++; appendJobs(collection); } void IOJobsThread::listDTrashItems(const QString& collectionPath) { ActionJobCollection collection; DTrashItemsListingJob* const j = new DTrashItemsListingJob(collectionPath); connect(j, SIGNAL(trashItemInfo(DTrashItemInfo)), this, SIGNAL(collectionTrashItemInfo(DTrashItemInfo))); connect(j, SIGNAL(signalDone()), this, SIGNAL(finished())); collection.insert(j, 0); d->jobsCount++; appendJobs(collection); } void IOJobsThread::restoreDTrashItems(const DTrashItemInfoList& items) { ActionJobCollection collection; RestoreDTrashItemsJob* const j = new RestoreDTrashItemsJob(items); connect(j, SIGNAL(signalDone()), this, SIGNAL(finished())); collection.insert(j, 0); d->jobsCount++; appendJobs(collection); } void IOJobsThread::deleteDTrashItems(const DTrashItemInfoList& items) { ActionJobCollection collection; DeleteDTrashItemsJob* const j = new DeleteDTrashItemsJob(items); connect(j, SIGNAL(signalDone()), this, SIGNAL(finished())); collection.insert(j, 0); d->jobsCount++; appendJobs(collection); } bool IOJobsThread::isCanceled() { return d->isCanceled; } bool IOJobsThread::hasErrors() { return !d->errorsList.isEmpty(); } QList& IOJobsThread::errorsList() { return d->errorsList; } void IOJobsThread::connectOneJob(IOJob* const j) { connect(j, SIGNAL(error(QString)), this, SLOT(slotError(QString))); connect(j, SIGNAL(signalDone()), this, SLOT(slotOneJobFinished())); connect(j, SIGNAL(signalOneProccessed(int)), this, SIGNAL(signalOneProccessed(int))); } void IOJobsThread::slotOneJobFinished() { d->jobsCount--; if (d->jobsCount == 0) { emit finished(); qCDebug(DIGIKAM_IOJOB_LOG) << "Thread Finished"; } } void IOJobsThread::slotError(const QString& errString) { d->errorsList.append(errString); } void IOJobsThread::slotCancel() { d->isCanceled = true; ActionThreadBase::cancel(); } IOJobData* IOJobsThread::jobData() { return d->jobData; } } // namespace Digikam diff --git a/libs/iojobs/iojobsthread.h b/libs/iojobs/iojobsthread.h index 2b22d2b037..15651cf34d 100644 --- a/libs/iojobs/iojobsthread.h +++ b/libs/iojobs/iojobsthread.h @@ -1,161 +1,162 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2015-06-15 * Description : IO Jobs thread for file system 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. * * ============================================================ */ #ifndef IOJOBSTHREAD_H #define IOJOBSTHREAD_H // Local includes #include "actionthreadbase.h" #include "digikam_export.h" #include "dtrashiteminfo.h" namespace Digikam { class IOJob; class IOJobData; class DIGIKAM_EXPORT IOJobsThread : public ActionThreadBase { Q_OBJECT public: IOJobsThread(QObject* const parent); ~IOJobsThread(); /** * @brief Starts a number of jobs to copy source files to destination * @param data: IOJobsData container */ void copy(IOJobData* const data); /** * @brief Starts a number of jobs to move source files to destination * @param data: IOJobsData container */ void move(IOJobData* const data); /** * @brief Starts a number of jobs to delete multiple files * @param data: IOJobsData container */ void deleteFiles(IOJobData* const data); /** * @brief Starts one job to rename a file to a new name * @param data: IOJobsData container */ void renameFile(IOJobData* const data); /** * @brief Starts a job for listing trash items in a collection * @param collectionPath */ void listDTrashItems(const QString& collectionPath); /** * @brief creates a job for every item to restore back to album * @param items to restore */ void restoreDTrashItems(const DTrashItemInfoList& items); /** * @brief creates a job for every item to delete from collection trash * @param items to delete */ void deleteDTrashItems(const DTrashItemInfoList& items); /** * @brief isCanceled * @return true if the thread was inturrupted */ bool isCanceled(); /** * @brief hasErrors * @return true if string list was not empty */ bool hasErrors(); /** * @brief errorsList * @return */ QList& errorsList(); /** * @brief jobData * @return */ IOJobData* jobData(); public Q_SLOTS: /** * @brief cancels thread execution */ void slotCancel(); Q_SIGNALS: void finished(); void signalRenamed(const QUrl& oldUrl); void signalRenameFailed(const QUrl& oldUrl); void signalOneProccessed(int operation); void collectionTrashItemInfo(const DTrashItemInfo& trashItemInfo); private: /** * @brief connects the job with signals/slots * @param job to be connected */ void connectOneJob(IOJob* const j); private Q_SLOTS: /** * @brief connected to all active jobs and checks if the job * list has finished to report that thread is finished */ void slotOneJobFinished(); /** * @brief A slot to receive the error from the job * @param errString: string to be appended */ void slotError(const QString& errString); private: class Private; Private* const d; }; } // namespace Digikam #endif // IOJOBSTHREAD_H