/* 
 * Copyright (C) 2007 Paolo Borelli
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: Paolo Borelli
 */

#define GETTEXT_PACKAGE "gvfs-du"

#include <locale.h>
#include <glib.h>
#include <gio/gio.h>

static gboolean apparent_size = FALSE;
static int block_size = -1;

static GOptionEntry entries[] = 
{
  { "apparent-size", 0, 0, G_OPTION_ARG_NONE, &apparent_size, "Print apparent sizes rather than disk usage", NULL },
  { "block-size", 'B', 0, G_OPTION_ARG_INT, &block_size, "Block size in bytes", NULL },
  { NULL }
};

#define DEFAULT_BLOCK_SIZE 512

static const char *attributes = G_FILE_ATTRIBUTE_STANDARD_NAME "," \
                                G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
                                G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
                                G_FILE_ATTRIBUTE_UNIX_BLOCKS;

typedef struct {
  GFile *root;
  gsize block_size;
  gint count;
  gsize size;
  gsize alloc_size;
} DuInfo;

static DuInfo *
du_info_new (GFile *file)
{
  DuInfo *info;

  info = g_new0 (DuInfo, 1);
  info->root = g_object_ref (file);

  return info;
}

static void
du_info_free (DuInfo *info)
{
  g_object_unref (info->root);
  g_free (info);
}

static void
scan_dir (DuInfo *info, GFile *file)
{
  GFileInfo *fileinfo;
  GError *error;
  GFileEnumerator *enumerator;

  error = NULL;
  enumerator = g_file_enumerate_children (file,
                                          attributes,
                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                          NULL,
                                          &error);
  if (error)
    {
      g_printerr ("%s\n", error->message); /* FIXME */
      g_error_free (error);
    }
  if (enumerator == NULL)
    {
      return;
    }

  error = NULL;
  while ((fileinfo = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL)
    {
      /* Ignore symlinks, special files, etc */

      /* Recurse inside directories */
      if (g_file_info_get_file_type (fileinfo) == G_FILE_TYPE_DIRECTORY)
        {
          GFile *subdir;

          subdir = g_file_get_child (file, g_file_info_get_name (fileinfo));
          scan_dir (info, subdir);

          g_object_unref (subdir);
        }

      /* Process regular files */
      else if (g_file_info_get_file_type (fileinfo) == G_FILE_TYPE_REGULAR)
        {

          // TODO: deal with hardlinks

          // TODO: what to do for files which do not have this attr?
          if (g_file_info_has_attribute (fileinfo, G_FILE_ATTRIBUTE_UNIX_BLOCKS))
            info->alloc_size += info->block_size * g_file_info_get_attribute_uint64 (fileinfo, G_FILE_ATTRIBUTE_UNIX_BLOCKS);
          else
            g_print ("Could not get block count for %s\n", g_file_info_get_name (fileinfo));

          info->size += g_file_info_get_size (fileinfo);
        }

      g_object_unref (fileinfo);
  }

  if (!g_file_enumerator_close (enumerator, NULL, &error))
    {
      g_print ("Error closing enumerator: %s\n", error->message);
      g_error_free (error);
      error = NULL;
    }
}

static void
du_scan (GFile *file)
{
  DuInfo *info;
  GFileInfo *fileinfo;
  GError *error;

  info = du_info_new (file);

  if (block_size > 0)
    info->block_size = block_size;
  else
    info->block_size = DEFAULT_BLOCK_SIZE; /* TODO: get from the fs? */

  error = NULL;
  fileinfo = g_file_query_info (file,
                                attributes,
                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                NULL,
                                &error);

  if (error)
    {
      g_printerr ("%s\n", error->message);
      g_error_free (error);
      return;
    }

  if (g_file_info_get_file_type (fileinfo) == G_FILE_TYPE_DIRECTORY)
    {
      scan_dir (info, file);
    }
  else
    {
      info->size += g_file_info_get_size (fileinfo);
    }

  /* Print results */
  if (apparent_size)
    g_print ("%d %s\n", info->size, g_file_info_get_name (fileinfo));
  else
    g_print ("%d %s\n", info->alloc_size, g_file_info_get_name (fileinfo));

  du_info_free (info);
}

int
main (int argc, char *argv[])
{
  GError *error;
  GOptionContext *context;
  GFile *file;

  setlocale (LC_ALL, "");

  g_type_init ();

  error = NULL;
  context = g_option_context_new ("- estimate file space usage");
  g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
  g_option_context_parse (context, &argc, &argv, &error);
  g_option_context_free (context);

  if (argc > 1)
    {
      int i;
      
      for (i = 1; i < argc; i++)
        {
          file = g_file_new_for_commandline_arg (argv[i]);
          du_scan (file);
          g_object_unref (file);
        }
    }
  else
    {
      char *cwd;

      cwd = g_get_current_dir ();
      file = g_file_new_for_path (cwd);
      g_free (cwd);
      du_scan (file);
      g_object_unref (file);
    }

  return 0;
}

