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*)&currentDesktopAppInfo))
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*)&currentDesktopAppInfo))
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