summaryrefslogtreecommitdiff
path: root/library/iconBuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'library/iconBuffer.cpp')
-rw-r--r--library/iconBuffer.cpp298
1 files changed, 298 insertions, 0 deletions
diff --git a/library/iconBuffer.cpp b/library/iconBuffer.cpp
new file mode 100644
index 00000000..a969f689
--- /dev/null
+++ b/library/iconBuffer.cpp
@@ -0,0 +1,298 @@
+#include "iconBuffer.h"
+#include <wx/thread.h>
+#include "globalFunctions.h"
+#include <wx/utils.h>
+#include <wx/bitmap.h>
+#include <wx/msw/wrapwin.h> //includes "windows.h"
+#include <wx/msgdlg.h>
+#include "../algorithm.h"
+#include <wx/icon.h>
+#include <map>
+#include <queue>
+
+using FreeFileSync::IconBuffer;
+
+
+class BasicString //simple thread safe string class
+{
+public:
+ BasicString()
+ {
+ data = new DefaultChar[1]; //compliance with delete []
+ *data = 0; //compliance with c_str()
+ }
+
+ BasicString(const Zstring& other) //make DEEP COPY from Zstring!
+ {
+ data = new DefaultChar[other.length() + 1];
+ memcpy(data, other.c_str(), (other.length() + 1) * sizeof(DefaultChar));
+ }
+
+ BasicString(const BasicString& other)
+ {
+ const size_t otherLen = defaultLength(other.c_str());
+ data = new DefaultChar[otherLen + 1];
+ memcpy(data, other.c_str(), (otherLen + 1) * sizeof(DefaultChar));
+ }
+
+ ~BasicString()
+ {
+ delete [] data;
+ }
+
+ BasicString& operator=(const BasicString& other)
+ { //exception safety; handle self-assignment implicitly
+ const size_t otherLen = defaultLength(other.c_str());
+ DefaultChar* dataNew = new DefaultChar[otherLen + 1];
+ memcpy(dataNew, other.c_str(), (otherLen + 1) * sizeof(DefaultChar));
+
+ delete data;
+ data = dataNew;
+
+ return *this;
+ }
+
+ const DefaultChar* c_str() const
+ {
+ return data;
+ }
+
+private:
+ DefaultChar* data;
+};
+
+
+class WorkerThread : public wxThread
+{
+public:
+ WorkerThread(IconBuffer* iconBuff);
+
+ void setWorkload(const std::vector<Zstring>& load); //(re-)set new workload of icons to be retrieved
+ void quitThread();
+
+ virtual ExitCode Entry();
+
+private:
+ void doWork();
+
+//---------------------- Shared Data -------------------------
+ typedef BasicString FileName;
+
+ wxCriticalSection lockWorkload; //use for locking shared data
+ std::vector<FileName> workload; //processes last elements of vector first!
+ bool threadHasMutex;
+ bool threadExitIsRequested;
+//------------------------------------------------------------
+
+ //event: icon buffer -> woker thread
+ wxMutex threadIsListening;
+ wxCondition continueWork; //wake up thread
+
+ IconBuffer* iconBuffer;
+};
+
+
+WorkerThread::WorkerThread(IconBuffer* iconBuff) :
+ wxThread(wxTHREAD_JOINABLE),
+ threadHasMutex(false),
+ threadExitIsRequested(false),
+ threadIsListening(),
+ continueWork(threadIsListening),
+ iconBuffer(iconBuff)
+{
+ if (Create() != wxTHREAD_NO_ERROR)
+ throw RuntimeException(wxString(wxT("Error creating icon buffer worker thread!")));
+
+ if (Run() != wxTHREAD_NO_ERROR)
+ throw RuntimeException(wxString(wxT("Error starting icon buffer worker thread!")));
+
+ //wait until thread has aquired mutex
+ bool hasMutex = false;
+ while (!hasMutex)
+ {
+ wxMilliSleep(5);
+ wxCriticalSectionLocker dummy(lockWorkload);
+ hasMutex = threadHasMutex;
+ } //polling -> it's not nice, but works and is no issue
+}
+
+
+void WorkerThread::setWorkload(const std::vector<Zstring>& load) //(re-)set new workload of icons to be retrieved
+{
+ wxCriticalSectionLocker dummy(lockWorkload);
+
+ workload.clear();
+ for (std::vector<Zstring>::const_iterator i = load.begin(); i != load.end(); ++i)
+ workload.push_back(*i); //make DEEP COPY from Zstring!
+
+ if (!workload.empty())
+ continueWork.Signal(); //wake thread IF he is waiting
+}
+
+
+void WorkerThread::quitThread()
+{
+ wxMutexLocker dummy(threadIsListening); //wait until thread is in waiting state
+ threadExitIsRequested = true; //no sharing conflicts in this situation
+ continueWork.Signal(); //exit thread
+}
+
+
+wxThread::ExitCode WorkerThread::Entry()
+{
+ try
+ {
+ wxMutexLocker dummy(threadIsListening); //this lock needs to be called from WITHIN the thread => calling it from constructor(Main thread) would be useless
+ { //this mutex STAYS locked all the time except of continueWork.Wait()!
+ wxCriticalSectionLocker dummy(lockWorkload);
+ threadHasMutex = true;
+ }
+
+ while (true)
+ {
+ continueWork.Wait(); //waiting for continueWork.Signal(); unlocks Mutex "threadIsListening"
+
+ //no mutex needed in this context
+ if (threadExitIsRequested) //no mutex here: atomicity is not prob for a bool, but visibility (e.g. caching in registers)
+ return 0; //shouldn't be a problem nevertheless because of implicit memory barrier caused by mutex.Lock() in .Wait()
+
+ //do work: get the file icon.
+ doWork();
+ }
+ }
+ catch (RuntimeException& e) //exceptions must be catched per thread
+ {
+ wxMessageBox(e.show());
+ return 0;
+ }
+}
+
+
+void WorkerThread::doWork()
+{
+ BasicString fileName; //don't use Zstring: reference-counted objects are NOT THREADSAFE!!! e.g. double deletion might happen
+
+ //do work: get the file icon.
+ while (true)
+ {
+ {
+ wxCriticalSectionLocker dummy(lockWorkload);
+ if (workload.empty())
+ break; //enter waiting state
+ fileName = workload.back();
+ workload.pop_back();
+ }
+
+ if (iconBuffer->requestIcon(Zstring(fileName.c_str()))) //thread safety: Zstring okay, won't be reference-counted in requestIcon()
+ break; //icon already in buffer: enter waiting state
+
+ //load icon
+ SHFILEINFO fileInfo;
+ fileInfo.hIcon = 0; //initialize hIcon
+
+ if (SHGetFileInfo(fileName.c_str(), //NOTE: CoInitializeEx()/CoUninitialize() implicitly called by wxWidgets on program startup!
+ 0,
+ &fileInfo,
+ sizeof(fileInfo),
+ SHGFI_ICON | SHGFI_SMALLICON) &&
+
+ fileInfo.hIcon != 0) //fix for weird error: SHGetFileInfo() might return successfully WITHOUT filling fileInfo.hIcon!!
+ { //bug report: https://sourceforge.net/tracker/?func=detail&aid=2768004&group_id=234430&atid=1093080
+
+ wxIcon newIcon; //attention: wxIcon uses reference counting!
+ newIcon.SetHICON(fileInfo.hIcon);
+ newIcon.SetSize(IconBuffer::ICON_SIZE, IconBuffer::ICON_SIZE);
+
+ iconBuffer->insertIntoBuffer(fileName.c_str(), newIcon); //thread safety: icon may be deleted only within insertIntoBuffer()
+
+ //freeing of icon handle seems to happen somewhere beyond wxIcon destructor
+ //if (!DestroyIcon(fileInfo.hIcon))
+ // throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted());
+
+ }
+ else
+ {
+ //if loading of icon fails for whatever reason, just save a dummy icon to avoid re-loading
+ iconBuffer->insertIntoBuffer(fileName.c_str(), wxNullIcon);
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------------
+
+typedef Zstring FileName;
+class IconDB : public std::map<FileName, wxIcon> {};
+class IconDbSequence : public std::queue<FileName> {};
+
+//---------------------------------------------------------------------------------------------------
+
+
+IconBuffer& IconBuffer::getInstance()
+{
+ static IconBuffer instance;
+ return instance;
+}
+
+
+IconBuffer::IconBuffer() :
+ lockIconDB( new wxCriticalSection),
+ buffer( new IconDB),
+ bufSequence(new IconDbSequence),
+ worker( new WorkerThread(this)) //might throw exceptions!
+{}
+
+
+IconBuffer::~IconBuffer()
+{
+ worker->quitThread();
+ worker->Wait(); //wait until thread has exitted
+}
+
+
+bool IconBuffer::requestIcon(const Zstring& fileName, wxIcon* icon)
+{
+ wxCriticalSectionLocker dummy(*lockIconDB);
+ IconDB::const_iterator i = buffer->find(fileName);
+
+ if (i != buffer->end())
+ {
+ if (icon != NULL)
+ *icon = i->second;
+ return true;
+ }
+
+ return false;
+}
+
+
+void IconBuffer::setWorkload(const std::vector<Zstring>& load)
+{
+ worker->setWorkload(load);
+}
+
+
+void IconBuffer::insertIntoBuffer(const DefaultChar* fileName, const wxIcon& icon) //called by worker thread
+{
+ if (icon.IsOk()) //this check won't hurt
+ {
+ wxCriticalSectionLocker dummy(*lockIconDB);
+
+ const Zstring fileNameZ = fileName;
+
+ const std::pair<IconDB::iterator, bool> rc = buffer->insert(IconDB::value_type(fileNameZ, icon));
+
+ if (rc.second) //if insertion took place
+ bufSequence->push(fileNameZ);
+
+ assert(buffer->size() == bufSequence->size());
+
+ //remove elements if buffer becomes too big:
+ if (buffer->size() > 1000) //limit buffer size: critical because large buffers seem to cause various wxIcon/wxBitmap issues!
+ {
+ //remove oldest element
+ buffer->erase(bufSequence->front());
+ bufSequence->pop();
+ }
+ }
+}
+
bgstack15