1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2015-07-27
7  * Description : Special digiKam trash implementation
8  *
9  * Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "dtrash.h"
25 
26 // Qt includes
27 
28 #include <QDir>
29 #include <QFile>
30 #include <QUuid>
31 #include <QJsonObject>
32 #include <QJsonDocument>
33 #include <QJsonValue>
34 #include <QDateTime>
35 
36 // Local includes
37 
38 #include "digikam_debug.h"
39 #include "collectionmanager.h"
40 #include "albummanager.h"
41 
42 namespace Digikam
43 {
44 
45 const QString DTrash::TRASH_FOLDER               = QLatin1String(".dtrash");
46 const QString DTrash::FILES_FOLDER               = QLatin1String("files");
47 const QString DTrash::INFO_FOLDER                = QLatin1String("info");
48 const QString DTrash::INFO_FILE_EXTENSION        = QLatin1String(".dtrashinfo");
49 const QString DTrash::PATH_JSON_KEY              = QLatin1String("path");
50 const QString DTrash::DELETIONTIMESTAMP_JSON_KEY = QLatin1String("deletiontimestamp");
51 const QString DTrash::IMAGEID_JSON_KEY           = QLatin1String("imageid");
52 
53 // ----------------------------------------------
54 
DTrash()55 DTrash::DTrash()
56 {
57 }
58 
deleteImage(const QString & imagePath,const QDateTime & deleteTime)59 bool DTrash::deleteImage(const QString& imagePath, const QDateTime& deleteTime)
60 {
61     QString collection = CollectionManager::instance()->albumRootPath(imagePath);
62 
63     qCDebug(DIGIKAM_IOJOB_LOG)  << "DTrash: Image album root path:"
64                                 << collection;
65 
66     if (!prepareCollectionTrash(collection))
67     {
68         return false;
69     }
70 
71     QFileInfo imageFileInfo(imagePath);
72     QString fileName     = imageFileInfo.fileName();
73 
74     // Get the album path, i.e. collection + album. For this,
75     // get the n leftmost characters where n is the complete path without the size of the filename
76 
77     QString completePath = imageFileInfo.path();
78 
79     qlonglong imageId    = -1;
80 
81     // Get the album and with this the image id of the image to trash.
82 
83     PAlbum* const pAlbum = AlbumManager::instance()->findPAlbum(QUrl::fromLocalFile(completePath));
84 
85     if (pAlbum)
86     {
87         imageId = AlbumManager::instance()->getItemFromAlbum(pAlbum, fileName);
88     }
89 
90     QString baseNameForMovingIntoTrash = createJsonRecordForFile(imageId,
91                                                                  imagePath,
92                                                                  deleteTime,
93                                                                  collection);
94 
95     QString destinationInTrash = collection + QLatin1Char('/') + TRASH_FOLDER       +
96                                  QLatin1Char('/') + FILES_FOLDER + QLatin1Char('/') +
97                                  baseNameForMovingIntoTrash + QLatin1Char('.')      +
98                                  imageFileInfo.completeSuffix();
99 
100     if (!QFile::rename(imagePath, destinationInTrash))
101     {
102         return false;
103     }
104 
105     return true;
106 }
107 
deleteDirRecursivley(const QString & dirToDelete,const QDateTime & deleteTime)108 bool DTrash::deleteDirRecursivley(const QString& dirToDelete, const QDateTime& deleteTime)
109 {
110     QDir srcDir(dirToDelete);
111 
112     foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Files))
113     {
114         if (!deleteImage(fileInfo.filePath(), deleteTime))
115         {
116             return false;
117         }
118     }
119 
120     foreach (const QFileInfo& fileInfo, srcDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
121     {
122         if (!deleteDirRecursivley(fileInfo.filePath(), deleteTime))
123         {
124             return false;
125         }
126     }
127 
128     return srcDir.removeRecursively();
129 }
130 
extractJsonForItem(const QString & collPath,const QString & baseName,DTrashItemInfo & itemInfo)131 void DTrash::extractJsonForItem(const QString& collPath, const QString& baseName, DTrashItemInfo& itemInfo)
132 {
133     QString jsonFilePath = collPath + QLatin1Char('/') + TRASH_FOLDER        +
134                            QLatin1Char('/') + INFO_FOLDER + QLatin1Char('/') +
135                            baseName + INFO_FILE_EXTENSION;
136 
137     QFile jsonFile(jsonFilePath);
138 
139     if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text))
140     {
141         return;
142     }
143 
144     QJsonDocument doc               = QJsonDocument::fromJson(jsonFile.readAll());
145     jsonFile.close();
146 
147     QJsonObject fileInfoObj         = doc.object();
148 
149     itemInfo.jsonFilePath           = jsonFilePath;
150 
151     itemInfo.collectionPath         = fileInfoObj.value(PATH_JSON_KEY).toString();
152 
153     itemInfo.collectionRelativePath = fileInfoObj.value(PATH_JSON_KEY).toString()
154                                       .replace(collPath, QLatin1String(""));
155 
156     itemInfo.deletionTimestamp      = QDateTime::fromString(
157                                       fileInfoObj.value(DELETIONTIMESTAMP_JSON_KEY).toString());
158 
159     QJsonValue imageIdValue         = fileInfoObj.value(IMAGEID_JSON_KEY);
160 
161     if (!imageIdValue.isUndefined())
162     {
163         itemInfo.imageId = imageIdValue.toString().toLongLong();
164     }
165     else
166     {
167         itemInfo.imageId = -1;
168     }
169 }
170 
prepareCollectionTrash(const QString & collectionPath)171 bool DTrash::prepareCollectionTrash(const QString& collectionPath)
172 {
173     QString trashFolder = collectionPath + QLatin1Char('/') + TRASH_FOLDER;
174     QDir trashDir(trashFolder);
175 
176     if (!trashDir.exists())
177     {
178         bool isCreated = true;
179 
180         isCreated     &= trashDir.mkpath(trashFolder);
181         isCreated     &= trashDir.mkpath(trashFolder + QLatin1Char('/') + FILES_FOLDER);
182         isCreated     &= trashDir.mkpath(trashFolder + QLatin1Char('/') + INFO_FOLDER);
183 
184         if (!isCreated)
185         {
186             qCDebug(DIGIKAM_IOJOB_LOG) << "DTrash: could not create trash folder for collection";
187             return false;
188         }
189     }
190 
191     qCDebug(DIGIKAM_IOJOB_LOG) << "Trash folder for collection: " << trashFolder;
192 
193     return true;
194 }
195 
createJsonRecordForFile(qlonglong imageId,const QString & imagePath,const QDateTime & deleteTime,const QString & collectionPath)196 QString DTrash::createJsonRecordForFile(qlonglong imageId,
197                                         const QString& imagePath,
198                                         const QDateTime& deleteTime,
199                                         const QString& collectionPath)
200 {
201     QJsonObject jsonObjForImg;
202 
203     QJsonValue pathJsonVal(imagePath);
204     QJsonValue timestampJsonVal(deleteTime.toString());
205     QJsonValue imageIdJsonVal(QString::number(imageId));
206 
207     jsonObjForImg.insert(PATH_JSON_KEY, pathJsonVal);
208     jsonObjForImg.insert(DELETIONTIMESTAMP_JSON_KEY, timestampJsonVal);
209     jsonObjForImg.insert(IMAGEID_JSON_KEY, imageIdJsonVal);
210 
211     QJsonDocument jsonDocForImg(jsonObjForImg);
212 
213     QFileInfo imgFileInfo(imagePath);
214 
215     QString jsonFileName = getAvialableJsonFilePathInTrash(collectionPath,
216                                                            imgFileInfo.baseName());
217 
218     QFile jsonFileForImg(jsonFileName);
219 
220     QFileInfo jsonFileInfo(jsonFileName);
221 
222     if (!jsonFileForImg.open(QFile::WriteOnly))
223     {
224         return jsonFileInfo.baseName();
225     }
226 
227     jsonFileForImg.write(jsonDocForImg.toJson());
228     jsonFileForImg.close();
229 
230     return jsonFileInfo.baseName();
231 }
232 
getAvialableJsonFilePathInTrash(const QString & collectionPath,const QString & baseName,int version)233 QString DTrash::getAvialableJsonFilePathInTrash(const QString& collectionPath,
234                                                 const QString& baseName, int version)
235 {
236     QString pathToCreateJsonFile = collectionPath + QLatin1Char('/')                        +
237                                    TRASH_FOLDER + QLatin1Char('/')                          +
238                                    INFO_FOLDER + QLatin1Char('/')                           +
239                                    baseName + QLatin1Char('-')                              +
240                                    QUuid::createUuid().toString().mid(1, 8)                 +
241                                    (version ? QString::number(version) : QLatin1String("")) +
242                                    INFO_FILE_EXTENSION;
243 
244     QFileInfo jsonFileInfo(pathToCreateJsonFile);
245 
246     if (jsonFileInfo.exists())
247     {
248         return getAvialableJsonFilePathInTrash(collectionPath, baseName, ++version);
249     }
250     else
251     {
252         return pathToCreateJsonFile;
253     }
254 }
255 
256 } // namespace Digikam
257