/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- Copyright (C) 1999, 2000, 2001 Eazel, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Author: Pavel Cisler <pavel@eazel.com> */ #include <config.h> #include "caja-file-changes-queue.h" #include "caja-directory-notify.h" #include <src/glibcompat.h> /* for g_list_free_full */ #ifdef G_THREADS_ENABLED #define MUTEX_LOCK(a) if ((a) != NULL) g_mutex_lock (a) #define MUTEX_UNLOCK(a) if ((a) != NULL) g_mutex_unlock (a) #else #define MUTEX_LOCK(a) #define MUTEX_UNLOCK(a) #endif typedef enum { CHANGE_FILE_INITIAL, CHANGE_FILE_ADDED, CHANGE_FILE_CHANGED, CHANGE_FILE_REMOVED, CHANGE_FILE_MOVED, CHANGE_POSITION_SET, CHANGE_POSITION_REMOVE } CajaFileChangeKind; typedef struct { CajaFileChangeKind kind; GFile *from; GFile *to; GdkPoint point; int screen; } CajaFileChange; typedef struct { GList *head; GList *tail; #ifdef G_THREADS_ENABLED # if GLIB_CHECK_VERSION(2, 32, 0) GMutex mutex; # else GMutex *mutex; # endif #endif } CajaFileChangesQueue; static CajaFileChangesQueue * caja_file_changes_queue_new (void) { CajaFileChangesQueue *result; result = g_new0 (CajaFileChangesQueue, 1); #ifdef G_THREADS_ENABLED # if GLIB_CHECK_VERSION(2, 32, 0) g_mutex_init (&result->mutex); # else result->mutex = g_mutex_new (); # endif #endif return result; } static CajaFileChangesQueue * caja_file_changes_queue_get (void) { static CajaFileChangesQueue *file_changes_queue; if (file_changes_queue == NULL) { file_changes_queue = caja_file_changes_queue_new (); } return file_changes_queue; } #if 0 /* no public free call yet */ static void caja_file_change_free (CajaFileChange *change) { if (change->from) { g_object_unref (change->from); } if (change->to) { g_object_unref (change->to); } } void caja_file_changes_queue_free (CajaFileChangesQueue *queue) { GList *p; if (queue == NULL) { return; } #ifdef G_THREADS_ENABLED /* if lock on a defunct mutex were defined (returning a failure) * we would lock here */ #endif for (p = queue->head; p != NULL; p = p->next) { caja_file_change_free (p->data); } g_list_free (queue->head); #ifdef G_THREADS_ENABLED # if GLIB_CHECK_VERSION(2, 32, 0) g_mutex_clear (&queue->mutex); # else g_mutex_free (queue->mutex); # endif #endif g_free (queue); } #endif /* no public free call yet */ static void caja_file_changes_queue_add_common (CajaFileChangesQueue *queue, CajaFileChange *new_item) { /* enqueue the new queue item while locking down the list */ #if GLIB_CHECK_VERSION(2, 32, 0) MUTEX_LOCK (&queue->mutex); #else MUTEX_LOCK (queue->mutex); #endif queue->head = g_list_prepend (queue->head, new_item); if (queue->tail == NULL) queue->tail = queue->head; #if GLIB_CHECK_VERSION(2, 32, 0) MUTEX_UNLOCK (&queue->mutex); #else MUTEX_UNLOCK (queue->mutex); #endif } void caja_file_changes_queue_file_added (GFile *location) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get(); new_item = g_new0 (CajaFileChange, 1); new_item->kind = CHANGE_FILE_ADDED; new_item->from = g_object_ref (location); caja_file_changes_queue_add_common (queue, new_item); } void caja_file_changes_queue_file_changed (GFile *location) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get(); new_item = g_new0 (CajaFileChange, 1); new_item->kind = CHANGE_FILE_CHANGED; new_item->from = g_object_ref (location); caja_file_changes_queue_add_common (queue, new_item); } void caja_file_changes_queue_file_removed (GFile *location) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get(); new_item = g_new0 (CajaFileChange, 1); new_item->kind = CHANGE_FILE_REMOVED; new_item->from = g_object_ref (location); caja_file_changes_queue_add_common (queue, new_item); } void caja_file_changes_queue_file_moved (GFile *from, GFile *to) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get (); new_item = g_new (CajaFileChange, 1); new_item->kind = CHANGE_FILE_MOVED; new_item->from = g_object_ref (from); new_item->to = g_object_ref (to); caja_file_changes_queue_add_common (queue, new_item); } void caja_file_changes_queue_schedule_position_set (GFile *location, GdkPoint point, int screen) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get (); new_item = g_new (CajaFileChange, 1); new_item->kind = CHANGE_POSITION_SET; new_item->from = g_object_ref (location); new_item->point = point; new_item->screen = screen; caja_file_changes_queue_add_common (queue, new_item); } void caja_file_changes_queue_schedule_position_remove (GFile *location) { CajaFileChange *new_item; CajaFileChangesQueue *queue; queue = caja_file_changes_queue_get (); new_item = g_new (CajaFileChange, 1); new_item->kind = CHANGE_POSITION_REMOVE; new_item->from = g_object_ref (location); caja_file_changes_queue_add_common (queue, new_item); } static CajaFileChange * caja_file_changes_queue_get_change (CajaFileChangesQueue *queue) { GList *new_tail; CajaFileChange *result; g_assert (queue != NULL); /* dequeue the tail item while locking down the list */ #if GLIB_CHECK_VERSION (2, 32, 0) MUTEX_LOCK (&queue->mutex); #else MUTEX_LOCK (queue->mutex); #endif if (queue->tail == NULL) { result = NULL; } else { new_tail = queue->tail->prev; result = queue->tail->data; queue->head = g_list_remove_link (queue->head, queue->tail); g_list_free_1 (queue->tail); queue->tail = new_tail; } #if GLIB_CHECK_VERSION (2, 32, 0) MUTEX_UNLOCK (&queue->mutex); #else MUTEX_UNLOCK (queue->mutex); #endif return result; } enum { CONSUME_CHANGES_MAX_CHUNK = 20 }; static void pairs_list_free (GList *pairs) { GList *p; GFilePair *pair; /* deep delete the list of pairs */ for (p = pairs; p != NULL; p = p->next) { /* delete the strings in each pair */ pair = p->data; g_object_unref (pair->from); g_object_unref (pair->to); } /* delete the list and the now empty pair structs */ g_list_free_full (pairs, g_free); } static void position_set_list_free (GList *list) { GList *p; CajaFileChangesQueuePosition *item; for (p = list; p != NULL; p = p->next) { item = p->data; g_object_unref (item->location); } /* delete the list and the now empty structs */ g_list_free_full (list, g_free); } /* go through changes in the change queue, send ones with the same kind * in a list to the different caja_directory_notify calls */ void caja_file_changes_consume_changes (gboolean consume_all) { CajaFileChange *change; GList *additions, *changes, *deletions, *moves; GList *position_set_requests; GFilePair *pair; CajaFileChangesQueuePosition *position_set; guint chunk_count; CajaFileChangesQueue *queue; gboolean flush_needed; additions = NULL; changes = NULL; deletions = NULL; moves = NULL; position_set_requests = NULL; queue = caja_file_changes_queue_get(); /* Consume changes from the queue, stuffing them into one of three lists, * keep doing it while the changes are of the same kind, then send them off. * This is to ensure that the changes get sent off in the same order that they * arrived. */ for (chunk_count = 0; ; chunk_count++) { change = caja_file_changes_queue_get_change (queue); /* figure out if we need to flush the pending changes that we collected sofar */ if (change == NULL) { flush_needed = TRUE; /* no changes left, flush everything */ } else { flush_needed = additions != NULL && change->kind != CHANGE_FILE_ADDED && change->kind != CHANGE_POSITION_SET && change->kind != CHANGE_POSITION_REMOVE; flush_needed |= changes != NULL && change->kind != CHANGE_FILE_CHANGED; flush_needed |= moves != NULL && change->kind != CHANGE_FILE_MOVED && change->kind != CHANGE_POSITION_SET && change->kind != CHANGE_POSITION_REMOVE; flush_needed |= deletions != NULL && change->kind != CHANGE_FILE_REMOVED; flush_needed |= position_set_requests != NULL && change->kind != CHANGE_POSITION_SET && change->kind != CHANGE_POSITION_REMOVE && change->kind != CHANGE_FILE_ADDED && change->kind != CHANGE_FILE_MOVED; flush_needed |= !consume_all && chunk_count >= CONSUME_CHANGES_MAX_CHUNK; /* we have reached the chunk maximum */ } if (flush_needed) { /* Send changes we collected off. * At one time we may only have one of the lists * contain changes. */ if (deletions != NULL) { deletions = g_list_reverse (deletions); caja_directory_notify_files_removed (deletions); g_list_free_full (deletions, g_object_unref); deletions = NULL; } if (moves != NULL) { moves = g_list_reverse (moves); caja_directory_notify_files_moved (moves); pairs_list_free (moves); moves = NULL; } if (additions != NULL) { additions = g_list_reverse (additions); caja_directory_notify_files_added (additions); g_list_free_full (additions, g_object_unref); additions = NULL; } if (changes != NULL) { changes = g_list_reverse (changes); caja_directory_notify_files_changed (changes); g_list_free_full (changes, g_object_unref); changes = NULL; } if (position_set_requests != NULL) { position_set_requests = g_list_reverse (position_set_requests); caja_directory_schedule_position_set (position_set_requests); position_set_list_free (position_set_requests); position_set_requests = NULL; } } if (change == NULL) { /* we are done */ return; } /* add the new change to the list */ switch (change->kind) { case CHANGE_FILE_ADDED: additions = g_list_prepend (additions, change->from); break; case CHANGE_FILE_CHANGED: changes = g_list_prepend (changes, change->from); break; case CHANGE_FILE_REMOVED: deletions = g_list_prepend (deletions, change->from); break; case CHANGE_FILE_MOVED: pair = g_new (GFilePair, 1); pair->from = change->from; pair->to = change->to; moves = g_list_prepend (moves, pair); break; case CHANGE_POSITION_SET: position_set = g_new (CajaFileChangesQueuePosition, 1); position_set->location = change->from; position_set->set = TRUE; position_set->point = change->point; position_set->screen = change->screen; position_set_requests = g_list_prepend (position_set_requests, position_set); break; case CHANGE_POSITION_REMOVE: position_set = g_new (CajaFileChangesQueuePosition, 1); position_set->location = change->from; position_set->set = FALSE; position_set_requests = g_list_prepend (position_set_requests, position_set); break; default: g_assert_not_reached (); break; } g_free (change); } }