1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642 | /* ============================================================
+ *
+ * This file is a part of digiKam project
+ * https://www.digikam.org
+ *
+ * Date : 2005-05-17
+ * Description : low level files management interface.
+ *
+ * Copyright (C) 2005 by Renchi Raju <renchi dot raju at gmail dot com>
+ * Copyright (C) 2012-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+ * Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
+ * Copyright (C) 2018 by Maik Qualmann <metzpinguin at gmail dot com>
+ *
+ * This program is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General
+ * Public License as published by the Free Software Foundation;
+ * either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * ============================================================ */
+
+#include "dio.h"
+
+// Qt includes
+
+#include <QFileInfo>
+
+// KDE includes
+
+#include <klocalizedstring.h>
+
+// Local includes
+
+#include "digikam_debug.h"
+#include "iteminfo.h"
+#include "diofinders.h"
+#include "albummanager.h"
+#include "tagscache.h"
+#include "coredb.h"
+#include "coredbaccess.h"
+#include "album.h"
+#include "dmetadata.h"
+#include "metaenginesettings.h"
+#include "scancontroller.h"
+#include "thumbsdb.h"
+#include "thumbsdbaccess.h"
+#include "iojobsmanager.h"
+#include "collectionmanager.h"
+#include "dnotificationwrapper.h"
+#include "loadingcacheinterface.h"
+#include "progressmanager.h"
+#include "digikamapp.h"
+#include "iojobdata.h"
+
+namespace Digikam
+{
+
+class Q_DECL_HIDDEN DIOCreator
+{
+public:
+
+ DIO object;
+};
+
+Q_GLOBAL_STATIC(DIOCreator, creator)
+
+// ------------------------------------------------------------------------------------------------
+
+DIO* DIO::instance()
+{
+ return &creator->object;
+}
+
+DIO::DIO()
+{
+ m_processingCount = 0;
+}
+
+DIO::~DIO()
+{
+}
+
+void DIO::cleanUp()
+{
+}
+
+bool DIO::itemsUnderProcessing()
+{
+ return instance()->m_processingCount;
+}
+
+// Album -> Album -----------------------------------------------------
+
+void DIO::copy(PAlbum* const src, PAlbum* const dest)
+{
+ if (!src || !dest)
+ {
+ return;
+ }
+
+ instance()->processJob(new IOJobData(IOJobData::CopyAlbum, src, dest));
+}
+
+void DIO::move(PAlbum* const src, PAlbum* const dest)
+{
+ if (!src || !dest)
+ {
+ return;
+ }
+
+#ifdef Q_OS_WIN
+ AlbumManager::instance()->removeWatchedPAlbums(src);
+#endif
+
+ instance()->processJob(new IOJobData(IOJobData::MoveAlbum, src, dest));
+}
+
+// Images -> Album ----------------------------------------------------
+
+void DIO::copy(const QList<ItemInfo>& infos, PAlbum* const dest)
+{
+ if (!dest)
+ {
+ return;
+ }
+
+ instance()->processJob(new IOJobData(IOJobData::CopyImage, infos, dest));
+}
+
+void DIO::move(const QList<ItemInfo>& infos, PAlbum* const dest)
+{
+ if (!dest)
+ {
+ return;
+ }
+
+ instance()->processJob(new IOJobData(IOJobData::MoveImage, infos, dest));
+}
+
+// External files -> album --------------------------------------------
+
+void DIO::copy(const QUrl& src, PAlbum* const dest)
+{
+ copy(QList<QUrl>() << src, dest);
+}
+
+void DIO::copy(const QList<QUrl>& srcList, PAlbum* const dest)
+{
+ if (!dest)
+ {
+ return;
+ }
+
+ instance()->processJob(new IOJobData(IOJobData::CopyFiles, srcList, dest));
+}
+
+void DIO::move(const QUrl& src, PAlbum* const dest)
+{
+ move(QList<QUrl>() << src, dest);
+}
+
+void DIO::move(const QList<QUrl>& srcList, PAlbum* const dest)
+{
+ if (!dest)
+ {
+ return;
+ }
+
+ instance()->processJob(new IOJobData(IOJobData::MoveFiles, srcList, dest));
+}
+
+// Rename --------------------------------------------------------------
+
+void DIO::rename(const QUrl& src, const QString& newName, bool overwrite)
+{
+ if (src.isEmpty() || newName.isEmpty())
+ {
+ return;
+ }
+
+ ItemInfo info = ItemInfo::fromUrl(src);
+
+ instance()->processJob(new IOJobData(IOJobData::Rename, info, newName, overwrite));
+}
+
+// Delete --------------------------------------------------------------
+
+void DIO::del(const QList<ItemInfo>& infos, bool useTrash)
+{
+ instance()->processJob(new IOJobData(useTrash ? IOJobData::Trash
+ : IOJobData::Delete, infos));
+}
+
+void DIO::del(const ItemInfo& info, bool useTrash)
+{
+ del(QList<ItemInfo>() << info, useTrash);
+}
+
+void DIO::del(PAlbum* const album, bool useTrash)
+{
+ if (!album)
+ {
+ return;
+ }
+
+#ifdef Q_OS_WIN
+ AlbumManager::instance()->removeWatchedPAlbums(album);
+#endif
+
+ instance()->createJob(new IOJobData(useTrash ? IOJobData::Trash
+ : IOJobData::Delete, album));
+}
+
+// Restore Trash -------------------------------------------------------
+
+void DIO::restoreTrash(const DTrashItemInfoList& infos)
+{
+ instance()->createJob(new IOJobData(IOJobData::Restore, infos));
+}
+
+// Empty Trash ---------------------------------------------------------
+
+void DIO::emptyTrash(const DTrashItemInfoList& infos)
+{
+ instance()->createJob(new IOJobData(IOJobData::Empty, infos));
+}
+
+// ------------------------------------------------------------------------------------------------
+
+void DIO::processJob(IOJobData* const data)
+{
+ const int operation = data->operation();
+
+ if (operation == IOJobData::CopyImage || operation == IOJobData::MoveImage)
+ {
+ // this is a fast db operation, do here
+ GroupedImagesFinder finder(data->itemInfos());
+ data->setItemInfos(finder.infos);
+
+ QStringList filenames;
+ QList<qlonglong> ids;
+
+ foreach (const ItemInfo& info, data->itemInfos())
+ {
+ filenames << info.name();
+ ids << info.id();
+ }
+
+ ScanController::instance()->hintAtMoveOrCopyOfItems(ids, data->destAlbum(), filenames);
+ }
+ else if (operation == IOJobData::CopyAlbum || operation == IOJobData::MoveAlbum)
+ {
+ ScanController::instance()->hintAtMoveOrCopyOfAlbum(data->srcAlbum(), data->destAlbum());
+ createJob(data);
+ return;
+ }
+ else if (operation == IOJobData::Delete || operation == IOJobData::Trash)
+ {
+ qCDebug(DIGIKAM_DATABASE_LOG) << "Number of files to be deleted:" << data->sourceUrls().count();
+ }
+
+ SidecarFinder finder(data->sourceUrls());
+ data->setSourceUrls(finder.localFiles);
+
+ if (operation == IOJobData::Rename)
+ {
+ if (!data->itemInfos().isEmpty())
+ {
+ ItemInfo info = data->itemInfos().first();<--- Shadowed declaration
+ PAlbum* const album = AlbumManager::instance()->findPAlbum(info.albumId());
+
+ if (album)
+ {
+ ScanController::instance()->hintAtMoveOrCopyOfItem(info.id(), album,
+ data->destUrl().fileName());
+ }
+
+ for (int i = 0 ; i < finder.localFiles.length() ; ++i)
+ {
+ if (finder.localFileModes.at(i))
+ {
+ data->setDestUrl(finder.localFiles.at(i),
+ QUrl::fromLocalFile(data->destUrl().toLocalFile() +
+ finder.localFileSuffixes.at(i)));
+ }
+ else
+ {
+ QFileInfo info(data->destUrl().toLocalFile());<--- Shadow variable
+
+ data->setDestUrl(finder.localFiles.at(i),
+ QUrl::fromLocalFile(info.path() +
+ QLatin1Char('/') +
+ info.completeBaseName() +
+ finder.localFileSuffixes.at(i)));
+ }
+ }
+ }
+ }
+
+ createJob(data);
+}
+
+void DIO::createJob(IOJobData* const data)
+{
+ if (data->sourceUrls().isEmpty())
+ {
+ delete data;
+ return;
+ }
+
+ ProgressItem* item = nullptr;
+ QString itemString = getItemString(data);
+
+ if (!itemString.isEmpty())
+ {
+ item = ProgressManager::instance()->createProgressItem(itemString,
+ QString(), true, false);
+ item->setTotalItems(data->sourceUrls().count());
+ data->setProgressId(item->id());
+ }
+
+ IOJobsThread* const jobThread = IOJobsManager::instance()->startIOJobs(data);
+
+ connect(jobThread, SIGNAL(signalOneProccessed(QUrl)),
+ this, SLOT(slotOneProccessed(QUrl)));
+
+ connect(jobThread, SIGNAL(finished()),
+ this, SLOT(slotResult()));
+
+ if (data->operation() == IOJobData::Rename)
+ {
+ connect(jobThread, SIGNAL(signalRenameFailed(QUrl)),
+ this, SIGNAL(signalRenameFailed(QUrl)));
+
+ connect(jobThread, SIGNAL(finished()),
+ this, SIGNAL(signalRenameFinished()));
+ }
+
+ if (data->operation() == IOJobData::Empty ||
+ data->operation() == IOJobData::Restore)
+ {
+ connect(jobThread, SIGNAL(finished()),
+ this, SIGNAL(signalTrashFinished()));
+ }
+
+ if (item)
+ {
+ connect(item, SIGNAL(progressItemCanceled(ProgressItem*)),
+ jobThread, SLOT(slotCancel()));
+
+ connect(item, SIGNAL(progressItemCanceled(ProgressItem*)),
+ this, SLOT(slotCancel(ProgressItem*)));
+ }
+
+ ++m_processingCount;
+}
+
+void DIO::slotResult()
+{
+ IOJobsThread* const jobThread = dynamic_cast<IOJobsThread*>(sender());
+
+ if (!jobThread || !jobThread->jobData())
+ {
+ return;
+ }
+
+ IOJobData* const data = jobThread->jobData();
+
+ if (jobThread->hasErrors() && data->operation() != IOJobData::Rename)
+ {
+ // Pop-up a message about the error.
+ QString errors = jobThread->errorsList().join(QLatin1Char('\n'));
+ DNotificationWrapper(QString(), errors, DigikamApp::instance(),
+ DigikamApp::instance()->windowTitle());
+ }
+
+ if (m_processingCount)
+ {
+ --m_processingCount;
+ }
+
+ slotCancel(getProgressItem(data));
+}
+
+void DIO::slotOneProccessed(const QUrl& url)
+{
+ IOJobsThread* const jobThread = dynamic_cast<IOJobsThread*>(sender());
+
+ if (!jobThread || !jobThread->jobData())
+ {
+ return;
+ }
+
+ IOJobData* const data = jobThread->jobData();
+ const int operation = data->operation();
+
+ if (operation == IOJobData::MoveImage)
+ {
+ ItemInfo info = data->findItemInfo(url);
+
+ if (!info.isNull() && data->destAlbum())
+ {
+ CoreDbAccess().db()->moveItem(info.albumId(), info.name(),
+ data->destAlbum()->id(), info.name());
+ }
+ }
+ else if (operation == IOJobData::Delete)
+ {
+ // Mark the images as obsolete and remove them
+ // from their album and from the grouped
+ PAlbum* const album = data->srcAlbum();
+
+ if (album && album->fileUrl() == url)
+ {
+ // get all deleted albums
+ CoreDbAccess access;
+ QList<int> albumsToDelete;
+ QList<qlonglong> imagesToRemove;
+
+ addAlbumChildrenToList(albumsToDelete, album);
+
+ foreach (int albumId, albumsToDelete)
+ {
+ imagesToRemove << access.db()->getItemIDsInAlbum(albumId);
+ }
+
+ foreach (const qlonglong& imageId, imagesToRemove)
+ {
+ access.db()->removeAllImageRelationsFrom(imageId,
+ DatabaseRelation::Grouped);
+ }
+
+ access.db()->removeItemsPermanently(imagesToRemove, albumsToDelete);
+ }
+ else
+ {
+ ItemInfo info = data->findItemInfo(url);
+
+ if (!info.isNull())
+ {
+ int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
+ int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph());
+ QList<qlonglong> imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom);
+
+ CoreDbAccess access;
+
+ foreach (const qlonglong& id, imageIds)
+ {
+ access.db()->removeItemTag(id, originalVersionTag);
+ access.db()->addItemTag(id, needTaggingTag);
+ }
+
+ access.db()->removeAllImageRelationsFrom(info.id(),
+ DatabaseRelation::Grouped);
+
+ access.db()->removeItemsPermanently(QList<qlonglong>() << info.id(),
+ QList<int>() << info.albumId());
+ }
+ }
+ }
+ else if (operation == IOJobData::Trash)
+ {
+ ItemInfo info = data->findItemInfo(url);
+
+ if (!info.isNull())
+ {
+ int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
+ int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph());
+ QList<qlonglong> imageIds = CoreDbAccess().db()->getImagesRelatedFrom(info.id(), DatabaseRelation::DerivedFrom);
+
+ CoreDbAccess access;
+
+ foreach (const qlonglong& id, imageIds)
+ {
+ access.db()->removeItemTag(id, originalVersionTag);
+ access.db()->addItemTag(id, needTaggingTag);
+ }
+
+ access.db()->removeItems(QList<qlonglong>() << info.id(),
+ QList<int>() << info.albumId());
+ }
+ }
+ else if (operation == IOJobData::Rename)
+ {
+ ItemInfo info = data->findItemInfo(url);
+
+ if (!info.isNull())
+ {
+ QString oldPath = url.toLocalFile();
+ QString newName = data->destUrl(url).fileName();
+ QString newPath = data->destUrl(url).toLocalFile();
+
+ if (data->overwrite())
+ {
+ ThumbsDbAccess().db()->removeByFilePath(newPath);
+ LoadingCacheInterface::fileChanged(newPath, false);
+ CoreDbAccess().db()->deleteItem(info.albumId(), newName);
+ }
+
+ ThumbsDbAccess().db()->renameByFilePath(oldPath, newPath);
+ // Remove old thumbnails and images from the cache
+ LoadingCacheInterface::fileChanged(oldPath, false);
+ // Rename in ItemInfo and database
+ info.setName(newName);
+ }
+ }
+
+ // Scan folders for changes
+
+ if (operation == IOJobData::Delete || operation == IOJobData::Trash ||
+ operation == IOJobData::MoveAlbum)
+ {
+ PAlbum* const album = data->srcAlbum();
+ QString scanPath;
+
+ if (album)
+ {
+ PAlbum* const parent = dynamic_cast<PAlbum*>(album->parent());
+
+ if (parent)
+ {
+ scanPath = parent->fileUrl().toLocalFile();
+ }
+ }
+
+ if (scanPath.isEmpty())
+ {
+ scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile();
+ }
+
+ ScanController::instance()->scheduleCollectionScanRelaxed(scanPath);
+ }
+
+ if (operation == IOJobData::CopyImage || operation == IOJobData::CopyAlbum ||
+ operation == IOJobData::CopyFiles || operation == IOJobData::MoveImage ||
+ operation == IOJobData::MoveAlbum || operation == IOJobData::MoveFiles)
+ {
+ QString scanPath = data->destUrl().toLocalFile();
+ ScanController::instance()->scheduleCollectionScanRelaxed(scanPath);
+ }
+
+ if (operation == IOJobData::Restore)
+ {
+ QString scanPath = url.adjusted(QUrl::RemoveFilename).toLocalFile();
+ ScanController::instance()->scheduleCollectionScanRelaxed(scanPath);
+ }
+
+ ProgressItem* const item = getProgressItem(data);
+
+ if (item)
+ {
+ item->advance(1);
+ }
+}
+
+QString DIO::getItemString(IOJobData* const data) const
+{
+ switch (data->operation())
+ {
+ case IOJobData::CopyAlbum:
+ return i18n("Copy Album");
+
+ case IOJobData::CopyImage:
+ return i18n("Copy Images");
+
+ case IOJobData::CopyFiles:
+ return i18n("Copy Files");
+
+ case IOJobData::MoveAlbum:
+ return i18n("Move Album");
+
+ case IOJobData::MoveImage:
+ return i18n("Move Images");
+
+ case IOJobData::MoveFiles:
+ return i18n("Move Files");
+
+ case IOJobData::Delete:
+ return i18n("Delete");
+
+ case IOJobData::Trash:
+ return i18n("Trash");
+
+ case IOJobData::Restore:
+ return i18n("Restore Trash");
+
+ case IOJobData::Empty:
+ return i18n("Empty Trash");
+
+ default:
+ break;
+ }
+
+ return QString();
+}
+
+ProgressItem* DIO::getProgressItem(IOJobData* const data) const
+{
+ QString itemId = data->getProgressId();
+
+ if (itemId.isEmpty())
+ {
+ return nullptr;
+ }
+
+ return ProgressManager::instance()->findItembyId(itemId);
+}
+
+void DIO::slotCancel(ProgressItem* item)
+{
+ if (item)
+ {
+ item->setComplete();
+ }
+}
+
+void DIO::addAlbumChildrenToList(QList<int>& list, Album* const album)
+{
+ // simple recursive helper function
+ if (album)
+ {
+ if (!list.contains(album->id()))
+ {
+ list.append(album->id());
+ }
+
+ AlbumIterator it(album);
+
+ while (it.current())
+ {
+ addAlbumChildrenToList(list, *it);
+ ++it;
+ }
+ }
+}
+
+} // namespace Digikam
+
+ |