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