/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Engrampa
 *
 *  Copyright (C) 2001, 2003 Free Software Foundation, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#define _XOPEN_SOURCE 700

#include <config.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <dirent.h>
#include <strings.h>

#include <glib.h>
#include <gio/gio.h>
#include <zip.h>
#include "file-utils.h"
#include "glib-utils.h"
#include "fr-init.h"
#include "gtk-utils.h"


#define BUF_SIZE 4096
#define FILE_PREFIX    "file://"
#define FILE_PREFIX_L  7
#define SPECIAL_DIR(x) ((strcmp ((x), "..") == 0) || (strcmp ((x), ".") == 0))


gboolean
uri_exists (const char *uri)
{
	GFile     *file;
	gboolean   exists;

	if (uri == NULL)
		return FALSE;

	file = g_file_new_for_uri (uri);
	exists = g_file_query_exists (file, NULL);
	g_object_unref (file);

	return exists;
}


static gboolean
uri_is_filetype (const char *uri,
		 GFileType   file_type)
{
	gboolean   result = FALSE;
	GFile     *file;
	GFileInfo *info;
	GError    *error = NULL;

	file = g_file_new_for_uri (uri);

	if (! g_file_query_exists (file, NULL)) {
		g_object_unref (file);
		return FALSE;
	}

	info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, &error);
	if (error == NULL) {
		result = (g_file_info_get_file_type (info) == file_type);
	}
	else {
		g_warning ("Failed to get file type for uri %s: %s", uri, error->message);
		g_error_free (error);
	}

	g_object_unref (info);
	g_object_unref (file);

	return result;
}


gboolean
uri_is_file (const char *uri)
{
	return uri_is_filetype (uri, G_FILE_TYPE_REGULAR);
}


gboolean
uri_is_dir (const char *uri)
{
	return uri_is_filetype (uri, G_FILE_TYPE_DIRECTORY);
}


gboolean
path_is_dir (const char *path)
{
	char     *uri;
	gboolean  result;

	uri = g_filename_to_uri (path, NULL, NULL);
	result = uri_is_dir (uri);
	g_free (uri);

	return result;
}

gboolean
uri_is_local (const char  *uri)
{
	return strncmp (uri, "file://", 7) == 0;
}


gboolean
dir_is_empty (const char *uri)
{
	GFile           *file;
	GFileEnumerator *file_enum;
	GFileInfo       *info;
	GError          *error = NULL;
	int              n = 0;

	file = g_file_new_for_uri (uri);

	if (! g_file_query_exists (file, NULL)) {
		g_object_unref (file);
		return TRUE;
	}

	file_enum = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
	if (error != NULL) {
		g_warning ("Failed to enumerate children of %s: %s", uri, error->message);
		g_error_free (error);
		g_object_unref (file_enum);
		g_object_unref (file);
		return TRUE;
	}

	while ((n == 0) && ((info = g_file_enumerator_next_file (file_enum, NULL, &error)) != NULL)) {
		if (error != NULL) {
			g_warning ("Encountered error while enumerating children of %s (ignoring): %s", uri, error->message);
			g_error_free (error);
		}
		else if (! SPECIAL_DIR (g_file_info_get_name (info)))
			n++;
		g_object_unref (info);
	}

	g_object_unref (file);
	g_object_unref (file_enum);

	return (n == 0);
}


gboolean
dir_contains_one_object (const char *uri)
{
	GFile           *file;
	GFileEnumerator *file_enum;
	GFileInfo       *info;
	GError          *err = NULL;
	int              n = 0;

	file = g_file_new_for_uri (uri);

	if (! g_file_query_exists (file, NULL)) {
		g_object_unref (file);
		return FALSE;
	}

	file_enum = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &err);
	if (err != NULL) {
		g_warning ("Failed to enumerate children of %s: %s", uri, err->message);
		g_error_free (err);
		g_object_unref (file_enum);
		g_object_unref (file);
		return FALSE;
	}

	while ((info = g_file_enumerator_next_file (file_enum, NULL, &err)) != NULL) {
		const char *name;

		if (err != NULL) {
			g_warning ("Encountered error while enumerating children of %s, ignoring: %s", uri, err->message);
			g_error_free (err);
			g_object_unref (info);
			continue;
		}

		name = g_file_info_get_name (info);
		if (strcmp (name, ".") == 0 || strcmp (name, "..") == 0) {
			g_object_unref (info);
 			continue;
		}

		g_object_unref (info);

		if (++n > 1)
			break;
	}

	g_object_unref (file);
	g_object_unref (file_enum);

	return (n == 1);
}


char *
get_dir_content_if_unique (const char  *uri)
{
	GFile           *file;
	GFileEnumerator *file_enum;
	GFileInfo       *info;
	GError          *err = NULL;
	char            *content_uri = NULL;

	file = g_file_new_for_uri (uri);

	if (! g_file_query_exists (file, NULL)) {
		g_object_unref (file);
		return NULL;
	}

	file_enum = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &err);
	if (err != NULL) {
		g_warning ("Failed to enumerate children of %s: %s", uri, err->message);
		g_error_free (err);
		return NULL;
	}

	while ((info = g_file_enumerator_next_file (file_enum, NULL, &err)) != NULL) {
		const char *name;

		if (err != NULL) {
			g_warning ("Failed to get info while enumerating children: %s", err->message);
			g_clear_error (&err);
			g_object_unref (info);
			continue;
		}

		name = g_file_info_get_name (info);
		if ((strcmp (name, ".") == 0) || (strcmp (name, "..") == 0)) {
			g_object_unref (info);
			continue;
		}

		if (content_uri != NULL) {
			g_free (content_uri);
			g_object_unref (info);
			content_uri = NULL;
			break;
		}

		content_uri = build_uri_escaped (uri, name, NULL);
		g_object_unref (info);
	}

	if (err != NULL) {
		g_warning ("Failed to get info after enumerating children: %s", err->message);
		g_clear_error (&err);
	}

	g_object_unref (file_enum);
	g_object_unref (file);

	return content_uri;
}


/* Check whether the dirname is contained in filename */
gboolean
path_in_path (const char *dirname,
	      const char *filename)
{
	int dirname_l, filename_l, separator_position;

	if ((dirname == NULL) || (filename == NULL))
		return FALSE;

	dirname_l = strlen (dirname);
	filename_l = strlen (filename);

	if ((dirname_l == filename_l + 1)
	     && (dirname[dirname_l - 1] == '/'))
		return FALSE;

	if ((filename_l == dirname_l + 1)
	     && (filename[filename_l - 1] == '/'))
		return FALSE;

	if (dirname[dirname_l - 1] == '/')
		separator_position = dirname_l - 1;
	else
		separator_position = dirname_l;

	return ((filename_l > dirname_l)
		&& (strncmp (dirname, filename, dirname_l) == 0)
		&& (filename[separator_position] == '/'));
}


goffset
get_file_size (const char *uri)
{
	goffset    size = 0;
	GFile     *file;
	GFileInfo *info;
	GError    *err = NULL;

	if ((uri == NULL) || (*uri == '\0'))
		return 0;

	file = g_file_new_for_uri (uri);
	info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE, 0, NULL, &err);
	if (err == NULL) {
		size = g_file_info_get_size (info);
	}
	else {
		g_warning ("Failed to get file size for %s: %s", uri, err->message);
		g_error_free (err);
	}

	g_object_unref (info);
	g_object_unref (file);

	return size;
}


goffset
get_file_size_for_path (const char *path)
{
	char    *uri;
	goffset  result;

	uri = g_filename_to_uri (path, NULL, NULL);
	result = get_file_size (uri);
	g_free (uri);

	return result;
}


static time_t
get_file_time_type (const char *uri,
		    const char *type)
{
	time_t     result = 0;
	GFile     *file;
	GFileInfo *info;
	GError    *err = NULL;

	if ((uri == NULL) || (*uri == '\0'))
 		return 0;

	file = g_file_new_for_uri (uri);
	info = g_file_query_info (file, type, 0, NULL, &err);
	if (err == NULL) {
		result = (time_t) g_file_info_get_attribute_uint64 (info, type);
	}
	else {
		g_warning ("Failed to get %s: %s", type, err->message);
		g_error_free (err);
		result = 0;
	}

	g_object_unref (info);
	g_object_unref (file);

	return result;
}


time_t
get_file_mtime (const char *uri)
{
	return get_file_time_type (uri, G_FILE_ATTRIBUTE_TIME_MODIFIED);
}


time_t
get_file_mtime_for_path (const char *path)
{
	char   *uri;
	time_t  result;

	uri = g_filename_to_uri (path, NULL, NULL);
	result = get_file_mtime (uri);
	g_free (uri);

	return result;
}


time_t
get_file_ctime (const char *uri)
{
	return get_file_time_type (uri, G_FILE_ATTRIBUTE_TIME_CREATED);
}


gboolean
file_is_hidden (const gchar *name)
{
	if (name[0] != '.') return FALSE;
	if (name[1] == '\0') return FALSE;
	if ((name[1] == '.') && (name[2] == '\0')) return FALSE;

	return TRUE;
}


/* like g_path_get_basename but does not warn about NULL and does not
 * alloc a new string. */
const gchar* file_name_from_path(const gchar *file_name)
{
	register char   *base;
	register gssize  last_char;

	if (file_name == NULL)
		return NULL;

	if ((file_name[0] == '\0') || (strlen (file_name) == 0))
		return "";

	last_char = strlen (file_name) - 1;

	if (file_name [last_char] == G_DIR_SEPARATOR)
		return "";

	base = g_utf8_strrchr (file_name, -1, G_DIR_SEPARATOR);
	if (! base)
		return file_name;

	return base + 1;
}


char *
dir_name_from_path (const gchar *path)
{
	register gssize base;
	register gssize last_char;

	if (path == NULL)
		return NULL;

	if (path[0] == '\0')
		return g_strdup ("");

	last_char = strlen (path) - 1;
	if (path[last_char] == G_DIR_SEPARATOR)
		last_char--;

	base = last_char;
	while ((base >= 0) && (path[base] != G_DIR_SEPARATOR))
		base--;

	return g_strndup (path + base + 1, last_char - base);
}


gchar *
remove_level_from_path (const gchar *path)
{
	int         p;
	const char *ptr = path;
	char       *new_path;

	if (path == NULL)
		return NULL;

	p = strlen (path) - 1;
	if (p < 0)
		return NULL;

	while ((p > 0) && (ptr[p] != '/'))
		p--;
	if ((p == 0) && (ptr[p] == '/'))
		p++;
	new_path = g_strndup (path, (guint)p);

	return new_path;
}


char *
remove_ending_separator (const char *path)
{
	gint len, copy_len;

	if (path == NULL)
		return NULL;

	copy_len = len = strlen (path);
	if ((len > 1) && (path[len - 1] == '/'))
		copy_len--;

	return g_strndup (path, copy_len);
}


char *
build_uri (const char *base, ...)
{
	va_list     args;
	const char *child;
	GString    *uri;

	uri = g_string_new (base);

	va_start (args, base);
        while ((child = va_arg (args, const char *)) != NULL) {
        	if (! g_str_has_suffix (uri->str, "/") && ! g_str_has_prefix (child, "/"))
        		g_string_append (uri, "/");
//        	g_string_append_uri_escaped (uri, child, NULL, TRUE);
               	        	g_string_append (uri, child);
        }
	va_end (args);

	return g_string_free (uri, FALSE);
}

char *
build_uri_escaped (const char *base, ...)
{
	va_list     args;
	const char *child;
	GString    *uri;

	uri = g_string_new (base);

	va_start (args, base);
        while ((child = va_arg (args, const char *)) != NULL) {
        	if (! g_str_has_suffix (uri->str, "/") && ! g_str_has_prefix (child, "/"))
        		g_string_append (uri, "/");
       			g_string_append_uri_escaped (uri, child, NULL, TRUE);
        }
	va_end (args);

	return g_string_free (uri, FALSE);
}

gchar *
remove_extension_from_path (const gchar *path)
{
	int         len;
	int         p;
	const char *ptr = path;
	char       *new_path;

	if (! path)
		return NULL;

	len = strlen (path);
	if (len == 1)
		return g_strdup (path);

	p = len - 1;
	while ((p > 0) && (ptr[p] != '.'))
		p--;
	if (p == 0)
		p = len;
	new_path = g_strndup (path, (guint) p);

	if (g_str_has_suffix(path, ".tgz")) {
		return g_strconcat(new_path, ".tar", NULL);
	}

	return new_path;
}


gboolean
make_directory_tree (GFile    *dir,
		     mode_t    mode,
		     GError  **error)
{
	gboolean  success = TRUE;
	GFile    *parent;

	if ((dir == NULL) || g_file_query_exists (dir, NULL))
		return TRUE;

	parent = g_file_get_parent (dir);
	if (parent != NULL) {
		success = make_directory_tree (parent, mode, error);
		g_object_unref (parent);
		if (! success)
			return FALSE;
	}

	success = g_file_make_directory (dir, NULL, error);
	if ((error != NULL) && (*error != NULL) && g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
		g_clear_error (error);
		success = TRUE;
	}

	if (success)
		g_file_set_attribute_uint32 (dir,
					     G_FILE_ATTRIBUTE_UNIX_MODE,
					     mode,
					     0,
					     NULL,
					     NULL);

	return success;
}


gboolean
ensure_dir_exists (const char  *uri,
		   mode_t       mode,
		   GError     **error)
{
	GFile  *dir;
	GError *priv_error = NULL;

	if (uri == NULL)
		return FALSE;

	if (error == NULL)
		error = &priv_error;

	dir = g_file_new_for_uri (uri);
	if (! make_directory_tree (dir, mode, error)) {
		g_warning ("could create directory %s: %s", uri, (*error)->message);
		if (priv_error != NULL)
			g_clear_error (&priv_error);
		return FALSE;
	}

	return TRUE;
}


gboolean
make_directory_tree_from_path (const char  *path,
		   	       mode_t       mode,
		   	       GError     **error)
{
	char     *uri;
	gboolean  result;

	uri = g_filename_to_uri (path, NULL, NULL);
	result = ensure_dir_exists (uri, mode, error);
	g_free (uri);

	return result;
}


const char *
get_file_extension (const char *filename)
{
	const char *ptr = filename;
	int         len;
	int         p;
	const char *ext;

	if (filename == NULL)
		return NULL;

	len = strlen (filename);
	if (len <= 1)
		return NULL;

	p = len - 1;
	while ((p >= 0) && (ptr[p] != '.'))
		p--;
	if (p < 0)
		return NULL;

	ext = filename + p;
	if (ext - 4 > filename) {
		const char *test = ext - 4;
		if (strncmp (test, ".tar", 4) == 0)
			ext = ext - 4;
	}
	return ext;
}


gboolean
file_extension_is (const char *filename,
		   const char *ext)
{
	int filename_l, ext_l;

	filename_l = strlen (filename);
	ext_l = strlen (ext);

	if (filename_l < ext_l)
		return FALSE;
	return strcasecmp (filename + filename_l - ext_l, ext) == 0;
}


gboolean
is_mime_type (const char *mime_type,
	      const char *pattern)
{
	return (strcasecmp (mime_type, pattern) == 0);
}


const char*
get_file_mime_type (const char *uri,
                    gboolean    fast_file_type)
{
	GFile      *file;
	GFileInfo  *info;
	GError     *err = NULL;
 	const char *result = NULL;

 	file = g_file_new_for_uri (uri);
	info = g_file_query_info (file,
				  fast_file_type ?
				  G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE :
				  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
				  0, NULL, &err);
	if (info == NULL) {
		g_warning ("could not get content type for %s: %s", uri, err->message);
		g_clear_error (&err);
	}
	else {
		result = get_static_string (g_file_info_get_content_type (info));
		g_object_unref (info);
	}

	g_object_unref (file);

	return result;
}


const char*
get_file_mime_type_for_path (const char  *filename,
                    	     gboolean     fast_file_type)
{
	char       *uri;
	const char *mime_type;

	uri = g_filename_to_uri (filename, NULL, NULL);
	mime_type = get_file_mime_type (uri, fast_file_type);
	g_free (uri);

	return mime_type;
}


void
path_list_free (GList *path_list)
{
	if (path_list == NULL)
		return;
	g_list_free_full (path_list, g_free);
}


GList *
path_list_dup (GList *path_list)
{
	GList *new_list = NULL;
	GList *scan;

	for (scan = path_list; scan; scan = scan->next)
		new_list = g_list_prepend (new_list, g_strdup (scan->data));

	return g_list_reverse (new_list);
}


guint64
get_dest_free_space (const char *path)
{
	guint64    freespace = 0;
	GFile     *file;
	GFileInfo *info;
	GError    *err = NULL;

	file = g_file_new_for_path (path);
	info = g_file_query_filesystem_info (file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, &err);
	if (info != NULL) {
		freespace = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
		g_object_unref (info);
	}
	else {
		g_warning ("Could not get filesystem free space on volume that contains %s: %s", path, err->message);
		g_error_free (err);
	}
	g_object_unref (file);

	return freespace;
}


static gboolean
delete_directory_recursive (GFile   *dir,
			    GError **error)
{
	char            *uri;
	GFileEnumerator *file_enum;
	GFileInfo       *info;
	gboolean         error_occurred = FALSE;

	if (error != NULL)
		*error = NULL;

	file_enum = g_file_enumerate_children (dir,
					       G_FILE_ATTRIBUTE_STANDARD_NAME ","
					       G_FILE_ATTRIBUTE_STANDARD_TYPE,
					       0, NULL, error);

	uri = g_file_get_uri (dir);
	while (! error_occurred && (info = g_file_enumerator_next_file (file_enum, NULL, error)) != NULL) {
		char  *child_uri;
		GFile *child;

		child_uri = build_uri (uri, g_file_info_get_name (info), NULL);
		child = g_file_new_for_uri (child_uri);

		switch (g_file_info_get_file_type (info)) {
		case G_FILE_TYPE_DIRECTORY:
			if (! delete_directory_recursive (child, error))
				error_occurred = TRUE;
			break;
		default:
			if (! g_file_delete (child, NULL, error))
				error_occurred = TRUE;
			break;
		}

		g_object_unref (child);
		g_free (child_uri);
		g_object_unref (info);
	}
	g_free (uri);

	if (! error_occurred && ! g_file_delete (dir, NULL, error))
 		error_occurred = TRUE;

	g_object_unref (file_enum);

	return ! error_occurred;
}


gboolean
remove_directory (const char *uri)
{
	GFile     *dir;
	gboolean   result;
	GError    *error = NULL;

	dir = g_file_new_for_uri (uri);
	result = delete_directory_recursive (dir, &error);
	if (! result) {
		g_warning ("Cannot delete %s: %s", uri, error->message);
		g_clear_error (&error);
	}
	g_object_unref (dir);

	return result;
}


gboolean
remove_local_directory (const char *path)
{
	char     *uri;
	gboolean  result;

	if (path == NULL)
		return TRUE;

	uri = g_filename_to_uri (path, NULL, NULL);
	result = remove_directory (uri);
	g_free (uri);

	return result;
}


static const char *try_folder[] = { "cache", "~", "tmp", NULL };


static char *
ith_temp_folder_to_try (int n)
{
	const char *folder;

	folder = try_folder[n];
	if (strcmp (folder, "cache") == 0)
		folder = g_get_user_cache_dir ();
	else if (strcmp (folder, "~") == 0)
		folder = g_get_home_dir ();
	else if (strcmp (folder, "tmp") == 0)
		folder = g_get_tmp_dir ();

	return g_strdup (folder);
}


char *
get_temp_work_dir (const char *parent_folder)
{
	guint64  max_size = 0;
	char    *best_folder = NULL;
	int      i;
	char    *template;
	char    *result = NULL;

	if (parent_folder == NULL) {
		/* find the folder with more free space. */

		for (i = 0; try_folder[i] != NULL; i++) {
			char    *folder;
			guint64  size;

			folder = ith_temp_folder_to_try (i);
			size = get_dest_free_space (folder);
			if (max_size < size) {
				max_size = size;
				g_free (best_folder);
				best_folder = folder;
			}
			else
				g_free (folder);
		}
	}
	else
		best_folder = g_strdup (parent_folder);

	if (best_folder == NULL)
		return NULL;

	template = g_strconcat (best_folder, "/.fr-XXXXXX", NULL);
	result = mkdtemp (template);

	if ((result == NULL) || (*result == '\0')) {
		g_free (template);
		result = NULL;
	}

	return result;
}


gboolean
is_temp_work_dir (const char *dir)
{
	int i;
	char *folder = NULL;

	if (strncmp (dir, "file://", 7) == 0)
		dir = dir + 7;
	else if (dir[0] != '/')
		return FALSE;

	for (i = 0; try_folder[i] != NULL; i++) {
		folder = ith_temp_folder_to_try (i);
		if (strncmp (dir, folder, strlen (folder)) == 0)
			if (strncmp (dir + strlen (folder), "/.fr-", 5) == 0) {
				g_free (folder);
				return TRUE;
			}
		g_free (folder);
	}

	return FALSE;
}


gboolean
is_temp_dir (const char *dir)
{
	if (strncmp (dir, "file://", 7) == 0)
		dir = dir + 7;
	if (strcmp (g_get_tmp_dir (), dir) == 0)
		return TRUE;
	if (path_in_path (g_get_tmp_dir (), dir))
		return TRUE;
	else
		return is_temp_work_dir (dir);
}


/* file list utils */


gboolean
file_list__match_pattern (const char *line,
			  const char *pattern)
{
	const char *l = line, *p = pattern;

	for (; (*p != 0) && (*l != 0); p++, l++) {
		if (*p != '%') {
			if (*p != *l)
				return FALSE;
		}
		else {
			p++;
			switch (*p) {
			case 'a':
				break;
			case 'n':
				if (!isdigit (*l))
					return FALSE;
				break;
			case 'c':
				if (!isalpha (*l))
					return FALSE;
				break;
			default:
				return FALSE;
			}
		}
	}

	return (*p == 0);
}


int
file_list__get_index_from_pattern (const char *line,
				   const char *pattern)
{
	int         line_l, pattern_l;
	const char *l;

	line_l = strlen (line);
	pattern_l = strlen (pattern);

	if ((pattern_l == 0) || (line_l == 0))
		return -1;

	for (l = line; *l != 0; l++)
		if (file_list__match_pattern (l, pattern))
			return (l - line);

	return -1;
}


char*
file_list__get_next_field (const char *line,
			   int         start_from,
			   int         field_n)
{
	const char *f_start, *f_end;

	line = line + start_from;

	f_start = line;
	while ((*f_start == ' ') && (*f_start != *line))
		f_start++;
	f_end = f_start;

	while ((field_n > 0) && (*f_end != 0)) {
		if (*f_end == ' ') {
			field_n--;
			if (field_n != 0) {
				while ((*f_end == ' ') && (*f_end != *line))
					f_end++;
				f_start = f_end;
			}
		} else
			f_end++;
	}

	return g_strndup (f_start, f_end - f_start);
}


char*
file_list__get_prev_field (const char *line,
			   int         start_from,
			   int         field_n)
{
	const char *f_start, *f_end;

	f_start = line + start_from - 1;
	while ((*f_start == ' ') && (*f_start != *line))
		f_start--;
	f_end = f_start;

	while ((field_n > 0) && (*f_start != *line)) {
		if (*f_start == ' ') {
			field_n--;
			if (field_n != 0) {
				while ((*f_start == ' ') && (*f_start != *line))
					f_start--;
				f_end = f_start;
			}
		} else
			f_start--;
	}

	return g_strndup (f_start + 1, f_end - f_start);
}


gboolean
check_permissions (const char *uri,
		   int         mode)
{
	GFile    *file;
	gboolean  result;

	file = g_file_new_for_uri (uri);
	result = check_file_permissions (file, mode);

	g_object_unref (file);

	return result;
}


gboolean
check_file_permissions (GFile *file,
		        int    mode)
{
	gboolean   result = TRUE;
	GFileInfo *info;
	GError    *err = NULL;
	gboolean   default_permission_when_unknown = TRUE;

	info = g_file_query_info (file, "access::*", 0, NULL, &err);
	if (err != NULL) {
		g_clear_error (&err);
		result = FALSE;
	}
	else {
		if ((mode & R_OK) == R_OK) {
			if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
				result = (result && g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ));
			else
				result = (result && default_permission_when_unknown);
		}

		if ((mode & W_OK) == W_OK) {
			if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
				result = (result && g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE));
			else
				result = (result && default_permission_when_unknown);
		}

		if ((mode & X_OK) == X_OK) {
			if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
				result = (result && g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE));
			else
				result = (result && default_permission_when_unknown);
		}

		g_object_unref (info);
	}

	return result;
}


gboolean
is_program_in_path (const char *filename)
{
	char *str;
	char *value;
	int   result = FALSE;

	value = g_hash_table_lookup (ProgramsCache, filename);
	if (value != NULL) {
		result = (strcmp (value, "1") == 0);
		return result;
	}

	str = g_find_program_in_path (filename);
	if (str != NULL) {
		g_free (str);
		result = TRUE;
	}

	g_hash_table_insert (ProgramsCache,
			     g_strdup (filename),
			     result ? "1" : "0");

	return result;
}


gboolean
is_program_available (const char *filename,
		      gboolean    check)
{
	return ! check || is_program_in_path (filename);
}


const char *
get_home_uri (void)
{
	static char *home_uri = NULL;
	if (home_uri == NULL)
		home_uri = g_filename_to_uri (g_get_home_dir (), NULL, NULL);
	return home_uri;
}


char *
get_home_relative_uri (const char *partial_uri)
{
	return g_strconcat (get_home_uri (),
			    "/",
			    partial_uri,
			    NULL);
}


GFile *
get_home_relative_file (const char *partial_uri)
{
	GFile *file;
	char  *uri;

	uri = g_strconcat (get_home_uri (), "/", partial_uri, NULL);
	file = g_file_new_for_uri (uri);
	g_free (uri);

	return file;
}


GFile *
get_user_config_subdirectory (const char *child_name,
			      gboolean    create_child)
{
	char   *full_path;
	GFile  *file;
	GError *error = NULL;

	full_path = g_strconcat (g_get_user_config_dir (), "/", child_name, NULL);
	file = g_file_new_for_path (full_path);
	g_free (full_path);

	if  (create_child && ! make_directory_tree (file, 0700, &error)) {
		g_warning ("%s", error->message);
		g_error_free (error);
		g_object_unref (file);
		file = NULL;
	}

	return file;
}


const char *
remove_host_from_uri (const char *uri)
{
        const char *idx, *sep;

        if (uri == NULL)
                return NULL;

        idx = strstr (uri, "://");
        if (idx == NULL)
                return uri;
        idx += 3;
        if (*idx == '\0')
                return "/";
        sep = strstr (idx, "/");
        if (sep == NULL)
                return idx;
        return sep;
}


char *
get_uri_host (const char *uri)
{
	const char *idx;

	idx = strstr (uri, "://");
	if (idx == NULL)
		return NULL;
	idx = strstr (idx + 3, "/");
	if (idx == NULL)
		return NULL;
	return g_strndup (uri, (idx - uri));
}


char *
get_uri_root (const char *uri)
{
	char *host;
	char *root;

	host = get_uri_host (uri);
	if (host == NULL)
		return NULL;
	root = g_strconcat (host, "/", NULL);
	g_free (host);

	return root;
}


int
uricmp (const char *uri1,
	const char *uri2)
{
	return strcmp_null_tolerant (uri1, uri2);
}


char *
get_alternative_uri (const char *folder,
	     const char *name)
{
	char *new_uri = NULL;
	int   n = 1;
char prefix[1024],suffix[1024];


	memset(prefix,0,sizeof(prefix));
	memset(suffix,0,sizeof(suffix));

	do {
		g_free (new_uri);
		if (n == 1)
			new_uri = g_strconcat (folder, "/", name, NULL);
		else{
			sscanf(name,"%[^.].%s",prefix,suffix);
			if(suffix[0]=='\0')
				new_uri = g_strdup_printf ("%s/%s%%20(%d)", folder, name, n-1);
			else
				new_uri = g_strdup_printf ("%s/%s%%20(%d).%s",folder,prefix,n-1,suffix);

		}
		n++;
	} while (uri_exists (new_uri));

	return new_uri;
}


char *
get_alternative_uri_for_uri (const char *uri)
{
	char *base_uri;
	char *new_uri;

//	base_uri = remove_level_from_path (uri);
	base_uri = "file:///tmp/";
	new_uri = get_alternative_uri (base_uri, file_name_from_path (uri));
//	g_free (base_uri);

	return new_uri;
}


GList *
gio_file_list_dup (GList *l)
{
	GList *r = NULL, *scan;
	for (scan = l; scan; scan = scan->next)
		r = g_list_prepend (r, g_file_dup ((GFile*)scan->data));
	return g_list_reverse (r);
}


void
gio_file_list_free (GList *l)
{
	GList *scan;
	for (scan = l; scan; scan = scan->next)
		g_object_unref (scan->data);
	g_list_free (l);
}


GList *
gio_file_list_new_from_uri_list (GList *uris)
{
	GList *r = NULL, *scan;
	for (scan = uris; scan; scan = scan->next)
		r = g_list_prepend (r, g_file_new_for_uri ((char*)scan->data));
	return g_list_reverse (r);
}


void
g_key_file_save (GKeyFile *key_file,
	         GFile    *file)
{
	char   *file_data;
	gsize   size;
	GError *error = NULL;

	file_data = g_key_file_to_data (key_file, &size, &error);
	if (error != NULL) {
		g_warning ("Could not save options: %s\n", error->message);
		g_clear_error (&error);
	}
	else {
		GFileOutputStream *stream;

		stream = g_file_replace (file, NULL, FALSE, 0, NULL, &error);
		if (stream == NULL) {
			g_warning ("Could not save options: %s\n", error->message);
			g_clear_error (&error);
		}
		else if (! g_output_stream_write_all (G_OUTPUT_STREAM (stream), file_data, size, NULL, NULL, &error)) {
			g_warning ("Could not save options: %s\n", error->message);
			g_clear_error (&error);
		}
		else if (! g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &error)) {
			g_warning ("Could not save options: %s\n", error->message);
			g_clear_error (&error);
		}

		g_object_unref (stream);
	}

	g_free (file_data);
}


/**
 * filter_broken_symlinks:
 * @file_list: 要过滤的文件列表
 * @base_dir: 基础目录路径
 * @follow_links: 是否跟随链接
 *
 * 过滤坏的符号链接文件
 *
 * Returns: 坏链接列表文件
 */
GList *
filter_broken_symlinks (GList         *file_list,
                       const char    *base_dir,
                       gboolean       follow_links)
{
    GList *filtered_list = NULL;
    GList *scan;

    if (file_list == NULL)
        return NULL;

    for (scan = file_list; scan != NULL; scan = scan->next) {
        char *file_path = scan->data;
        char *full_path;
        GFile *file;
        GFileInfo *info;
        gboolean is_broken_link = FALSE;

        // 构建完整路径
        if (base_dir != NULL && !g_path_is_absolute(file_path)) {
            full_path = g_build_filename (base_dir, file_path, NULL);
        } else {
            full_path = g_strdup (file_path);
        }

        file = g_file_new_for_path (full_path);

        // 获取文件信息，不跟随符号链接
        info = g_file_query_info (file,
                                 G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                 NULL, NULL);


        if (info != NULL) {
            GFileType file_type = g_file_info_get_file_type (info);

            // 检查是否为符号链接
            if (file_type == G_FILE_TYPE_SYMBOLIC_LINK) {
                const char *target = g_file_info_get_symlink_target (info);
		GFile *link_dir = g_file_get_parent(file);
		GFile* abs_target_file = g_file_resolve_relative_path(link_dir, target);
		const char* abs_target_file_path = g_file_get_path(abs_target_file);
		g_object_unref(link_dir);

                // 检查链接目标是否存在
                if (!g_file_query_exists (abs_target_file, NULL)) {
                    is_broken_link = TRUE;
                    g_warning ("Broken symlink detected: %s -> %s", full_path, abs_target_file_path ? abs_target_file_path : "NULL");
                }
		g_object_unref(abs_target_file);
            }

            g_object_unref (info);
        }

        // 如果是坏链接，添加到列表
        if (is_broken_link) {
            filtered_list = g_list_prepend (filtered_list, g_strdup (file_path));
        }

        g_object_unref (file);
        g_free (full_path);
    }

    // 反转列表以保持原始顺序
    return g_list_reverse (filtered_list);
}

void
display_file_list(GList      *file_list)
{
	GList *scan;
	debug(DEBUG_INFO, "*****************file list display info\n");
	for (scan = file_list; scan; scan = scan->next) {
		char       *path = scan->data;
		const char *filename;

		filename = file_name_from_path (path);
		printf("filename is %s\n", filename);
	}
	printf( "----------------------------------------------------\n");
}

gchar *
file_utils_get_zip_name_utf8 (zip_t *archive, zip_uint64_t index)
{
	const char *raw_name = zip_get_name(archive, index, ZIP_FL_ENC_RAW);
	if (raw_name == NULL) {
		return NULL;
	}

	// 打印 RAW 数据的十六进制，以便确诊编码
	GString *hex = g_string_new(NULL);
	for (const char *p = raw_name; *p; p++) g_string_append_printf(hex, "%02X ", (unsigned char)*p);
	g_string_free(hex, TRUE);

	/* get_converted_utf8_string will:
	 * 1. Check if valid UTF-8 -> return strdup(raw_name)
	 * 2. Else try convert GB18030 -> UTF-8
	 */
	gchar *converted_raw = get_converted_utf8_string(raw_name);

	const char *guess_name = zip_get_name(archive, index, ZIP_FL_ENC_GUESS);

	if (guess_name != NULL) {
		/* If converted_raw is identical to raw_name, it implies raw_name was
		 * already valid UTF-8 (or ASCII).
		 * In this case, we check if libzip's GUESS provides a different name,
		 * which would imply an EFS/Unicode Path extension was used to provide
		 * a better name (e.g. raw is ASCII short name, guess is Unicode long name).
		 */
		if (strcmp(converted_raw, raw_name) == 0) {
			if (strcmp(guess_name, raw_name) != 0) {
				g_free(converted_raw);
				return g_strdup(guess_name);
			}
		}
		/* If converted_raw != raw_name, it means raw was invalid UTF-8 (e.g. GBK).
		 * In this case, 'guess' (if present) likely represents a CP437-converted
		 * string which is garbage for GBK names. So we ignore 'guess' and keep
		 * our 'converted_raw' (GBK->UTF8).
		 */
	}

	return converted_raw;
}



gboolean
is_encry_zip_file(const char *file_path)
{
	int errcode = 0;
	zip_error_t err;
	zip_t *archive_zip = zip_open(file_path, ZIP_RDONLY, &errcode);
	zip_error_init_with_code(&err, errcode);
	if (archive_zip == NULL) {
		return FALSE;
	}

	const zip_int64_t nofEntries = zip_get_num_entries(archive_zip, 0);
	for (zip_int64_t index = 0; index < nofEntries; index++) {
		zip_stat_t statBuffer = {0};
		if (zip_stat_index(archive_zip, (zip_uint64_t)index, ZIP_FL_ENC_RAW, &statBuffer)) {
			continue;
		}

		if (statBuffer.encryption_method != 0) {
			zip_close(archive_zip);
			return TRUE;
		}
	}

	zip_close(archive_zip);
	return FALSE;
}
