/*
 *  nautilus-wipe - a nautilus extension to wipe file(s)
 * 
 *  Copyright (C) 2009-2022 Colomban Wendling <ban@herbesfolles.org>
 *
 *  This library 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 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "nw-fill-operation-utils.h"

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#if HAVE_GIO_UNIX
# include <gio/gunixmounts.h>
#endif


GQuark
nw_fill_operation_error_quark (void)
{
  static volatile gsize quark = 0;
  
  if (g_once_init_enter (&quark)) {
    GQuark q = g_quark_from_static_string ("NwFillOperationError");
    
    g_once_init_leave (&quark, q);
  }
  
  return (GQuark) quark;
}

#if HAVE_GIO_UNIX
/*
 * find_mountpoint_unix:
 * @path: An absolute path for which find the mountpoint.
 * 
 * Gets the UNIX mountpoint path of a given path.
 * <note>
 *  This function would actually never return %NULL since the mount point of
 *  every files is at least /.
 * </note>
 * 
 * Returns: The path of the mountpoint of @path or %NULL if not found.
 *          Free with g_free().
 */
static gchar *
find_mountpoint_unix (const gchar *path)
{
  gchar    *mountpoint = g_strdup (path);
  gboolean  found = FALSE;
  
  while (! found && mountpoint) {
    GUnixMountEntry *unix_mount;
    
    unix_mount = g_unix_mount_at (mountpoint, NULL);
    if (unix_mount) {
      found = TRUE;
      g_unix_mount_free (unix_mount);
    } else {
      gchar *tmp = mountpoint;
      
      mountpoint = g_path_get_dirname (tmp);
      /* check if dirname() changed the path to avoid infinite loop (e.g. when
       * / was reached) */
      if (strcmp (mountpoint, tmp) == 0) {
        g_free (mountpoint);
        mountpoint = NULL;
      }
      g_free (tmp);
    }
  }
  
  return mountpoint;
}
#endif

static gchar *
find_mountpoint (const gchar *path,
                 GError     **error)
{
  gchar  *mountpoint_path = NULL;
  GFile  *file;
  GMount *mount;
  GError *err = NULL;
  
  /* Try with GIO first */
  file = g_file_new_for_path (path);
  mount = g_file_find_enclosing_mount (file, NULL, &err);
  if (mount) {
    GFile *mountpoint_file;
    
    mountpoint_file = g_mount_get_root (mount);
    mountpoint_path = g_file_get_path (mountpoint_file);
    if (! mountpoint_path) {
      gchar *uri = g_file_get_uri (mountpoint_file);
      
      g_set_error (&err,
                   NW_FILL_OPERATION_ERROR,
                   NW_FILL_OPERATION_ERROR_REMOTE_MOUNT,
                   _("Mount \"%s\" is not local"), uri);
      g_free (uri);
    }
    g_object_unref (mountpoint_file);
    g_object_unref (mount);
  }
  g_object_unref (file);
  #if HAVE_GIO_UNIX
  /* fallback to find_unix_mount() */
  if (! mountpoint_path) {
    g_clear_error (&err);
    mountpoint_path = find_mountpoint_unix (path);
    if (! mountpoint_path) {
      g_set_error (&err,
                   NW_FILL_OPERATION_ERROR,
                   NW_FILL_OPERATION_ERROR_MISSING_MOUNT,
                   _("No mount point found for path \"%s\""), path);
    }
  }
  #endif
  if (! mountpoint_path) {
    g_propagate_error (error, err);
  }
  
  return mountpoint_path;
}

/*
 * nw_fill_operation_filter_files:
 * @paths: A list of paths to filter
 * @work_paths_: return location for filtered paths
 * @work_mounts_: return location for filtered paths' mounts
 * @error: return location for errors, or %NULL to ignore them
 * 
 * Tries to get usable paths (local directories) and keep only one per
 * mountpoint.
 * 
 * The returned lists (@work_paths_ and @work_mounts_) have the same length, and
 * an index in a list correspond to the same in the other:
 * g_list_index(work_paths_, 0) is the path of g_list_index(work_mounts_, 0).
 * Free returned lists with g_list_free_full(list, g_free) or equivalent.
 * 
 * Returns: %TRUE on success, %FALSE otherwise.
 */
gboolean
nw_fill_operation_filter_files (GList    *paths,
                                GList   **work_paths_,
                                GList   **work_mounts_,
                                GError  **error)
{
  GList  *work_paths  = NULL;
  GError *err         = NULL;
  GList  *work_mounts = NULL;
  
  g_return_val_if_fail (paths != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  
  for (; ! err && paths; paths = g_list_next (paths)) {
    const gchar  *file_path = paths->data;
    gchar        *mountpoint;
    
    mountpoint = find_mountpoint (file_path, &err);
    if (G_LIKELY (mountpoint)) {
      if (g_list_find_custom (work_mounts, mountpoint, (GCompareFunc)strcmp)) {
        /* the mountpoint is already added, skip it */
        g_free (mountpoint);
      } else {
        gchar *path;
        
        work_mounts = g_list_prepend (work_mounts, mountpoint);
        /* if it is not a directory, gets its container directory.
         * no harm since files cannot be mountpoint themselves, then it gets
         * at most the mountpoint itself */
        if (! g_file_test (file_path, G_FILE_TEST_IS_DIR)) {
          path = g_path_get_dirname (file_path);
        } else {
          path = g_strdup (file_path);
        }
        work_paths = g_list_prepend (work_paths, path);
      }
    }
  }
  if (err || ! work_paths_) {
    g_list_free_full (work_paths, g_free);
  } else {
    *work_paths_ = g_list_reverse (work_paths);
  }
  if (err || ! work_mounts_) {
    g_list_free_full (work_mounts, g_free);
  } else {
    *work_mounts_ = g_list_reverse (work_mounts);
  }
  if (err) {
    g_propagate_error (error, err);
  }
  
  return ! err;
}
