From ee1c8c5c25d25dfa42120125a8a45dc9831ee412 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:23:48 +0200 Subject: 5.14 --- lib/icon_buffer.cpp | 560 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 323 insertions(+), 237 deletions(-) (limited to 'lib/icon_buffer.cpp') diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp index 44440be8..dd80f6dd 100644 --- a/lib/icon_buffer.cpp +++ b/lib/icon_buffer.cpp @@ -12,54 +12,52 @@ #ifdef FFS_WIN #include -#include "Thumbnail/thumbnail.h" #include +#include +#include "Thumbnail/thumbnail.h" #elif defined FFS_LINUX #include + +#elif defined FFS_MAC +#include "osx_file_icon.h" #endif using namespace zen; -warn_static("mac") - -#if defined FFS_MAC -struct IconBuffer::Pimpl {}; -IconBuffer::IconBuffer(IconSize sz): pimpl(), icoSize(sz), genDirIcon(), genFileIcon() {} -IconBuffer::~IconBuffer() {} -int IconBuffer::getSize(IconSize icoSize) {return 16; } -bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon) { return false; } -void IconBuffer::setWorkload(const std::vector& load) {} -#else - namespace { -const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to buffer +const size_t BUFFER_SIZE_MAX = 600; //maximum number of icons to hold in buffer + +#ifndef NDEBUG +boost::thread::id mainThreadId = boost::this_thread::get_id(); +#endif + +#ifdef FFS_WIN +#define DEF_DLL_FUN(name) DllFun name(thumb::getDllName(), thumb::funName_##name); -class IconHolder //handle HICON/GdkPixbuf ownership WITHOUT ref-counting to allow thread-safe usage (in contrast to wxIcon) +DEF_DLL_FUN(getIconByIndex); // +DEF_DLL_FUN(getThumbnail); //let's spare the boost::call_once hustle and allocate statically +DEF_DLL_FUN(releaseImageData); // +#endif + +class IconHolder //handle HICON/GdkPixbuf ownership supporting thread-safe usage (in contrast to wxIcon/wxBitmap) { public: #ifdef FFS_WIN - typedef HICON HandleType; + typedef const thumb::ImageData* HandleType; #elif defined FFS_LINUX typedef GdkPixbuf* HandleType; +#elif defined FFS_MAC + typedef osx::ImageData* HandleType; #endif explicit IconHolder(HandleType handle = nullptr) : handle_(handle) {} //take ownership! - //icon holder has value semantics! - IconHolder(const IconHolder& other) : handle_(other.handle_ == nullptr ? nullptr : -#ifdef FFS_WIN - ::CopyIcon(other.handle_) -#elif defined FFS_LINUX - ::gdk_pixbuf_copy(other.handle_) //create new Pix buf with reference count 1 or return 0 on error -#endif - ) {} - - IconHolder(IconHolder&& other) : handle_(other.handle_) { other.handle_ = nullptr; } + IconHolder(IconHolder&& other) : handle_(other.release()) {} - IconHolder& operator=(IconHolder other) //unifying assignment: no need for r-value reference optimization! + IconHolder& operator=(IconHolder other) //unifying assignment { other.swap(*this); return *this; @@ -69,72 +67,69 @@ public: { if (handle_ != nullptr) #ifdef FFS_WIN - ::DestroyIcon(handle_); + releaseImageData(handle_); #elif defined FFS_LINUX ::g_object_unref(handle_); //superseedes "::gdk_pixbuf_unref"! +#elif defined FFS_MAC + delete handle_; #endif } + HandleType release() + { + ZEN_ON_SCOPE_EXIT(handle_ = nullptr); + return handle_; + } + void swap(IconHolder& other) { std::swap(handle_, other.handle_); } //throw() - wxIcon toWxIcon(int expectedSize) const //copy HandleType, caller needs to take ownership! + //destroys raw icon! Call from GUI thread only! + wxBitmap extractWxBitmap() { - if (handle_ == nullptr) - return wxNullIcon; + ZEN_ON_SCOPE_EXIT(assert(!*this)); + assert(boost::this_thread::get_id() == mainThreadId ); - IconHolder clone(*this); + if (!handle_) + return wxNullBitmap; - wxIcon newIcon; //attention: wxIcon uses reference counting! #ifdef FFS_WIN - newIcon.SetHICON(clone.handle_); - { - //this block costs ~0.04 ms - ICONINFO icoInfo = {}; - if (::GetIconInfo(clone.handle_, &icoInfo)) - { - if (icoInfo.hbmMask) //VC11 static analyzer warns this could be null - ::DeleteObject(icoInfo.hbmMask); //nice potential for a GDI leak! - - if (icoInfo.hbmColor) //optional (for black and white bitmap) - { - ZEN_ON_SCOPE_EXIT(::DeleteObject(icoInfo.hbmColor)); // - - BITMAP bmpInfo = {}; - if (::GetObject(icoInfo.hbmColor, //__in HGDIOBJ hgdiobj, - sizeof(BITMAP), //__in int cbBuffer, - &bmpInfo) != 0) // __out LPVOID lpvObject - { - const int maxExtent = std::max(bmpInfo.bmWidth, bmpInfo.bmHeight); - if (0 < expectedSize && expectedSize < maxExtent) - { - bmpInfo.bmWidth = bmpInfo.bmWidth * expectedSize / maxExtent; //scale those Vista jumbo 256x256 icons down! - bmpInfo.bmHeight = bmpInfo.bmHeight * expectedSize / maxExtent; // - } - newIcon.SetSize(bmpInfo.bmWidth, bmpInfo.bmHeight); //wxIcon is stretched to this size - } - } - } - } - //no stretching for now - //newIcon.SetSize(expectedSize, expectedSize); //icon is stretched to this size if referenced HICON differs + ZEN_ON_SCOPE_EXIT(IconHolder().swap(*this)); //destroy after extraction + + //let wxImage reference data without taking ownership: + wxImage fileIcon(handle_->width, handle_->height, handle_->rgb, true); + fileIcon.SetAlpha(handle_->alpha, true); + return wxBitmap(fileIcon); #elif defined FFS_LINUX - // transfer ownership!! #if wxCHECK_VERSION(2, 9, 4) - newIcon.CopyFromBitmap(wxBitmap(clone.handle_)); + return wxBitmap(release()); //ownership passed! #else - newIcon.SetPixbuf(clone.handle_); + wxBitmap newIcon; + newIcon.SetPixbuf(release()); //ownership passed! + return newIcon; #endif -#endif // - clone.handle_ = nullptr; // - return newIcon; +#elif defined FFS_MAC + ZEN_ON_SCOPE_EXIT(IconHolder().swap(*this)); //destroy after extraction + + //let wxImage reference data without taking ownership: + if (!handle_->rgb.empty()) + { + wxImage fileIcon(handle_->width, handle_->height, &handle_->rgb[0], true); + if (!handle_->alpha.empty()) + fileIcon.SetAlpha(&handle_->alpha[0], true); + return wxBitmap(fileIcon); + } + assert(false); //rgb and alpha should never be empty + return wxBitmap(); +#endif } private: HandleType handle_; - struct ConversionToBool { int dummy; }; + IconHolder(const IconHolder& other); //move semantics! + struct ConversionToBool { int dummy; }; public: //use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20) operator int ConversionToBool::* () const { return handle_ != nullptr ? &ConversionToBool::dummy : nullptr; } @@ -151,51 +146,51 @@ Zstring getFileExtension(const Zstring& filename) Zstring(); } + std::set priceyExtensions; //thread-safe! boost::once_flag initExtensionsOnce = BOOST_ONCE_INIT; // - //test for extension for non-thumbnail icons that physically have to be retrieved from disc bool isCheapExtension(const Zstring& extension) { boost::call_once(initExtensionsOnce, []() { priceyExtensions.insert(L"exe"); - priceyExtensions.insert(L"lnk"); priceyExtensions.insert(L"ico"); priceyExtensions.insert(L"ani"); priceyExtensions.insert(L"cur"); - priceyExtensions.insert(L"url"); priceyExtensions.insert(L"msc"); priceyExtensions.insert(L"scr"); + + priceyExtensions.insert(L"lnk"); // + priceyExtensions.insert(L"url"); //make sure shortcuts are pricey to get them to be detected by SHGetFileInfo + priceyExtensions.insert(L"pif"); // + priceyExtensions.insert(L"website"); // + }); return priceyExtensions.find(extension) == priceyExtensions.end(); } - const bool wereVistaOrLater = vistaOrLater(); //thread-safety: init at startup -int getShilIconType(IconBuffer::IconSize sz) +thumb::IconSizeType getThumbSizeType(IconBuffer::IconSize sz) { + using namespace thumb; switch (sz) { case IconBuffer::SIZE_SMALL: - return SHIL_SMALL; //16x16, but the size can be customized by the user. + return ICON_SIZE_16; case IconBuffer::SIZE_MEDIUM: - return SHIL_EXTRALARGE; //typically 48x48, but the size can be customized by the user. + if (!wereVistaOrLater) return ICON_SIZE_32; //48x48 doesn't look sharp on XP + return ICON_SIZE_48; case IconBuffer::SIZE_LARGE: - return wereVistaOrLater ? SHIL_JUMBO //normally 256x256 pixels -> will be scaled down by IconHolder - : SHIL_EXTRALARGE; //XP doesn't have jumbo icons + return ICON_SIZE_128; } - return SHIL_SMALL; + return ICON_SIZE_16; } -DllFun getIconByIndex; -boost::once_flag initGetIconByIndexOnce = BOOST_ONCE_INIT; - - IconHolder getIconByAttribute(LPCWSTR pszPath, DWORD dwFileAttributes, IconBuffer::IconSize sz) { //NOTE: CoInitializeEx()/CoUninitialize() needs to be called for THIS thread! @@ -205,16 +200,13 @@ IconHolder getIconByAttribute(LPCWSTR pszPath, DWORD dwFileAttributes, IconBuffe &fileInfo, sizeof(fileInfo), SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES); - //no need to IUnknown::Release() imgList! - if (!imgList) + if (!imgList) //no need to IUnknown::Release() imgList! return IconHolder(); - boost::call_once(initGetIconByIndexOnce, [] //thread-safe init - { - using namespace thumb; - getIconByIndex = DllFun(getDllName(), funName_getIconByIndex); - }); - return IconHolder(getIconByIndex ? static_cast(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : nullptr); + if (!getIconByIndex) + return IconHolder(); + + return IconHolder(getIconByIndex(fileInfo.iIcon, getThumbSizeType(sz))); } @@ -224,33 +216,66 @@ IconHolder getAssociatedIconByExt(const Zstring& extension, IconBuffer::IconSize return getIconByAttribute((L"dummy." + extension).c_str(), FILE_ATTRIBUTE_NORMAL, sz); } - -DllFun getThumbnailIcon; -boost::once_flag initThumbnailOnce = BOOST_ONCE_INIT; +#elif defined FFS_LINUX +IconHolder iconHolderFromGicon(GIcon* gicon, IconBuffer::IconSize sz) +{ + if (gicon) + if (GtkIconTheme* defaultTheme = ::gtk_icon_theme_get_default()) //not owned! + if (GtkIconInfo* iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, gicon, IconBuffer::getSize(sz), GTK_ICON_LOOKUP_USE_BUILTIN)) //this may fail if icon is not installed on system + { + ZEN_ON_SCOPE_EXIT(::gtk_icon_info_free(iconInfo);) + if (GdkPixbuf* pixBuf = ::gtk_icon_info_load_icon(iconInfo, nullptr)) + return IconHolder(pixBuf); //pass ownership + } + return IconHolder(); +} #endif } -//################################################################################################################################################ +//################################################################################################################################################ -IconHolder getThumbnail(const Zstring& filename, int requestedSize) //return 0 on failure +IconHolder getThumbnailIcon(const Zstring& filename, int requestedSize) //return 0 on failure { #ifdef FFS_WIN - boost::call_once(initThumbnailOnce, [] //note: "getThumbnail" function itself is already thread-safe - { - using namespace thumb; - getThumbnailIcon = DllFun(getDllName(), funName_getThumbnail); - }); - return IconHolder(getThumbnailIcon ? static_cast< ::HICON>(getThumbnailIcon(filename.c_str(), requestedSize)) : nullptr); + if (getThumbnail) + return IconHolder(getThumbnail(filename.c_str(), requestedSize)); #elif defined FFS_LINUX - GdkPixbuf* pixBuf = gdk_pixbuf_new_from_file_at_size(filename.c_str(), requestedSize, requestedSize, nullptr); - return IconHolder(pixBuf); //pass ownership (may be 0) + gint width = 0; + gint height = 0; + if (GdkPixbufFormat* fmt = ::gdk_pixbuf_get_file_info(filename.c_str(), &width, &height)) + { + (void)fmt; + if (width > 0 && height > 0 && requestedSize > 0) + { + int trgWidth = width; + int trgHeight = height; + + const int maxExtent = std::max(width, height); //don't stretch small images, but shrink large ones instead! + if (requestedSize < maxExtent) + { + trgWidth = width * requestedSize / maxExtent; + trgHeight = height * requestedSize / maxExtent; + } + if (GdkPixbuf* pixBuf = ::gdk_pixbuf_new_from_file_at_size(filename.c_str(), trgWidth, trgHeight, nullptr)) + return IconHolder(pixBuf); //pass ownership + } + } + +#elif defined FFS_MAC + try + { + return IconHolder(new osx::ImageData(osx::getThumbnail(filename.c_str(), requestedSize))); //throw OsxError + } + catch (osx::OsxError&) {} #endif + return IconHolder(); } IconHolder getGenericFileIcon(IconBuffer::IconSize sz) { + //we're called by getAssociatedIcon()! -> avoid endless recursion! #ifdef FFS_WIN return getIconByAttribute(L"dummy", FILE_ATTRIBUTE_NORMAL, sz); @@ -259,18 +284,44 @@ IconHolder getGenericFileIcon(IconBuffer::IconSize sz) { "application-x-zerosize", //Kubuntu: /usr/share/icons/oxygen/48x48/mimetypes "text-x-generic", //http://live.gnome.org/GnomeArt/Tutorials/IconThemes - "empty", // - "gtk-file", //Ubuntu: /usr/share/icons/Humanity/mimes/48 + "empty", //Ubuntu: /usr/share/icons/Humanity/mimes/48 + GTK_STOCK_FILE, //"gtk-file", "gnome-fs-regular", // }; - const int requestedSize = IconBuffer::getSize(sz); - if (GtkIconTheme* defaultTheme = gtk_icon_theme_get_default()) //not owned! for (auto it = std::begin(mimeFileIcons); it != std::end(mimeFileIcons); ++it) - if (GdkPixbuf* pixBuf = gtk_icon_theme_load_icon(defaultTheme, *it, requestedSize, GTK_ICON_LOOKUP_USE_BUILTIN, nullptr)) - return IconHolder(pixBuf); //pass ownership (may be nullptr) + if (GdkPixbuf* pixBuf = gtk_icon_theme_load_icon(defaultTheme, *it, IconBuffer::getSize(sz), GTK_ICON_LOOKUP_USE_BUILTIN, nullptr)) + return IconHolder(pixBuf); //pass ownership + return IconHolder(); + +#elif defined FFS_MAC + try + { + return IconHolder(new osx::ImageData(osx::getDefaultFileIcon(IconBuffer::getSize(sz)))); //throw OsxError + } + catch (osx::OsxError&) {} + return IconHolder(); +#endif +} + + +IconHolder getGenericDirectoryIcon(IconBuffer::IconSize sz) +{ +#ifdef FFS_WIN + return getIconByAttribute(L"dummy", //Windows 7 doesn't like this parameter to be an empty string! + FILE_ATTRIBUTE_DIRECTORY, sz); +#elif defined FFS_LINUX + if (GIcon* dirIcon = ::g_content_type_get_icon("inode/directory")) //should contain fallback to GTK_STOCK_DIRECTORY ("gtk-directory") + return iconHolderFromGicon(dirIcon, sz); return IconHolder(); + +#elif defined FFS_MAC + try + { + return IconHolder(new osx::ImageData(osx::getDefaultFolderIcon(IconBuffer::getSize(sz)))); //throw OsxError + } + catch (osx::OsxError&) { return IconHolder(); } #endif } @@ -284,15 +335,14 @@ IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz) break; case IconBuffer::SIZE_MEDIUM: case IconBuffer::SIZE_LARGE: - { - IconHolder ico = getThumbnail(filename, IconBuffer::getSize(sz)); - if (ico) + if (IconHolder ico = getThumbnailIcon(filename, IconBuffer::getSize(sz))) return ico; //else: fallback to non-thumbnail icon - } - break; + break; } + warn_static("problem: für folder links ist getThumbnail erfolgreich => SFGAO_LINK nicht gecheckt!") + //2. retrieve file icons #ifdef FFS_WIN //perf: optimize fallback case for SIZE_MEDIUM and SIZE_LARGE: @@ -303,70 +353,52 @@ IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz) //which means the access to get thumbnail failed: thumbnail failure is not dependent from extension in general! SHFILEINFO fileInfo = {}; - DWORD_PTR imgList = ::SHGetFileInfo(filename.c_str(), //_In_ LPCTSTR pszPath, -> note: ::SHGetFileInfo() can't handle \\?\-prefix! - 0, //DWORD dwFileAttributes, - &fileInfo, //_Inout_ SHFILEINFO *psfi, - sizeof(fileInfo), //UINT cbFileInfo, - SHGFI_SYSICONINDEX); //UINT uFlags - - //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as - // needed; for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList." - //http://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx + if (DWORD_PTR imgList = ::SHGetFileInfo(filename.c_str(), //_In_ LPCTSTR pszPath, -> note: ::SHGetFileInfo() can't handle \\?\-prefix! + 0, //DWORD dwFileAttributes, + &fileInfo, //_Inout_ SHFILEINFO *psfi, + sizeof(fileInfo), //UINT cbFileInfo, + SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES)) //UINT uFlags + { + //imgList->Release(); //empiric study: crash on XP if we release this! Seems we do not own it... -> also no GDI leak on Win7 -> okay + //another comment on http://msdn.microsoft.com/en-us/library/bb762179(v=VS.85).aspx describes exact same behavior on Win7/XP - if (!imgList) - return IconHolder(); - //imgList->Release(); //empiric study: crash on XP if we release this! Seems we do not own it... -> also no GDI leak on Win7 -> okay - //another comment on http://msdn.microsoft.com/en-us/library/bb762179(v=VS.85).aspx describes exact same behavior on Win7/XP + //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as + // needed; for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList." + //http://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx - boost::call_once(initGetIconByIndexOnce, [] //thread-safe init - { - getIconByIndex = DllFun(thumb::getDllName(), thumb::funName_getIconByIndex); - }); - return IconHolder(getIconByIndex ? static_cast(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : nullptr); +#ifndef SFGAO_LINK //Shobjidl.h +#define SFGAO_LINK 0x00010000L // Shortcut (link) or symlinks +#endif -#elif defined FFS_LINUX - const int requestedSize = IconBuffer::getSize(sz); + warn_static("support SFGAO_GHOSTED or hidden?") - GFile* file = g_file_new_for_path(filename.c_str()); //never fails - ZEN_ON_SCOPE_EXIT(g_object_unref(file);) + const bool isLink = (fileInfo.dwAttributes & SFGAO_LINK) != 0; - if (GFileInfo* fileInfo = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr, nullptr)) - { - ZEN_ON_SCOPE_EXIT(g_object_unref(fileInfo);) - - if (GIcon* gicon = g_file_info_get_icon(fileInfo)) //not owned! - if (GtkIconTheme* defaultTheme = gtk_icon_theme_get_default()) //not owned! - if (GtkIconInfo* iconInfo = gtk_icon_theme_lookup_by_gicon(defaultTheme, gicon, requestedSize, GTK_ICON_LOOKUP_USE_BUILTIN)) //this may fail if icon is not installed on system - { - ZEN_ON_SCOPE_EXIT(gtk_icon_info_free(iconInfo);) - if (GdkPixbuf* pixBuf = gtk_icon_info_load_icon(iconInfo, nullptr)) - return IconHolder(pixBuf); //pass ownership (may be nullptr) - } + if (getIconByIndex) + if (const thumb::ImageData* imgData = getIconByIndex(fileInfo.iIcon, getThumbSizeType(sz))) + return IconHolder(imgData); } - //fallback: icon lookup may fail because some icons are currently not present on system - return ::getGenericFileIcon(sz); -#endif -} -/* Dependency Diagram: +#elif defined FFS_LINUX + GFile* file = ::g_file_new_for_path(filename.c_str()); //never fails + ZEN_ON_SCOPE_EXIT(::g_object_unref(file);) -getGenericFileIcon() - /|\ - | -getAssociatedIcon() - /|\ - | -getGenericDirectoryIcon() -*/ + if (GFileInfo* fileInfo = ::g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr, nullptr)) + { + ZEN_ON_SCOPE_EXIT(::g_object_unref(fileInfo);) + if (GIcon* gicon = ::g_file_info_get_icon(fileInfo)) //not owned! + return iconHolderFromGicon(gicon, sz); + } + //need fallback: icon lookup may fail because some icons are currently not present on system -IconHolder getGenericDirectoryIcon(IconBuffer::IconSize sz) -{ -#ifdef FFS_WIN - return getIconByAttribute(L"dummy", //Windows 7 doesn't like this parameter to be an empty string! - FILE_ATTRIBUTE_DIRECTORY, sz); -#elif defined FFS_LINUX - return ::getAssociatedIcon(Zstr("/usr/"), sz); //all directories will look like "/usr/" +#elif defined FFS_MAC + try + { + return IconHolder(new osx::ImageData(osx::getFileIcon(filename.c_str(), IconBuffer::getSize(sz)))); //throw OsxError + } + catch (osx::OsxError&) {} #endif + return ::getGenericFileIcon(sz); //make sure this does not internally call getAssociatedIcon("someDefaultFile.txt")!!! => endless recursion! } //################################################################################################################################################ @@ -377,6 +409,7 @@ class WorkLoad public: Zstring extractNextFile() //context of worker thread, blocking { + assert(boost::this_thread::get_id() != mainThreadId ); boost::unique_lock dummy(lockFiles); while (filesToLoad.empty()) @@ -387,8 +420,9 @@ public: return fileName; } - void setWorkload(const std::vector& newLoad) //context of main thread + void setWorkload(const std::list& newLoad) //context of main thread { + assert(boost::this_thread::get_id() == mainThreadId ); { boost::unique_lock dummy(lockFiles); filesToLoad = newLoad; @@ -397,57 +431,95 @@ public: //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref } + void addToWorkload(const Zstring& newEntry) //context of main thread + { + assert(boost::this_thread::get_id() == mainThreadId ); + { + boost::unique_lock dummy(lockFiles); + filesToLoad.push_back(newEntry); //set as next item to retrieve + } + conditionNewFiles.notify_all(); + } + private: - std::vector filesToLoad; //processes last elements of vector first! + std::list filesToLoad; //processes last elements of vector first! boost::mutex lockFiles; boost::condition_variable conditionNewFiles; //signal event: data for processing available }; -typedef std::map NameIconMap; //entryName/icon -> note: Zstring is "thread-safe like an int" -typedef std::queue IconDbSequence; //entryName - class Buffer { public: - bool requestFileIcon(const Zstring& fileName, IconHolder* icon = nullptr) + //called by main and worker thread: + bool hasFileIcon(const Zstring& fileName) { - boost::lock_guard dummy(lockBuffer); + boost::lock_guard dummy(lockIconList); + return iconList.find(fileName) != iconList.end(); + } - auto it = iconMappping.find(fileName); - if (it != iconMappping.end()) + //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!) + Opt retrieveFileIcon(const Zstring& fileName) + { + assert(boost::this_thread::get_id() == mainThreadId ); + boost::lock_guard dummy(lockIconList); + auto it = iconList.find(fileName); + if (it == iconList.end()) + return NoValue(); + + IconData& idata = it->second; + if (idata.iconRaw) //if not yet converted... { - if (icon) - *icon = it->second; - return true; + idata.iconFmt = make_unique(idata.iconRaw.extractWxBitmap()); //convert in main thread! + assert(!idata.iconRaw); } - return false; + return idata.iconFmt ? *idata.iconFmt : wxNullBitmap; //idata.iconRaw may be inserted as empty from worker thread! } - void insertIntoBuffer(const Zstring& entryName, const IconHolder& icon) //called by worker thread + //must be called by main thread only! => ~wxBitmap() is NOT thread-safe! + //call at an appropriate time, e.g. after Workload::setWorkload() + void limitBufferSize() //critical because GDI resources are limited (e.g. 10000 on XP per process) { - boost::lock_guard dummy(lockBuffer); + assert(boost::this_thread::get_id() == mainThreadId ); + boost::lock_guard dummy(lockIconList); + while (iconList.size() > BUFFER_SIZE_MAX) + { + iconList.erase(iconSequence.front()); //remove oldest element + iconSequence.pop(); + } + } + + //called by main and worker thread: + void moveIntoBuffer(const Zstring& entryName, IconHolder&& icon) + { + boost::lock_guard dummy(lockIconList); - //thread saftey: icon uses ref-counting! But is NOT shared with main thread! - auto rc = iconMappping.insert(std::make_pair(entryName, icon)); + //thread safety: moving IconHolder is free from side effects, but ~wxBitmap() is NOT! => do NOT delete items from iconList here! + auto rc = iconList.insert(std::make_pair(entryName, IconData(std::move(icon)))); if (rc.second) //if insertion took place iconSequence.push(entryName); //note: sharing Zstring with IconDB!!! - assert(iconMappping.size() == iconSequence.size()); - - //remove elements if buffer becomes too big: - if (iconMappping.size() > BUFFER_SIZE_MAX) //limit buffer size: critical because GDI resources are limited (e.g. 10000 on XP per process) - { - //remove oldest element - iconMappping.erase(iconSequence.front()); - iconSequence.pop(); - } + assert(iconList.size() == iconSequence.size()); } private: - boost::mutex lockBuffer; - NameIconMap iconMappping; //use synchronisation when accessing this! - IconDbSequence iconSequence; //save sequence of buffer entry to delete oldest elements + struct IconData + { + IconData(IconHolder&& tmp) : iconRaw(std::move(tmp)) {} + IconData(IconData&& tmp) : iconRaw(std::move(tmp.iconRaw)), iconFmt(std::move(tmp.iconFmt)) {} + + IconHolder iconRaw; //native icon representation: may be used by any thread + + std::unique_ptr iconFmt; //use ONLY from main thread! + //wxBitmap is NOT thread-safe: non-atomic ref-count just to begin with... + //- prohibit implicit calls to wxBitmap(const wxBitmap&) + //- prohibit calls to ~wxBitmap() and transitively ~IconData() + //- prohibit even wxBitmap() default constructor - better be safe than sorry! + }; + + boost::mutex lockIconList; + std::map iconList; //shared resource; Zstring is thread-safe like an int + std::queue iconSequence; //save sequence of buffer entry to delete oldest elements }; //################################################################################################################################################ @@ -457,17 +529,17 @@ class WorkerThread //lifetime is part of icon buffer public: WorkerThread(const std::shared_ptr& workload, const std::shared_ptr& buffer, - IconBuffer::IconSize sz) : + IconBuffer::IconSize st) : workload_(workload), buffer_(buffer), - icoSize(sz) {} + iconSizeType(st) {} void operator()(); //thread entry private: std::shared_ptr workload_; //main/worker thread may access different shared_ptr instances safely (even though they have the same target!) std::shared_ptr buffer_; //http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/shared_ptr.htm?sess=8153b05b34d890e02d48730db1ff7ddc#ThreadSafety - const IconBuffer::IconSize icoSize; + const IconBuffer::IconSize iconSizeType; }; @@ -493,20 +565,20 @@ void WorkerThread::operator()() //thread entry { boost::this_thread::interruption_point(); - const Zstring fileName = workload_->extractNextFile(); //start work: get next icon to load + const Zstring fileName = workload_->extractNextFile(); //start work: blocks until next icon to load is retrieved - if (buffer_->requestFileIcon(fileName)) - continue; //icon already in buffer: skip - - buffer_->insertIntoBuffer(fileName, getAssociatedIcon(fileName, icoSize)); + if (!buffer_->hasFileIcon(fileName)) //perf: workload may contain duplicate entries? + buffer_->moveIntoBuffer(fileName, getAssociatedIcon(fileName, iconSizeType)); } } + //######################### redirect to impl ##################################################### struct IconBuffer::Pimpl { - Pimpl() : workload(std::make_shared()), - buffer(std::make_shared()) {} + Pimpl() : + workload(std::make_shared()), + buffer (std::make_shared()) {} std::shared_ptr workload; std::shared_ptr buffer; @@ -516,9 +588,9 @@ struct IconBuffer::Pimpl IconBuffer::IconBuffer(IconSize sz) : pimpl(make_unique()), - icoSize(sz), - genDirIcon(::getGenericDirectoryIcon(sz).toWxIcon(IconBuffer::getSize(icoSize))), - genFileIcon(::getGenericFileIcon(sz).toWxIcon(IconBuffer::getSize(icoSize))) + iconSizeType(sz), + genDirIcon (::getGenericDirectoryIcon(sz).extractWxBitmap()), + genFileIcon(::getGenericFileIcon (sz).extractWxBitmap()) { pimpl->worker = boost::thread(WorkerThread(pimpl->workload, pimpl->buffer, sz)); } @@ -526,7 +598,7 @@ IconBuffer::IconBuffer(IconSize sz) : pimpl(make_unique()), IconBuffer::~IconBuffer() { - setWorkload(std::vector()); //make sure interruption point is always reached! + setWorkload(std::list()); //make sure interruption point is always reached! pimpl->worker.interrupt(); pimpl->worker.join(); //we assume precondition "worker.joinable()"!!! } @@ -543,7 +615,11 @@ int IconBuffer::getSize(IconSize icoSize) return 24; #endif case IconBuffer::SIZE_MEDIUM: +#ifdef FFS_WIN + if (!wereVistaOrLater) return 32; //48x48 doesn't look sharp on XP +#endif return 48; + case IconBuffer::SIZE_LARGE: return 128; } @@ -552,41 +628,51 @@ int IconBuffer::getSize(IconSize icoSize) } -bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon) +bool IconBuffer::readyForRetrieval(const Zstring& filename) { - auto getIcon = [&](const Zstring& entryName) -> bool - { - if (!icon) - return pimpl->buffer->requestFileIcon(entryName); - - IconHolder heldIcon; - if (!pimpl->buffer->requestFileIcon(entryName, &heldIcon)) - return false; - *icon = heldIcon.toWxIcon(IconBuffer::getSize(icoSize)); - return true; - }; +#ifdef FFS_WIN + if (iconSizeType == IconBuffer::SIZE_SMALL) + if (isCheapExtension(getFileExtension(filename))) + return true; +#endif + return pimpl->buffer->hasFileIcon(filename); +} + +Opt IconBuffer::retrieveFileIcon(const Zstring& filename) +{ #ifdef FFS_WIN //perf: let's read icons which don't need file access right away! No async delay justified! - if (icoSize == IconBuffer::SIZE_SMALL) //non-thumbnail view, we need file type icons only! + if (iconSizeType == IconBuffer::SIZE_SMALL) //non-thumbnail view, we need file type icons only! { const Zstring& extension = getFileExtension(filename); if (isCheapExtension(extension)) //"pricey" extensions are stored with fullnames and are read from disk, while cheap ones require just the extension { - if (!getIcon(extension)) - { - IconHolder heldIcon = getAssociatedIconByExt(extension, icoSize); //fast! - pimpl->buffer->insertIntoBuffer(extension, heldIcon); - if (icon) - *icon = heldIcon.toWxIcon(IconBuffer::getSize(icoSize)); - } - return true; + if (Opt ico = pimpl->buffer->retrieveFileIcon(extension)) + return ico; + + //make sure icon is in buffer, even if icon needs not be retrieved! + pimpl->buffer->moveIntoBuffer(extension, getAssociatedIconByExt(extension, iconSizeType)); + + Opt ico = pimpl->buffer->retrieveFileIcon(extension); + assert(ico); + return ico; } } #endif - return getIcon(filename); + if (Opt ico = pimpl->buffer->retrieveFileIcon(filename)) + return ico; + + //since this icon seems important right now, we don't want to wait until next setWorkload() to start retrieving + pimpl->workload->addToWorkload(filename); + pimpl->buffer->limitBufferSize(); + return NoValue(); } -void IconBuffer::setWorkload(const std::vector& load) { pimpl->workload->setWorkload(load); } -#endif + +void IconBuffer::setWorkload(const std::list& load) +{ + pimpl->workload->setWorkload(load); //since buffer can only increase due to new workload, + pimpl->buffer->limitBufferSize(); //this is the place to impose the limit from main thread! +} -- cgit