[easytag/wip/gio: 10/10] 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: 10/10] WIP Use GIO when reading and writing FLAC tags
- Date: Sat, 6 Dec 2014 22:36:50 +0000 (UTC)
commit 110e4c10dbd4221df850297515e2c022bdfaa084
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: Verify that errors are caught at the first problem and returned to
the caller.
src/tags/flac_tag.c | 552 +++++++++++++++++++++++++++++++++++++++++----------
1 files changed, 449 insertions(+), 103 deletions(-)
---
diff --git a/src/tags/flac_tag.c b/src/tags/flac_tag.c
index 2109fb0..f5fa4eb 100644
--- a/src/tags/flac_tag.c
+++ b/src/tags/flac_tag.c
@@ -41,14 +41,35 @@
#include "picture.h"
#include "charset.h"
+/*
+ * EtFlacReadState:
+ *
+ * Used as a FLAC__IOHandle for FLAC__metadata_chain_read_with_callbacks().
+ */
+typedef struct
+{
+ GFileInputStream *istream;
+ gboolean eof;
+ GError *error;
+} EtFlacReadState;
-/***************
- * Declaration *
- ***************/
+/*
+ * EtFlacWriteState:
+ *
+ * Used as a FLAC__IOHandle for FLAC__metadata_chain_write_with_callbacks() and
+ * FLAC__metadata_chain_write_with_callbacks_and_tempfile().
+ */
+typedef struct
+{
+ GFileInputStream *istream;
+ GFileOutputStream *ostream;
+ GFileIOStream *iostream;
+ gboolean eof;
+ GError *error;
+} EtFlacWriteState;
#define MULTIFIELD_SEPARATOR " - "
-
/* FLAC uses Ogg Vorbis comments
* Ogg Vorbis fields names :
* - TITLE : Track name
@@ -76,23 +97,247 @@
* 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
+read_func (GInputStream *istream,
+ void *buffer,
+ gsize count,
+ gboolean *eof,
+ GError **error)
+{
+ gssize bytes_read;
+
+ *eof = FALSE;
+
+ bytes_read = g_input_stream_read (istream, buffer, count, NULL, error);
+
+ if (bytes_read == -1)
+ {
+ errno = EIO;
+ return 0;
+ }
+ else if (bytes_read == 0)
+ {
+ *eof = TRUE;
+ }
+
+ return bytes_read;
+}
+
+static size_t
+et_flac_read_read_func (void *ptr,
+ size_t size,
+ size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ EtFlacReadState *state;
+
+ state = (EtFlacReadState *)handle;
+
+ return read_func (G_INPUT_STREAM (state->istream), ptr, size * nmemb,
+ &state->eof, &state->error);
+}
+
+static size_t
+et_flac_write_read_func (void *ptr,
+ size_t size,
+ size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ EtFlacWriteState *state;
+
+ state = (EtFlacWriteState *)handle;
+
+ return read_func (G_INPUT_STREAM (state->istream), ptr, size * nmemb,
+ &state->eof, &state->error);
+}
+
+static size_t
+et_flac_write_write_func (const void *ptr,
+ size_t size,
+ size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ EtFlacWriteState *state;
+ gsize bytes_written;
+
+ state = (EtFlacWriteState *)handle;
+
+ 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 bytes_written;
+}
+
+static gint
+seek_func (GSeekable *seekable,
+ goffset offset,
+ gint whence,
+ GError **error)
+{
+ GSeekType seektype;
+
+ if (!g_seekable_can_seek (seekable))
+ {
+ errno = EBADF;
+ 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:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (g_seekable_seek (seekable, offset, seektype, NULL, error))
+ {
+ return 0;
+ }
+ else
+ {
+ /* TODO: More suitable error. */
+ errno = EINVAL;
+ return -1;
+ }
+ }
+}
+
+static int
+et_flac_read_seek_func (FLAC__IOHandle handle,
+ FLAC__int64 offset,
+ int whence)
+{
+ EtFlacReadState *state;
+
+ state = (EtFlacReadState *)handle;
+
+ return seek_func (G_SEEKABLE (state->istream), offset, whence,
+ &state->error);
+}
+
+static int
+et_flac_write_seek_func (FLAC__IOHandle handle,
+ FLAC__int64 offset,
+ int whence)
+{
+ EtFlacWriteState *state;
+
+ state = (EtFlacWriteState *)handle;
+
+ /* Seeking on the IOStream causes both the input and output stream to have
+ * the same offset. */
+ return seek_func (G_SEEKABLE (state->iostream), offset, whence,
+ &state->error);
+}
+
+static FLAC__int64
+tell_func (GSeekable *seekable)
+{
+ if (!g_seekable_can_seek (seekable))
+ {
+ errno = EBADF;
+ return -1;
+ }
+ else
+ {
+ return g_seekable_tell (seekable);
+ }
+}
+
+static FLAC__int64
+et_flac_read_tell_func (FLAC__IOHandle handle)
+{
+ EtFlacReadState *state;
+
+ state = (EtFlacReadState *)handle;
+
+ return tell_func (G_SEEKABLE (state->istream));
+}
+
+static FLAC__int64
+et_flac_write_tell_func (FLAC__IOHandle handle)
+{
+ EtFlacWriteState *state;
+
+ state = (EtFlacWriteState *)handle;
+
+ return tell_func (G_SEEKABLE (state->iostream));
+}
+
+static int
+et_flac_read_eof_func (FLAC__IOHandle handle)
+{
+ EtFlacReadState *state;
+
+ state = (EtFlacReadState *)handle;
+
+ /* EOF is not directly supported by GFileInputStream. */
+ return state->eof ? 1 : 0;
+}
+
+static int
+et_flac_write_eof_func (FLAC__IOHandle handle)
+{
+ EtFlacWriteState *state;
+
+ state = (EtFlacWriteState *)handle;
+
+ /* EOF is not directly supported by GFileInputStream. */
+ return state->eof ? 1 : 0;
+}
+
+static int
+et_flac_read_close_func (FLAC__IOHandle handle)
+{
+ EtFlacReadState *state;
+
+ state = (EtFlacReadState *)handle;
+
+ g_clear_object (&state->istream);
+ g_clear_error (&state->error);
+
+ /* Always return success. */
+ return 0;
+}
+
+static int
+et_flac_write_close_func (FLAC__IOHandle handle)
+{
+ EtFlacWriteState *state;
+
+ state = (EtFlacWriteState *)handle;
+
+ g_clear_object (&state->iostream);
+ g_clear_error (&state->error);
+
+ /* Always return success. */
+ return 0;
+}
/*
- * Read tag data from a FLAC file using the level 1 flac interface,
+ * Read tag data from a FLAC file using the level 2 flac interface,
* Note:
* - if field is found but contains no info (strlen(str)==0), we don't read it
*/
@@ -101,11 +346,17 @@ 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;
+ EtFlacReadState state;
+ FLAC__IOCallbacks callbacks = { et_flac_read_read_func,
+ NULL, /* Do not set a write callback. */
+ et_flac_read_seek_func,
+ et_flac_read_tell_func,
+ et_flac_read_eof_func,
+ et_flac_read_close_func };
+ FLAC__Metadata_Iterator *iter;
+
gchar *string = NULL;
- gchar *filename;
- gchar *filename_utf8;
guint i;
Picture *prev_pic = NULL;
//gint j = 1;
@@ -113,48 +364,57 @@ flac_tag_read_file_tag (GFile *file,
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. */
- filename = g_file_get_path (file);
- iter = FLAC__metadata_simple_iterator_new();
+ chain = FLAC__metadata_chain_new ();
- if ( iter == NULL || !FLAC__metadata_simple_iterator_init(iter, filename, true, false) )
+ 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;
+ }
+
+ state.error = NULL;
+ state.istream = g_file_read (file, NULL, &state.error);
+
+ 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_read_close_func (&state);
+ break;
}
- g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
- _("Error while opening file: %s"), flac_error_msg);
- g_free (filename);
return FALSE;
}
-
- filename_utf8 = filename_to_display (filename);
- g_free (filename);
- /* 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_read_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;
@@ -702,14 +962,11 @@ flac_tag_read_file_tag (GFile *file,
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_read_close_func (&state);
#ifdef ENABLE_MP3
/* If no FLAC vorbis tag found : we try to get the ID3 tag if it exists
@@ -732,8 +989,9 @@ flac_tag_read_file_tag (GFile *file,
&& FileTag->encoded_by == NULL
&& FileTag->picture == NULL)
{
- /* Ignore errors. */
- id3tag_read_file_tag (file, FileTag, NULL);
+ gboolean rc;
+
+ rc = id3tag_read_file_tag (file, FileTag, NULL);
// If an ID3 tag has been found (and no FLAC tag), we mark the file as
// unsaved to rewrite a flac tag.
@@ -757,10 +1015,11 @@ flac_tag_read_file_tag (GFile *file,
{
FileTag->saved = FALSE;
}
+
+ return rc;
}
#endif
- g_free(filename_utf8);
return TRUE;
}
@@ -819,6 +1078,15 @@ flac_tag_write_file_tag (const ET_File *ETFile,
GError **error)
{
const File_Tag *FileTag;
+ GFile *file;
+ GFileIOStream *iostream;
+ EtFlacWriteState state;
+ FLAC__IOCallbacks callbacks = { et_flac_write_read_func,
+ et_flac_write_write_func,
+ et_flac_write_seek_func,
+ et_flac_write_tell_func,
+ et_flac_write_eof_func,
+ et_flac_write_close_func };
const gchar *filename;
const gchar *filename_utf8;
const gchar *flac_error_msg;
@@ -836,31 +1104,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.error = NULL;
+ /* TODO: Fallback to an in-memory copy of the file for non-local files,
+ * where creation of the GFileIOStream may 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_io_stream_get_output_stream (G_IO_STREAM (iostream)));
+ state.iostream = 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_write_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];
@@ -868,19 +1159,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_write_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.
//
@@ -911,7 +1202,7 @@ flac_tag_write_file_tag (const ET_File *ETFile,
FLAC__metadata_iterator_delete_block(iter,true);
break;
}
-
+
default:
break;
}
@@ -1104,37 +1395,92 @@ 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];
+ EtFlacWriteState temp_state;
+ GFile *temp_file;
+ GFileIOStream *temp_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", &temp_iostream,
+ &temp_error);
+
+ if (temp_file == NULL)
+ {
+ FLAC__metadata_chain_delete (chain);
+ g_propagate_error (error, temp_error);
+ et_flac_write_close_func (&state);
+ return FALSE;
+ }
+
+ temp_state.error = NULL;
+ temp_state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM
(temp_iostream)));
+ temp_state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM
(temp_iostream)));
+ temp_state.iostream = temp_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);
+ et_flac_write_close_func (&temp_state);
+ et_flac_write_close_func (&state);
+
+ 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);
+ et_flac_write_close_func (&temp_state);
+
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Failed to write comments to file ‘%s’: %s"),
+ filename_utf8, state.error->message);
+ et_flac_write_close_func (&state);
+ return FALSE;
+ }
}
-
- 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);
+ et_flac_write_close_func (&state);
+
+ 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);
+ et_flac_write_close_func (&state);
-
#ifdef ENABLE_MP3
{
// Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]