summaryrefslogtreecommitdiff
path: root/src/services/application-service.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/services/application-service.c')
-rw-r--r--src/services/application-service.c733
1 files changed, 733 insertions, 0 deletions
diff --git a/src/services/application-service.c b/src/services/application-service.c
new file mode 100644
index 0000000..4c81a27
--- /dev/null
+++ b/src/services/application-service.c
@@ -0,0 +1,733 @@
+#include "services/application-service.h"
+#include "utils.h"
+#include <gtk/gtk.h>
+#include <gio/gdesktopappinfo.h>
+#include <json-glib/json-glib.h>
+#include <string.h>
+
+//
+// FoobarApplicationItem:
+//
+// Represents a single application/desktop file.
+//
+
+struct _FoobarApplicationItem
+{
+ GObject parent_instance;
+ FoobarApplicationService* service;
+ GAppInfo* info;
+};
+
+enum
+{
+ APP_PROP_ID = 1,
+ APP_PROP_NAME,
+ APP_PROP_DESCRIPTION,
+ APP_PROP_EXECUTABLE,
+ APP_PROP_CATEGORIES,
+ APP_PROP_ICON,
+ APP_PROP_FREQUENCY,
+ N_APP_PROPS,
+};
+
+static GParamSpec* app_props[N_APP_PROPS] = { 0 };
+
+static void foobar_application_item_class_init ( FoobarApplicationItemClass* klass );
+static void foobar_application_item_init ( FoobarApplicationItem* self );
+static void foobar_application_item_get_property( GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec );
+static void foobar_application_item_finalize ( GObject* object );
+static FoobarApplicationItem* foobar_application_item_new ( FoobarApplicationService* service );
+static void foobar_application_item_set_info ( FoobarApplicationItem* self,
+ GAppInfo* value );
+
+G_DEFINE_FINAL_TYPE( FoobarApplicationItem, foobar_application_item, G_TYPE_OBJECT )
+
+//
+// FoobarApplicationService:
+//
+// Service providing a list of all installed applications. This implemented using GLib's AppInfo API.
+//
+
+struct _FoobarApplicationService
+{
+ GObject parent_instance;
+ GListStore* items;
+ GtkSortListModel* sorted_items;
+ GAppInfoMonitor* monitor;
+ GHashTable* frequencies; // only modified on the main thread, but possibly read on a background thread
+ GMutex frequencies_mutex;
+ GMutex write_cache_mutex;
+ gchar* cache_path;
+ gulong changed_handler_id;
+};
+
+enum
+{
+ PROP_ITEMS = 1,
+ N_PROPS,
+};
+
+static GParamSpec* props[N_PROPS] = { 0 };
+
+static void foobar_application_service_class_init ( FoobarApplicationServiceClass* klass );
+static void foobar_application_service_init ( FoobarApplicationService* self );
+static void foobar_application_service_get_property ( GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec );
+static void foobar_application_service_finalize ( GObject* object );
+static void foobar_application_service_handle_changed ( GAppInfoMonitor* monitor,
+ gpointer userdata );
+static void foobar_application_service_update ( FoobarApplicationService* self );
+static void foobar_application_service_read_cache ( FoobarApplicationService* self );
+static void foobar_application_service_read_cache_foreach_cb ( JsonObject* object,
+ gchar const* member_name,
+ JsonNode* member_node,
+ gpointer userdata );
+static void foobar_application_service_write_cache ( FoobarApplicationService* self );
+static void foobar_application_service_write_cache_cb ( GObject* object,
+ GAsyncResult* result,
+ gpointer userdata );
+static void foobar_application_service_write_cache_async ( FoobarApplicationService* self,
+ GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer userdata );
+static gboolean foobar_application_service_write_cache_finish ( FoobarApplicationService* self,
+ GAsyncResult* result,
+ GError** error );
+static void foobar_application_service_write_cache_thread ( GTask* task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable* cancellable );
+static void foobar_application_service_write_cache_foreach_cb( gpointer key,
+ gpointer value,
+ gpointer userdata );
+static gint foobar_application_service_sort_func ( gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata );
+
+G_DEFINE_FINAL_TYPE( FoobarApplicationService, foobar_application_service, G_TYPE_OBJECT )
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Item Implementation
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Static initialization for application items.
+//
+void foobar_application_item_class_init( FoobarApplicationItemClass* klass )
+{
+ GObjectClass* object_klass = G_OBJECT_CLASS( klass );
+ object_klass->get_property = foobar_application_item_get_property;
+ object_klass->finalize = foobar_application_item_finalize;
+
+ app_props[APP_PROP_ID] = g_param_spec_string(
+ "id",
+ "ID",
+ "Application identifier string.",
+ NULL,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_NAME] = g_param_spec_string(
+ "name",
+ "Name",
+ "Displayed name for the application.",
+ NULL,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_DESCRIPTION] = g_param_spec_string(
+ "description",
+ "Description",
+ "Brief description of the application.",
+ NULL,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_EXECUTABLE] = g_param_spec_string(
+ "executable",
+ "Executable",
+ "The executable name of the application.",
+ NULL,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_CATEGORIES] = g_param_spec_string(
+ "categories",
+ "Categories",
+ "Semicolon-separated list of application categories.",
+ NULL,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_ICON] = g_param_spec_object(
+ "icon",
+ "Icon",
+ "Icon to show for the application.",
+ G_TYPE_ICON,
+ G_PARAM_READABLE );
+ app_props[APP_PROP_FREQUENCY] = g_param_spec_int64(
+ "frequency",
+ "Frequency",
+ "The number of times the application was opened.",
+ 0,
+ INT64_MAX,
+ 0,
+ G_PARAM_READABLE );
+ g_object_class_install_properties( object_klass, N_APP_PROPS, app_props );
+}
+
+//
+// Instance initialization for application items.
+//
+void foobar_application_item_init( FoobarApplicationItem* self )
+{
+ (void)self;
+}
+
+//
+// Property getter implementation, mapping a property id to a method.
+//
+void foobar_application_item_get_property(
+ GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec )
+{
+ FoobarApplicationItem* self = (FoobarApplicationItem*)object;
+
+ switch ( prop_id )
+ {
+ case APP_PROP_ID:
+ g_value_set_string( value, foobar_application_item_get_id( self ) );
+ break;
+ case APP_PROP_NAME:
+ g_value_set_string( value, foobar_application_item_get_name( self ) );
+ break;
+ case APP_PROP_DESCRIPTION:
+ g_value_set_string( value, foobar_application_item_get_description( self ) );
+ break;
+ case APP_PROP_EXECUTABLE:
+ g_value_set_string( value, foobar_application_item_get_executable( self ) );
+ break;
+ case APP_PROP_CATEGORIES:
+ g_value_set_string( value, foobar_application_item_get_categories( self ) );
+ break;
+ case APP_PROP_ICON:
+ g_value_set_object( value, foobar_application_item_get_icon( self ) );
+ break;
+ case APP_PROP_FREQUENCY:
+ g_value_set_int64( value, foobar_application_item_get_frequency( self ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Instance cleanup for application items.
+//
+void foobar_application_item_finalize( GObject* object )
+{
+ FoobarApplicationItem* self = (FoobarApplicationItem*)object;
+
+ g_clear_object( &self->info );
+
+ G_OBJECT_CLASS( foobar_application_item_parent_class )->finalize( object );
+}
+
+//
+// Create a new application item belonging to the parent service (captured as an unowned reference).
+//
+FoobarApplicationItem* foobar_application_item_new( FoobarApplicationService* service )
+{
+ FoobarApplicationItem* self = g_object_new( FOOBAR_TYPE_APPLICATION_ITEM, NULL );
+ self->service = service;
+ return self;
+}
+
+//
+// Get the application's unique textual identifier.
+//
+gchar const* foobar_application_item_get_id( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return self->info ? g_app_info_get_id( self->info ) : NULL;
+}
+
+//
+// Get the application's human-readable name.
+//
+gchar const* foobar_application_item_get_name( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return self->info ? g_app_info_get_display_name( self->info ) : NULL;
+}
+
+//
+// Get an optional short description for the application.
+//
+gchar const* foobar_application_item_get_description( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return self->info ? g_app_info_get_description( self->info ) : NULL;
+}
+
+//
+// Get the application's executable name.
+//
+gchar const* foobar_application_item_get_executable( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return self->info ? g_app_info_get_executable( self->info ) : NULL;
+}
+
+//
+// Get the semicolon-separated list of categories for this application.
+//
+gchar const* foobar_application_item_get_categories( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return G_IS_DESKTOP_APP_INFO( self->info )
+ ? g_desktop_app_info_get_categories( G_DESKTOP_APP_INFO( self->info ) )
+ : NULL;
+}
+
+//
+// Get an icon representing the application.
+//
+GIcon* foobar_application_item_get_icon( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), NULL );
+ return self->info ? g_app_info_get_icon( self->info ) : NULL;
+}
+
+//
+// Get the number of times this application was opened using the launcher.
+//
+gint64 foobar_application_item_get_frequency( FoobarApplicationItem* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), 0 );
+
+ if ( !self->service ) { return 0; }
+ gpointer value = g_hash_table_lookup( self->service->frequencies, foobar_application_item_get_id( self ) );
+ return GPOINTER_TO_SIZE( value );
+}
+
+//
+// Update the backing GAppInfo object for the application.
+//
+void foobar_application_item_set_info(
+ FoobarApplicationItem* self,
+ GAppInfo* value )
+{
+ g_return_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ) );
+
+ if ( self->info != value )
+ {
+ g_clear_object( &self->info );
+
+ if ( value ) { self->info = g_object_ref( value ); }
+
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_ID] );
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_NAME] );
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_DESCRIPTION] );
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_EXECUTABLE] );
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_ICON] );
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_FREQUENCY] );
+ }
+}
+
+//
+// Match the item against the given search terms.
+//
+gboolean foobar_application_item_match(
+ FoobarApplicationItem* self,
+ gchar const* const* terms )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ), FALSE );
+ g_return_val_if_fail( terms != NULL, FALSE );
+
+ for ( gchar const* const* it = terms; *it; ++it )
+ {
+ gchar const* term = *it;
+ gchar const* name = foobar_application_item_get_name( self );
+ gchar const* description = foobar_application_item_get_description( self );
+ gchar const* executable = foobar_application_item_get_executable( self );
+ gchar const* categories = foobar_application_item_get_categories( self );
+ gchar const* id = foobar_application_item_get_id( self );
+ if ( name && strcasestr( name, term ) != NULL ) { continue; }
+ if ( description && strcasestr( description, term ) != NULL ) { continue; }
+ if ( executable && strcasestr( executable, term ) != NULL ) { continue; }
+ if ( categories && strcasestr( categories, term ) != NULL ) { continue; }
+ if ( id && strcasestr( id, term ) != NULL ) { continue; }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+//
+// Launch the application, increasing its frequency by one.
+//
+void foobar_application_item_launch( FoobarApplicationItem* self )
+{
+ g_return_if_fail( FOOBAR_IS_APPLICATION_ITEM( self ) );
+
+ if ( !self->info ) { return; }
+
+ g_autoptr( GError ) error = NULL;
+ if ( !g_app_info_launch( self->info, NULL, NULL, &error ) )
+ {
+ g_warning( "Unable to launch application: %s", error->message );
+ }
+
+ gchar const* id = foobar_application_item_get_id( self );
+ if ( !self->service || !id ) { return; }
+
+ gpointer value = g_hash_table_lookup( self->service->frequencies, id );
+ value = GSIZE_TO_POINTER( GPOINTER_TO_SIZE( value ) + 1 );
+ g_mutex_lock( &self->service->frequencies_mutex );
+ g_hash_table_insert( self->service->frequencies, g_strdup( id ), value );
+ g_mutex_unlock( &self->service->frequencies_mutex );
+
+ g_object_notify_by_pspec( G_OBJECT( self ), app_props[APP_PROP_FREQUENCY] );
+ gtk_sorter_changed( gtk_sort_list_model_get_sorter( self->service->sorted_items ), GTK_SORTER_CHANGE_DIFFERENT );
+ foobar_application_service_write_cache( self->service );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Service Implementation
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Static initialization for the application service.
+//
+void foobar_application_service_class_init( FoobarApplicationServiceClass* klass )
+{
+ GObjectClass* object_klass = G_OBJECT_CLASS( klass );
+ object_klass->get_property = foobar_application_service_get_property;
+ object_klass->finalize = foobar_application_service_finalize;
+
+ props[PROP_ITEMS] = g_param_spec_object(
+ "items",
+ "Items",
+ "Application items, sorted by their frequency.",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READABLE );
+ g_object_class_install_properties( object_klass, N_PROPS, props );
+}
+
+//
+// Instance initialization for the application service.
+//
+void foobar_application_service_init( FoobarApplicationService* self )
+{
+ self->items = g_list_store_new( FOOBAR_TYPE_APPLICATION_ITEM );
+
+ GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_application_service_sort_func, NULL, NULL );
+ self->sorted_items = gtk_sort_list_model_new( G_LIST_MODEL( g_object_ref( self->items ) ), GTK_SORTER( sorter ) );
+
+ self->cache_path = foobar_get_cache_path( "application-frequencies.json" );
+ self->frequencies = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL );
+ g_mutex_init( &self->frequencies_mutex );
+ g_mutex_init( &self->write_cache_mutex );
+
+ foobar_application_service_read_cache( self );
+ foobar_application_service_update( self );
+
+ self->monitor = g_app_info_monitor_get( );
+ self->changed_handler_id = g_signal_connect(
+ self->monitor,
+ "changed",
+ G_CALLBACK( foobar_application_service_handle_changed ),
+ self );
+}
+
+//
+// Property getter implementation, mapping a property id to a method.
+//
+void foobar_application_service_get_property(
+ GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec )
+{
+ FoobarApplicationService* self = (FoobarApplicationService*)object;
+
+ switch ( prop_id )
+ {
+ case PROP_ITEMS:
+ g_value_set_object( value, foobar_application_service_get_items( self ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Instance cleanup for the application service.
+//
+void foobar_application_service_finalize( GObject* object )
+{
+ FoobarApplicationService* self = (FoobarApplicationService*)object;
+
+ for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( self->items ) ); ++i )
+ {
+ FoobarApplicationItem* item = g_list_model_get_item( G_LIST_MODEL( self->items ), i );
+ item->service = NULL;
+ g_object_notify_by_pspec( G_OBJECT( item ), app_props[APP_PROP_FREQUENCY] );
+ }
+ g_clear_signal_handler( &self->changed_handler_id, self->monitor );
+ g_clear_object( &self->sorted_items );
+ g_clear_object( &self->items );
+ g_clear_object( &self->monitor );
+ g_clear_pointer( &self->frequencies, g_hash_table_unref );
+ g_clear_pointer( &self->cache_path, g_free );
+
+ g_mutex_clear( &self->frequencies_mutex );
+ g_mutex_clear( &self->write_cache_mutex );
+
+ G_OBJECT_CLASS( foobar_application_service_parent_class )->finalize( object );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Public API
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Create a new application service instance.
+//
+FoobarApplicationService* foobar_application_service_new( void )
+{
+ return g_object_new( FOOBAR_TYPE_APPLICATION_SERVICE, NULL );
+}
+
+//
+// Get a sorted list of all available applications.
+//
+GListModel* foobar_application_service_get_items( FoobarApplicationService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_APPLICATION_SERVICE( self ), NULL );
+
+ return G_LIST_MODEL( self->sorted_items );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Signal Handlers
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Called by GLib when the list of applications has changed.
+//
+void foobar_application_service_handle_changed(
+ GAppInfoMonitor* monitor,
+ gpointer userdata )
+{
+ (void)monitor;
+ FoobarApplicationService* self = (FoobarApplicationService*)userdata;
+
+ foobar_application_service_update( self );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Helper Methods
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Reload the list of applications.
+//
+void foobar_application_service_update( FoobarApplicationService* self )
+{
+ g_list_store_remove_all( self->items );
+
+ g_autolist( GAppInfo ) info = g_app_info_get_all( );
+ for ( GList* it = info; it; it = it->next )
+ {
+ if ( g_app_info_should_show( it->data ) )
+ {
+ g_autoptr( FoobarApplicationItem ) item = foobar_application_item_new( self );
+ foobar_application_item_set_info( item, it->data );
+ g_list_store_append( self->items, item );
+ }
+ }
+}
+
+//
+// Synchronously read the frequency cache file at self->cache_path, populating self->frequencies.
+//
+void foobar_application_service_read_cache( FoobarApplicationService* self )
+{
+ g_autoptr( GError ) error = NULL;
+
+ if ( self->cache_path && g_file_test( self->cache_path, G_FILE_TEST_EXISTS ) )
+ {
+ g_autoptr( JsonParser ) parser = json_parser_new( );
+ if ( !json_parser_load_from_mapped_file( parser, self->cache_path, &error ) )
+ {
+ g_warning( "Unable to read cached application frequencies: %s", error->message );
+ return;
+ }
+
+ JsonNode* root_node = json_parser_get_root( parser );
+ JsonObject* root_object = json_node_get_object( root_node );
+ json_object_foreach_member( root_object, foobar_application_service_read_cache_foreach_cb, self );
+ }
+}
+
+//
+// Callback used by foobar_application_service_read_cache to read a single entry.
+//
+void foobar_application_service_read_cache_foreach_cb(
+ JsonObject* object,
+ gchar const* member_name,
+ JsonNode* member_node,
+ gpointer userdata )
+{
+ (void)object;
+ FoobarApplicationService* self = (FoobarApplicationService*)userdata;
+
+ gsize frequency = json_node_get_int( member_node );
+ g_hash_table_insert( self->frequencies, g_strdup( member_name ), GSIZE_TO_POINTER( frequency ) );
+}
+
+//
+// Asynchronously start writing the frequency cache file at self->cache_path.
+//
+void foobar_application_service_write_cache( FoobarApplicationService* self )
+{
+ foobar_application_service_write_cache_async( self, NULL, foobar_application_service_write_cache_cb, NULL );
+}
+
+//
+// Callback invoked when the frequency cache was written successfully or failed.
+//
+void foobar_application_service_write_cache_cb(
+ GObject* object,
+ GAsyncResult* result,
+ gpointer userdata )
+{
+ (void)userdata;
+ FoobarApplicationService* self = (FoobarApplicationService*)object;
+
+ g_autoptr( GError ) error = NULL;
+ if ( !foobar_application_service_write_cache_finish( self, result, &error ) )
+ {
+ g_warning( "Unable to write cached application frequencies: %s", error->message );
+ }
+}
+
+//
+// Asynchronously write the frequency cache file at self->cache_path.
+//
+void foobar_application_service_write_cache_async(
+ FoobarApplicationService* self,
+ GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer userdata )
+{
+ g_autoptr( GTask ) task = g_task_new( self, cancellable, callback, userdata );
+ g_task_set_name( task, "write-application-cache" );
+ g_task_run_in_thread( task, foobar_application_service_write_cache_thread );
+}
+
+//
+// Get the asynchronous result for writing the frequency cache file, returning TRUE on success, or FALSE on error.
+//
+gboolean foobar_application_service_write_cache_finish(
+ FoobarApplicationService* self,
+ GAsyncResult* result,
+ GError** error )
+{
+ (void)self;
+
+ return g_task_propagate_boolean( G_TASK( result ), error );
+}
+
+//
+// Task implementation for foobar_application_service_write_cache_async, invoked on a background thread.
+//
+void foobar_application_service_write_cache_thread(
+ GTask* task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable* cancellable )
+{
+ (void)cancellable;
+ (void)task_data;
+ FoobarApplicationService* self = (FoobarApplicationService*)source_object;
+
+ if ( !self->cache_path )
+ {
+ g_task_return_boolean( task, TRUE );
+ return;
+ }
+
+ g_autoptr( GError ) error = NULL;
+
+ g_autoptr( JsonBuilder ) builder = json_builder_new( );
+ json_builder_begin_object( builder );
+
+ g_mutex_lock( &self->frequencies_mutex );
+ g_hash_table_foreach( self->frequencies, foobar_application_service_write_cache_foreach_cb, builder );
+ g_mutex_unlock( &self->frequencies_mutex );
+
+ json_builder_end_object( builder );
+
+ g_autoptr( JsonNode ) root_node = json_builder_get_root( builder );
+ g_autoptr( JsonGenerator ) generator = json_generator_new( );
+ json_generator_set_root( generator, root_node );
+
+ g_mutex_lock( &self->write_cache_mutex );
+ gboolean success = json_generator_to_file( generator, self->cache_path, &error );
+ g_mutex_unlock( &self->write_cache_mutex );
+ if ( !success )
+ {
+ g_task_return_error( task, g_steal_pointer( &error ) );
+ return;
+ }
+
+ g_task_return_boolean( task, TRUE );
+}
+
+//
+// Callback used by foobar_application_service_write_cache_thread to build a single entry.
+//
+void foobar_application_service_write_cache_foreach_cb(
+ gpointer key,
+ gpointer value,
+ gpointer userdata )
+{
+ JsonBuilder* builder = (JsonBuilder*)userdata;
+
+ json_builder_set_member_name( builder, key );
+ json_builder_add_int_value( builder, GPOINTER_TO_SIZE( value ) );
+}
+
+//
+// Sorting callback for application items. Items are sorted based on:
+// 1. frequency (descending)
+// 2. name (ascending)
+// 3. id (ascending)
+//
+gint foobar_application_service_sort_func(
+ gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata )
+{
+ (void)userdata;
+
+ FoobarApplicationItem* app_a = (FoobarApplicationItem*)item_a;
+ FoobarApplicationItem* app_b = (FoobarApplicationItem*)item_b;
+
+ gint64 freq_a = foobar_application_item_get_frequency( app_a );
+ gint64 freq_b = foobar_application_item_get_frequency( app_b );
+ if ( freq_a > freq_b ) { return -1; }
+ if ( freq_a < freq_b ) { return 1; }
+
+ gchar const* name_a = foobar_application_item_get_name( app_a );
+ gchar const* name_b = foobar_application_item_get_name( app_b );
+ gint name_res = g_strcmp0( name_a, name_b );
+ if ( name_res ) { return name_res; }
+
+ gchar const* id_a = foobar_application_item_get_id( app_a );
+ gchar const* id_b = foobar_application_item_get_id( app_b );
+ return g_strcmp0( id_a, id_b );
+} \ No newline at end of file