diff options
Diffstat (limited to 'src/ithumb-writer.c')
-rw-r--r-- | src/ithumb-writer.c | 456 |
1 files changed, 338 insertions, 118 deletions
diff --git a/src/ithumb-writer.c b/src/ithumb-writer.c index 075a9dc..7fb3bed 100644 --- a/src/ithumb-writer.c +++ b/src/ithumb-writer.c @@ -35,6 +35,12 @@ #include <string.h> #include <gdk-pixbuf/gdk-pixbuf.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + struct _iThumbWriter { off_t cur_offset; FILE *f; @@ -48,11 +54,11 @@ typedef struct _iThumbWriter iThumbWriter; * here to specify which size we are interested in in case the pixbuf is non * square */ -static gushort * +static guint16 * pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) { guchar *pixels; - gushort *result; + guint16 *result; gint row_stride; gint channels; gint width; @@ -66,9 +72,7 @@ pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) "pixels", &pixels, NULL); g_return_val_if_fail ((width <= dst_width) && (height <= dst_height), NULL); result = g_malloc0 (dst_width * dst_height * 2); - if (result == NULL) { - return NULL; - } + for (h = 0; h < height; h++) { for (w = 0; w < width; w++) { gint r; @@ -92,117 +96,147 @@ pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) -static Itdb_Image * -itdb_image_dup (Itdb_Image *image) -{ - Itdb_Image *result; - result = g_new0 (Itdb_Image, 1); - if (result == NULL) { - return NULL; - } - result->type = image->type; - result->height = image->height; - result->width = image->width; - result->offset = image->offset; - result->size = image->size; +static char * +ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) +{ + char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; + char *filename, *buf; - return result; -} + buf = g_strdup_printf ("F%04u_1.ithmb", correlation_id); -static Itdb_Image * -ithumb_writer_write_thumbnail (iThumbWriter *writer, - const char *filename) -{ - GdkPixbuf *thumb; - gushort *pixels; - Itdb_Image *image; + paths[2] = buf; - image = g_hash_table_lookup (writer->cache, filename); - if (image != NULL) { - return itdb_image_dup (image); - } + filename = itdb_resolve_path (mount_point, (const char **)paths); - image = g_new0 (Itdb_Image, 1); - if (image == NULL) { - return NULL; + /* itdb_resolve_path() only returns existing paths */ + if (!filename) + { + gchar *path; + paths[2] = NULL; + path = itdb_resolve_path (mount_point, (const char **)paths); + if (path) + { + filename = g_build_filename (path, buf, NULL); + } + g_free (path); } - thumb = gdk_pixbuf_new_from_file_at_size (filename, - writer->img_info->width, - writer->img_info->height, - NULL); - if (thumb == NULL) { - g_free (image); - return NULL; - } - g_object_get (G_OBJECT (thumb), - "height", &image->height, - "width", &image->width, - NULL); - image->offset = writer->cur_offset; - image->type = writer->img_info->type; - image->size = writer->img_info->width * writer->img_info->height * 2; - /* FIXME: under certain conditions (probably related to writer->offset - * getting too big), this should be :F%04u_2.ithmb and so on - */ - image->filename = g_strdup_printf (":F%04u_1.ithmb", - writer->img_info->correlation_id); - pixels = pack_RGB_565 (thumb, writer->img_info->width, - writer->img_info->height); - g_object_unref (G_OBJECT (thumb)); - if (pixels == NULL) { - g_free (image); - return NULL; - } - if (fwrite (pixels, image->size, 1, writer->f) != 1) { - g_free (image); - g_free (pixels); - g_print ("Error writing to file: %s\n", strerror (errno)); - return NULL; - } - g_free (pixels); - writer->cur_offset += image->size; - g_hash_table_insert (writer->cache, g_strdup (filename), image); + g_free (buf); - return image; + return filename; } -static char * -ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) + +static gboolean +ithumb_writer_write_thumbnail (iThumbWriter *writer, + Itdb_Thumb *thumb) { - char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; - char *filename; + GdkPixbuf *pixbuf; + guint16 *pixels; + gchar *filename; + gint width, height; + + Itdb_Thumb *old_thumb; + + g_return_val_if_fail (writer, FALSE); + g_return_val_if_fail (thumb, FALSE); + + /* If the same filename was written before, just use the old + thumbnail to save space on the iPod */ + old_thumb = g_hash_table_lookup (writer->cache, thumb->filename); + if (old_thumb != NULL) + { + g_free (thumb->filename); + memcpy (thumb, old_thumb, sizeof (Itdb_Thumb)); + thumb->filename = g_strdup (old_thumb->filename); + return TRUE; + } + + filename = g_strdup (thumb->filename); + + pixbuf = gdk_pixbuf_new_from_file_at_size (filename, + writer->img_info->width, + writer->img_info->height, + NULL); + if (pixbuf == NULL) { + return FALSE; + } + + /* !! cannot write directly to &thumb->width/height because + g_object_get() returns a gint, but thumb->width/height are + gint16 !! */ + g_object_get (G_OBJECT (pixbuf), + "width", &width, + "height", &height, + NULL); + + thumb->width = width; + thumb->height = height; + thumb->offset = writer->cur_offset; + thumb->size = writer->img_info->width * writer->img_info->height * 2; +/* printf("offset: %d type: %d, size: %d\n", thumb->offset, thumb->type, thumb->size); */ + /* FIXME: under certain conditions (probably related to + * writer->offset getting too big), this should be :F%04u_2.ithmb + * and so on + */ + thumb->filename = g_strdup_printf (":F%04u_1.ithmb", + writer->img_info->correlation_id); + pixels = pack_RGB_565 (pixbuf, writer->img_info->width, + writer->img_info->height); + g_object_unref (G_OBJECT (pixbuf)); + + if (pixels == NULL) + { + return FALSE; + } + if (fwrite (pixels, thumb->size, 1, writer->f) != 1) { + g_free (pixels); + g_print ("Error writing to file: %s\n", strerror (errno)); + return FALSE; + } + g_free (pixels); + writer->cur_offset += thumb->size; + g_hash_table_insert (writer->cache, filename, thumb); - paths[2] = g_strdup_printf ("F%04u_1.ithmb", correlation_id); - filename = itdb_resolve_path (mount_point, (const char **)paths); - g_free (paths[2]); - return filename; + /* !! filename is g_free()d when destroying the hash table. Do not + do it here */ + + return TRUE; } +static void +write_thumbnail (gpointer _writer, gpointer _artwork) +{ + iThumbWriter *writer = _writer; + Itdb_Artwork *artwork = _artwork; + Itdb_Thumb *thumb; + + thumb = itdb_artwork_get_thumb_by_type (artwork, + writer->img_info->type); + + /* size == 0 indicates a thumbnail not yet written to the + thumbnail file */ + if (thumb && (thumb->size == 0)) + { + ithumb_writer_write_thumbnail (writer, thumb); + } +} static iThumbWriter * ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) { char *filename; iThumbWriter *writer; + writer = g_new0 (iThumbWriter, 1); - if (writer == NULL) { - return NULL; - } + writer->img_info = g_memdup (info, sizeof (IpodArtworkFormat)); - if (writer->img_info == NULL) { - g_free (writer); - return NULL; - } + writer->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - if (writer->cache == NULL) { - g_free (writer->img_info); - g_free (writer); - return NULL; - } + filename = ipod_image_get_ithmb_filename (mount_point, info->correlation_id); if (filename == NULL) { @@ -211,7 +245,7 @@ ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) g_free (writer); return NULL; } - writer->f = fopen (filename, "w"); + writer->f = fopen (filename, "ab"); if (writer->f == NULL) { g_print ("Error opening %s: %s\n", filename, strerror (errno)); g_free (filename); @@ -220,6 +254,7 @@ ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) g_free (writer); return NULL; } + writer->cur_offset = ftell (writer->f); g_free (filename); return writer; @@ -236,33 +271,222 @@ ithumb_writer_free (iThumbWriter *writer) } -static void -write_thumbnail (gpointer data, gpointer user_data) +static gboolean ithumb_rearrange_thumbnail_file (gpointer _key, + gpointer _thumbs, + gpointer _user_data) { - iThumbWriter *writer; - Itdb_Track *song; - Itdb_Image *thumb; + auto gint offset_sort (gconstpointer a, gconstpointer b); + gint offset_sort (gconstpointer a, gconstpointer b) + { + return (((Itdb_Thumb *)a)->offset - + ((Itdb_Thumb *)b)->offset); + } + gchar *filename = _key; + GList *thumbs = _thumbs; + gboolean *result = _user_data; + gint fd = -1; + guint32 size = 0; + guint32 tnf_num, tn_num, i; + GList *gl; + struct stat statbuf; + void *buf = NULL; + +/* printf ("%s: %d\n", filename, g_list_length (thumbs)); */ + + /* check if an error occured */ + if (*result == FALSE) + goto out; + + /* check if all thumbnails have the same size */ + for (gl=thumbs; gl; gl=gl->next) + { + Itdb_Thumb *img = gl->data; + + if (size == 0) + size = img->size; + if (size != img->size) + { + *result = FALSE; + goto out; + } + } + /* OK, all thumbs are the same size @size, let's see how many + * thumbnails are in the actual file */ +/* printf (" %d\n", size); */ + if (g_stat (filename, &statbuf) != 0) + { + *result = FALSE; + goto out; + } + tnf_num = statbuf.st_size / size; + + /* check if the file size is a multiple of @size */ + if (tnf_num*size != statbuf.st_size) + { + *result = FALSE; + goto out; + } + + tn_num = g_list_length (thumbs); + + /* We're finished if the number here and the number of thumbnails + * in our list is the same */ + if (tn_num == tnf_num) + goto out; + + fd = open (filename, O_RDWR, 0); + if (fd == -1) + { + *result = FALSE; + goto out; + } + + /* Performance note: for performance reaons the list should be + ordered in reverse order of offsets because of frequent use + g_list_last(), and instead of using g_list_nth_data() the list + should be crawled by element from the end -- I will do that + eventually unless someone beats me to it. */ + + /* Sort the list of thumbs according to img->offset */ + thumbs = g_list_sort (thumbs, offset_sort); + + buf = g_malloc (size); + + for (i=0; i<tn_num; ++i) + { + guint offset = i * size; + Itdb_Thumb *img = g_list_nth_data (thumbs, i); + if (offset != img->offset) + { /* We found an open space -> copy the last element here */ + gl = g_list_last (thumbs); + img = gl->data; + thumbs = g_list_delete_link (thumbs, gl); + thumbs = g_list_insert (thumbs, img, i); + + /* actually copy the data */ + if (lseek (fd, img->offset, SEEK_SET) != img->offset) + { + *result = FALSE; + goto out; + } + if (read (fd, buf, size) != size) + { + *result = FALSE; + goto out; + } + if (lseek (fd, offset, SEEK_SET) != offset) + { + *result = FALSE; + goto out; + } + if (write (fd, buf, size) != size) + { + *result = FALSE; + goto out; + } + + img->offset = offset; + } + } + /* truncate the file */ + if (ftruncate (fd, tn_num*size) == -1) + { + *result = FALSE; + goto out; + } + + out: + if (fd != -1) close (fd); + if (buf) g_free (buf); + g_list_free (thumbs); + return TRUE; +} + + +/* The actual image data of thumbnails is not read into memory. As a + consequence, writing the thumbnail file is not as straight-forward + as e.g. writing the iTunesDB where all data is held in memory. - song = (Itdb_Track *)user_data; - writer = (iThumbWriter *)data; + To avoid the need to read large amounts from the iPod and back, or + have to large files exist on the iPod (reading from the original + thumbnail fail and writing to the new thumbnail file), the + modifications are done in place. - thumb = ithumb_writer_write_thumbnail (writer, - song->orig_image_filename); - if (thumb != NULL) { - song->thumbnails = g_list_append (song->thumbnails, thumb); - song->artwork_count++; + It is assumed that all thumbnails have the same data size. If not, + FALSE is returned. + + If a thumbnail has been removed, a slot in the file is opened. This + slot is filled by copying data from the end of the file and + adjusting the corresponding Itdb_Image offset pointer. When all + slots are filled, the file is truncated to the new length. +*/ +static gboolean +ithmb_rearrange_existing_thumbnails (Itdb_iTunesDB *itdb, + const IpodArtworkFormat *info) +{ + GList *gl; + GHashTable *filenamehash; + gboolean result = TRUE; + + g_return_val_if_fail (itdb, FALSE); + g_return_val_if_fail (info, FALSE); + g_return_val_if_fail (itdb->mountpoint, FALSE); + + filenamehash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + /* Create a hash with all filenames used for thumbnails. + This will usually be a number of "F%04d_%d.ithmb" files. A + GList is kept with pointers to all images in a given file which + allows to adjust the offset pointers */ + for (gl=itdb->tracks; gl; gl=gl->next) + { + Itdb_Thumb *thumb; + Itdb_Track *track = gl->data; + g_return_val_if_fail (track, FALSE); + + thumb = itdb_artwork_get_thumb_by_type (track->artwork, + info->type); + if (thumb && thumb->filename && (thumb->size != 0)) + { + GList *thumbs; + gchar *filename = itdb_thumb_get_filename (itdb->device, + thumb); + if (filename) + { + thumbs = g_hash_table_lookup (filenamehash, filename); + thumbs = g_list_append (thumbs, thumb); + g_hash_table_insert (filenamehash, filename, thumbs); + } } + } + + g_hash_table_foreach_remove (filenamehash, + ithumb_rearrange_thumbnail_file, &result); + g_hash_table_destroy (filenamehash); + + return result; } +#endif G_GNUC_INTERNAL int -itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) +itdb_write_ithumb_files (Itdb_iTunesDB *db) { +#ifdef HAVE_GDKPIXBUF GList *writers; GList *it; + gchar *mount_point; const IpodArtworkFormat *format; /* g_print ("%s\n", G_GNUC_FUNCTION);*/ + g_return_val_if_fail (db, -1); + + mount_point = db->mountpoint; + /* FIXME: support writing to directory rather than writing to + iPod */ + if (mount_point == NULL) + return -1; if (db->device == NULL) { return -1; @@ -281,6 +505,8 @@ itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) switch (format->type) { case IPOD_COVER_SMALL: case IPOD_COVER_LARGE: + ithmb_rearrange_existing_thumbnails (db, + format); writer = ithumb_writer_new (mount_point, format); if (writer != NULL) { writers = g_list_prepend (writers, writer); @@ -297,26 +523,20 @@ itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) } for (it = db->tracks; it != NULL; it = it->next) { - Itdb_Track *song; + Itdb_Track *track; - song = (Itdb_Track *)it->data; - song->artwork_count = 0; - itdb_track_free_generated_thumbnails (song); - if (song->orig_image_filename == NULL) { - continue; - } - g_list_foreach (writers, write_thumbnail, song); + track = it->data; + g_return_val_if_fail (track, -1); + track->artwork_count = 0; + + g_list_foreach (writers, write_thumbnail, track->artwork); } g_list_foreach (writers, (GFunc)ithumb_writer_free, NULL); g_list_free (writers); return 0; -} #else -G_GNUC_INTERNAL int -itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) -{ return -1; -} #endif +} |