File indexing completed on 2025-04-27 03:58:06

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-01-20
0007  * Description : image file IO threaded interface.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2005-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "managedloadsavethread.h"
0017 
0018 // Local includes
0019 
0020 #include "digikam_debug.h"
0021 #include "loadsavetask.h"
0022 #include "previewtask.h"
0023 #include "thumbnailtask.h"
0024 
0025 namespace Digikam
0026 {
0027 
0028 ManagedLoadSaveThread::ManagedLoadSaveThread(QObject* const parent)
0029     : LoadSaveThread     (parent),
0030       m_loadingPolicy    (LoadingPolicyAppend),
0031       m_terminationPolicy(TerminationPolicyTerminateLoading)
0032 {
0033 }
0034 
0035 ManagedLoadSaveThread::~ManagedLoadSaveThread()
0036 {
0037     shutDown();
0038 }
0039 
0040 void ManagedLoadSaveThread::shutDown()
0041 {
0042     switch (m_terminationPolicy)
0043     {
0044         case TerminationPolicyTerminateLoading:
0045         {
0046             QMutexLocker lock(threadMutex());
0047             LoadingTask* loadingTask = nullptr;
0048 
0049             if ((loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)))
0050             {
0051                 loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0052             }
0053 
0054             removeLoadingTasks(LoadingDescription(QString()), LoadingTaskFilterAll);
0055             break;
0056         }
0057 
0058         case TerminationPolicyTerminatePreloading:
0059         {
0060             QMutexLocker lock(threadMutex());
0061             LoadingTask* loadingTask = nullptr;
0062 
0063             if ((loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)))
0064             {
0065                 loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0066             }
0067 
0068             removeLoadingTasks(LoadingDescription(QString()), LoadingTaskFilterPreloading);
0069             break;
0070         }
0071 
0072         case TerminationPolicyWait:
0073         {
0074             break;
0075         }
0076 
0077         case TerminationPolicyTerminateAll:
0078         {
0079             stopAllTasks();
0080             break;
0081         }
0082     }
0083 
0084     LoadSaveThread::shutDown();
0085 }
0086 
0087 LoadingTask* ManagedLoadSaveThread::checkLoadingTask(LoadSaveTask* const task, LoadingTaskFilter filter) const
0088 {
0089     if (task && (task->type() == LoadSaveTask::TaskTypeLoading))
0090     {
0091         LoadingTask* const loadingTask = dynamic_cast<LoadingTask*>(task);
0092 
0093         if      (filter == LoadingTaskFilterAll)
0094         {
0095             return loadingTask;
0096         }
0097         else if (filter == LoadingTaskFilterPreloading)
0098         {
0099             if (loadingTask && (loadingTask->status() == LoadingTask::LoadingTaskStatusPreloading))
0100             {
0101                 return loadingTask;
0102             }
0103         }
0104     }
0105 
0106     return nullptr;
0107 }
0108 
0109 LoadingTask* ManagedLoadSaveThread::findExistingTask(const LoadingDescription& loadingDescription) const
0110 {
0111     LoadingTask* loadingTask = nullptr;
0112 
0113     if (m_currentTask)
0114     {
0115         if (m_currentTask->type() == LoadSaveTask::TaskTypeLoading)
0116         {
0117             loadingTask = dynamic_cast<LoadingTask*>(m_currentTask);
0118 
0119             if (loadingTask)
0120             {
0121                 const LoadingDescription& taskDescription = loadingTask->loadingDescription();
0122 
0123                 if (taskDescription == loadingDescription)
0124                 {
0125                     return loadingTask;
0126                 }
0127             }
0128         }
0129     }
0130 
0131     for (int i = 0 ; i < m_todo.size() ; ++i)
0132     {
0133         LoadSaveTask* const task = m_todo.at(i);
0134 
0135         if (task->type() == LoadSaveTask::TaskTypeLoading)
0136         {
0137             loadingTask = dynamic_cast<LoadingTask*>(task);
0138 
0139             if (loadingTask && (loadingTask->loadingDescription() == loadingDescription))
0140             {
0141                 return loadingTask;
0142             }
0143         }
0144     }
0145 
0146     return nullptr;
0147 }
0148 
0149 void ManagedLoadSaveThread::setTerminationPolicy(TerminationPolicy terminationPolicy)
0150 {
0151     m_terminationPolicy = terminationPolicy;
0152 }
0153 
0154 ManagedLoadSaveThread::TerminationPolicy ManagedLoadSaveThread::terminationPolicy() const
0155 {
0156     return m_terminationPolicy;
0157 }
0158 
0159 void ManagedLoadSaveThread::setLoadingPolicy(LoadingPolicy policy)
0160 {
0161     m_loadingPolicy = policy;
0162 }
0163 
0164 ManagedLoadSaveThread::LoadingPolicy ManagedLoadSaveThread::loadingPolicy() const
0165 {
0166     return m_loadingPolicy;
0167 }
0168 
0169 void ManagedLoadSaveThread::load(const LoadingDescription& description, LoadingPolicy policy)
0170 {
0171     load(description, LoadingModeNormal, policy);
0172 }
0173 
0174 void ManagedLoadSaveThread::load(const LoadingDescription& description)
0175 {
0176     load(description, LoadingModeNormal, m_loadingPolicy);
0177 }
0178 
0179 void ManagedLoadSaveThread::load(const LoadingDescription& description, LoadingMode loadingMode,
0180                                  AccessMode accessMode)
0181 {
0182     load(description, loadingMode, m_loadingPolicy, accessMode);
0183 }
0184 
0185 void ManagedLoadSaveThread::load(const LoadingDescription& description, LoadingMode loadingMode,
0186                                  LoadingPolicy policy, AccessMode accessMode)
0187 {
0188     QMutexLocker lock(threadMutex());
0189     LoadingTask* loadingTask  = nullptr;
0190     LoadingTask* existingTask = nullptr;
0191 
0192     if ((policy != LoadingPolicySimplePrepend) && (policy != LoadingPolicySimpleAppend))
0193     {
0194         existingTask = findExistingTask(description);
0195     }
0196 
0197     //qCDebug(DIGIKAM_GENERAL_LOG) << "ManagedLoadSaveThread::load " << description.filePath << ", policy " << policy;
0198 
0199     switch (policy)
0200     {
0201         case LoadingPolicyFirstRemovePrevious:
0202         {
0203             // reuse task if it exists
0204 
0205             if (existingTask)
0206             {
0207                 existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
0208             }
0209 
0210             // stop current task
0211 
0212             if ((loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterAll)))
0213             {
0214                 if (loadingTask != existingTask)
0215                 {
0216                     loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0217                 }
0218             }
0219 /*
0220             qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingPolicyFirstRemovePrevious, Existing task " << existingTask
0221                                          << ", m_currentTask " << m_currentTask << ", loadingTask " << loadingTask;
0222 */
0223             // remove all loading tasks
0224 
0225             for (int i = 0 ; i < m_todo.size() ; ++i)
0226             {
0227                 LoadingTask* task = nullptr;
0228 
0229                 if ((task = checkLoadingTask(m_todo.at(i), LoadingTaskFilterAll)))
0230                 {
0231                     if (task != existingTask)
0232                     {
0233 /*
0234                         qCDebug(DIGIKAM_GENERAL_LOG) << "Removing task " << task << " from list";
0235 */
0236                         delete m_todo.takeAt(i--);
0237                     }
0238                 }
0239             }
0240 
0241             // append new, exclusive loading task
0242 
0243             if (existingTask)
0244             {
0245                 break;
0246             }
0247 
0248             m_todo.append(createLoadingTask(description, false, loadingMode, accessMode));
0249             break;
0250         }
0251 
0252         case LoadingPolicyPrepend:
0253         {
0254             if (existingTask)
0255             {
0256                 existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
0257             }
0258 
0259             // stop and postpone current task if it is a preloading task
0260 
0261             if ((loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)))
0262             {
0263                 loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0264                 load(loadingTask->loadingDescription(), LoadingPolicyPreload);
0265             }
0266 /*
0267             qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingPolicyPrepend, Existing task " << existingTask << ", m_currentTask " << m_currentTask;
0268 */
0269             // prepend new loading task
0270 
0271             if (existingTask)
0272             {
0273                 break;
0274             }
0275 
0276             m_todo.prepend(createLoadingTask(description, false, loadingMode, accessMode));
0277             break;
0278         }
0279 
0280         case LoadingPolicySimplePrepend:
0281         {
0282             m_todo.prepend(createLoadingTask(description, false, loadingMode, accessMode));
0283             break;
0284         }
0285 
0286         case LoadingPolicyAppend:
0287         {
0288             if (existingTask)
0289             {
0290                 existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
0291             }
0292 
0293             // stop and postpone current task if it is a preloading task
0294 
0295             if ((loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)))
0296             {
0297                 loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0298                 load(loadingTask->loadingDescription(), LoadingPolicyPreload);
0299             }
0300 
0301             if (existingTask)
0302             {
0303                 break;
0304             }
0305 /*
0306             qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingPolicyAppend, Existing task " << existingTask << ", m_currentTask " << m_currentTask;
0307 */
0308             // append new loading task, put it in front of preloading tasks
0309 
0310             int i;
0311 
0312             for (i = 0 ; i < m_todo.count() ; ++i)
0313             {
0314                 if (checkLoadingTask(m_todo.at(i), LoadingTaskFilterPreloading))
0315                 {
0316                     break;
0317                 }
0318             }
0319 
0320             m_todo.insert(i, createLoadingTask(description, false, loadingMode, accessMode));
0321             break;
0322         }
0323 
0324         case LoadingPolicySimpleAppend:
0325         {
0326             m_todo.append(createLoadingTask(description, false, loadingMode, accessMode));
0327             break;
0328         }
0329 
0330         case LoadingPolicyPreload:
0331         {
0332             // append to the very end of the list
0333 /*
0334             qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingPolicyPreload, Existing task " << existingTask;
0335 */
0336             if (existingTask)
0337             {
0338                 break;
0339             }
0340 
0341             m_todo.append(createLoadingTask(description, true, loadingMode, accessMode));
0342             break;
0343         }
0344     }
0345 
0346     start(lock);
0347 }
0348 
0349 void ManagedLoadSaveThread::loadPreview(const LoadingDescription& description, LoadingPolicy policy)
0350 {
0351     // Preview threads typically only support preview tasks,
0352     // so no need to differentiate with normal loading tasks.
0353 
0354     load(description, LoadingModeShared, policy);
0355 }
0356 
0357 void ManagedLoadSaveThread::loadThumbnail(const LoadingDescription& description)
0358 {
0359     // Thumbnail threads typically only support thumbnail tasks,
0360     // so no need to differentiate with normal loading tasks.
0361 
0362     QMutexLocker lock(threadMutex());
0363     LoadingTask* const existingTask = findExistingTask(description);
0364 
0365     // reuse task if it exists
0366 
0367     if (existingTask)
0368     {
0369         existingTask->setStatus(LoadingTask::LoadingTaskStatusLoading);
0370         return;
0371     }
0372 
0373     // append new loading task
0374 
0375     m_todo.prepend(new ThumbnailLoadingTask(this, description));
0376 
0377     start(lock);
0378 }
0379 
0380 void ManagedLoadSaveThread::preloadThumbnail(const LoadingDescription& description)
0381 {
0382     QMutexLocker lock(threadMutex());
0383     LoadingTask* const existingTask = findExistingTask(description);
0384 
0385     // reuse task if it exists
0386 
0387     if (existingTask)
0388     {
0389         return;
0390     }
0391 
0392     // create new loading task
0393 
0394     ThumbnailLoadingTask* const task = new ThumbnailLoadingTask(this, description);
0395 
0396     // mark as preload task
0397 
0398     task->setStatus(LoadingTask::LoadingTaskStatusPreloading);
0399 
0400     // append to the end of the list
0401 
0402     m_todo.append(task);
0403     start(lock);
0404 }
0405 
0406 void ManagedLoadSaveThread::preloadThumbnailGroup(const QList<LoadingDescription>& descriptions)
0407 {
0408     if (descriptions.isEmpty())
0409     {
0410         return;
0411     }
0412 
0413     QMutexLocker lock(threadMutex());
0414     QList<LoadSaveTask*> todo;
0415 
0416     Q_FOREACH (const LoadingDescription& description, descriptions)
0417     {
0418         LoadingTask* const existingTask = findExistingTask(description);
0419 
0420         // reuse task if it exists
0421 
0422         if (existingTask)
0423         {
0424             continue;
0425         }
0426 
0427         // create new loading task
0428 
0429         ThumbnailLoadingTask* const task = new ThumbnailLoadingTask(this, description);
0430 
0431         // mark as preload task
0432 
0433         task->setStatus(LoadingTask::LoadingTaskStatusPreloading);
0434 
0435         // append to the end of the list
0436 
0437         todo << task;
0438     }
0439 
0440     if (!todo.isEmpty())
0441     {
0442         m_todo << todo;
0443         start(lock);
0444     }
0445 }
0446 
0447 void ManagedLoadSaveThread::prependThumbnailGroup(const QList<LoadingDescription>& descriptions)
0448 {
0449     // This method is meant to prepend a group of loading tasks after the current task,
0450     // in the order they are given here, pushing the existing tasks to the back respectively removing double tasks.
0451 
0452     if (descriptions.isEmpty())
0453     {
0454         return;
0455     }
0456 
0457     QMutexLocker lock(threadMutex());
0458 
0459     int index = 0;
0460 
0461     for (int i = 0 ; i < descriptions.size() ; ++i)
0462     {
0463         LoadingTask* const existingTask = findExistingTask(descriptions.at(i));
0464 
0465         // remove task, if not the current task
0466 
0467         if (existingTask)
0468         {
0469             if (existingTask == dynamic_cast<LoadingTask*>(m_currentTask))
0470             {
0471                 continue;
0472             }
0473 
0474             m_todo.removeAll(existingTask);
0475 
0476             if (index > m_todo.size())
0477             {
0478                 --index;
0479             }
0480 
0481             delete existingTask;
0482         }
0483 
0484         // insert new loading task, in the order given by descriptions list
0485 
0486         m_todo.insert(index++, new ThumbnailLoadingTask(this, descriptions.at(i)));
0487     }
0488 
0489     start(lock);
0490 }
0491 
0492 LoadingTask* ManagedLoadSaveThread::createLoadingTask(const LoadingDescription& description,
0493                                                       bool preloading, LoadingMode loadingMode,
0494                                                       AccessMode accessMode)
0495 {
0496     if (description.previewParameters.type == LoadingDescription::PreviewParameters::PreviewImage)
0497     {
0498         return new PreviewLoadingTask(this, description);
0499     }
0500 
0501     if (loadingMode == LoadingModeShared)
0502     {
0503         if (preloading)
0504         {
0505             return new SharedLoadingTask(this, description, accessMode, LoadingTask::LoadingTaskStatusPreloading);
0506         }
0507         else
0508         {
0509             return new SharedLoadingTask(this, description, accessMode);
0510         }
0511     }
0512     else
0513     {
0514         if (preloading)
0515         {
0516             return new LoadingTask(this, description, LoadingTask::LoadingTaskStatusPreloading);
0517         }
0518         else
0519         {
0520             return new LoadingTask(this, description);
0521         }
0522     }
0523 }
0524 
0525 void ManagedLoadSaveThread::stopLoading(const QString& filePath, LoadingTaskFilter filter)
0526 {
0527     QMutexLocker lock(threadMutex());
0528     removeLoadingTasks(LoadingDescription(filePath), filter);
0529 }
0530 
0531 void ManagedLoadSaveThread::stopLoading(const LoadingDescription& desc, LoadingTaskFilter filter)
0532 {
0533     QMutexLocker lock(threadMutex());
0534     removeLoadingTasks(desc, filter);
0535 }
0536 
0537 void ManagedLoadSaveThread::stopAllTasks()
0538 {
0539     QMutexLocker lock(threadMutex());
0540 
0541     if (m_currentTask)
0542     {
0543         if      (m_currentTask->type() == LoadSaveTask::TaskTypeSaving)
0544         {
0545             SavingTask* const savingTask = dynamic_cast<SavingTask*>(m_currentTask);
0546 
0547             if (savingTask)
0548             {
0549                 savingTask->setStatus(SavingTask::SavingTaskStatusStopping);
0550             }
0551         }
0552         else if (m_currentTask->type() == LoadSaveTask::TaskTypeLoading)
0553         {
0554             LoadingTask* const loadingTask = dynamic_cast<LoadingTask*>(m_currentTask);
0555 
0556             if (loadingTask)
0557             {
0558                 loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0559             }
0560         }
0561     }
0562 
0563     Q_FOREACH (LoadSaveTask* const task, m_todo)
0564     {
0565         delete task;
0566     }
0567 
0568     m_todo.clear();
0569 }
0570 
0571 void ManagedLoadSaveThread::stopSaving(const QString& filePath)
0572 {
0573     QMutexLocker lock(threadMutex());
0574 
0575     // stop current task if it is matching the criteria
0576 
0577     if (m_currentTask && (m_currentTask->type() == LoadSaveTask::TaskTypeSaving))
0578     {
0579         SavingTask* const savingTask = dynamic_cast<SavingTask*>(m_currentTask);
0580 
0581         if (savingTask && (filePath.isNull() || (savingTask->filePath() == filePath)))
0582         {
0583             savingTask->setStatus(SavingTask::SavingTaskStatusStopping);
0584         }
0585     }
0586 
0587     // remove relevant tasks from list
0588 
0589     for (int i = 0 ; i < m_todo.size() ; ++i)
0590     {
0591         LoadSaveTask* const task = m_todo.at(i);
0592 
0593         if (task->type() == LoadSaveTask::TaskTypeSaving)
0594         {
0595             SavingTask* const savingTask = dynamic_cast<SavingTask*>(task);
0596 
0597             if (savingTask && (filePath.isNull() || (savingTask->filePath() == filePath)))
0598             {
0599                 delete m_todo.takeAt(i--);
0600             }
0601         }
0602     }
0603 }
0604 
0605 void ManagedLoadSaveThread::removeLoadingTasks(const LoadingDescription& description, LoadingTaskFilter filter)
0606 {
0607     LoadingTask* loadingTask = nullptr;
0608 
0609     // stop current task if it is matching the criteria
0610 
0611     if ((loadingTask = checkLoadingTask(m_currentTask, filter)))
0612     {
0613         if (description.filePath.isNull() || (loadingTask->loadingDescription() == description))
0614         {
0615             loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0616         }
0617     }
0618 
0619     // remove relevant tasks from list
0620 
0621     for (int i = 0 ; i < m_todo.size() ; ++i)
0622     {
0623         LoadSaveTask* const task = m_todo.at(i);
0624 
0625         if ((loadingTask = checkLoadingTask(task, filter)))
0626         {
0627             if (description.filePath.isNull() || (loadingTask->loadingDescription() == description))
0628             {
0629                 delete m_todo.takeAt(i--);
0630             }
0631         }
0632     }
0633 }
0634 
0635 void ManagedLoadSaveThread::save(const DImg& image, const QString& filePath, const QString& format)
0636 {
0637     QMutexLocker lock(threadMutex());
0638     LoadingTask* loadingTask = nullptr;
0639 
0640     // stop and postpone current task if it is a preloading task
0641 
0642     if (m_currentTask && (loadingTask = checkLoadingTask(m_currentTask, LoadingTaskFilterPreloading)))
0643     {
0644         loadingTask->setStatus(LoadingTask::LoadingTaskStatusStopping);
0645         load(loadingTask->loadingDescription(), LoadingPolicyPreload);
0646     }
0647 
0648     // append new loading task, put it in front of preloading tasks
0649 
0650     int i;
0651 
0652     for (i = 0 ; i < m_todo.count() ; ++i)
0653     {
0654         LoadSaveTask* const task = m_todo.at(i);
0655 
0656         if (checkLoadingTask(task, LoadingTaskFilterPreloading))
0657         {
0658             break;
0659         }
0660     }
0661 
0662     m_todo.insert(i, new SavingTask(this, image, filePath, format));
0663     start(lock);
0664 }
0665 
0666 } // namespace Digikam
0667 
0668 #include "moc_managedloadsavethread.cpp"