/* $Id: e2_filelist.c 856 2008-05-05 02:00:15Z tpgww $

Copyright (C) 2004-2008 tooar <tooar@gmx.net>.

This file is part of emelFM2.
emelFM2 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 3, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/e2_filelist.c
@brief directory content liststore functions

This file contains functions related to creation, filling and emptying a
liststore of directory content data, and fns for the associated model.
Also filtering of store content, and other related utilties.
*/

#include "e2_filelist.h"
#include <string.h>
#include <sys/time.h>
#include <langinfo.h>
#include <pwd.h>
#include <time.h>
#include <grp.h>
#include "e2_option.h"
#include "e2_task.h"
#include "e2_dialog.h"

#ifdef E2_SELTXT_RECOLOR
extern GdkColor selectedtext;
#endif

#ifdef E2_FAM_DNOTIFY
#include <signal.h>
extern sigset_t dnotify_signal_set;
#endif

//disable-refresh counter for both filelists
gint refresh_refcount;
//disable-refresh counter for single filelists
static gint refresh_active_refcount;
static gint refresh_inactive_refcount;
//refresh deferral counters
static gint pane1repeats = 0;
static gint pane2repeats = 0;

//static gboolean case_sensitive; //local copy of option value, for faster access

/**
@brief determine whether pane defined by @a pane is the active one
@param pane enumerator for pane to be checked
@return TRUE if @a pane is the active one
*/
static gboolean _e2_filelist_check_active_pane (E2_ListChoice pane)
{
	gboolean active;
	switch (pane)
	{
		case PANE1:
			active = (curr_view == &app.pane1_view);
			break;
		case PANE2:
			active = (curr_view == &app.pane2_view);
			break;
		case PANEINACTIVE:
			active = FALSE;
			break;
		default:
			active = TRUE;
			break;
	}
	return active;
}
/**
@brief disable refreshing of a single pane filelist
This is mainly intended to block changes of filesystem CWD when a process is
working in a displayed directory
@param pane enumerator for pane to be blocked
@return
*/
void e2_filelist_disable_one_refresh (E2_ListChoice pane)
{
	if (_e2_filelist_check_active_pane (pane))
	{
		refresh_active_refcount++;
		if (refresh_active_refcount == 1)
		{
			LISTS_LOCK
			curr_view->listcontrols.norefresh = TRUE;
			LISTS_UNLOCK
		}
	}
	else
	{
		refresh_inactive_refcount++;
		if (refresh_inactive_refcount == 1)
		{
			LISTS_LOCK
			other_view->listcontrols.norefresh = TRUE;
			LISTS_UNLOCK
		}
	}
}
/**
@brief enable refreshing of a single pane filelist

@param pane enumerator for pane to be unblocked

@return
*/
void e2_filelist_enable_one_refresh (E2_ListChoice pane)
{
	if (_e2_filelist_check_active_pane (pane))
	{
		refresh_active_refcount--;
		if (refresh_active_refcount == 0 && refresh_refcount == 0)
		{
			LISTS_LOCK
			curr_view->listcontrols.norefresh = FALSE;
			LISTS_UNLOCK
		}
		if (refresh_active_refcount < 0)
			refresh_active_refcount = 0;
	}
	else
	{
		refresh_inactive_refcount--;
		if (refresh_inactive_refcount == 0 && refresh_refcount == 0)
		{
			LISTS_LOCK
			other_view->listcontrols.norefresh = FALSE;
			LISTS_UNLOCK
		}
		if (refresh_inactive_refcount < 0)
			refresh_inactive_refcount = 0;
	}
}
/**
@brief disable -refresh action

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE always
*/
gboolean e2_filelist_disable_refresh_action (gpointer from, E2_ActionRuntime *art)
{
	e2_filelist_disable_refresh ();
	return TRUE;
}
/**
@brief enable -refresh action

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE always
*/
gboolean e2_filelist_enable_refresh_action (gpointer from, E2_ActionRuntime *art)
{
	e2_filelist_enable_refresh ();
	return TRUE;
}
/**
@brief reset polling of config data and pane filelists

@return
*/
void e2_filelist_reset_refresh (void)
{
	if (refresh_refcount != 0)
	{
		refresh_refcount = 1;
		e2_filelist_enable_refresh ();
	}
}
/* These functions must encapsulate any instructions that access the FileInfo
structs produced when a selection is interrogated, because a refresh in the
middle of such an operation would free FileInfo structs as a side-effect.
*/
/**
@brief  disable refreshing of pane filelists and config data

Increase ref count, if == 1, stop the timer process which calls the refresh fns
Why is config data check bundled here ?

@return
*/
void e2_filelist_disable_refresh (void)
{
	refresh_refcount++;
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, refresh ref now = %d", refresh_refcount);
#endif
	if (refresh_refcount == 1)
	{
		if (app.timers[REFRESHBEGIN_T] > 0)
		{
			//we're waiting for a pending refresh
			g_source_remove (app.timers[REFRESHBEGIN_T]);
			app.timers[REFRESHBEGIN_T] = 0;
		}
		if (e2_option_bool_get ("auto-refresh"))
		{
			//don't cancel check-dirty timer, to reduce risk that FAM
			//dirty-reports queue may over-fill
			LISTS_LOCK
			curr_view->listcontrols.norefresh = TRUE;
			other_view->listcontrols.norefresh = TRUE;
			LISTS_UNLOCK
		}
		if (e2_option_bool_get ("auto-refresh-config"))
		{
			//CHECKME just block this too?
			//(can't reasonably be many changes to the config file)
			if (app.timers[CONFIG_T] > 0)
			{
				g_source_remove (app.timers[CONFIG_T]);
				app.timers[CONFIG_T] = 0;
			}
		}

#ifndef E2_STATUS_DEMAND
		e2_window_disable_status_update ();
#endif
	}
}
/**
@brief re-enable polling of pane filelists and config data

Decrease ref count, if == 0, re-enable/restart the processes which detect
changes of filelist content and/or config data
Config timer is bundled here because the same func refreshes the panes

@return
*/
void e2_filelist_enable_refresh (void)
{
	refresh_refcount--;
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, refresh ref now = %d", refresh_refcount);
#endif
	if (refresh_refcount < 0)
	{
		printd (WARN, "The auto-refresh refresh count is < 0");
		gdk_threads_enter ();
		e2_window_output_show (NULL, NULL);
		e2_output_print_error (
		_("Something is wrong with the auto-refresh mechanism"), FALSE);
		gdk_threads_leave ();
	}

	if (refresh_refcount == 0)
	{
		if (e2_option_bool_get ("auto-refresh"))
		{
			LISTS_LOCK
			curr_view->listcontrols.norefresh = FALSE;
			other_view->listcontrols.norefresh = FALSE;
			LISTS_UNLOCK
		}
		//this timer is used, even with fam backends
		if (e2_option_bool_get ("auto-refresh-config"))
			app.timers[CONFIG_T] =
#ifdef USE_GLIB2_14
			g_timeout_add_seconds (E2_CONFIGCHECK_INTERVAL_S,
#else
			g_timeout_add (E2_CONFIGCHECK_INTERVAL,
#endif
				(GSourceFunc) e2_option_check_config_files, NULL);

#ifndef E2_STATUS_DEMAND
		e2_window_enable_status_update (-1);
#endif
	}
}
/**
@brief timer destroy function after cancelling filelists checking
@param data UNUSED data specified when the timer was established
@return
*/
static void _e2_filelist_timer_shutdown (gpointer data)
{
	app.timers[REFRESHBEGIN_T] = 0;
}
/**
@brief timer callback function which refreshes filelist(s) as appropriate, as soon as any block is gone
@param data pointerised value of current timer delay
@return FALSE when there's nothing left to refresh or more waiting is needed
*/
static gboolean _e2_filelist_refresh_manage (gpointer data)
{
	pthread_t thisID;
	gpointer result;
	gboolean cvdone, ovdone;
	LISTS_LOCK
	ViewInfo *cv = curr_view;	//log these, in case active pane is changed during this process
	ViewInfo *ov = other_view;
retest:
	if (cv->listcontrols.refresh_requested)
	{
		cvdone = !(
		cv->listcontrols.norefresh || //norefresh flags set when refresh is disabled
		cv->listcontrols.refresh_working ||
		cv->listcontrols.cd_working
#ifdef E2_STATUS_BLOCK
		|| app.status_working
#endif
		);
		if (cvdone)
		{
			LISTS_UNLOCK
#ifdef E2_VFSTMP
			//FIXME dir when not mounted local
#else
			printd (DEBUG, "accepting request to refresh %s", cv->dir);
#endif
			//refresh function expects BGL open
			if (pthread_create (&thisID, NULL,
				(gpointer) e2_fileview_refresh_list, cv) == 0)
					pthread_join (thisID, &result);
			else
				printd (WARN, "Failed to create list refresh thread");
			LISTS_LOCK
			//clear flag, too bad if a request happened late in the process
			if (result != NULL)
				cv->listcontrols.refresh_requested = FALSE;
		}
	}
	else
		cvdone = TRUE;
retest2:
	if (ov->listcontrols.refresh_requested)
	{
		ovdone = !(
		ov->listcontrols.norefresh ||
		ov->listcontrols.refresh_working ||
		ov->listcontrols.cd_working
#ifdef E2_STATUS_BLOCK
		|| app.status_working
#endif
		);
		if (ovdone)
		{
			LISTS_UNLOCK
#ifdef E2_VFSTMP
		//FIXME dir when not mounted local
#else
			printd (DEBUG, "accepting request to refresh %s", ov->dir);
#endif
			if (pthread_create (&thisID, NULL,
				(gpointer) e2_fileview_refresh_list, ov) == 0)
					pthread_join (thisID, &result);
			else
				printd (WARN, "Failed to create list refresh thread");
			LISTS_LOCK
			if (result != NULL)
				ov->listcontrols.refresh_requested = FALSE;
		}
	}
	else
		ovdone = TRUE;

	if (cv->listcontrols.refresh_requested || ov->listcontrols.refresh_requested)
	{
		//again/now/still want to do one or both panes
		//but only go back now not blocked before
		if (cv->listcontrols.refresh_requested && cvdone)
		{
			LISTS_UNLOCK
			goto retest;
		}
		if (ov->listcontrols.refresh_requested && ovdone)
		{
			LISTS_UNLOCK
			goto retest2;
		}

		printd (DEBUG, "more refresh request(s), deferred because busy");
		app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 200,
			(GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (200),
				(GDestroyNotify) _e2_filelist_timer_shutdown);
	}

	LISTS_UNLOCK

	return FALSE;
}
/**
@brief log request, and possibly initiate, filelist(s) refresh if it/they currently show @a dir
If a refresh is initiated, either or both flagged lists will be refreshed
as soon as any in-progress re-list (either or both panes) is completed
@param dir the dir to be refreshed (utf8 string)
@param immediate TRUE to intiate a refresh

@return TRUE if a refresh was initiated
*/
gboolean e2_filelist_request_refresh (gchar *dir, gboolean immediate)
{
	gboolean matched = FALSE;
#ifdef E2_VFSTMP
	//CHECKME v-dir ok
#endif
	if (g_str_equal (curr_view->dir, dir))
	{
		matched = TRUE;
		LISTS_LOCK
		curr_view->listcontrols.refresh_requested = TRUE;
		LISTS_UNLOCK
	}
#ifdef E2_VFSTMP
	//CHECKME v-dir ok
#endif
	if (g_str_equal (other_view->dir, dir))
	{
		matched = TRUE;
		LISTS_LOCK
		other_view->listcontrols.refresh_requested = TRUE;
		LISTS_UNLOCK
	}

	if (matched && immediate)
		//clear any fs report about dirty list, then do the refresh
		e2_filelist_check_dirty (NULL);

	return matched;
}
/**
@brief decide whether to lengthen the interval between filelist refresh requests
This is called when a 'dirty' flag has been detected

@param pane enumerator for pane 1 or pane 2, which was reported dirty

@return TRUE when
*/
static gboolean _e2_filelist_refresh_ok (E2_ListChoice pane)
{
	gboolean retval;
	/* Ideally this would check CPU resource-usage for this app and all children,
	   and only proceed if there's enough(?) idle capacity
	   Instead we assume small dirs can be processed without much adverse effect */
	gint times, itemcount = (pane == PANE1) ?
		//these are counts of total items, not filtered items
		gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane1_view.store), NULL):
		gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.pane2_view.store), NULL);
	if (itemcount < 51)
		times = 1;
	else if (itemcount < 251)
		times = 2;
	else
		times = 3;

	if (pane == PANE1)
	{
		if (++pane1repeats >= times)
		{
			printd (DEBUG, "Pane 1 refresh interval count = %d", times);
			pane1repeats = 0;
			retval = TRUE;
		}
		else
			retval = FALSE;
	}
	else
	{
		if (++pane2repeats >= times)
		{
			printd (DEBUG, "Pane 2 refresh interval count = %d", times);
			pane2repeats = 0;
			retval = TRUE;
		}
		else
			retval = FALSE;
	}
	return retval;
}
/**
@brief request refresh of filepane(s) contents (and with FAM, config too) if the corresponding changed is detected

Among other uses, this is the timer callback function for filelists auto refresh
When FAM/gamin in use, it also checks for and flags (not processes)
config file updates, as that data comes from the same data stream.
In certain circumstances any dirty flag might be ignored, or deferred to lengthen
the effective interval between refreshes
This expects gtk's BGL to be off/open

@param userdata data specified when the timer was initialised (NULL), or when called directly, non-NULL

@return TRUE so the timer keeps working
*/
gboolean e2_filelist_check_dirty (gpointer userdata)
{
	static gboolean busy = FALSE;
	if (busy)
		return TRUE;	//no reentrant usage
	busy = TRUE;
#ifdef E2_FAM
	static gboolean cfgdeferred = FALSE;
#endif

	gboolean p1dirty, p2dirty;
#ifdef E2_FAM
	gboolean localcfgdirty;	//use a local copy so that the extern value doesn't get cleared before it's noticed
	extern gboolean cfgdirty;

	if (userdata != NULL
		 && app.monitor_type != E2_MONITOR_DEFAULT)
			g_usleep (E2_FAMWAIT);	//wait for things to be noticed
#endif

#ifndef E2_FAM
	if (userdata != NULL)
	{
		p1dirty = p2dirty = TRUE;	//force a refresh
	}
	else
#endif
	//this reports 'gone' as 'dirty', and then the list-refesh
	//function handles choosing a replacement dir
	e2_fs_FAM_check_dirty (&p1dirty, &p2dirty
#ifdef E2_FAM
	, &localcfgdirty
#endif
	);

	p1dirty = p1dirty || pane1repeats;
	p2dirty = p2dirty || pane2repeats;

	if (p1dirty || p2dirty)
	{
		LISTS_LOCK
		if (p1dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE1)))
			app.pane1_view.listcontrols.refresh_requested = TRUE;
		else
			p1dirty = FALSE;
		if (p2dirty && (userdata != NULL || _e2_filelist_refresh_ok (PANE2)))
			app.pane2_view.listcontrols.refresh_requested = TRUE;
		else
			p2dirty = FALSE;
		LISTS_UNLOCK
		if (p1dirty || p2dirty)
		{
			if (app.timers[REFRESHBEGIN_T] > 0)
			{
				//clear any incomplete refresh request
				g_source_remove (app.timers[REFRESHBEGIN_T]);
			}
		//the delay between attempts will be lengthened later, if appropriate
//CHECKME we may be in a timer callback here - are nested timers still acceptable to glib/gtk ?
			app.timers[REFRESHBEGIN_T] = g_timeout_add_full (G_PRIORITY_HIGH, 5,
				(GSourceFunc) _e2_filelist_refresh_manage, GINT_TO_POINTER (5),
				(GDestroyNotify) _e2_filelist_timer_shutdown);
		}
	}

#ifdef E2_FAM
	if (localcfgdirty || cfgdeferred)
	{
		if (!app.rebuild_enabled)
		{
			cfgdeferred = TRUE;
			printd (DEBUG, "config refresh deferred");
		}
		else
		{
			cfgdeferred = FALSE;
			if (localcfgdirty)
			{
				cfgdirty = TRUE;	//set, but don't clear, the main flag
				printd (DEBUG, "config refresh flag set");
			}
		}
	}
#endif	//def E2_FAM

	busy = FALSE;
	return TRUE;
}
/**
@brief initialise timer which polls for various 'dirty' indicator(s)

The timer is never suspended or paused, to avoid the risk of overflow from
kernel-fam processes. It may be stopped permanently if change-monitoring is
abandoned via a config dialog

@return
*/
void e2_filelist_start_refresh_polling (void)
{
	if (e2_option_bool_get ("auto-refresh"))
	{
		app.timers[DIRTYCHECK_T] =
#ifdef USE_GLIB2_14
			g_timeout_add_seconds (E2_FILESCHECK_INTERVAL_S,
#else
			g_timeout_add (E2_FILESCHECK_INTERVAL,
#endif
				(GSourceFunc) e2_filelist_check_dirty, NULL);
	}
}
/**
@brief idle function to clear old liststores

@param user_data UNUSED data specified when the idle was setup

@return FALSE, to stop the callbacks
*/
gboolean e2_filelist_clear_old_stores (gpointer user_data)
{
	GSList *tmp;
#ifdef DEBUG_MESSAGES
	gint debug = g_slist_length (app.used_stores);
#endif
	for (tmp = app.used_stores; tmp != NULL; tmp = tmp->next)
	{
		GtkListStore *store = tmp->data;
		GtkTreeModel *mdl = GTK_TREE_MODEL (store);
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter_first (mdl, &iter))
		{	//it's not empty already
			//clear file info data structs referred to in the store
			//CHECKME need to clear anything else?
			FileInfo *info;
			do
			{
				gtk_tree_model_get (mdl, &iter, FINFO, &info, -1);
#ifdef USE_GLIB2_10
				g_slice_free1 (sizeof(FileInfo), info);
				//DEALLOCATE (FileInfo, info);
#else
				DEALLOCATE (FileInfo, info);
#endif
			} while (gtk_tree_model_iter_next (mdl, &iter));
		}
		//CHECKME clear filtermodel ?
		g_object_unref (G_OBJECT (store));
	}
	g_slist_free (app.used_stores);
	app.used_stores = NULL;
	printd (DEBUG, "%d old liststore(s) cleared", debug);
	return FALSE;
}
/**
@brief create and populate filelist-compatible list store with rows for each item in @a entries

@param entries list of FileInfo's to be processed
@param view pointer to data struct for the view to which the filled store will apply

@return pointer to the liststore, or NULL if a problem occurs
*/
GtkListStore *e2_filelist_fill_store (GList *entries, ViewInfo *view)
{
//	printd (DEBUG, "start store fill");
	GtkListStore *store = e2_filelist_make_store ();
	if (store == NULL)
		return NULL;

	GList *tmp;
	FileInfo *infoptr;
	GtkTreeIter iter;
	struct tm *tm_ptr;
	struct passwd *pwd_buf;
	struct group *grp_buf;
	gchar size_buf[20];	//enough for 999 Tb
	gchar modified_buf[25];
	gchar accessed_buf[25];
	gchar changed_buf[25];
//	gchar perm_buf[11];
	gchar uid_buf[20];
	gchar gid_buf[20];
	gchar *buf[NAMEKEY+1];	//pointers to strings to be inserted into each row
	gchar *freeme;
	gchar *__utf__;	//for e2_utf8_from_locale_fast() macro

	//get format parameters

	//format for dates display
	gchar *strf_string;
	switch	(e2_option_int_get ("date-string"))
	{
		case 1:
			strf_string = "%d/%m/%y %H:%M";	//standard
			break;
		case 2:
			strf_string = "%m/%d/%y %H:%M";	//american
			break;
		case 3:
			strf_string = "%Y-%m-%dT%H:%M";	//ISO8601
			break;
		case 4:
/*			{
				strf_string = nl_langinfo (D_T_FMT);
				if (strf_string != NULL && *strf_string != '\0')
					break;
			} */
			strf_string = "%x %X";	//localised
			break;
		default:
			strf_string = "%b %d %H:%M";
			break;
	}
	//get format for size display
	gchar *comma;
	switch	(e2_option_int_get ("size-string"))
	{
		case 1:
		{
			comma = nl_langinfo (THOUSEP);
			if (comma == NULL || *comma == '\0')
				comma = ",";
			break;
		}
		default:
			comma = NULL;	//signal to use the condensed version
			break;
	}

	gboolean caseignore = ! e2_option_bool_get ("namesort-case-sensitive");
	GtkStyle *style = gtk_rc_get_style (curr_view->treeview);
	GdkColor *default_color = &style->text[GTK_STATE_NORMAL];

#ifdef E2_ASSISTED
	GdkColor *back_color = (e2_option_bool_get ("color-background-set")) ?
		e2_option_color_get ("color-background") : NULL;
#endif
	GdkColor *link_color = e2_option_color_get ("color-ft-link");
	GdkColor *dir_color = e2_option_color_get ("color-ft-dir");
	GdkColor *dev_color = e2_option_color_get ("color-ft-dev");
	GdkColor *sock_color = e2_option_color_get ("color-ft-socket");
	GdkColor *exec_color = e2_option_color_get ("color-ft-exec");

	//iterate through the listed FileInfo's
	for (tmp = entries; tmp != NULL; tmp = tmp->next)
	{
		infoptr = (FileInfo *) tmp->data;

		//convert to utf if appropriate
		buf[FILENAME] = F_DISPLAYNAME_FROM_LOCALE (infoptr->filename);
		//directories (and links to dirs) get a trailing separator
		//FIXME speed this by using a more efficient process to check for dir
		if (e2_fs_is_dir (infoptr, view))
		{
			freeme = buf[FILENAME];
			buf[FILENAME] = e2_utils_strcat (freeme, "/");
			if (freeme != infoptr->filename)
				g_free (freeme);
		}
		else if (buf[FILENAME] == infoptr->filename)	//no actual conversion, above
			buf[FILENAME] = g_strdup (buf[FILENAME]);

		if (comma == NULL)
		{	//use condensed size format
			if (infoptr->statbuf.st_size < 1024) //less than 1k
			{
			  g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64,
					infoptr->statbuf.st_size);
			}
			else if (infoptr->statbuf.st_size < 1048576) //less than a meg
			{
			  g_snprintf(size_buf, sizeof(size_buf), "%.1f%s",
				(gfloat) infoptr->statbuf.st_size / 1024.0, _("k"));
			}
			else  //a meg or more  if (infoptr->statbuf.st_size < 1073741824)
			{
			  g_snprintf(size_buf, sizeof(size_buf), "%.1f%s",
				(gfloat) infoptr->statbuf.st_size / 1048576.0, _("M"));
			}
/*			else //a gig or more
			{
			  g_snprintf(size_buf, sizeof(size_buf), "%.1f%s",
				(gfloat) infoptr->statbuf.st_size / 1073741824.0, _("G"));
			} */
		}
		else
		{	//use actual size, with commas
			g_snprintf(size_buf, sizeof(size_buf), "%"PRIu64,
				infoptr->statbuf.st_size);

			guint len = strlen (size_buf);
			guint ths = len-1;  //0-based index
			guint i;
			while (ths > 2 && len < sizeof(size_buf))
			{
				for (i = len-1; i > ths-3; i--)
					size_buf[i+1] = size_buf[i];

				size_buf[i+1] = *comma;
				size_buf[++len] = '\0';
				ths = i;
			}
		}
		buf[SIZE] = g_strdup (size_buf);	//content is ascii, no need to convert to utf

		buf[PERM] = e2_fs_get_perm_string (infoptr->statbuf.st_mode); //string is already utf

		if ((pwd_buf = getpwuid (infoptr->statbuf.st_uid)) == NULL)
		{
		  g_snprintf (uid_buf, sizeof(uid_buf), "%d",
		     (guint) infoptr->statbuf.st_uid);
		  buf[OWNER] = g_strdup (uid_buf); //content is ascii number, no need to convert to utf
		}
		else
		{
		  buf[OWNER] = e2_utf8_from_locale_fast (pwd_buf->pw_name);
		}

		if ((grp_buf = getgrgid (infoptr->statbuf.st_gid)) == NULL)
		{
		  g_snprintf(gid_buf, sizeof(gid_buf), "%d",
		       (guint) infoptr->statbuf.st_gid);
		  buf[GROUP] = g_strdup (gid_buf); //content is ascii number, no need to convert to utf
		}
		else
		{
		  buf[GROUP] = e2_utf8_from_locale_fast (grp_buf->gr_name);
		}
		tm_ptr = localtime (&(infoptr->statbuf.st_mtime));
		strftime (modified_buf, sizeof(modified_buf), strf_string, tm_ptr);
		buf[MODIFIED] = e2_utf8_from_locale_fast (modified_buf);

		tm_ptr = localtime (&(infoptr->statbuf.st_atime));
		strftime (accessed_buf, sizeof(accessed_buf), strf_string, tm_ptr);
		buf[ACCESSED] = e2_utf8_from_locale_fast (accessed_buf);

		tm_ptr = localtime(&(infoptr->statbuf.st_ctime));
		strftime (changed_buf, sizeof(changed_buf), strf_string, tm_ptr);
		buf[CHANGED] = e2_utf8_from_locale_fast (changed_buf);

		if (caseignore)
		{
			freeme = g_utf8_casefold (buf[FILENAME], -1);
#ifdef USE_GTK2_8
			buf[NAMEKEY] = g_utf8_collate_key_for_filename (freeme, -1);
#else
			buf[NAMEKEY] = g_utf8_collate_key (freeme, -1);
#endif
			g_free (freeme);
		}
		else
#ifdef USE_GTK2_8
			buf[NAMEKEY] = g_utf8_collate_key_for_filename (buf[FILENAME], -1);
#else
			buf[NAMEKEY] = g_utf8_collate_key (buf[FILENAME], -1);
#endif
		GdkColor *foreground;
		switch (infoptr->statbuf.st_mode & S_IFMT)
		{
		  case S_IFLNK:
		    foreground = link_color;
		    break;
		  case S_IFDIR:
		    foreground = dir_color;
		    break;
		  case S_IFCHR:
		  case S_IFBLK:
		    foreground = dev_color;
		    break;
		  case S_IFSOCK:
		    foreground = sock_color;
		    break;
		  case S_IFREG:
			  //show as executable if _anyone_ has that permission
		    if ((S_IXUSR & infoptr->statbuf.st_mode)
		  		|| (S_IXGRP & infoptr->statbuf.st_mode)
		  		|| (S_IXOTH & infoptr->statbuf.st_mode))
			  foreground = exec_color;
			else
			{
#ifdef E2_RAINBOW
				//find extension of current item
				//localised text, but assumes . is ascii
				//hashed extension-strings used for comparison have been localised
				foreground = NULL;	//in case there's no '.' at all
				gchar *ext = infoptr->filename + 1;	//+1 in case item is hidden
				while ((ext = strchr (ext,'.')) != NULL)
				{
					ext++;	//pass the '.'
					//use its color, if any
					foreground = g_hash_table_lookup (app.colors, ext);
					if (foreground != NULL)
						break;
				}
				if (foreground == NULL)
					foreground = default_color;
#else
				foreground = default_color;
#endif
			}
		    break;
		  default:
			foreground = default_color;
		    break;
		}
		//gtk >= 2.10 can handle &iter = NULL
		gtk_list_store_insert_with_values (store, &iter, -1,
			FILENAME, buf[FILENAME],
			SIZE, buf[SIZE],
			PERM, buf[PERM],
			OWNER, buf[OWNER],
			GROUP, buf[GROUP],
			MODIFIED, buf[MODIFIED],
			ACCESSED, buf[ACCESSED],
			CHANGED, buf[CHANGED],
			NAMEKEY, buf[NAMEKEY],
			FINFO, infoptr,
			FORECOLOR, foreground,
#ifdef E2_ASSISTED
			BACKCOLOR, back_color,
#endif
//			VISIBLE, TRUE,
			-1);

		//cleanup
		gint i;
		for (i = 0; i <= NAMEKEY; i++)
			g_free (buf[i]);

	}
/*
//		gettimeofday (&strt, NULL); //FOR DEBUGGING, check time this load takes
//		gettimeofday (&nd, NULL);
		if (nd.tv_usec < strt.tv_usec)
		{
		    int nsec = (strt.tv_usec - nd.tv_usec) / 1000000 + 1;
    		strt.tv_usec -= 1000000 * nsec;
    		strt.tv_sec += nsec;
		}
		if (nd.tv_usec - strt.tv_usec > 1000000)
		{
			int nsec = (strt.tv_usec - nd.tv_usec) / 1000000;
			strt.tv_usec += 1000000 * nsec;
			strt.tv_sec -= nsec;
		}
		result.tv_sec = result.tv_sec + nd.tv_sec - strt.tv_sec;
		result.tv_usec = result.tv_usec + nd.tv_usec - strt.tv_usec;
		if (result.tv_usec > 1000000)
		{
			int nsec = result.tv_usec / 1000000 + 1;
    		result.tv_usec -= 1000000 * nsec;
    		result.tv_sec += nsec;
		}

//	printd (DEBUG, "populating rows took %f seconds", result.tv_sec + result.tv_usec / 1000000.0 );
*/
//	printd (DEBUG, "populating rows finished");
	return store;
}
/**
@brief create list store structure

Creates basic structure of list store, for panes filelists
No rows or data are added.

@return the new liststore
*/
GtkListStore *e2_filelist_make_store (void)
{
	GtkListStore *store = gtk_list_store_new (MODEL_COLUMNS,
		G_TYPE_STRING,  //FILENAME
		G_TYPE_STRING,  //SIZE
		G_TYPE_STRING,  //PERM
		G_TYPE_STRING,  //OWNER
		G_TYPE_STRING,  //GROUP
		G_TYPE_STRING,  //MODIFIED
		G_TYPE_STRING,  //ACCESSED
		G_TYPE_STRING,  //CHANGED
		// the rest are not displayed
		G_TYPE_STRING,  //NAMEKEY for i18n name sorts
		G_TYPE_POINTER,  //FINFO pr to FileInfo for the item
		GDK_TYPE_COLOR,  //FORECOLOR line colour
		GDK_TYPE_COLOR  //BACKCOLOR line colour
	//	G_TYPE_BOOLEAN	//VISIBLE whether filtered or not
		);
	return store;
}

#ifdef STORECOPY
/**
@brief helper function to copy each FileInfo stored in a filelist store

@param model the store to be updated
@param tpath UNUSED tree path of iter being processed ?
@param iter pointer to iter being processed
@param user_data UNUSED data specified when foreach was called

@return FALSE if the process can continue
*/
static gboolean _e2_filelist_copy_storeinfos (GtkTreeModel *model,
	GtkTreePath *tpath, GtkTreeIter *iter, gpointer user_data)
{
	FileInfo *info;
	gtk_tree_model_get (model, iter, FINFO, &info, -1);
	//FIXME duplicate the data
#ifdef USE_GLIB2_14
	info = (FileInfo *) g_slice_copy (sizeof (FileInfo), info);
	gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info, -1);
#else
	FileInfo *info2 = ALLOCATE (FileInfo);
	CHECKALLOCATEDWARN (info2, return TRUE;)
	memcpy (info2, info, sizeof (FileInfo));
	gtk_list_store_set (GTK_LIST_STORE (model), iter, FINFO, info2, -1);
#endif
//	gtk_tree_path_free (tpath); CRASHER
	return FALSE;
}
/**
@brief copy a filelist liststore with all its contents

@param original the store to be copied

@return the new liststore
*/
GtkListStore *e2_filelist_copy_store (GtkListStore *original)
{
	gpointer copied;
	e2_tree_store_copy (GTK_TREE_MODEL (original), FALSE, &copied);
	gtk_tree_model_foreach (GTK_TREE_MODEL ((GtkListStore *)copied),
		(GtkTreeModelForeachFunc) _e2_filelist_copy_storeinfos, NULL);
	return ((GtkListStore *)copied);
}
#endif
