//
//	misc.c
//
//	contains miscellaneous functions (e.g. for dialog pop-up windows)
//
//  (c) 2001-2002 Tim-Philipp Muller <t.i.m@orange.net>
//
//

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "config.h" /* for OS definition */

#define _GNU_SOURCE
#include <stdio.h>	/* for the vasprintf() definition */

#include "core-conn.h"
#include "http_get.h"
#include "global.h"
#include "mainwindow.h"
#include "misc.h"
#include "misc_gtk.h"
#include "misc_strings.h"
#include "options.h"

#include "statusbar.h"
#include "status_page.h"
#include "icons.h"

#include <sys/param.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifdef G_OS_UNIX
# include <sys/wait.h>
# include <sys/resource.h>
# ifdef HAVE_SYS_SYSLIMITS_H
#  include <sys/syslimits.h>
# endif
#endif

#ifdef G_OS_WIN32
# include <Windows.h>
#endif

#include <ctype.h>
#include <dirent.h>
#include <errno.h>

#ifdef G_OS_UNIX
#include <netdb.h>
#include <syslog.h>
#endif

#include <limits.h>
#include <signal.h>	// for kill(pid,sig);
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

/* We just have them here as well, so xgettext will pick them up */

#define GETTEXT_FILETYPE_AUDIO       N_("Audio")
#define GETTEXT_FILETYPE_VIDEO       N_("Video")
#define GETTEXT_FILETYPE_PROGRAM     N_("Program")
#define GETTEXT_FILETYPE_DOCUMENT    N_("Document")
#define GETTEXT_FILETYPE_IMAGE       N_("Image")
#define GETTEXT_FILETYPE_ARCHIVE     N_("Archive")
#define GETTEXT_FILETYPE_SUBTITLES   N_("Subtitles")
#define GETTEXT_FILETYPE_CDIMAGE     N_("CD Image")

/* Mingw is missing declaration of NAME_MAX */
/* so here is small hack */
#ifdef G_OS_WIN32
# ifndef NAME_MAX
#  define NAME_MAX FILENAME_MAX
# endif
#endif


/* functions */

// hash_to_hash_str
//
// takes 128-bit hash
// returns pointer to hash as hex string

const gchar *
hash_to_hash_str (const guint8 *hash)
{
	const gchar    *hexdigits = "0123456789abcdef";
	static gchar    m_hash_str[33];
	gint            j;

	g_return_val_if_fail ( hash != NULL, NULL );

	for ( j = 0; j < 16; j++ )
	{
		m_hash_str[(j<<1)  ] = hexdigits[(((hash[j]) & 0xf0) >> 4)];
		m_hash_str[(j<<1)+1] = hexdigits[(((hash[j]) & 0x0f)     )];
	}

	m_hash_str[32] = 0x00;

	return m_hash_str;
}

const guint8 *
hash_str_to_hash (const gchar *hash_str)
{
	char hexnum[3] = { 0x00, 0x00, 0x00 };
	int i;
	static guint8 m_hash[16];

	for (i=0; i<16; i++)
	{
		strncpy (hexnum, hash_str+(i*2), 2);
		m_hash[i] = (guint8) strtol (hexnum, NULL, 16);
	}

	return m_hash;
}


// human_size
//
// returns the given filesize in bytes in 'human' format (e.g. '9.32M')

const gchar *
human_size (guint bytes)
{
	gfloat			size = (gfloat) bytes;
	static gchar	buf[128];

	if ( bytes >= 1024 )
	{
		if ( bytes >= (1024*1024))
		{
			if ( bytes >= (1024*1024*1024))
			{
				g_snprintf (buf,sizeof(buf), "%.2fG", size/(1024.0*1024.0*1024.0));	// Giga
			} else g_snprintf (buf,sizeof(buf), "%.2fM", size/(1024.0*1024.0)); 	// Mega
		} else g_snprintf (buf,sizeof(buf), "%uk", bytes >> 10); // kilo
	} else g_snprintf (buf,sizeof(buf), "%db", bytes); // bytes
	return buf;
}



/*******************************************************************************
 *
 *   invoke_browser_with_url
 *
 *   invokes a browser to display the given URL.
 *
 ***/

#if !defined(G_OS_WIN32)

void
invoke_browser_with_url (const gchar *url)
{
	gchar        *browserpath = NULL;
	const gchar  *envbrowser = g_getenv("BROWSER");
	const gchar  *argv[3];
	GError       *error = NULL;

	g_return_if_fail (url!=NULL);

	/* check whether $BROWSER environment variable is set (thanks to Yorzik for this) */
	statusbar_msg (_("invoking browser... (environment variable $BROWSER is %s)\n"),
								(envbrowser != NULL) ? _("set") : _("not set"));

	if ( envbrowser != NULL )
	{
		browserpath = g_find_program_in_path( envbrowser );

		if ( browserpath == NULL )
			g_printerr (_("Environment variable $BROWSER contains invalid or incomplete path to browser binary.\n"));
	}

#ifdef MACOSX
	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("safari");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("chimera");
#endif

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("konqueror");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("galeon");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("firefox");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("mozilla-firefox");
	
	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("mozilla-bin");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("netscape");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("opera");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("phoenix");

	if ( browserpath == NULL )
		browserpath = g_find_program_in_path("epiphany");

	/* display error message if we still didn't find a browser */
	if (!browserpath)
	{
		misc_gtk_ok_dialog (GTK_MESSAGE_WARNING,
			_("I tried to invoke a browser,\n"
			  "but couldn't find one.\n\n"
			  "Please set the environment variable $BROWSER\n"
			  "to a full path to a browser binary of your choice.\n") );

		statusbar_msg ("    ");
		return;
 	}

	argv[0] = browserpath;
	argv[1] = url;
	argv[2] = NULL;

	if ( g_spawn_async(NULL, (gchar**)argv, NULL,
		G_SPAWN_STDOUT_TO_DEV_NULL|G_SPAWN_STDERR_TO_DEV_NULL, 
	        NULL, NULL, NULL, &error) == FALSE )
	{
		g_printerr("starting browser failed: %s\n", error->message);
		g_error_free(error);
	}

	G_FREE(browserpath);
}

#else

void
invoke_browser_with_url (const gchar *url)
{
	gint	res;

	g_return_if_fail (url!=NULL);
		
	res = (gint)ShellExecute( NULL, "open", url, NULL, NULL, 0 );
	
	if( res<=32 ) {		
		g_printerr("starting browser failed\n");	
	}
}


#endif


// seconds_into_human_time
//
// takes a span of time in units of secons and returns it in 'human' format (e.g. '5.4h')

#define SECS_TWO_YEARS   (60*60*24*365*2)
#define SECS_ONE_YEAR    (60*60*24*365  )
#define SECS_ONE_DAY     (60*60*24      )
#define SECS_ONE_HOUR    (60*60         )

const gchar *
seconds_into_human_time (guint seconds)
{
	static gchar    output[64];

	if ( seconds == 0 )
		return _("now");

	/* assume no one keeps a file in the queue longer than 2yrs */
	if ( seconds > SECS_TWO_YEARS )
		return _("never");

	if ( seconds >= SECS_ONE_YEAR )
		g_snprintf (output, sizeof(output)/sizeof(gchar), "%.1fy", (gfloat)seconds/((gfloat)SECS_ONE_YEAR*1.0));

	else if ( seconds >= SECS_ONE_DAY )
		g_snprintf (output, sizeof(output)/sizeof(gchar), "%.1fd", (gfloat)seconds/((gfloat)SECS_ONE_DAY*1.0));

	else if ( seconds >= SECS_ONE_HOUR )
		g_snprintf (output, sizeof(output)/sizeof(gchar), "%.1fh", (gfloat)seconds/((gfloat)SECS_ONE_HOUR*1.0));

	else if ( seconds >= 60 )
		g_snprintf (output, sizeof(output)/sizeof(gchar), "%.1fm", (gfloat)seconds/60.0);

	else
		g_snprintf (output, sizeof(output)/sizeof(gchar), "%us", seconds);

	return output;
}


// exec_command_on_complete_download
//
// takes the filename of the file that is complete and runs the command specified in options
//	when a download is complete (if a command is specified that is)

void
exec_command_on_complete_download (const gchar *filename)
{
	const CoreOptions *copts;
	const gchar       *exec;

	g_return_if_fail (filename != NULL);

	exec = opt_get_str(OPT_GUI_EXEC_ON_COMPLETE_COMMAND);
	copts = gui_core_conn_get_core_options(core);

	g_return_if_fail (copts->incomingdir != NULL);

	/* is a command specified in the options? */
	if ((exec) && (*exec))
	{
		gchar *cmdlinestr = g_strdup_printf ("%s%s", exec, (exec[strlen(exec)-1] == '&') ? "" : " &");

#ifdef G_OS_UNIX
		/* set environment variables ED2K_FN and ED2K_IN */
		(void) setenv ("ED2K_FN", filename, 1);             /* 1 = overwrite old value */
		(void) setenv ("ED2K_IN", copts->incomingdir, 1);   /* 1 = overwrite old value */
#endif

		/* now run the specified command line in a
		 * shell in the background (we've added the '&' at the end) */

		if ( system (cmdlinestr) < 0 )
		{
			g_printerr (_("exec shell command on complete failed for some reason:\n"));
			g_printerr ("%s\n", cmdlinestr);
			status_system_error_msg (_("Error:"));
		}

		g_free(cmdlinestr);
	}
}


// remove_junk_chunk
//
// remove chunk in string INCLUDING both from and to - no validity tests made for parameters!

void
remove_junk_chunk (gchar *str, gchar *from, gchar *to)
{
	if ((!str) || (!from) || (!to))
		return;

	// remove spaces before bracket if sth else than a letter or a number follows the expression
	if(!isalnum(to[1]))
	{
		while (from>str &&  from[-1]==' ')
			from--;
	}

	// and remove chunk

	/* if there is nothing after 'to', then it suffices to terminate string at beginning of section to remove */
	if (to[0] == 0x00)
	{
		from[0] = 0x00;
		return;
	}

	/* we can't use strcpy(from,to+1) here because 'man strcpy' says the "strings may not overlap" (?!) */
	/* +1 as we want to delete the string INCLUDING the character at *to */
	memmove(from, to+1, strlen(to+1)+1);
}


// remove_word_chunks
//
// remove all chunks which match a given word case-insensitive
// original word must be lower case!

static void
remove_word_chunks (gchar *str, const gchar *w)
{
	gint	n, w1len;

	if ((!w) || (!str))
		return;

	n = (w[0]==' ') ? 1 : 0;
	w1len = strlen(w) - 1 - n;

	if (w1len != -1)
	{
		gchar	*match, *word, *ostr;

		word = g_ascii_strdown(w,-1); /* to contain the word chunk to remove in lower case */
		ostr = g_ascii_strdown(str,-1); /* to contain the original string in lower case */

		while ((match = strstr(ostr,word))!=NULL)
		{
			guint idx = (match - ostr) + n;
			remove_junk_chunk (ostr, ostr + idx, ostr + idx + w1len);
			remove_junk_chunk (str, str + idx, str + idx + w1len);
				
/* original:
			remove_junk_chunk (str, match+n, match+strlen(w)-1);
			remove_junk_chunk (str, str+(match-ostr)+n, str+(match+strlen(w)-ostr-1));
*/
		}

		g_free (word);
		g_free (ostr);
	}
}

// remove_word_chunk_until_end
//
//

static void
remove_word_chunk_until_end (gchar *str, gchar *phrase)
{
	gchar *d, *e;
	if ((!str) || (!phrase)) return;
	d = strstr (str, phrase);
	if (d!=NULL)
	{
		e=strchr(d+strlen(phrase)+1,' ');
		if (e==NULL) e=strchr(d+strlen(phrase)+1,'.');
		if (e==NULL) e=d+strlen(d)-1;
		if (e>d) remove_junk_chunk (str,d,e);
	}
}

// remove_word_chunk_and_previous_word
//
// note: this will only remove one instance of the word

static void
remove_word_chunk_and_previous_word (gchar *str, gchar *word)
{
	gchar *s, *match, c;

	if ((!str) || (!word))
		return;

	match = strstr (str,word);

	if ((!match) || (match<str+2))			// word not found
		return;

//	printf ("match = '%s'\n", match);
	c = match[-1];						// -1 as we don't want the space
	match[-1] = 0x00;
	s = strrchr (str,' ');
	match[-1] = c;
	if (!s) return;				// only one or none word before
//	printf ("previous word = '%s'\n", s);
	remove_junk_chunk (str,s+1,match+strlen(word)-1);
}


// remove_bracket_junks
//
// remove all chunks of a certain bracket type

void
remove_bracket_junks (gchar *str, gchar open, gchar close)
{
	gchar *s, *e;	// start, end of junk chunk
	// remove brackets - assuming that if we have two brackets there's nothing useful in between both
	while ((s=strchr(str, open))!=NULL)
	{
		if ((e=strchr(s+1, close))==NULL) return;	// no closing bracket found -> finito
		remove_junk_chunk (str,s,e);
	}
}

static const char *junkwords[] =
{
 "german", "english", "french", "spanish", "dvdrip", "svcd", "dvd",
 "sddivx", "dubbed", "subtitled", "subtitles", "tgsc", "deutsch",
 "divx", "shared", "vcd", "vhsrip", "vhs-rip", "vhs", "scr ",
 "shared by", "released by", "release by", "release", "slicer",
 "vbr", "stereo", "mono ", "128kbs", "160kbs", "192kbs", "256kbs",
 "128kb", "160kb", "192kb", "256kb", "full album",
 "#tradeftp", "kein fake", "no fake", "25fps", "engl ",
 "engl. ", " dt ", "dt.", "uncut", "high quality", "CloneCD image",
 "surround ", "ppv-rip", "directors cut", "director&apos;s cut", " cam ",
 "sharereactor", "filenexus", "ac3 guru com", "ac3", " stinky tofu ", " rus ",
 NULL
};


// remove_junk_from_filename
//
//

void
remove_junk_from_filename (char *str)
{
	char *d, *e, *amp;
	int i;

//	int debug = (str[0]=='M');
//	if (debug) printf ("1 - %s\n", str);
	remove_bracket_junks (str, '(', ')');
	remove_bracket_junks (str, '{', '}');
	remove_bracket_junks (str, '[', ']');
	remove_bracket_junks (str, '=', '=');
//	if (debug) printf ("2 - %s\n", str);


	/* escaped chars are almost with certainty
	 * chars we don't want (are they?)          */

	for ( amp = strchr(str,'&'); amp != NULL; amp = strchr(amp+1,'&') )
	{
		gchar *semi = strchr(amp+1,';');

		if (!semi)
			break;

		/* apostrophe's are fine */
		if ( g_ascii_strncasecmp(amp,"&apos;",6) == 0 )
			continue;

		memset (amp, ' ', (guint) (semi-amp) + 1 );
	}


	// substitute '_' with spaces (do not replace & ; and ' here
	g_strdelimit (str, "-=_:+%$#^*(){}[]~@!£", ' ');

//	if (debug) printf ("3 - %s\n", str);

	// substitute all dots but the last with spaces
	e = strrchr (str, '.');
	if (e)
	{
		while ((d = strchr(str,'.')) && d<e) d[0]=' ';
	}
//	if (debug) printf ("4 - %s\n", str);

	remove_word_chunk_until_end (str, "shared by");
	remove_word_chunk_until_end (str, "release by");
	remove_word_chunk_until_end (str, "released by");
	remove_word_chunk_until_end (str, "realase by");
	remove_word_chunk_until_end (str, "ripped by");
	remove_word_chunk_until_end (str, "rip by");
	remove_word_chunk_until_end (str, "capture by");
	remove_word_chunk_until_end (str, "captured by");
	remove_word_chunk_until_end (str, "encoded by");
	remove_word_chunk_until_end (str, "encoder by");
	remove_word_chunk_until_end (str, "resized by");
	remove_word_chunk_and_previous_word (str, "crew");
//	if (debug) printf ("5 - %s\n", str);

	// remove file extensions if not too long
	if (!opt_get_bool(OPT_GUI_DOWNLOADS_KEEP_EXTENSION_WHEN_HIDING_JUNK))
	{
		d = strrchr (str,'.');
		if (((str+strlen(str))-d)<=6)
		{
			d[0]=0x00;
		}
//		if (debug) printf ("6 - %s\n", str);
	}

	// remove all chunks with words we don't want
	for (i=0; (junkwords[i] != NULL); i++)
		remove_word_chunks (str, junkwords[i]);

//	if (debug) printf ("7 - %s\n", str);

//	we've turned these into spaces now anyway
//	remove_multiple_characters (str, '-');	// twice so we get encapsulations
//	remove_multiple_characters (str, '=');
//	remove_multiple_characters (str, '-');
//	remove_multiple_characters (str, '=');

	// if double spaces, we only want to remove one, not both, do we?
	if (str[0]!=0x00)
	{
		d = str+1;
		while (d[0]!=0x00)
		{
			if (d[-1]==' ' && d[0]==' ')
			{
				remove_junk_chunk (str,d,d);
			} else d++;
		}
	}

//	if (debug) printf ("8 - %s\n", str);

	// remove initial spaces
	while (str[0]==' ') remove_junk_chunk (str,str,str);
//	if (debug) printf ("9 - %s\n", str);

	// remove spaces before file extension dot - looks uncool
	if (opt_get_bool(OPT_GUI_DOWNLOADS_KEEP_EXTENSION_WHEN_HIDING_JUNK))
	{
		d = strrchr (str,'.');
		if (d>str)
		{
			if (d[-1]==' ') remove_junk_chunk (str,d-1,d-1);
		}
	}
}



// choplast
//
// chops off last character of a string
// returns: pointer to string
//

gchar *
choplast (gchar *str)
{
	g_return_val_if_fail (str!=NULL,NULL);
	str[strlen(str)-1]=0x00;
	return str;
}



/***************************************************************************
 *
 *   spawn_file_preview
 *
 *   returns 0 if everything goes ok, otherwise something <0
 *
 ***************************************************************************/

/* TODO: Why are we not using g_spawn_async() here? */

#ifdef G_OS_UNIX

gint
spawn_file_preview (const gchar *filename, const gchar *extension)
{
	const gchar *terminals[] = { "xterm", "konsole" }; /* note: gnome-terminal doesn't parse -e option properly */
	const char  *program_base = NULL;
	const char  *program_preview = opt_get_opt_filename_without_instance("ed2k_gui_preview");
	char         program_preview2[PATH_MAX+NAME_MAX+1];
	char        *parameter[5]; 
	char         resolved_path[PATH_MAX], dir_path[PATH_MAX];
	int          i;
	pid_t        pid;
	struct       rlimit rl;

	for (i = 0;  program_base == NULL  &&  i < G_N_ELEMENTS(terminals);  ++i)
	{
		program_base = g_find_program_in_path(terminals[i]);
	}
	
	if (!program_base)
	{
		GString *terms;
		terms = g_string_new(NULL);
		for (i = 0;  i < G_N_ELEMENTS(terminals);  ++i)
		{
			g_string_append(terms, terminals[i]);
			if (i < (G_N_ELEMENTS(terminals)-1))
				g_string_append(terms, ", ");
		}
		g_printerr (_("Preview: could not find an X terminal emulator. Checked for %s\n"), terms->str);
		g_string_free(terms, TRUE);
		return(-3); // FIXME - value?
	}

	status_message_blue (_("Preview: found X terminal emulator '%s'\n"), program_base);
	parameter[0] = (char*) program_base;
	parameter[1] = "-e";
	parameter[2] = (char*) program_preview2;
	parameter[3] = (char*) filename;
	parameter[4] = NULL;
	
	if (realpath(filename, resolved_path) == NULL)
	{
		g_printerr (_("Preview: could not resolve resolved_path from filename '%s'!\n"), filename);
		return(-3);
	}

	dir_path[0] = resolved_path[0] = '\0';

	g_return_val_if_fail (filename!=NULL, -2);
	g_return_val_if_fail (program_preview!=NULL, -5);

	strcpy (program_preview2, program_preview);

	if (realpath(filename, resolved_path) == NULL)
	{
		g_printerr (_("Preview: could not resolve resolved_path from filename '%s'!\n"), filename);
		return(-3);
	}
  
	// If you thread this program, g_dirname is not thread safe
	// in most OSes
	// strcpy is safe here because g_dirname never overflows
	// MAXPATHLEN

	if (strcpy(dir_path, g_path_get_dirname(resolved_path)) == NULL)	// strdup->leaks
	{
		g_printerr (_("Preview: could not get dir_path from resolved_path '%s'!\n"), resolved_path);
		return(-3);		// low memory
	}

	/* check whether directory with temp files is accessible and readable */
	if (access(dir_path, R_OK | X_OK) != 0)
	{
		g_printerr (_("Preview: cannot access dir_path '%s'!\n"), dir_path);
		return(-4);		// directory is not accessible
	}

	/* check whether preview script exists in config folder */
	if (access(program_preview2, R_OK | X_OK) != 0)
	{
		/* preview script does not exist yet. Create it. */
		FILE *of;
		of = fopen (program_preview2, "w");
		if (!of)
		{
			gchar *msg = g_strdup_printf (_("Could not create ed2k_gui_preview script '%s'!?\n"), program_preview);
			status_system_error_msg (msg);
			g_free(msg);
			return (-6);
		}
		status_message_blue ("GUI: Creating GUI preview skript in '%s'...\n", program_preview2);

		fprintf (of, "#!/bin/sh\n\n");

		fprintf (of, "PREVIEW_BIN=\"\"\n");
		fprintf (of, "MP3PLAYER=\"mpg123 -v\"\n");
		fprintf (of, "OGGPLAYER=\"ogg123\"\n");
		fprintf (of, "VIDPLAYER=\"mplayer -idx\"\n");

		fprintf (of, "\n");
		fprintf (of, _("echo \"Prototype of ed2k_gui_preview !\"\n"));
		fprintf (of, _("echo \"Got the following parameter: $*\"\n"));
		fprintf (of, _("echo \"File extension             : $ED2K_PREVIEW_EXTENSION\"\n"));
		fprintf (of, "echo\n\n");
		fprintf (of, "\n");

		fprintf (of, "case \"$ED2K_PREVIEW_EXTENSION\" in\n");

		fprintf (of, "mp3)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$MP3PLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "ogg)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$OGGPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "avi)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$VIDPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "divx)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$VIDPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "mpg)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$VIDPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "mpeg)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$VIDPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "# .bin files are usually mpeg with some format junk.\n");
		fprintf (of, "# mplayer can play .bin files\n");
		fprintf (of, "bin)\n");
		fprintf (of, "\tPREVIEW_BIN=\"$VIDPLAYER\"\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "*)\n");
		fprintf (of, _("\techo \"Don't know which binary to use with this format.\"\n"));
		fprintf (of, _("\techo \"Please configure this script (it's easy, don't worry)\"\n"));
		fprintf (of, "\techo\n");
		fprintf (of, _("\techo \"Hit <enter> to close this window.\"\n"));
		fprintf (of, "\tread\n");
		fprintf (of, "\texit 0\n");
		fprintf (of, "\t;;\n");

		fprintf (of, "esac\n");

		fprintf (of, "$PREVIEW_BIN \"$*\"\n\n");
		fprintf (of, _("echo \"Finished. Hit <enter> to close this window\"\n"));
		fprintf (of, "read\n\n");
		/* make script executable */
		if (fchmod(fileno(of), S_IRUSR|S_IWUSR|S_IXUSR)<0)
		{
			status_system_error_msg (_("Couldn't make script executable!\n"));
		}
		fclose(of);
		status_message_blue (_("GUI: preview script '%s' created.\n"), program_preview2);

		misc_gtk_ok_dialog ( GTK_MESSAGE_INFO,
			_("I have created a preview script in the GUI configuration file\n"
			  "directory (probably $HOME/.ed2k_gui/ed2k_gui_preview\n"
			  "unless you specified a different directory on the command line).\n\n"
			  "You can customise this script to use the player of you choice\n"
			  "to preview files depending on their file extension...\n") );
	}

	status_message_blue ("GUI: preview - parameter used: %s %s\n", program_preview2, filename);

	if ((pid = fork()) == -1)
		return(-1);		// cannot fork

	// parent continues
	if (pid != 0)
	{
		status_message_blue(_("GUI: preview: got a new pid: %u\n"), pid);
		g_usleep(1*G_USEC_PER_SEC);	// sleep a bit to avoid hammering
		return(0);		// forked fine
	}

	// close all file descriptors (apart from the 3 standard ones)
	getrlimit(RLIMIT_NOFILE, &rl);
	for (i = rl.rlim_max - 1; i >= 3; i--)
	{
		(void)close(i);
	}

	/* export file extension in lower case */
	if (extension)
	{
		gchar *ext_lowercase = g_ascii_strdown(extension,-1);
		setenv("ED2K_PREVIEW_EXTENSION", ext_lowercase, 1);
		g_free(ext_lowercase);
	}

	// child continues
	execvp(program_base, parameter);
	perror (_("bad error in execvp()"));

	_exit(0);
}

#else /* !G_OS_UNIX */

gint
spawn_file_preview (const gchar *filename, const gchar *extension)
{
	SHELLEXECUTEINFO sei;
	gchar		 *class_name;
	
	class_name = g_strdup_printf(".%s", extension);
					
	sei.cbSize 	= sizeof(SHELLEXECUTEINFO);
	sei.fMask  	= SEE_MASK_CLASSNAME;
	sei.hwnd	= NULL;
	sei.lpVerb 	= "open";
	sei.lpFile	= filename;
	sei.lpParameters= NULL;
	sei.lpDirectory	= NULL;
	sei.nShow	= 0;
	sei.lpClass	= class_name;
	
	if( !ShellExecuteEx( &sei ) ) {
	    g_printerr (_("Preview: could not preview '%s'!\n"), filename);
	}
	
	g_free(class_name);
	
	return 0;
}

#endif /* !G_OS_UNIX */

/******************************************************************************
 *
 *   misc_get_array_of_lines_from_textfile
 *
 *   TODO: we could make this better by using less allocations/free's if
 *         we walked the string ourselves and just replaced '\n' with '\000' -
 *         this would save the extra alloations by g_strsplit().
 *
 *   Reads in a text file and returns it as an array of lines.
 *
 *   Returns NULL on error.
 *
 ***/

gchar **
misc_get_array_of_lines_from_textfile ( const gchar *filename,
                                        gboolean err_if_not_exist,
                                        gboolean err_if_empty )
{
	gchar    **arr;
	gchar     *contents = NULL;
	GError    *err = NULL;
	gboolean   ret;

	ret = g_file_get_contents (filename, &contents, NULL, &err);

	if (!ret)
	{
		if (err)
		{
			if ( err->code != G_FILE_ERROR_NOENT  ||  err_if_not_exist == TRUE )
				g_printerr (_("Couldn't read file '%s': %s.\n"), filename, err->message);

			g_error_free(err);
		}
		return NULL;
	}

	g_return_val_if_fail ( contents != NULL, NULL );

	if ( *contents == 0x00 )
	{
		if ( err_if_empty == TRUE )
			g_printerr (_("File '%s' is empty.\n"), filename);

		g_free(contents);
		return NULL;
	}

	arr = g_strsplit (contents, "\n", 0);

	g_free(contents);

	return arr;
}




// misc_get_extension_from_filename
//
// Returns the extension of the filename in lower case,
//   or a pointer to an empty string if not found

const gchar *
misc_get_extension_from_filename (const gchar *name)
{
	static gchar  ext[15];
	gchar        *dot;
	guint         i;

	memset (ext, 0x00, sizeof(ext));

	g_return_val_if_fail ( name != NULL, ext);

	dot = strrchr (name, '.');

	if (!dot)
		return ext;

	/* if file is hidden file without extension... */
	if ( dot == name )
		return ext;

	if ( strlen(++dot) > 5 )
		return ext;

	for ( i = 0; dot[i] != 0x00; i++ )
		ext[i] = tolower (dot[i]);

	return ext;
}

/*******************************************************************************
 *
 *   misc_get_filetype_from_extension
 *   misc_get_filetype_from_extension_init_hashtable
 *
 *   Tries to determine the file type (Audio/Video/etc.) from
 *   the given extension and returns it as a string.
 *
 *   Returns empty string if type couldn't be determined.
 *
 ***/

static const gchar *cdimg[] = { "iso", "bin", "cue", "cdi", "nrg", NULL};
static const gchar *audio[] = { "mp3", "wav", "au", "ogg", NULL };
static const gchar *video[] = { "rm", "mpg", "mpeg", "avi", "asf", "mov", "divx", "ogm", NULL };
static const gchar *prog[]  = { "exe", "zip", "sit", NULL };
static const gchar *doc[]   = { "pdf", "html", "txt", "doc", "abw", "rtf", "asc", "xml", NULL };
static const gchar *imgs[]  = { "jpg", "jpeg", "gif", "png", "bmp", "xpm", NULL };
static const gchar *packs[] = { "zip", "rar", "ace", "arj", "rpm", "deb", "tgz", "gz", "Z" "tar.bz2", "bzip", "tar", NULL };
static const gchar *subs[]  = { "smi", "srt", "sub" , NULL };

static GHashTable *
misc_get_filetype_from_extension_init_hashtable (void)
{
	GHashTable    *ht;
	const gchar  **suffix;

	ht = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);

	/* these are all strings constants, so we can store them as is in the hash table */

	for ( suffix = cdimg; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_CDIMAGE));

	for ( suffix = audio; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_AUDIO));

	for ( suffix = video; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_VIDEO));

	for ( suffix = prog; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_PROGRAM));

	for ( suffix = doc; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_DOCUMENT));

	for ( suffix = imgs; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_IMAGE));

	for ( suffix = packs; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_ARCHIVE));

	for ( suffix = subs; *suffix != NULL; suffix++ )
		g_hash_table_insert (ht, (gpointer) *suffix, g_strdup(FILETYPE_SUBTITLES));

	return ht;
}

const gchar *
misc_get_filetype_from_extension (const gchar *ext)
{
	static GHashTable  *ht = NULL;
	gchar              *extlc, *ret;

	if ((!ext) || *ext == 0x00 )
		return "";

	if (!ht)
		ht = misc_get_filetype_from_extension_init_hashtable();

	extlc = g_ascii_strdown(ext,-1);
	g_return_val_if_fail ( extlc != NULL, "" );

	ret = g_hash_table_lookup (ht, extlc);

	g_free(extlc);

	if (!ret)
		return "";

	return ret;
}



/* misc_get_ncpus
 *
 * returns the number of CPUs the current machine has from the
 * information in /proc/cpuinfo
 *
 * (we need to do this at least for the dns_lookup routines,
 *  because due to a bug in libgnet or glib the async dns lookup
 *  is prone to crash on SMP machines.)
 *
 * written by Roman Hodek.
 *
 * returns the number of CPUs or 0 in case of an error.
 *
 */

#define PROC_CPUINFO_FILE "/proc/cpuinfo"

static gint
misc_get_ncpus_check (void)
{
	FILE *f;
	gchar line[1024];
	gint n = 0;

	f = fopen(PROC_CPUINFO_FILE, "r");
	if (!f)
	{
		perror(PROC_CPUINFO_FILE);
		return 0;
	}

	while(fgets(line, sizeof(line), f))
	{
		if (g_ascii_strncasecmp(line, "processor", 9) == 0)
			 ++n;
	}

	fclose(f);

	return n;
}

gint
misc_get_ncpus (void)
{
	static gint num_cpus = -1;

	/* we only need to check once */
	if (num_cpus == -1)
	{
		num_cpus = misc_get_ncpus_check();
	}
	return num_cpus;
}


/* misc_ip_is_local
 *
 * IP is in 'edonkey-format' (ie. little endian, not network byte order)
 * returns TRUE if this IP is local
 *
 */

gboolean
misc_ip_is_local (guint32 ip)
{
	guint8 n4 = *((guint8*)&ip);
	guint8 n3 = *(((guint8*)&ip)+1);
	guint8 n2 = *(((guint8*)&ip)+2);
/*	guint8 n1 = *(((guint8*)&ip)+3); */

	if (n4==10)
		return TRUE;

	if (n4==172 && n3>=16 && n3<=31)
		return TRUE;

	if (n4==192 && n3==168)
		return TRUE;	

	if (n4==127 && n3==0 && n2==0)
		return TRUE;

	return FALSE;
}


/******************************************************************************
 *
 *   misc_check_if_ed2klink_is_valid
 *
 *   returns TRUE if the string passed is a valid ed2k file link,
 *    otherwise FALSE (note: it does not check for bad chars in filename)
 *
 *   If fn, size and hashstring are set, and the link is valid,
 *    memory is allocated and these vars are set to contain the fields.
 *    Caller must free this allocated memory!
 *
 *  note: ed2klink passed must be UTF-8
 *  note: filename returned is also in UTF-8 encoding
 *
 ******************************************************************************/

gboolean
misc_check_if_ed2klink_is_valid (const gchar *ed2klink_utf8, 
                                 gchar      **fn, 
                                 guint       *size, 
                                 gchar      **hashstring, 
                                 gboolean    *addpaused,
                                 guint       *addpriority)
{
	const gchar *checkpos = ed2klink_utf8;
	const gchar *fnpos;
	const gchar *sizepos;
	const gchar *hashpos;
	guint        filenamelength = 0;
	guint        hashlength = 0;
	guint        sizelength = 0;

	g_return_val_if_fail ( ed2klink_utf8 != NULL, FALSE );
	g_return_val_if_fail ( g_utf8_validate(ed2klink_utf8, -1, NULL) == TRUE, FALSE );

	if (addpriority)
		*addpriority = 1; /* normal */

	/* here utf8 and ascii are identical */
	if ( g_ascii_strncasecmp(checkpos,"ed2k:",5) != 0 )
		return FALSE;

	checkpos += 5;

	while ( *checkpos == '/' )
		checkpos++;

	if ( g_ascii_strncasecmp(checkpos,"|file|",6) != 0 )
		return FALSE;

	checkpos +=6;

	fnpos = checkpos;
	while ( g_utf8_get_char(checkpos) !=  (gunichar) '|' )
	{
		gchar *checkpos2;

		if (!g_unichar_isprint(g_utf8_get_char(checkpos)))	/* control character or sth like that? */
			return FALSE;

		checkpos2 = g_utf8_next_char(checkpos);
		filenamelength += (checkpos2 - checkpos);
		checkpos = checkpos2;
	}

	if ( filenamelength == 0 || filenamelength > NAME_MAX)
		return FALSE;

	checkpos = g_utf8_next_char(checkpos);	/* skip '|' */

	sizepos = checkpos;
	while ( g_utf8_get_char(checkpos) !=  (gunichar) '|' )
	{
		gchar *checkpos2;

		if (!g_unichar_isdigit(g_utf8_get_char(checkpos)))
			return FALSE;

		checkpos2 = g_utf8_next_char(checkpos);
		sizelength += (checkpos2 - checkpos);
		checkpos = checkpos2;
	}

	if ( sizelength == 0 ||  sizelength > 10)
		return FALSE;

	checkpos = g_utf8_next_char(checkpos);	/* skip '|' */

	hashpos = checkpos;
	while ( g_utf8_get_char(checkpos) !=  (gunichar) '|' )
	{
		gchar *checkpos2;

		if (!g_unichar_isxdigit(g_utf8_get_char(checkpos)))
			return FALSE;

		checkpos2 = g_utf8_next_char(checkpos);
		hashlength += (checkpos2 - checkpos);
		checkpos = checkpos2;
	}

	if ( hashlength != 32 )
		return FALSE;

	if (fn)
	{
		*fn = g_new(gchar, filenamelength+1);
		g_return_val_if_fail(fn!=NULL,FALSE);
		memcpy (*fn, fnpos, filenamelength*sizeof(gchar));
		*((*fn)+filenamelength) = 0x00;
	}

	if (size)
	{
		*size = atoi(sizepos);
	}

	/* these are hex digit, ie. utf8 == ascii */
	if (hashstring)
	{
		*hashstring = g_new(gchar, 33);
		g_return_val_if_fail (hashstring!=NULL,FALSE);
		memcpy (*hashstring, hashpos, 32*sizeof(gchar));
		*((*hashstring)+32) = 0x00;
	}

	if (addpaused)
	{
		if (strstr(ed2klink_utf8, "|pause|") != NULL)
			*addpaused = TRUE;
		else
			*addpaused = FALSE;
	}

	if (addpriority)
	{
		if (strstr (ed2klink_utf8, "|low|") != NULL)
			*addpriority = 0;
		else if (strstr (ed2klink_utf8, "|normal|") != NULL)
			*addpriority = 1;
		else if (strstr (ed2klink_utf8, "|high|") != NULL)
			*addpriority = 2;
		else if (strstr (ed2klink_utf8, "|highest|") != NULL)
			*addpriority = 3;
	}

	return TRUE;
}


/******************************************************************************
 *
 *   misc_boot_overnet
 *
 *   Tries to find overnet contacts by resolving dyndns hostnames, and
 *     tries booting from those.
 *
 ***/

#define BOOT_OVERNET_INTERVAL     5

static GSList *overnet_bootstrings = NULL;
static gint    overnet_boot_timeout_id = 0;

static gboolean
misc_boot_overnet_timeout (gpointer data)
{
	/* this will trigger the routine in parse_message to
	 * call misc_boot_overnet() again if we still have no
	 * contacts, which will take the next ip from the list */

	gui_core_conn_send_get_overnet_contacts(core);

	return TRUE;
}

void
misc_boot_overnet (void)
{
	if ( overnet_bootstrings == NULL )
	{
		static time_t last = 0;
		if ( last == 0 || (time(NULL)-last) > 60 )
		{
			status_message_blue (_("GUI: Looks like overnet has no contacts. Trying to bootstrap into the network...\n"));
			http_get_retrieve_serverlist ("http://ed2k-gtk-gui.sourceforge.net/contacts.txt", 0);
			last = time(NULL);
		}
	}
	else
	{
		static time_t last = 0;

		if ( last == 0 || (time(NULL)-last) >= BOOT_OVERNET_INTERVAL )
		{
			/* strip first entry off the list and send it to core */

			GSList *node;
			guint   num;

			srand(time(NULL));
			num = rand() % g_slist_length(overnet_bootstrings);

			node = g_slist_nth(overnet_bootstrings, num);

			g_return_if_fail ( node != NULL );

			if (node->data)
			{
				gchar *cmd = (gchar*)node->data;
				status_message_blue (_("Trying to bootstrap from contact %s ...\n"), cmd+5);
				gui_core_conn_send_command(core, cmd);
				g_free(cmd);
				last = time(NULL);
			}

			overnet_bootstrings = g_slist_delete_link (overnet_bootstrings, node);
		}
	}
}


void
misc_boot_overnet_queue_boot_string (gchar *command)
{
	static time_t  sent_last = 0;

	g_return_if_fail ( command != NULL );

	if ( sent_last == 0  ||  (time(NULL)-sent_last) > BOOT_OVERNET_INTERVAL )
	{
		status_message_blue (_("Trying to bootstrap from contact %s ...\n"), command+5);
		gui_core_conn_send_command(core, command);
		g_free(command);
	}
	else
	{
		overnet_bootstrings = g_slist_append (overnet_bootstrings, (gpointer)command);
	}

	if ( overnet_boot_timeout_id == 0 )
		overnet_boot_timeout_id = g_timeout_add ( BOOT_OVERNET_INTERVAL*1000, misc_boot_overnet_timeout, NULL );

	sent_last = time(NULL);
}


void
misc_boot_overnet_stop_booting (void)
{
	if ( overnet_boot_timeout_id > 0 )
	{
		GSList *node = overnet_bootstrings;

		g_source_remove(overnet_boot_timeout_id);
		overnet_boot_timeout_id = 0;

		if ( overnet_bootstrings != 0 )
			status_message_blue (_("Looks like we are part of the overnet network now.\n"));

		while (node)
		{
			G_FREE(node->data);
			node = node->next;
		}
		g_slist_free(overnet_bootstrings);

		overnet_bootstrings = NULL;
	}
}


/******************************************************************************
 *
 *   misc_get_prio_string
 *
 *   returns translated priority string in UTF8
 *
 ***/

const gchar *
misc_get_prio_string (guint prio)
{
	static gchar    *prio_strings_utf8[5]; /* all NULL */

	if (!prio_strings_utf8[0])
	{
		prio_strings_utf8[0] = TO_UTF8(_("low"));
		prio_strings_utf8[1] = TO_UTF8(_("normal"));
		prio_strings_utf8[2] = TO_UTF8(_("high"));
		prio_strings_utf8[3] = TO_UTF8(_("highest"));
		prio_strings_utf8[4] = TO_UTF8(_("unknown"));
	}

	return prio_strings_utf8[MIN(4,prio)];
}

/******************************************************************************
 *
 *   misc_get_human_size_utf8
 *
 *   returns size as 'human size' string in UTF-8 format
 *
 ***/

const gchar *
misc_get_human_size_utf8 (gchar *buf, guint buflen, guint size)
{
	static gboolean  initialised = FALSE;
	static gchar    *b_template, *kb_template, *mb_template, *gb_template;
	static gchar     sizestring[32];

	if (!buf)
	{
		buf = sizestring;
		buflen = sizeof(sizestring)/sizeof(gchar);
	}

	if ( !initialised )
	{
		b_template  = TO_UTF8("%ub");
		kb_template = TO_UTF8("%uk");
		mb_template = TO_UTF8("%u.%02uM");
		gb_template = TO_UTF8("%.2fG");
		initialised = TRUE;
	}

	if ( size < 1024 )
		g_snprintf (buf, buflen, b_template, size);                         /* bytes */

	else if ( size < (1024*1024) )
		g_snprintf (buf, buflen, kb_template, (size >> 10) );               /* kB    */

	else if ( size <= (1024*1024*1024) )
		g_snprintf (buf, buflen, mb_template,
		            size >> 20, (size & 0xFFFFF) * 100 / 0xFFFFF);               /* MB    */

	else
		/* floats actually seem to be faster than longs here */
		g_snprintf (buf, buflen, gb_template, ((gfloat)size)/1073741824.0); /* GB    */

	return buf;
}


/*******************************************************************************
 *
 *   misc_hash_hash
 *
 *   Take 128-bit hash and turn that into an integer hash value
 *
 ***/

guint
misc_hash_hash (gconstpointer key)
{
	guint32  *md4;

	g_return_val_if_fail ( key != NULL, 0 );

	md4 = (guint32*) key;

	return (guint) (((md4[0] ^ md4[1]) ^ md4[2]) ^ md4[3]);
}


/*******************************************************************************
 *
 *   misc_hash_equal
 *
 *   Returns TRUE if both 128-bit hashes are equal
 *
 *   TODO: use guint64 here? or would this actually be slower on 32-bit machines?
 *
 ***/

gboolean
misc_hash_equal (gconstpointer key1, gconstpointer key2)
{
	g_assert ((key1) && (key2));

	return (    *(((const guint32*)key1)+ 0) == *(((const guint32*)key2)+ 0)
	         && *(((const guint32*)key1)+ 1) == *(((const guint32*)key2)+ 1)
	         && *(((const guint32*)key1)+ 2) == *(((const guint32*)key2)+ 2)
	         && *(((const guint32*)key1)+ 3) == *(((const guint32*)key2)+ 3));
}


/*******************************************************************************
 *
 *   misc_hashtable_destroy
 *
 *   g_object_set_data_full() callback to destroy hashtables when
 *   widgets/g_objects are destroyed.
 *
 ***/

void
misc_hashtable_destroy (GHashTable **p_ht)
{
	g_return_if_fail ( p_ht != NULL );

	if (*p_ht)
	{
		g_hash_table_destroy(*p_ht);
		*p_ht = NULL;
	}
}



/******************************************************************************
 *
 *   misc_remove_junk_from_servername
 *
 *   Takes a server name and removes all junk from it, so
 *     it doesn't take more space than necessary in the toolbar.
 *
 *   Caller needs to free returned string.
 *
 ******************************************************************************/

gchar *
misc_remove_junk_from_servername (const gchar *name)
{
	gint	 n1,n2,n3,n4;
	gchar	*newname, *pos, *match;

	g_return_val_if_fail (name!=NULL, NULL);

	/* empty string? */
	if (!*name)
		return NULL;

	/* is it an IP? we don't need to modify that. */
	if (sscanf(name,"%u.%u.%u.%u",&n1,&n2,&n3,&n4)==4)
		return g_strdup(name);

	newname = g_strdup(name);
	g_return_val_if_fail (newname!=NULL, NULL);

	pos = newname;
	while (*pos)
	{
		if (   g_ascii_strncasecmp(pos,".de",3)==0
			|| g_ascii_strncasecmp(pos,".vu",3)==0
			|| g_ascii_strncasecmp(pos,".fr",3)==0
			|| g_ascii_strncasecmp(pos,".com",4)==0
			|| g_ascii_strncasecmp(pos,".net",4)==0
			|| g_ascii_strncasecmp(pos,".org",4)==0)
		{
			*pos = 0x00;
		}

		else if (!g_ascii_isalnum(*pos))
			*pos = ' ';

		else if (g_ascii_strncasecmp(pos,"http",4)==0)
			strncpy (pos, "         ",4);

		else if (g_ascii_strncasecmp(pos,"www",3)==0)
			strncpy (pos, "         ",3);

		else if (g_ascii_strncasecmp(pos,"server",6)==0)
			strncpy (pos, "         ",6);

		else if (g_ascii_strncasecmp(pos,"serveur",7)==0)
			strncpy (pos, "         ",7);

		else if (g_ascii_strncasecmp(pos,"sparen",6)==0)
			strncpy (pos, "         ",6);

		else if (g_ascii_strncasecmp(pos,"verdienen",9)==0)
			strncpy (pos, "         ",9);

		else if (g_ascii_strncasecmp(pos,"gewinnen",8)==0)
			strncpy (pos, "         ",8);

		pos++;
	}

	if ((match=strstr(newname,".to/")))
	{
		gchar *newname_old = newname;
		newname = g_strdup(newname+4);
		g_free(newname_old);
	}
	else if ((match=strstr(newname,".TO/")))
	{
		gchar *newname_old = newname;
		newname = g_strdup(newname+4);
		g_free(newname_old);
	}

	g_strdelimit (newname, "_-|><.:;~[](){}+=#@/\\,!\"£$%^&*", ' ');

	/* remove leading and trailing spaces */
	g_strstrip (newname);

	/* remove multiple spaces within the string */
	pos = newname;
	while (*pos)
	{
		if (*pos == ' ' && *(pos+1) == ' ')
		{
			gchar *nospace = pos+2;
			while (*nospace == ' ')
				nospace++;
			g_memmove(pos+1, nospace, strlen(nospace) + 1);
		}
		pos++;
	}

	return newname;
}


