File indexing completed on 2025-01-05 03:56:11

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2015-07-27
0007  * Description : Special digiKam trash implementation
0008  *
0009  * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dtrash.h"
0016 
0017 // Qt includes
0018 
0019 #include <QDir>
0020 #include <QFile>
0021 #include <QUuid>
0022 #include <QJsonObject>
0023 #include <QJsonDocument>
0024 #include <QJsonValue>
0025 #include <QDateTime>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "collectionmanager.h"
0031 #include "albummanager.h"
0032 
0033 namespace Digikam
0034 {
0035 
0036 const QString DTrash::TRASH_FOLDER               = QLatin1String(".dtrash");
0037 const QString DTrash::FILES_FOLDER               = QLatin1String("files");
0038 const QString DTrash::INFO_FOLDER                = QLatin1String("info");
0039 const QString DTrash::INFO_FILE_EXTENSION        = QLatin1String(".dtrashinfo");
0040 const QString DTrash::PATH_JSON_KEY              = QLatin1String("path");
0041 const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp");
0042 const QString DTrash::IMAGEID_JSON_KEY           = QLatin1String("imageid");
0043 
0044 // ----------------------------------------------
0045 
0046 DTrash::DTrash()
0047 {
0048 }
0049 
0050 bool DTrash::deleteImage(const QString& imagePath, const QDateTime& deleteTime)
0051 {
0052     QString collection = CollectionManager::instance()->albumRootPath(imagePath);
0053 
0054     qCDebug(DIGIKAM_IOJOB_LOG)  << "DTrash: Image album root path:"
0055                                 << collection;
0056 
0057     if (!prepareCollectionTrash(collection))
0058     {
0059         return false;
0060     }
0061 
0062     QFileInfo imageFileInfo(imagePath);
0063     QString fileName     = imageFileInfo.fileName();
0064 
0065     // Get the album path, i.e. collection + album. For this,
0066     // get the n leftmost characters where n is the complete path without the size of the filename
0067 
0068     QString completePath = imageFileInfo.path();
0069 
0070     qlonglong imageId    = -1;
0071 
0072     // Get the album and with this the image id of the image to trash.
0073 
0074     PAlbum* const pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath));
0075 
0076     if (pAlbum)
0077     {
0078         imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName);
0079     }
0080 
0081     QString baseNameForMovingIntoTrash = createJsonRecordForFile(imageId,
0082                                                                  imagePath,
0083                                                                  deleteTime,
0084                                                                  collection);
0085 
0086     QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER       +
0087                                  QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') +
0088                                  baseNameForMovingIntoTrash + QLatin1Char('.')      +
0089                                  imageFileInfo.completeSuffix();
0090 
0091     if (!QFile::rename(imagePath, destinationInTrash))
0092     {
0093         return false;
0094     }
0095 
0096     return true;
0097 }
0098 
0099 bool DTrash::deleteDirRecursivley(const QString& dirToDelete, const QDateTime& deleteTime)
0100 {
0101     QDir srcDir(dirToDelete);
0102 
0103     Q_FOREACH (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files))
0104     {
0105         if (!deleteImage(fileInfo.filePath(), deleteTime))
0106         {   // cppcheck-suppress useStlAlgorithm
0107             return false;
0108         }
0109     }
0110 
0111     Q_FOREACH (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
0112     {
0113         if (!deleteDirRecursivley(fileInfo.filePath(), deleteTime))
0114         {   // cppcheck-suppress useStlAlgorithm
0115             return false;
0116         }
0117     }
0118 
0119     return srcDir.removeRecursively();
0120 }
0121 
0122 void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo)
0123 {
0124     QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER        +
0125                            QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') +
0126                            baseName + INFO_FILE_EXTENSION;
0127 
0128     QFile jsonFile(jsonFilePath);
0129 
0130     if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text))
0131     {
0132         return;
0133     }
0134 
0135     QJsonDocument doc               = QJsonDocument::fromJson(jsonFile.readAll());
0136     jsonFile.close();
0137 
0138     QJsonObject fileInfoObj         = doc.object();
0139 
0140     itemInfo.jsonFilePath           = jsonFilePath;
0141 
0142     itemInfo.collectionPath         = fileInfoObj.value(PATH_JSON_KEY).toString();
0143 
0144     itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString()
0145                                       .replace(collPath, QLatin1String(""));
0146 
0147     itemInfo.deletionTimestamp      = QDateTime::fromString(
0148                                       fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString(), Qt::ISODate);
0149 
0150     if (!itemInfo.deletionTimestamp.isValid())
0151     {
0152         // Failback to date encoded as string using locale.
0153         // This is an older way to store date in JSOn, which do not support change in locale.
0154         // This is why ISO format is now used.
0155 
0156         itemInfo.deletionTimestamp  = QDateTime::fromString(
0157                                       fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString());
0158     }
0159 
0160     QJsonValue imageIdValue         = fileInfoObj.value(IMAGEID_JSON_KEY);
0161 
0162     if (!imageIdValue.isUndefined())
0163     {
0164         itemInfo.imageId = imageIdValue.toString().toLongLong();
0165     }
0166     else
0167     {
0168         itemInfo.imageId = -1;
0169     }
0170 }
0171 
0172 bool DTrash::prepareCollectionTrash(const QString& collectionPath)
0173 {
0174     QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER;
0175     QString trashFiles  = trashFolder    + QLatin1Char('/') + FILES_FOLDER;
0176     QString trashInfo   = trashFolder    + QLatin1Char('/') + INFO_FOLDER;
0177     bool    isCreated   = !collectionPath.isEmpty();
0178 
0179     if (isCreated && !QFileInfo::exists(trashFolder))
0180     {
0181         isCreated &= QDir().mkpath(trashFolder);
0182     }
0183 
0184     if (isCreated && !QFileInfo::exists(trashFiles))
0185     {
0186         isCreated &= QDir().mkpath(trashFiles);
0187     }
0188 
0189     if (isCreated && !QFileInfo::exists(trashInfo))
0190     {
0191         isCreated &= QDir().mkpath(trashInfo);
0192     }
0193 
0194     if (!isCreated)
0195     {
0196         qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection";
0197 
0198         return false;
0199     }
0200 
0201     qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection:" << trashFolder;
0202 
0203     return true;
0204 }
0205 
0206 QString DTrash::createJsonRecordForFile(qlonglong imageId,
0207                                         const QString& imagePath,
0208                                         const QDateTime& deleteTime,
0209                                         const QString& collectionPath)
0210 {
0211     QJsonObject jsonObjForImg;
0212 
0213     QJsonValue pathJsonVal(imagePath);
0214     QJsonValue timestampJsonVal(deleteTime.toString(Qt::ISODate));
0215     QJsonValue imageIdJsonVal(QString::number(imageId));
0216 
0217     jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal);
0218     jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal);
0219     jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal);
0220 
0221     QJsonDocument jsonDocForImg(jsonObjForImg);
0222 
0223     QFileInfo imgFileInfo(imagePath);
0224 
0225     QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath,
0226                                                            imgFileInfo.baseName());
0227 
0228     QFile jsonFileForImg(jsonFileName);
0229 
0230     QFileInfo jsonFileInfo(jsonFileName);
0231 
0232     if (!jsonFileForImg.open(QFile::WriteOnly))
0233     {
0234         return jsonFileInfo.baseName();
0235     }
0236 
0237     jsonFileForImg.write(jsonDocForImg.toJson());
0238     jsonFileForImg.close();
0239     jsonFileForImg.setPermissions(QFileDevice::ReadOwner  |
0240                                   QFileDevice::ReadGroup  |
0241                                   QFileDevice::ReadOther  |
0242                                   QFileDevice::WriteOwner |
0243                                   QFileDevice::WriteGroup);
0244 
0245     return jsonFileInfo.baseName();
0246 }
0247 
0248 QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath,
0249                                                 const QString& baseName,
0250                                                 int version)
0251 {
0252     QString pathToCreateJsonFile = collectionPath + QLatin1Char('/')                        +
0253                                    TRASH_FOLDER + QLatin1Char('/')                          +
0254                                    INFO_FOLDER + QLatin1Char('/')                           +
0255                                    baseName + QLatin1Char('-')                              +
0256                                    QUuid::createUuid().toString().mid(1, 8)                 +
0257                                    (version ? QString::number(version) : QLatin1String("")) +
0258                                    INFO_FILE_EXTENSION;
0259 
0260     QFileInfo jsonFileInfo(pathToCreateJsonFile);
0261 
0262     if (jsonFileInfo.exists())
0263     {
0264         return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version);
0265     }
0266     else
0267     {
0268         return pathToCreateJsonFile;
0269     }
0270 }
0271 
0272 } // namespace Digikam