/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-12-17 * Description : image file IO threaded interface. * * Copyright (C) 2005-2007 by Marcel Wiesweg * Copyright (C) 2005-2007 by Gilles Caulier * * 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 "loadsavetask.h" // TQt includes. #include // Local includes. #include "ddebug.h" #include "loadsavethread.h" #include "managedloadsavethread.h" #include "sharedloadsavethread.h" #include "loadingcache.h" namespace Digikam { void LoadingProgressEvent::notify(LoadSaveThread *thread) { thread->loadingProgress(m_loadingDescription, m_progress); } void SavingProgressEvent::notify(LoadSaveThread *thread) { thread->savingProgress(m_filePath, m_progress); } void StartedLoadingEvent::notify(LoadSaveThread *thread) { thread->imageStartedLoading(m_loadingDescription); } void StartedSavingEvent::notify(LoadSaveThread *thread) { thread->imageStartedSaving(m_filePath); } void LoadedEvent::notify(LoadSaveThread *thread) { thread->imageLoaded(m_loadingDescription, m_img); } void MoreCompleteLoadingAvailableEvent::notify(LoadSaveThread *thread) { thread->moreCompleteLoadingAvailable(m_oldDescription, m_newDescription); } void SavedEvent::notify(LoadSaveThread *thread) { thread->imageSaved(m_filePath, m_success); } //--------------------------------------------------------------------------------------------------- void LoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) return; DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); m_thread->taskHasFinished(); TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img)); } LoadingTask::TaskType LoadingTask::type() { return TaskTypeLoading; } void LoadingTask::progressInfo(const DImg *, float progress) { if (m_loadingTaskStatus == LoadingTaskStatusLoading) { if (m_thread->querySendNotifyEvent()) TQApplication::postEvent(m_thread, new LoadingProgressEvent(m_loadingDescription.filePath, progress)); } } bool LoadingTask::continueQuery(const DImg *) { return m_loadingTaskStatus != LoadingTaskStatusStopping; } void LoadingTask::setStatus(LoadingTaskStatus status) { m_loadingTaskStatus = status; } // This is a hack needed to prevent hanging when a TDEProcess-based loader (raw loader) // is waiting for the process to finish, but the main thread is waiting // for the thread to finish and no TDEProcess events are delivered. // Remove when porting to TQt4. bool LoadingTask::isShuttingDown() { return m_thread->isShuttingDown(); } //--------------------------------------------------------------------------------------------------- void SharedLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) return; // send StartedLoadingEvent from each single Task, not via LoadingProcess list TQApplication::postEvent(m_thread, new StartedLoadingEvent(m_loadingDescription.filePath)); LoadingCache *cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg *cachedImg = 0; TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { if ( (cachedImg = cache->retrieveImage(*it)) ) break; } if (cachedImg) { // image is found in image cache, loading is successful DImg img(*cachedImg); if (accessMode() == LoadSaveThread::AccessModeReadWrite) img = img.copy(); TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img)); return; } else { // find possible running loading process m_usedProcess = 0; for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) { if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) ) { break; } } if (m_usedProcess) { // Other process is right now loading this image. // Add this task to the list of listeners and // attach this thread to the other thread, wait until loading // has finished. m_usedProcess->addListener(this); // break loop when either the loading has completed, or this task is being stopped while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping ) lock.timedWait(); // remove listener from process m_usedProcess->removeListener(this); // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); // set to 0, as checked in setStatus m_usedProcess = 0; //DDebug() << "SharedLoadingTask " << this << ": waited" << endl; return; } else { // Neither in cache, nor currently loading in different thread. // Load it here and now, add this LoadingProcess to cache list. cache->addLoadingProcess(this); // Add this to the list of listeners addListener(this); // for use in setStatus m_usedProcess = this; // Notify other processes that we are now loading this image. // They might be interested - see notifyNewLoadingProcess below cache->notifyNewLoadingProcess(this, m_loadingDescription); } } } // load image DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); bool isCached = false; { LoadingCache::CacheLock lock(cache); // put (valid) image into cache of loaded images if (!img.isNull()) isCached = cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath); // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); } // following the golden rule to avoid deadlocks, do this when CacheLock is not held m_thread->taskHasFinished(); { LoadingCache::CacheLock lock(cache); //DDebug() << "SharedLoadingTask " << this << ": image loaded, " << img.isNull() << endl; // indicate that loading has finished so that listeners can stop waiting m_completed = true; // Optimize so that no unnecessary copying is done. // If image has been put in cache, the initial copy has been consumed for this. // If image is too large for cache, the initial copy is still available. bool usedInitialCopy = isCached; // dispatch image to all listeners, including this for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) { // This code sends a copy only when ReadWrite access is requested. // Otherwise, the image from the cache is sent. // As the image in the cache will be deleted from any thread, the explicit sharing // needs to be thread-safe to avoid the risk of memory leaks. // This is the case only for TQt4, so uncomment this code when porting. /* if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. DImg copy = img.copy(); TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, copy)); } else TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription.filePath, img)); */ // TQt3: The same copy for all Read listeners (it is assumed that they will delete it only in the main thread), // an extra copy for each ReadWrite listener DImg readerCopy; if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. DImg copy; if (usedInitialCopy) { copy = img.copy(); } else { copy = img; usedInitialCopy = true; } TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, copy)); } else { if (readerCopy.isNull()) { if (usedInitialCopy) { readerCopy = img.copy(); } else { readerCopy = img; usedInitialCopy = true; } } TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, readerCopy)); } } // remove myself from list of listeners removeListener(this); // wake all listeners waiting on cache condVar, so that they remove themselves lock.wakeAll(); // wait until all listeners have removed themselves while (m_listeners.count() != 0) lock.timedWait(); // set to 0, as checked in setStatus m_usedProcess = 0; } } void SharedLoadingTask::progressInfo(const DImg *, float progress) { if (m_loadingTaskStatus == LoadingTaskStatusLoading) { LoadingCache *cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) { if (l->querySendNotifyEvent()) TQApplication::postEvent(l->eventReceiver(), new LoadingProgressEvent(m_loadingDescription, progress)); } } } bool SharedLoadingTask::continueQuery(const DImg *) { // If this is called, the thread is currently loading an image. // In shared loading, we cannot stop until all listeners have been removed as well return (m_loadingTaskStatus != LoadingTaskStatusStopping) || (m_listeners.count() != 0); } void SharedLoadingTask::setStatus(LoadingTaskStatus status) { m_loadingTaskStatus = status; if (m_loadingTaskStatus == LoadingTaskStatusStopping) { LoadingCache *cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); // check for m_usedProcess, to avoid race condition that it has finished before if (m_usedProcess) { // remove this from list of listeners - check in continueQuery() of active thread m_usedProcess->removeListener(this); // wake all listeners - particularly this - from waiting on cache condvar lock.wakeAll(); } } } bool SharedLoadingTask::completed() { return m_completed; } TQString SharedLoadingTask::filePath() { return m_loadingDescription.filePath; } TQString SharedLoadingTask::cacheKey() { return m_loadingDescription.cacheKey(); } void SharedLoadingTask::addListener(LoadingProcessListener *listener) { m_listeners.append(listener); } void SharedLoadingTask::removeListener(LoadingProcessListener *listener) { m_listeners.remove(listener); } void SharedLoadingTask::notifyNewLoadingProcess(LoadingProcess *process, LoadingDescription description) { // Ok, we are notified that another task has been started in another thread. // We are of course only interested if the task loads the same file, // and we are right now loading a reduced version, and the other task is loading the full version. // In this case, we notify our own thread (a signal to the API user is emitted) of this. // The fact that we are receiving the method call shows that this task is registered with the LoadingCache, // somewhere in between the calls to addLoadingProcess(this) and removeLoadingProcess(this) above. if (process != this && m_loadingDescription.isReducedVersion() && m_loadingDescription.equalsIgnoreReducedVersion(description) && !description.isReducedVersion() ) { for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next()) { TQApplication::postEvent(l->eventReceiver(), new MoreCompleteLoadingAvailableEvent(m_loadingDescription, description)); } } } bool SharedLoadingTask::querySendNotifyEvent() { return m_thread->querySendNotifyEvent(); } TQObject *SharedLoadingTask::eventReceiver() { return m_thread; } LoadSaveThread::AccessMode SharedLoadingTask::accessMode() { return m_accessMode; } //--------------------------------------------------------------------------------------------------- void SavingTask::execute() { bool success = m_img.save(m_filePath, m_format, this); m_thread->taskHasFinished(); TQApplication::postEvent(m_thread, new SavedEvent(m_filePath, success)); }; LoadingTask::TaskType SavingTask::type() { return TaskTypeSaving; } void SavingTask::progressInfo(const DImg *, float progress) { if (m_thread->querySendNotifyEvent()) TQApplication::postEvent(m_thread, new SavingProgressEvent(m_filePath, progress)); } bool SavingTask::continueQuery(const DImg *) { return m_savingTaskStatus != SavingTaskStatusStopping; } void SavingTask::setStatus(SavingTaskStatus status) { m_savingTaskStatus = status; } } //namespace Digikam