1 /* gbp-recent-workbench-addin.c
2  *
3  * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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 General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "gbp-recent-workbench-addin"
22 
23 #include "config.h"
24 
25 #include <libide-projects.h>
26 #include <libide-gui.h>
27 
28 #include "ide-project-info-private.h"
29 
30 #include "gbp-recent-workbench-addin.h"
31 
32 struct _GbpRecentWorkbenchAddin
33 {
34   GObject       parent_instance;
35   IdeWorkbench *workbench;
36 };
37 
38 static gboolean
directory_is_ignored(GFile * file)39 directory_is_ignored (GFile *file)
40 {
41   g_autofree gchar *relative_path = NULL;
42   g_autoptr(GFile) downloads_dir = NULL;
43   g_autoptr(GFile) home_dir = NULL;
44   g_autoptr(GFile) projects_dir = NULL;
45   GFileType file_type;
46 
47   g_assert (G_IS_FILE (file));
48 
49   projects_dir = g_file_new_for_path (ide_get_projects_dir ());
50   home_dir = g_file_new_for_path (g_get_home_dir ());
51   relative_path = g_file_get_relative_path (home_dir, file);
52   file_type = g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL);
53 
54   if (!g_file_has_prefix (file, home_dir) &&
55       !g_file_has_prefix (file, projects_dir))
56     return TRUE;
57 
58   /* First check downloads directory as we never want that */
59   downloads_dir = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD));
60   if (downloads_dir != NULL &&
61       (g_file_equal (file, downloads_dir) ||
62        g_file_has_prefix (file, downloads_dir)))
63     return TRUE;
64 
65   /* If the directory is in the projects dir (and the projects dir is
66    * not $HOME, then short-circuit as not ignored.
67    */
68   if (!g_file_equal (home_dir, projects_dir) &&
69       g_file_has_prefix (file, projects_dir))
70     return FALSE;
71 
72   /* Not in home or projects directory, ignore */
73   if (relative_path == NULL)
74     return TRUE;
75 
76   /*
77    * Ignore dot directories, except .local.
78    * We've had too many bug reports with people creating things
79    * like gnome-shell extensions in their .local directory.
80    */
81   if (relative_path[0] == '.' &&
82       !g_str_has_prefix (relative_path, ".local"G_DIR_SEPARATOR_S))
83     return TRUE;
84 
85   if (file_type != G_FILE_TYPE_DIRECTORY)
86     {
87       g_autoptr(GFile) parent = g_file_get_parent (file);
88 
89       if (g_file_equal (home_dir, parent))
90         return TRUE;
91     }
92 
93   return FALSE;
94 }
95 
96 static void
gbp_recent_workbench_addin_add_recent(GbpRecentWorkbenchAddin * self,IdeProjectInfo * project_info)97 gbp_recent_workbench_addin_add_recent (GbpRecentWorkbenchAddin *self,
98                                        IdeProjectInfo          *project_info)
99 {
100   g_autofree gchar *recent_projects_path = NULL;
101   g_autoptr(GBookmarkFile) projects_file = NULL;
102   g_autoptr(GPtrArray) groups = NULL;
103   g_autoptr(GError) error = NULL;
104   g_autofree gchar *uri = NULL;
105   g_autofree gchar *app_exec = NULL;
106   g_autofree gchar *dir = NULL;
107   IdeBuildSystem *build_system;
108   IdeDoap *doap;
109   GFile *file;
110   GFile *directory;
111 
112   IDE_ENTRY;
113 
114   g_assert (GBP_IS_RECENT_WORKBENCH_ADDIN (self));
115   g_assert (IDE_IS_WORKBENCH (self->workbench));
116   g_assert (IDE_IS_PROJECT_INFO (project_info));
117 
118   if (!(file = _ide_project_info_get_real_file (project_info)) ||
119       directory_is_ignored (file))
120     IDE_EXIT;
121 
122   recent_projects_path = g_build_filename (g_get_user_data_dir (),
123                                            ide_get_program_name (),
124                                            IDE_RECENT_PROJECTS_BOOKMARK_FILENAME,
125                                            NULL);
126 
127   projects_file = g_bookmark_file_new ();
128 
129   if (!g_bookmark_file_load_from_file (projects_file, recent_projects_path, &error))
130     {
131       /*
132        * If there was an error loading the file and the error is not "File does not
133        * exist" then stop saving operation
134        */
135       if (error != NULL &&
136           !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
137         {
138           g_warning ("Unable to open recent projects \"%s\" file: %s",
139                      recent_projects_path, error->message);
140           IDE_EXIT;
141         }
142     }
143 
144   uri = g_file_get_uri (file);
145   app_exec = g_strdup_printf ("%s -p %%p", ide_get_program_name ());
146 
147   g_bookmark_file_set_title (projects_file, uri, ide_project_info_get_name (project_info));
148   g_bookmark_file_set_mime_type (projects_file, uri, "application/x-builder-project");
149   g_bookmark_file_add_application (projects_file, uri, ide_get_program_name (), app_exec);
150   g_bookmark_file_set_is_private (projects_file, uri, FALSE);
151 
152   doap = ide_project_info_get_doap (project_info);
153 
154   /* attach project description to recent info */
155   if (doap != NULL)
156     g_bookmark_file_set_description (projects_file, uri, ide_doap_get_shortdesc (doap));
157 
158   /* attach discovered languages to recent info */
159   groups = g_ptr_array_new_with_free_func (g_free);
160   g_ptr_array_add (groups, g_strdup (IDE_RECENT_PROJECTS_GROUP));
161   if (doap != NULL)
162     {
163       gchar **languages;
164 
165       if ((languages = ide_doap_get_languages (doap)))
166         {
167           for (guint i = 0; languages[i]; i++)
168             g_ptr_array_add (groups,
169                              g_strdup_printf ("%s%s",
170                                               IDE_RECENT_PROJECTS_LANGUAGE_GROUP_PREFIX,
171                                               languages[i]));
172         }
173     }
174 
175   g_bookmark_file_set_groups (projects_file, uri, (const gchar **)groups->pdata, groups->len);
176 
177   build_system = ide_workbench_get_build_system (self->workbench);
178 
179   if (build_system != NULL)
180     {
181       g_autofree gchar *build_system_name = NULL;
182       g_autofree gchar *build_system_group = NULL;
183 
184       build_system_name = ide_build_system_get_display_name (build_system);
185       build_system_group = g_strdup_printf ("%s%s", IDE_RECENT_PROJECTS_BUILD_SYSTEM_GROUP_PREFIX, build_system_name);
186       g_bookmark_file_add_group (projects_file, uri, build_system_group);
187     }
188 
189   if ((directory = _ide_project_info_get_real_directory (project_info)))
190     {
191       g_autofree gchar *dir_group = NULL;
192       g_autofree gchar *diruri = g_file_get_uri (directory);
193 
194       dir_group = g_strdup_printf ("%s%s", IDE_RECENT_PROJECTS_DIRECTORY, diruri);
195       g_bookmark_file_add_group (projects_file, uri, dir_group);
196     }
197 
198   IDE_TRACE_MSG ("Registering %s as recent project.", uri);
199 
200   /* ensure the containing directory exists */
201   dir = g_path_get_dirname (recent_projects_path);
202   g_mkdir_with_parents (dir, 0750);
203 
204   if (!g_bookmark_file_to_file (projects_file, recent_projects_path, &error))
205     {
206       g_warning ("Unable to save recent projects %s file: %s",
207                  recent_projects_path, error->message);
208       g_clear_error (&error);
209     }
210 
211   IDE_EXIT;
212 }
213 
214 
215 static void
gbp_recent_workbench_addin_project_loaded(IdeWorkbenchAddin * addin,IdeProjectInfo * project_info)216 gbp_recent_workbench_addin_project_loaded (IdeWorkbenchAddin *addin,
217                                            IdeProjectInfo    *project_info)
218 {
219   GbpRecentWorkbenchAddin *self = (GbpRecentWorkbenchAddin *)addin;
220 
221   g_assert (IDE_IS_MAIN_THREAD ());
222   g_assert (GBP_IS_RECENT_WORKBENCH_ADDIN (self));
223   g_assert (IDE_IS_PROJECT_INFO (project_info));
224 
225   gbp_recent_workbench_addin_add_recent (self, project_info);
226 }
227 
228 static void
gbp_recent_workbench_addin_load(IdeWorkbenchAddin * addin,IdeWorkbench * workbench)229 gbp_recent_workbench_addin_load (IdeWorkbenchAddin *addin,
230                                  IdeWorkbench      *workbench)
231 {
232   GBP_RECENT_WORKBENCH_ADDIN (addin)->workbench = workbench;
233 }
234 
235 static void
gbp_recent_workbench_addin_unload(IdeWorkbenchAddin * addin,IdeWorkbench * workbench)236 gbp_recent_workbench_addin_unload (IdeWorkbenchAddin *addin,
237                                    IdeWorkbench      *workbench)
238 {
239   GBP_RECENT_WORKBENCH_ADDIN (addin)->workbench = NULL;
240 }
241 
242 static void
workbench_addin_iface_init(IdeWorkbenchAddinInterface * iface)243 workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
244 {
245   iface->load = gbp_recent_workbench_addin_load;
246   iface->unload = gbp_recent_workbench_addin_unload;
247   iface->project_loaded = gbp_recent_workbench_addin_project_loaded;
248 }
249 
G_DEFINE_FINAL_TYPE_WITH_CODE(GbpRecentWorkbenchAddin,gbp_recent_workbench_addin,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN,workbench_addin_iface_init))250 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpRecentWorkbenchAddin, gbp_recent_workbench_addin, G_TYPE_OBJECT,
251                          G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
252 
253 static void
254 gbp_recent_workbench_addin_class_init (GbpRecentWorkbenchAddinClass *klass)
255 {
256 }
257 
258 static void
gbp_recent_workbench_addin_init(GbpRecentWorkbenchAddin * self)259 gbp_recent_workbench_addin_init (GbpRecentWorkbenchAddin *self)
260 {
261 }
262