/*
 *  misc.c:		Miscellaneous functions
 *
 *  Written by:		Ullrich Hafner
 *		
 *  Copyright (C) 1998 Ullrich Hafner <hafner@bigfoot.de>
 *
 *  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, USA.
 */

/*
 *  $Date: 2000/09/25 18:15:23 $
 *  $Author: hafner $
 *  $Revision: 1.28 $
 *  $State: Exp $
 */

#include "config.h"

#if HAVE_STDLIB_H
#	include <stdlib.h>
#endif /* not HAVE_STDLIB_H */
#if HAVE_STRING_H
#	include <string.h>
#else /* not HAVE_STRING_H */
#	include <strings.h>
#endif /* not HAVE_STRING_H */
#if HAVE_SYS_TYPES_H
#	include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <stdio.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <pwd.h>
#if HAVE_SYS_STAT_H
#	include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */
/* unistd.h defines _POSIX_VERSION on POSIX.1 systems. */
#if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION)
#   include <dirent.h>
#   define NLENGTH(dirent) (strlen ((dirent)->d_name))
#else
#   define dirent direct
#   define NLENGTH(dirent) ((dirent)->d_namlen)
#   ifdef HAVE_SYS_NDIR_H
#       include <sys/ndir.h>
#   endif /* HAVE_SYS_NDIR_H */
#   ifdef HAVE_SYS_DIR_H
#       include <sys/dir.h>
#   endif /* HAVE_SYS_DIR_H */
#   ifdef HAVE_NDIR_H
#       include <ndir.h>
#   endif /* HAVE_NDIR_H */
#endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */

#include "misc.h"
#include "load.h"
#include "dialog.h"
#include "error.h"

void
Free (void *ptr)
{
   g_free (ptr);
}

void *
Calloc (size_t n, size_t size)
/*
 *  Allocate memory like calloc ().
 *
 *  Return value: Pointer to the new block of memory on success,
 *		  else terminate the program.
 */
{
   void		*ptr;			/* pointer to the new memory block */

   if (n <= 0 || size <= 0)
      error ("Can't allocate memory for %d items of size %d",
	     (unsigned) n, (unsigned) size);

   ptr = g_malloc (n * size);
   if (ptr == NULL)
      error (_("Out of memory!"));
   memset (ptr, 0, n * size);
   return ptr;
}

char *
get_tempdir (void)
{
   static char * tempdir;
   tempdir = getenv ("TMPDIR");
   if (tempdir && *tempdir)
      return g_strdup (tempdir);
   else
      return g_strdup ("/tmp");
}

char *
make_temporary_directory (void)
{
#ifdef HAVE_MKDTEMP
   static char *name = NULL;
   static char *tmpdir;

   Free (name);
   tmpdir = get_tempdir ();
   name = g_strconcat (tmpdir, "/wmdirXXXXXX", NULL);
   Free (tmpdir);

   if (!mkdtemp (name))
   {
      Free (name);
      name = NULL;
   }
      
#else /* not HAVE_MKDTEMP */
   char *name = tmpnam (NULL);
   if (name)
      make_directory (name);
     
#endif /* not HAVE_MKDTEMP */
   
   if (name)
      return name;
   else
   {
      make_directory ("/tmp/wmakerconf.dir");
      return "/tmp/wmakerconf.dir";
   }
}

char *
get_temporary_file_name (void)
{
#ifdef HAVE_MKSTEMP
   static char *name = NULL;
   static char *tmpdir;
   int fd;
   
   Free (name);
   tmpdir = get_tempdir ();
   name = g_strconcat (tmpdir, "/wmcnfXXXXXX", NULL);
   Free (tmpdir);
   
   if ((fd = mkstemp (name)) >= 0)
      close (fd);
   else
   {
      Free (name);
      name = NULL;
   }

#else  /* not HAVE_MKSTEMP */
   char *name = tmpnam (NULL);
#endif /* not HAVE_MKSTEMP */

   if (name)
      return name;
   else
      return "/tmp/wmakerconf.tmp";
}

char *
get_gnustep_path (const char *domain)
/*
 *  Generate path to file 'domain' in users GNUstep directory
 *
 *  E.g. domain = 'Defaults/WindowMaker', 'Defaults/WMRootMenu'
 *
 *  Return value:
 *	pointer to char array containing generated path
 */
{
   const char *gspath = getenv ("GNUSTEP_USER_ROOT");
   
   if (gspath)
      return g_strconcat (gspath, "/", domain, NULL);
   else
      return g_strconcat (g_get_home_dir (), "/GNUstep/", domain, NULL);
}

char *
expand_tilde (const char *name)
/*
 *  Try to expand tilde (~) in filename.
 *
 *  Return value:
 *	string with expanded path 
 */
{
   struct passwd *user = NULL;
   
   assert (name);

   if (name [0] != '~' && name [0] != '$')
      return g_strdup (name);		/* nothing to do */
   
   if (name [1] == '/' || !name [1]	/* users home directory */
       || strncmp (name, "$HOME", 5) == 0
       || strncmp (name, "$(HOME)", 7) == 0)
   {
      if (name [0] == '~')
	 name += 1;			/* skip ~ */
      else if (strncmp (name, "$HOME", 5) == 0)
	 name += 5;
      else if (strncmp (name, "$(HOME)", 7) == 0)
	 name += 7;
      
      return g_strconcat (g_get_home_dir (), name, NULL);
   }
   else if (name [0] == '$')		/* environment expansion */
   {
      const char *first = name + 1;
      const char *last;
      const char *rest;
      
      if (*first == '(')		/* $(ENV) */
      {
	 if (!(last = strrchr (name, ')')))
	    return g_strdup (name);	/* parse error */
	 first++;
	 rest = last + 1;
	 last--;
      }
      else
      {
	 if (!(rest = strchr (name, '/'))) /* "$ENV" */
	 {
	    last = first + strlen (first) - 1;
	    rest = last + 1;		/* empty */
	 }
	 else				/* "$ENV/rest" */
	    last = rest - 1;
      }
      {
	 char		*var = g_strndup (first, last - first + 1);
	 const char	*dir = getenv (var);

	 Free (var);
	 if (dir)
	    return g_strconcat (dir, rest, NULL);
	 else
	    return g_strdup (name);	/* parse error */
      }
   }
#ifdef HAVE_GETPWNAM   
   else					/* other user directory */
   {
      char *usrname = strchr (name, '/');

      if (usrname)
      {
	 char *tmp = g_strndup (name + 1, usrname - name - 1);
	 
	 user = getpwnam (tmp);
	 Free (tmp);
	 name = usrname;		/* first slash */
      }
      else
      {
	 user = getpwnam (name + 1);
	 name += strlen (name);		/* empty string */
      }
   }

   if (!user)
      warning (_("Can't find passwd entry to expand\n`~' in filename %s"));
   else
      return g_strconcat (user->pw_dir, name, NULL);
#endif /* HAVE_GETPWNAM */

   return g_strdup (name);		/* no success */
}

bool_t
delete_file_or_dir (const char *filename)
/*
 *  Delete the given file or directory 'filename' (works like rm and rmdir).
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
#ifdef HAVE_REMOVE
   
   if (remove (filename) == -1)
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL,
		    _("Can't delete\n'%s'\n\n%s"),
		    filename, strerror (errno));
      return YES;
   }
   else
      return NO;
   
#else  /* not HAVE_REMOVE */

   char   *tmp;
   DIR    *dir 	      = opendir (filename);
   char   *quotedname = protect_quotes (g_strdup (filename));
   char   *msg 	      = g_strdup_printf (_("Can't delete\n`%s'"), filename);
   bool_t  returncode;
   
   if (!dir)				/* a file */
      tmp = g_strconcat ("rm -f \"", quotedname, "\"", NULL);
   else					/* a directory */
   {
      tmp = g_strconcat ("rmdir \"", quotedname, "\"", NULL);
      closedir (dir);
   }
   
   returncode = shell_command (tmp, msg);
   
   g_free (tmp);
   g_free (msg);
   g_free (quotedname);
   
   return returncode;
   
#endif /* not HAVE_REMOVE */
}

bool_t
rename_file_or_dir (const char *oldname, const char *newname)
/*
 *  Rename file 'oldname' to 'newname' (works like mv).
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
#ifdef HAVE_RENAME
   
   if (rename (oldname, newname) == -1)
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL,
		    _("Can't move\n'%s'\nto\n'%s'\n\n%s"),
		    oldname, newname, strerror (errno));
      return YES;
   }
   else
      return NO;
   
#else  /* not HAVE_RENAME */

   char   *quotedoldname = protect_quotes (g_strdup (oldname));
   char   *quotednewname = protect_quotes (g_strdup (newname));
   char   *cmd 		 = g_strconcat ("mv \"", quotedoldname, "\" \"",
					quotednewname, "\"", NULL);
   char   *msg 		 = g_strdup_printf (_("Can't move\n'%s'\nto\n'%s'"),
					    oldname, newname);
   bool_t  returncode;
   
   returncode = shell_command (cmd, msg);
   
   g_free (cmd);
   g_free (msg);
   g_free (quotednewname);
   g_free (quotedoldname);
   
   return returncode;
   
#endif /* not HAVE_RENAME */
}

bool_t
remove_directory (const char *directory)
/*
 *  Recursively delete the given directory 'directory' with all its
 *  files and subdirectories (works like rm -rf).
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
   DIR *dir = opendir (directory);
   
   if (!dir)				/* a file */
      return delete_file_or_dir (directory);
   else
   {
      struct dirent *file;
      
      while ((file = readdir (dir)))
      {
	 if (!streq (file->d_name, ".")
	     && !streq (file->d_name, ".."))
	 {
	    char *name = g_strconcat (directory, "/", file->d_name, NULL);
	    if (remove_directory (name))
	    {
	       closedir (dir);
	       Free (name);
	       return YES;
	    };
	    Free (name);
	 }
      }
      closedir (dir);
      return delete_file_or_dir (directory);
   }
}

bool_t
make_directory (const char *dirname)
/*
 *  Create a nmew directory 'dirname' (works like mkdir).
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
#if defined(HAVE_MKDIR) && defined(HAVE_SYS_STAT_H)

   if (mkdir (dirname, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) == -1)
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL,
		    _("Can't create directory\n'%s'\n\n%s"),
		    dirname, strerror (errno));
      return YES;
   }
   else
      return NO;
   
   
#else  /* not defined(HAVE_MKDIR) && defined(HAVE_SYS_STAT_H) */

   char *quotedname = protect_quotes (g_strdup (dirname));
   char *cmd 	    = g_strconcat ("mkdir \"", quotedname, "\"", NULL);
   char *msg 	    = g_strdup_printf (_("Can't create directory\n`%s'\n"),
				       quotedname);
   
   returncode = shell_command (cmd, msg);
   
   g_free (cmd);
   g_free (msg);
   g_free (quotedname);
   
   return returncode;
   
#endif /* not defined(HAVE_MKDIR) && defined(HAVE_SYS_STAT_H) */
}

bool_t
copy_file (const char *dst, const char *src)
/*
 *  Copy file 'src' to file or directory 'dst' (works like cp).
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
   if (!streq (src, dst))
   {
      DIR  *dir = opendir (dst);
      char *dst_name;
      FILE *fsrc;
      FILE *fdst;
   
      if (dir)				/* directory is destination */
      {
	 closedir (dir);
	 if (streq (dst, g_dirname (src)))
	    return 0;
	 dst_name = g_strconcat (dst, "/", g_basename (src), NULL);
      }
      else
	 dst_name = g_strdup (dst);
   
      fsrc = fopen (src, "r");
      if (!fsrc)
      {
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Can't open input file\n'%s'\n\n%s"),
		       src, strerror (errno));
	 return YES;
      }
      fdst = fopen (dst_name, "w");
      if (!fdst)
      {
	 fclose (fsrc);
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Can't open output file\n'%s'\n\n%s"),
		       dst_name, strerror (errno));
	 return YES;
      }
      {
	 char   buffer [4096];
	 size_t n;

	 do
	 {
	    size_t m;
	 
	    n = fread (buffer, 1, 4096, fsrc);
	    if (n)
	       m = fwrite (buffer, 1, n, fdst);
	    else
	       m = 0;
	    if (n != m)
	    {
	       fclose (fsrc);
	       fclose (fdst);
	       dialog_popup (DIALOG_ERROR, NULL, NULL,
			     _("Can't write to output file\n'%s'\n\n%s"),
			     dst_name, strerror (errno));
	       return YES;
	    }
	 } while (n == 4096);
      }
      fclose (fsrc);
      fclose (fdst);
      Free (dst_name);
   }
   return NO;
}

bool_t
shell_command (const char *command, const char *text)
/*
 *  Execute shell 'command'.
 *  If the operation fails, show a popup error window.
 *
 *  Return value:
 *	FALSE on success, TRUE on error
 */
{
#if defined(HAVE_POPEN)
   extern GtkWidget *log_text;
   GtkTextBuffer    *buffer;
   GtkTextIter	     iter;
   char 	    *cmd  = g_strconcat (command, " 2>&1", NULL);
   FILE		    *pipe = popen (cmd, "r");
   char		     tmp [MAXSTRLEN];

   Free (cmd);
   if (!pipe)
   {
      if (text)
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("%s\nShell command failed.\n'%s'"), text, command);
      else
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Shell command failed.\n'%s'"), command);
      return YES;
   }

   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (log_text));
   while (fgets (tmp, MAXSTRLEN, pipe))
   {
      gtk_text_buffer_get_iter_at_offset (buffer, &iter, -1);
      gtk_text_buffer_insert (buffer, &iter, tmp, -1);

      gtk_widget_show_all (gtk_widget_get_toplevel (log_text));
      while (gtk_events_pending())
	 gtk_main_iteration();
   }
   if (pclose (pipe))
   {
      if (text)
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("%s\nShell command failed.\n'%s'"), text, command);
      else
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Shell command failed.\n'%s'"), command);
      return YES;
   }
   else
      return NO;
   
#else /* not HAVE_POPEN */

   if (system (command))
   {
      if (text)
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("%s\nShell command failed.\n'%s'\n"
			 "Please check `stderr' for more details."),
		       text, command);
      else
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Shell command failed.\n'%s'\n"
			 "Please check `stderr' for more details."), command);
      return YES;
   }
   else
      return NO;

#endif /* not HAVE_POPEN */
}

bool_t
file_exists (const char *filename)
/*
 *  Checks whether a directory or file 'filename' already exists.
 *
 *  Return value:
 *	FALSE if file does not exist, TRUE if file exists
 */
{
   DIR *dir = opendir (filename);
   
   if (dir)				/* directory exists */
   {
      closedir (dir);
      return YES;
   }
   else
   {
      FILE *file = fopen (filename, "r");
      if (!file)
	 return NO;
      else
      {
	 fclose (file);
	 return YES;
      }
   }
}

char *
preview_name (const char *name)
/*
 *  Compute the filename of the preview of the given image 'name'.
 *  E.g., filename /path/to/foo.jpg is expanded to
 *  ~/.wmakerconf-path-to-foo.jpg
 *
 *  Return value:
 *	preview filename
 */
{
   char *str = g_strdup (name);
   char *ptr, *path;

   for (ptr = str; *ptr; ptr++)
      if (*ptr == '/')
	 *ptr = '-';
   path = g_strconcat (g_get_home_dir (), "/.wmakerconf/", str, NULL);
   Free (str);

   return path;
}

proplist_t
read_proplist (const char *filename)
/*
 *  Call Alfredo's PropList parser ...
 */
{
   DIR *dir = opendir (filename);

   if (dir)				/* directory */
   {
      closedir (dir);
      return NULL;
   }
   else
      return ReadProplistFromFile (filename);
}

char *
protect_quotes (char *string)
/*
 *  Protect double quotes in the given string.
 *  The old string is freed if it contains a double quote ".
 *
 *  Return value:
 *	new string with \" instead of "
 */
{
   char *pos;
   int  offset = 0;

   while ((pos = strchr (string + offset, '"')))	
   {
      char *new;
      if (pos > string + offset)	/* not first character */
      {
	 if (*(pos - 1) == '\\')	/* if '\' is already present */
	 {
	    offset = pos - string + 1;
	    continue;
	 }
      }
      offset = pos - string + 2;
      
      *pos = 0;
      new = g_strconcat (string, "\\\"", pos + 1, NULL);
      Free (string);

      string = new;
   }

   return string;
}
