1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2015-06-15
7  * Description : IO Jobs for file systems jobs
8  *
9  * Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
10  * Copyright (C) 2018 by Maik Qualmann <metzpinguin at gmail dot com>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "iojob.h"
26 
27 // Qt includes
28 
29 #include <QDir>
30 #include <QFile>
31 #include <QDirIterator>
32 
33 // KDE includes
34 
35 #include <klocalizedstring.h>
36 
37 // Local includes
38 
39 #include "digikam_debug.h"
40 #include "iteminfo.h"
41 #include "dtrash.h"
42 #include "coredb.h"
43 #include "coredbaccess.h"
44 #include "albummanager.h"
45 #include "dfileoperations.h"
46 #include "coredboperationgroup.h"
47 
48 namespace Digikam
49 {
50 
IOJob()51 IOJob::IOJob()
52 {
53 }
54 
55 // --------------------------------------------
56 
CopyOrMoveJob(IOJobData * const data)57 CopyOrMoveJob::CopyOrMoveJob(IOJobData* const data)
58     : m_data(data)
59 {
60 }
61 
run()62 void CopyOrMoveJob::run()
63 {
64     while (m_data && !m_cancel)
65     {
66         QUrl srcUrl = m_data->getNextUrl();
67 
68         if (srcUrl.isEmpty())
69         {
70             break;
71         }
72 
73         QFileInfo srcInfo(srcUrl.adjusted(QUrl::StripTrailingSlash).toLocalFile());
74         QDir dstDir(m_data->destUrl().toLocalFile());
75         QString srcName = srcInfo.fileName();
76 
77         if (!srcInfo.exists())
78         {
79             emit signalError(i18n("File/Folder %1 does not exist anymore", srcName));
80             continue;
81         }
82 
83         if (!dstDir.exists())
84         {
85             emit signalError(i18n("Album %1 does not exist anymore", dstDir.dirName()));
86             continue;
87         }
88 
89         // Checking if there is a file with the same name in destination folder
90 
91         QString destenation = dstDir.path() + QLatin1Char('/') + srcName;
92 
93         if (QFileInfo::exists(destenation))
94         {
95             if      (m_data->fileConflict() == IOJobData::Overwrite)
96             {
97                 if (srcInfo.isDir())
98                 {
99                     continue;
100                 }
101                 else
102                 {
103                     if (srcInfo.filePath() == destenation)
104                     {
105                         continue;
106                     }
107 
108                     if      (m_data->operation() == IOJobData::CopyToExt)
109                     {
110                         if (!QFile::remove(destenation))
111                         {
112                             emit signalError(i18n("Could not overwrite image %1",
113                                              srcName));
114 
115                             continue;
116                         }
117                     }
118                     else if (!DTrash::deleteImage(destenation, m_data->jobTime()))
119                     {
120                         emit signalError(i18n("Could not move image %1 to collection trash",
121                                               srcName));
122 
123                         continue;
124                     }
125                 }
126             }
127             else if (m_data->fileConflict() == IOJobData::AutoRename)
128             {
129                 QUrl destUrl = QUrl::fromLocalFile(destenation);
130 
131                 if (srcInfo.isDir())
132                 {
133                     destenation = DFileOperations::getUniqueFolderUrl(destUrl).toLocalFile();
134                 }
135                 else
136                 {
137                     QUrl renamed = DFileOperations::getUniqueFileUrl(destUrl);
138                     destenation  = renamed.toLocalFile();
139                     m_data->setDestUrl(srcUrl, renamed);
140                 }
141             }
142             else
143             {
144                 emit signalError(i18n("A file or folder named %1 already exists in %2",
145                                       srcName, QDir::toNativeSeparators(dstDir.path())));
146 
147                 continue;
148             }
149         }
150 
151         if ((m_data->operation() == IOJobData::MoveAlbum) ||
152             (m_data->operation() == IOJobData::MoveImage) ||
153             (m_data->operation() == IOJobData::MoveFiles))
154         {
155             if (srcInfo.isDir())
156             {
157                 QDir srcDir(srcInfo.filePath());
158 
159                 if (!srcDir.rename(srcDir.path(), destenation))
160                 {
161                     // If QDir::rename fails, try copy and remove.
162 
163                     if      (!DFileOperations::copyFolderRecursively(srcDir.path(), dstDir.path(),
164                                                                      m_data->getProgressId(), &m_cancel))
165                     {
166                         emit signalOneProccessed(srcUrl);
167 
168                         if (m_cancel)
169                         {
170                             break;
171                         }
172 
173                         emit signalError(i18n("Could not move folder %1 to album %2",
174                                               srcName, QDir::toNativeSeparators(dstDir.path())));
175 
176                         continue;
177                     }
178                     else if (!srcDir.removeRecursively())
179                     {
180                         emit signalError(i18n("Could not move folder %1 to album %2. "
181                                               "The folder %1 was copied as well to album %2",
182                                               srcName, QDir::toNativeSeparators(dstDir.path())));
183                     }
184                 }
185             }
186             else
187             {
188                 if (!DFileOperations::renameFile(srcInfo.filePath(), destenation))
189                 {
190                     emit signalError(i18n("Could not move file %1 to album %2",
191                                           srcName, QDir::toNativeSeparators(dstDir.path())));
192 
193                     continue;
194                 }
195            }
196         }
197         else
198         {
199             if (srcInfo.isDir())
200             {
201                 QDir srcDir(srcInfo.filePath());
202 
203                 if (!DFileOperations::copyFolderRecursively(srcDir.path(), dstDir.path(),
204                                                             m_data->getProgressId(), &m_cancel))
205                 {
206                     emit signalOneProccessed(srcUrl);
207 
208                     if (m_cancel)
209                     {
210                         break;
211                     }
212 
213                     emit signalError(i18n("Could not copy folder %1 to album %2",
214                                           srcName, QDir::toNativeSeparators(dstDir.path())));
215 
216                     continue;
217                 }
218             }
219             else
220             {
221                 if (!DFileOperations::copyFile(srcInfo.filePath(), destenation))
222                 {
223                     if (m_data->operation() == IOJobData::CopyToExt)
224                     {
225                         emit signalError(i18n("Could not copy file %1 to folder %2",
226                                          srcName, QDir::toNativeSeparators(dstDir.path())));
227                     }
228                     else
229                     {
230                         emit signalError(i18n("Could not copy file %1 to album %2",
231                                          srcName, QDir::toNativeSeparators(dstDir.path())));
232                     }
233 
234                     continue;
235                 }
236             }
237         }
238 
239         emit signalOneProccessed(srcUrl);
240     }
241 
242     emit signalDone();
243 }
244 
245 // --------------------------------------------
246 
DeleteJob(IOJobData * const data)247 DeleteJob::DeleteJob(IOJobData* const data)
248     : m_data(data)
249 {
250 }
251 
run()252 void DeleteJob::run()
253 {
254     while (m_data && !m_cancel)
255     {
256         QUrl deleteUrl = m_data->getNextUrl();
257 
258         if (deleteUrl.isEmpty())
259         {
260             break;
261         }
262 
263         bool useTrash = (m_data->operation() == IOJobData::Trash);
264 
265         QFileInfo fileInfo(deleteUrl.toLocalFile());
266         qCDebug(DIGIKAM_IOJOB_LOG) << "Deleting:   " << fileInfo.filePath();
267         qCDebug(DIGIKAM_IOJOB_LOG) << "File exists?" << fileInfo.exists();
268         qCDebug(DIGIKAM_IOJOB_LOG) << "Is to trash?" << useTrash;
269 
270         if (!fileInfo.exists())
271         {
272             emit signalError(i18n("File/Folder %1 does not exist",
273                                   QDir::toNativeSeparators(fileInfo.filePath())));
274 
275             continue;
276         }
277 
278         if (useTrash)
279         {
280             if (fileInfo.isDir())
281             {
282                 if (!DTrash::deleteDirRecursivley(deleteUrl.toLocalFile(), m_data->jobTime()))
283                 {
284                     emit signalError(i18n("Could not move folder %1 to collection trash",
285                                           QDir::toNativeSeparators(fileInfo.path())));
286 
287                     continue;
288                 }
289             }
290             else
291             {
292                 if (!DTrash::deleteImage(deleteUrl.toLocalFile(), m_data->jobTime()))
293                 {
294                     emit signalError(i18n("Could not move image %1 to collection trash",
295                                           QDir::toNativeSeparators(fileInfo.filePath())));
296 
297                     continue;
298                 }
299             }
300         }
301         else
302         {
303             if (fileInfo.isDir())
304             {
305                 QDir dir(fileInfo.filePath());
306 
307                 if (!dir.removeRecursively())
308                 {
309                     emit signalError(i18n("Album %1 could not be removed",
310                                           QDir::toNativeSeparators(fileInfo.path())));
311 
312                     continue;
313                 }
314             }
315             else
316             {
317                 QFile file(fileInfo.filePath());
318 
319                 if (!file.remove())
320                 {
321                     emit signalError(i18n("Image %1 could not be removed",
322                                           QDir::toNativeSeparators(fileInfo.filePath())));
323 
324                     continue;
325                 }
326             }
327         }
328 
329         emit signalOneProccessed(deleteUrl);
330     }
331 
332     emit signalDone();
333 }
334 
335 // --------------------------------------------
336 
RenameFileJob(IOJobData * const data)337 RenameFileJob::RenameFileJob(IOJobData* const data)
338     : m_data(data)
339 {
340 }
341 
run()342 void RenameFileJob::run()
343 {
344     while (m_data && !m_cancel)
345     {
346         QUrl renameUrl = m_data->getNextUrl();
347 
348         if (renameUrl.isEmpty())
349         {
350             break;
351         }
352 
353         QUrl destUrl = m_data->destUrl(renameUrl);
354         QFileInfo fileInfo(destUrl.toLocalFile());
355 
356         QDir dir(fileInfo.dir());
357         const QStringList& dirList = dir.entryList(QDir::Dirs    |
358                                                    QDir::Files   |
359                                                    QDir::NoDotAndDotDot);
360 
361         if (dirList.contains(fileInfo.fileName()))
362         {
363             if (m_data->fileConflict() == IOJobData::Overwrite)
364             {
365                 if (!DTrash::deleteImage(destUrl.toLocalFile(), m_data->jobTime()))
366                 {
367                     emit signalError(i18n("Could not move image %1 to collection trash",
368                                           QDir::toNativeSeparators(destUrl.toLocalFile())));
369 
370                     emit signalRenameFailed(renameUrl);
371                     continue;
372                 }
373             }
374             else
375             {
376                 qCDebug(DIGIKAM_IOJOB_LOG) << "File with the same name exists!";
377                 emit signalError(i18n("Image with the same name %1 already there",
378                                       QDir::toNativeSeparators(destUrl.toLocalFile())));
379 
380                 emit signalRenameFailed(renameUrl);
381                 continue;
382             }
383         }
384 
385         qCDebug(DIGIKAM_IOJOB_LOG) << "Trying to rename"
386                                    << renameUrl.toLocalFile() << "to"
387                                    << destUrl.toLocalFile();
388 
389         if (!DFileOperations::renameFile(renameUrl.toLocalFile(), destUrl.toLocalFile()))
390         {
391             qCDebug(DIGIKAM_IOJOB_LOG) << "File could not be renamed!";
392             emit signalError(i18n("Image %1 could not be renamed",
393                                   QDir::toNativeSeparators(renameUrl.toLocalFile())));
394 
395             emit signalRenameFailed(renameUrl);
396             continue;
397         }
398 
399         emit signalOneProccessed(renameUrl);
400     }
401 
402     emit signalDone();
403 }
404 
405 // ----------------------------------------------
406 
DTrashItemsListingJob(const QString & collectionPath)407 DTrashItemsListingJob::DTrashItemsListingJob(const QString& collectionPath)
408     : m_collectionPath(collectionPath)
409 {
410 }
411 
run()412 void DTrashItemsListingJob::run()
413 {
414     DTrashItemInfo itemInfo;
415 
416     QString collectionTrashFilesPath = m_collectionPath + QLatin1Char('/') + DTrash::TRASH_FOLDER +
417                                        QLatin1Char('/') + DTrash::FILES_FOLDER;
418 
419     qCDebug(DIGIKAM_IOJOB_LOG) << "Collection trash files path:" << collectionTrashFilesPath;
420 
421     QDir filesDir(collectionTrashFilesPath);
422 
423     foreach (const QFileInfo& fileInfo, filesDir.entryInfoList(QDir::Files))
424     {
425         qCDebug(DIGIKAM_IOJOB_LOG) << "File in trash:" << fileInfo.filePath();
426         itemInfo.trashPath = fileInfo.filePath();
427 
428         DTrash::extractJsonForItem(m_collectionPath, fileInfo.baseName(), itemInfo);
429 
430         emit trashItemInfo(itemInfo);
431     }
432 
433     emit signalDone();
434 }
435 
436 // ----------------------------------------------
437 
RestoreDTrashItemsJob(IOJobData * const data)438 RestoreDTrashItemsJob::RestoreDTrashItemsJob(IOJobData* const data)
439     : m_data(data)
440 {
441 }
442 
run()443 void RestoreDTrashItemsJob::run()
444 {
445     if (!m_data)
446     {
447         return;
448     }
449 
450     foreach (const DTrashItemInfo& item, m_data->trashItems())
451     {
452         QUrl srcToRename = QUrl::fromLocalFile(item.collectionPath);
453         QUrl newName     = DFileOperations::getUniqueFileUrl(srcToRename);
454 
455         QFileInfo fi(item.collectionPath);
456 
457         if (!fi.dir().exists())
458         {
459             fi.dir().mkpath(fi.dir().path());
460         }
461 
462         if (!QFile::rename(item.trashPath, newName.toLocalFile()))
463         {
464             emit signalError(i18n("Could not restore file %1 from trash",
465                                   QDir::toNativeSeparators(newName.toLocalFile())));
466         }
467         else
468         {
469             QFile::remove(item.jsonFilePath);
470         }
471 
472         emit signalOneProccessed(newName);
473     }
474 
475     emit signalDone();
476 }
477 
478 // ----------------------------------------------
479 
EmptyDTrashItemsJob(IOJobData * const data)480 EmptyDTrashItemsJob::EmptyDTrashItemsJob(IOJobData* const data)
481     : m_data(data)
482 {
483 }
484 
run()485 void EmptyDTrashItemsJob::run()
486 {
487     if (!m_data)
488     {
489         return;
490     }
491 
492     QList<int> albumsFromImages;
493     QList<qlonglong> imagesToRemove;
494 
495     foreach (const DTrashItemInfo& item, m_data->trashItems())
496     {
497         QFile::remove(item.trashPath);
498         QFile::remove(item.jsonFilePath);
499 
500         imagesToRemove   << item.imageId;
501         albumsFromImages << ItemInfo(item.imageId).albumId();
502 
503         emit signalOneProccessed(QUrl());
504     }
505 
506     {
507         CoreDbOperationGroup group;
508         group.setMaximumTime(200);
509 
510         CoreDbAccess().db()->removeItemsPermanently(imagesToRemove, albumsFromImages);
511     }
512 
513     emit signalDone();
514 }
515 
516 } // namespace Digikam
517