[easytag/wip/gio] WIP Use GIO when reading and writing FLAC tags



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]