1 | /* |
2 | * Copyright 2015 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Author: Matthias Clasen <mclasen@redhat.com> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include <gio/gio.h> |
23 | #include <gi18n.h> |
24 | |
25 | #include "gio-tool.h" |
26 | |
27 | |
28 | static gboolean show_hidden = FALSE; |
29 | static gboolean follow_symlinks = FALSE; |
30 | |
31 | static const GOptionEntry entries[] = { |
32 | { "hidden" , 'h', 0, G_OPTION_ARG_NONE, &show_hidden, N_("Show hidden files" ), NULL }, |
33 | { "follow-symlinks" , 'l', 0, G_OPTION_ARG_NONE, &follow_symlinks, N_("Follow symbolic links, mounts and shortcuts" ), NULL }, |
34 | { NULL } |
35 | }; |
36 | |
37 | static gint |
38 | sort_info_by_name (GFileInfo *a, GFileInfo *b) |
39 | { |
40 | const char *na; |
41 | const char *nb; |
42 | |
43 | na = g_file_info_get_name (info: a); |
44 | nb = g_file_info_get_name (info: b); |
45 | |
46 | if (na == NULL) |
47 | na = "" ; |
48 | if (nb == NULL) |
49 | nb = "" ; |
50 | |
51 | return strcmp (s1: na, s2: nb); |
52 | } |
53 | |
54 | static void |
55 | do_tree (GFile *f, unsigned int level, guint64 pattern) |
56 | { |
57 | GFileEnumerator *enumerator; |
58 | GError *error = NULL; |
59 | unsigned int n; |
60 | GFileInfo *info; |
61 | |
62 | info = g_file_query_info (file: f, |
63 | G_FILE_ATTRIBUTE_STANDARD_TYPE "," |
64 | G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, |
65 | flags: 0, |
66 | NULL, NULL); |
67 | if (info != NULL) |
68 | { |
69 | if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_MOUNTABLE) |
70 | { |
71 | /* don't process mountables; we avoid these by getting the target_uri below */ |
72 | g_object_unref (object: info); |
73 | return; |
74 | } |
75 | g_object_unref (object: info); |
76 | } |
77 | |
78 | enumerator = g_file_enumerate_children (file: f, |
79 | G_FILE_ATTRIBUTE_STANDARD_NAME "," |
80 | G_FILE_ATTRIBUTE_STANDARD_TYPE "," |
81 | G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," |
82 | G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," |
83 | G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET "," |
84 | G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, |
85 | flags: 0, |
86 | NULL, |
87 | error: &error); |
88 | if (enumerator != NULL) |
89 | { |
90 | GList *l; |
91 | GList *info_list; |
92 | |
93 | info_list = NULL; |
94 | while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) |
95 | { |
96 | if (g_file_info_get_is_hidden (info) && !show_hidden) |
97 | { |
98 | g_object_unref (object: info); |
99 | } |
100 | else |
101 | { |
102 | info_list = g_list_prepend (list: info_list, data: info); |
103 | } |
104 | } |
105 | g_file_enumerator_close (enumerator, NULL, NULL); |
106 | |
107 | info_list = g_list_sort (list: info_list, compare_func: (GCompareFunc) sort_info_by_name); |
108 | |
109 | for (l = info_list; l != NULL; l = l->next) |
110 | { |
111 | const char *name; |
112 | const char *target_uri; |
113 | GFileType type; |
114 | gboolean is_last_item; |
115 | |
116 | info = l->data; |
117 | is_last_item = (l->next == NULL); |
118 | |
119 | name = g_file_info_get_name (info); |
120 | type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE); |
121 | if (name != NULL) |
122 | { |
123 | |
124 | for (n = 0; n < level; n++) |
125 | { |
126 | if (pattern & (1<<n)) |
127 | { |
128 | g_print (format: "| " ); |
129 | } |
130 | else |
131 | { |
132 | g_print (format: " " ); |
133 | } |
134 | } |
135 | |
136 | if (is_last_item) |
137 | { |
138 | g_print (format: "`-- %s" , name); |
139 | } |
140 | else |
141 | { |
142 | g_print (format: "|-- %s" , name); |
143 | } |
144 | |
145 | target_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); |
146 | if (target_uri != NULL) |
147 | { |
148 | g_print (format: " -> %s" , target_uri); |
149 | } |
150 | else |
151 | { |
152 | if (g_file_info_get_is_symlink (info)) |
153 | { |
154 | const char *target; |
155 | target = g_file_info_get_symlink_target (info); |
156 | g_print (format: " -> %s" , target); |
157 | } |
158 | } |
159 | |
160 | g_print (format: "\n" ); |
161 | |
162 | if ((type & G_FILE_TYPE_DIRECTORY) && |
163 | (follow_symlinks || !g_file_info_get_is_symlink (info))) |
164 | { |
165 | guint64 new_pattern; |
166 | GFile *child; |
167 | |
168 | if (is_last_item) |
169 | new_pattern = pattern; |
170 | else |
171 | new_pattern = pattern | (1<<level); |
172 | |
173 | child = NULL; |
174 | if (target_uri != NULL) |
175 | { |
176 | if (follow_symlinks) |
177 | child = g_file_new_for_uri (uri: target_uri); |
178 | } |
179 | else |
180 | { |
181 | child = g_file_get_child (file: f, name); |
182 | } |
183 | |
184 | if (child != NULL) |
185 | { |
186 | do_tree (f: child, level: level + 1, pattern: new_pattern); |
187 | g_object_unref (object: child); |
188 | } |
189 | } |
190 | } |
191 | g_object_unref (object: info); |
192 | } |
193 | g_list_free (list: info_list); |
194 | } |
195 | else |
196 | { |
197 | for (n = 0; n < level; n++) |
198 | { |
199 | if (pattern & (1<<n)) |
200 | { |
201 | g_print (format: "| " ); |
202 | } |
203 | else |
204 | { |
205 | g_print (format: " " ); |
206 | } |
207 | } |
208 | |
209 | g_print (format: " [%s]\n" , error->message); |
210 | |
211 | g_error_free (error); |
212 | } |
213 | } |
214 | |
215 | static void |
216 | tree (GFile *f) |
217 | { |
218 | char *uri; |
219 | |
220 | uri = g_file_get_uri (file: f); |
221 | g_print (format: "%s\n" , uri); |
222 | g_free (mem: uri); |
223 | |
224 | do_tree (f, level: 0, pattern: 0); |
225 | } |
226 | |
227 | int |
228 | handle_tree (int argc, char *argv[], gboolean do_help) |
229 | { |
230 | GOptionContext *context; |
231 | GError *error = NULL; |
232 | GFile *file; |
233 | gchar *param; |
234 | int i; |
235 | |
236 | g_set_prgname (prgname: "gio tree" ); |
237 | |
238 | /* Translators: commandline placeholder */ |
239 | param = g_strdup_printf (format: "[%s…]" , _("LOCATION" )); |
240 | context = g_option_context_new (parameter_string: param); |
241 | g_free (mem: param); |
242 | g_option_context_set_help_enabled (context, FALSE); |
243 | g_option_context_set_summary (context, |
244 | _("List contents of directories in a tree-like format." )); |
245 | g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); |
246 | |
247 | if (do_help) |
248 | { |
249 | show_help (context, NULL); |
250 | g_option_context_free (context); |
251 | return 0; |
252 | } |
253 | |
254 | g_option_context_parse (context, argc: &argc, argv: &argv, error: &error); |
255 | |
256 | if (error != NULL) |
257 | { |
258 | show_help (context, message: error->message); |
259 | g_error_free (error); |
260 | g_option_context_free (context); |
261 | return 1; |
262 | } |
263 | |
264 | g_option_context_free (context); |
265 | |
266 | if (argc > 1) |
267 | { |
268 | for (i = 1; i < argc; i++) |
269 | { |
270 | file = g_file_new_for_commandline_arg (arg: argv[i]); |
271 | tree (f: file); |
272 | g_object_unref (object: file); |
273 | } |
274 | } |
275 | else |
276 | { |
277 | char *cwd; |
278 | |
279 | cwd = g_get_current_dir (); |
280 | file = g_file_new_for_path (path: cwd); |
281 | g_free (mem: cwd); |
282 | tree (f: file); |
283 | g_object_unref (object: file); |
284 | } |
285 | |
286 | return 0; |
287 | } |
288 | |