File indexing completed on 2025-01-19 03:55:51

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2015-06-15
0007  * Description : IO Jobs for file systems jobs
0008  *
0009  * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0010  * SPDX-FileCopyrightText: 2018 by Maik Qualmann <metzpinguin at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "iojob.h"
0017 
0018 // Qt includes
0019 
0020 #include <QDir>
0021 #include <QFile>
0022 
0023 // KDE includes
0024 
0025 #include <klocalizedstring.h>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "iteminfo.h"
0031 #include "dtrash.h"
0032 #include "coredb.h"
0033 #include "coredbaccess.h"
0034 #include "albummanager.h"
0035 #include "dfileoperations.h"
0036 #include "collectionmanager.h"
0037 #include "coredboperationgroup.h"
0038 
0039 namespace Digikam
0040 {
0041 
0042 IOJob::IOJob()
0043 {
0044 }
0045 
0046 // --------------------------------------------
0047 
0048 CopyOrMoveJob::CopyOrMoveJob(IOJobData* const data)
0049     : m_data(data)
0050 {
0051 }
0052 
0053 void CopyOrMoveJob::run()
0054 {
0055     while (m_data && !m_cancel)
0056     {
0057         QUrl srcUrl = m_data->getNextUrl();
0058 
0059         if (srcUrl.isEmpty())
0060         {
0061             break;
0062         }
0063 
0064         QFileInfo srcInfo(srcUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile());
0065         QDir dstDir(m_data->destUrl().toLocalFile());
0066         QString srcName = srcInfo.fileName();
0067 
0068         if (!srcInfo.exists())
0069         {
0070             Q_EMIT signalError(i18n("File/Folder %1 does not exist anymore", srcName));
0071             continue;
0072         }
0073 
0074         if (!dstDir.exists())
0075         {
0076             Q_EMIT signalError(i18n("Album %1 does not exist anymore", dstDir.dirName()));
0077             continue;
0078         }
0079 
0080         // Checking if there is a file with the same name in destination folder
0081 
0082         QString destenation = dstDir.path() + QLatin1Char('/') + srcName;
0083 
0084         if (QFileInfo::exists(destenation))
0085         {
0086             if      (m_data->fileConflict() == IOJobData::Overwrite)
0087             {
0088                 if (srcInfo.isDir())
0089                 {
0090                     continue;
0091                 }
0092                 else
0093                 {
0094                     if (srcInfo.filePath() == destenation)
0095                     {
0096                         continue;
0097                     }
0098 
0099                     if      (m_data->operation() == IOJobData::CopyToExt)
0100                     {
0101                         if (!QFile::remove(destenation))
0102                         {
0103                             Q_EMIT signalError(i18n("Could not overwrite image %1",
0104                                                     srcName));
0105 
0106                             continue;
0107                         }
0108                     }
0109                     else if (!DTrash::deleteImage(destenation, m_data->jobTime()))
0110                     {
0111                         Q_EMIT signalError(i18n("Could not move image %1 to collection trash.\n"
0112                                                 "Check the file permission on the trash folder "
0113                                                 "\"%2\" in the image collection.",
0114                                                 srcName, DTrash::TRASH_FOLDER));
0115 
0116                         continue;
0117                     }
0118                 }
0119             }
0120             else if (m_data->fileConflict() == IOJobData::AutoRename)
0121             {
0122                 QUrl destUrl = QUrl::fromLocalFile(destenation);
0123 
0124                 if (srcInfo.isDir())
0125                 {
0126                     QUrl renamed = DFileOperations::getUniqueFolderUrl(destUrl);
0127                     destenation  = renamed.toLocalFile();
0128                     m_data->setDestUrl(srcUrl, renamed);
0129                 }
0130                 else
0131                 {
0132                     QUrl renamed = DFileOperations::getUniqueFileUrl(destUrl);
0133                     destenation  = renamed.toLocalFile();
0134                     m_data->setDestUrl(srcUrl, renamed);
0135                 }
0136             }
0137             else
0138             {
0139                 Q_EMIT signalError(i18n("A file or folder named %1 already exists in %2",
0140                                         srcName, QDir::toNativeSeparators(dstDir.path())));
0141 
0142                 continue;
0143             }
0144         }
0145 
0146         if ((m_data->operation() == IOJobData::MoveAlbum) ||
0147             (m_data->operation() == IOJobData::MoveImage) ||
0148             (m_data->operation() == IOJobData::MoveFiles))
0149         {
0150             if (srcInfo.isDir())
0151             {
0152                 QDir srcDir(srcInfo.filePath());
0153 
0154                 if (!srcDir.rename(srcDir.path(), destenation))
0155                 {
0156                     // If QDir::rename fails, try copy and remove.
0157 
0158                     if      (!DFileOperations::copyFolderRecursively(srcDir.path(), destenation,
0159                                                                      m_data->getProgressId(),
0160                                                                      &m_cancel, true))
0161                     {
0162                         m_data->setErrorOrCancel(true);
0163 
0164                         if (m_cancel)
0165                         {
0166                             break;
0167                         }
0168 
0169                         Q_EMIT signalError(i18n("Could not move folder %1 to album %2",
0170                                                 srcName, QDir::toNativeSeparators(dstDir.path())));
0171 
0172                         continue;
0173                     }
0174                     else if (!srcDir.removeRecursively())
0175                     {
0176                         Q_EMIT signalError(i18n("Could not move folder %1 to album %2. "
0177                                                 "The folder %1 was copied as well to album %2",
0178                                                 srcName, QDir::toNativeSeparators(dstDir.path())));
0179                     }
0180                 }
0181             }
0182             else
0183             {
0184                 // move the possible sidecar files first
0185 
0186                 if (!DFileOperations::sidecarFiles(srcInfo.filePath(), destenation, DFileOperations::Rename))
0187                 {
0188                     Q_EMIT signalError(i18n("Could not move sidecar from file %1 to album %2",
0189                                             srcName, QDir::toNativeSeparators(dstDir.path())));
0190                 }
0191 
0192                 if (!DFileOperations::renameFile(srcInfo.filePath(), destenation))
0193                 {
0194                     Q_EMIT signalError(i18n("Could not move file %1 to album %2",
0195                                             srcName, QDir::toNativeSeparators(dstDir.path())));
0196 
0197                     continue;
0198                 }
0199            }
0200         }
0201         else
0202         {
0203             if (srcInfo.isDir())
0204             {
0205                 QDir srcDir(srcInfo.filePath());
0206 
0207                 if (!DFileOperations::copyFolderRecursively(srcDir.path(), destenation,
0208                                                             m_data->getProgressId(),
0209                                                             &m_cancel, true))
0210                 {
0211                     m_data->setErrorOrCancel(true);
0212 
0213                     if (m_cancel)
0214                     {
0215                         break;
0216                     }
0217 
0218                     Q_EMIT signalError(i18n("Could not copy folder %1 to album %2",
0219                                             srcName, QDir::toNativeSeparators(dstDir.path())));
0220 
0221                     continue;
0222                 }
0223             }
0224             else
0225             {
0226                 // copy the possible sidecar files first
0227 
0228                 if (!DFileOperations::sidecarFiles(srcInfo.filePath(), destenation, DFileOperations::Copy))
0229                 {
0230                     Q_EMIT signalError(i18n("Could not copy sidecar from file %1 to folder %2",
0231                                             srcName, QDir::toNativeSeparators(dstDir.path())));
0232                 }
0233 
0234                 if (!DFileOperations::copyFile(srcInfo.filePath(), destenation))
0235                 {
0236                     if (m_data->operation() == IOJobData::CopyToExt)
0237                     {
0238                         Q_EMIT signalError(i18n("Could not copy file %1 to folder %2",
0239                                                 srcName, QDir::toNativeSeparators(dstDir.path())));
0240                     }
0241                     else
0242                     {
0243                         Q_EMIT signalError(i18n("Could not copy file %1 to album %2",
0244                                                 srcName, QDir::toNativeSeparators(dstDir.path())));
0245                     }
0246 
0247                     continue;
0248                 }
0249             }
0250         }
0251 
0252         Q_EMIT signalOneProccessed(srcUrl);
0253     }
0254 
0255     m_data->setErrorOrCancel(m_cancel);
0256 
0257     Q_EMIT signalDone();
0258 }
0259 
0260 // --------------------------------------------
0261 
0262 DeleteJob::DeleteJob(IOJobData* const data)
0263     : m_data(data)
0264 {
0265 }
0266 
0267 void DeleteJob::run()
0268 {
0269     while (m_data && !m_cancel)
0270     {
0271         QUrl deleteUrl = m_data->getNextUrl();
0272 
0273         if (deleteUrl.isEmpty())
0274         {
0275             break;
0276         }
0277 
0278         bool useTrash = (m_data->operation() == IOJobData::Trash);
0279 
0280         QFileInfo fileInfo(deleteUrl.toLocalFile());
0281         qCDebug(DIGIKAM_IOJOB_LOG) << "Deleting:   " << fileInfo.filePath();
0282         qCDebug(DIGIKAM_IOJOB_LOG) << "File exists?" << fileInfo.exists();
0283         qCDebug(DIGIKAM_IOJOB_LOG) << "Is to trash?" << useTrash;
0284 
0285         if (!fileInfo.exists())
0286         {
0287             Q_EMIT signalError(i18n("File/Folder %1 does not exist",
0288                                     QDir::toNativeSeparators(fileInfo.filePath())));
0289 
0290             continue;
0291         }
0292 
0293         if (useTrash)
0294         {
0295             if (fileInfo.isDir())
0296             {
0297                 if (!DTrash::deleteDirRecursivley(deleteUrl.toLocalFile(), m_data->jobTime()))
0298                 {
0299                     Q_EMIT signalError(i18n("Could not move folder %1 to collection trash.\n"
0300                                             "Check the file permission on the trash folder "
0301                                             "\".dtrash\" in the image collection.",
0302                                             QDir::toNativeSeparators(fileInfo.path())));
0303 
0304                     continue;
0305                 }
0306             }
0307             else
0308             {
0309                 if (!DTrash::deleteImage(deleteUrl.toLocalFile(), m_data->jobTime()))
0310                 {
0311                     Q_EMIT signalError(i18n("Could not move image %1 to collection trash.\n"
0312                                             "Check the file permission on the trash folder "
0313                                             "\".dtrash\" in the image collection.",
0314                                             QDir::toNativeSeparators(fileInfo.filePath())));
0315 
0316                     continue;
0317                 }
0318             }
0319         }
0320         else
0321         {
0322             if (fileInfo.isDir())
0323             {
0324                 QDir dir(fileInfo.filePath());
0325 
0326                 if (!dir.removeRecursively())
0327                 {
0328                     Q_EMIT signalError(i18n("Album %1 could not be removed",
0329                                             QDir::toNativeSeparators(fileInfo.path())));
0330 
0331                     continue;
0332                 }
0333             }
0334             else
0335             {
0336                 QFile file(fileInfo.filePath());
0337 
0338                 if (!file.remove())
0339                 {
0340                     Q_EMIT signalError(i18n("Image %1 could not be removed",
0341                                             QDir::toNativeSeparators(fileInfo.filePath())));
0342 
0343                     continue;
0344                 }
0345             }
0346         }
0347 
0348         Q_EMIT signalOneProccessed(deleteUrl);
0349     }
0350 
0351     Q_EMIT signalDone();
0352 }
0353 
0354 // --------------------------------------------
0355 
0356 RenameFileJob::RenameFileJob(IOJobData* const data)
0357     : m_data(data)
0358 {
0359 }
0360 
0361 void RenameFileJob::run()
0362 {
0363     while (m_data && !m_cancel)
0364     {
0365         QUrl renameUrl = m_data->getNextUrl();
0366 
0367         if (renameUrl.isEmpty())
0368         {
0369             break;
0370         }
0371 
0372         QUrl destUrl = m_data->destUrl(renameUrl);
0373         QFileInfo fileInfo(destUrl.toLocalFile());
0374 
0375         QDir dir(fileInfo.dir());
0376         const QStringList& dirList = dir.entryList(QDir::Dirs    |
0377                                                    QDir::Files   |
0378                                                    QDir::NoDotAndDotDot);
0379 
0380         if (dirList.contains(fileInfo.fileName()))
0381         {
0382             if (m_data->fileConflict() == IOJobData::Overwrite)
0383             {
0384                 if (!DTrash::deleteImage(destUrl.toLocalFile(), m_data->jobTime()))
0385                 {
0386                     Q_EMIT signalError(i18n("Could not move image %1 to collection trash",
0387                                             QDir::toNativeSeparators(destUrl.toLocalFile())));
0388 
0389                     Q_EMIT signalRenameFailed(renameUrl);
0390                     continue;
0391                 }
0392             }
0393             else
0394             {
0395                 qCDebug(DIGIKAM_IOJOB_LOG) << "File with the same name exists!";
0396                 Q_EMIT signalError(i18n("Image with the same name %1 already there",
0397                                         QDir::toNativeSeparators(destUrl.toLocalFile())));
0398 
0399                 Q_EMIT signalRenameFailed(renameUrl);
0400                 continue;
0401             }
0402         }
0403 
0404         qCDebug(DIGIKAM_IOJOB_LOG) << "Trying to rename"
0405                                    << renameUrl.toLocalFile() << "to"
0406                                    << destUrl.toLocalFile();
0407 
0408         if (!DFileOperations::renameFile(renameUrl.toLocalFile(), destUrl.toLocalFile()))
0409         {
0410             qCDebug(DIGIKAM_IOJOB_LOG) << "File could not be renamed!";
0411             Q_EMIT signalError(i18n("Image %1 could not be renamed",
0412                                     QDir::toNativeSeparators(renameUrl.toLocalFile())));
0413 
0414             Q_EMIT signalRenameFailed(renameUrl);
0415             continue;
0416         }
0417 
0418         Q_EMIT signalOneProccessed(renameUrl);
0419     }
0420 
0421     Q_EMIT signalDone();
0422 }
0423 
0424 // ----------------------------------------------
0425 
0426 DTrashItemsListingJob::DTrashItemsListingJob(const QString& collectionPath)
0427     : m_collectionPath(collectionPath)
0428 {
0429 }
0430 
0431 void DTrashItemsListingJob::run()
0432 {
0433     DTrashItemInfo itemInfo;
0434 
0435     QString collectionTrashFilesPath = m_collectionPath     + QLatin1Char('/') +
0436                                        DTrash::TRASH_FOLDER + QLatin1Char('/') +
0437                                        DTrash::FILES_FOLDER;
0438 
0439     qCDebug(DIGIKAM_IOJOB_LOG) << "Collection trash files path:" << collectionTrashFilesPath;
0440 
0441     QDir filesDir(collectionTrashFilesPath);
0442 
0443     Q_FOREACH (const QFileInfo& fileInfo, filesDir.entryInfoList(QDir::Files))
0444     {
0445         if (m_cancel)
0446         {
0447             break;
0448         }
0449 
0450         qCDebug(DIGIKAM_IOJOB_LOG) << "File in trash:" << fileInfo.filePath();
0451         itemInfo.trashPath = fileInfo.filePath();
0452 
0453         DTrash::extractJsonForItem(m_collectionPath, fileInfo.baseName(), itemInfo);
0454 
0455         Q_EMIT trashItemInfo(itemInfo);
0456     }
0457 
0458     Q_EMIT signalDone();
0459 }
0460 
0461 // ----------------------------------------------
0462 
0463 RestoreDTrashItemsJob::RestoreDTrashItemsJob(IOJobData* const data)
0464     : m_data(data)
0465 {
0466 }
0467 
0468 void RestoreDTrashItemsJob::run()
0469 {
0470     if (!m_data)
0471     {
0472         return;
0473     }
0474 
0475     Q_FOREACH (const DTrashItemInfo& item, m_data->trashItems())
0476     {
0477         QUrl srcToRename = QUrl::fromLocalFile(item.collectionPath);
0478         QUrl newName     = DFileOperations::getUniqueFileUrl(srcToRename);
0479 
0480         QFileInfo fi(item.collectionPath);
0481 
0482         if (!fi.dir().exists())
0483         {
0484             fi.dir().mkpath(fi.dir().path());
0485         }
0486 
0487         if (!QFile::rename(item.trashPath, newName.toLocalFile()))
0488         {
0489             Q_EMIT signalError(i18n("Could not restore file %1 from trash",
0490                                     QDir::toNativeSeparators(newName.toLocalFile())));
0491         }
0492         else
0493         {
0494             QFile::remove(item.jsonFilePath);
0495         }
0496 
0497         Q_EMIT signalOneProccessed(newName);
0498     }
0499 
0500     Q_EMIT signalDone();
0501 }
0502 
0503 // ----------------------------------------------
0504 
0505 EmptyDTrashItemsJob::EmptyDTrashItemsJob(IOJobData* const data)
0506     : m_data(data)
0507 {
0508 }
0509 
0510 void EmptyDTrashItemsJob::run()
0511 {
0512     if (!m_data)
0513     {
0514         return;
0515     }
0516 
0517     QList<int> albumsFromImages;
0518     QList<qlonglong> imagesToRemove;
0519 
0520     Q_FOREACH (const DTrashItemInfo& item, m_data->trashItems())
0521     {
0522         QFile::remove(item.trashPath);
0523         QFile::remove(item.jsonFilePath);
0524 
0525         imagesToRemove   << item.imageId;
0526         albumsFromImages << ItemInfo(item.imageId).albumId();
0527 
0528         Q_EMIT signalOneProccessed(QUrl());
0529     }
0530 
0531     {
0532         CoreDbOperationGroup group;
0533         group.setMaximumTime(200);
0534 
0535         CoreDbAccess().db()->removeItemsPermanently(imagesToRemove, albumsFromImages);
0536     }
0537 
0538     Q_EMIT signalDone();
0539 }
0540 
0541 // ----------------------------------------------
0542 
0543 BuildTrashCountersJob::BuildTrashCountersJob()
0544 {
0545 }
0546 
0547 void BuildTrashCountersJob::run()
0548 {
0549     QMap<QString, int> trashCountersMap;
0550     QList<CollectionLocation> allLocations = CollectionManager::instance()->allAvailableLocations();
0551 
0552     Q_FOREACH (const CollectionLocation& location, allLocations)
0553     {
0554         QString path = location.albumRootPath() + QLatin1Char('/') +
0555                        DTrash::TRASH_FOLDER     + QLatin1Char('/') +
0556                        DTrash::FILES_FOLDER;
0557 
0558         QDir dir(path, QLatin1String(""), QDir::Unsorted, QDir::Files);
0559 
0560         if (dir.exists() && dir.isReadable())
0561         {
0562             trashCountersMap.insert(location.albumRootPath(), dir.count());
0563         }
0564         else
0565         {
0566             trashCountersMap.insert(location.albumRootPath(), 0);
0567         }
0568     }
0569 
0570     Q_EMIT signalTrashCountersMap(trashCountersMap);
0571 
0572     Q_EMIT signalDone();
0573 }
0574 
0575 } // namespace Digikam
0576 
0577 #include "moc_iojob.cpp"