96
103
: type(type_), data(data_), size(size_), key(key_)
97
104
, slot(slot_), handle(handle_), self(self_)
105
, icon_info(NULL), result(NULL)
108
void InvokeSlot(GdkPixbuf* pixbuf)
110
slot(data, size, pixbuf);
112
// notify shadow tasks
113
for (auto shadow_task : shadow_tasks)
115
shadow_task->slot(shadow_task->data, shadow_task->size, pixbuf);
117
self->task_map_.erase(shadow_task->handle);
121
shadow_tasks.clear();
101
125
Handle ReturnCachedOrQueue(std::string const& data,
117
141
IconLoaderCallback slot);
143
// these methods might run asynchronously
119
144
bool ProcessTask(IconLoaderTask* task);
120
145
bool ProcessIconNameTask(IconLoaderTask* task);
121
146
bool ProcessGIconTask(IconLoaderTask* task);
123
// URI processing is async.
124
147
bool ProcessURITask(IconLoaderTask* task);
125
void ProcessURITaskReady(IconLoaderTask* task, char* contents, gsize length);
126
static void LoadContentsReady(GObject* object, GAsyncResult* res, IconLoaderTask* task);
149
// Loading/rendering of pixbufs is done in a separate thread
150
static gboolean LoaderJobFunc(GIOSchedulerJob* job, GCancellable *canc,
151
IconLoaderTask *task);
152
static gboolean LoadIconComplete(IconLoaderTask* task);
153
static gboolean CoalesceTasksCb(IconLoader::Impl* self);
128
155
// Loop calls the iteration function.
129
static bool Loop(Impl* self);
156
static gboolean Loop(Impl* self);
130
157
bool Iteration();
133
160
typedef std::map<std::string, glib::Object<GdkPixbuf>> ImageCache;
134
161
ImageCache cache_;
162
typedef std::map<std::string, IconLoaderTask*> CacheQueue;
163
CacheQueue queued_tasks_;
135
164
typedef std::queue<IconLoaderTask*> TaskQueue;
136
165
TaskQueue tasks_;
137
166
typedef std::map<Handle, IconLoaderTask*> TaskMap;
138
167
TaskMap task_map_;
168
typedef std::vector<IconLoaderTask*> TaskArray;
169
TaskArray finished_tasks_;
142
174
GtkIconTheme* theme_; // Not owned.
143
175
Handle handle_counter_;
250
283
IconLoaderTask* task = new IconLoaderTask(type, data, size, key,
251
284
slot, ++handle_counter_, this);
286
auto iter = queued_tasks_.find(key);
287
bool already_queued = iter != queued_tasks_.end();
288
IconLoaderTask* running_task = already_queued ? iter->second : NULL;
290
if (running_task != NULL)
292
running_task->shadow_tasks.push_back(task);
293
// do NOT push the task into the tasks queue,
294
// the parent task (which is in the queue) will handle it
295
task_map_[task->handle] = task;
297
LOG_DEBUG(logger) << "Appending shadow task " << data
298
<< ", queue size now at " << tasks_.size();
304
queued_tasks_[key] = task;
252
307
tasks_.push(task);
253
308
task_map_[task->handle] = task;
306
361
LOG_WARNING(logger) << "Request type " << task->type
307
362
<< " is not supported (" << task->data
308
363
<< " " << task->size << ")";
309
task->slot(task->data, task->size, nullptr);
364
task->InvokeSlot(nullptr);
313
368
bool IconLoader::Impl::ProcessIconNameTask(IconLoaderTask* task)
315
GdkPixbuf* pixbuf = nullptr;
316
370
GtkIconInfo* info = gtk_icon_theme_lookup_icon(theme_,
317
371
task->data.c_str(),
319
373
(GtkIconLookupFlags)0);
376
task->icon_info = info;
377
g_io_scheduler_push_job ((GIOSchedulerJobFunc) LoaderJobFunc,
378
task, NULL, G_PRIORITY_HIGH_IDLE, NULL);
324
pixbuf = gtk_icon_info_load_icon(info, &error);
325
if (GDK_IS_PIXBUF(pixbuf))
327
cache_[task->key] = pixbuf;
331
LOG_WARNING(logger) << "Unable to load icon " << task->data
332
<< " at size " << task->size << ": " << error;
334
gtk_icon_info_free(info);
409
446
<< " at size " << task->size << ": " << error;
412
task->slot(task->data, task->size, pixbuf);
449
task->InvokeSlot(nullptr);
416
453
bool IconLoader::Impl::ProcessURITask(IconLoaderTask* task)
418
glib::Object<GFile> file(g_file_new_for_uri(task->data.c_str()));
420
g_file_load_contents_async(file,
422
(GAsyncReadyCallback)LoadContentsReady,
455
g_io_scheduler_push_job ((GIOSchedulerJobFunc) LoaderJobFunc,
456
task, NULL, G_PRIORITY_HIGH_IDLE, NULL);
428
void IconLoader::Impl::ProcessURITaskReady(IconLoaderTask* task,
432
GInputStream* stream = g_memory_input_stream_new_from_data(contents, length, NULL);
435
glib::Object<GdkPixbuf> pixbuf(gdk_pixbuf_new_from_stream_at_scale(stream,
443
LOG_WARNING(logger) << "Unable to create pixbuf from input stream for "
444
<< task->data << " at size " << task->size << ": " << error;
448
cache_[task->key] = pixbuf;
451
task->slot(task->data, task->size, pixbuf);
452
g_input_stream_close(stream, NULL, NULL);
461
gboolean IconLoader::Impl::LoaderJobFunc(GIOSchedulerJob* job,
463
IconLoaderTask *task)
465
// careful here this is running in non-main thread
468
task->result = gtk_icon_info_load_icon(task->icon_info, &task->error);
470
gtk_icon_info_free (task->icon_info);
471
task->icon_info = NULL;
473
else if (task->type == REQUEST_TYPE_URI)
475
glib::Object<GFile> file(g_file_new_for_uri(task->data.c_str()));
476
glib::String contents;
479
if (g_file_load_contents(file, canc, &contents, &length,
482
glib::Object<GInputStream> stream(
483
g_memory_input_stream_new_from_data(contents.Value(), length, NULL));
485
task->result = gdk_pixbuf_new_from_stream_at_scale(stream,
491
g_input_stream_close(stream, canc, NULL);
495
g_io_scheduler_job_send_to_mainloop_async (job,
496
(GSourceFunc) LoadIconComplete,
503
// this will be invoked back in the thread from which push_job was called
504
gboolean IconLoader::Impl::LoadIconComplete(IconLoaderTask* task)
506
if (task->self->coalesce_id_ == 0)
508
// we're using lower priority than the GIOSchedulerJob uses to deliver
509
// results to the mainloop
510
task->self->coalesce_id_ =
511
g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE + 10,
513
(GSourceFunc) IconLoader::Impl::CoalesceTasksCb,
518
task->self->finished_tasks_.push_back (task);
523
gboolean IconLoader::Impl::CoalesceTasksCb(IconLoader::Impl* self)
525
for (auto task : self->finished_tasks_)
527
// FIXME: we could update the cache sooner, but there are ref-counting
528
// issues on the pixbuf (and inside the slot callbacks) that prevent us
530
if (GDK_IS_PIXBUF(task->result))
532
task->self->cache_[task->key] = task->result;
536
LOG_WARNING(logger) << "Unable to load icon " << task->data
537
<< " at size " << task->size << ": " << task->error;
540
task->InvokeSlot(task->result);
542
// this was all async, we need to erase the task from the task_map
543
self->task_map_.erase(task->handle);
544
self->queued_tasks_.erase(task->key);
548
self->finished_tasks_.clear ();
549
self->coalesce_id_ = 0;
455
554
bool IconLoader::Impl::Iteration()
457
static const int MAX_MICRO_SECS = 10000;
556
static const int MAX_MICRO_SECS = 1000;
458
557
util::Timer timer;
460
559
bool queue_empty = tasks_.empty();
462
while (!queue_empty &&
463
(timer.ElapsedMicroSeconds() < MAX_MICRO_SECS))
561
// always do at least one iteration if the queue isn't empty
465
564
IconLoaderTask* task = tasks_.front();
467
566
if (ProcessTask(task))
469
568
task_map_.erase(task->handle);
569
queued_tasks_.erase(task->key);
474
574
queue_empty = tasks_.empty();
576
if (timer.ElapsedMicroSeconds() >= MAX_MICRO_SECS) break;
477
579
LOG_DEBUG(logger) << "Iteration done, queue size now at " << tasks_.size();
490
bool IconLoader::Impl::Loop(IconLoader::Impl* self)
492
return self->Iteration();
495
void IconLoader::Impl::LoadContentsReady(GObject* obj,
497
IconLoaderTask* task)
499
glib::String contents;
503
if (g_file_load_contents_finish(G_FILE(obj), res, &contents, &length, NULL, &error))
505
task->self->ProcessURITaskReady(task, contents.Value(), length);
509
LOG_WARNING(logger) << "Unable to load contents of "
510
<< task->data << ": " << error;
511
task->slot(task->data, task->size, nullptr);
513
task->self->task_map_.erase(task->handle);
592
gboolean IconLoader::Impl::Loop(IconLoader::Impl* self)
594
return self->Iteration() ? TRUE : FALSE;
518
597
IconLoader::IconLoader()
519
598
: pimpl(new Impl())