[PATCH] Improve performance of list view in single-click mode



The attached patches resolve performance problems and X cursor leak,
both of which occur when using list view in single-click mode.

I've reported this problem as bug #332669
http://bugzilla.gnome.org/show_bug.cgi?id=332669 and attached patches
for Nautilus 2.12 to the bug report.

The attached patches are intended for nautilus 2.13/HEAD.


Regards
Zbigniew

--- ChangeLog	26 Feb 2006 17:01:58 -0000
+++ ChangeLog	26 Feb 2006 17:21:44 -0000
@@ -0,0 +1,30 @@
+2006-02-26  Zbigniew Chyla  <mail zbigniew chyla pl>
+
+	Avoid heavy operations inside "motion_notify_event" handler: don't
+	call gtk_tree_model_row_changed (which causes updating the whole
+	view), don't create new mouse cursor, change cursor only when
+	necessary.
+	As a bonus we avoid leaking hand cursor in nautilus and X server.
+
+	* src/file-manager/fm-list-view.c:
+	(hand_cursor): new global variable for storing hand cursor used in
+	single click mode
+	(motion_notify_callback): don't call gtk_tree_model_row_changed on
+	the model when changing rows, it's very slow and unnecessary - GTK+
+	automatically invalidates parts of the view occupied by old and new
+	row; don't leak the hand cursor (GdkCursor), change mouse cursor
+	only when necessary (from GDK_HAND2 to default and the other way
+	round)
+	(leave_notify_callback): don't call gtk_tree_model_row_changed on
+	the current row in the model, GTK+ automatically redraws the current
+	row
+	(enter_notify_callback): in case single click mode is used, update
+	details->hover_path and set hand cursor if necessary
+	(create_and_set_up_tree_view): connect to enter_notify_event signal
+	in addition to leave_notify_event.
+	(fm_list_view_click_policy_changed): unref global hand_cursor and
+	set it to NULL when changing to double click mode, create new cursor
+	and assign it to hand_cursor when changing to single click one.
+	(fm_list_view_finalize): free details->hover_path if necessary
+	(fm_list_view_init): explicitly set details->hover_path to NULL
+
--- src/file-manager/fm-list-view.c	21 Feb 2006 15:10:43 -0000
+++ src/file-manager/fm-list-view.c	26 Feb 2006 17:21:46 -0000
@@ -141,6 +141,7 @@ static gboolean			default_sort_reversed_
 static NautilusZoomLevel        default_zoom_level_auto_value;
 static GList *                  default_visible_columns_auto_value;
 static GList *                  default_column_order_auto_value;
+static GdkCursor *              hand_cursor = NULL;
 
 static GList *fm_list_view_get_selection                   (FMDirectoryView   *view);
 static GList *fm_list_view_get_selection_for_file_transfer (FMDirectoryView   *view);
@@ -457,9 +458,6 @@ motion_notify_callback (GtkWidget *widge
 {
 	FMListView *view;
 	GdkDragContext *context;
-	GdkCursor *cursor;
-	GtkTreePath *last_hover_path;
-	GtkTreeIter iter;
 	
 	view = FM_LIST_VIEW (callback_data);
 	
@@ -468,42 +466,25 @@ motion_notify_callback (GtkWidget *widge
 	}
 
 	if (click_policy_auto_value == NAUTILUS_CLICK_POLICY_SINGLE) {
-		last_hover_path = view->details->hover_path;
+		GtkTreePath *old_hover_path;
 
+		old_hover_path = view->details->hover_path;
 		gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
 					       event->x, event->y,
 					       &view->details->hover_path,
 					       NULL, NULL, NULL);
 
-		if (view->details->hover_path != NULL) {
-			cursor = gdk_cursor_new (GDK_HAND2);
-		} else {
-			cursor = NULL;
-		}
-
-		gdk_window_set_cursor (widget->window, cursor);
-
-		/* only redraw if the hover row has changed */
-		if (!(last_hover_path == NULL && view->details->hover_path == NULL) &&
-		    (!(last_hover_path != NULL && view->details->hover_path != NULL) ||
-		     gtk_tree_path_compare (last_hover_path, view->details->hover_path))) {
-			if (last_hover_path) {
-				if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
-							     &iter, last_hover_path)) {
-					gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
-								    last_hover_path, &iter);
-				}
-			}
-
-			if (view->details->hover_path) {
-				gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
-							 &iter, view->details->hover_path);
-				gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
-							    view->details->hover_path, &iter);
+		if ((old_hover_path != NULL) != (view->details->hover_path != NULL)) {
+			if (view->details->hover_path != NULL) {
+				gdk_window_set_cursor (widget->window, hand_cursor);
+			} else {
+				gdk_window_set_cursor (widget->window, NULL);
 			}
 		}
 
-		gtk_tree_path_free (last_hover_path);
+		if (old_hover_path != NULL) {
+			gtk_tree_path_free (old_hover_path);
+		}
 	}
 
 	if (view->details->drag_button != 0) {
@@ -531,22 +512,40 @@ leave_notify_callback (GtkWidget *widget
 		       gpointer callback_data)
 {
 	FMListView *view;
-	GtkTreeIter iter;
 
 	view = FM_LIST_VIEW (callback_data);
 
 	if (click_policy_auto_value == NAUTILUS_CLICK_POLICY_SINGLE &&
 	    view->details->hover_path != NULL) {
-		if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
-					     &iter, view->details->hover_path)) {
-			gtk_tree_model_row_changed (GTK_TREE_MODEL (view->details->model),
-						    view->details->hover_path, &iter);
-		}
-
 		gtk_tree_path_free (view->details->hover_path);
 		view->details->hover_path = NULL;
+	}
 
-		return TRUE;
+	return FALSE;
+}
+
+static gboolean
+enter_notify_callback (GtkWidget *widget,
+		       GdkEventCrossing *event,
+		       gpointer callback_data)
+{
+	FMListView *view;
+
+	view = FM_LIST_VIEW (callback_data);
+
+	if (click_policy_auto_value == NAUTILUS_CLICK_POLICY_SINGLE) {
+		if (view->details->hover_path != NULL) {
+			gtk_tree_path_free (view->details->hover_path);
+		}
+
+		gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+					       event->x, event->y,
+					       &view->details->hover_path,
+					       NULL, NULL, NULL);
+
+		if (view->details->hover_path != NULL) {
+			gdk_window_set_cursor (widget->window, hand_cursor);
+		}
 	}
 
 	return FALSE;
@@ -1234,6 +1233,8 @@ create_and_set_up_tree_view (FMListView 
 				 G_CALLBACK (drag_data_get_callback), view, 0);
 	g_signal_connect_object (view->details->tree_view, "motion_notify_event",
 				 G_CALLBACK (motion_notify_callback), view, 0);
+	g_signal_connect_object (view->details->tree_view, "enter_notify_event",
+				 G_CALLBACK (enter_notify_callback), view, 0);
 	g_signal_connect_object (view->details->tree_view, "leave_notify_event",
 				 G_CALLBACK (leave_notify_callback), view, 0);
 	g_signal_connect_object (view->details->tree_view, "button_press_event",
@@ -2288,10 +2289,10 @@ fm_list_view_click_policy_changed (FMDir
 	GtkTreeIter iter;
 	GtkTreeView *tree;
 
+	view = FM_LIST_VIEW (directory_view);
+
 	/* ensure that we unset the hand cursor and refresh underlined rows */
 	if (click_policy_auto_value == NAUTILUS_CLICK_POLICY_DOUBLE) {
-		view = FM_LIST_VIEW (directory_view);
-		
 		if (view->details->hover_path != NULL) {
 			if (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->model),
 						     &iter, view->details->hover_path)) {
@@ -2313,7 +2314,15 @@ fm_list_view_click_policy_changed (FMDir
 				gdk_display_flush (display);
 			}
 		}
-		
+
+		if (hand_cursor != NULL) {
+			gdk_cursor_unref(hand_cursor);
+			hand_cursor = NULL;
+		}
+	} else if (click_policy_auto_value == NAUTILUS_CLICK_POLICY_SINGLE) {
+		if (hand_cursor == NULL) {
+			hand_cursor = gdk_cursor_new(GDK_HAND2);
+		}
 	}
 }
 
@@ -2457,6 +2466,10 @@ fm_list_view_finalize (GObject *object)
 	g_list_free (list_view->details->cells);
 	g_hash_table_destroy (list_view->details->columns);
 
+	if (list_view->details->hover_path != NULL) {
+		gtk_tree_path_free (list_view->details->hover_path);
+	}
+
 	g_free (list_view->details);
 
 	G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -2663,6 +2676,8 @@ fm_list_view_init (FMListView *list_view
 	
 	/* ensure that the zoom level is always set in begin_loading */
 	list_view->details->zoom_level = NAUTILUS_ZOOM_LEVEL_SMALLEST - 1;
+
+	list_view->details->hover_path = NULL;
 }
 
 static NautilusView *
--- ChangeLog	26 Feb 2006 17:01:58 -0000
+++ ChangeLog	26 Feb 2006 17:23:40 -0000
@@ -0,0 +1,30 @@
+2006-02-26  Zbigniew Chyla  <mail zbigniew chyla pl>
+
+	Don't add all possible columns to GtkTreeView when initializing
+	FMListView, only the ones we actually show. This way we avoid getting
+	unnecessary data from the model, e.g. quite heavy "owner" and "group"
+	columns with the default settings.
+
+	* src/file-manager/fm-list-view.c:
+	(apply_visible_columns_foreach, apply_visible_columns,
+	apply_column_order): removed
+	(apply_columns_settings): new function, updates a list of columns in
+	GtkTreeView given both a list of visible columns and their ordering,
+	replacement for apply_visible_columns and apply_column_order.
+	(create_and_set_up_tree_view): when creating details->columns hash
+	table, pass g_object_unref as value_destroy_func; before adding
+	columns to the hash call g_object_ref+gtk_object_sink on them, don't
+	add columns to the view, the appropriate columns will be added when
+	applying the configuration; in order to apply initial columns
+	settings, call apply_columns_settings (instead of
+	apply_visible_columns + apply_column_order).
+	(set_visible_columns_from_metadata_and_preferences,
+	set_column_order_from_metadata_and_preferences): removed
+	(set_columns_settings_from_metadata_and_preferences): new, replacement
+	for set_visible_columns_* and set_column_order_*.
+	(fm_list_view_begin_loading, column_chooser_changed_callback,
+	column_chooser_use_default_callback, fm_list_view_reset_to_defaults,
+	default_visible_columns_changed_callback,
+	default_column_order_changed_callback): use newly added
+	set_columns_settings_from_metadata_and_preferences.
+
--- src/file-manager/fm-list-view.c	21 Feb 2006 15:10:43 -0000
+++ src/file-manager/fm-list-view.c	26 Feb 2006 17:23:41 -0000
@@ -1087,57 +1087,57 @@ move_copy_items_callback (NautilusTreeVi
 }
 
 static void
-apply_visible_columns_foreach (gpointer key, gpointer value, gpointer user_data)
+apply_columns_settings (FMListView *list_view, GList *column_order, GList *visible_columns)
 {
-	gboolean visible;
-	
-	visible = (eel_g_str_list_index (user_data, key) != -1);
-	
-	gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN (value), 
-					  visible);
-}
-
-static void
-apply_visible_columns (FMListView *list_view, GList *visible_columns)
-{
-	g_hash_table_foreach (list_view->details->columns, 
-			      apply_visible_columns_foreach,
-			      visible_columns);
-	
-}
-
-static void
-apply_column_order (FMListView *list_view, GList *column_order)
-{
-	GList *columns;	
-	GtkTreeViewColumn *last_view_column;
+	GList *all_columns;
+	GList *old_view_columns, *view_columns;
+	GtkTreeViewColumn *prev_view_column;
 	GList *l;
-	
-	columns = nautilus_get_all_columns ();
-	columns = nautilus_sort_columns (columns, column_order);
 
-	last_view_column = NULL;
-	for (l = columns; l != NULL; l = l->next) {
-		GtkTreeViewColumn *view_column;
+	/* prepare ordered list of view columns using column_order and visible_columns */
+	view_columns = NULL;
+	all_columns = nautilus_get_all_columns ();
+	all_columns = nautilus_sort_columns (all_columns, column_order);
+	for (l = all_columns; l != NULL; l = l->next) {
 		char *name;
 
 		g_object_get (G_OBJECT (l->data), "name", &name, NULL);
-		
-		view_column = g_hash_table_lookup (list_view->details->columns,
-						   name);
+		if (g_list_find_custom (visible_columns, name, (GCompareFunc) g_ascii_strcasecmp) != NULL) {
+			GtkTreeViewColumn *view_column;
 
+			view_column = g_hash_table_lookup (list_view->details->columns, name);
+			if (view_column != NULL) {
+				view_columns = g_list_append (view_columns, view_column);
+			}
+		}
 		g_free (name);
-		
-		if (view_column) {
-			gtk_tree_view_move_column_after 
-				(list_view->details->tree_view,
-				 view_column,
-				 last_view_column);
-			
-			last_view_column = view_column;
+	}
+	nautilus_column_list_free (all_columns);
+
+	/* remove columns that are not present in the configuration */
+	old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+	for (l = old_view_columns; l != NULL; l = l->next) {
+		if (g_list_find (view_columns, l->data) == NULL) {
+			gtk_tree_view_remove_column (list_view->details->tree_view, l->data);
 		}
 	}
-	nautilus_column_list_free (columns);
+	g_list_free (old_view_columns);
+
+	/* append new columns from the configuration */
+	old_view_columns = gtk_tree_view_get_columns (list_view->details->tree_view);
+	for (l = view_columns; l != NULL; l = l->next) {
+		if (g_list_find (old_view_columns, l->data) == NULL) {
+			gtk_tree_view_append_column (list_view->details->tree_view, l->data);
+		}
+	}
+	g_list_free (old_view_columns);
+
+	/* place columns in the correct order */
+	prev_view_column = NULL;
+	for (l = view_columns; l != NULL; l = l->next) {
+		gtk_tree_view_move_column_after (list_view->details->tree_view, l->data, prev_view_column);
+		prev_view_column = l->data;
+	}
 }
 
 static void
@@ -1193,7 +1193,7 @@ create_and_set_up_tree_view (FMListView 
 	view->details->columns = g_hash_table_new_full (g_str_hash, 
 							g_str_equal,
 							(GDestroyNotify)g_free,
-							NULL);
+							(GDestroyNotify) g_object_unref);
 	gtk_tree_view_set_enable_search (view->details->tree_view, TRUE);
 
 	/* Don't handle backspace key. It's used to open the parent folder. */
@@ -1295,6 +1295,8 @@ create_and_set_up_tree_view (FMListView 
 			view->details->pixbuf_cell = (GtkCellRendererPixbuf *)cell;
 			
 			view->details->file_name_column = gtk_tree_view_column_new ();
+			g_object_ref (view->details->file_name_column);
+			gtk_object_sink (GTK_OBJECT (view->details->file_name_column));
 			view->details->file_name_column_num = column_num;
 			
 			g_hash_table_insert (view->details->columns,
@@ -1323,7 +1325,6 @@ create_and_set_up_tree_view (FMListView 
 			gtk_tree_view_column_set_cell_data_func (view->details->file_name_column, cell,
 								 (GtkTreeCellDataFunc) filename_cell_data_func,
 								 view, NULL);
-			gtk_tree_view_append_column (view->details->tree_view, view->details->file_name_column);
 		} else {		
 			cell = gtk_cell_renderer_text_new ();
 			g_object_set (cell, "xalign", xalign, NULL);
@@ -1333,6 +1334,8 @@ create_and_set_up_tree_view (FMListView 
 									   cell,
 									   "text", column_num,
 									   NULL);
+			g_object_ref (column);
+			gtk_object_sink (GTK_OBJECT (column));
 			gtk_tree_view_column_set_sort_column_id (column, column_num);
 			g_hash_table_insert (view->details->columns, 
 					     g_strdup (name), 
@@ -1340,7 +1343,6 @@ create_and_set_up_tree_view (FMListView 
 			
 			gtk_tree_view_column_set_resizable (column, TRUE);
 			gtk_tree_view_column_set_visible (column, TRUE);
-			gtk_tree_view_append_column (view->details->tree_view, column);
 		}
 		g_free (name);
 		g_free (label);
@@ -1350,8 +1352,7 @@ create_and_set_up_tree_view (FMListView 
 	/* Apply the default column order and visible columns, to get it
 	 * right most of the time. The metadata will be checked when a 
 	 * folder is loaded */
-	apply_visible_columns (view, default_visible_columns_auto_value);
-	apply_column_order (view, default_column_order_auto_value);
+	apply_columns_settings (view, default_column_order_auto_value, default_visible_columns_auto_value);
 
 	gtk_widget_show (GTK_WIDGET (view->details->tree_view));
 	gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view));
@@ -1390,16 +1391,6 @@ get_visible_columns (FMListView *list_vi
 	return visible_columns;
 }
 
-static void
-set_visible_columns_from_metadata_and_preferences (FMListView *list_view)
-{
-	GList *visible_columns;
-
-	visible_columns = get_visible_columns (list_view);
-	apply_visible_columns (list_view, visible_columns);
-	eel_g_list_free_deep (visible_columns);
-}
-
 static GList *
 get_column_order (FMListView *list_view)
 {
@@ -1421,13 +1412,18 @@ get_column_order (FMListView *list_view)
 }
 
 static void
-set_column_order_from_metadata_and_preferences (FMListView *list_view)
+set_columns_settings_from_metadata_and_preferences (FMListView *list_view)
 {
 	GList *column_order;
+	GList *visible_columns;
 
 	column_order = get_column_order (list_view);
-	apply_column_order (list_view, column_order);
+	visible_columns = get_visible_columns (list_view);
+
+	apply_columns_settings (list_view, column_order, visible_columns);
+
 	eel_g_list_free_deep (column_order);
+	eel_g_list_free_deep (visible_columns);
 }
 
 static void
@@ -1509,8 +1505,7 @@ fm_list_view_begin_loading (FMDirectoryV
 
 	set_sort_order_from_metadata_and_preferences (list_view);
 	set_zoom_level_from_metadata_and_preferences (list_view);
-	set_visible_columns_from_metadata_and_preferences (list_view);
-	set_column_order_from_metadata_and_preferences (list_view);
+	set_columns_settings_from_metadata_and_preferences (list_view);
 }
 
 static void
@@ -1867,8 +1862,7 @@ column_chooser_changed_callback (Nautilu
 	eel_g_list_free_deep (visible_columns);
 	eel_g_list_free_deep (column_order);
 
-	set_visible_columns_from_metadata_and_preferences (view);
-	set_column_order_from_metadata_and_preferences (view);
+	set_columns_settings_from_metadata_and_preferences (view);
 }
 
 static void
@@ -1907,8 +1901,7 @@ column_chooser_use_default_callback (Nau
 	nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NAUTILUS_METADATA_SUBKEY_COLUMNS, NULL);
 	nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NAUTILUS_METADATA_SUBKEY_COLUMNS, NULL);
 
-	set_visible_columns_from_metadata_and_preferences (FM_LIST_VIEW (view));
-	set_column_order_from_metadata_and_preferences (FM_LIST_VIEW (view));
+	set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view));
 	column_chooser_set_from_settings (chooser, view);
 }
 
@@ -2056,8 +2049,7 @@ fm_list_view_reset_to_defaults (FMDirect
 		 default_sort_reversed_auto_value ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING);
 
 	fm_list_view_set_zoom_level (FM_LIST_VIEW (view), get_default_zoom_level (), FALSE);
-	set_visible_columns_from_metadata_and_preferences (FM_LIST_VIEW (view));
-	set_column_order_from_metadata_and_preferences (FM_LIST_VIEW (view));
+	set_columns_settings_from_metadata_and_preferences (FM_LIST_VIEW (view));
 }
 
 static void
@@ -2356,8 +2348,8 @@ default_visible_columns_changed_callback
 	FMListView *list_view;
 	
 	list_view = FM_LIST_VIEW (callback_data);
-	
-	set_visible_columns_from_metadata_and_preferences (list_view);
+
+	set_columns_settings_from_metadata_and_preferences (list_view);	
 }
 
 static void
@@ -2366,8 +2358,8 @@ default_column_order_changed_callback (g
 	FMListView *list_view;
 	
 	list_view = FM_LIST_VIEW (callback_data);
-	
-	set_column_order_from_metadata_and_preferences (list_view);
+
+	set_columns_settings_from_metadata_and_preferences (list_view);
 }
 
 static void
--- ChangeLog	26 Feb 2006 15:15:36 -0000
+++ ChangeLog	26 Feb 2006 17:02:07 -0000
@@ -0,0 +1,20 @@
+2006-02-26  Zbigniew Chyla  <mail zbigniew chyla pl>
+
+	Get users' and groups' names via a cache to avoid calling
+	getpwuid/getgrgid too often (i.e. many times during single redraw).
+
+	* libnautilus-private/Makefile.am: (libnautilus_private_la_SOURCES):
+	Added nautilus-users-groups-cache.[ch]
+	* libnautilus-private/nautilus-users-groups-cache.[ch]: New,
+	implementation of cache for getpwuid/getgrgid results.
+	* libnautilus-private/nautilus-file.c:
+	(get_user_name_from_id): removed
+	(get_real_name): accept "name + gecos" pair instead of "struct passwd".
+	(get_user_and_real_name_from_id): get user name and gecos using
+	nautilus_users_cache_* functions instead of using getpwuid directly.
+	(nautilus_get_user_names): adjusted to changed signature of get_real_name.
+	(nautilus_file_get_group_name): get group name using
+	nautilus_groups_cache_get_name instead of using getgrgid directly.
+	(nautilus_file_get_owner_as_string): get user name using
+	nautilus_users_cache_get_name.
+
--- libnautilus-private/Makefile.am	16 Jan 2006 09:30:58 -0000
+++ libnautilus-private/Makefile.am	26 Feb 2006 17:02:07 -0000
@@ -193,6 +193,8 @@ libnautilus_private_la_SOURCES = \
 	nautilus-undo-transaction.h \
 	nautilus-undo.c \
 	nautilus-undo.h \
+	nautilus-users-groups-cache.c \
+	nautilus-users-groups-cache.h \
 	nautilus-vfs-directory.c \
 	nautilus-vfs-directory.h \
 	nautilus-vfs-file.c \
--- libnautilus-private/nautilus-file.c	21 Feb 2006 22:37:22 -0000
+++ libnautilus-private/nautilus-file.c	26 Feb 2006 17:02:09 -0000
@@ -45,6 +45,7 @@
 #include "nautilus-thumbnails.h"
 #include "nautilus-trash-directory.h"
 #include "nautilus-trash-file.h"
+#include "nautilus-users-groups-cache.h"
 #include "nautilus-vfs-file.h"
 #include "nautilus-saved-search-file.h"
 #include <eel/eel-debug.h>
@@ -3537,30 +3538,15 @@ nautilus_file_set_permissions (NautilusF
 }
 
 static char *
-get_user_name_from_id (uid_t uid)
-{
-	struct passwd *password_info;
-	
-	/* No need to free result of getpwuid */
-	password_info = getpwuid (uid);
-
-	if (password_info == NULL) {
-		return NULL;
-	}
-	
-	return g_strdup (password_info->pw_name);
-}
-
-static char *
-get_real_name (struct passwd *user)
+get_real_name (const char *name, const char *gecos)
 {
 	char *locale_string, *part_before_comma, *capitalized_login_name, *real_name;
 
-	if (user->pw_gecos == NULL) {
+	if (gecos == NULL) {
 		return NULL;
 	}
 
-	locale_string = eel_str_strip_substring_and_after (user->pw_gecos, ",");
+	locale_string = eel_str_strip_substring_and_after (gecos, ",");
 	if (!g_utf8_validate (locale_string, -1, NULL)) {
 		part_before_comma = g_locale_to_utf8 (locale_string, -1, NULL, NULL, NULL);
 		g_free (locale_string);
@@ -3568,10 +3554,10 @@ get_real_name (struct passwd *user)
 		part_before_comma = locale_string;
 	}
 
-	if (!g_utf8_validate (user->pw_name, -1, NULL)) {
-		locale_string = g_locale_to_utf8 (user->pw_name, -1, NULL, NULL, NULL);
+	if (!g_utf8_validate (name, -1, NULL)) {
+		locale_string = g_locale_to_utf8 (name, -1, NULL, NULL, NULL);
 	} else {
-		locale_string = g_strdup (user->pw_name);
+		locale_string = g_strdup (name);
 	}
 	
 	capitalized_login_name = eel_str_capitalize (locale_string);
@@ -3587,7 +3573,7 @@ get_real_name (struct passwd *user)
 
 
 	if (eel_str_is_empty (real_name)
-	    || eel_strcmp (user->pw_name, real_name) == 0
+	    || eel_strcmp (name, real_name) == 0
 	    || eel_strcmp (capitalized_login_name, real_name) == 0) {
 		g_free (real_name);
 		real_name = NULL;
@@ -3601,25 +3587,23 @@ get_real_name (struct passwd *user)
 static char *
 get_user_and_real_name_from_id (uid_t uid)
 {
+	char *name, *gecos;
 	char *real_name, *user_and_real_name;
-	struct passwd *password_info;
 	
-	/* No need to free result of getpwuid */
-	password_info = getpwuid (uid);
+	name = nautilus_users_cache_get_name (uid);
+	gecos = nautilus_users_cache_get_gecos (uid);
 
-	if (password_info == NULL) {
-		return NULL;
-	}
-
-	real_name = get_real_name (password_info);
+	real_name = get_real_name (name, gecos);
 	if (real_name != NULL) {
-		user_and_real_name = g_strdup_printf
-			("%s - %s", password_info->pw_name, real_name);
+		user_and_real_name = g_strdup_printf ("%s - %s", name, real_name);
 	} else {
-		user_and_real_name = g_strdup (password_info->pw_name);
+		user_and_real_name = g_strdup (name);
 	}
 	g_free (real_name);
 
+	g_free (name);
+	g_free (gecos);
+
 	return user_and_real_name;
 }
 
@@ -3893,7 +3877,7 @@ nautilus_get_user_names (void)
 	setpwent ();
 
 	while ((user = getpwent ()) != NULL) {
-		real_name = get_real_name (user);
+		real_name = get_real_name (user->pw_name, user->pw_gecos);
 		if (real_name != NULL) {
 			name = g_strconcat (user->pw_name, "\n", real_name, NULL);
 		} else {
@@ -3939,24 +3923,22 @@ nautilus_file_can_get_group (NautilusFil
 char *
 nautilus_file_get_group_name (NautilusFile *file)
 {
-	struct group *group_info;
+	char *group_name;
 
 	/* Before we have info on a file, the owner is unknown. */
 	if (nautilus_file_info_missing (file, GNOME_VFS_FILE_INFO_FIELDS_IDS)) {
 		return NULL;
 	}
 
-	/* No need to free result of getgrgid */
-	group_info = getgrgid ((gid_t) file->details->info->gid);
-
-	if (group_info != NULL) {
-		return g_strdup (group_info->gr_name);
+	group_name = nautilus_groups_cache_get_name ((gid_t) file->details->info->gid);
+	if (group_name == NULL) {
+		/* In the oddball case that the group name has been set to an id for which
+		 * there is no defined group, return the id in string form.
+		 */
+		group_name = g_strdup_printf ("%d", file->details->info->gid);
 	}
-	
-	/* In the oddball case that the group name has been set to an id for which
-	 * there is no defined group, return the id in string form.
-	 */
-	return g_strdup_printf ("%d", file->details->info->gid);
+
+	return group_name;
 }
 
 /**
@@ -4252,17 +4234,17 @@ nautilus_file_get_owner_as_string (Nauti
 	if (include_real_name) {
 		user_name = get_user_and_real_name_from_id (file->details->info->uid);
 	} else {
-		user_name = get_user_name_from_id (file->details->info->uid);
+		user_name = nautilus_users_cache_get_name (file->details->info->uid);
 	}
 
-	if (user_name != NULL) {
-		return user_name;
+	if (user_name == NULL) {
+		/* In the oddball case that the user name has been set to an id for which
+		 * there is no defined user, return the id in string form.
+		 */
+		user_name = g_strdup_printf ("%d", file->details->info->uid);
 	}
 
-	/* In the oddball case that the user name has been set to an id for which
-	 * there is no defined user, return the id in string form.
-	 */
-	return g_strdup_printf ("%d", file->details->info->uid);
+	return user_name;
 }
 
 static char *
--- libnautilus-private/nautilus-users-groups-cache.h	1970-01-01 01:00:00.000000000 +0100
+++ libnautilus-private/nautilus-users-groups-cache.h	2006-02-26 17:59:02.000000000 +0100
@@ -0,0 +1,34 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+   nautilus-users-groups-cache.h: cache of users' and groups' names.
+ 
+   Copyright (C) 2006 Zbigniew Chyla
+  
+   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., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+  
+   Author: Zbigniew Chyla <mail zbigniew chyla pl>
+*/
+
+#ifndef NAUTILUS_USERS_GROUPS_CACHE_H
+#define NAUTILUS_USERS_GROUPS_CACHE_H
+
+#include <sys/types.h>
+
+char *nautilus_users_cache_get_name (uid_t uid);
+char *nautilus_users_cache_get_gecos (uid_t uid);
+char *nautilus_groups_cache_get_name (gid_t gid);
+
+#endif /* NAUTILUS_USERS_GROUPS_CACHE_H */
--- libnautilus-private/nautilus-users-groups-cache.c	1970-01-01 01:00:00.000000000 +0100
+++ libnautilus-private/nautilus-users-groups-cache.c	2006-02-26 18:43:48.000000000 +0100
@@ -0,0 +1,277 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+   nautilus-users-groups-cache.c: cache of users' and groups' names.
+ 
+   Copyright (C) 2006 Zbigniew Chyla
+  
+   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., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+  
+   Author: Zbigniew Chyla <mail zbigniew chyla pl>
+*/
+
+#include <config.h>
+#include "nautilus-users-groups-cache.h"
+
+#include <glib.h>
+#include <grp.h>
+#include <pwd.h>
+
+
+typedef struct _ExpiringCache ExpiringCache;
+
+/* times in milliseconds */
+#define USERS_CACHE_EXPIRE_TIME   (60 * 1000)
+#define GROUPS_CACHE_EXPIRE_TIME  (60 * 1000)
+
+/* cache of users' names */
+static ExpiringCache *users_cache = NULL;
+
+/* cache of groups' names */
+static ExpiringCache *groups_cache = NULL;
+
+
+/**
+ * Generic implementation of cache with guint keys and values which expire
+ * after specified amount of time.
+ */
+
+typedef gpointer (*ExpiringCacheGetValFunc) (guint key);
+
+
+struct _ExpiringCache
+{
+	/* Expiration time of cached value */
+	time_t expire_time;
+
+	/* Called to obtain a value by a key */
+	ExpiringCacheGetValFunc get_value_func;
+
+	/* Called to destroy a value */
+	GDestroyNotify value_destroy_func;
+
+	/* Stores cached values */
+	GHashTable *cached_values;
+};
+
+
+typedef struct _ExpiringCacheEntry ExpiringCacheEntry;
+
+struct _ExpiringCacheEntry
+{
+	ExpiringCache *cache;
+	guint key;
+	gpointer value;
+};
+
+
+static ExpiringCache *
+expiring_cache_new (time_t expire_time, ExpiringCacheGetValFunc get_value_func,
+                    GDestroyNotify value_destroy_func)
+{
+	ExpiringCache *cache;
+
+	g_return_val_if_fail (get_value_func != NULL, NULL);
+
+	cache = g_new (ExpiringCache, 1);
+	cache->expire_time = expire_time;
+	cache->get_value_func = get_value_func;
+	cache->value_destroy_func = value_destroy_func;
+	cache->cached_values = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+	return cache;
+}
+
+static ExpiringCacheEntry *
+expiring_cache_entry_new (ExpiringCache *cache, guint key, gpointer value)
+{
+	ExpiringCacheEntry *entry;
+
+	entry = g_slice_new (ExpiringCacheEntry);
+	entry->cache = cache;
+	entry->key = key;
+	entry->value = value;
+
+	return entry;
+}
+
+static void
+expiring_cache_entry_destroy (ExpiringCacheEntry *entry)
+{
+	if (entry->cache->value_destroy_func != NULL) {
+		entry->cache->value_destroy_func (entry->value);
+	}
+	g_slice_free (ExpiringCacheEntry, entry);
+}
+
+static gboolean
+cb_cache_entry_expired (ExpiringCacheEntry *entry)
+{
+	g_hash_table_remove (entry->cache->cached_values, GSIZE_TO_POINTER (entry->key));
+	expiring_cache_entry_destroy (entry);
+
+	return FALSE;
+}
+
+static gpointer
+expiring_cache_get_value (ExpiringCache *cache, guint key)
+{
+	ExpiringCacheEntry *entry;
+
+	g_return_val_if_fail (cache != NULL, NULL);
+
+	if (!g_hash_table_lookup_extended (cache->cached_values, GSIZE_TO_POINTER (key),
+	                                   NULL, (gpointer) &entry)) {
+		entry = expiring_cache_entry_new (cache, key, cache->get_value_func (key));
+		g_hash_table_insert (cache->cached_values, GSIZE_TO_POINTER (key), entry);
+		g_timeout_add (cache->expire_time, (GSourceFunc) cb_cache_entry_expired, entry);
+	}
+
+	return entry->value;
+}
+
+
+/*
+ * Cache of users' names based on ExpiringCache.
+ */
+
+typedef struct _UserInfo UserInfo;
+
+struct _UserInfo {
+	char *name;
+	char *gecos;
+};
+
+
+static UserInfo *
+user_info_new (struct passwd *password_info)
+{
+	UserInfo *uinfo;
+
+	if (password_info != NULL) {
+		uinfo = g_slice_new (UserInfo);
+		uinfo->name = g_strdup (password_info->pw_name);
+		uinfo->gecos = g_strdup (password_info->pw_gecos);
+	} else {
+		uinfo = NULL;
+	}
+
+	return uinfo;
+}
+
+static void
+user_info_free (UserInfo *uinfo)
+{
+	if (uinfo != NULL) {
+		g_free (uinfo->name);
+		g_free (uinfo->gecos);
+		g_slice_free (UserInfo, uinfo);
+	}
+}
+
+static gpointer
+users_cache_get_value (guint key)
+{
+	return user_info_new (getpwuid (key));
+}
+
+static UserInfo *
+get_cached_user_info(guint uid)
+{
+	if (users_cache == NULL) {
+		users_cache = expiring_cache_new (USERS_CACHE_EXPIRE_TIME, users_cache_get_value,
+		                                  (GDestroyNotify) user_info_free);
+	}
+
+	return expiring_cache_get_value (users_cache, uid);
+}
+
+/**
+ * nautilus_users_cache_get_name:
+ *
+ * Returns name of user with given uid (using cached data if possible) or
+ * NULL in case a user with given uid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+nautilus_users_cache_get_name (uid_t uid)
+{
+	UserInfo *uinfo;
+
+	uinfo = get_cached_user_info (uid);
+	if (uinfo != NULL) {
+		return g_strdup (uinfo->name);
+	} else {
+		return NULL;
+	}
+}
+
+/**
+ * nautilus_users_cache_get_gecos:
+ *
+ * Returns gecos of user with given uid (using cached data if possible) or
+ * NULL in case a user with given uid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+nautilus_users_cache_get_gecos (uid_t uid)
+{
+	UserInfo *uinfo;
+
+	uinfo = get_cached_user_info (uid);
+	if (uinfo != NULL) {
+		return g_strdup (uinfo->gecos);
+	} else {
+		return NULL;
+	}
+}
+
+/*
+ * Cache of groups' names based on ExpiringCache.
+ */
+
+static gpointer
+groups_cache_get_value (guint key)
+{
+	struct group *group_info;
+
+	group_info = getgrgid (GPOINTER_TO_SIZE (key));
+	if (group_info != NULL) {
+		return g_strdup (group_info->gr_name);
+	} else {
+		return NULL;
+	}
+}
+
+
+/**
+ * nautilus_groups_cache_get_name:
+ *
+ * Returns name of group with given gid (using cached data if possible) or
+ * NULL in case a group with given gid can't be found.
+ *
+ * Returns: Newly allocated string or NULL.
+ */
+char *
+nautilus_groups_cache_get_name (gid_t gid)
+{
+	if (groups_cache == NULL) {
+		groups_cache = expiring_cache_new (GROUPS_CACHE_EXPIRE_TIME, groups_cache_get_value, g_free);
+	}
+
+	return g_strdup (expiring_cache_get_value (groups_cache, gid));
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]