1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3    Copyright (C) 1999, 2000, 2001 Eazel, Inc.
4 
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 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 GNU
13    General Public License for more details.
14 
15    You should have received a copy of the GNU General Public
16    License along with this program; if not, write to the
17    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 
20    Author: Pavel Cisler <pavel@eazel.com>
21 */
22 
23 #include <config.h>
24 #include "caja-file-changes-queue.h"
25 
26 #include "caja-directory-notify.h"
27 
28 typedef enum
29 {
30     CHANGE_FILE_INITIAL,
31     CHANGE_FILE_ADDED,
32     CHANGE_FILE_CHANGED,
33     CHANGE_FILE_REMOVED,
34     CHANGE_FILE_MOVED,
35     CHANGE_POSITION_SET,
36     CHANGE_POSITION_REMOVE
37 } CajaFileChangeKind;
38 
39 typedef struct
40 {
41     CajaFileChangeKind kind;
42     GFile *from;
43     GFile *to;
44     GdkPoint point;
45     int screen;
46 } CajaFileChange;
47 
48 typedef struct
49 {
50     GList *head;
51     GList *tail;
52     GMutex mutex;
53 } CajaFileChangesQueue;
54 
55 static CajaFileChangesQueue *
caja_file_changes_queue_new(void)56 caja_file_changes_queue_new (void)
57 {
58     CajaFileChangesQueue *result;
59 
60     result = g_new0 (CajaFileChangesQueue, 1);
61 
62     g_mutex_init (&result->mutex);
63 
64     return result;
65 }
66 
67 static CajaFileChangesQueue *
caja_file_changes_queue_get(void)68 caja_file_changes_queue_get (void)
69 {
70     static CajaFileChangesQueue *file_changes_queue;
71 
72     if (file_changes_queue == NULL)
73     {
74         file_changes_queue = caja_file_changes_queue_new ();
75     }
76 
77     return file_changes_queue;
78 }
79 
80 static void
caja_file_changes_queue_add_common(CajaFileChangesQueue * queue,CajaFileChange * new_item)81 caja_file_changes_queue_add_common (CajaFileChangesQueue *queue,
82                                     CajaFileChange *new_item)
83 {
84     /* enqueue the new queue item while locking down the list */
85     g_mutex_lock (&queue->mutex);
86 
87     queue->head = g_list_prepend (queue->head, new_item);
88     if (queue->tail == NULL)
89         queue->tail = queue->head;
90 
91     g_mutex_unlock (&queue->mutex);
92 }
93 
94 void
caja_file_changes_queue_file_added(GFile * location)95 caja_file_changes_queue_file_added (GFile *location)
96 {
97     CajaFileChange *new_item;
98     CajaFileChangesQueue *queue;
99 
100     queue = caja_file_changes_queue_get();
101 
102     new_item = g_new0 (CajaFileChange, 1);
103     new_item->kind = CHANGE_FILE_ADDED;
104     new_item->from = g_object_ref (location);
105     caja_file_changes_queue_add_common (queue, new_item);
106 }
107 
108 void
caja_file_changes_queue_file_changed(GFile * location)109 caja_file_changes_queue_file_changed (GFile *location)
110 {
111     CajaFileChange *new_item;
112     CajaFileChangesQueue *queue;
113 
114     queue = caja_file_changes_queue_get();
115 
116     new_item = g_new0 (CajaFileChange, 1);
117     new_item->kind = CHANGE_FILE_CHANGED;
118     new_item->from = g_object_ref (location);
119     caja_file_changes_queue_add_common (queue, new_item);
120 }
121 
122 void
caja_file_changes_queue_file_removed(GFile * location)123 caja_file_changes_queue_file_removed (GFile *location)
124 {
125     CajaFileChange *new_item;
126     CajaFileChangesQueue *queue;
127 
128     queue = caja_file_changes_queue_get();
129 
130     new_item = g_new0 (CajaFileChange, 1);
131     new_item->kind = CHANGE_FILE_REMOVED;
132     new_item->from = g_object_ref (location);
133     caja_file_changes_queue_add_common (queue, new_item);
134 }
135 
136 void
caja_file_changes_queue_file_moved(GFile * from,GFile * to)137 caja_file_changes_queue_file_moved (GFile *from,
138                                     GFile *to)
139 {
140     CajaFileChange *new_item;
141     CajaFileChangesQueue *queue;
142 
143     queue = caja_file_changes_queue_get ();
144 
145     new_item = g_new (CajaFileChange, 1);
146     new_item->kind = CHANGE_FILE_MOVED;
147     new_item->from = g_object_ref (from);
148     new_item->to = g_object_ref (to);
149     caja_file_changes_queue_add_common (queue, new_item);
150 }
151 
152 void
caja_file_changes_queue_schedule_position_set(GFile * location,GdkPoint point,int screen)153 caja_file_changes_queue_schedule_position_set (GFile *location,
154         GdkPoint point,
155         int screen)
156 {
157     CajaFileChange *new_item;
158     CajaFileChangesQueue *queue;
159 
160     queue = caja_file_changes_queue_get ();
161 
162     new_item = g_new (CajaFileChange, 1);
163     new_item->kind = CHANGE_POSITION_SET;
164     new_item->from = g_object_ref (location);
165     new_item->point = point;
166     new_item->screen = screen;
167     caja_file_changes_queue_add_common (queue, new_item);
168 }
169 
170 void
caja_file_changes_queue_schedule_position_remove(GFile * location)171 caja_file_changes_queue_schedule_position_remove (GFile *location)
172 {
173     CajaFileChange *new_item;
174     CajaFileChangesQueue *queue;
175 
176     queue = caja_file_changes_queue_get ();
177 
178     new_item = g_new (CajaFileChange, 1);
179     new_item->kind = CHANGE_POSITION_REMOVE;
180     new_item->from = g_object_ref (location);
181     caja_file_changes_queue_add_common (queue, new_item);
182 }
183 
184 static CajaFileChange *
caja_file_changes_queue_get_change(CajaFileChangesQueue * queue)185 caja_file_changes_queue_get_change (CajaFileChangesQueue *queue)
186 {
187     GList *new_tail;
188     CajaFileChange *result;
189 
190     g_assert (queue != NULL);
191 
192     /* dequeue the tail item while locking down the list */
193     g_mutex_lock (&queue->mutex);
194 
195     if (queue->tail == NULL)
196     {
197         result = NULL;
198     }
199     else
200     {
201         new_tail = queue->tail->prev;
202         result = queue->tail->data;
203         queue->head = g_list_remove_link (queue->head,
204                                           queue->tail);
205         g_list_free_1 (queue->tail);
206         queue->tail = new_tail;
207     }
208 
209     g_mutex_unlock (&queue->mutex);
210 
211     return result;
212 }
213 
214 enum
215 {
216     CONSUME_CHANGES_MAX_CHUNK = 20
217 };
218 
219 static void
pairs_list_free(GList * pairs)220 pairs_list_free (GList *pairs)
221 {
222     GList *p;
223     GFilePair *pair = NULL;
224 
225     /* deep delete the list of pairs */
226 
227     for (p = pairs; p != NULL; p = p->next)
228     {
229         /* delete the strings in each pair */
230         pair = p->data;
231         g_object_unref (pair->from);
232         g_object_unref (pair->to);
233     }
234 
235     /* delete the list and the now empty pair structs */
236     g_list_free_full (pairs, g_free);
237 }
238 
239 static void
position_set_list_free(GList * list)240 position_set_list_free (GList *list)
241 {
242     GList *p;
243     CajaFileChangesQueuePosition *item = NULL;
244 
245     for (p = list; p != NULL; p = p->next)
246     {
247         item = p->data;
248         g_object_unref (item->location);
249     }
250     /* delete the list and the now empty structs */
251     g_list_free_full (list, g_free);
252 }
253 
254 /* go through changes in the change queue, send ones with the same kind
255  * in a list to the different caja_directory_notify calls
256  */
257 void
caja_file_changes_consume_changes(gboolean consume_all)258 caja_file_changes_consume_changes (gboolean consume_all)
259 {
260     CajaFileChange *change;
261     GList *additions, *changes, *deletions, *moves;
262     GList *position_set_requests;
263     GFilePair *pair;
264     CajaFileChangesQueuePosition *position_set;
265     guint chunk_count;
266     CajaFileChangesQueue *queue;
267     gboolean flush_needed;
268 
269 
270     additions = NULL;
271     changes = NULL;
272     deletions = NULL;
273     moves = NULL;
274     position_set_requests = NULL;
275 
276     queue = caja_file_changes_queue_get();
277 
278     /* Consume changes from the queue, stuffing them into one of three lists,
279      * keep doing it while the changes are of the same kind, then send them off.
280      * This is to ensure that the changes get sent off in the same order that they
281      * arrived.
282      */
283     for (chunk_count = 0; ; chunk_count++)
284     {
285         change = caja_file_changes_queue_get_change (queue);
286 
287         /* figure out if we need to flush the pending changes that we collected sofar */
288 
289         if (change == NULL)
290         {
291             flush_needed = TRUE;
292             /* no changes left, flush everything */
293         }
294         else
295         {
296             flush_needed = additions != NULL
297                            && change->kind != CHANGE_FILE_ADDED
298                            && change->kind != CHANGE_POSITION_SET
299                            && change->kind != CHANGE_POSITION_REMOVE;
300 
301             flush_needed |= changes != NULL
302                             && change->kind != CHANGE_FILE_CHANGED;
303 
304             flush_needed |= moves != NULL
305                             && change->kind != CHANGE_FILE_MOVED
306                             && change->kind != CHANGE_POSITION_SET
307                             && change->kind != CHANGE_POSITION_REMOVE;
308 
309             flush_needed |= deletions != NULL
310                             && change->kind != CHANGE_FILE_REMOVED;
311 
312             flush_needed |= position_set_requests != NULL
313                             && change->kind != CHANGE_POSITION_SET
314                             && change->kind != CHANGE_POSITION_REMOVE
315                             && change->kind != CHANGE_FILE_ADDED
316                             && change->kind != CHANGE_FILE_MOVED;
317 
318             flush_needed |= !consume_all && chunk_count >= CONSUME_CHANGES_MAX_CHUNK;
319             /* we have reached the chunk maximum */
320         }
321 
322         if (flush_needed)
323         {
324             /* Send changes we collected off.
325              * At one time we may only have one of the lists
326              * contain changes.
327              */
328 
329             if (deletions != NULL)
330             {
331                 deletions = g_list_reverse (deletions);
332                 caja_directory_notify_files_removed (deletions);
333     		g_list_free_full (deletions, g_object_unref);
334                 deletions = NULL;
335             }
336             if (moves != NULL)
337             {
338                 moves = g_list_reverse (moves);
339                 caja_directory_notify_files_moved (moves);
340                 pairs_list_free (moves);
341                 moves = NULL;
342             }
343             if (additions != NULL)
344             {
345                 additions = g_list_reverse (additions);
346                 caja_directory_notify_files_added (additions);
347     		g_list_free_full (additions, g_object_unref);
348                 additions = NULL;
349             }
350             if (changes != NULL)
351             {
352                 changes = g_list_reverse (changes);
353                 caja_directory_notify_files_changed (changes);
354     		g_list_free_full (changes, g_object_unref);
355                 changes = NULL;
356             }
357             if (position_set_requests != NULL)
358             {
359                 position_set_requests = g_list_reverse (position_set_requests);
360                 caja_directory_schedule_position_set (position_set_requests);
361                 position_set_list_free (position_set_requests);
362                 position_set_requests = NULL;
363             }
364         }
365 
366         if (change == NULL)
367         {
368             /* we are done */
369             return;
370         }
371 
372         /* add the new change to the list */
373         switch (change->kind)
374         {
375         case CHANGE_FILE_ADDED:
376             additions = g_list_prepend (additions, change->from);
377             break;
378 
379         case CHANGE_FILE_CHANGED:
380             changes = g_list_prepend (changes, change->from);
381             break;
382 
383         case CHANGE_FILE_REMOVED:
384             deletions = g_list_prepend (deletions, change->from);
385             break;
386 
387         case CHANGE_FILE_MOVED:
388             pair = g_new (GFilePair, 1);
389             pair->from = change->from;
390             pair->to = change->to;
391             moves = g_list_prepend (moves, pair);
392             break;
393 
394         case CHANGE_POSITION_SET:
395             position_set = g_new (CajaFileChangesQueuePosition, 1);
396             position_set->location = change->from;
397             position_set->set = TRUE;
398             position_set->point = change->point;
399             position_set->screen = change->screen;
400             position_set_requests = g_list_prepend (position_set_requests,
401                                                     position_set);
402             break;
403 
404         case CHANGE_POSITION_REMOVE:
405             position_set = g_new (CajaFileChangesQueuePosition, 1);
406             position_set->location = change->from;
407             position_set->set = FALSE;
408             position_set_requests = g_list_prepend (position_set_requests,
409                                                     position_set);
410             break;
411 
412         default:
413             g_assert_not_reached ();
414             break;
415         }
416 
417         g_free (change);
418     }
419 }
420