No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | hotplug-sniffer/shell-mime-sniffer.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
4 * Copyright (C) 2011 Red Hat, Inc.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 * 02111-1307, USA.
20 *
21 * Author: Cosimo Cecchi <cosimoc@redhat.com>
22 *
23 * The code for crawling the directory hierarchy is based on
24 * nautilus/libnautilus-private/nautilus-directory-async.c, with
25 * the following copyright and author:
26 *
27 * Copyright (C) 1999, 2000, 2001 Eazel, Inc.
28 * Author: Darin Adler <darin@bentspoon.com>
29 *
30 */
31
32 #include "shell-mime-sniffer.h"
33 #include "hotplug-mimetypes.h"
34
35 #include <glib/gi18n.h>
36
37 #include <gdk-pixbuf/gdk-pixbuf.h>
38
39 #define LOADER_ATTRS \
40 G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
41 G_FILE_ATTRIBUTE_STANDARD_NAME "," \
42 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
43
44 #define WATCHDOG_TIMEOUT 1500
45 #define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
46 #define HIGH_SCORE_RATIO 0.10
47
48 G_DEFINE_TYPE (ShellMimeSniffer, shell_mime_sniffer, G_TYPE_OBJECT);
49
50 enum {
51 PROP_FILE = 1,
52 NUM_PROPERTIES
53 };
54
55 static GHashTable *image_type_table = NULL;
56 static GHashTable *audio_type_table = NULL;
57 static GHashTable *video_type_table = NULL;
58 static GHashTable *docs_type_table = NULL;
59
60 static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
61
62 typedef struct {
63 ShellMimeSniffer *self;
64
65 GFile *file;
66 GFileEnumerator *enumerator;
67 GList *deep_count_subdirectories;
68
69 gint audio_count;
70 gint image_count;
71 gint document_count;
72 gint video_count;
73
74 gint total_items;
75 } DeepCountState;
76
77 struct _ShellMimeSnifferPrivate {
78 GFile *file;
79
80 GCancellable *cancellable;
81 guint watchdog_id;
82
83 GSimpleAsyncResult *async_result;
84 gchar **sniffed_mime;
85 };
86
87 static void deep_count_load (DeepCountState *state,
88 GFile *file);
89
90 static void
91 init_mimetypes (void)
92 {
93 static gsize once_init = 0;
94
95 if (g_once_init_enter (&once_init))
96 {
97 GSList *formats, *l;
98 GdkPixbufFormat *format;
99 gchar **types;
100 gint idx;
101
102 image_type_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
103 video_type_table = g_hash_table_new (g_str_hash, g_str_equal);
104 audio_type_table = g_hash_table_new (g_str_hash, g_str_equal);
105 docs_type_table = g_hash_table_new (g_str_hash, g_str_equal);
106
107 formats = gdk_pixbuf_get_formats ();
108
109 for (l = formats; l != NULL; l = l->next)
110 {
111 format = l->data;
112 types = gdk_pixbuf_format_get_mime_types (format);
113
114 for (idx = 0; types[idx] != NULL; idx++)
115 g_hash_table_insert (image_type_table, g_strdup (types[idx]), GINT_TO_POINTER (1));
116
117 g_strfreev (types);
118 }
119
120 g_slist_free (formats);
121
122 for (idx = 0; audio_mimetypes[idx] != NULL; idx++)
123 g_hash_table_insert (audio_type_table, (gpointer) audio_mimetypes[idx], GINT_TO_POINTER (1));
124
125 for (idx = 0; video_mimetypes[idx] != NULL; idx++)
126 g_hash_table_insert (video_type_table, (gpointer) video_mimetypes[idx], GINT_TO_POINTER (1));
127
128 for (idx = 0; docs_mimetypes[idx] != NULL; idx++)
129 g_hash_table_insert (docs_type_table, (gpointer) docs_mimetypes[idx], GINT_TO_POINTER (1));
130
131 g_once_init_leave (&once_init, 1);
132 }
133 }
134
135 static void
136 add_content_type_to_cache (DeepCountState *state,
137 const gchar *content_type)
138 {
139 gboolean matched = TRUE;
140
141 if (g_hash_table_lookup (image_type_table, content_type))
142 state->image_count++;
143 else if (g_hash_table_lookup (video_type_table, content_type))
144 state->video_count++;
145 else if (g_hash_table_lookup (docs_type_table, content_type))
146 state->document_count++;
147 else if (g_hash_table_lookup (audio_type_table, content_type))
148 state->audio_count++;
149 else
150 matched = FALSE;
151
152 if (matched)
153 state->total_items++;
154 }
155
156 typedef struct {
157 const gchar *type;
158 gdouble ratio;
159 } SniffedResult;
160
161 static gint
162 results_cmp_func (gconstpointer a,
163 gconstpointer b)
164 {
165 const SniffedResult *sniffed_a = a;
166 const SniffedResult *sniffed_b = b;
167
168 if (sniffed_a->ratio < sniffed_b->ratio)
169 return 1;
170
171 if (sniffed_a->ratio > sniffed_b->ratio)
172 return -1;
173
174 return 0;
175 }
176
177 static void
178 prepare_async_result (DeepCountState *state)
179 {
180 ShellMimeSniffer *self = state->self;
181 GArray *results;
182 GPtrArray *sniffed_mime;
183 SniffedResult result;
184
185 sniffed_mime = g_ptr_array_new ();
186 results = g_array_new (TRUE, TRUE, sizeof (SniffedResult));
187
188 if (state->total_items == 0)
189 goto out;
190
191 result.type = "x-content/video";
192 result.ratio = (gdouble) state->video_count / (gdouble) state->total_items;
193 g_array_append_val (results, result);
194
195 result.type = "x-content/audio";
196 result.ratio = (gdouble) state->audio_count / (gdouble) state->total_items;
197 g_array_append_val (results, result);
198
199 result.type = "x-content/pictures";
200 result.ratio = (gdouble) state->image_count / (gdouble) state->total_items;
201 g_array_append_val (results, result);
202
203 result.type = "x-content/documents";
204 result.ratio = (gdouble) state->document_count / (gdouble) state->total_items;
205 g_array_append_val (results, result);
206
207 g_array_sort (results, results_cmp_func);
208
209 result = g_array_index (results, SniffedResult, 0);
210 g_ptr_array_add (sniffed_mime, g_strdup (result.type));
211
212 /* if other types score high in ratio, add them, up to three */
213 result = g_array_index (results, SniffedResult, 1);
214 if (result.ratio < HIGH_SCORE_RATIO)
215 goto out;
216 g_ptr_array_add (sniffed_mime, g_strdup (result.type));
217
218 result = g_array_index (results, SniffedResult, 2);
219 if (result.ratio < HIGH_SCORE_RATIO)
220 goto out;
221 g_ptr_array_add (sniffed_mime, g_strdup (result.type));
222
223 out:
224 g_ptr_array_add (sniffed_mime, NULL);
225 self->priv->sniffed_mime = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
226
227 g_array_free (results, TRUE);
228 g_simple_async_result_complete_in_idle (self->priv->async_result);
229 }
230
231 /* adapted from nautilus/libnautilus-private/nautilus-directory-async.c */
232 static void
233 deep_count_one (DeepCountState *state,
234 GFileInfo *info)
235 {
236 GFile *subdir;
237 const char *content_type;
238
239 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
240 {
241 /* record the fact that we have to descend into this directory */
242 subdir = g_file_get_child (state->file, g_file_info_get_name (info));
243 state->deep_count_subdirectories =
244 g_list_append (state->deep_count_subdirectories, subdir);
245 }
246 else
247 {
248 content_type = g_file_info_get_content_type (info);
249 add_content_type_to_cache (state, content_type);
250 }
251 }
252
253 static void
254 deep_count_finish (DeepCountState *state)
255 {
256 prepare_async_result (state);
257
258 if (state->enumerator)
259 {
260 if (!g_file_enumerator_is_closed (state->enumerator))
261 g_file_enumerator_close_async (state->enumerator,
262 0, NULL, NULL, NULL);
263
264 g_object_unref (state->enumerator);
265 }
266
267 g_cancellable_reset (state->self->priv->cancellable);
268 g_clear_object (&state->file);
269
270 g_list_free_full (state->deep_count_subdirectories, g_object_unref);
271
272 g_free (state);
273 }
274
275 static void
276 deep_count_next_dir (DeepCountState *state)
277 {
278 GFile *new_file;
279
280 g_clear_object (&state->file);
281
282 if (state->deep_count_subdirectories != NULL)
283 {
284 /* Work on a new directory. */
285 new_file = state->deep_count_subdirectories->data;
286 state->deep_count_subdirectories =
287 g_list_remove (state->deep_count_subdirectories, new_file);
288
289 deep_count_load (state, new_file);
290 g_object_unref (new_file);
291 }
292 else
293 {
294 deep_count_finish (state);
295 }
296 }
297
298 static void
299 deep_count_more_files_callback (GObject *source_object,
300 GAsyncResult *res,
301 gpointer user_data)
302 {
303 DeepCountState *state;
304 GList *files, *l;
305 GFileInfo *info;
306
307 state = user_data;
308
309 if (g_cancellable_is_cancelled (state->self->priv->cancellable))
310 {
311 deep_count_finish (state);
312 return;
313 }
314
315 files = g_file_enumerator_next_files_finish (state->enumerator,
316 res, NULL);
317
318 for (l = files; l != NULL; l = l->next)
319 {
320 info = l->data;
321 deep_count_one (state, info);
322 g_object_unref (info);
323 }
324
325 if (files == NULL)
326 {
327 g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL);
328 g_object_unref (state->enumerator);
329 state->enumerator = NULL;
330
331 deep_count_next_dir (state);
332 }
333 else
334 {
335 g_file_enumerator_next_files_async (state->enumerator,
336 DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
337 G_PRIORITY_LOW,
338 state->self->priv->cancellable,
339 deep_count_more_files_callback,
340 state);
341 }
342
343 g_list_free (files);
344 }
345
346 static void
347 deep_count_callback (GObject *source_object,
348 GAsyncResult *res,
349 gpointer user_data)
350 {
351 DeepCountState *state;
352 GFileEnumerator *enumerator;
353
354 state = user_data;
355
356 if (g_cancellable_is_cancelled (state->self->priv->cancellable))
357 {
358 deep_count_finish (state);
359 return;
360 }
361
362 enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
363 res, NULL);
364
365 if (enumerator == NULL)
366 {
367 deep_count_next_dir (state);
368 }
369 else
370 {
371 state->enumerator = enumerator;
372 g_file_enumerator_next_files_async (state->enumerator,
373 DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
374 G_PRIORITY_LOW,
375 state->self->priv->cancellable,
376 deep_count_more_files_callback,
377 state);
378 }
379 }
380
381 static void
382 deep_count_load (DeepCountState *state,
383 GFile *file)
384 {
385 state->file = g_object_ref (file);
386
387 g_file_enumerate_children_async (state->file,
388 LOADER_ATTRS,
389 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */
390 G_PRIORITY_LOW, /* prio */
391 state->self->priv->cancellable,
392 deep_count_callback,
393 state);
394 }
395
396 static void
397 deep_count_start (ShellMimeSniffer *self)
398 {
399 DeepCountState *state;
400
401 state = g_new0 (DeepCountState, 1);
402 state->self = self;
403
404 deep_count_load (state, self->priv->file);
405 }
406
407 static void
408 query_info_async_ready_cb (GObject *source,
409 GAsyncResult *res,
410 gpointer user_data)
411 {
412 GFileInfo *info;
413 GError *error = NULL;
414 ShellMimeSniffer *self = user_data;
415
416 info = g_file_query_info_finish (G_FILE (source),
417 res, &error);
418
419 if (error != NULL)
420 {
421 g_simple_async_result_take_error (self->priv->async_result,
422 error);
423 g_simple_async_result_complete_in_idle (self->priv->async_result);
424
425 return;
426 }
427
428 if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
429 {
430 g_simple_async_result_set_error (self->priv->async_result,
431 G_IO_ERROR,
432 G_IO_ERROR_NOT_DIRECTORY,
433 "Not a directory");
434 g_simple_async_result_complete_in_idle (self->priv->async_result);
435
436 return;
437 }
438
439 deep_count_start (self);
440 }
441
442 static gboolean
443 watchdog_timeout_reached_cb (gpointer user_data)
444 {
445 ShellMimeSniffer *self = user_data;
446
447 self->priv->watchdog_id = 0;
448 g_cancellable_cancel (self->priv->cancellable);
449
450 return FALSE;
451 }
452
453 static void
454 start_loading_file (ShellMimeSniffer *self)
455 {
456 g_file_query_info_async (self->priv->file,
457 LOADER_ATTRS,
458 G_FILE_QUERY_INFO_NONE,
459 G_PRIORITY_DEFAULT,
460 self->priv->cancellable,
461 query_info_async_ready_cb,
462 self);
463 }
464
465 static void
466 shell_mime_sniffer_set_file (ShellMimeSniffer *self,
467 GFile *file)
468 {
469 g_clear_object (&self->priv->file);
470 self->priv->file = g_object_ref (file);
471 }
472
473 static void
474 shell_mime_sniffer_dispose (GObject *object)
475 {
476 ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
477
478 g_clear_object (&self->priv->file);
479 g_clear_object (&self->priv->cancellable);
480 g_clear_object (&self->priv->async_result);
481
482 if (self->priv->watchdog_id != 0)
483 {
484 g_source_remove (self->priv->watchdog_id);
485 self->priv->watchdog_id = 0;
486 }
487
488 G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->dispose (object);
489 }
490
491 static void
492 shell_mime_sniffer_finalize (GObject *object)
493 {
494 ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
495
496 g_strfreev (self->priv->sniffed_mime);
497
498 G_OBJECT_CLASS (shell_mime_sniffer_parent_class)->finalize (object);
499 }
500
501 static void
502 shell_mime_sniffer_get_property (GObject *object,
503 guint prop_id,
504 GValue *value,
505 GParamSpec *pspec)
506 {
507 ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
508
509 switch (prop_id) {
510 case PROP_FILE:
511 g_value_set_object (value, self->priv->file);
512 break;
513 default:
514 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
515 break;
516 }
517 }
518
519 static void
520 shell_mime_sniffer_set_property (GObject *object,
521 guint prop_id,
522 const GValue *value,
523 GParamSpec *pspec)
524 {
525 ShellMimeSniffer *self = SHELL_MIME_SNIFFER (object);
526
527 switch (prop_id) {
528 case PROP_FILE:
529 shell_mime_sniffer_set_file (self, g_value_get_object (value));
530 break;
531 default:
532 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
533 break;
534 }
535 }
536
537 static void
538 shell_mime_sniffer_class_init (ShellMimeSnifferClass *klass)
539 {
540 GObjectClass *oclass;
541
542 oclass = G_OBJECT_CLASS (klass);
543 oclass->dispose = shell_mime_sniffer_dispose;
544 oclass->finalize = shell_mime_sniffer_finalize;
545 oclass->get_property = shell_mime_sniffer_get_property;
546 oclass->set_property = shell_mime_sniffer_set_property;
547
548 properties[PROP_FILE] =
549 g_param_spec_object ("file",
550 "File",
551 "The loaded file",
552 G_TYPE_FILE,
553 G_PARAM_READWRITE);
554
555 g_type_class_add_private (klass, sizeof (ShellMimeSnifferPrivate));
556 g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
557 }
558
559 static void
560 shell_mime_sniffer_init (ShellMimeSniffer *self)
561 {
562 self->priv =
563 G_TYPE_INSTANCE_GET_PRIVATE (self,
564 SHELL_TYPE_MIME_SNIFFER,
565 ShellMimeSnifferPrivate);
566 init_mimetypes ();
567 }
568
569 ShellMimeSniffer *
570 shell_mime_sniffer_new (GFile *file)
571 {
572 return g_object_new (SHELL_TYPE_MIME_SNIFFER,
573 "file", file,
574 NULL);
575 }
576
577 void
578 shell_mime_sniffer_sniff_async (ShellMimeSniffer *self,
579 GAsyncReadyCallback callback,
580 gpointer user_data)
581 {
582 g_assert (self->priv->watchdog_id == 0);
583 g_assert (self->priv->async_result == NULL);
584
585 self->priv->async_result =
586 g_simple_async_result_new (G_OBJECT (self),
587 callback, user_data,
588 shell_mime_sniffer_sniff_finish);
589
590 self->priv->cancellable = g_cancellable_new ();
591
592 self->priv->watchdog_id =
593 g_timeout_add (WATCHDOG_TIMEOUT,
594 watchdog_timeout_reached_cb, self);
595
596 start_loading_file (self);
597 }
598
599 gchar **
600 shell_mime_sniffer_sniff_finish (ShellMimeSniffer *self,
601 GAsyncResult *res,
602 GError **error)
603 {
604 if (g_simple_async_result_propagate_error (self->priv->async_result, error))
605 return NULL;
606
607 return g_strdupv (self->priv->sniffed_mime);
608 }