1 /*
2  * Copyright (C) 2019-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "audio/supported_file.h"
24 #include "gui/backend/file_manager.h"
25 #include "settings/settings.h"
26 #include "utils/arrays.h"
27 #include "utils/io.h"
28 #include "utils/objects.h"
29 #include "utils/string.h"
30 #include "utils/strv_builder.h"
31 #include "zrythm.h"
32 
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 
36 /**
37  * Creates the file manager.
38  */
39 FileManager *
file_manager_new(void)40 file_manager_new (void)
41 {
42   FileManager * self = object_new (FileManager);
43 
44   /*self->num_collections = 0;*/
45 
46   self->files =
47     g_ptr_array_new_full (
48       400, (GDestroyNotify) supported_file_free);
49   self->locations =
50     g_ptr_array_new_with_free_func (
51       (GDestroyNotify) file_browser_location_free);
52 
53   /* add standard locations */
54   FileBrowserLocation * fl =
55     file_browser_location_new ();
56   /* TRANSLATORS: Home directory */
57   fl->label = g_strdup (_("Home"));
58   fl->path = g_strdup (g_get_home_dir ());
59   fl->special_location = FILE_MANAGER_HOME;
60   g_ptr_array_add (self->locations, fl);
61 
62   file_manager_set_selection (
63     self, fl, false, false);
64 
65   fl =
66     file_browser_location_new ();
67   /* TRANSLATORS: Desktop directory */
68   fl->label = g_strdup (_("Desktop"));
69   fl->path =
70     g_strdup (
71       g_get_user_special_dir (
72         G_USER_DIRECTORY_DESKTOP));
73   fl->special_location = FILE_MANAGER_DESKTOP;
74   g_ptr_array_add (self->locations, fl);
75 
76   if (!ZRYTHM_TESTING)
77     {
78       /* add bookmarks */
79       char ** bookmarks =
80         g_settings_get_strv (
81           S_UI_FILE_BROWSER,
82           "file-browser-bookmarks");
83       for (size_t i = 0; bookmarks[i] != NULL; i++)
84         {
85           char * bookmark = bookmarks[i];
86           fl =
87             file_browser_location_new ();
88           fl->label = g_path_get_basename (bookmark);
89           fl->path = g_strdup (bookmark);
90           fl->special_location =
91             FILE_MANAGER_NONE;
92           g_ptr_array_add (self->locations, fl);
93         }
94       g_strfreev (bookmarks);
95 
96       /* set remembered location */
97       FileBrowserLocation * loc =
98         file_browser_location_new ();
99       loc->path =
100         g_settings_get_string (
101           S_UI_FILE_BROWSER, "last-location");
102       if (strlen (loc->path) > 0 &&
103           g_file_test (
104             loc->path, G_FILE_TEST_IS_DIR))
105         {
106           file_manager_set_selection (
107             self, loc, true, false);
108         }
109       file_browser_location_free (loc);
110     }
111 
112   return self;
113 }
114 
115 static int
alphaBetize(const void * _a,const void * _b)116 alphaBetize (const void * _a,
117              const void * _b)
118 {
119   SupportedFile * a = *(SupportedFile * const *) _a;
120   SupportedFile * b = *(SupportedFile * const *) _b;
121   int r = strcasecmp(a->label, b->label);
122   if (r) return r;
123   /* if equal ignoring case, use opposite of strcmp()
124    * result to get lower before upper */
125   return -strcmp(a->label, b->label); /* aka: return strcmp(b, a); */
126 }
127 
128 static void
load_files_from_location(FileManager * self,FileBrowserLocation * location)129 load_files_from_location (
130   FileManager *         self,
131   FileBrowserLocation * location)
132 {
133   const gchar * file;
134   SupportedFile * fd;
135 
136   g_ptr_array_remove_range (
137     self->files, 0, self->files->len);
138 
139   GDir * dir =
140     g_dir_open (location->path, 0, NULL);
141   if (!dir)
142     {
143       g_warning ("Could not open dir %s",
144                  location->path);
145       return;
146     }
147 
148   /* create special parent dir entry */
149   fd = object_new (SupportedFile);
150   /*g_message ("pre path %s",*/
151              /*location->path);*/
152   fd->abs_path =
153     io_path_get_parent_dir (location->path);
154   /*g_message ("after path %s",*/
155              /*fd->abs_path);*/
156   fd->type = FILE_TYPE_PARENT_DIR;
157   fd->hidden = 0;
158   fd->label = g_strdup ("..");
159   if (strlen (location->path) > 1)
160     {
161       g_ptr_array_add (self->files, fd);
162     }
163   else
164     {
165       supported_file_free (fd);
166       fd = NULL;
167     }
168 
169   while ((file = g_dir_read_name (dir)))
170     {
171       fd = object_new (SupportedFile);
172       /*fd->dnd_type = UI_DND_TYPE_FILE_DESCRIPTOR;*/
173 
174       /* set absolute path & label */
175       char * absolute_path =
176         g_strdup_printf (
177           "%s%s%s",
178           strlen (location->path) == 1 ?
179             "" : location->path,
180           G_DIR_SEPARATOR_S, file);
181       fd->abs_path = absolute_path;
182       fd->label = g_strdup (file);
183 
184       GError * err = NULL;
185       GFile * gfile =
186         g_file_new_for_path (absolute_path);
187       GFileInfo * info =
188         g_file_query_info (
189           gfile, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
190           G_FILE_QUERY_INFO_NONE, NULL, &err);
191       if (err)
192         {
193           g_warning (
194             "failed to query file info for %s",
195             absolute_path);
196         }
197       else
198         {
199           fd->hidden =
200             g_file_info_get_is_hidden (info);
201           g_object_unref (info);
202         }
203       g_object_unref (gfile);
204 
205       /* set type */
206       if (g_file_test (
207             absolute_path, G_FILE_TEST_IS_DIR))
208         {
209           fd->type = FILE_TYPE_DIR;
210         }
211       else
212         {
213           fd->type = supported_file_get_type (file);
214         }
215 
216       /* force hidden if starts with . */
217       if (file[0] == '.')
218         fd->hidden = true;
219 
220       g_ptr_array_add (self->files, fd);
221       /*g_message ("File found: %s (%d - %d)",*/
222                  /*fd->abs_path,*/
223                  /*fd->type,*/
224                  /*fd->hidden);*/
225     }
226   g_dir_close (dir);
227 
228   g_ptr_array_sort (
229     self->files, (GCompareFunc) alphaBetize);
230   g_message ("Total files: %d", self->files->len);
231 }
232 
233 /**
234  * Loads the files under the current selection.
235  */
236 void
file_manager_load_files(FileManager * self)237 file_manager_load_files (FileManager * self)
238 {
239   if (self->selection)
240     {
241       load_files_from_location (
242         self,
243         (FileBrowserLocation *) self->selection);
244     }
245   else
246     {
247       g_ptr_array_remove_range (
248         self->files, 0, self->files->len);
249     }
250 }
251 
252 /**
253  * @param save_to_settings Whether to save this
254  *   location to GSettings.
255  */
256 void
file_manager_set_selection(FileManager * self,FileBrowserLocation * sel,bool load_files,bool save_to_settings)257 file_manager_set_selection (
258   FileManager *            self,
259   FileBrowserLocation *    sel,
260   bool                     load_files,
261   bool                     save_to_settings)
262 {
263   g_debug ("setting selection to %s", sel->path);
264 
265   if (self->selection)
266     file_browser_location_free (self->selection);
267 
268   self->selection =
269     file_browser_location_clone (sel);
270   if (load_files)
271     {
272       file_manager_load_files (self);
273     }
274   if (save_to_settings)
275     {
276       g_settings_set_string (
277         S_UI_FILE_BROWSER, "last-location",
278         self->selection->path);
279     }
280 }
281 
282 static bool
file_browser_location_equal_func(const FileBrowserLocation * a,const FileBrowserLocation * b)283 file_browser_location_equal_func (
284   const FileBrowserLocation * a,
285   const FileBrowserLocation * b)
286 {
287   bool ret =
288     string_is_equal (a->path, b->path);
289   return ret;
290 }
291 
292 static void
save_locations(FileManager * self)293 save_locations (
294   FileManager * self)
295 {
296   StrvBuilder * strv_builder = strv_builder_new ();
297   for (guint i = 0;
298        i < FILE_MANAGER->locations->len; i++)
299     {
300       FileBrowserLocation * loc =
301         g_ptr_array_index (
302           FILE_MANAGER->locations, i);
303       if (loc->special_location > FILE_MANAGER_NONE)
304         continue;
305 
306       strv_builder_add (
307         strv_builder, loc->path);
308     }
309 
310   char ** strings = strv_builder_end (strv_builder);
311   g_settings_set_strv (
312     S_UI_FILE_BROWSER, "file-browser-bookmarks",
313     (const char * const *) strings);
314   g_strfreev (strings);
315 }
316 
317 /**
318  * Adds a location and saves the settings.
319  */
320 void
file_manager_add_location_and_save(FileManager * self,const char * abs_path)321 file_manager_add_location_and_save (
322   FileManager * self,
323   const char *  abs_path)
324 {
325   FileBrowserLocation * loc =
326     file_browser_location_new ();
327   loc->path = g_strdup (abs_path);
328   loc->label = g_path_get_basename (loc->path);
329 
330   g_ptr_array_add (self->locations, loc);
331 
332   save_locations (self);
333 }
334 
335 /**
336  * Removes the given location (bookmark) from the
337  * saved locations.
338  *
339  * @param skip_if_standard Skip removal if the
340  *   given location is a standard location.
341  */
342 void
file_manager_remove_location_and_save(FileManager * self,const char * location,bool skip_if_standard)343 file_manager_remove_location_and_save (
344   FileManager * self,
345   const char *  location,
346   bool          skip_if_standard)
347 {
348   FileBrowserLocation * loc =
349     file_browser_location_new ();
350   loc->path = g_strdup (location);
351 
352   unsigned int idx;
353   bool ret =
354     g_ptr_array_find_with_equal_func (
355       self->locations, loc,
356       (GEqualFunc)
357       file_browser_location_equal_func, &idx);
358   if (ret)
359     {
360       FileBrowserLocation * existing_loc =
361         g_ptr_array_index (
362           self->locations, idx);
363       if (!skip_if_standard ||
364           existing_loc->special_location ==
365             FILE_MANAGER_NONE)
366         {
367           g_ptr_array_remove_index (
368             self->locations, idx);
369         }
370     }
371   else
372     {
373       g_warning ("%s not found", location);
374     }
375 
376   file_browser_location_free (loc);
377 
378   save_locations (self);
379 }
380 
381 /**
382  * Frees the file manager.
383  */
384 void
file_manager_free(FileManager * self)385 file_manager_free (
386   FileManager * self)
387 {
388   g_ptr_array_free (self->files, true);
389   g_ptr_array_free (self->locations, true);
390 
391   object_zero_and_free (self);
392 }
393 
394 
395 FileBrowserLocation *
file_browser_location_new(void)396 file_browser_location_new (void)
397 {
398   return object_new (FileBrowserLocation);
399 }
400 
401 FileBrowserLocation *
file_browser_location_clone(FileBrowserLocation * loc)402 file_browser_location_clone (
403   FileBrowserLocation * loc)
404 {
405   FileBrowserLocation * self =
406     file_browser_location_new ();
407   self->path = g_strdup (loc->path);
408   self->label = g_strdup (loc->label);
409 
410   return self;
411 }
412 
413 void
file_browser_location_free(FileBrowserLocation * loc)414 file_browser_location_free (
415   FileBrowserLocation * loc)
416 {
417   g_free_and_null (loc->label);
418   g_free_and_null (loc->path);
419   object_zero_and_free (loc);
420 }
421