/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2009-2010 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, see .
*/
#include
#include
#ifdef HAVE_EXIV2
#include
#endif
#include
#include "gth-import-task.h"
#include "preferences.h"
#include "utils.h"
#define IMPORTED_KEY "imported"
struct _GthImportTaskPrivate {
GthBrowser *browser;
GList *files;
GFile *destination;
GHashTable *destinations;
char *subfolder_template;;
char *event_name;
char **tags;
GTimeVal import_start_time;
gboolean delete_imported;
gboolean overwrite_files;
gboolean adjust_orientation;
GHashTable *catalogs;
gsize tot_size;
gsize copied_size;
gsize current_file_size;
GList *current;
GthFileData *destination_file;
GFile *imported_catalog;
gboolean delete_not_supported;
int n_imported;
GthOverwriteResponse default_response;
void *buffer;
gsize buffer_size;
};
G_DEFINE_TYPE_WITH_CODE (GthImportTask,
gth_import_task,
GTH_TYPE_TASK,
G_ADD_PRIVATE (GthImportTask))
static void
gth_import_task_finalize (GObject *object)
{
GthImportTask *self;
self = GTH_IMPORT_TASK (object);
if (gth_browser_get_close_with_task (self->priv->browser))
gtk_window_present (GTK_WINDOW (self->priv->browser));
g_free (self->priv->buffer);
g_hash_table_unref (self->priv->destinations);
_g_object_list_unref (self->priv->files);
g_object_unref (self->priv->destination);
_g_object_unref (self->priv->destination_file);
g_free (self->priv->subfolder_template);
g_free (self->priv->event_name);
if (self->priv->tags != NULL)
g_strfreev (self->priv->tags);
g_hash_table_destroy (self->priv->catalogs);
_g_object_unref (self->priv->imported_catalog);
g_object_unref (self->priv->browser);
G_OBJECT_CLASS (gth_import_task_parent_class)->finalize (object);
}
static void import_current_file (GthImportTask *self);
static void
import_next_file (GthImportTask *self)
{
self->priv->copied_size += self->priv->current_file_size;
self->priv->current = self->priv->current->next;
import_current_file (self);
}
static void
save_catalog (gpointer key,
gpointer value,
gpointer user_data)
{
GthCatalog *catalog = value;
gth_catalog_save (catalog);
}
static void
save_catalogs (GthImportTask *self)
{
g_hash_table_foreach (self->priv->catalogs, save_catalog, self);
}
static void
catalog_imported_file (GthImportTask *self)
{
char *key;
GObject *metadata;
GTimeVal timeval;
GthCatalog *catalog;
self->priv->n_imported++;
if (! gth_main_extension_is_active ("catalogs")) {
import_next_file (self);
return;
}
key = NULL;
metadata = g_file_info_get_attribute_object (self->priv->destination_file->info, "Embedded::Photo::DateTimeOriginal");
if (metadata != NULL) {
if (_g_time_val_from_exif_date (gth_metadata_get_raw (GTH_METADATA (metadata)), &timeval))
key = _g_time_val_strftime (&timeval, "%Y.%m.%d");
}
if (key == NULL) {
g_get_current_time (&timeval);
key = _g_time_val_strftime (&timeval, "%Y.%m.%d");
}
catalog = g_hash_table_lookup (self->priv->catalogs, key);
if (catalog == NULL) {
GthDateTime *date_time;
GFile *catalog_file;
date_time = gth_datetime_new ();
gth_datetime_from_timeval (date_time, &timeval);
catalog_file = gth_catalog_get_file_for_date (date_time, ".catalog");
catalog = gth_catalog_load_from_file (catalog_file);
if (catalog == NULL)
catalog = gth_catalog_new ();
gth_catalog_set_date (catalog, date_time);
gth_catalog_set_file (catalog, catalog_file);
g_hash_table_insert (self->priv->catalogs, g_strdup (key), catalog);
g_object_unref (catalog_file);
gth_datetime_free (date_time);
}
gth_catalog_insert_file (catalog, self->priv->destination_file->file, -1);
catalog = g_hash_table_lookup (self->priv->catalogs, IMPORTED_KEY);
if (catalog != NULL)
gth_catalog_insert_file (catalog, self->priv->destination_file->file, -1);
import_next_file (self);
g_free (key);
}
static void
write_metadata_ready_func (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GthImportTask *self = user_data;
GError *error = NULL;
if (! _g_write_metadata_finish (result, &error)
&& g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
gth_task_completed (GTH_TASK (self), error);
return;
}
if (error != NULL)
g_clear_error (&error);
catalog_imported_file (self);
}
static void
write_file_tags (GthImportTask *self)
{
GthStringList *tag_list;
GthMetadata *metadata;
GList *file_list;
if ((self->priv->tags == NULL) || (self->priv->tags[0] == NULL)) {
catalog_imported_file (self);
return;
}
tag_list = gth_string_list_new_from_strv (self->priv->tags);
metadata = gth_metadata_new_for_string_list (tag_list);
g_file_info_set_attribute_object (self->priv->destination_file->info, "comment::categories", G_OBJECT (metadata));
file_list = g_list_prepend (NULL, self->priv->destination_file);
_g_write_metadata_async (file_list,
GTH_METADATA_WRITE_DEFAULT,
"comment::categories",
gth_task_get_cancellable (GTH_TASK (self)),
write_metadata_ready_func,
self);
g_list_free (file_list);
g_object_unref (metadata);
g_object_unref (tag_list);
}
static void
write_file_to_destination (GthImportTask *self,
GFile *destination_file,
void *buffer,
gsize buffer_size,
gboolean overwrite);
static void
overwrite_dialog_response_cb (GtkDialog *dialog,
gint response_id,
gpointer user_data)
{
GthImportTask *self = user_data;
if (response_id != GTK_RESPONSE_OK)
self->priv->default_response = GTH_OVERWRITE_RESPONSE_CANCEL;
else
self->priv->default_response = gth_overwrite_dialog_get_response (GTH_OVERWRITE_DIALOG (dialog));
gtk_widget_hide (GTK_WIDGET (dialog));
gth_task_dialog (GTH_TASK (self), FALSE, NULL);
switch (self->priv->default_response) {
case GTH_OVERWRITE_RESPONSE_NO:
case GTH_OVERWRITE_RESPONSE_ALWAYS_NO:
case GTH_OVERWRITE_RESPONSE_UNSPECIFIED:
import_next_file (self);
break;
case GTH_OVERWRITE_RESPONSE_YES:
case GTH_OVERWRITE_RESPONSE_ALWAYS_YES:
write_file_to_destination (self,
self->priv->destination_file->file,
self->priv->buffer,
self->priv->buffer_size,
TRUE);
break;
case GTH_OVERWRITE_RESPONSE_RENAME:
{
GthFileData *file_data;
GFile *destination_folder;
GFile *new_destination;
file_data = self->priv->current->data;
destination_folder = gth_import_utils_get_file_destination (file_data,
self->priv->destination,
self->priv->subfolder_template,
self->priv->event_name,
self->priv->import_start_time);
new_destination = g_file_get_child_for_display_name (destination_folder, gth_overwrite_dialog_get_filename (GTH_OVERWRITE_DIALOG (dialog)), NULL);
write_file_to_destination (self,
new_destination,
self->priv->buffer,
self->priv->buffer_size,
FALSE);
g_object_unref (new_destination);
g_object_unref (destination_folder);
}
break;
case GTH_OVERWRITE_RESPONSE_CANCEL:
{
GError *error;
error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, "");
gth_task_completed (GTH_TASK (self), error);
}
break;
}
gtk_widget_destroy (GTK_WIDGET (dialog));
}
static void
after_saving_to_destination (GthImportTask *self,
void **buffer,
gsize count,
GError *error)
{
GthFileData *file_data;
file_data = self->priv->current->data;
if (error != NULL) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
if (self->priv->default_response != GTH_OVERWRITE_RESPONSE_ALWAYS_NO) {
GInputStream *stream;
GthImage *image;
GtkWidget *dialog;
/* take ownership of the buffer */
if (buffer != NULL) {
self->priv->buffer = *buffer;
self->priv->buffer_size = count;
*buffer = NULL;
}
else {
self->priv->buffer = NULL;
self->priv->buffer_size = 0;
}
/* show the overwrite dialog */
if (self->priv->buffer != NULL) {
stream = g_memory_input_stream_new_from_data (self->priv->buffer, self->priv->buffer_size, NULL);
image = gth_image_new_from_stream (stream, 128, NULL, NULL, NULL, NULL);
}
else {
stream = NULL;
image = NULL;
}
dialog = gth_overwrite_dialog_new (file_data->file,
image,
self->priv->destination_file->file,
self->priv->default_response,
self->priv->files->next == NULL);
g_signal_connect (dialog,
"response",
G_CALLBACK (overwrite_dialog_response_cb),
self);
gtk_widget_show (dialog);
gth_task_dialog (GTH_TASK (self), TRUE, dialog);
_g_object_unref (image);
_g_object_unref (stream);
}
else
import_next_file (self);
return;
}
gth_task_completed (GTH_TASK (self), error);
return;
}
if (self->priv->delete_imported) {
GError *local_error = NULL;
if (! g_file_delete (file_data->file,
gth_task_get_cancellable (GTH_TASK (self)),
&local_error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
self->priv->delete_imported = FALSE;
self->priv->delete_not_supported = TRUE;
local_error = NULL;
}
if (local_error != NULL) {
gth_task_completed (GTH_TASK (self), local_error);
return;
}
}
}
write_file_tags (self);
}
static void
copy_non_image_ready_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
g_file_copy_finish (G_FILE (source_object), res, &error);
after_saving_to_destination (GTH_IMPORT_TASK (user_data), NULL, 0, error);
}
static void
copy_non_image_progress_cb (goffset current_num_bytes,
goffset total_num_bytes,
gpointer user_data)
{
GthImportTask *self = user_data;
GthFileData *file_data;
file_data = self->priv->current->data;
gth_task_progress (GTH_TASK (self),
_("Importing files"),
g_file_info_get_display_name (file_data->info),
FALSE,
CLAMP ((double) (self->priv->copied_size + current_num_bytes) / self->priv->tot_size, 0.0, 1.0));
}
static void
write_buffer_ready_cb (void **buffer,
gsize count,
GError *error,
gpointer user_data)
{
after_saving_to_destination (GTH_IMPORT_TASK (user_data), buffer, count, error);
}
static void
write_file_to_destination (GthImportTask *self,
GFile *destination_file,
void *buffer,
gsize buffer_size,
gboolean replace)
{
GthFileData *file_data;
file_data = self->priv->current->data;
if ((self->priv->destination_file == NULL) || (destination_file != self->priv->destination_file->file)) {
_g_object_unref (self->priv->destination_file);
self->priv->destination_file = gth_file_data_new (destination_file, file_data->info);
}
if (buffer != NULL) {
gth_task_progress (GTH_TASK (self),
_("Importing files"),
g_file_info_get_display_name (file_data->info),
FALSE,
(double) (self->priv->copied_size + ((double) self->priv->current_file_size / 3.0 * 2.0)) / self->priv->tot_size);
self->priv->buffer = NULL; /* the buffer will be deallocated in _g_file_write_async */
#ifdef HAVE_LIBJPEG
if (self->priv->adjust_orientation && gth_main_extension_is_active ("image_rotation")) {
if (g_content_type_equals (gth_file_data_get_mime_type (self->priv->destination_file), "image/jpeg")) {
GthTransform orientation;
GthMetadata *metadata;
orientation = GTH_TRANSFORM_NONE;
metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->destination_file->info, "Embedded::Image::Orientation");
if ((metadata != NULL) && (gth_metadata_get_raw (metadata) != NULL))
orientation = strtol (gth_metadata_get_raw (metadata), (char **) NULL, 10);
if (orientation != GTH_TRANSFORM_NONE) {
void *out_buffer;
gsize out_buffer_size;
if (jpegtran (buffer,
buffer_size,
&out_buffer,
&out_buffer_size,
orientation,
JPEG_MCU_ACTION_ABORT,
NULL))
{
g_free (buffer);
buffer = out_buffer;
buffer_size = out_buffer_size;
}
}
}
}
#endif /* HAVE_LIBJPEG */
_g_file_write_async (self->priv->destination_file->file,
buffer,
buffer_size,
replace,
G_PRIORITY_DEFAULT,
gth_task_get_cancellable (GTH_TASK (self)),
write_buffer_ready_cb,
self);
}
else
g_file_copy_async (file_data->file,
self->priv->destination_file->file,
(replace ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE) | G_FILE_COPY_TARGET_DEFAULT_PERMS,
G_PRIORITY_DEFAULT,
gth_task_get_cancellable (GTH_TASK (self)),
copy_non_image_progress_cb,
self,
copy_non_image_ready_cb,
self);
}
static GFile *
get_destination_file (GthImportTask *self,
GthFileData *file_data)
{
GError *error = NULL;
GFile *destination;
GFile *destination_file;
destination = gth_import_utils_get_file_destination (file_data,
self->priv->destination,
self->priv->subfolder_template,
self->priv->event_name,
self->priv->import_start_time);
if (! g_file_make_directory_with_parents (destination, gth_task_get_cancellable (GTH_TASK (self)), &error)) {
if (! g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
gth_task_completed (GTH_TASK (self), error);
return NULL;
}
}
/* Get the destination file avoiding to overwrite an already imported
* file. */
destination_file = _g_file_get_destination (file_data->file, NULL, destination);
while (g_hash_table_lookup (self->priv->destinations, destination_file) != NULL) {
GFile *tmp = destination_file;
destination_file = _g_file_get_duplicated (tmp);
g_object_unref (tmp);
}
g_hash_table_insert (self->priv->destinations, g_object_ref (destination_file), GINT_TO_POINTER (1));
g_object_unref (destination);
return destination_file;
}
static void
file_buffer_ready_cb (void **buffer,
gsize count,
GError *error,
gpointer user_data)
{
GthImportTask *self = user_data;
GthFileData *file_data;
GFile *destination_file;
if (error != NULL) {
gth_task_completed (GTH_TASK (self), error);
return;
}
file_data = self->priv->current->data;
#ifdef HAVE_EXIV2
if (gth_main_extension_is_active ("exiv2_tools"))
exiv2_read_metadata_from_buffer (*buffer,
count,
file_data->info,
TRUE,
NULL);
#endif
destination_file = get_destination_file (self, file_data);
if (destination_file == NULL)
return;
write_file_to_destination (self,
destination_file,
*buffer,
count,
self->priv->default_response == GTH_OVERWRITE_RESPONSE_ALWAYS_YES);
*buffer = NULL; /* _g_file_write_async takes ownership of the buffer */
g_object_unref (destination_file);
}
static void
import_current_file (GthImportTask *self)
{
GthFileData *file_data;
gboolean adjust_image_orientation;
gboolean need_image_metadata;
g_free (self->priv->buffer);
self->priv->buffer = NULL;
if (self->priv->current == NULL) {
save_catalogs (self);
if (self->priv->n_imported == 0) {
GtkWidget *d;
d = _gtk_message_dialog_new (GTK_WINDOW (self->priv->browser),
0,
_GTK_ICON_NAME_DIALOG_WARNING,
_("No file imported"),
_("The selected files are already present in the destination."),
_GTK_LABEL_CLOSE, GTK_RESPONSE_CANCEL,
NULL);
g_signal_connect (G_OBJECT (d), "response",
G_CALLBACK (gtk_widget_destroy),
NULL);
gtk_widget_show (d);
}
else {
GSettings *settings;
if (! _g_str_empty (self->priv->subfolder_template) && (self->priv->imported_catalog != NULL))
gth_browser_go_to (self->priv->browser, self->priv->imported_catalog, NULL);
else
gth_browser_go_to (self->priv->browser, self->priv->destination, NULL);
settings = g_settings_new (PIX_IMPORTER_SCHEMA);
if (self->priv->delete_not_supported && g_settings_get_boolean (settings, PREF_IMPORTER_WARN_DELETE_UNSUPPORTED)) {
GtkWidget *d;
d = _gtk_message_dialog_new (GTK_WINDOW (self->priv->browser),
0,
_GTK_ICON_NAME_DIALOG_WARNING,
_("Could not delete the files"),
_("Delete operation not supported."),
_GTK_LABEL_CLOSE, GTK_RESPONSE_CANCEL,
NULL);
g_signal_connect (G_OBJECT (d), "response",
G_CALLBACK (gtk_widget_destroy),
NULL);
gtk_widget_show (d);
g_settings_set_boolean (settings, PREF_IMPORTER_WARN_DELETE_UNSUPPORTED, FALSE);
}
g_object_unref (settings);
}
gth_task_completed (GTH_TASK (self), NULL);
return;
}
file_data = self->priv->current->data;
self->priv->current_file_size = g_file_info_get_size (file_data->info);
adjust_image_orientation = self->priv->adjust_orientation && gth_main_extension_is_active ("image_rotation");
need_image_metadata = (_g_utf8_find_str (self->priv->subfolder_template, "%D") != NULL) || adjust_image_orientation;
if (_g_mime_type_is_image (gth_file_data_get_mime_type (file_data)) && need_image_metadata) {
gth_task_progress (GTH_TASK (self),
_("Importing files"),
g_file_info_get_display_name (file_data->info),
FALSE,
(double) (self->priv->copied_size + ((double) self->priv->current_file_size / 3.0)) / self->priv->tot_size);
_g_file_load_async (file_data->file,
G_PRIORITY_DEFAULT,
gth_task_get_cancellable (GTH_TASK (self)),
file_buffer_ready_cb,
self);
}
else {
GFile *destination_file;
destination_file = get_destination_file (self, file_data);
if (destination_file != NULL) {
write_file_to_destination (self,
destination_file,
NULL,
0,
self->priv->default_response == GTH_OVERWRITE_RESPONSE_ALWAYS_YES);
g_object_unref (destination_file);
}
}
}
static void
gth_import_task_exec (GthTask *base)
{
GthImportTask *self = (GthImportTask *) base;
GTimeVal timeval;
GList *scan;
self->priv->n_imported = 0;
self->priv->tot_size = 0;
for (scan = self->priv->files; scan; scan = scan->next) {
GthFileData *file_data = scan->data;
self->priv->tot_size += g_file_info_get_size (file_data->info);
}
g_get_current_time (&timeval);
self->priv->import_start_time = timeval;
self->priv->default_response = GTH_OVERWRITE_RESPONSE_UNSPECIFIED;
/* create the imported files catalog */
if (gth_main_extension_is_active ("catalogs")) {
GthDateTime *date_time;
char *display_name;
GthCatalog *catalog = NULL;
date_time = gth_datetime_new ();
gth_datetime_from_timeval (date_time, &timeval);
if ((self->priv->event_name != NULL) && ! _g_utf8_all_spaces (self->priv->event_name)) {
display_name = g_strdup (self->priv->event_name);
self->priv->imported_catalog = _g_file_new_for_display_name ("catalog://", display_name, ".catalog");
/* append files to the catalog if an event name was given */
catalog = gth_catalog_load_from_file (self->priv->imported_catalog);
}
else {
display_name = g_strdup (_("Last imported"));
self->priv->imported_catalog = _g_file_new_for_display_name ("catalog://", display_name, ".catalog");
/* overwrite the catalog content if the generic "last imported" catalog is used. */
catalog = NULL;
}
if (catalog == NULL)
catalog = gth_catalog_new ();
gth_catalog_set_file (catalog, self->priv->imported_catalog);
gth_catalog_set_date (catalog, date_time);
gth_catalog_set_name (catalog, display_name);
g_hash_table_insert (self->priv->catalogs, g_strdup (IMPORTED_KEY), catalog);
g_free (display_name);
gth_datetime_free (date_time);
}
self->priv->buffer = NULL;
self->priv->current = self->priv->files;
import_current_file (self);
}
static void
gth_import_task_class_init (GthImportTaskClass *klass)
{
GObjectClass *object_class;
GthTaskClass *task_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gth_import_task_finalize;
task_class = GTH_TASK_CLASS (klass);
task_class->exec = gth_import_task_exec;
}
static void
gth_import_task_init (GthImportTask *self)
{
self->priv = gth_import_task_get_instance_private (self);
self->priv->catalogs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
self->priv->delete_not_supported = FALSE;
self->priv->destinations = g_hash_table_new_full (g_file_hash,
(GEqualFunc) g_file_equal,
g_object_unref,
NULL);
self->priv->buffer = NULL;
}
GthTask *
gth_import_task_new (GthBrowser *browser,
GList *files,
GFile *destination,
const char *subfolder_template,
const char *event_name,
char **tags,
gboolean delete_imported,
gboolean overwrite_files,
gboolean adjust_orientation)
{
GthImportTask *self;
self = GTH_IMPORT_TASK (g_object_new (GTH_TYPE_IMPORT_TASK, NULL));
self->priv->browser = g_object_ref (browser);
self->priv->files = _g_object_list_ref (files);
self->priv->destination = g_file_dup (destination);
self->priv->subfolder_template = g_strdup (subfolder_template);
self->priv->event_name = g_strdup (event_name);
self->priv->tags = g_strdupv (tags);
self->priv->delete_imported = delete_imported;
self->priv->overwrite_files = overwrite_files;
self->priv->default_response = overwrite_files ? GTH_OVERWRITE_RESPONSE_ALWAYS_YES : GTH_OVERWRITE_RESPONSE_UNSPECIFIED;
self->priv->adjust_orientation = adjust_orientation;
return (GthTask *) self;
}
gboolean
gth_import_task_check_free_space (GFile *destination,
GList *files, /* GthFileData list */
GError **error)
{
GFileInfo *info;
guint64 free_space;
goffset total_file_size;
goffset max_file_size;
goffset min_free_space;
GList *scan;
if (files == NULL) {
if (error != NULL)
*error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s", _("No file specified."));
return FALSE;
}
info = g_file_query_filesystem_info (destination, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, error);
if (info == NULL)
return FALSE;
free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
total_file_size = 0;
max_file_size = 0;
for (scan = files; scan; scan = scan->next) {
GthFileData *file_data = scan->data;
goffset file_size = g_file_info_get_size (file_data->info);
total_file_size += file_size;
if (file_size > max_file_size)
max_file_size = file_size;
}
min_free_space = total_file_size +
max_file_size + /* image rotation can require a temporary file */
(total_file_size * 5 / 100); /* 5% of FS fragmentation */
if ((free_space < min_free_space) && (error != NULL)) {
char *destination_name;
char *min_free_space_s;
char *free_space_s;
destination_name = g_file_get_parse_name (destination);
min_free_space_s = g_format_size (min_free_space);
free_space_s = g_format_size (free_space);
*error = g_error_new (G_IO_ERROR,
G_IO_ERROR_NO_SPACE,
/* Translators: For example: Not enough free space in “/home/user/Images”.\n1.3 GB of space is required but only 300 MB is available. */
_("Not enough free space in “%s”.\n%s of space is required but only %s is available."),
destination_name,
min_free_space_s,
free_space_s);
g_free (free_space_s);
g_free (min_free_space_s);
g_free (destination_name);
}
return free_space >= min_free_space;
}