1 | /* GTK - The GIMP Toolkit |
2 | * gtktrashmonitor.h: Monitor the trash:/// folder to see if there is trash or not |
3 | * Copyright (C) 2011 Suse |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU Lesser General Public License as |
7 | * published by the Free Software Foundation; either version 2.1 of the |
8 | * License, or (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 Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public License |
16 | * along with this program; if not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Authors: Federico Mena Quintero <federico@gnome.org> |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include "gtkintl.h" |
24 | #include "gtkmarshalers.h" |
25 | #include "gtktrashmonitor.h" |
26 | |
27 | #define UPDATE_RATE_SECONDS 1 |
28 | |
29 | struct _GtkTrashMonitor |
30 | { |
31 | GObject parent; |
32 | |
33 | GFileMonitor *file_monitor; |
34 | gulong file_monitor_changed_id; |
35 | |
36 | gboolean pending; |
37 | int timeout_id; |
38 | |
39 | guint has_trash : 1; |
40 | }; |
41 | |
42 | struct _GtkTrashMonitorClass |
43 | { |
44 | GObjectClass parent_class; |
45 | |
46 | void (* trash_state_changed) (GtkTrashMonitor *monitor); |
47 | }; |
48 | |
49 | enum { |
50 | TRASH_STATE_CHANGED, |
51 | LAST_SIGNAL |
52 | }; |
53 | |
54 | static guint signals[LAST_SIGNAL]; |
55 | |
56 | G_DEFINE_TYPE (GtkTrashMonitor, _gtk_trash_monitor, G_TYPE_OBJECT) |
57 | |
58 | static GtkTrashMonitor *the_trash_monitor; |
59 | |
60 | #define ICON_NAME_TRASH_EMPTY "user-trash-symbolic" |
61 | #define ICON_NAME_TRASH_FULL "user-trash-full-symbolic" |
62 | |
63 | static void |
64 | gtk_trash_monitor_dispose (GObject *object) |
65 | { |
66 | GtkTrashMonitor *monitor; |
67 | |
68 | monitor = GTK_TRASH_MONITOR (object); |
69 | |
70 | if (monitor->file_monitor) |
71 | { |
72 | g_signal_handler_disconnect (instance: monitor->file_monitor, handler_id: monitor->file_monitor_changed_id); |
73 | monitor->file_monitor_changed_id = 0; |
74 | |
75 | g_clear_object (&monitor->file_monitor); |
76 | } |
77 | |
78 | if (monitor->timeout_id > 0) |
79 | g_source_remove (tag: monitor->timeout_id); |
80 | monitor->timeout_id = 0; |
81 | |
82 | G_OBJECT_CLASS (_gtk_trash_monitor_parent_class)->dispose (object); |
83 | } |
84 | |
85 | static void |
86 | _gtk_trash_monitor_class_init (GtkTrashMonitorClass *class) |
87 | { |
88 | GObjectClass *gobject_class; |
89 | |
90 | gobject_class = (GObjectClass *) class; |
91 | |
92 | gobject_class->dispose = gtk_trash_monitor_dispose; |
93 | |
94 | signals[TRASH_STATE_CHANGED] = |
95 | g_signal_new (I_("trash-state-changed" ), |
96 | G_OBJECT_CLASS_TYPE (gobject_class), |
97 | signal_flags: G_SIGNAL_RUN_FIRST, |
98 | G_STRUCT_OFFSET (GtkTrashMonitorClass, trash_state_changed), |
99 | NULL, NULL, |
100 | NULL, |
101 | G_TYPE_NONE, n_params: 0); |
102 | } |
103 | |
104 | /* Updates the internal has_trash flag and emits the "trash-state-changed" signal */ |
105 | static void |
106 | update_has_trash_and_notify (GtkTrashMonitor *monitor, |
107 | gboolean has_trash) |
108 | { |
109 | if (monitor->has_trash == !!has_trash) |
110 | return; |
111 | |
112 | monitor->has_trash = !!has_trash; |
113 | |
114 | g_signal_emit (instance: monitor, signal_id: signals[TRASH_STATE_CHANGED], detail: 0); |
115 | } |
116 | |
117 | /* Use G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT since we only want to know whether the |
118 | * trash is empty or not, not access its children. This is available for the |
119 | * trash backend since it uses a cache. In this way we prevent flooding the |
120 | * trash backend with enumeration requests when trashing > 1000 files |
121 | */ |
122 | static void |
123 | trash_query_info_cb (GObject *source, |
124 | GAsyncResult *result, |
125 | gpointer user_data) |
126 | { |
127 | GtkTrashMonitor *monitor = GTK_TRASH_MONITOR (user_data); |
128 | GFileInfo *info; |
129 | guint32 item_count; |
130 | gboolean has_trash = FALSE; |
131 | |
132 | info = g_file_query_info_finish (G_FILE (source), res: result, NULL); |
133 | |
134 | if (info != NULL) |
135 | { |
136 | item_count = g_file_info_get_attribute_uint32 (info, |
137 | G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); |
138 | has_trash = item_count > 0; |
139 | |
140 | g_object_unref (object: info); |
141 | } |
142 | |
143 | update_has_trash_and_notify (monitor, has_trash); |
144 | |
145 | g_object_unref (object: monitor); /* was reffed in recompute_trash_state() */ |
146 | } |
147 | |
148 | static void recompute_trash_state (GtkTrashMonitor *monitor); |
149 | |
150 | static gboolean |
151 | recompute_trash_state_cb (gpointer data) |
152 | { |
153 | GtkTrashMonitor *monitor = data; |
154 | |
155 | monitor->timeout_id = 0; |
156 | if (monitor->pending) |
157 | { |
158 | monitor->pending = FALSE; |
159 | recompute_trash_state (monitor); |
160 | } |
161 | |
162 | return G_SOURCE_REMOVE; |
163 | } |
164 | |
165 | /* Asynchronously recomputes whether there is trash or not */ |
166 | static void |
167 | recompute_trash_state (GtkTrashMonitor *monitor) |
168 | { |
169 | GFile *file; |
170 | |
171 | /* Rate limit the updates to not flood the gvfsd-trash when too many changes |
172 | * happened in a short time. |
173 | */ |
174 | if (monitor->timeout_id > 0) |
175 | { |
176 | monitor->pending = TRUE; |
177 | return; |
178 | } |
179 | |
180 | file = g_file_new_for_uri (uri: "trash:///" ); |
181 | g_file_query_info_async (file, |
182 | G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, |
183 | flags: G_FILE_QUERY_INFO_NONE, |
184 | G_PRIORITY_DEFAULT, NULL, |
185 | callback: trash_query_info_cb, g_object_ref (monitor)); |
186 | |
187 | monitor->timeout_id = g_timeout_add_seconds (UPDATE_RATE_SECONDS, |
188 | function: recompute_trash_state_cb, |
189 | data: monitor); |
190 | |
191 | g_object_unref (object: file); |
192 | } |
193 | |
194 | /* Callback used when the "trash:///" file monitor changes; we just recompute the trash state |
195 | * whenever something happens. |
196 | */ |
197 | static void |
198 | file_monitor_changed_cb (GFileMonitor *file_monitor, |
199 | GFile *child, |
200 | GFile *other_file, |
201 | GFileMonitorEvent event_type, |
202 | GtkTrashMonitor *monitor) |
203 | { |
204 | recompute_trash_state (monitor); |
205 | } |
206 | |
207 | static void |
208 | _gtk_trash_monitor_init (GtkTrashMonitor *monitor) |
209 | { |
210 | GFile *file; |
211 | |
212 | file = g_file_new_for_uri (uri: "trash:///" ); |
213 | |
214 | monitor->file_monitor = g_file_monitor_file (file, flags: G_FILE_MONITOR_NONE, NULL, NULL); |
215 | monitor->pending = FALSE; |
216 | monitor->timeout_id = 0; |
217 | |
218 | g_object_unref (object: file); |
219 | |
220 | if (monitor->file_monitor) |
221 | monitor->file_monitor_changed_id = g_signal_connect (monitor->file_monitor, "changed" , |
222 | G_CALLBACK (file_monitor_changed_cb), monitor); |
223 | |
224 | recompute_trash_state (monitor); |
225 | } |
226 | |
227 | /* |
228 | * _gtk_trash_monitor_get: |
229 | * |
230 | * Returns: (transfer full): a new reference to the singleton |
231 | * GtkTrashMonitor object. Be sure to call g_object_unref() on it when you are |
232 | * done with the trash monitor. |
233 | */ |
234 | GtkTrashMonitor * |
235 | _gtk_trash_monitor_get (void) |
236 | { |
237 | if (the_trash_monitor != NULL) |
238 | { |
239 | g_object_ref (the_trash_monitor); |
240 | } |
241 | else |
242 | { |
243 | the_trash_monitor = g_object_new (GTK_TYPE_TRASH_MONITOR, NULL); |
244 | g_object_add_weak_pointer (G_OBJECT (the_trash_monitor), weak_pointer_location: (gpointer *) &the_trash_monitor); |
245 | } |
246 | |
247 | return the_trash_monitor; |
248 | } |
249 | |
250 | /* |
251 | * _gtk_trash_monitor_get_icon: |
252 | * @monitor: a GtkTrashMonitor |
253 | * |
254 | * Returns: (transfer full): the GIcon that should be used to represent |
255 | * the state of the trash folder on screen, based on whether there is trash or |
256 | * not. |
257 | */ |
258 | GIcon * |
259 | _gtk_trash_monitor_get_icon (GtkTrashMonitor *monitor) |
260 | { |
261 | const char *icon_name; |
262 | |
263 | g_return_val_if_fail (GTK_IS_TRASH_MONITOR (monitor), NULL); |
264 | |
265 | if (monitor->has_trash) |
266 | icon_name = ICON_NAME_TRASH_FULL; |
267 | else |
268 | icon_name = ICON_NAME_TRASH_EMPTY; |
269 | |
270 | return g_themed_icon_new (iconname: icon_name); |
271 | } |
272 | |
273 | /* |
274 | * _gtk_trash_monitor_get_has_trash: |
275 | * @monitor: a GtkTrashMonitor |
276 | * |
277 | * Returns: #TRUE if there is trash in the trash:/// folder, or #FALSE otherwise. |
278 | */ |
279 | gboolean |
280 | _gtk_trash_monitor_get_has_trash (GtkTrashMonitor *monitor) |
281 | { |
282 | g_return_val_if_fail (GTK_IS_TRASH_MONITOR (monitor), FALSE); |
283 | |
284 | return monitor->has_trash; |
285 | } |
286 | |