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