1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*
3  * Copyright (c) 2017 Ali Abdallah <ali@xfce.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <math.h>
26 
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <glib-object.h>
30 
31 #include <gdk-pixbuf/gdk-pixbuf.h>
32 
33 #include <gio/gio.h>
34 #include <glib/gstdio.h>
35 
36 #include <tumbler/tumbler.h>
37 
38 #include <desktop-thumbnailer/desktop-thumbnailer.h>
39 
40 
41 
42 static void desktop_thumbnailer_create   (TumblerAbstractThumbnailer *thumbnailer,
43                                           GCancellable               *cancellable,
44                                           TumblerFileInfo            *info);
45 
46 static void desktop_thumbnailer_get_property(GObject *object,
47                                              guint prop_id,
48                                              GValue *value,
49                                              GParamSpec *pspec);
50 
51 static void desktop_thumbnailer_set_property(GObject *object,
52                                              guint prop_id,
53                                              const GValue *value,
54                                              GParamSpec *pspec);
55 
56 static void desktop_thumbnailer_finalize(GObject *object);
57 
58 
59 
60 struct _DesktopThumbnailerClass
61 {
62   TumblerAbstractThumbnailerClass __parent__;
63 };
64 
65 struct _DesktopThumbnailer
66 {
67   TumblerAbstractThumbnailer __parent__;
68 
69   gchar *exec;
70 };
71 
72 
73 
74 G_DEFINE_DYNAMIC_TYPE (DesktopThumbnailer,
75                        desktop_thumbnailer,
76                        TUMBLER_TYPE_ABSTRACT_THUMBNAILER);
77 
78 
79 enum
80 {
81   PROP_0,
82   PROP_EXEC
83 };
84 
85 
86 
87 void
desktop_thumbnailer_register(TumblerProviderPlugin * plugin)88 desktop_thumbnailer_register (TumblerProviderPlugin *plugin)
89 {
90   desktop_thumbnailer_register_type (G_TYPE_MODULE (plugin));
91 }
92 
93 
94 
desktop_thumbnailer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)95 static void desktop_thumbnailer_get_property(GObject *object,
96                                              guint prop_id,
97                                              GValue *value,
98                                              GParamSpec *pspec)
99 {
100   DesktopThumbnailer *thumbnailer = DESKTOP_THUMBNAILER(object);
101 
102   switch(prop_id)
103     {
104     case PROP_EXEC:
105       g_value_set_string(value, thumbnailer->exec);
106       break;
107 
108     default:
109       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
110       break;
111     }
112 }
113 
114 
115 
desktop_thumbnailer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)116 static void desktop_thumbnailer_set_property(GObject *object,
117                                              guint prop_id,
118                                              const GValue *value,
119                                              GParamSpec *pspec)
120 {
121   DesktopThumbnailer *thumbnailer = DESKTOP_THUMBNAILER(object);
122 
123   switch(prop_id)
124     {
125     case PROP_EXEC:
126       thumbnailer->exec = g_strdup(g_value_get_string(value));
127       break;
128 
129     default:
130       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
131       break;
132     }
133 }
134 
135 
136 
137 static void
desktop_thumbnailer_class_init(DesktopThumbnailerClass * klass)138 desktop_thumbnailer_class_init (DesktopThumbnailerClass *klass)
139 {
140   TumblerAbstractThumbnailerClass *abstractthumbnailer_class;
141   GObjectClass                    *gobject_class;
142 
143   gobject_class = G_OBJECT_CLASS(klass);
144 
145   gobject_class->get_property = desktop_thumbnailer_get_property;
146   gobject_class->set_property = desktop_thumbnailer_set_property;
147 
148   g_object_class_install_property (gobject_class,
149                                    PROP_EXEC,
150                                    g_param_spec_string("exec",
151                                                        NULL,
152                                                        NULL,
153                                                        NULL,
154                                                        G_PARAM_CONSTRUCT_ONLY |
155                                                        G_PARAM_READWRITE));
156 
157   gobject_class->finalize = desktop_thumbnailer_finalize;
158   abstractthumbnailer_class = TUMBLER_ABSTRACT_THUMBNAILER_CLASS (klass);
159   abstractthumbnailer_class->create = desktop_thumbnailer_create;
160 }
161 
162 
163 
164 static void
desktop_thumbnailer_class_finalize(DesktopThumbnailerClass * klass)165 desktop_thumbnailer_class_finalize (DesktopThumbnailerClass *klass)
166 {
167 }
168 
169 
170 static void
desktop_thumbnailer_finalize(GObject * object)171 desktop_thumbnailer_finalize(GObject *object)
172 {
173 
174 }
175 
176 static void
desktop_thumbnailer_init(DesktopThumbnailer * thumbnailer)177 desktop_thumbnailer_init (DesktopThumbnailer *thumbnailer)
178 {
179 }
180 
181 
182 static void
te_string_append_quoted(GString * string,const gchar * unquoted)183 te_string_append_quoted (GString     *string,
184                          const gchar *unquoted)
185 {
186   gchar *quoted;
187 
188   quoted = g_shell_quote (unquoted);
189   g_string_append (string, quoted);
190   g_free (quoted);
191 }
192 
193 
194 
195 static GdkPixbuf *
desktop_thumbnailer_get_pixbuf(GInputStream * stream,int dest_width,int dest_height,GCancellable * cancellable)196 desktop_thumbnailer_get_pixbuf (GInputStream *stream,
197                                 int dest_width,
198                                 int dest_height,
199                                 GCancellable *cancellable)
200 {
201   GdkPixbuf *source;
202   gdouble    hratio;
203   gdouble    wratio;
204   gint       source_width;
205   gint       source_height;
206   GError    *error = NULL;
207 
208   source = gdk_pixbuf_new_from_stream (stream, cancellable, &error);
209 
210   if (!source)
211     {
212       g_clear_error (&error);
213       return NULL;
214     }
215 
216   /* determine the source pixbuf dimensions */
217   source_width = gdk_pixbuf_get_width (source);
218   source_height = gdk_pixbuf_get_height (source);
219 
220 
221   /* return the same pixbuf if no scaling is required */
222   if (source_width <= dest_width && source_height <= dest_height)
223     return source;
224 
225   /* determine which axis needs to be scaled down more */
226   wratio = (gdouble) source_width / (gdouble) dest_width;
227   hratio = (gdouble) source_height / (gdouble) dest_height;
228 
229   /* adjust the other axis */
230   if (hratio > wratio)
231     dest_width = rint (source_width / hratio);
232   else
233     dest_height = rint (source_height / wratio);
234 
235   /* scale the pixbuf down to the desired size */
236   return gdk_pixbuf_scale_simple (source,
237                                   MAX (dest_width, 1), MAX (dest_height, 1),
238                                   GDK_INTERP_BILINEAR);
239 
240 }
241 
242 
243 
244 static gboolean
desktop_thumbnailer_exec_parse(const gchar * exec,const gchar * file_path,const gchar * file_uri,gint desired_size,const gchar * output_file,gint * argc,gchar *** argv,GError ** error)245 desktop_thumbnailer_exec_parse (const gchar *exec,
246                                 const gchar *file_path,
247                                 const gchar *file_uri,
248                                 gint         desired_size,
249                                 const gchar *output_file,
250                                 gint        *argc,
251                                 gchar     ***argv,
252                                 GError     **error)
253 {
254   const gchar *p;
255   gboolean     result = FALSE;
256   GString     *command_line = g_string_new (NULL);
257 
258   for (p = exec; *p != '\0'; ++p)
259     {
260       if (p[0] == '%' && p[1] != '\0')
261         {
262           switch (*++p)
263             {
264             case 'u':
265               if (G_LIKELY (file_uri != NULL))
266                 te_string_append_quoted (command_line, file_uri);
267               break;
268 
269             case 'i':
270               if (G_LIKELY (file_path != NULL))
271                 te_string_append_quoted (command_line, file_path);
272               break;
273 
274             case 's':
275               g_string_append_printf(command_line, "%d", desired_size);
276               break;
277 
278             case 'o':
279               if (G_LIKELY (output_file != NULL))
280                 te_string_append_quoted (command_line, output_file);
281               break;
282 
283             case '%':
284               g_string_append_c (command_line, '%');
285               break;
286             }
287         }
288       else
289         {
290           g_string_append_c (command_line, *p);
291         }
292     }
293 
294   result = g_shell_parse_argv (command_line->str, argc, argv, error);
295 
296   g_string_free (command_line, TRUE);
297   return result;
298 }
299 
300 
301 
302 static GdkPixbuf *
desktop_thumbnailer_load_thumbnail(DesktopThumbnailer * thumbnailer,const gchar * uri,const gchar * path,gint width,gint height,GCancellable * cancellable)303 desktop_thumbnailer_load_thumbnail (DesktopThumbnailer *thumbnailer,
304                                     const gchar        *uri,
305                                     const gchar        *path,
306                                     gint                width,
307                                     gint                height,
308                                     GCancellable       *cancellable)
309 {
310   GFileIOStream *stream;
311   GFile     *tmpfile;
312   gchar     *exec;
313   gchar    **cmd_argv;
314   gchar     *tmpfilepath;
315   gboolean   res;
316   gint       cmd_argc;
317   gint       size;
318   gchar     *working_directory = NULL;
319   GdkPixbuf *pixbuf            = NULL;
320   GError    *error             = NULL;
321 
322   g_object_get (G_OBJECT (thumbnailer), "exec", &exec, NULL);
323 
324   tmpfile = g_file_new_tmp ("tumbler-XXXXXXX.png", &stream, NULL);
325 
326   if ( G_LIKELY (tmpfile) )
327     {
328       tmpfilepath = g_file_get_path (tmpfile);
329 
330       size = MIN (width, height);
331 
332       res = desktop_thumbnailer_exec_parse (exec,
333                                             path,
334                                             uri,
335                                             size,
336                                             tmpfilepath,
337                                             &cmd_argc,
338                                             &cmd_argv,
339                                             &error);
340 
341       if (G_LIKELY (res))
342         {
343           working_directory = g_path_get_dirname (path);
344 
345 
346           res = g_spawn_sync (working_directory,
347                               cmd_argv, NULL,
348                               G_SPAWN_SEARCH_PATH,
349                               NULL, NULL,
350                               NULL, NULL,
351                               NULL,
352                               &error);
353 
354           if (G_LIKELY (res))
355             {
356               pixbuf = desktop_thumbnailer_get_pixbuf (g_io_stream_get_input_stream(G_IO_STREAM(stream)),
357                                                        width,
358                                                        height,
359                                                        cancellable);
360               g_unlink (tmpfilepath);
361             }
362 
363           g_free (working_directory);
364           g_strfreev (cmd_argv);
365         }
366       else
367         {
368           g_warning (_("Malformed command line \"%s\": %s"), exec, error->message);
369           g_clear_error (&error);
370         }
371       g_free (tmpfilepath);
372       g_object_unref (tmpfile);
373       g_object_unref (stream);
374     }
375 
376   g_free (exec);
377 
378   return pixbuf;
379 }
380 
381 
382 
383 static void
desktop_thumbnailer_create(TumblerAbstractThumbnailer * thumbnailer,GCancellable * cancellable,TumblerFileInfo * info)384 desktop_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer,
385                             GCancellable               *cancellable,
386                             TumblerFileInfo            *info)
387 {
388 
389   TumblerThumbnail           *thumbnail;
390   TumblerThumbnailFlavor     *flavor;
391   TumblerImageData            data;
392   const gchar                *uri;
393   gchar                      *path;
394   GFile                      *file;
395   gint                        height;
396   gint                        width;
397   GError                     *error  =  NULL;
398   GdkPixbuf                  *pixbuf =  NULL;
399 
400   g_return_if_fail (IS_DESKTOP_THUMBNAILER (thumbnailer));
401   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
402   g_return_if_fail (TUMBLER_IS_FILE_INFO (info));
403 
404   /* do nothing if cancelled */
405   if (g_cancellable_is_cancelled (cancellable))
406     return;
407 
408   uri = tumbler_file_info_get_uri (info);
409 
410   file = g_file_new_for_uri (uri);
411   path = g_file_get_path (file);
412 
413   thumbnail = tumbler_file_info_get_thumbnail (info);
414   g_assert (thumbnail != NULL);
415 
416   flavor = tumbler_thumbnail_get_flavor (thumbnail);
417   g_assert (flavor != NULL);
418 
419   tumbler_thumbnail_flavor_get_size (flavor, &width, &height);
420 
421   pixbuf = desktop_thumbnailer_load_thumbnail (DESKTOP_THUMBNAILER(thumbnailer), uri, path, width, height, cancellable);
422 
423   if (pixbuf != NULL)
424     {
425       data.data = gdk_pixbuf_get_pixels (pixbuf);
426       data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
427       data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf);
428       data.width = gdk_pixbuf_get_width (pixbuf);
429       data.height = gdk_pixbuf_get_height (pixbuf);
430       data.rowstride = gdk_pixbuf_get_rowstride (pixbuf);
431       data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf);
432 
433       tumbler_thumbnail_save_image_data (thumbnail, &data,
434                                          tumbler_file_info_get_mtime (info),
435                                          NULL, &error);
436 
437       g_object_unref (pixbuf);
438     }
439 
440   if (error != NULL)
441     {
442       g_signal_emit_by_name (thumbnailer, "error", uri, error->code, error->message);
443       g_error_free (error);
444     }
445   else
446     {
447       g_signal_emit_by_name (thumbnailer, "ready", uri);
448     }
449 
450   g_free (path);
451 
452   g_object_unref (thumbnail);
453   g_object_unref (flavor);
454   g_object_unref (file);
455 }
456