Back to index

unity  6.0.0
IconLoader.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003 * Copyright (C) 2010, 2011 Canonical Ltd
00004 *
00005 * This program is free software: you can redistribute it and/or modify
00006 * it under the terms of the GNU General Public License version 3 as
00007 * published by the Free Software Foundation.
00008 *
00009 * This program is distributed in the hope that it will be useful,
00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 * GNU General Public License for more details.
00013 *
00014 * You should have received a copy of the GNU General Public License
00015 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016 *
00017 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
00018 */
00019 
00020 #include "IconLoader.h"
00021 
00022 #include <queue>
00023 #include <sstream>
00024 #include <boost/algorithm/string.hpp>
00025 
00026 #include <NuxCore/Logger.h>
00027 #include <UnityCore/GLibSource.h>
00028 #include <UnityCore/GLibSignal.h>
00029 
00030 #include "unity-shared/Timer.h"
00031 
00032 namespace unity
00033 {
00034 namespace
00035 {
00036 nux::logging::Logger logger("unity.iconloader");
00037 const unsigned MIN_ICON_SIZE = 2;
00038 }
00039 
00040 class IconLoader::Impl
00041 {
00042 public:
00043   // The Handle typedef is used to explicitly indicate which integers are
00044   // infact our opaque handles.
00045   typedef int Handle;
00046 
00047   Impl();
00048 
00049   Handle LoadFromIconName(std::string const& icon_name,
00050                           unsigned size,
00051                           IconLoaderCallback slot);
00052 
00053   Handle LoadFromGIconString(std::string const& gicon_string,
00054                              unsigned size,
00055                              IconLoaderCallback slot);
00056 
00057   Handle LoadFromFilename(std::string const& filename,
00058                           unsigned size,
00059                           IconLoaderCallback slot);
00060 
00061   Handle LoadFromURI(std::string const& uri,
00062                      unsigned size,
00063                      IconLoaderCallback slot);
00064 
00065   void DisconnectHandle(Handle handle);
00066 
00067 private:
00068 
00069   enum IconLoaderRequestType
00070   {
00071     REQUEST_TYPE_ICON_NAME = 0,
00072     REQUEST_TYPE_GICON_STRING,
00073     REQUEST_TYPE_URI,
00074   };
00075 
00076   struct IconLoaderTask
00077   {
00078     typedef std::shared_ptr<IconLoaderTask> Ptr;
00079 
00080     IconLoaderRequestType type;
00081     std::string data;
00082     unsigned int size;
00083     std::string key;
00084     IconLoaderCallback slot;
00085     Handle handle;
00086     Impl* impl;
00087     GtkIconInfo* icon_info;
00088     glib::Object<GdkPixbuf> result;
00089     glib::Error error;
00090     std::list<IconLoaderTask::Ptr> shadow_tasks;
00091 
00092     IconLoaderTask(IconLoaderRequestType type_,
00093                    std::string const& data_,
00094                    unsigned size_,
00095                    std::string const& key_,
00096                    IconLoaderCallback slot_,
00097                    Handle handle_,
00098                    Impl* self_)
00099       : type(type_), data(data_), size(size_), key(key_)
00100       , slot(slot_), handle(handle_), impl(self_)
00101       , icon_info(nullptr)
00102       {}
00103 
00104     ~IconLoaderTask()
00105     {
00106       if (icon_info)
00107         ::gtk_icon_info_free(icon_info);
00108     }
00109 
00110     void InvokeSlot()
00111     {
00112       slot(data, size, result);
00113 
00114       // notify shadow tasks
00115       for (auto shadow_task : shadow_tasks)
00116       {
00117         shadow_task->slot(shadow_task->data, shadow_task->size, result);
00118         impl->task_map_.erase(shadow_task->handle);
00119       }
00120 
00121       shadow_tasks.clear();
00122     }
00123 
00124     bool Process()
00125     {
00126       // Check the cache again, as previous tasks might have wanted the same
00127       if (impl->CacheLookup(key, data, size, slot))
00128         return true;
00129 
00130       LOG_DEBUG(logger) << "Processing  " << data << " at size " << size;
00131 
00132       // Rely on the compiler to tell us if we miss a new type
00133       switch (type)
00134       {
00135         case REQUEST_TYPE_ICON_NAME:
00136           return ProcessIconNameTask();
00137         case REQUEST_TYPE_GICON_STRING:
00138           return ProcessGIconTask();
00139         case REQUEST_TYPE_URI:
00140           return ProcessURITask();
00141       }
00142 
00143       LOG_WARNING(logger) << "Request type " << type
00144                           << " is not supported (" << data
00145                           << " " << size << ")";
00146       result = nullptr;
00147       InvokeSlot();
00148 
00149       return true;
00150     }
00151 
00152     bool ProcessIconNameTask()
00153     {
00154       GtkIconInfo* info = ::gtk_icon_theme_lookup_icon(impl->theme_, data.c_str(),
00155                                                        size, static_cast<GtkIconLookupFlags>(0));
00156       if (info)
00157       {
00158         icon_info = info;
00159         PushSchedulerJob();
00160 
00161         return false;
00162       }
00163       else
00164       {
00165         LOG_WARNING(logger) << "Unable to load icon " << data
00166                             << " at size " << size;
00167       }
00168 
00169       result = nullptr;
00170       InvokeSlot();
00171 
00172       return true;
00173     }
00174 
00175     bool ProcessGIconTask()
00176     {
00177       glib::Error error;
00178       glib::Object<GIcon> icon(::g_icon_new_for_string(data.c_str(), &error));
00179 
00180       if (G_IS_FILE_ICON(icon.RawPtr()))
00181       {
00182         // [trasfer none]
00183         GFile* file = ::g_file_icon_get_file(G_FILE_ICON(icon.RawPtr()));
00184         glib::String uri(::g_file_get_uri(file));
00185 
00186         type = REQUEST_TYPE_URI;
00187         data = uri.Str();
00188 
00189         return ProcessURITask();
00190       }
00191       else if (G_IS_ICON(icon.RawPtr()))
00192       {
00193         GtkIconInfo* info = ::gtk_icon_theme_lookup_by_gicon(impl->theme_, icon, size,
00194                                                              static_cast<GtkIconLookupFlags>(0));
00195         if (info)
00196         {
00197           icon_info = info;
00198           PushSchedulerJob();
00199 
00200           return false;
00201         }
00202         else
00203         {
00204           // There is some funkiness in some programs where they install
00205           // their icon to /usr/share/icons/hicolor/apps/, but they
00206           // name the Icon= key as `foo.$extension` which breaks loading
00207           // So we can try and work around that here.
00208 
00209           if (boost::iends_with(data, ".png") ||
00210               boost::iends_with(data, ".xpm") ||
00211               boost::iends_with(data, ".gif") ||
00212               boost::iends_with(data, ".jpg"))
00213           {
00214             data = data.substr(0, data.size() - 4);
00215             return ProcessIconNameTask();
00216           }
00217           else
00218           {
00219             LOG_WARNING(logger) << "Unable to load icon " << data
00220                                 << " at size " << size;
00221           }
00222         }
00223       }
00224       else
00225       {
00226         LOG_WARNING(logger) << "Unable to load icon " << data
00227                             << " at size " << size << ": " << error;
00228       }
00229 
00230       InvokeSlot();
00231       return true;
00232     }
00233 
00234     bool ProcessURITask()
00235     {
00236       PushSchedulerJob();
00237 
00238       return false;
00239     }
00240 
00241     void PushSchedulerJob()
00242     {
00243       ::g_io_scheduler_push_job (LoaderJobFunc, this, nullptr, G_PRIORITY_HIGH_IDLE, nullptr);
00244     }
00245 
00246     // Loading/rendering of pixbufs is done in a separate thread
00247     static gboolean LoaderJobFunc(GIOSchedulerJob* job, GCancellable *canc, gpointer data)
00248     {
00249       auto task = static_cast<IconLoaderTask*>(data);
00250 
00251       // careful here this is running in non-main thread
00252       if (task->icon_info)
00253       {
00254         task->result = ::gtk_icon_info_load_icon(task->icon_info, &task->error);
00255       }
00256       else if (task->type == REQUEST_TYPE_URI)
00257       {
00258         glib::Object<GFile> file(::g_file_new_for_uri(task->data.c_str()));
00259         glib::String contents;
00260         gsize length = 0;
00261 
00262         if (::g_file_load_contents(file, canc, &contents, &length,
00263                                  nullptr, &task->error))
00264         {
00265           glib::Object<GInputStream> stream(
00266               ::g_memory_input_stream_new_from_data(contents.Value(), length, nullptr));
00267 
00268           task->result = ::gdk_pixbuf_new_from_stream_at_scale(stream,
00269                                                                -1,
00270                                                                task->size,
00271                                                                TRUE,
00272                                                                canc,
00273                                                                &task->error);
00274           ::g_input_stream_close(stream, canc, nullptr);
00275         }
00276       }
00277 
00278       ::g_io_scheduler_job_send_to_mainloop_async (job, LoadIconComplete, task, nullptr);
00279 
00280       return FALSE;
00281     }
00282 
00283     // this will be invoked back in the thread from which push_job was called
00284     static gboolean LoadIconComplete(gpointer data)
00285     {
00286       auto task = static_cast<IconLoaderTask*>(data);
00287       auto impl = task->impl;
00288 
00289       if (GDK_IS_PIXBUF(task->result.RawPtr()))
00290       {
00291         impl->cache_[task->key] = task->result;
00292       }
00293       else
00294       {
00295         if (task->result)
00296           task->result = nullptr;
00297 
00298         LOG_WARNING(logger) << "Unable to load icon " << task->data
00299                             << " at size " << task->size << ": " << task->error;
00300       }
00301 
00302       impl->finished_tasks_.push_back(task);
00303 
00304       if (!impl->coalesce_timeout_)
00305       {
00306         // we're using lower priority than the GIOSchedulerJob uses to deliver
00307         // results to the mainloop
00308         auto prio = static_cast<glib::Source::Priority>(glib::Source::Priority::DEFAULT_IDLE + 40);
00309         impl->coalesce_timeout_.reset(new glib::Timeout(40, prio));
00310         impl->coalesce_timeout_->Run(sigc::mem_fun(impl, &Impl::CoalesceTasksCb));
00311       }
00312 
00313       return FALSE;
00314     }
00315   };
00316 
00317   Handle ReturnCachedOrQueue(std::string const& data,
00318                              unsigned size,
00319                              IconLoaderCallback slot,
00320                              IconLoaderRequestType type);
00321 
00322   Handle QueueTask(std::string const& key,
00323                    std::string const& data,
00324                    unsigned size,
00325                    IconLoaderCallback slot,
00326                    IconLoaderRequestType type);
00327 
00328   std::string Hash(std::string const& data, unsigned size);
00329 
00330   bool CacheLookup(std::string const& key,
00331                    std::string const& data,
00332                    unsigned size,
00333                    IconLoaderCallback slot);
00334 
00335   // Looping idle callback function
00336   bool Iteration();
00337   bool CoalesceTasksCb();
00338 
00339 private:
00340   std::map<std::string, glib::Object<GdkPixbuf>> cache_;
00341   std::map<std::string, IconLoaderTask::Ptr> queued_tasks_;
00342   std::queue<IconLoaderTask::Ptr> tasks_;
00343   std::map<Handle, IconLoaderTask::Ptr> task_map_;
00344   std::vector<IconLoaderTask*> finished_tasks_;
00345 
00346   bool no_load_;
00347   GtkIconTheme* theme_; // Not owned.
00348   Handle handle_counter_;
00349   glib::Source::UniquePtr idle_;
00350   glib::Source::UniquePtr coalesce_timeout_;
00351   glib::Signal<void, GtkIconTheme*> theme_changed_signal_;
00352 };
00353 
00354 
00355 IconLoader::Impl::Impl()
00356   : // Option to disable loading, if you're testing performance of other things
00357     no_load_(::getenv("UNITY_ICON_LOADER_DISABLE"))
00358   , theme_(::gtk_icon_theme_get_default())
00359   , handle_counter_(0)
00360 {
00361   theme_changed_signal_.Connect(theme_, "changed", [&] (GtkIconTheme*) {
00362     /* Since the theme has been changed we can clear the cache, however we
00363      * could include two improvements here:
00364      *  1) clear only the themed icons in cache
00365      *  2) make the clients of this class to update their icons forcing them
00366      *     to reload the pixbufs and erase the cached textures, to make this
00367      *     apply immediately. */
00368     cache_.clear();
00369   });
00370 }
00371 
00372 int IconLoader::Impl::LoadFromIconName(std::string const& icon_name,
00373                                        unsigned size,
00374                                        IconLoaderCallback slot)
00375 {
00376   if (no_load_ || icon_name.empty() || size < MIN_ICON_SIZE)
00377     return 0;
00378 
00379   // We need to check this because of legacy desktop files
00380   if (icon_name[0] == '/')
00381   {
00382     return LoadFromFilename(icon_name, size, slot);
00383   }
00384 
00385   return ReturnCachedOrQueue(icon_name, size, slot, REQUEST_TYPE_ICON_NAME);
00386 }
00387 
00388 int IconLoader::Impl::LoadFromGIconString(std::string const& gicon_string,
00389                                           unsigned size,
00390                                           IconLoaderCallback slot)
00391 {
00392   if (no_load_ || gicon_string.empty() || size < MIN_ICON_SIZE)
00393     return 0;
00394 
00395   return ReturnCachedOrQueue(gicon_string, size, slot, REQUEST_TYPE_GICON_STRING);
00396 }
00397 
00398 int IconLoader::Impl::LoadFromFilename(std::string const& filename,
00399                                        unsigned size,
00400                                        IconLoaderCallback slot)
00401 {
00402   if (no_load_ || filename.empty() || size < MIN_ICON_SIZE)
00403     return 0;
00404 
00405   glib::Object<GFile> file(::g_file_new_for_path(filename.c_str()));
00406   glib::String uri(::g_file_get_uri(file));
00407 
00408   return LoadFromURI(uri.Str(), size, slot);
00409 }
00410 
00411 int IconLoader::Impl::LoadFromURI(std::string const& uri,
00412                                   unsigned size,
00413                                   IconLoaderCallback slot)
00414 {
00415   if (no_load_ || uri.empty() || size < MIN_ICON_SIZE)
00416     return 0;
00417 
00418   return ReturnCachedOrQueue(uri, size, slot, REQUEST_TYPE_URI);
00419 }
00420 
00421 void IconLoader::Impl::DisconnectHandle(Handle handle)
00422 {
00423   auto iter = task_map_.find(handle);
00424 
00425   if (iter != task_map_.end())
00426   {
00427     iter->second->slot.disconnect();
00428   }
00429 }
00430 
00431 //
00432 // Private Methods
00433 //
00434 
00435 int IconLoader::Impl::ReturnCachedOrQueue(std::string const& data,
00436                                           unsigned size,
00437                                           IconLoaderCallback slot,
00438                                           IconLoaderRequestType type)
00439 {
00440   Handle result = 0;
00441   std::string key(Hash(data, size));
00442 
00443   if (!CacheLookup(key, data, size, slot))
00444   {
00445     result = QueueTask(key, data, size, slot, type);
00446   }
00447   return result;
00448 }
00449 
00450 
00451 int IconLoader::Impl::QueueTask(std::string const& key,
00452                                 std::string const& data,
00453                                 unsigned size,
00454                                 IconLoaderCallback slot,
00455                                 IconLoaderRequestType type)
00456 {
00457   auto task = std::make_shared<IconLoaderTask>(type, data, size, key, slot, ++handle_counter_, this);
00458   auto iter = queued_tasks_.find(key);
00459 
00460   if (iter != queued_tasks_.end())
00461   {
00462     IconLoaderTask::Ptr const& running_task = iter->second;
00463     running_task->shadow_tasks.push_back(task);
00464     // do NOT push the task into the tasks queue,
00465     // the parent task (which is in the queue) will handle it
00466     task_map_[task->handle] = task;
00467 
00468     LOG_DEBUG(logger) << "Appending shadow task  " << data
00469                       << ", queue size now at " << tasks_.size();
00470 
00471     return task->handle;
00472   }
00473   else
00474   {
00475     queued_tasks_[key] = task;
00476   }
00477 
00478   tasks_.push(task);
00479   task_map_[task->handle] = task;
00480 
00481   LOG_DEBUG(logger) << "Pushing task  " << data << " at size " << size
00482                     << ", queue size now at " << tasks_.size();
00483 
00484   if (!idle_)
00485   {
00486     idle_.reset(new glib::Idle(sigc::mem_fun(this, &Impl::Iteration), glib::Source::Priority::LOW));
00487   }
00488   return task->handle;
00489 }
00490 
00491 std::string IconLoader::Impl::Hash(std::string const& data, unsigned size)
00492 {
00493   std::ostringstream sout;
00494   sout << data << ":" << size;
00495   return sout.str();
00496 }
00497 
00498 bool IconLoader::Impl::CacheLookup(std::string const& key,
00499                                    std::string const& data,
00500                                    unsigned size,
00501                                    IconLoaderCallback slot)
00502 {
00503   auto iter = cache_.find(key);
00504   bool found = iter != cache_.end();
00505 
00506   if (found)
00507   {
00508     glib::Object<GdkPixbuf> const& pixbuf = iter->second;
00509     slot(data, size, pixbuf);
00510   }
00511 
00512   return found;
00513 }
00514 
00515 bool IconLoader::Impl::CoalesceTasksCb()
00516 {
00517   for (auto task : finished_tasks_)
00518   {
00519     task->InvokeSlot();
00520 
00521     // this was all async, we need to erase the task from the task_map
00522     task_map_.erase(task->handle);
00523     queued_tasks_.erase(task->key);
00524   }
00525 
00526   finished_tasks_.clear();
00527   coalesce_timeout_.reset();
00528 
00529   return false;
00530 }
00531 
00532 bool IconLoader::Impl::Iteration()
00533 {
00534   static const int MAX_MICRO_SECS = 1000;
00535   util::Timer timer;
00536 
00537   bool queue_empty = tasks_.empty();
00538 
00539   // always do at least one iteration if the queue isn't empty
00540   while (!queue_empty)
00541   {
00542     IconLoaderTask::Ptr const& task = tasks_.front();
00543 
00544     if (task->Process())
00545     {
00546       task_map_.erase(task->handle);
00547       queued_tasks_.erase(task->key);
00548     }
00549 
00550     tasks_.pop();
00551     queue_empty = tasks_.empty();
00552 
00553     if (timer.ElapsedMicroSeconds() >= MAX_MICRO_SECS) break;
00554   }
00555 
00556   LOG_DEBUG(logger) << "Iteration done, queue size now at " << tasks_.size();
00557 
00558   if (queue_empty)
00559   {
00560     if (task_map_.empty())
00561       handle_counter_ = 0;
00562 
00563     idle_.reset();
00564   }
00565 
00566   return !queue_empty;
00567 }
00568 
00569 IconLoader::IconLoader()
00570   : pimpl(new Impl())
00571 {
00572 }
00573 
00574 IconLoader::~IconLoader()
00575 {
00576 }
00577 
00578 IconLoader& IconLoader::GetDefault()
00579 {
00580   static IconLoader default_loader;
00581   return default_loader;
00582 }
00583 
00584 int IconLoader::LoadFromIconName(std::string const& icon_name,
00585                                  unsigned size,
00586                                  IconLoaderCallback slot)
00587 {
00588   return pimpl->LoadFromIconName(icon_name, size, slot);
00589 }
00590 
00591 int IconLoader::LoadFromGIconString(std::string const& gicon_string,
00592                                     unsigned size,
00593                                     IconLoaderCallback slot)
00594 {
00595   return pimpl->LoadFromGIconString(gicon_string, size, slot);
00596 }
00597 
00598 int IconLoader::LoadFromFilename(std::string const& filename,
00599                                  unsigned size,
00600                                  IconLoaderCallback slot)
00601 {
00602   return pimpl->LoadFromFilename(filename, size, slot);
00603 }
00604 
00605 int IconLoader::LoadFromURI(std::string const& uri,
00606                             unsigned size,
00607                             IconLoaderCallback slot)
00608 {
00609   return pimpl->LoadFromURI(uri, size, slot);
00610 }
00611 
00612 void IconLoader::DisconnectHandle(int handle)
00613 {
00614   pimpl->DisconnectHandle(handle);
00615 }
00616 
00617 
00618 }