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