1 /*
2 * application-database: A singelton managing desktop files and menus
3 * for installed applications
4 *
5 * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 *
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <glib/gi18n-lib.h>
30
31 #include <libxfdashboard/application-database.h>
32 #include <libxfdashboard/desktop-app-info.h>
33 #include <libxfdashboard/compat.h>
34 #include <libxfdashboard/debug.h>
35
36
37 /* Define this class in GObject system */
38 struct _XfdashboardApplicationDatabasePrivate
39 {
40 /* Properties related */
41 gboolean isLoaded;
42
43 /* Instance related */
44 GList *searchPaths;
45
46 GarconMenu *appsMenu;
47 guint appsMenuReloadRequiredID;
48
49 GHashTable *applications;
50 GList *appDirMonitors;
51 };
52
53 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardApplicationDatabase,
54 xfdashboard_application_database,
55 G_TYPE_OBJECT)
56
57 /* Properties */
58 enum
59 {
60 PROP_0,
61
62 PROP_IS_LOADED,
63
64 PROP_LAST
65 };
66
67 static GParamSpec* XfdashboardApplicationDatabaseProperties[PROP_LAST]={ 0, };
68
69 /* Signals */
70 enum
71 {
72 SIGNAL_MENU_RELOAD_REQUIRED,
73
74 SIGNAL_APPLICATION_ADDED,
75 SIGNAL_APPLICATION_REMOVED,
76
77 SIGNAL_LAST
78 };
79
80 static guint XfdashboardApplicationDatabaseSignals[SIGNAL_LAST]={ 0, };
81
82 /* IMPLEMENTATION: Private variables and methods */
83
84 /* Single instance of application database */
85 static XfdashboardApplicationDatabase* _xfdashboard_application_database=NULL;
86
87 typedef struct _XfdashboardApplicationDatabaseFileMonitorData XfdashboardApplicationDatabaseFileMonitorData;
88 struct _XfdashboardApplicationDatabaseFileMonitorData
89 {
90 GFile *path;
91 GFileMonitor *monitor;
92 guint changedID;
93 };
94
95 /* Forward declarations */
96 static gboolean _xfdashboard_application_database_load_application_menu(XfdashboardApplicationDatabase *self, GError **outError);
97
98 /* Callback function for hash table iterator to add each value to a list of type GList */
_xfdashboard_application_database_add_hashtable_item_to_list(gpointer inKey,gpointer inValue,gpointer inUserData)99 static void _xfdashboard_application_database_add_hashtable_item_to_list(gpointer inKey,
100 gpointer inValue,
101 gpointer inUserData)
102 {
103 GList **applicationsList;
104
105 g_return_if_fail(XFDASHBOARD_DESKTOP_APP_INFO(inValue));
106
107 applicationsList=(GList**)inUserData;
108
109 /* Add a reference of value (a XfdashboardDesktopAppInfo) to list */
110 *applicationsList=g_list_prepend(*applicationsList, g_object_ref(G_OBJECT(inValue)));
111 }
112
113 /* Application menu needs to be reloaded */
_xfdashboard_application_database_on_application_menu_reload_required(XfdashboardApplicationDatabase * self,gpointer inUserData)114 static void _xfdashboard_application_database_on_application_menu_reload_required(XfdashboardApplicationDatabase *self,
115 gpointer inUserData)
116 {
117 GarconMenu *menu;
118 GError *error;
119
120 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self));
121 g_return_if_fail(GARCON_IS_MENU(inUserData));
122
123 menu=GARCON_MENU(inUserData);
124 error=NULL;
125
126 /* Reload application menu. This also emits all necessary signals. */
127 XFDASHBOARD_DEBUG(self, APPLICATIONS,
128 "Menu '%s' changed and requires a reload of application menu",
129 garcon_menu_element_get_name(GARCON_MENU_ELEMENT(menu)));
130 if(!_xfdashboard_application_database_load_application_menu(self, &error))
131 {
132 g_critical("Could not reload application menu: %s",
133 error ? error->message : "Unknown error");
134
135 /* Release allocated resources */
136 if(error) g_error_free(error);
137 }
138 }
139
140 /* Create a new data structure for file monitor */
_xfdashboard_application_database_monitor_data_new(GFile * inPath)141 static XfdashboardApplicationDatabaseFileMonitorData* _xfdashboard_application_database_monitor_data_new(GFile *inPath)
142 {
143 XfdashboardApplicationDatabaseFileMonitorData *monitorData;
144
145 g_return_val_if_fail(G_IS_FILE(inPath), NULL);
146
147 /* Allocate memory for file monitor data structure */
148 monitorData=g_new0(XfdashboardApplicationDatabaseFileMonitorData, 1);
149 if(!monitorData) return(NULL);
150
151 monitorData->path=g_object_ref(inPath);
152
153 /* Return newly create and initialized file monitor data structure */
154 return(monitorData);
155 }
156
157 /* Free a file monitor data structure */
_xfdashboard_application_database_monitor_data_free(XfdashboardApplicationDatabaseFileMonitorData * inData)158 static void _xfdashboard_application_database_monitor_data_free(XfdashboardApplicationDatabaseFileMonitorData *inData)
159 {
160 g_return_if_fail(inData);
161
162 /* Release each data in file monitor structure */
163 if(inData->path) g_object_unref(inData->path);
164 if(inData->monitor)
165 {
166 if(inData->changedID)
167 {
168 g_signal_handler_disconnect(inData->monitor, inData->changedID);
169 }
170
171 g_object_unref(inData->monitor);
172 }
173
174 /* Release allocated memory */
175 g_free(inData);
176 }
177
178 /* Find file monitor data structure */
_xfdashboard_application_database_monitor_data_find_by_monitor(XfdashboardApplicationDatabase * self,GFileMonitor * inMonitor)179 static XfdashboardApplicationDatabaseFileMonitorData* _xfdashboard_application_database_monitor_data_find_by_monitor(XfdashboardApplicationDatabase *self,
180 GFileMonitor *inMonitor)
181 {
182 XfdashboardApplicationDatabasePrivate *priv;
183 GList *iter;
184 XfdashboardApplicationDatabaseFileMonitorData *iterData;
185
186 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
187 g_return_val_if_fail(G_IS_FILE_MONITOR(inMonitor), NULL);
188
189 priv=self->priv;
190
191 /* Iterate through list of registered file monitor data structure and
192 * lookup the one matching requested file monitor.
193 */
194 for(iter=priv->appDirMonitors; iter; iter=g_list_next(iter))
195 {
196 iterData=(XfdashboardApplicationDatabaseFileMonitorData*)iter->data;
197 if(iterData &&
198 iterData->monitor &&
199 iterData->monitor==inMonitor)
200 {
201 return(iterData);
202 }
203 }
204
205 /* If we get here we could not find a file monitor data structure
206 * matching the requested file monitor so return NULL.
207 */
208 return(NULL);
209 }
210
_xfdashboard_application_database_monitor_data_find_by_file(XfdashboardApplicationDatabase * self,GFile * inPath)211 static XfdashboardApplicationDatabaseFileMonitorData* _xfdashboard_application_database_monitor_data_find_by_file(XfdashboardApplicationDatabase *self,
212 GFile *inPath)
213 {
214 XfdashboardApplicationDatabasePrivate *priv;
215 GList *iter;
216 XfdashboardApplicationDatabaseFileMonitorData *iterData;
217
218 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
219 g_return_val_if_fail(G_IS_FILE(inPath), NULL);
220
221 priv=self->priv;
222
223 /* Iterate through list of registered file monitor data structure and
224 * lookup the one matching requested path.
225 */
226 for(iter=priv->appDirMonitors; iter; iter=g_list_next(iter))
227 {
228 iterData=(XfdashboardApplicationDatabaseFileMonitorData*)iter->data;
229 if(iterData &&
230 iterData->path &&
231 g_file_equal(iterData->path, inPath))
232 {
233 return(iterData);
234 }
235 }
236
237 /* If we get here we could not find a file monitor data structure
238 * matching the requested path so return NULL.
239 */
240 return(NULL);
241 }
242
243 /* A directory containing desktop files has changed */
_xfdashboard_application_database_on_file_monitor_changed(XfdashboardApplicationDatabase * self,GFile * inFile,GFile * inOtherFile,GFileMonitorEvent inEventType,gpointer inUserData)244 static void _xfdashboard_application_database_on_file_monitor_changed(XfdashboardApplicationDatabase *self,
245 GFile *inFile,
246 GFile *inOtherFile,
247 GFileMonitorEvent inEventType,
248 gpointer inUserData)
249 {
250 XfdashboardApplicationDatabasePrivate *priv;
251 GFileMonitor *monitor;
252 XfdashboardApplicationDatabaseFileMonitorData *monitorData;
253 gchar *filePath;
254
255 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self));
256 g_return_if_fail(G_IS_FILE_MONITOR(inUserData));
257
258 priv=self->priv;
259 monitor=G_FILE_MONITOR(inUserData);
260
261 /* Find file monitor data structure of file monitor emitting this signal */
262 monitorData=_xfdashboard_application_database_monitor_data_find_by_monitor(self, monitor);
263 if(!monitorData)
264 {
265 g_warning("Received event from unknown file monitor");
266 return;
267 }
268
269 /* Get file path */
270 filePath=g_file_get_path(inFile);
271
272 /* Check if a new directory was created */
273 if(inEventType==G_FILE_MONITOR_EVENT_CREATED &&
274 g_file_query_file_type(inFile, G_FILE_QUERY_INFO_NONE, NULL)==G_FILE_TYPE_DIRECTORY)
275 {
276 XfdashboardApplicationDatabaseFileMonitorData *fileMonitorData;
277 GError *error;
278
279 error=NULL;
280
281 XFDASHBOARD_DEBUG(self, APPLICATIONS,
282 "Directory '%s' in application search paths was created",
283 filePath);
284
285 /* A new directory was created so create a file monitor for it,
286 * connect signals and add to list of registered file monitors.
287 */
288 fileMonitorData=_xfdashboard_application_database_monitor_data_new(inFile);
289 if(!fileMonitorData)
290 {
291 g_warning("Unable to create file monitor for newly created directory '%s'", filePath);
292
293 /* Release allocated resources */
294 if(filePath) g_free(filePath);
295
296 return;
297 }
298
299 fileMonitorData->monitor=g_file_monitor(inFile, G_FILE_MONITOR_NONE, NULL, &error);
300 if(!fileMonitorData->monitor)
301 {
302 g_warning("Unable to create file monitor for '%s': %s",
303 filePath,
304 error ? error->message : "Unknown error");
305
306 /* Release allocated resources */
307 if(fileMonitorData) _xfdashboard_application_database_monitor_data_free(fileMonitorData);
308 if(filePath) g_free(filePath);
309
310 return;
311 }
312
313 fileMonitorData->changedID=g_signal_connect_swapped(fileMonitorData->monitor,
314 "changed",
315 G_CALLBACK(_xfdashboard_application_database_on_file_monitor_changed),
316 self);
317
318 priv->appDirMonitors=g_list_prepend(priv->appDirMonitors, fileMonitorData);
319 }
320
321 /* Check if a new desktop file was created */
322 if(inEventType==G_FILE_MONITOR_EVENT_CREATED &&
323 g_file_query_file_type(inFile, G_FILE_QUERY_INFO_NONE, NULL)==G_FILE_TYPE_REGULAR)
324 {
325 /* Handle only desktop files but that can only be done
326 * if a hash table exists.
327 */
328 if(g_str_has_suffix(filePath, ".desktop") &&
329 priv->applications)
330 {
331 gchar *desktopID;
332
333 XFDASHBOARD_DEBUG(self, APPLICATIONS,
334 "Desktop file '%s' in application search paths was created",
335 filePath);
336
337 /* Get desktop ID to check */
338 desktopID=xfdashboard_application_database_get_desktop_id_from_file(inFile);
339
340 /* If we found the desktop ID for the desktop file created check
341 * if it would replace an existing desktop ID in hash table or
342 * if it is a completely new one.
343 */
344 if(desktopID)
345 {
346 XfdashboardDesktopAppInfo *currentDesktopAppInfo;
347
348 /* Get current desktop app info */
349 if(g_hash_table_lookup_extended(priv->applications, desktopID, NULL, (gpointer*)¤tDesktopAppInfo))
350 {
351 gchar *newDesktopFilename;
352 GFile *newDesktopFile;
353
354 /* Check if newly created desktop file would replace current one.
355 * Now as the desktop file is created and exists we can call the
356 * function which searches the first matching desktop file for
357 * this desktop ID. If it is the same as the file created then
358 * it replaces the current desktop app info. Otherwise ignore this
359 * newly created file because id does not affect the hash table.
360 */
361 newDesktopFilename=xfdashboard_application_database_get_file_from_desktop_id(desktopID);
362 newDesktopFile=g_file_new_for_path(newDesktopFilename);
363 if(g_file_equal(newDesktopFile, inFile))
364 {
365 /* Newly created desktop file replaces current one. Set
366 * new file at desktop app info to emit 'changed' signal.
367 * No need to change anything at hash table.
368 */
369 g_object_set(currentDesktopAppInfo, "file", newDesktopFile, NULL);
370
371 XFDASHBOARD_DEBUG(self, APPLICATIONS,
372 "Replacing known desktop ID '%s' at desktop file '%s' with new desktop file '%s'",
373 desktopID,
374 filePath,
375 newDesktopFilename);
376 }
377 else
378 {
379 XFDASHBOARD_DEBUG(self, APPLICATIONS,
380 "Ignoring new desktop file at '%s' for known desktop ID '%s'",
381 filePath,
382 desktopID);
383 }
384
385 /* Release allocated resources */
386 if(newDesktopFile) g_object_unref(newDesktopFile);
387 if(newDesktopFilename) g_free(newDesktopFilename);
388 }
389 else
390 {
391 XfdashboardDesktopAppInfo *newDesktopAppInfo;
392
393 /* There is no desktop app info for this desktop ID in
394 * hash table. So it is completely new. Create a new
395 * desktop app info, add it to hash table and emit the
396 * signal for application added.
397 */
398 newDesktopAppInfo=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
399 "desktop-id", desktopID,
400 "file", inFile,
401 NULL));
402
403 if(xfdashboard_desktop_app_info_is_valid(newDesktopAppInfo))
404 {
405 /* Add desktop app info to hash table because creation
406 * was successful.
407 */
408 g_hash_table_insert(priv->applications, g_strdup(desktopID), newDesktopAppInfo);
409
410 /* Emit signal that an application has been removed from hash table */
411 g_signal_emit(self, XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_ADDED], 0, newDesktopAppInfo);
412
413 XFDASHBOARD_DEBUG(self, APPLICATIONS,
414 "Adding new desktop ID '%s' for new desktop file at '%s'",
415 desktopID,
416 filePath);
417 }
418 else
419 {
420 XFDASHBOARD_DEBUG(self, APPLICATIONS,
421 "Adding new desktop ID '%s' for new desktop file '%s' failed",
422 desktopID,
423 filePath);
424
425 /* Release allocated resources */
426 g_object_unref(newDesktopAppInfo);
427 }
428 }
429 }
430 }
431 }
432
433 /* Check if a file was modified */
434 if(inEventType==G_FILE_MONITOR_EVENT_CHANGED &&
435 g_file_query_file_type(inFile, G_FILE_QUERY_INFO_NONE, NULL)==G_FILE_TYPE_REGULAR)
436 {
437 /* Handle only desktop files but that can only be done
438 * if a hash table exists.
439 */
440 if(g_str_has_suffix(filePath, ".desktop") &&
441 priv->applications)
442 {
443 gchar *desktopID;
444 XfdashboardDesktopAppInfo *appInfo;
445
446 XFDASHBOARD_DEBUG(self, APPLICATIONS,
447 "Desktop file '%s' was modified",
448 filePath);
449
450 /* Get desktop ID to check */
451 desktopID=xfdashboard_application_database_get_desktop_id_from_file(inFile);
452
453 /* Get current desktop app info in hash table for desktop ID */
454 appInfo=NULL;
455 if(desktopID)
456 {
457 /* We have to check if the modified file is exactly the same
458 * as the file used by current desktop app info for desktop ID.
459 * Because a desktop file could be created which overrides
460 * this desktop ID so the changes are not important anymore
461 * and we do not need to emit 'changed' signal.
462 * If a desktop file was added or removed the other handlers
463 * have or will emit the needed signal to keep them up-to-date.
464 */
465 if(g_hash_table_lookup_extended(priv->applications, desktopID, NULL, (gpointer*)&appInfo))
466 {
467 GFile *appInfoFile;
468 gchar *appInfoFilename;
469
470 /* Get file of desktop app info found */
471 appInfoFile=xfdashboard_desktop_app_info_get_file(appInfo);
472 appInfoFilename=g_file_get_path(appInfoFile);
473
474 /* If file of desktop app info is the same as the one modified
475 * then reload desktop app info.
476 */
477 if(g_file_equal(appInfoFile, inFile))
478 {
479 /* Reload desktop app info but remove desktop app info
480 * if reload failed or is invalid after reload.
481 */
482 if(!xfdashboard_desktop_app_info_reload(appInfo) ||
483 !xfdashboard_desktop_app_info_is_valid(appInfo))
484 {
485 /* Take a extra reference of desktop app info which is
486 * going to be removed from hash table because when removing
487 * desktop ID from hash table could release the last reference
488 * to desktop app info and destroy it. So we are not able
489 * to send object when emitting signal 'application-removed'.
490 */
491 g_object_ref(appInfo);
492
493 /* Remove desktop app info from hash table because either
494 * reload failed or it is invalid now.
495 */
496 g_hash_table_remove(priv->applications, desktopID);
497
498 XFDASHBOARD_DEBUG(self, APPLICATIONS,
499 "Removed desktop ID '%s' with origin desktop file '%s' with modified desktop file '%s' because reload failed or it is invalid",
500 desktopID,
501 filePath,
502 appInfoFilename);
503
504 /* Emit signal that an application has been removed */
505 g_signal_emit(self, XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_REMOVED], 0, appInfo);
506
507 /* Release extra reference we took on desktop app info and
508 * let object maybe destroyed now because it was the last one.
509 */
510 g_object_unref(appInfo);
511 }
512 else
513 {
514 XFDASHBOARD_DEBUG(self, APPLICATIONS,
515 "Reloaded desktop ID '%s' with origin desktop file '%s' with modified desktop file '%s'",
516 desktopID,
517 filePath,
518 appInfoFilename);
519 }
520 }
521
522 /* Release allocated resources */
523 if(appInfoFilename) g_free(appInfoFilename);
524 }
525 else
526 {
527 XfdashboardDesktopAppInfo *newDesktopAppInfo;
528
529 /* We have a valid desktop ID but no app info in hash table
530 * so try to create a desktop app info for modified file
531 * and if it is valid add it to hash table and emit the
532 * signal for application added.
533 */
534 newDesktopAppInfo=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
535 "desktop-id", desktopID,
536 "file", inFile,
537 NULL));
538 if(xfdashboard_desktop_app_info_is_valid(newDesktopAppInfo))
539 {
540 g_hash_table_insert(priv->applications, g_strdup(desktopID), newDesktopAppInfo);
541
542 /* Emit signal that an application has been removed from hash table */
543 g_signal_emit(self, XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_ADDED], 0, newDesktopAppInfo);
544
545 XFDASHBOARD_DEBUG(self, APPLICATIONS,
546 "Adding new desktop ID '%s' for modified desktop file at '%s'",
547 desktopID,
548 filePath);
549 }
550 else
551 {
552 XFDASHBOARD_DEBUG(self, APPLICATIONS,
553 "Got valid desktop id '%s' but invalid desktop app info for file '%s'",
554 desktopID,
555 filePath);
556 g_object_unref(newDesktopAppInfo);
557 }
558 }
559 }
560
561 /* Release allocated resources */
562 if(desktopID) g_free(desktopID);
563 }
564 }
565
566 /* Check if a file or directory was removed.
567 * The problem here is that we cannot determine if the removed file
568 * is really a file or a directory because we cannot query the file type
569 * because it is removed at filesystem. So assume it was both:
570 * a file and a directory.
571 */
572 if(inEventType==G_FILE_MONITOR_EVENT_DELETED)
573 {
574 XfdashboardApplicationDatabaseFileMonitorData *fileMonitorData;
575
576 /* Assume it was a directory so remove file monitor and free
577 * it's file monitor data structure.
578 */
579 fileMonitorData=_xfdashboard_application_database_monitor_data_find_by_file(self, inFile);
580 if(fileMonitorData)
581 {
582 XFDASHBOARD_DEBUG(self, APPLICATIONS,
583 "Removing file monitor for deleted directory '%s'",
584 filePath);
585
586 priv->appDirMonitors=g_list_remove_all(priv->appDirMonitors, fileMonitorData);
587 _xfdashboard_application_database_monitor_data_free(fileMonitorData);
588 fileMonitorData=NULL;
589 }
590
591 /* Assume it was a file so check if it was a desktop file, then check
592 * if any other desktop file in the other paths in list of search paths
593 * matches the removed desktop ID. If so replace desktop app info and
594 * emit signal. Otherwise remove desktop app info from hash table and
595 * emit signal. Check can only be performed if a hash table exists.
596 */
597 if(g_str_has_suffix(filePath, ".desktop") &&
598 priv->applications)
599 {
600 gchar *desktopID;
601
602 XFDASHBOARD_DEBUG(self, APPLICATIONS,
603 "Desktop file '%s' was removed",
604 filePath);
605
606 /* Get desktop ID to check */
607 desktopID=xfdashboard_application_database_get_desktop_id_from_file(inFile);
608
609 /* If we found the desktop ID for the desktop file removed, check if
610 * another desktop file in search paths could replace desktop ID or
611 * if it was removed completely.
612 */
613 if(desktopID)
614 {
615 XfdashboardDesktopAppInfo *currentDesktopAppInfo;
616
617 /* Get current desktop app info */
618 if(g_hash_table_lookup_extended(priv->applications, desktopID, NULL, (gpointer*)¤tDesktopAppInfo))
619 {
620 /* Check if there is another desktop file which could
621 * replace the desktop ID.
622 */
623 gchar *newDesktopFilename;
624
625 newDesktopFilename=xfdashboard_application_database_get_file_from_desktop_id(desktopID);
626 if(newDesktopFilename)
627 {
628 GFile *newDesktopFile;
629
630 XFDASHBOARD_DEBUG(self, APPLICATIONS,
631 "Replacing known desktop ID '%s' at desktop file '%s' with new desktop file '%s'",
632 desktopID,
633 filePath,
634 newDesktopFilename);
635
636 /* There is another desktop file which could replace the
637 * desktop ID. Set new file at desktop app info which causes
638 * the 'changed' signal to be emitted. No need to change hash table.
639 */
640 newDesktopFile=g_file_new_for_path(newDesktopFilename);
641 g_object_set(currentDesktopAppInfo, "file", newDesktopFile, NULL);
642 g_object_unref(newDesktopFile);
643
644 /* Release allocated resources */
645 g_free(newDesktopFilename);
646 }
647 else
648 {
649 /* Take a reference on current desktop app info to be able
650 * to emit a 'changed' signal and 'application-removed' signal
651 * when setting the new file at it after it has been removed
652 * from hash table. Otherwise the last reference at this object
653 * could be release which causes the object to be destroyed.
654 */
655 g_object_ref(currentDesktopAppInfo);
656
657 /* There is no other desktop file for this desktop ID.
658 * Remove desktop app info and desktop ID from hash table.
659 */
660 g_hash_table_remove(priv->applications, desktopID);
661
662 XFDASHBOARD_DEBUG(self, APPLICATIONS,
663 "Removing desktop ID '%s'",
664 desktopID);
665
666 /* Emit signal that an application has been removed */
667 g_signal_emit(self, XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_REMOVED], 0, currentDesktopAppInfo);
668
669 /* Set a NULL file at desktop app info which causes
670 * the 'changed' signal to be emitted.
671 */
672 g_object_set(currentDesktopAppInfo, "file", NULL, NULL);
673
674 /* Release the extra reference we took */
675 g_object_unref(currentDesktopAppInfo);
676 }
677 }
678
679 /* Release allocated resources */
680 g_free(desktopID);
681 }
682 }
683 }
684
685 /* Release allocated resources */
686 if(filePath) g_free(filePath);
687 }
688
689 /* Load installed and user-overidden application desktop files */
_xfdashboard_application_database_load_applications_recursive(XfdashboardApplicationDatabase * self,GFile * inTopLevelPath,GFile * inCurrentPath,GHashTable ** ioDesktopAppInfos,GList ** ioFileMonitors,GError ** outError)690 static gboolean _xfdashboard_application_database_load_applications_recursive(XfdashboardApplicationDatabase *self,
691 GFile *inTopLevelPath,
692 GFile *inCurrentPath,
693 GHashTable **ioDesktopAppInfos,
694 GList **ioFileMonitors,
695 GError **outError)
696 {
697 XfdashboardApplicationDatabasePrivate *priv G_GNUC_UNUSED;
698 gchar *topLevelPath;
699 gchar *path;
700 GFileEnumerator *enumerator;
701 GFileInfo *info;
702 XfdashboardApplicationDatabaseFileMonitorData *monitorData;
703 GError *error;
704
705 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), FALSE);
706 g_return_val_if_fail(G_IS_FILE(inTopLevelPath), FALSE);
707 g_return_val_if_fail(G_IS_FILE(inCurrentPath), FALSE);
708 g_return_val_if_fail(ioDesktopAppInfos && *ioDesktopAppInfos, FALSE);
709 g_return_val_if_fail(ioFileMonitors, FALSE);
710
711 priv=self->priv;
712 error=NULL;
713
714 /* Get path of current path to scan for desktop files */
715 path=g_file_get_path(inCurrentPath);
716 topLevelPath=g_file_get_path(inTopLevelPath);
717
718 XFDASHBOARD_DEBUG(self, APPLICATIONS,
719 "Scanning directory '%s' for search path '%s'",
720 path,
721 topLevelPath);
722
723 /* Create enumerator for current path to iterate through path and
724 * searching for desktop files.
725 */
726 enumerator=g_file_enumerate_children(inCurrentPath,
727 G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME,
728 G_FILE_QUERY_INFO_NONE,
729 NULL,
730 &error);
731 if(!enumerator)
732 {
733 /* Propagate error */
734 g_propagate_error(outError, error);
735
736 /* Release allocated resources */
737 if(path) g_free(path);
738 if(topLevelPath) g_free(topLevelPath);
739
740 return(FALSE);
741 }
742
743 /* Iterate through files in current path recursively and for each
744 * desktop file found create a desktop app info object but only
745 * if it is the first occurence of that desktop ID in hash table.
746 */
747 while((info=g_file_enumerator_next_file(enumerator, NULL, &error)))
748 {
749 /* If current file is a directory call this function recursively
750 * with absolute path to directory.
751 */
752 if(g_file_info_get_file_type(info)==G_FILE_TYPE_DIRECTORY)
753 {
754 GFile *childPath;
755 gboolean childSuccess;
756
757 XFDASHBOARD_DEBUG(self, APPLICATIONS,
758 "Suspend scanning directory '%s' at search path '%s' for sub-directory '%s'",
759 path,
760 topLevelPath,
761 g_file_info_get_name(info));
762
763 childPath=g_file_resolve_relative_path(inCurrentPath,
764 g_file_info_get_name(info));
765 if(!childPath)
766 {
767 XFDASHBOARD_DEBUG(self, APPLICATIONS,
768 "Unable to build path to search for desktop files for path='%s' and file='%s'",
769 path,
770 g_file_info_get_name(info));
771
772 /* Set error */
773 g_set_error(outError,
774 G_IO_ERROR,
775 G_IO_ERROR_FAILED,
776 "Unable to build path to '%s%s%s' to search for desktop files",
777 path,
778 G_DIR_SEPARATOR_S,
779 g_file_info_get_name(info));
780
781 /* Release allocated resources */
782 if(path) g_free(path);
783 if(topLevelPath) g_free(topLevelPath);
784 if(enumerator) g_object_unref(enumerator);
785
786 return(FALSE);
787 }
788
789 childSuccess=_xfdashboard_application_database_load_applications_recursive(self,
790 inTopLevelPath,
791 childPath,
792 ioDesktopAppInfos,
793 ioFileMonitors,
794 &error);
795 if(!childSuccess)
796 {
797 XFDASHBOARD_DEBUG(self, APPLICATIONS,
798 "Unable to iterate desktop files at %s%s%s",
799 path,
800 G_DIR_SEPARATOR_S,
801 g_file_info_get_name(info));
802
803 /* Propagate error */
804 g_propagate_error(outError, error);
805
806 /* Release allocated resources */
807 if(childPath) g_object_unref(childPath);
808 if(path) g_free(path);
809 if(topLevelPath) g_free(topLevelPath);
810 if(enumerator) g_object_unref(enumerator);
811
812 return(FALSE);
813 }
814
815 /* Release allocated resources */
816 if(childPath) g_object_unref(childPath);
817
818 XFDASHBOARD_DEBUG(self, APPLICATIONS,
819 "Resume scanning directory '%s' at search path '%s'",
820 path,
821 topLevelPath);
822 }
823
824 /* If current file is a regular file and if it is a desktop file
825 * then create desktop app info object if it is the first occurence
826 * of this desktop ID in hash table.
827 */
828 if(g_file_info_get_file_type(info)==G_FILE_TYPE_REGULAR &&
829 g_str_has_suffix(g_file_info_get_name(info), ".desktop"))
830 {
831 const gchar *childName;
832 GFile *childFile;
833 gchar *desktopID;
834
835 /* Get file of current enumerated file */
836 childName=g_file_info_get_name(info);
837 childFile=g_file_get_child(g_file_enumerator_get_container(enumerator), childName);
838
839 /* Determine desktop ID for file */
840 desktopID=g_file_get_relative_path(inTopLevelPath, childFile);
841 if(desktopID)
842 {
843 gchar *iter;
844
845 iter=desktopID;
846 while(*iter)
847 {
848 /* Replace directory sepearator with dash if needed */
849 if(*iter==G_DIR_SEPARATOR) *iter='-';
850
851 /* Continue with next character in desktop ID */
852 iter++;
853 }
854 }
855 else
856 {
857 g_warning("Could not determine desktop ID for '%s'",
858 g_file_info_get_name(info));
859 }
860
861 /* If no desktop app info with determined desktop ID exists
862 * then create it now.
863 */
864 if(desktopID &&
865 !g_hash_table_lookup_extended(*ioDesktopAppInfos,
866 desktopID,
867 NULL,
868 NULL))
869 {
870 XfdashboardDesktopAppInfo *appInfo;
871
872 appInfo=XFDASHBOARD_DESKTOP_APP_INFO(g_object_new(XFDASHBOARD_TYPE_DESKTOP_APP_INFO,
873 "desktop-id", desktopID,
874 "file", childFile,
875 NULL));
876 if(xfdashboard_desktop_app_info_is_valid(appInfo))
877 {
878 g_hash_table_insert(*ioDesktopAppInfos, g_strdup(desktopID), g_object_ref(appInfo));
879
880 XFDASHBOARD_DEBUG(self, APPLICATIONS,
881 "Found desktop file '%s%s%s' with desktop ID '%s' at search path '%s'",
882 path,
883 G_DIR_SEPARATOR_S,
884 childName,
885 desktopID,
886 topLevelPath);
887 }
888 else
889 {
890 /* Although desktop file for desktop ID is invalid, add
891 * it to the database to prevent that a valid desktop file
892 * for the same desktop ID will be found at path of lower
893 * prioritory which will then be store in the database as
894 * no entry exists. The first entry found - valid or invalid -
895 * has the highest priority. Later the caller has to ensure
896 * that all invalid desktop IDs in the database will be removed.
897 */
898 g_hash_table_insert(*ioDesktopAppInfos, g_strdup(desktopID), g_object_ref(appInfo));
899
900 XFDASHBOARD_DEBUG(self, APPLICATIONS,
901 "Adding and mark invalid desktop file '%s%s%s' with desktop ID '%s' at search path '%s'",
902 path,
903 G_DIR_SEPARATOR_S,
904 childName,
905 desktopID,
906 topLevelPath);
907 }
908
909 g_object_unref(appInfo);
910 }
911
912 /* Release allocated resources */
913 if(desktopID) g_free(desktopID);
914 if(childFile) g_object_unref(childFile);
915 }
916
917 g_object_unref(info);
918 }
919
920 if(error)
921 {
922 /* Propagate error */
923 g_propagate_error(outError, error);
924
925 /* Release allocated resources */
926 if(path) g_free(path);
927 if(topLevelPath) g_free(topLevelPath);
928 if(enumerator) g_object_unref(enumerator);
929
930 return(FALSE);
931 }
932
933 /* Iterating through given path was successful so create file monitor
934 * for this path.
935 */
936 monitorData=_xfdashboard_application_database_monitor_data_new(inCurrentPath);
937 if(!monitorData)
938 {
939 XFDASHBOARD_DEBUG(self, APPLICATIONS,
940 "Failed to create data object for file monitor for path '%s'",
941 path);
942
943 /* Set error */
944 g_set_error(outError,
945 G_IO_ERROR,
946 G_IO_ERROR_FAILED,
947 "Unable to create file monitor for '%s'",
948 path);
949
950 /* Release allocated resources */
951 if(path) g_free(path);
952 if(topLevelPath) g_free(topLevelPath);
953 if(enumerator) g_object_unref(enumerator);
954
955 return(FALSE);
956 }
957
958 monitorData->monitor=g_file_monitor(inCurrentPath, G_FILE_MONITOR_NONE, NULL, &error);
959 if(!monitorData->monitor && error)
960 {
961 #if defined(__unix__)
962 /* Workaround for FreeBSD with Glib bug (file/directory monitors cannot be created) */
963 g_warning("[workaround for FreeBSD] Cannot initialize file monitor for path '%s' but will not fail: %s",
964 path,
965 error ? error->message : "Unknown error");
966
967 /* Clear error as this error will not fail at FreeBSD */
968 g_clear_error(&error);
969 #else
970 XFDASHBOARD_DEBUG(self, APPLICATIONS,
971 "Failed to initialize file monitor for path '%s'",
972 path);
973
974 /* Propagate error */
975 g_propagate_error(outError, error);
976
977 /* Release allocated resources */
978 if(monitorData) _xfdashboard_application_database_monitor_data_free(monitorData);
979
980 if(path) g_free(path);
981 if(topLevelPath) g_free(topLevelPath);
982 if(enumerator) g_object_unref(enumerator);
983
984 return(FALSE);
985 #endif
986 }
987
988 /* If file monitor could be created, add it to list of file monitors ... */
989 if(monitorData && monitorData->monitor)
990 {
991 *ioFileMonitors=g_list_prepend(*ioFileMonitors, monitorData);
992
993 XFDASHBOARD_DEBUG(self, APPLICATIONS,
994 "Added file monitor for path '%s'",
995 path);
996 }
997 /* ... otherwise free file monitor data */
998 else
999 {
1000 if(monitorData) _xfdashboard_application_database_monitor_data_free(monitorData);
1001
1002 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1003 "Destroying file monitor for path '%s'",
1004 path);
1005 }
1006
1007 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1008 "Finished scanning directory '%s' for search path '%s'",
1009 path,
1010 topLevelPath);
1011
1012 /* Release allocated resources */
1013 if(path) g_free(path);
1014 if(topLevelPath) g_free(topLevelPath);
1015 if(enumerator) g_object_unref(enumerator);
1016
1017 /* Return success result */
1018 return(TRUE);
1019 }
1020
_xfdashboard_application_database_load_applications(XfdashboardApplicationDatabase * self,GError ** outError)1021 static gboolean _xfdashboard_application_database_load_applications(XfdashboardApplicationDatabase *self, GError **outError)
1022 {
1023 XfdashboardApplicationDatabasePrivate *priv;
1024 GHashTable *apps;
1025 GList *fileMonitors;
1026 GError *error;
1027 GList *iter;
1028 XfdashboardApplicationDatabaseFileMonitorData *monitorData;
1029
1030 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), FALSE);
1031 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1032
1033 priv=self->priv;
1034 error=NULL;
1035
1036 /* Iterate through enumerated files at each path in list of search paths
1037 * and add only the first occurence of each desktop ID. Also set up
1038 * file monitors to get notified if a desktop file changes, was removed
1039 * or a new one added.
1040 */
1041 fileMonitors=NULL;
1042 apps=g_hash_table_new_full(g_str_hash,
1043 g_str_equal,
1044 g_free,
1045 g_object_unref);
1046
1047 for(iter=priv->searchPaths; iter; iter=g_list_next(iter))
1048 {
1049 const gchar *path;
1050 GFile *directory;
1051
1052 /* Iterate through files in current search path recursively and
1053 * for each desktop file create a desktop app info object but only
1054 * if it is the first occurence of that desktop ID.
1055 */
1056 path=(const gchar*)iter->data;
1057 directory=g_file_new_for_path(path);
1058
1059 /* Only scan current search path if path exists and is a directory.
1060 * Otherwise the called function will fail and then this function
1061 * will fail also. But not all search path must exist so check.
1062 */
1063 if(g_file_query_file_type(directory, G_FILE_QUERY_INFO_NONE, NULL)==G_FILE_TYPE_DIRECTORY &&
1064 !_xfdashboard_application_database_load_applications_recursive(self, directory, directory, &apps, &fileMonitors, &error))
1065 {
1066 /* Propagate error */
1067 g_propagate_error(outError, error);
1068
1069 /* Release allocated resources */
1070 if(fileMonitors) g_list_free_full(fileMonitors, g_object_unref);
1071 if(apps) g_hash_table_unref(apps);
1072 if(directory) g_object_unref(directory);
1073
1074 return(FALSE);
1075 }
1076
1077 if(directory) g_object_unref(directory);
1078 }
1079
1080 /* Remove invalid desktop IDs from database */
1081 if(apps)
1082 {
1083 GHashTableIter appsIter;
1084 const gchar *desktopID;
1085 XfdashboardDesktopAppInfo *appInfo;
1086
1087 g_hash_table_iter_init(&appsIter, apps);
1088 while(g_hash_table_iter_next(&appsIter, (gpointer*)&desktopID, (gpointer*)&appInfo))
1089 {
1090 /* If value for key is invalid then remove this entry now */
1091 if(!xfdashboard_desktop_app_info_is_valid(appInfo))
1092 {
1093 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1094 "Removing invalid desktop ID '%s' from application database",
1095 desktopID);
1096
1097 /* Remove entry from hash table via the iterator */
1098 g_hash_table_iter_remove(&appsIter);
1099 }
1100 }
1101 }
1102
1103 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1104 "Loaded %u applications desktop files",
1105 g_hash_table_size(apps));
1106
1107 /* Release old list of installed applications and set new one */
1108 if(priv->applications)
1109 {
1110 g_hash_table_unref(priv->applications);
1111 priv->applications=NULL;
1112 }
1113
1114 priv->applications=apps;
1115
1116 /* Release old list of installed applications and set new one.
1117 * Now ee can also connect signals to all file monitors created.
1118 */
1119 if(priv->appDirMonitors)
1120 {
1121 for(iter=priv->appDirMonitors; iter; iter=g_list_next(iter))
1122 {
1123 _xfdashboard_application_database_monitor_data_free((XfdashboardApplicationDatabaseFileMonitorData*)iter->data);
1124 }
1125 g_list_free(priv->appDirMonitors);
1126 priv->appDirMonitors=NULL;
1127 }
1128
1129 priv->appDirMonitors=fileMonitors;
1130 for(iter=priv->appDirMonitors; iter; iter=g_list_next(iter))
1131 {
1132 monitorData=(XfdashboardApplicationDatabaseFileMonitorData*)iter->data;
1133 if(monitorData->monitor)
1134 {
1135 monitorData->changedID=g_signal_connect_swapped(monitorData->monitor,
1136 "changed",
1137 G_CALLBACK(_xfdashboard_application_database_on_file_monitor_changed),
1138 self);
1139 }
1140 }
1141
1142 /* Desktop files were loaded successfully */
1143 return(TRUE);
1144 }
1145
1146 /* Load menus and applications */
_xfdashboard_application_database_load_application_menu(XfdashboardApplicationDatabase * self,GError ** outError)1147 static gboolean _xfdashboard_application_database_load_application_menu(XfdashboardApplicationDatabase *self, GError **outError)
1148 {
1149 XfdashboardApplicationDatabasePrivate *priv;
1150 GarconMenu *appsMenu;
1151 GError *error;
1152
1153 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), FALSE);
1154 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1155
1156 priv=self->priv;
1157 error=NULL;
1158
1159 /* Load menu */
1160 appsMenu=garcon_menu_new_applications();
1161 if(!garcon_menu_load(appsMenu, NULL, &error))
1162 {
1163 /* Propagate error */
1164 g_propagate_error(outError, error);
1165
1166 /* Release allocated resources */
1167 g_object_unref(appsMenu);
1168
1169 return(FALSE);
1170 }
1171 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1172 "Loaded application menu '%s'",
1173 garcon_menu_element_get_name(GARCON_MENU_ELEMENT(appsMenu)));
1174
1175 /* Release old menus and set new one */
1176 if(priv->appsMenu)
1177 {
1178 if(priv->appsMenuReloadRequiredID)
1179 {
1180 g_signal_handler_disconnect(priv->appsMenu, priv->appsMenuReloadRequiredID);
1181 priv->appsMenuReloadRequiredID=0;
1182 }
1183
1184 g_object_unref(priv->appsMenu);
1185 priv->appsMenu=NULL;
1186 }
1187
1188 priv->appsMenu=appsMenu;
1189 priv->appsMenuReloadRequiredID=g_signal_connect_swapped(priv->appsMenu,
1190 "reload-required",
1191 G_CALLBACK(_xfdashboard_application_database_on_application_menu_reload_required),
1192 self);
1193
1194 /* Emit signal 'menu-reload-required' */
1195 g_signal_emit(self, XfdashboardApplicationDatabaseSignals[SIGNAL_MENU_RELOAD_REQUIRED], 0);
1196
1197 /* Menu was loaded successfully */
1198 return(TRUE);
1199 }
1200
1201 /* Release all allocated resources of this object and clean up */
_xfdashboard_application_database_clean(XfdashboardApplicationDatabase * self)1202 static void _xfdashboard_application_database_clean(XfdashboardApplicationDatabase *self)
1203 {
1204 XfdashboardApplicationDatabasePrivate *priv;
1205
1206 g_return_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self));
1207
1208 priv=self->priv;
1209
1210 /* Release allocated resources */
1211 if(priv->appDirMonitors)
1212 {
1213 GList *iter;
1214
1215 for(iter=priv->appDirMonitors; iter; iter=g_list_next(iter))
1216 {
1217 _xfdashboard_application_database_monitor_data_free((XfdashboardApplicationDatabaseFileMonitorData*)iter->data);
1218 }
1219 g_list_free(priv->appDirMonitors);
1220 priv->appDirMonitors=NULL;
1221 }
1222
1223 if(priv->appsMenu)
1224 {
1225 if(priv->appsMenuReloadRequiredID)
1226 {
1227 g_signal_handler_disconnect(priv->appsMenu, priv->appsMenuReloadRequiredID);
1228 priv->appsMenuReloadRequiredID=0;
1229 }
1230
1231 g_object_unref(priv->appsMenu);
1232 priv->appsMenu=NULL;
1233 }
1234
1235 if(priv->applications)
1236 {
1237 g_hash_table_unref(priv->applications);
1238 priv->applications=NULL;
1239 }
1240
1241 /* Now as all allocated resources are released, this database is not loaded anymore */
1242 priv->isLoaded=FALSE;
1243
1244 /* Notify about property change */
1245 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardApplicationDatabaseProperties[PROP_IS_LOADED]);
1246 }
1247
1248 /* Callback function to check for duplicate search path entries used in object instance initialization */
_xfdashboard_application_database_check_search_path_duplicate(gpointer inData,gpointer inUserData)1249 static void _xfdashboard_application_database_check_search_path_duplicate(gpointer inData, gpointer inUserData)
1250 {
1251 const gchar *searchPath;
1252 gchar **pathLookup;
1253
1254 g_return_if_fail(inData);
1255 g_return_if_fail(inUserData);
1256
1257 searchPath=(const gchar*)inData;
1258 pathLookup=(gchar**)inUserData;
1259
1260 /* Check if both paths are equal then reset pointer to pointer containing
1261 * search path to lookup to NULL indicating a duplicate entry.
1262 */
1263 if(g_strcmp0(*pathLookup, searchPath)==0) *pathLookup=NULL;
1264 }
1265
1266
1267 /* IMPLEMENTATION: GObject */
1268
1269 /* Construct this object */
_xfdashboard_application_database_constructor(GType inType,guint inNumberConstructParams,GObjectConstructParam * inConstructParams)1270 static GObject* _xfdashboard_application_database_constructor(GType inType,
1271 guint inNumberConstructParams,
1272 GObjectConstructParam *inConstructParams)
1273 {
1274 GObject *object;
1275
1276 if(!_xfdashboard_application_database)
1277 {
1278 object=G_OBJECT_CLASS(xfdashboard_application_database_parent_class)->constructor(inType, inNumberConstructParams, inConstructParams);
1279 _xfdashboard_application_database=XFDASHBOARD_APPLICATION_DATABASE(object);
1280 }
1281 else
1282 {
1283 object=g_object_ref(G_OBJECT(_xfdashboard_application_database));
1284 }
1285
1286 return(object);
1287 }
1288
1289 /* Dispose this object */
_xfdashboard_application_database_dispose(GObject * inObject)1290 static void _xfdashboard_application_database_dispose(GObject *inObject)
1291 {
1292 XfdashboardApplicationDatabase *self=XFDASHBOARD_APPLICATION_DATABASE(inObject);
1293 XfdashboardApplicationDatabasePrivate *priv=self->priv;
1294
1295 /* Release allocated resources */
1296 _xfdashboard_application_database_clean(self);
1297
1298 if(priv->searchPaths)
1299 {
1300 g_list_free_full(priv->searchPaths, g_free);
1301 priv->searchPaths=NULL;
1302 }
1303
1304 /* Call parent's class dispose method */
1305 G_OBJECT_CLASS(xfdashboard_application_database_parent_class)->dispose(inObject);
1306 }
1307
1308 /* Finalize this object */
_xfdashboard_application_database_finalize(GObject * inObject)1309 static void _xfdashboard_application_database_finalize(GObject *inObject)
1310 {
1311 /* Release allocated resources finally, e.g. unset singleton */
1312 if(G_LIKELY(G_OBJECT(_xfdashboard_application_database)==inObject))
1313 {
1314 _xfdashboard_application_database=NULL;
1315 }
1316
1317 /* Call parent's class dispose method */
1318 G_OBJECT_CLASS(xfdashboard_application_database_parent_class)->finalize(inObject);
1319 }
1320
1321 /* Set/get properties */
_xfdashboard_application_database_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)1322 static void _xfdashboard_application_database_get_property(GObject *inObject,
1323 guint inPropID,
1324 GValue *outValue,
1325 GParamSpec *inSpec)
1326 {
1327 XfdashboardApplicationDatabase *self=XFDASHBOARD_APPLICATION_DATABASE(inObject);
1328 XfdashboardApplicationDatabasePrivate *priv=self->priv;;
1329
1330 switch(inPropID)
1331 {
1332 case PROP_IS_LOADED:
1333 g_value_set_boolean(outValue, priv->isLoaded);
1334 break;
1335
1336 default:
1337 G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1338 break;
1339 }
1340 }
1341
1342 /* Class initialization
1343 * Override functions in parent classes and define properties
1344 * and signals
1345 */
xfdashboard_application_database_class_init(XfdashboardApplicationDatabaseClass * klass)1346 static void xfdashboard_application_database_class_init(XfdashboardApplicationDatabaseClass *klass)
1347 {
1348 GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
1349
1350 /* Override functions */
1351 gobjectClass->constructor=_xfdashboard_application_database_constructor;
1352 gobjectClass->dispose=_xfdashboard_application_database_dispose;
1353 gobjectClass->finalize=_xfdashboard_application_database_finalize;
1354 gobjectClass->get_property=_xfdashboard_application_database_get_property;
1355
1356 /* Define properties */
1357 XfdashboardApplicationDatabaseProperties[PROP_IS_LOADED]=
1358 g_param_spec_boolean("is-loaded",
1359 "Is loaded",
1360 "Flag indicating if application database has been initialized and loaded successfully",
1361 FALSE,
1362 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1363
1364 /* Define signals */
1365 XfdashboardApplicationDatabaseSignals[SIGNAL_MENU_RELOAD_REQUIRED]=
1366 g_signal_new("menu-reload-required",
1367 G_TYPE_FROM_CLASS(klass),
1368 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
1369 G_STRUCT_OFFSET(XfdashboardApplicationDatabaseClass, menu_reload_required),
1370 NULL,
1371 NULL,
1372 g_cclosure_marshal_VOID__VOID,
1373 G_TYPE_NONE,
1374 0);
1375
1376 XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_ADDED]=
1377 g_signal_new("application-added",
1378 G_TYPE_FROM_CLASS(klass),
1379 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
1380 G_STRUCT_OFFSET(XfdashboardApplicationDatabaseClass, application_added),
1381 NULL,
1382 NULL,
1383 g_cclosure_marshal_VOID__OBJECT,
1384 G_TYPE_NONE,
1385 1,
1386 G_TYPE_APP_INFO);
1387
1388 XfdashboardApplicationDatabaseSignals[SIGNAL_APPLICATION_REMOVED]=
1389 g_signal_new("application-removed",
1390 G_TYPE_FROM_CLASS(klass),
1391 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,
1392 G_STRUCT_OFFSET(XfdashboardApplicationDatabaseClass, application_removed),
1393 NULL,
1394 NULL,
1395 g_cclosure_marshal_VOID__OBJECT,
1396 G_TYPE_NONE,
1397 1,
1398 G_TYPE_APP_INFO);
1399 }
1400
1401 /* Object initialization
1402 * Create private structure and set up default values
1403 */
xfdashboard_application_database_init(XfdashboardApplicationDatabase * self)1404 static void xfdashboard_application_database_init(XfdashboardApplicationDatabase *self)
1405 {
1406 XfdashboardApplicationDatabasePrivate *priv;
1407 const gchar* const *systemPaths;
1408 gchar *path;
1409
1410 priv=self->priv=xfdashboard_application_database_get_instance_private(self);
1411
1412 /* Set default values */
1413 priv->isLoaded=FALSE;
1414 priv->searchPaths=NULL;
1415 priv->appsMenu=NULL;
1416 priv->appsMenuReloadRequiredID=0;
1417 priv->applications=NULL;
1418 priv->appDirMonitors=NULL;
1419
1420 /* Set up search paths but eliminate duplicates */
1421 path=g_build_filename(g_get_user_data_dir(), "applications", NULL);
1422 priv->searchPaths=g_list_append(priv->searchPaths, path);
1423 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1424 "Added search path '%s' to application database",
1425 path);
1426
1427 systemPaths=g_get_system_data_dirs();
1428 while(*systemPaths)
1429 {
1430 gchar *searchPathUnique;
1431
1432 /* Build path to applications directory and add to list of search paths */
1433 path=g_build_filename(*systemPaths, "applications", NULL);
1434
1435 /* Check for duplicate search path by calling a callback functions
1436 * for each search path already in list. The callback function expects
1437 * a pointer to a pointer containing the search path to lookup. If
1438 * the callback find a search path matching the requested one
1439 * the pointer to the pointer will be set to NULL.
1440 */
1441 searchPathUnique=path;
1442 g_list_foreach(priv->searchPaths, _xfdashboard_application_database_check_search_path_duplicate, &searchPathUnique);
1443 if(searchPathUnique)
1444 {
1445 priv->searchPaths=g_list_append(priv->searchPaths, g_strdup(searchPathUnique));
1446 XFDASHBOARD_DEBUG(self, APPLICATIONS,
1447 "Added search path '%s' to application database",
1448 searchPathUnique);
1449 }
1450
1451 /* Release allocated resources */
1452 g_free(path);
1453
1454 /* Continue with next system path */
1455 systemPaths++;
1456 }
1457 }
1458
1459 /* IMPLEMENTATION: Public API */
1460
1461 /* Get single instance of application */
xfdashboard_application_database_get_default(void)1462 XfdashboardApplicationDatabase* xfdashboard_application_database_get_default(void)
1463 {
1464 GObject *singleton;
1465
1466 singleton=g_object_new(XFDASHBOARD_TYPE_APPLICATION_DATABASE, NULL);
1467 return(XFDASHBOARD_APPLICATION_DATABASE(singleton));
1468 }
1469
1470 /* Determine if menu and applications has been loaded successfully */
xfdashboard_application_database_is_loaded(const XfdashboardApplicationDatabase * self)1471 gboolean xfdashboard_application_database_is_loaded(const XfdashboardApplicationDatabase *self)
1472 {
1473 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), FALSE);
1474
1475 return(self->priv->isLoaded);
1476 }
1477
1478 /* Load menu and applications */
xfdashboard_application_database_load(XfdashboardApplicationDatabase * self,GError ** outError)1479 gboolean xfdashboard_application_database_load(XfdashboardApplicationDatabase *self, GError **outError)
1480 {
1481 XfdashboardApplicationDatabasePrivate *priv;
1482 GError *error;
1483
1484 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), FALSE);
1485 g_return_val_if_fail(outError && *outError==NULL, FALSE);
1486
1487 priv=self->priv;
1488 error=NULL;
1489
1490 /* Load menu */
1491 if(!_xfdashboard_application_database_load_application_menu(self, &error))
1492 {
1493 /* Propagate error */
1494 g_propagate_error(outError, error);
1495
1496 /* Clean up this instance by releasing all allocated resources */
1497 _xfdashboard_application_database_clean(self);
1498
1499 return(FALSE);
1500 }
1501
1502 /* Load installed and user-overidden application desktop files */
1503 if(!_xfdashboard_application_database_load_applications(self, &error))
1504 {
1505 /* Propagate error */
1506 g_propagate_error(outError, error);
1507
1508 /* Clean up this instance by releasing all allocated resources */
1509 _xfdashboard_application_database_clean(self);
1510
1511 return(FALSE);
1512 }
1513
1514 /* Loading was successful */
1515 priv->isLoaded=TRUE;
1516
1517 /* Notify about property change */
1518 g_object_notify_by_pspec(G_OBJECT(self), XfdashboardApplicationDatabaseProperties[PROP_IS_LOADED]);
1519
1520 /* Loading was successful so return TRUE here */
1521 return(TRUE);
1522 }
1523
1524 /* Get list of search paths where desktop files could be stored at */
xfdashboard_application_database_get_application_search_paths(const XfdashboardApplicationDatabase * self)1525 const GList* xfdashboard_application_database_get_application_search_paths(const XfdashboardApplicationDatabase *self)
1526 {
1527 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
1528
1529 return(self->priv->searchPaths);
1530 }
1531
1532 /* Get application menu.
1533 * The return object has to be freed with g_object_unref().
1534 */
xfdashboard_application_database_get_application_menu(XfdashboardApplicationDatabase * self)1535 GarconMenu* xfdashboard_application_database_get_application_menu(XfdashboardApplicationDatabase *self)
1536 {
1537 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
1538
1539 if(self->priv->appsMenu)
1540 {
1541 return(GARCON_MENU(g_object_ref(self->priv->appsMenu)));
1542 }
1543
1544 /* If we get here no application menu is available, so return NULL */
1545 return(NULL);
1546 }
1547
1548 /* Get list of all installed applications in database */
xfdashboard_application_database_get_all_applications(XfdashboardApplicationDatabase * self)1549 GList* xfdashboard_application_database_get_all_applications(XfdashboardApplicationDatabase *self)
1550 {
1551 XfdashboardApplicationDatabasePrivate *priv;
1552 GList *applicationsList;
1553
1554 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
1555
1556 priv=self->priv;
1557 applicationsList=NULL;
1558
1559 /* Add each item in hash table to list */
1560 if(priv->applications)
1561 {
1562 g_hash_table_foreach(priv->applications,
1563 _xfdashboard_application_database_add_hashtable_item_to_list,
1564 &applicationsList);
1565
1566 applicationsList=g_list_reverse(applicationsList);
1567 }
1568
1569 /* Return list of installed applications */
1570 return(applicationsList);
1571 }
1572
1573 /* Get GAppInfo for desktop ID from cache which was built while iterating
1574 * through search paths and scanning for desktop files.
1575 * If a GAppInfo object for desktop ID was found the return object has to
1576 * be freed with g_object_unref().
1577 * If a GAppInfo object for desktop ID was not found, NULL will be returned.
1578 */
xfdashboard_application_database_lookup_desktop_id(XfdashboardApplicationDatabase * self,const gchar * inDesktopID)1579 GAppInfo* xfdashboard_application_database_lookup_desktop_id(XfdashboardApplicationDatabase *self,
1580 const gchar *inDesktopID)
1581 {
1582 XfdashboardApplicationDatabasePrivate *priv;
1583 gpointer appInfo;
1584
1585 g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_DATABASE(self), NULL);
1586 g_return_val_if_fail(inDesktopID && *inDesktopID, NULL);
1587
1588 priv=self->priv;
1589
1590 /* Lookup desktop ID in cache */
1591 if(priv->applications &&
1592 g_hash_table_lookup_extended(priv->applications, inDesktopID, NULL, &appInfo))
1593 {
1594 g_object_ref(G_OBJECT(appInfo));
1595 return(G_APP_INFO(appInfo));
1596 }
1597
1598 /* Desktop ID not found, so return NULL */
1599 return(NULL);
1600 }
1601
1602 /* Get path to desktop file for requested desktop ID.
1603 * Returns NULL if desktop file is invalid or was not found at any search path.
1604 */
xfdashboard_application_database_get_file_from_desktop_id(const gchar * inDesktopID)1605 gchar* xfdashboard_application_database_get_file_from_desktop_id(const gchar *inDesktopID)
1606 {
1607 XfdashboardApplicationDatabase *appDB;
1608 const GList *searchPaths;
1609 gchar *foundDesktopFile;
1610
1611 g_return_val_if_fail(inDesktopID && *inDesktopID, NULL);
1612
1613 /* Get singleton of application database */
1614 appDB=xfdashboard_application_database_get_default();
1615
1616 /* Requested desktop ID must have ".desktop" suffix */
1617 if(!g_str_has_suffix(inDesktopID, ".desktop"))
1618 {
1619 XFDASHBOARD_DEBUG(appDB, APPLICATIONS,
1620 "Skipping non-desktop file '%s'",
1621 inDesktopID);
1622 g_object_unref(appDB);
1623 return(NULL);
1624 }
1625
1626 /* Find the desktop file for a desktop ID isn't as easy as it sounds.
1627 * Especially if the desktop file contains at least one dash. The dash
1628 * could either be part of the desktop file's file name or is a
1629 * directory seperator or it is part of the directory name where to
1630 * continue to lookup the desktop file.
1631 * So we have to perform a lot of check to find (hopefully) the right
1632 * desktop file for requested desktop ID. We also let matching file
1633 * name win over matching directory names and shorter directory names
1634 * over longer ones.
1635 *
1636 * 1.) Check if a file name with desktop ID and the suffix ".desktop"
1637 * exists at current search path. If it does return it as result.
1638 * 2.) Split desktop ID into parts at the dashes. Check if a directory
1639 * with the first part exists. If it does not exists append the
1640 * next part with a dash to the first part and check if a directory
1641 * with this name exists. If necessary repeat until all parts are
1642 * consumed. If no directory was found get next search path and
1643 * repeat from step 1.
1644 * 3.) If a directory was found combine the remaining parts with dashes
1645 * to form a new desktop ID which needs to be found at the directory
1646 * found. Repeat at step 1.
1647 * 4.) Get next path from search path and repeat from step 1 if there
1648 * is any search path left.
1649 * 5.) If this step is reached, no desktop file was found.
1650 */
1651
1652 /* Get search paths */
1653 searchPaths=xfdashboard_application_database_get_application_search_paths(appDB);
1654 if(!searchPaths)
1655 {
1656 /* Release allocated resources */
1657 g_object_unref(appDB);
1658
1659 return(NULL);
1660 }
1661
1662 /* Iterate through search paths and try to find desktop file for
1663 * desktop ID.
1664 */
1665 foundDesktopFile=NULL;
1666 for(; searchPaths && !foundDesktopFile; searchPaths=g_list_next(searchPaths))
1667 {
1668 const gchar *searchPath;
1669 GFile *directory;
1670 gchar *desktopID;
1671 gchar *desktopIDIter;
1672
1673 /* Get search path */
1674 searchPath=(const gchar*)searchPaths->data;
1675 if(!searchPath) continue;
1676
1677 /* Set up variable to query desktop file for requested desktop ID */
1678 directory=g_file_new_for_path(searchPath);
1679 desktopID=g_strdup(inDesktopID);
1680
1681 /* Try to find desktop file */
1682 desktopIDIter=desktopID;
1683 while(!foundDesktopFile && desktopIDIter && *desktopIDIter)
1684 {
1685 GFile *desktopFile;
1686
1687 /* Check if a file with desktop ID and suffix ".desktop" exists
1688 * at current directory path.
1689 */
1690 desktopFile=g_file_get_child(directory, desktopIDIter);
1691 if(g_file_query_exists(desktopFile, NULL))
1692 {
1693 foundDesktopFile=g_file_get_path(desktopFile);
1694 }
1695 g_object_unref(desktopFile);
1696
1697 /* There was no desktop file at directory so lookup next possible
1698 * directory to lookup desktop file at.
1699 */
1700 if(!foundDesktopFile)
1701 {
1702 gchar *p;
1703 gboolean foundDirectory;
1704 GFile *subDirectory;
1705
1706 /* Iterate through remaining desktop ID and lookup dash.
1707 * If a dash is found check if directory with this name exists.
1708 * If a directory exists set up new base directory and repeat
1709 * with outer loop (lookup file, then sub-directories) again.
1710 */
1711 foundDirectory=FALSE;
1712 p=desktopIDIter;
1713 while(!foundDirectory && *p)
1714 {
1715 /* Found a dash so check if a sub-directory with this name
1716 * up to this dash exists.
1717 */
1718 if(*p=='-')
1719 {
1720 /* Truncate desktop ID to current position */
1721 *p=0;
1722
1723 /* Check if sub-directory with truncated desktop ID
1724 * as name exists.
1725 */
1726 subDirectory=g_file_get_child(directory, desktopIDIter);
1727 if(g_file_query_exists(subDirectory, NULL))
1728 {
1729 /* Directory with this name exists. So set up
1730 * sub-directory as new base directory to lookup
1731 * remaining desktop ID at. Also set up desktop ID
1732 * iterator to remaining desktop ID to lookup.
1733 */
1734 g_object_unref(directory);
1735 directory=g_object_ref(subDirectory);
1736
1737 desktopIDIter=p;
1738 desktopIDIter++;
1739
1740 foundDirectory=TRUE;
1741 }
1742 g_object_unref(subDirectory);
1743
1744 /* Restore desktop ID by setting dash again we removed
1745 * when checking sub-directory existance.
1746 */
1747 *p='-';
1748 }
1749
1750 /* Continue with next character in remaining desktop ID */
1751 p++;
1752 }
1753
1754 /* If we have not found a sub-directory then set desktop ID
1755 * iterator to NULL to break loop and to continue with next
1756 * path in list of search paths.
1757 */
1758 if(!foundDirectory) desktopIDIter=NULL;
1759 }
1760 }
1761
1762 /* Release allocated resources */
1763 g_object_unref(directory);
1764 g_free(desktopID);
1765 }
1766
1767 /* Release allocated resources */
1768 g_object_unref(appDB);
1769
1770 /* Return found desktop file */
1771 return(foundDesktopFile);
1772 }
1773
1774 /* Get desktop ID from requested desktop file path
1775 * Returns NULL if desktop file is not in any search path.
1776 */
xfdashboard_application_database_get_desktop_id_from_path(const gchar * inFilename)1777 gchar* xfdashboard_application_database_get_desktop_id_from_path(const gchar *inFilename)
1778 {
1779 XfdashboardApplicationDatabase *appDB;
1780 const GList *searchPaths;
1781 gchar *foundDesktopID;
1782
1783 g_return_val_if_fail(inFilename && *inFilename, NULL);
1784 g_return_val_if_fail(g_str_has_suffix(inFilename, ".desktop"), NULL);
1785
1786 foundDesktopID=NULL;
1787
1788 /* Get singleton of application database */
1789 appDB=xfdashboard_application_database_get_default();
1790
1791 /* Get search paths */
1792 searchPaths=xfdashboard_application_database_get_application_search_paths(appDB);
1793 if(!searchPaths)
1794 {
1795 /* Release allocated resources */
1796 g_object_unref(appDB);
1797
1798 return(NULL);
1799 }
1800
1801 /* Iterate through search paths and check if filename of desktop file
1802 * begins with search path. If it does then strip search path from
1803 * desktop file's filename and replace all directory seperators with
1804 * dashes.
1805 */
1806 for(; searchPaths && !foundDesktopID; searchPaths=g_list_next(searchPaths))
1807 {
1808 const gchar *searchPath;
1809
1810 /* Get search path */
1811 searchPath=(const gchar*)searchPaths->data;
1812 if(!searchPath) continue;
1813
1814 /* Check if filename path begins with current search path */
1815 if(g_str_has_prefix(inFilename, searchPath))
1816 {
1817 const gchar *begin;
1818
1819 /* Strip search path from desktop file's path */
1820 begin=inFilename+strlen(searchPath);
1821 while(*begin==G_DIR_SEPARATOR) begin++;
1822
1823 foundDesktopID=g_strdup(begin);
1824 }
1825 }
1826
1827 /* If desktop ID was found replace directory sepearators with dashes */
1828 if(foundDesktopID)
1829 {
1830 gchar *separatorIter;
1831
1832 separatorIter=foundDesktopID;
1833 while(*separatorIter)
1834 {
1835 /* Replace directory sepearator with dash if needed */
1836 if(*separatorIter==G_DIR_SEPARATOR) *separatorIter='-';
1837
1838 /* Continue with next character in desktop ID */
1839 separatorIter++;
1840 }
1841 }
1842
1843 /* Release allocated resources */
1844 g_object_unref(appDB);
1845
1846 /* Return found desktop ID */
1847 return(foundDesktopID);
1848 }
1849
1850 /* Get desktop ID from requested desktop file object
1851 * Returns NULL if desktop file is not in any search path.
1852 */
xfdashboard_application_database_get_desktop_id_from_file(GFile * inFile)1853 gchar* xfdashboard_application_database_get_desktop_id_from_file(GFile *inFile)
1854 {
1855 gchar *path;
1856 gchar *foundDesktopID;
1857
1858 g_return_val_if_fail(G_IS_FILE(inFile), NULL);
1859
1860 /* Get path of file object */
1861 path=g_file_get_path(inFile);
1862
1863 /* Get desktop ID for path */
1864 foundDesktopID=xfdashboard_application_database_get_desktop_id_from_path(path);
1865
1866 /* Release allocated resources */
1867 if(path) g_free(path);
1868
1869 /* Return found desktop ID */
1870 return(foundDesktopID);
1871 }
1872