You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/libs/threadimageio/loadsavetask.cpp

425 lines
14 KiB

/* ============================================================
*
* 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 <marcel.wiesweg@gmx.de>
* Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles 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 "loadsavetask.h"
// TQt includes.
#include <tqapplication.h>
// 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