[easytag/wip/gio] WIP Use GIO when reading and writing FLAC tags
- From: David King <davidk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [easytag/wip/gio] WIP Use GIO when reading and writing FLAC tags
- Date: Fri, 3 Oct 2014 16:47:29 +0000 (UTC)
commit aeb92289c9151eca223b917f795121ada650804c
Author: David King <amigadave amigadave com>
Date: Fri Oct 3 17:44:31 2014 +0100
WIP Use GIO when reading and writing FLAC tags
TODO: Handle non-local files with a memory- (or file-) backed IOStream.
Simplify differences between reading and writing callbacks. Verify that
errors are caught at the first problem and returned to the caller.
src/et_core.c | 2 +-
src/tags/flac_tag.c | 488 ++++++++++++++++++++++++++++++++++++++++-----------
src/tags/flac_tag.h | 2 +-
3 files changed, 389 insertions(+), 103 deletions(-)
---
diff --git a/src/et_core.c b/src/et_core.c
index 89d76c6..afd64dc 100644
--- a/src/et_core.c
+++ b/src/et_core.c
@@ -550,7 +550,7 @@ GList *ET_Add_File_To_File_List (gchar *filename)
#endif
#ifdef ENABLE_FLAC
case FLAC_TAG:
- if (!flac_tag_read_file_tag (filename, FileTag, &error))
+ if (!flac_tag_read_file_tag (file, FileTag, &error))
{
Log_Print (LOG_ERROR,
_("Error reading tag from FLAC file ‘%s’: %s"),
diff --git a/src/tags/flac_tag.c b/src/tags/flac_tag.c
index acb5ea8..b7b6bd8 100644
--- a/src/tags/flac_tag.c
+++ b/src/tags/flac_tag.c
@@ -42,14 +42,16 @@
#include "picture.h"
#include "charset.h"
-
-/***************
- * Declaration *
- ***************/
+typedef struct
+{
+ GFile *file;
+ GFileInputStream *istream;
+ GFileOutputStream *ostream;
+ GError *error;
+} EtFlacState;
#define MULTIFIELD_SEPARATOR " - "
-
/* FLAC uses Ogg Vorbis comments
* Ogg Vorbis fields names :
* - TITLE : Track name
@@ -77,20 +79,184 @@
* of any language.
*/
-
-
-/**************
- * Prototypes *
- **************/
-
static gboolean Flac_Write_Delimetered_Tag (FLAC__StreamMetadata *vc_block, const gchar *tag_name, gchar
*values);
static gboolean Flac_Write_Tag (FLAC__StreamMetadata *vc_block, const gchar *tag_name, gchar *value);
static gboolean Flac_Set_Tag (FLAC__StreamMetadata *vc_block, const gchar *tag_name, gchar *value, gboolean
split);
-/*************
- * Functions *
- *************/
+static size_t
+et_flac_read_func (void *ptr,
+ size_t size,
+ size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ EtFlacState *state;
+ gssize bytes_read;
+
+ state = (EtFlacState *)handle;
+ bytes_read = g_input_stream_read (G_INPUT_STREAM (state->istream), ptr,
+ size * nmemb, NULL, &state->error);
+
+ if (bytes_read == -1)
+ {
+ return 0;
+ }
+ else
+ {
+ return bytes_read;
+ }
+}
+
+static size_t
+et_flac_write_func (const void *ptr,
+ size_t size,
+ size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ EtFlacState *state;
+ gsize bytes_written;
+ GSeekable *seekable_istream;
+ GSeekable *seekable_ostream;
+
+ state = (EtFlacState *)handle;
+
+ /* Should only be needed the first time (except for temporary files). */
+ if (state->ostream == NULL)
+ {
+ state->ostream = g_file_replace (state->file, NULL, FALSE,
+ G_FILE_CREATE_NONE, NULL,
+ &state->error);
+
+ if (state->ostream == NULL)
+ {
+ errno = EIO;
+ return 0;
+ }
+ }
+
+ /* Seek to the same position as is set on the input stream. */
+ seekable_istream = G_SEEKABLE (state->istream);
+ seekable_ostream = G_SEEKABLE (state->ostream);
+
+ if (g_seekable_can_seek (seekable_istream)
+ && g_seekable_can_seek (seekable_ostream))
+ {
+ goffset offset;
+
+ offset = g_seekable_tell (seekable_istream);
+
+ if (!g_seekable_seek (seekable_ostream, offset, G_SEEK_SET, NULL,
+ &state->error))
+ {
+ errno = EIO;
+ return 0;
+ }
+ }
+
+ if (!g_output_stream_write_all (G_OUTPUT_STREAM (state->ostream),
+ ptr, size * nmemb, &bytes_written, NULL,
+ &state->error))
+ {
+ g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %" G_GSIZE_FORMAT
+ " bytes of data were written", bytes_written,
+ size);
+ errno = EIO;
+ return 0;
+ }
+ else
+ {
+ return bytes_written;
+ }
+
+ /* FIXME: Preserve the modification time dependent on user preferences. */
+ g_settings_get_boolean (MainSettings, "file-preserve-modification-time");
+ return 0;
+}
+
+static int
+et_flac_seek_func (FLAC__IOHandle handle,
+ FLAC__int64 offset,
+ int whence)
+{
+ EtFlacState *state;
+ GSeekable *seekable;
+ GSeekType seektype;
+
+ state = (EtFlacState *)handle;
+ seekable = G_SEEKABLE (state->istream);
+
+ if (!g_seekable_can_seek (seekable))
+ {
+ return -1;
+ }
+ else
+ {
+ switch (whence)
+ {
+ case SEEK_SET:
+ seektype = G_SEEK_SET;
+ break;
+ case SEEK_CUR:
+ seektype = G_SEEK_CUR;
+ break;
+ case SEEK_END:
+ seektype = G_SEEK_END;
+ break;
+ default:
+ return -1;
+ }
+
+ if (g_seekable_seek (seekable, offset, seektype, NULL, &state->error))
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+}
+
+static FLAC__int64
+et_flac_tell_func (FLAC__IOHandle handle)
+{
+ EtFlacState *state;
+ GSeekable *seekable;
+
+ state = (EtFlacState *)handle;
+ seekable = G_SEEKABLE (state->istream);
+
+ if (!g_seekable_can_seek (seekable))
+ {
+ return -1;
+ }
+ else
+ {
+ return g_seekable_tell (seekable);
+ }
+}
+
+static int
+et_flac_eof_func (FLAC__IOHandle handle)
+{
+ /* EOF is not directly supported by GFileInputStream. */
+ return 0;
+}
+
+static int
+et_flac_close_func (FLAC__IOHandle handle)
+{
+ EtFlacState *state;
+
+ state = (EtFlacState *)handle;
+
+ g_clear_object (&state->istream);
+ g_clear_object (&state->ostream);
+ g_clear_error (&state->error);
+
+ /* Always return success. */
+ return 0;
+}
/*
* Read tag data from a FLAC file using the level 1 flac interface,
@@ -98,58 +264,80 @@ static gboolean Flac_Set_Tag (FLAC__StreamMetadata *vc_block, const gchar *tag_n
* - if field is found but contains no info (strlen(str)==0), we don't read it
*/
gboolean
-flac_tag_read_file_tag (const gchar *filename,
+flac_tag_read_file_tag (GFile *file,
File_Tag *FileTag,
GError **error)
{
- FLAC__Metadata_SimpleIterator *iter;
- const gchar *flac_error_msg;
+ FLAC__Metadata_Chain *chain;
+ EtFlacState state;
+ FLAC__IOCallbacks callbacks = { et_flac_read_func, et_flac_write_func,
+ et_flac_seek_func, et_flac_tell_func,
+ et_flac_eof_func, et_flac_close_func };
+ FLAC__Metadata_Iterator *iter;
+
gchar *string = NULL;
- gchar *filename_utf8 = filename_to_display(filename);
guint i;
Picture *prev_pic = NULL;
//gint j = 1;
- g_return_val_if_fail (filename != NULL && FileTag != NULL, FALSE);
+ g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- // Initialize the iterator for the blocks
- iter = FLAC__metadata_simple_iterator_new();
- if ( iter == NULL || !FLAC__metadata_simple_iterator_init(iter, filename, true, false) )
+ chain = FLAC__metadata_chain_new ();
+
+ if (chain == NULL)
{
- if ( iter == NULL )
- {
- // Error with "FLAC__metadata_simple_iterator_new"
- flac_error_msg =
FLAC__Metadata_SimpleIteratorStatusString[FLAC__METADATA_SIMPLE_ITERATOR_STATUS_MEMORY_ALLOCATION_ERROR];
- }else
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s",
+ g_strerror (ENOMEM));
+ return FALSE;
+ }
+
+ /* A GFile is not needed for reading. */
+ state.file = NULL;
+ state.error = NULL;
+ state.istream = g_file_read (file, NULL, &state.error);
+ /* Open the output stream in the first call to et_flac_write_func(). */
+ state.ostream = NULL;
+
+ if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks))
+ {
+ FLAC__Metadata_ChainStatus status;
+
+ status = FLAC__metadata_chain_status (chain);
+
+ switch (status)
{
- // Error with "FLAC__metadata_simple_iterator_init"
- FLAC__Metadata_SimpleIteratorStatus status = FLAC__metadata_simple_iterator_status(iter);
- flac_error_msg = FLAC__Metadata_SimpleIteratorStatusString[status];
-
- FLAC__metadata_simple_iterator_delete(iter);
+ /* TODO: Provide a dedicated error enum corresponding to status. */
+ default:
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
+ _("Error opening FLAC file"));
+ et_flac_close_func (&state);
+ break;
}
- g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
- _("Error while opening file: %s"), flac_error_msg);
return FALSE;
}
-
- /* libFLAC is able to detect (and skip) ID3v2 tags by itself */
+ iter = FLAC__metadata_iterator_new ();
- while (FLAC__metadata_simple_iterator_next(iter))
+ if (iter == NULL)
{
- // Get block data
- FLAC__StreamMetadata *block = FLAC__metadata_simple_iterator_get_block(iter);
- //g_print("Read: %d %s -> block type:
%d\n",j++,g_path_get_basename(filename),FLAC__metadata_simple_iterator_get_block_type(iter));
-
- // Action to do according the type
- switch ( FLAC__metadata_simple_iterator_get_block_type(iter) )
+ et_flac_close_func (&state);
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s",
+ g_strerror (ENOMEM));
+ return FALSE;
+ }
+
+ FLAC__metadata_iterator_init (iter, chain);
+
+ while (FLAC__metadata_iterator_next (iter))
+ {
+ FLAC__StreamMetadata *block;
+
+ block = FLAC__metadata_iterator_get_block (iter);
+
+ switch (block->type)
{
- //
- // Read the VORBIS_COMMENT block (only one should exist)
- //
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
{
FLAC__StreamMetadata_VorbisComment *vc;
@@ -352,9 +540,27 @@ flac_tag_read_file_tag (const gchar *filename,
FileTag->year = field_value;
if (g_utf8_strlen (FileTag->year, -1) > 4)
{
+ GFileInfo *info;
+ const gchar *display_name;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, error);
+
+ if (!info)
+ {
+ et_flac_close_func (&state);
+ FLAC__metadata_iterator_delete (iter);
+ FLAC__metadata_chain_delete (chain);
+ return FALSE;
+ }
+
+ display_name = g_file_info_get_display_name (info);
Log_Print (LOG_WARNING,
_("The year value ‘%s’ seems to be invalid in file ‘%s’. The
information will be lost when saving"),
- FileTag->year, filename_utf8);
+ FileTag->year, display_name);
+ g_object_unref (info);
}
}
}
@@ -703,14 +909,11 @@ flac_tag_read_file_tag (const gchar *filename,
default:
break;
}
-
- /* Free block data. */
- FLAC__metadata_object_delete (block);
}
-
- // Free iter
- FLAC__metadata_simple_iterator_delete(iter);
+ FLAC__metadata_iterator_delete (iter);
+ FLAC__metadata_chain_delete (chain);
+ et_flac_close_func (&state);
#ifdef ENABLE_MP3
/* If no FLAC vorbis tag found : we try to get the ID3 tag if it exists
@@ -733,7 +936,12 @@ flac_tag_read_file_tag (const gchar *filename,
&& FileTag->encoded_by == NULL
&& FileTag->picture == NULL)
{
- gboolean rc = id3tag_read_file_tag (filename, FileTag, NULL);
+ gchar *filename;
+ gboolean rc;
+
+ filename = g_file_get_path (file);
+ rc = id3tag_read_file_tag (filename, FileTag, NULL);
+ g_free (filename);
// If an ID3 tag has been found (and no FLAC tag), we mark the file as
// unsaved to rewrite a flac tag.
@@ -758,12 +966,10 @@ flac_tag_read_file_tag (const gchar *filename,
FileTag->saved = FALSE;
}
- g_free(filename_utf8);
return rc;
}
#endif
- g_free(filename_utf8);
return TRUE;
}
@@ -822,6 +1028,12 @@ flac_tag_write_file_tag (const ET_File *ETFile,
GError **error)
{
const File_Tag *FileTag;
+ GFile *file;
+ GFileIOStream *iostream;
+ EtFlacState state;
+ FLAC__IOCallbacks callbacks = { et_flac_read_func, et_flac_write_func,
+ et_flac_seek_func, et_flac_tell_func,
+ et_flac_eof_func, et_flac_close_func };
const gchar *filename;
const gchar *filename_utf8;
const gchar *flac_error_msg;
@@ -839,31 +1051,54 @@ flac_tag_write_file_tag (const ET_File *ETFile,
/* libFLAC is able to detect (and skip) ID3v2 tags by itself */
- // Create a new chain instance to get all blocks in one time
- chain = FLAC__metadata_chain_new();
- if (chain == NULL || !FLAC__metadata_chain_read(chain,filename))
+ /* Create a new chain instance to get all blocks in one time. */
+ chain = FLAC__metadata_chain_new ();
+
+ if (chain == NULL)
{
- if (chain == NULL)
- {
- // Error with "FLAC__metadata_chain_new"
- flac_error_msg =
FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR];
- }else
- {
- // Error with "FLAC__metadata_chain_read"
- FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain);
- flac_error_msg = FLAC__Metadata_ChainStatusString[status];
-
- FLAC__metadata_chain_delete(chain);
- }
+ flac_error_msg =
FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR];
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Error while opening file ‘%s’ as FLAC: %s"),
filename_utf8, flac_error_msg);
return FALSE;
}
+
+ file = g_file_new_for_path (filename);
+
+ state.file = file;
+ state.error = NULL;
+ /* FIXME: Fallback to an in-memory copy of the file for non-local files,
+ * where creation of the GFileIOStream will fail. */
+ iostream = g_file_open_readwrite (file, NULL, &state.error);
+
+ if (iostream == NULL)
+ {
+ FLAC__metadata_chain_delete (chain);
+ g_propagate_error (error, state.error);
+ g_object_unref (file);
+ return FALSE;
+ }
+
+ state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (iostream)));
+ state.ostream = G_FILE_OUTPUT_STREAM (g_object_ref (G_OBJECT (g_io_stream_get_output_stream (G_IO_STREAM
(iostream)))));
+
+ if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks))
+ {
+ const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
+ flac_error_msg = FLAC__Metadata_ChainStatusString[status];
+ FLAC__metadata_chain_delete (chain);
+
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error while opening file ‘%s’ as FLAC: %s"),
+ filename_utf8, flac_error_msg);
+ et_flac_close_func (&state);
+ return FALSE;
+ }
- // Create a new iterator instance for the chain
- iter = FLAC__metadata_iterator_new();
+ /* Create a new iterator instance for the chain. */
+ iter = FLAC__metadata_iterator_new ();
+
if (iter == NULL)
{
flac_error_msg =
FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR];
@@ -871,19 +1106,19 @@ flac_tag_write_file_tag (const ET_File *ETFile,
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Error while opening file ‘%s’ as FLAC: %s"),
filename_utf8, flac_error_msg);
+ FLAC__metadata_chain_delete (chain);
+ et_flac_close_func (&state);
return FALSE;
}
- // Initialize the iterator to point to the first metadata block in the given chain.
- FLAC__metadata_iterator_init(iter,chain);
+ FLAC__metadata_iterator_init (iter, chain);
- while (FLAC__metadata_iterator_next(iter))
+ while (FLAC__metadata_iterator_next (iter))
{
- //g_print("Write: %d %s -> block type:
%d\n",j++,g_path_get_basename(filename),FLAC__metadata_iterator_get_block_type(iter));
-
- // Action to do according the type
- switch ( FLAC__metadata_iterator_get_block_type(iter) )
+ switch (FLAC__metadata_iterator_get_block_type (iter))
{
+ /* TODO: Modify the blocks directly, rather than deleting and
+ * recreating. */
//
// Delete the VORBIS_COMMENT block and convert to padding. But before, save the original vendor
string.
//
@@ -914,7 +1149,7 @@ flac_tag_write_file_tag (const ET_File *ETFile,
FLAC__metadata_iterator_delete_block(iter,true);
break;
}
-
+
default:
break;
}
@@ -1103,37 +1338,88 @@ flac_tag_write_file_tag (const ET_File *ETFile,
}
}
- // Free iter
- FLAC__metadata_iterator_delete(iter);
-
+ FLAC__metadata_iterator_delete (iter);
//
// Prepare for writing tag
//
- // Move all PADDING blocks to the end on the metadata, and merge them into a single block.
- FLAC__metadata_chain_sort_padding(chain);
+ FLAC__metadata_chain_sort_padding (chain);
/* Write tag. */
- if (!FLAC__metadata_chain_write (chain, /*padding*/TRUE,
- g_settings_get_boolean (MainSettings,
- "file-preserve-modification-time")))
+ if (FLAC__metadata_chain_check_if_tempfile_needed (chain, true))
{
- // Error with "FLAC__metadata_chain_write"
- FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain);
- flac_error_msg = FLAC__Metadata_ChainStatusString[status];
+ EtFlacState temp_state;
+ GFile *temp_file;
+ GFileIOStream *iostream;
+ GError *temp_error = NULL;
- FLAC__metadata_chain_delete(chain);
-
- g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
- _("Failed to write comments to file ‘%s’: %s"),
- filename_utf8, flac_error_msg);
- return FALSE;
+ temp_file = g_file_new_tmp ("easytag-XXXXXX", &iostream, &temp_error);
+
+ if (temp_file == NULL)
+ {
+ FLAC__metadata_chain_delete (chain);
+ g_propagate_error (error, temp_error);
+ return FALSE;
+ }
+
+ temp_state.file = temp_file;
+ temp_state.error = NULL;
+ temp_state.istream = G_FILE_INPUT_STREAM (iostream);
+ temp_state.ostream = G_FILE_OUTPUT_STREAM (g_object_ref (G_OBJECT (iostream)));
+
+ if (!FLAC__metadata_chain_write_with_callbacks_and_tempfile (chain,
+ true,
+ &state,
+ callbacks,
+ &temp_state,
+ callbacks))
+ {
+ const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
+ flac_error_msg = FLAC__Metadata_ChainStatusString[status];
+
+ FLAC__metadata_chain_delete (chain);
+ g_object_unref (temp_state.file);
+
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Failed to write comments to file ‘%s’: %s"),
+ filename_utf8, flac_error_msg);
+ return FALSE;
+ }
+
+ if (!g_file_move (temp_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL,
+ NULL, &state.error))
+ {
+ FLAC__metadata_chain_delete (chain);
+ g_object_unref (temp_state.file);
+
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Failed to write comments to file ‘%s’: %s"),
+ filename_utf8, state.error->message);
+ return FALSE;
+ }
+
+ g_object_unref (temp_state.file);
}
-
- FLAC__metadata_chain_delete(chain);
+ else
+ {
+ if (!FLAC__metadata_chain_write_with_callbacks (chain, true, &state,
+ callbacks))
+ {
+ const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain);
+ flac_error_msg = FLAC__Metadata_ChainStatusString[status];
+
+ FLAC__metadata_chain_delete (chain);
+
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Failed to write comments to file ‘%s’: %s"),
+ filename_utf8, flac_error_msg);
+ return FALSE;
+ }
+ }
+
+ FLAC__metadata_chain_delete (chain);
-
#ifdef ENABLE_MP3
{
// Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function)
diff --git a/src/tags/flac_tag.h b/src/tags/flac_tag.h
index 2b0e20e..85da0c9 100644
--- a/src/tags/flac_tag.h
+++ b/src/tags/flac_tag.h
@@ -26,7 +26,7 @@
G_BEGIN_DECLS
-gboolean flac_tag_read_file_tag (const gchar *filename, File_Tag *FileTag, GError **error);
+gboolean flac_tag_read_file_tag (GFile *file, File_Tag *FileTag, GError **error);
gboolean flac_tag_write_file_tag (const ET_File *ETFile, GError **error);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]