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"