File indexing completed on 2025-01-19 03:53:47

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-05-17
0007  * Description : low level files management interface.
0008  *
0009  * SPDX-FileCopyrightText: 2005      by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2012-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0011  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0012  * SPDX-FileCopyrightText: 2018      by Maik Qualmann <metzpinguin at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "dio.h"
0019 
0020 // Qt includes
0021 
0022 #include <QDir>
0023 #include <QFileInfo>
0024 #include <QMessageBox>
0025 #include <QMutexLocker>
0026 #include <QApplication>
0027 #include <QAbstractButton>
0028 
0029 // KDE includes
0030 
0031 #include <klocalizedstring.h>
0032 
0033 // Local includes
0034 
0035 #include "digikam_debug.h"
0036 #include "iteminfo.h"
0037 #include "diofinders.h"
0038 #include "albummanager.h"
0039 #include "coredb.h"
0040 #include "coredbaccess.h"
0041 #include "coredbtransaction.h"
0042 #include "album.h"
0043 #include "dmetadata.h"
0044 #include "metaenginesettings.h"
0045 #include "scancontroller.h"
0046 #include "itemscanner.h"
0047 #include "thumbsdb.h"
0048 #include "thumbsdbaccess.h"
0049 #include "iojobsmanager.h"
0050 #include "collectionmanager.h"
0051 #include "collectionlocation.h"
0052 #include "dnotificationwrapper.h"
0053 #include "loadingcacheinterface.h"
0054 #include "progressmanager.h"
0055 #include "digikamapp.h"
0056 #include "iojobdata.h"
0057 
0058 namespace Digikam
0059 {
0060 
0061 class Q_DECL_HIDDEN DIOCreator
0062 {
0063 public:
0064 
0065     DIO object;
0066 };
0067 
0068 Q_GLOBAL_STATIC(DIOCreator, creator)
0069 
0070 // ------------------------------------------------------------------------------------------------
0071 
0072 DIO* DIO::instance()
0073 {
0074     return &creator->object;
0075 }
0076 
0077 DIO::DIO()
0078 {
0079     qRegisterMetaType<QMap<QString,int>>("QMap<QString,int>");
0080 
0081     m_processingCount = 0;
0082 }
0083 
0084 DIO::~DIO()
0085 {
0086 }
0087 
0088 void DIO::cleanUp()
0089 {
0090 }
0091 
0092 bool DIO::itemsUnderProcessing()
0093 {
0094     return instance()->m_processingCount;
0095 }
0096 
0097 // Album -> Album -----------------------------------------------------
0098 
0099 void DIO::copy(PAlbum* const src, PAlbum* const dest)
0100 {
0101     if (!src || !dest)
0102     {
0103         return;
0104     }
0105 
0106     instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest));
0107 }
0108 
0109 void DIO::move(PAlbum* const src, PAlbum* const dest)
0110 {
0111     if (!src || !dest)
0112     {
0113         return;
0114     }
0115 
0116 #ifdef Q_OS_WIN
0117 
0118     AlbumManager::instance()->removeWatchedPAlbums(src);
0119 
0120 #endif
0121 
0122     instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest));
0123 }
0124 
0125 // Images -> Album ----------------------------------------------------
0126 
0127 void DIO::copy(const QList<ItemInfo>& infos, PAlbum* const dest)
0128 {
0129     if (!dest)
0130     {
0131         return;
0132     }
0133 
0134     instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest));
0135 }
0136 
0137 void DIO::move(const QList<ItemInfo>& infos, PAlbum* const dest)
0138 {
0139     if (!dest)
0140     {
0141         return;
0142     }
0143 
0144     instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest));
0145 }
0146 
0147 // External files -> album --------------------------------------------
0148 
0149 void DIO::copy(const QUrl& src, PAlbum* const dest)
0150 {
0151     copy(QList<QUrl>() << src, dest);
0152 }
0153 
0154 void DIO::copy(const QList<QUrl>& srcList, PAlbum* const dest)
0155 {
0156     if (!dest)
0157     {
0158         return;
0159     }
0160 
0161     instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest));
0162 }
0163 
0164 void DIO::move(const QUrl& src, PAlbum* const dest)
0165 {
0166     move(QList<QUrl>() << src, dest);
0167 }
0168 
0169 void DIO::move(const QList<QUrl>& srcList, PAlbum* const dest)
0170 {
0171     if (!dest)
0172     {
0173         return;
0174     }
0175 
0176     instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest));
0177 }
0178 
0179 // Images -> External -------------------------------------------------
0180 
0181 void DIO::copy(const QList<ItemInfo>& infos, const QUrl& dest)
0182 {
0183     instance()->processJob(new IOJobData(IOJobData::CopyToExt, infos, dest));
0184 }
0185 
0186 // Rename -------------------------------------------------------------
0187 
0188 void DIO::rename(const QUrl& src, const QString& newName, bool overwrite)
0189 {
0190     if (src.isEmpty() || newName.isEmpty())
0191     {
0192         return;
0193     }
0194 
0195     ItemInfo info = ItemInfo::fromUrl(src);
0196 
0197     instance()->processJob(new IOJobData(IOJobData::Rename, info, newName, overwrite));
0198 }
0199 
0200 // Delete -------------------------------------------------------------
0201 
0202 void DIO::del(const QList<ItemInfo>& infos, bool useTrash)
0203 {
0204     instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash
0205                                                   : IOJobData::Delete, infos));
0206 }
0207 
0208 void DIO::del(const ItemInfo& info, bool useTrash)
0209 {
0210     del(QList<ItemInfo>() << info, useTrash);
0211 }
0212 
0213 void DIO::del(PAlbum* const album, bool useTrash)
0214 {
0215     if (!album)
0216     {
0217         return;
0218     }
0219 
0220 #ifdef Q_OS_WIN
0221 
0222     AlbumManager::instance()->removeWatchedPAlbums(album);
0223 
0224 #endif
0225 
0226     instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash
0227                                                  : IOJobData::Delete, album));
0228 }
0229 
0230 // Get Trash Counter --------------------------------------------------
0231 
0232 int DIO::getTrashCounter(const QString& albumRootPath)
0233 {
0234     QUrl url = QUrl::fromLocalFile(albumRootPath);
0235     url      = url.adjusted(QUrl::StripTrailingSlash);
0236 
0237     QMutexLocker locker(&instance()->m_trashCounterMutex);
0238 
0239     return instance()->m_trashCounterMap.value(url.toLocalFile(), 0);
0240 }
0241 
0242 // Restore Trash ------------------------------------------------------
0243 
0244 void DIO::restoreTrash(const DTrashItemInfoList& infos)
0245 {
0246     instance()->createJob(new IOJobData(IOJobData::Restore, infos));
0247 }
0248 
0249 // Empty Trash --------------------------------------------------------
0250 
0251 void DIO::emptyTrash(const DTrashItemInfoList& infos)
0252 {
0253     instance()->createJob(new IOJobData(IOJobData::Empty, infos));
0254 }
0255 
0256 // Build Trash Counters -----------------------------------------------
0257 
0258 void DIO::buildCollectionTrashCounters()
0259 {
0260     IOJobsThread* const jobThread = IOJobsManager::instance()->buildCollectionTrashCounters();
0261 
0262     connect(jobThread, SIGNAL(signalTrashCountersMap(QMap<QString,int>)),
0263             instance(), SLOT(slotTrashCounterMap(QMap<QString,int>)));
0264 }
0265 
0266 // ------------------------------------------------------------------------------------------------
0267 
0268 void DIO::processJob(IOJobData* const data)
0269 {
0270     const int operation = data->operation();
0271 
0272     if      ((operation == IOJobData::CopyImage) ||
0273              (operation == IOJobData::MoveImage))
0274     {
0275         // this is a fast db operation, do here
0276 
0277         GroupedImagesFinder finder(data->itemInfos());
0278         data->setItemInfos(finder.infos);
0279     }
0280     else if ((operation == IOJobData::Trash) ||
0281              (operation == IOJobData::Delete))
0282     {
0283         SidecarFinder finder(data->sourceUrls());
0284         data->setSourceUrls(finder.localFiles);
0285 
0286         qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count();
0287     }
0288     else if (operation == IOJobData::Rename)
0289     {
0290         SidecarFinder finder(data->sourceUrls());
0291         data->setSourceUrls(finder.localFiles);
0292 
0293         for (int i = 0 ; i < finder.localFiles.size() ; ++i)
0294         {
0295             if (finder.localFileModes.at(i))
0296             {
0297                 data->setDestUrl(finder.localFiles.at(i),
0298                                  QUrl::fromLocalFile(data->destUrl().toLocalFile() +
0299                                                      finder.localFileSuffixes.at(i)));
0300             }
0301             else
0302             {
0303                 QFileInfo basInfo(data->destUrl().toLocalFile());
0304 
0305                 data->setDestUrl(finder.localFiles.at(i),
0306                                  QUrl::fromLocalFile(basInfo.path()             +
0307                                                      QLatin1Char('/')           +
0308                                                      basInfo.completeBaseName() +
0309                                                      finder.localFileSuffixes.at(i)));
0310             }
0311         }
0312     }
0313 
0314     createJob(data);
0315 }
0316 
0317 void DIO::createJob(IOJobData* const data)
0318 {
0319     if (data->sourceUrls().isEmpty())
0320     {
0321         delete data;
0322 
0323         return;
0324     }
0325 
0326     const int operation = data->operation();
0327 
0328     if ((operation == IOJobData::CopyImage) || (operation == IOJobData::CopyAlbum) ||
0329         (operation == IOJobData::CopyFiles) || (operation == IOJobData::CopyToExt) ||
0330         (operation == IOJobData::MoveImage) || (operation == IOJobData::MoveAlbum) ||
0331         (operation == IOJobData::MoveFiles))
0332     {
0333         CollectionLocation location         = CollectionManager::instance()->locationForUrl(data->destUrl());
0334         Qt::CaseSensitivity caseSensitivity = location.asQtCaseSensitivity();
0335         QDir dir(data->destUrl().toLocalFile());
0336 
0337         const QStringList& dirList          = dir.entryList(QDir::Dirs    |
0338                                                             QDir::Files   |
0339                                                             QDir::NoDotAndDotDot);
0340 
0341         Q_FOREACH (const QUrl& url, data->sourceUrls())
0342         {
0343             if (dirList.contains(url.adjusted(QUrl::StripTrailingSlash).fileName(), caseSensitivity))
0344             {
0345                 QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Warning,
0346                         i18nc("@title:window", "File Conflict"),
0347                         i18n("Files or folders with the same name already exist in the target folder.\n\n"
0348                              "What action is applied in the event of a file conflict?\n\n"
0349                              "Available options are:\n"
0350                              "Rename automatically: conflicting files will be renamed.\n"
0351                              "Overwrite automatically: conflicting files will be overwritten.\n"
0352                              "Skip automatically: conflicting files will be skipped."),
0353                         QMessageBox::Yes | QMessageBox::No | QMessageBox::Ok | QMessageBox::Cancel,
0354                         qApp->activeWindow());
0355 
0356                 msgBox->button(QMessageBox::Yes)->setText(i18nc("@action:button", "Rename Automatically"));
0357                 msgBox->button(QMessageBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("document-edit")));
0358                 msgBox->button(QMessageBox::No)->setText(i18nc("@action:button", "Overwrite Automatically"));
0359                 msgBox->button(QMessageBox::No)->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
0360                 msgBox->button(QMessageBox::Ok)->setText(i18nc("@action:button", "Skip Automatically"));
0361                 msgBox->button(QMessageBox::Ok)->setIcon(QIcon::fromTheme(QLatin1String("go-next")));
0362 
0363                 if ((operation == IOJobData::CopyAlbum) || (operation == IOJobData::MoveAlbum))
0364                 {
0365                     msgBox->button(QMessageBox::No)->hide();
0366                 }
0367 
0368                 int result = msgBox->exec();
0369                 delete msgBox;
0370 
0371                 if      (result == QMessageBox::Cancel)
0372                 {
0373                     delete data;
0374 
0375                     return;
0376                 }
0377                 else if (result == QMessageBox::Yes)
0378                 {
0379                     data->setFileConflict(IOJobData::AutoRename);
0380                 }
0381                 else if (result == QMessageBox::No)
0382                 {
0383                     data->setFileConflict(IOJobData::Overwrite);
0384                 }
0385 
0386                 break;
0387             }
0388         }
0389     }
0390 
0391     ProgressItem* item = nullptr;
0392     QString itemString = getItemString(data);
0393 
0394     if (!itemString.isEmpty())
0395     {
0396         item = ProgressManager::instance()->createProgressItem(itemString,
0397                                                                QString(), true, false);
0398         item->setTotalItems(data->sourceUrls().count());
0399         data->setProgressId(item->id());
0400     }
0401 
0402     IOJobsThread* const jobThread = IOJobsManager::instance()->startIOJobs(data);
0403 
0404     connect(jobThread, SIGNAL(signalOneProccessed(QUrl)),
0405             this, SLOT(slotOneProccessed(QUrl)));
0406 
0407     connect(jobThread, SIGNAL(signalFinished()),
0408             this, SLOT(slotResult()));
0409 
0410     if (operation == IOJobData::Rename)
0411     {
0412         connect(jobThread, SIGNAL(signalRenameFailed(QUrl)),
0413                 this, SIGNAL(signalRenameFailed(QUrl)));
0414 
0415         connect(jobThread, SIGNAL(signalFinished()),
0416                 this, SIGNAL(signalRenameFinished()));
0417     }
0418 
0419     if ((operation == IOJobData::Empty) ||
0420         (operation == IOJobData::Restore))
0421     {
0422         connect(jobThread, SIGNAL(signalFinished()),
0423                 this, SIGNAL(signalTrashFinished()));
0424     }
0425 
0426     if (item)
0427     {
0428         connect(item, SIGNAL(progressItemCanceled(ProgressItem*)),
0429                 jobThread, SLOT(slotCancel()));
0430 
0431         connect(item, SIGNAL(progressItemCanceled(ProgressItem*)),
0432                 this, SLOT(slotCancel(ProgressItem*)));
0433     }
0434 
0435     ++m_processingCount;
0436 }
0437 
0438 void DIO::slotResult()
0439 {
0440     IOJobsThread* const jobThread = dynamic_cast<IOJobsThread*>(sender());
0441 
0442     if (!jobThread || !jobThread->jobData())
0443     {
0444         return;
0445     }
0446 
0447     IOJobData* const data = jobThread->jobData();
0448     const int operation   = data->operation();
0449 
0450     if      (jobThread->hasErrors() && operation != IOJobData::Rename)
0451     {
0452         // Pop-up a message about the error.
0453 
0454         QString errors = jobThread->errorsList().join(QLatin1Char('\n'));
0455         DNotificationWrapper(QString(), errors, DigikamApp::instance(),
0456                              DigikamApp::instance()->windowTitle());
0457     }
0458     else
0459     {
0460         if ((operation == IOJobData::CopyImage) ||
0461             (operation == IOJobData::MoveImage))
0462         {
0463             if (data->destAlbum())
0464             {
0465                 updateAlbumDate(data->destAlbum()->id());
0466             }
0467         }
0468 
0469         if ((operation == IOJobData::Trash)  ||
0470             (operation == IOJobData::Delete) ||
0471             (operation == IOJobData::MoveImage))
0472         {
0473             Q_FOREACH (int albumID, data->srcAlbumIds())
0474             {
0475                 updateAlbumDate(albumID);
0476             }
0477         }
0478 
0479         if (data->errorOrCancel())
0480         {
0481             if ((operation == IOJobData::CopyAlbum) ||
0482                 (operation == IOJobData::MoveAlbum))
0483             {
0484                 QString scanPath = data->destUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile();
0485                 ScanController::instance()->scheduleCollectionScanRelaxed(scanPath);
0486             }
0487         }
0488 
0489         if ((operation == IOJobData::Trash) ||
0490             (operation == IOJobData::Empty) ||
0491             (operation == IOJobData::Restore))
0492         {
0493             buildCollectionTrashCounters();
0494         }
0495     }
0496 
0497     if (m_processingCount)
0498     {
0499         --m_processingCount;
0500     }
0501 
0502     slotCancel(getProgressItem(data));
0503 }
0504 
0505 void DIO::slotOneProccessed(const QUrl& url)
0506 {
0507     IOJobsThread* const jobThread = dynamic_cast<IOJobsThread*>(sender());
0508 
0509     if (!jobThread || !jobThread->jobData())
0510     {
0511         return;
0512     }
0513 
0514     IOJobData* const data = jobThread->jobData();
0515     const int operation   = data->operation();
0516 
0517     switch (operation)
0518     {
0519         case IOJobData::CopyImage:
0520         {
0521             ItemInfo info = data->findItemInfo(url);
0522 
0523             if (!info.isNull() && data->destAlbum())
0524             {
0525                 CoreDbAccess access;
0526                 CoreDbTransaction transaction(&access);
0527 
0528                 qlonglong id = access.db()->copyItem(info.albumId(), info.name(),
0529                                                      data->destAlbum()->id(), data->destName(url));
0530 
0531                 // Remove grouping for copied items.
0532 
0533                 access.db()->removeAllImageRelationsFrom(id, DatabaseRelation::Grouped);
0534                 access.db()->removeAllImageRelationsTo(id, DatabaseRelation::Grouped);
0535             }
0536 
0537             break;
0538         }
0539 
0540         case IOJobData::MoveImage:
0541         {
0542             ItemInfo info = data->findItemInfo(url);
0543 
0544             if (!info.isNull() && data->destAlbum())
0545             {
0546                 CoreDbAccess().db()->moveItem(info.albumId(), info.name(),
0547                                               data->destAlbum()->id(), data->destName(url));
0548             }
0549 
0550             break;
0551         }
0552 
0553         case IOJobData::CopyFiles:
0554         case IOJobData::MoveFiles:
0555         {
0556             QString scanPath = data->destUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile();
0557             scanPath        += QLatin1Char('/') + data->destName(url);
0558 
0559             QFileInfo scanInfo(scanPath);
0560 
0561             if (scanInfo.isDir())
0562             {
0563                 ScanController::instance()->scheduleCollectionScanRelaxed(scanPath);
0564             }
0565             else
0566             {
0567                 ScanController::instance()->scannedInfo(scanPath);
0568             }
0569 
0570             break;
0571 
0572         }
0573 
0574         case IOJobData::CopyToExt:
0575         {
0576             CollectionLocation location = CollectionManager::instance()->locationForUrl(data->destUrl());
0577 
0578             // The target of the copy is within the digiKam collections?
0579 
0580             if (!location.isNull())
0581             {
0582                 QString filePath = data->destUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile();
0583                 filePath        += QLatin1Char('/') + data->destName(url);
0584                 ScanController::instance()->scannedInfo(filePath);
0585             }
0586 
0587             break;
0588         }
0589 
0590         case IOJobData::CopyAlbum:
0591         {
0592             if (data->srcAlbum() && data->destAlbum())
0593             {
0594                 CoreDbAccess access;
0595                 QList<int> albumsToCopy;
0596                 QString newName  = data->destName(url);
0597                 QString basePath = data->srcAlbum()->albumPath();
0598                 QString destPath = data->destAlbum()->albumPath();
0599 
0600                 if (!destPath.endsWith(QLatin1Char('/')))
0601                 {
0602                     destPath.append(QLatin1Char('/'));
0603                 }
0604 
0605                 addAlbumChildrenToList(albumsToCopy, data->srcAlbum());
0606 
0607                 Q_FOREACH (int albumId, albumsToCopy)
0608                 {
0609                     QString relativePath = access.db()->getAlbumRelativePath(albumId);
0610                     relativePath         = relativePath.section(basePath, 1, -1);
0611                     relativePath         = destPath + newName + relativePath;
0612 
0613                     int copyId = access.db()->addAlbum(data->srcAlbum()->albumRootId(),
0614                                                        relativePath, QString(), QDate(), QString());
0615                     access.db()->copyAlbumProperties(albumId, copyId);
0616                     const QList<qlonglong>& imageIds = access.db()->getItemIDsInAlbum(albumId);
0617 
0618                     Q_FOREACH (const qlonglong& id, imageIds)
0619                     {
0620                         QString name     = access.db()->getItemName(id);
0621                         qlonglong itemId = access.db()->copyItem(albumId, name, copyId, name);
0622 
0623                         // Remove grouping for copied items.
0624 
0625                         access.db()->removeAllImageRelationsFrom(itemId, DatabaseRelation::Grouped);
0626                         access.db()->removeAllImageRelationsTo(itemId, DatabaseRelation::Grouped);
0627                     }
0628                 }
0629             }
0630 
0631             break;
0632         }
0633 
0634         case IOJobData::MoveAlbum:
0635         {
0636             if (data->srcAlbum() && data->destAlbum())
0637             {
0638                 CoreDbAccess access;
0639                 QList<int> albumsToMove;
0640                 QString newName  = data->destName(url);
0641                 QString basePath = data->srcAlbum()->albumPath();
0642                 QString destPath = data->destAlbum()->albumPath();
0643 
0644                 if (!destPath.endsWith(QLatin1Char('/')))
0645                 {
0646                     destPath.append(QLatin1Char('/'));
0647                 }
0648 
0649                 addAlbumChildrenToList(albumsToMove, data->srcAlbum());
0650 
0651                 Q_FOREACH (int albumId, albumsToMove)
0652                 {
0653                     QString relativePath = access.db()->getAlbumRelativePath(albumId);
0654                     relativePath         = relativePath.section(basePath, 1, -1);
0655                     relativePath         = destPath + newName + relativePath;
0656                     access.db()->renameAlbum(albumId, data->destAlbum()->albumRootId(), relativePath);
0657                 }
0658             }
0659 
0660             break;
0661         }
0662 
0663         case IOJobData::Trash:
0664         case IOJobData::Delete:
0665         {
0666             // Mark the images as obsolete and remove them
0667             // from their album and from the grouped
0668 
0669             PAlbum* const album = data->srcAlbum();
0670 
0671             if (album)
0672             {
0673                 // Get all deleted albums
0674 
0675                 CoreDbAccess access;
0676                 CoreDbTransaction transaction(&access);
0677 
0678                 QList<int> albumsToDelete;
0679                 QList<qlonglong> imagesToRemove;
0680 
0681                 addAlbumChildrenToList(albumsToDelete, album);
0682 
0683                 Q_FOREACH (int albumId, albumsToDelete)
0684                 {
0685                     imagesToRemove << access.db()->getItemIDsInAlbum(albumId);
0686                 }
0687 
0688                 Q_FOREACH (const qlonglong& removeId, imagesToRemove)
0689                 {
0690                     ItemInfo info(removeId);
0691 
0692                     if (info.isNull())
0693                     {
0694                         continue;
0695                     }
0696 
0697                     const QList<qlonglong>& imageIdsFrom = access.db()->
0698                           getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom);
0699 
0700                     access.db()->removeAllImageRelationsFrom(info.id(),
0701                                                              DatabaseRelation::Grouped);
0702 
0703                     if (operation == IOJobData::Trash)
0704                     {
0705                         access.db()->removeItems(QList<qlonglong>() << info.id(),
0706                                                  QList<int>() << info.albumId());
0707                     }
0708                     else
0709                     {
0710                         access.db()->removeItemsPermanently(QList<qlonglong>() << info.id(),
0711                                                             QList<int>() << info.albumId());
0712                     }
0713 
0714                     Q_FOREACH (const qlonglong& id, imageIdsFrom)
0715                     {
0716                         ItemScanner::resolveImageHistory(id);
0717                         ItemScanner::tagItemHistoryGraph(id);
0718                     }
0719                 }
0720 
0721                 Q_FOREACH (int albumId, albumsToDelete)
0722                 {
0723                     if (operation == IOJobData::Trash)
0724                     {
0725                         access.db()->makeStaleAlbum(albumId);
0726                     }
0727                     else
0728                     {
0729                         access.db()->deleteAlbum(albumId);
0730                     }
0731                 }
0732             }
0733             else
0734             {
0735                 ItemInfo info = data->findItemInfo(url);
0736 
0737                 if (!info.isNull())
0738                 {
0739                     CoreDbAccess access;
0740 
0741                     const QList<qlonglong>& imageIdsFrom = access.db()->
0742                           getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom);
0743 
0744                     access.db()->removeAllImageRelationsFrom(info.id(),
0745                                                              DatabaseRelation::Grouped);
0746 
0747                     if (operation == IOJobData::Trash)
0748                     {
0749                         access.db()->removeItems(QList<qlonglong>() << info.id(),
0750                                                  QList<int>() << info.albumId());
0751                     }
0752                     else
0753                     {
0754                         access.db()->removeItemsPermanently(QList<qlonglong>() << info.id(),
0755                                                             QList<int>() << info.albumId());
0756                     }
0757 
0758                     Q_FOREACH (const qlonglong& id, imageIdsFrom)
0759                     {
0760                         ItemScanner::resolveImageHistory(id);
0761                         ItemScanner::tagItemHistoryGraph(id);
0762                     }
0763                 }
0764             }
0765 
0766             break;
0767         }
0768 
0769         case IOJobData::Rename:
0770         {
0771             ItemInfo info = data->findItemInfo(url);
0772 
0773             if (!info.isNull())
0774             {
0775                 QString oldPath = url.toLocalFile();
0776                 QString newName = data->destUrl(url).fileName();
0777                 QString newPath = data->destUrl(url).toLocalFile();
0778 
0779                 if (data->fileConflict() == IOJobData::Overwrite)
0780                 {
0781                     ThumbsDbAccess().db()->removeByFilePath(newPath);
0782                     LoadingCacheInterface::fileChanged(newPath, false);
0783                     CoreDbAccess().db()->deleteItem(info.albumId(), newName);
0784                 }
0785 
0786                 ThumbsDbAccess().db()->renameByFilePath(oldPath, newPath);
0787 
0788                 // Remove old thumbnails and images from the cache
0789 
0790                 LoadingCacheInterface::fileChanged(oldPath, false);
0791 
0792                 // Rename in ItemInfo and database
0793 
0794                 info.setName(newName);
0795             }
0796 
0797             break;
0798         }
0799 
0800         case IOJobData::Restore:
0801         {
0802             ItemInfo info = ScanController::instance()->scannedInfo(url.toLocalFile());
0803 
0804             if (!info.isNull())
0805             {
0806                 ItemScanner::resolveImageHistory(info.id());
0807                 ItemScanner::tagItemHistoryGraph(info.id());
0808             }
0809 
0810             break;
0811         }
0812 
0813         default:
0814         {
0815             break;
0816         }
0817     }
0818 
0819     ProgressItem* const item = getProgressItem(data);
0820 
0821     if (item)
0822     {
0823         item->advance(1);
0824     }
0825 }
0826 
0827 void DIO::updateAlbumDate(int albumID)
0828 {
0829     QDate newAlbumDate;
0830     MetaEngineSettingsContainer settings = MetaEngineSettings::instance()->settings();
0831 
0832     if      (settings.albumDateFrom == MetaEngineSettingsContainer::OldestItemDate)
0833     {
0834         newAlbumDate = CoreDbAccess().db()->getAlbumLowestDate(albumID);
0835     }
0836     else if (settings.albumDateFrom == MetaEngineSettingsContainer::NewestItemDate)
0837     {
0838         newAlbumDate = CoreDbAccess().db()->getAlbumHighestDate(albumID);
0839     }
0840     else if (settings.albumDateFrom == MetaEngineSettingsContainer::AverageDate)
0841     {
0842         newAlbumDate = CoreDbAccess().db()->getAlbumAverageDate(albumID);
0843     }
0844 
0845     if (newAlbumDate.isValid())
0846     {
0847         CoreDbAccess().db()->setAlbumDate(albumID, newAlbumDate);
0848     }
0849 }
0850 
0851 QString DIO::getItemString(IOJobData* const data) const
0852 {
0853     switch (data->operation())
0854     {
0855         case IOJobData::CopyAlbum:
0856         {
0857             return i18n("Copy Album");
0858         }
0859 
0860         case IOJobData::CopyImage:
0861         {
0862             return i18n("Copy Images");
0863         }
0864 
0865         case IOJobData::CopyFiles:
0866         case IOJobData::CopyToExt:
0867         {
0868             return i18n("Copy Files");
0869         }
0870 
0871         case IOJobData::MoveAlbum:
0872         {
0873             return i18n("Move Album");
0874         }
0875 
0876         case IOJobData::MoveImage:
0877         {
0878             return i18n("Move Images");
0879         }
0880 
0881         case IOJobData::MoveFiles:
0882         {
0883             return i18n("Move Files");
0884         }
0885 
0886         case IOJobData::Delete:
0887         {
0888             return i18n("Delete");
0889         }
0890 
0891         case IOJobData::Trash:
0892         {
0893             return i18n("Trash");
0894         }
0895 
0896         case IOJobData::Restore:
0897         {
0898             return i18n("Restore Trash");
0899         }
0900 
0901         case IOJobData::Empty:
0902         {
0903             return i18n("Empty Trash");
0904         }
0905 
0906         default:
0907         {
0908             break;
0909         }
0910     }
0911 
0912     return QString();
0913 }
0914 
0915 ProgressItem* DIO::getProgressItem(IOJobData* const data) const
0916 {
0917     QString itemId = data->getProgressId();
0918 
0919     if (itemId.isEmpty())
0920     {
0921         return nullptr;
0922     }
0923 
0924     return ProgressManager::instance()->findItembyId(itemId);
0925 }
0926 
0927 void DIO::slotCancel(ProgressItem* item)
0928 {
0929     if (item)
0930     {
0931         item->setComplete();
0932     }
0933 }
0934 
0935 void DIO::slotTrashCounterMap(const QMap<QString, int>& counterMap)
0936 {
0937     {
0938         QMutexLocker locker(&m_trashCounterMutex);
0939 
0940         m_trashCounterMap = counterMap;
0941     }
0942 
0943     Q_EMIT signalTrashCounters();
0944 }
0945 
0946 void DIO::addAlbumChildrenToList(QList<int>& list, Album* const album)
0947 {
0948     // simple recursive helper function
0949 
0950     if (album)
0951     {
0952         if (!list.contains(album->id()))
0953         {
0954             list.append(album->id());
0955         }
0956 
0957         AlbumIterator it(album);
0958 
0959         while (it.current())
0960         {
0961             addAlbumChildrenToList(list, *it);
0962             ++it;
0963         }
0964     }
0965 }
0966 
0967 } // namespace Digikam
0968 
0969 #include "moc_dio.cpp"