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