1 /*
2  *      fm-templates.c
3  *
4  *      Copyright 2012-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
5  *      Copyright 2016 Mamoru TASAKA <mtasaka@fedoraproject.org>
6  *
7  *      This file is a part of the Libfm library.
8  *
9  *      This library is free software; you can redistribute it and/or
10  *      modify it under the terms of the GNU Lesser General Public
11  *      License as published by the Free Software Foundation; either
12  *      version 2.1 of the License, or (at your option) any later version.
13  *
14  *      This library is distributed in the hope that it will be useful,
15  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *      Lesser General Public License for more details.
18  *
19  *      You should have received a copy of the GNU Lesser General Public
20  *      License along with this library; if not, write to the Free Software
21  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23 
24 /**
25  * SECTION:fm-templates
26  * @short_description: Templates for new files creation.
27  * @title: FmTemplate
28  *
29  * @include: libfm/fm.h
30  *
31  * The #FmTemplate object represents description which files was set for
32  * creation and how those files should be created - that includes custom
33  * prompt, file name template, and template contents.
34  */
35 
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>
38 #endif
39 
40 #include <glib.h>
41 #include <glib/gi18n-lib.h>
42 #include <string.h>
43 
44 #include "fm-templates.h"
45 #include "fm-monitor.h"
46 #include "fm-dir-list-job.h"
47 #include "fm-config.h"
48 #include "fm-folder.h"
49 
50 typedef struct _FmTemplateFile  FmTemplateFile;
51 typedef struct _FmTemplateDir   FmTemplateDir;
52 
53 struct _FmTemplate
54 {
55     GObject parent;
56     FmTemplateFile *files; /* not referenced, it references instead */
57     FmMimeType *mime_type;
58     FmPath *template_file;
59     FmIcon *icon;
60     gchar *command;
61     gchar *prompt;
62     gchar *label;
63 };
64 
65 struct _FmTemplateClass
66 {
67     GObjectClass parent;
68 };
69 
70 
71 static void fm_template_finalize(GObject *object);
72 
73 G_DEFINE_TYPE(FmTemplate, fm_template, G_TYPE_OBJECT);
74 
fm_template_class_init(FmTemplateClass * klass)75 static void fm_template_class_init(FmTemplateClass *klass)
76 {
77     GObjectClass *g_object_class;
78 
79     g_object_class = G_OBJECT_CLASS(klass);
80     g_object_class->finalize = fm_template_finalize;
81 }
82 
83 static GList *templates = NULL; /* in appearance reversed order */
84 G_LOCK_DEFINE_STATIC(templates);
85 
fm_template_finalize(GObject * object)86 static void fm_template_finalize(GObject *object)
87 {
88     FmTemplate *self;
89 
90     g_return_if_fail(FM_IS_TEMPLATE(object));
91     self = (FmTemplate*)object;
92     if(self->files)
93         g_error("template reference failure");
94     fm_mime_type_unref(self->mime_type);
95     if(self->template_file)
96         fm_path_unref(self->template_file);
97     if(self->icon)
98         g_object_unref(self->icon);
99     g_free(self->command);
100     g_free(self->prompt);
101     g_free(self->label);
102 
103     G_OBJECT_CLASS(fm_template_parent_class)->finalize(object);
104 }
105 
fm_template_init(FmTemplate * self)106 static void fm_template_init(FmTemplate *self)
107 {
108 }
109 
fm_template_new(void)110 static FmTemplate* fm_template_new(void)
111 {
112     return (FmTemplate*)g_object_new(FM_TEMPLATE_TYPE, NULL);
113 }
114 
115 
116 struct _FmTemplateFile
117 {
118     /* using 'built-in' GList/GSList for speed and less memory consuming */
119     FmTemplateFile *next_in_dir;
120     FmTemplateFile *prev_in_dir;
121     FmTemplateDir *dir;
122     FmTemplateFile *next_in_templ; /* in priority-less order */
123     /* referenced */
124     FmTemplate *templ;
125     FmPath *path;
126     gboolean is_desktop_entry : 1;
127     gboolean inactive : 1;
128 };
129 
130 struct _FmTemplateDir
131 {
132     /* using 'built-in' GSList for speed and less memory consuming */
133     FmTemplateDir *next;
134     FmTemplateFile *files;
135     /* referenced */
136     FmPath *path;
137     GFileMonitor *monitor;
138     gboolean user_dir : 1;
139 };
140 
141 /* allocated once, on init */
142 static FmTemplateDir *templates_dirs = NULL;
143 
144 
145 /* determine mime type for the template
146    using just fm_mime_type_* for this isn't appropriate because
147    we need completely another guessing for templates content */
_fm_template_guess_mime_type(FmPath * path,FmMimeType * mime_type,FmPath ** tpath)148 static FmMimeType *_fm_template_guess_mime_type(FmPath *path, FmMimeType *mime_type,
149                                                 FmPath **tpath)
150 {
151     const gchar *basename = fm_path_get_basename(path);
152     FmPath *subpath = NULL;
153     gchar *filename, *type, *url;
154     GKeyFile *kf;
155 
156     /* SF bug #902: if file was deleted instantly we get NULL here */
157     if (mime_type == NULL)
158         return NULL;
159 
160     /* if file is desktop entry then find the real template file path */
161     if(mime_type != _fm_mime_type_get_application_x_desktop())
162     {
163         if(tpath)
164             *tpath = fm_path_ref(path);
165         return fm_mime_type_ref(mime_type);
166     }
167     /* parse file to find mime type */
168     kf = g_key_file_new();
169     filename = fm_path_to_str(path);
170     type = NULL;
171     if(g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, NULL))
172     {
173         type = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
174                                      G_KEY_FILE_DESKTOP_KEY_TYPE, NULL);
175         if(type && strcmp(type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) == 0)
176         {
177             /* Type=Application, assume it's just file */
178             g_key_file_free(kf);
179             g_free(filename);
180             g_free(type);
181             if(tpath)
182                 *tpath = fm_path_ref(path);
183             return fm_mime_type_ref(_fm_mime_type_get_application_x_desktop());
184         }
185         if(!type || strcmp(type, G_KEY_FILE_DESKTOP_TYPE_LINK) != 0)
186         {
187             /* desktop entry file invalid as template */
188             g_key_file_free(kf);
189             g_free(filename);
190             g_free(type);
191             return NULL;
192         }
193         g_free(type);
194         /* valid template should have 'URL' key */
195         url = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
196                                     G_KEY_FILE_DESKTOP_KEY_URL, NULL);
197         if(url)
198         {
199             if(G_UNLIKELY(url[0] == '/')) /* absolute path */
200                 subpath = fm_path_new_for_path(url);
201             else if(G_UNLIKELY(strchr(url, ':'))) /* URI (huh?) */
202                 subpath = fm_path_new_for_uri(url);
203             else /* path relative to directory containing file */
204                 subpath = fm_path_new_relative(fm_path_get_parent(path), url);
205             path = subpath; /* shift to new path */
206             basename = fm_path_get_basename(path);
207             g_free(url);
208         }
209         else
210         {
211             g_key_file_free(kf);
212             g_free(filename);
213             return NULL; /* invalid template file */
214         }
215         /* some templates may have 'MimeType' key */
216         type = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
217                                      G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL);
218         if(type)
219         {
220             if(tpath)
221                 *tpath = subpath;
222             else
223                 fm_path_unref(subpath);
224             g_key_file_free(kf);
225             g_free(filename);
226             mime_type = fm_mime_type_from_name(type);
227             g_free(type);
228             return mime_type;
229         }
230     }
231     /* so we have real template file now, guess from file content first */
232     mime_type = NULL;
233     g_free(filename);
234     filename = fm_path_to_str(path);
235     /* type is NULL still */
236     mime_type = fm_mime_type_from_native_file(filename, basename, NULL);
237     if(mime_type == _fm_mime_type_get_application_x_desktop())
238     {
239         /* template file is an entry */
240         kf = g_key_file_new();
241         if(g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, NULL))
242         {
243             type = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
244                                          G_KEY_FILE_DESKTOP_KEY_TYPE, NULL);
245             if(type)
246             {
247                 /* TODO: we support only 'Application' type for now */
248                 if(strcmp(type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) == 0)
249                     ;
250                 else
251                 {
252                     /* desktop entry file invalid as template */
253                     g_key_file_free(kf);
254                     g_free(filename);
255                     if(subpath)
256                         fm_path_unref(subpath);
257                     g_free(type);
258                     fm_mime_type_unref(mime_type);
259                     return NULL;
260                 }
261                 g_free(type);
262             }
263         }
264         g_key_file_free(kf);
265     }
266     /* we got a desktop entry link to not existing file, guess from file name */
267     if(!mime_type)
268     {
269         gboolean uncertain;
270         type = g_content_type_guess(basename, NULL, 0, &uncertain);
271         if(type && !uncertain)
272             mime_type = fm_mime_type_from_name(type);
273         g_free(type);
274         /* ignore unrecognizable files instead of using application/octet-stream */
275     }
276     g_free(filename);
277     if(tpath)
278         *tpath = subpath;
279     else if(subpath)
280         fm_path_unref(subpath);
281     return mime_type;
282 }
283 
284 /* find or create new FmTemplate */
285 /* requires lock held */
_fm_template_find_for_file(FmPath * path,FmMimeType * mime_type)286 static FmTemplate *_fm_template_find_for_file(FmPath *path, FmMimeType *mime_type)
287 {
288     GList *l;
289     FmTemplate *templ;
290     FmPath *tpath = NULL;
291     gboolean template_type_once = fm_config->template_type_once;
292 
293     if(template_type_once)
294         mime_type = _fm_template_guess_mime_type(path, mime_type, NULL);
295     else
296         mime_type = _fm_template_guess_mime_type(path, mime_type, &tpath);
297     if(!mime_type)
298     {
299         /* g_debug("could not guess MIME type for template %s, ignoring it",
300                 fm_path_get_basename(path)); */
301         if(tpath)
302             fm_path_unref(tpath);
303         return NULL;
304     }
305     if(!template_type_once && !tpath)
306     {
307         /* g_debug("no basename defined in template %s, ignoring it",
308                 fm_path_get_basename(path)); */
309         fm_mime_type_unref(mime_type);
310         return NULL;
311     }
312     for(l = templates; l; l = l->next)
313     {
314         templ = l->data;
315         if(!template_type_once)
316         {
317             if(strcmp(fm_path_get_basename(templ->template_file),
318                       fm_path_get_basename(tpath)) == 0)
319             {
320                 g_object_ref(templ);
321                 fm_mime_type_unref(mime_type);
322                 fm_path_unref(tpath);
323                 return templ;
324             }
325         }
326         else if(templ->mime_type == mime_type)
327         {
328             g_object_ref(templ);
329             fm_mime_type_unref(mime_type);
330             return templ;
331         }
332     }
333     templ = fm_template_new();
334     templ->mime_type = mime_type;
335     templ->template_file = tpath;
336     templates = g_list_prepend(templates, g_object_ref(templ));
337     return templ;
338 }
339 
340 /* requires lock held */
_fm_template_update_from_file(FmTemplate * templ,FmTemplateFile * file)341 static void _fm_template_update_from_file(FmTemplate *templ, FmTemplateFile *file)
342 {
343     if(file == NULL)
344         return;
345     /* update from less relevant file first */
346     _fm_template_update_from_file(templ, file->next_in_templ);
347     if(file->is_desktop_entry) /* desktop entry */
348     {
349         GKeyFile *kf = g_key_file_new();
350         char *filename = fm_path_to_str(file->path);
351         char *tmp;
352         GError *error = NULL;
353         gboolean hidden;
354 
355         if(g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, &error))
356         {
357             hidden = g_key_file_get_boolean(kf, G_KEY_FILE_DESKTOP_GROUP,
358                                             G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL);
359             file->inactive = hidden;
360             /* g_debug("%s hidden: %d", filename, hidden); */
361             /* FIXME: test for G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN ? */
362             if(!hidden)
363             {
364                 tmp = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
365                                             G_KEY_FILE_DESKTOP_KEY_TYPE, NULL);
366                 if(!tmp || strcmp(tmp, G_KEY_FILE_DESKTOP_TYPE_LINK) != 0)
367                 {
368                     /* it seems it's just Application template */
369                     g_key_file_free(kf);
370                     g_free(filename);
371                     g_free(tmp);
372                     if(!templ->template_file)
373                         templ->template_file = fm_path_ref(file->path);
374                     return;
375                 }
376                 g_free(tmp);
377                 tmp = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
378                                             G_KEY_FILE_DESKTOP_KEY_URL, NULL);
379                 if(tmp)
380                 {
381                     if(templ->template_file)
382                         fm_path_unref(templ->template_file);
383                     if(G_UNLIKELY(tmp[0] == '/')) /* absolute path */
384                         templ->template_file = fm_path_new_for_path(tmp);
385                     else if(G_UNLIKELY(strchr(tmp, ':'))) /* URI (huh?) */
386                         templ->template_file = fm_path_new_for_uri(tmp);
387                     else /* path relative to directory containing file */
388                         templ->template_file = fm_path_new_relative(file->dir->path, tmp);
389                     g_free(tmp);
390                 }
391                 tmp = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
392                                             G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
393                 if(tmp)
394                 {
395                     if(templ->icon)
396                         g_object_unref(templ->icon);
397                     templ->icon = fm_icon_from_name(tmp);
398                     g_free(tmp);
399                 }
400                 tmp = g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP,
401                                             G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
402                 if(tmp)
403                 {
404                     g_free(templ->command);
405                     templ->command = tmp;
406                 }
407                 tmp = g_key_file_get_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP,
408                                             G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
409                 if(tmp)
410                 {
411                     g_free(templ->label);
412                     templ->label = tmp;
413                 }
414                 tmp = g_key_file_get_locale_string(kf, G_KEY_FILE_DESKTOP_GROUP,
415                                             G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL);
416                 if(tmp)
417                 {
418                     g_free(templ->prompt);
419                     templ->prompt = tmp;
420                 }
421                 /* FIXME: forge prompt from 'Name' if not set yet? */
422             }
423         }
424         else
425         {
426             g_warning("problem loading template %s: %s", filename, error->message);
427             g_error_free(error);
428             file->inactive = TRUE;
429         }
430         g_key_file_free(kf);
431         g_free(filename);
432     }
433     else /* plain file */
434     {
435         if(!templ->template_file)
436             templ->template_file = fm_path_ref(file->path);
437         file->inactive = FALSE;
438     }
439 }
440 
441 /* recreate data in FmTemplate from entries */
442 /* requires lock held */
_fm_template_update(FmTemplate * templ)443 static FmTemplate *_fm_template_update(FmTemplate *templ)
444 {
445     GList *l;
446     FmTemplate *new_templ;
447     FmTemplateFile *file;
448 
449     if(templ->files == NULL) /* template is empty now */
450     {
451         templates = g_list_remove(templates, templ);
452         g_object_unref(templ); /* we removed it from list so drop reference */
453         return NULL;
454     }
455     l = g_list_find(templates, templ);
456     if(l == NULL)
457         g_error("FmTemplate not found in list");
458     /* isolate and unref old template */
459     l->data = new_templ = fm_template_new(); /* reference is bound to list */
460     new_templ->mime_type = fm_mime_type_ref(templ->mime_type);
461     new_templ->files = templ->files;
462     templ->files = NULL;
463     for(file = new_templ->files; file; file = file->next_in_templ)
464     {
465         file->templ = g_object_ref(new_templ);
466         g_object_unref(templ);
467     }
468     /* update from file list */
469     _fm_template_update_from_file(new_templ, new_templ->files);
470     /* set template if it's not set, sorting by name requires it */
471     if(!new_templ->template_file && templ->template_file && !fm_config->template_type_once)
472         new_templ->template_file = fm_path_ref(templ->template_file);
473     g_object_unref(templ); /* we removed it from list so drop reference */
474     return new_templ;
475 }
476 
477 /* add file into FmTemplate */
478 /* requires lock held */
_fm_template_insert_sorted(FmTemplate * templ,FmTemplateFile * file)479 static void _fm_template_insert_sorted(FmTemplate *templ, FmTemplateFile *file)
480 {
481     FmTemplateDir *last_dir = templates_dirs;
482     FmTemplateFile *last = NULL, *next;
483 
484     for(next = templ->files; next; next = next->next_in_templ)
485     {
486         while(last_dir && last_dir != file->dir && last_dir != next->dir)
487             last_dir = last_dir->next;
488         g_assert(last_dir != NULL); /* it must be corruption otherwise */
489         if(last_dir == file->dir)
490         {
491             if(next->dir == last_dir && !file->is_desktop_entry)
492             {
493                 if(!next->is_desktop_entry)
494                     break;
495                 /* sort files after desktop items */
496             }
497             else
498                 break;
499         }
500         last = next;
501     }
502     file->next_in_templ = next;
503     if(last)
504         last->next_in_templ = file;
505     else
506         templ->files = file;
507 }
508 
509 /* delete file from FmTemplate and free it */
510 /* requires lock held */
_fm_template_file_free(FmTemplate * templ,FmTemplateFile * file,gboolean do_update)511 static void _fm_template_file_free(FmTemplate *templ, FmTemplateFile *file,
512                                    gboolean do_update)
513 {
514     FmTemplateFile *file2 = templ->files;
515 
516     if(file2 == file)
517         templ->files = file->next_in_templ;
518     else while(file2)
519     {
520         if(file2->next_in_templ == file)
521         {
522             file2->next_in_templ = file->next_in_templ;
523             break;
524         }
525         file2 = file2->next_in_templ;
526     }
527     if(!file2)
528         g_critical("FmTemplate: file being freed is missed in template");
529     g_object_unref(templ);
530     fm_path_unref(file->path);
531     g_slice_free(FmTemplateFile, file);
532     if(do_update)
533         _fm_template_update(templ);
534 }
535 
on_job_finished(FmJob * job,FmTemplateDir * dir)536 static void on_job_finished(FmJob *job, FmTemplateDir *dir)
537 {
538     GList *file_infos, *l;
539     FmFileInfo *fi;
540     FmPath *path;
541     FmTemplateFile *file;
542     FmTemplate *templ;
543 
544     g_signal_handlers_disconnect_by_func(job, on_job_finished, dir);
545     file_infos = fm_file_info_list_peek_head_link(fm_dir_list_job_get_files(FM_DIR_LIST_JOB(job)));
546     for(l = file_infos; l; l = l->next)
547     {
548         fi = l->data;
549         if(fm_file_info_is_hidden(fi) || fm_file_info_is_backup(fi))
550             continue;
551         path = fm_file_info_get_path(fi);
552         G_LOCK(templates);
553         for(file = dir->files; file; file = file->next_in_dir)
554             if(fm_path_equal(path, file->path))
555                 break;
556         G_UNLOCK(templates);
557         if(file) /* it's duplicate */
558             continue;
559         /* ensure the path is based on dir->path */
560         path = fm_path_new_child(dir->path, fm_path_get_basename(path));
561         G_LOCK(templates);
562         templ = _fm_template_find_for_file(path, fm_file_info_get_mime_type(fi));
563         G_UNLOCK(templates);
564         if(!templ) /* mime type guessing error */
565         {
566             fm_path_unref(path);
567             continue;
568         }
569         file = g_slice_new(FmTemplateFile);
570         file->templ = templ;
571         file->path = path;
572         file->is_desktop_entry = fm_file_info_is_desktop_entry(fi);
573         file->dir = dir;
574         G_LOCK(templates);
575         file->next_in_dir = dir->files;
576         file->prev_in_dir = NULL;
577         if(dir->files)
578             dir->files->prev_in_dir = file;
579         dir->files = file;
580         _fm_template_insert_sorted(templ, file);
581         _fm_template_update(templ);
582         G_UNLOCK(templates);
583     }
584 }
585 
on_dir_changed(GFileMonitor * mon,GFile * gf,GFile * other,GFileMonitorEvent evt,FmTemplateDir * dir)586 static void on_dir_changed(GFileMonitor *mon, GFile *gf, GFile *other,
587                            GFileMonitorEvent evt, FmTemplateDir *dir)
588 {
589     GFile *gfile = fm_path_to_gfile(dir->path);
590     char *basename, *pathname;
591     FmTemplateFile *file;
592     FmPath *path;
593     FmTemplate *templ;
594     FmMimeType *mime_type;
595 
596     if(g_file_equal(gf, gfile))
597     {
598         /* it's event on folder itself, ignoring */
599         g_object_unref(gfile);
600         return;
601     }
602     g_object_unref(gfile);
603     switch(evt)
604     {
605     case G_FILE_MONITOR_EVENT_CHANGED:
606         basename = g_file_get_basename(gf);
607         if (basename == NULL)
608             break;
609         G_LOCK(templates);
610         for(file = dir->files; file; file = file->next_in_dir)
611             if(strcmp(fm_path_get_basename(file->path), basename) == 0)
612                 break;
613         g_free(basename);
614         if(file)
615         {
616             if(file->is_desktop_entry) /* we aware only of entries content */
617                 _fm_template_update(file->templ);
618         }
619         else
620             g_warning("templates monitor: change for unknown file");
621         G_UNLOCK(templates);
622         break;
623     case G_FILE_MONITOR_EVENT_DELETED:
624         basename = g_file_get_basename(gf);
625         if (basename == NULL)
626             break;
627         G_LOCK(templates);
628         for(file = dir->files; file; file = file->next_in_dir)
629             if(strcmp(fm_path_get_basename(file->path), basename) == 0)
630                 break;
631         g_free(basename);
632         if(file)
633         {
634             if(file == dir->files)
635                 dir->files = file->next_in_dir;
636             else if(file->prev_in_dir)
637                 file->prev_in_dir->next_in_dir = file->next_in_dir;
638             if(file->next_in_dir)
639                 file->next_in_dir->prev_in_dir = file->prev_in_dir;
640             _fm_template_file_free(file->templ, file, TRUE);
641             G_UNLOCK(templates);
642         }
643         else
644             G_UNLOCK(templates);
645             /* else it is already deleted */
646         break;
647     case G_FILE_MONITOR_EVENT_CREATED:
648         basename = g_file_get_basename(gf);
649         G_LOCK(templates);
650         for(file = dir->files; file; file = file->next_in_dir)
651             if(strcmp(fm_path_get_basename(file->path), basename) == 0)
652                 break;
653         G_UNLOCK(templates);
654         /* NOTE: to query file info is too heavy so do own assumptions */
655         if(!file && basename[0] != '.' && !g_str_has_suffix(basename, "~"))
656         {
657             path = fm_path_new_child(dir->path, basename);
658             pathname = fm_path_to_str(path);
659             mime_type = fm_mime_type_from_native_file(pathname, basename, NULL);
660             g_free(pathname);
661             G_LOCK(templates);
662             templ = _fm_template_find_for_file(path, mime_type);
663             if(templ)
664             {
665                 file = g_slice_new(FmTemplateFile);
666                 file->templ = templ;
667                 file->path = path;
668                 file->is_desktop_entry = (mime_type == _fm_mime_type_get_application_x_desktop());
669                 file->dir = dir;
670                 file->next_in_dir = dir->files;
671                 file->prev_in_dir = NULL;
672                 if (dir->files)
673                     dir->files->prev_in_dir = file;
674                 dir->files = file;
675                 _fm_template_insert_sorted(templ, file);
676                 _fm_template_update(templ);
677                 G_UNLOCK(templates);
678             }
679             else
680             {
681                 G_UNLOCK(templates);
682                 fm_path_unref(path);
683                 g_warning("could not guess type of template %s, ignoring it",
684                           basename);
685             }
686             if (G_LIKELY(mime_type))
687                 fm_mime_type_unref(mime_type);
688         }
689         else
690             g_debug("templates monitor: duplicate file %s", basename);
691         g_free(basename);
692         break;
693 #if GLIB_CHECK_VERSION(2, 24, 0)
694     case G_FILE_MONITOR_EVENT_MOVED:
695 #endif
696     case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
697     case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
698     case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
699     case G_FILE_MONITOR_EVENT_UNMOUNTED:
700         /* ignore those */
701         break;
702     }
703 }
704 
_template_dir_init(FmTemplateDir * dir,GFile * gf)705 static void _template_dir_init(FmTemplateDir *dir, GFile *gf)
706 {
707     FmDirListJob *job = fm_dir_list_job_new2(dir->path, FM_DIR_LIST_JOB_FAST);
708     GError *error = NULL;
709 
710     dir->files = NULL;
711     g_signal_connect(job, "finished", G_CALLBACK(on_job_finished), dir);
712     if(!fm_job_run_async(FM_JOB(job)))
713         g_signal_handlers_disconnect_by_func(job, on_job_finished, dir);
714     dir->monitor = fm_monitor_directory(gf, &error);
715     if(dir->monitor)
716         g_signal_connect(dir->monitor, "changed", G_CALLBACK(on_dir_changed), dir);
717     else
718     {
719         g_debug("file monitor cannot be created: %s", error->message);
720         g_error_free(error);
721     }
722     g_object_unref(job);
723 }
724 
on_once_type_changed(FmConfig * cfg,gpointer unused)725 static void on_once_type_changed(FmConfig *cfg, gpointer unused)
726 {
727     GList *l, *old_list;
728     FmTemplateFile *file;
729     FmTemplate *templ;
730 
731     G_LOCK(templates);
732     /* rebuild templates list from known files */
733     old_list = templates;
734     templates = NULL;
735     for(l = old_list; l; l = l->next)
736     {
737         templ = l->data;
738         while((file = templ->files))
739         {
740             templ->files = file->next_in_templ;
741             file->templ = _fm_template_find_for_file(file->path, templ->mime_type);
742             _fm_template_insert_sorted(file->templ, file);
743             g_object_unref(templ); /* for a removed file */
744         }
745         g_object_unref(templ); /* for removing from list */
746     }
747     g_list_free(old_list);
748     /* update all templates now */
749     g_list_foreach(templates, (GFunc)_fm_template_update, NULL);
750     G_UNLOCK(templates);
751 }
752 
_fm_templates_init(void)753 void _fm_templates_init(void)
754 {
755     const gchar * const *data_dirs = g_get_system_data_dirs();
756     const gchar * const *data_dir;
757     const gchar *dir_name;
758     FmTemplateDir *dir = NULL;
759     GFile *parent, *gfile;
760 
761     if(templates_dirs)
762         return; /* someone called us again? */
763     /* prepare list of system template directories */
764     for(data_dir = data_dirs; *data_dir; ++data_dir)
765     {
766         parent = g_file_new_for_path(*data_dir);
767         gfile = g_file_get_child(parent, "templates");
768         g_object_unref(parent);
769         if(g_file_query_exists(gfile, NULL))
770         {
771             if(G_LIKELY(dir))
772             {
773                 dir->next = g_slice_new(FmTemplateDir);
774                 dir = dir->next;
775             }
776             else
777                 templates_dirs = dir = g_slice_new(FmTemplateDir);
778             dir->path = fm_path_new_for_gfile(gfile);
779             dir->user_dir = FALSE;
780             _template_dir_init(dir, gfile);
781         }
782         g_object_unref(gfile);
783     }
784     if(G_LIKELY(dir))
785         dir->next = NULL;
786     /* add templates dir in user data */
787     dir = g_slice_new(FmTemplateDir);
788     dir->next = templates_dirs;
789     templates_dirs = dir;
790     parent = g_file_new_for_path(g_get_user_data_dir());
791     gfile = g_file_get_child(parent, "templates");
792     g_object_unref(parent);
793     dir->path = fm_path_new_for_gfile(gfile);
794     dir->user_dir = TRUE;
795     /* FIXME: create it if it doesn't exist? */
796     if(g_file_query_exists(gfile, NULL))
797         _template_dir_init(dir, gfile);
798     else
799     {
800         dir->files = NULL;
801         dir->monitor = NULL;
802     }
803     g_object_unref(gfile);
804     /* add XDG_TEMPLATES_DIR at last */
805     dir = g_slice_new(FmTemplateDir);
806     dir->next = templates_dirs;
807     templates_dirs = dir;
808     dir_name = g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES);
809     if(dir_name)
810         dir->path = fm_path_new_for_path(dir_name);
811     else
812         dir->path = fm_path_new_child(fm_path_get_home(), "Templates");
813     dir->user_dir = TRUE;
814     gfile = fm_path_to_gfile(dir->path);
815     if(!g_file_query_exists(gfile, NULL))
816     {
817         g_warning("The directory '%s' doesn't exist, ignoring it",
818                   dir_name ? dir_name : "~/Templates");
819         goto _skip_templates_dir;
820     }
821     if (dir->path == fm_path_get_home() || dir->path == fm_path_get_root())
822     {
823         /* $HOME or / are invalid templates paths so just ignore */
824         g_warning("XDG_TEMPLATES_DIR is set to invalid path, ignoring it");
825 _skip_templates_dir:
826         dir->files = NULL;
827         dir->monitor = NULL;
828     }
829     else
830         _template_dir_init(dir, gfile);
831     g_object_unref(gfile);
832     /* jobs will fill list of files async */
833     g_signal_connect(fm_config, "changed::template_type_once",
834                      G_CALLBACK(on_once_type_changed), NULL);
835 }
836 
_fm_templates_finalize(void)837 void _fm_templates_finalize(void)
838 {
839     FmTemplateDir *dir;
840     FmTemplateFile *file;
841 
842     g_signal_handlers_disconnect_by_func(fm_config, on_once_type_changed, NULL);
843     while(templates_dirs)
844     {
845         dir = templates_dirs;
846         templates_dirs = dir->next;
847         fm_path_unref(dir->path);
848         if(dir->monitor)
849         {
850             g_signal_handlers_disconnect_by_func(dir->monitor, on_dir_changed, dir);
851             g_object_unref(dir->monitor);
852         }
853         while(dir->files)
854         {
855             file = dir->files;
856             dir->files = file->next_in_dir;
857             if(dir->files)
858                 dir->files->prev_in_dir = NULL;
859             _fm_template_file_free(file->templ, file, FALSE);
860         }
861         g_slice_free(FmTemplateDir, dir);
862     }
863     g_list_foreach(templates, (GFunc)g_object_unref, NULL);
864     g_list_free(templates);
865     templates = NULL;
866 }
867 
868 /**
869  * fm_template_list_all
870  * @user_only: %TRUE to ignore system templates
871  *
872  * Retrieves list of all templates. Returned data should be freed after
873  * usage with g_list_free_full(list, g_object_unref).
874  *
875  * Returns: (transfer full) (element-type FmTemplate): list of all known templates.
876  *
877  * Since: 1.2.0
878  */
fm_template_list_all(gboolean user_only)879 GList *fm_template_list_all(gboolean user_only)
880 {
881     GList *list = NULL, *l;
882 
883     G_LOCK(templates);
884     for(l = templates; l; l = l->next)
885         if(!((FmTemplate*)l->data)->files->inactive &&
886            (!user_only || ((FmTemplate*)l->data)->files->dir->user_dir))
887             list = g_list_prepend(list, g_object_ref(l->data));
888     G_UNLOCK(templates);
889     return list;
890 }
891 
892 /**
893  * fm_template_get_name
894  * @templ: a template descriptor
895  * @nlen: (allow-none): location to get template name length
896  *
897  * Retrieves file name template for @templ. If @nlen isn't %NULL then it
898  * will receive length of file name template without suffix (in characters).
899  * Returned data are owned by @templ and should be not freed by caller.
900  *
901  * Returns: (transfer none): file name template.
902  *
903  * Since: 1.2.0
904  */
fm_template_get_name(FmTemplate * templ,gint * nlen)905 const gchar *fm_template_get_name(FmTemplate *templ, gint *nlen)
906 {
907     const gchar *name;
908 
909     name = templ->template_file ? fm_path_get_basename(templ->template_file) : NULL;
910     if(nlen)
911     {
912         char *point;
913         if(!name)
914             *nlen = 0;
915         else if((point = strrchr(name, '.')))
916             *nlen = g_utf8_strlen(name, point - name);
917         else
918             *nlen = g_utf8_strlen(name, -1);
919     }
920     return name;
921 }
922 
923 /**
924  * fm_template_get_mime_type
925  * @templ: a template descriptor
926  *
927  * Retrieves MIME type descriptor for @templ. Returned data are owned by
928  * @templ and should be not freed by caller.
929  *
930  * Returns: (transfer none): mime type descriptor.
931  *
932  * Since: 1.2.0
933  */
fm_template_get_mime_type(FmTemplate * templ)934 FmMimeType *fm_template_get_mime_type(FmTemplate *templ)
935 {
936     return templ->mime_type;
937 }
938 
939 /**
940  * fm_template_get_icon
941  * @templ: a template descriptor
942  *
943  * Retrieves icon defined for @templ. Returned data are owned by @templ
944  * and should be not freed by caller.
945  *
946  * Returns: (transfer none): icon for template.
947  *
948  * Since: 1.2.0
949  */
fm_template_get_icon(FmTemplate * templ)950 FmIcon *fm_template_get_icon(FmTemplate *templ)
951 {
952     if(templ->icon)
953         return templ->icon;
954     return fm_mime_type_get_icon(templ->mime_type);
955 }
956 
957 /**
958  * fm_template_get_prompt
959  * @templ: a template descriptor
960  *
961  * Retrieves prompt for @templ. It can be used as label in entry for the
962  * desired name. If no prompt is defined then returns %NULL. Returned
963  * data are owned by @templ and should be not freed by caller.
964  *
965  * Returns: (transfer none): file prompt.
966  *
967  * Since: 1.2.0
968  */
fm_template_get_prompt(FmTemplate * templ)969 const gchar *fm_template_get_prompt(FmTemplate *templ)
970 {
971     return templ->prompt;
972 }
973 
974 /**
975  * fm_template_get_label
976  * @templ: a template descriptor
977  *
978  * Retrieves label for @templ. It can be used as label in menu. Returned
979  * data are owned by @templ and should be not freed by caller.
980  *
981  * Returns: (transfer none): template label.
982  *
983  * Since: 1.2.0
984  */
fm_template_get_label(FmTemplate * templ)985 const gchar *fm_template_get_label(FmTemplate *templ)
986 {
987     if(!templ->label && !fm_config->template_type_once && templ->template_file)
988     {
989         /* set label to filename if no MIME types are used */
990         const char *basename = fm_path_get_basename(templ->template_file);
991         const char *tmp = strrchr(basename, '.'); /* strip last suffix */
992         if(tmp)
993             templ->label = g_strndup(basename, tmp - basename);
994         else
995             templ->label = g_strdup(basename);
996     }
997     return templ->label;
998 }
999 
1000 /**
1001  * fm_template_is_directory
1002  * @templ: a template descriptor
1003  *
1004  * Checks if @templ is directory template.
1005  *
1006  * Returns: %TRUE if @templ is directory template.
1007  *
1008  * Since: 1.2.0
1009  */
fm_template_is_directory(FmTemplate * templ)1010 gboolean fm_template_is_directory(FmTemplate *templ)
1011 {
1012     return (templ->mime_type == _fm_mime_type_get_inode_directory());
1013 }
1014 
1015 /**
1016  * fm_template_create_file
1017  * @templ: (allow-none): a template descriptor
1018  * @path: path to file to create
1019  * @error: (allow-none): location to retrieve error
1020  * @run_default: %TRUE to run default application on new file
1021  *
1022  * Tries to create file at @path using rules of creating from @templ.
1023  *
1024  * Returns: %TRUE if file created successfully.
1025  *
1026  * Since: 1.2.0
1027  */
fm_template_create_file(FmTemplate * templ,GFile * path,GError ** error,gboolean run_default)1028 gboolean fm_template_create_file(FmTemplate *templ, GFile *path, GError **error,
1029                                  gboolean run_default)
1030 {
1031     char *command;
1032     GAppInfo *app;
1033     GFile *tfile;
1034     GList *list;
1035     GFileOutputStream *f;
1036     FmPath *fm_path;
1037     FmFolder *fm_folder;
1038     gboolean ret;
1039 
1040     if((templ && !FM_IS_TEMPLATE(templ)) || !G_IS_FILE(path))
1041     {
1042         g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED,
1043                             _("fm_template_create_file: invalid argument"));
1044         return FALSE;
1045     }
1046     tfile = NULL;
1047     if(!templ)
1048         goto _create_empty_file;
1049     if(templ->template_file)
1050     {
1051         command = fm_path_to_str(templ->template_file);
1052         tfile = g_file_new_for_path(command);
1053         g_free(command);
1054     }
1055     /* FIXME: it may block */
1056     if(templ->mime_type == _fm_mime_type_get_inode_directory())
1057     {
1058         if(!g_file_make_directory(path, NULL, error))
1059             return FALSE;
1060     }
1061     else if(!g_file_copy(tfile, path, G_FILE_COPY_TARGET_DEFAULT_PERMS, NULL,
1062                          NULL, NULL, error))
1063     {
1064         if((*error)->domain != G_IO_ERROR || (*error)->code != G_IO_ERROR_NOT_FOUND)
1065         {
1066             /* we ran into problems, application will run into them too
1067                the most probably, so don't try to launch it then */
1068             g_object_unref(tfile);
1069             return FALSE;
1070         }
1071         /* template file not found, it's normal */
1072         g_clear_error(error);
1073         /* create empty file instead */
1074 _create_empty_file:
1075         f = g_file_create(path, G_FILE_CREATE_NONE, NULL, error);
1076         if(!f)
1077         {
1078             if(tfile)
1079                 g_object_unref(tfile);
1080             return FALSE;
1081         }
1082         g_object_unref(f);
1083     }
1084     if(tfile)
1085         g_object_unref(tfile);
1086     fm_path = fm_path_new_for_gfile(path);
1087     fm_folder = fm_folder_find_by_path(fm_path_get_parent(fm_path));
1088     if (!fm_folder || !_fm_folder_event_file_added(fm_folder, fm_path))
1089         fm_path_unref(fm_path);
1090     if (fm_folder)
1091         g_object_unref(fm_folder);
1092     if(!run_default || !templ)
1093         return TRUE;
1094     if(templ->command)
1095     {
1096         app = g_app_info_create_from_commandline(templ->command, NULL,
1097                                                  G_APP_INFO_CREATE_NONE, error);
1098     }
1099     else
1100     {
1101         app = g_app_info_get_default_for_type(fm_mime_type_get_type(templ->mime_type), FALSE);
1102         if(!app && error)
1103             g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
1104                         _("No default application is set for MIME type %s"),
1105                         fm_mime_type_get_type(templ->mime_type));
1106     }
1107     if(!app)
1108         return FALSE;
1109     list = g_list_prepend(NULL, path);
1110     ret = g_app_info_launch(app, list, NULL, error);
1111     g_list_free(list);
1112     g_object_unref(app);
1113     return ret;
1114 }
1115