forked from len0rd/rockbox
Initial import of tagdb
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7039 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
5e9f52f6d1
commit
d1c294c17d
25 changed files with 4144 additions and 0 deletions
32
apps/tagdb/Makefile
Normal file
32
apps/tagdb/Makefile
Normal file
|
@ -0,0 +1,32 @@
|
|||
OBJECTS = main.o db.o array_buffer.o unique.o malloc.o \
|
||||
header.o artist.o album.o song.o file.o \
|
||||
tag_dummy.o
|
||||
|
||||
all : tagdb parser
|
||||
|
||||
tagdb : $(OBJECTS)
|
||||
$(CC) -o tagdb $(OBJECTS)
|
||||
|
||||
parser: parser.o malloc.o
|
||||
$(CC) -o parser parser.o malloc.o
|
||||
|
||||
main.o : main.c config.h
|
||||
|
||||
db.o : db.c db.h config.h
|
||||
|
||||
array_buffer.o : array_buffer.c array_buffer.h config.h
|
||||
unique.o : unique.c unique.h
|
||||
malloc.o : malloc.c malloc.h config.h
|
||||
|
||||
header.o : header.c header.h config.h
|
||||
artist.o : artist.c artist.h config.h
|
||||
album.o : album.c album.h config.h
|
||||
song.o : song.c song.h config.h
|
||||
file.o : file.c file.h config.h
|
||||
|
||||
tag_dummy.o : tag_dummy.c tag_dummy.h config.h
|
||||
|
||||
parser.o : parser.c config.h
|
||||
|
||||
clean :
|
||||
rm -rf *.o tagdb parser
|
9
apps/tagdb/README
Normal file
9
apps/tagdb/README
Normal file
|
@ -0,0 +1,9 @@
|
|||
The code is currently a working mess... needs cleanup
|
||||
also it should be transformed into rockbox-format (header in each file).
|
||||
|
||||
things that work:
|
||||
* DB creation
|
||||
|
||||
things that don't work (yet)
|
||||
* Sorting
|
||||
* reading files to parse the tags
|
454
apps/tagdb/album.c
Normal file
454
apps/tagdb/album.c
Normal file
|
@ -0,0 +1,454 @@
|
|||
#include "malloc.h" // realloc() and free()
|
||||
#include <strings.h> // strncasecmp()
|
||||
#include <string.h> // strlen()
|
||||
|
||||
#include "album.h"
|
||||
|
||||
// how is our flag organized?
|
||||
#define FLAG(deleted, spare) ( 0xE0 | (deleted?0x10:0x00) | (spare & 0x0F) )
|
||||
#define FLAG_VALID(flag) ((flag & 0xE0) == 0xE0)
|
||||
#define FLAG_DELETED(flag) (flag & 0x10)
|
||||
#define FLAG_SPARE(flag) (flag & 0x0F)
|
||||
|
||||
static int do_resize(struct album_entry *e, const uint32_t name_len, const uint32_t song_count, const int zero_fill);
|
||||
|
||||
struct album_entry* new_album_entry(const uint32_t name_len, const uint32_t song_count) {
|
||||
// Start my allocating memory
|
||||
struct album_entry *e = (struct album_entry*)malloc(sizeof(struct album_entry));
|
||||
if( e == NULL ) {
|
||||
DEBUGF("new_album_entry: could not allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We begin empty
|
||||
e->name = NULL;
|
||||
e->size.name_len = 0;
|
||||
e->key = NULL;
|
||||
e->artist = 0;
|
||||
e->song = NULL;
|
||||
e->size.song_count = 0;
|
||||
|
||||
e->flag = FLAG(0, 0);
|
||||
|
||||
// and resize to the requested size
|
||||
if( do_resize(e, name_len, song_count, 1) ) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
int album_entry_destruct(struct album_entry *e) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
free(e->name);
|
||||
free(e->key);
|
||||
free(e->song);
|
||||
|
||||
free(e);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_resize(struct album_entry *e, const uint32_t name_len, const uint32_t song_count, const int zero_fill) {
|
||||
void* temp;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// begin with name
|
||||
if( name_len != e->size.name_len ) {
|
||||
temp = realloc(e->name, name_len);
|
||||
if(temp == NULL && name_len > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("do_resize: out of memory to resize name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->name = (char*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.name_len; i<name_len; i++)
|
||||
e->name[i] = (char)0x00;
|
||||
}
|
||||
|
||||
e->size.name_len = name_len;
|
||||
}
|
||||
|
||||
// now the song[]
|
||||
if( song_count != e->size.song_count ) {
|
||||
temp = realloc(e->song, song_count * sizeof(*e->song));
|
||||
if(temp == NULL && song_count > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("album_entry_resize: out of memory to resize song[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->song = (uint32_t*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.song_count; i<song_count; i++)
|
||||
e->song[i] = (uint32_t)0x00000000;
|
||||
}
|
||||
|
||||
e->size.song_count = song_count;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int album_entry_resize(struct album_entry *e, const uint32_t name_len, const uint32_t song_count) {
|
||||
return do_resize(e, name_len, song_count, 1);
|
||||
}
|
||||
|
||||
int album_entry_serialize(FILE *fd, const struct album_entry *e) {
|
||||
uint32_t length;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
assert(fd != NULL);
|
||||
|
||||
if( FLAG_DELETED(e->flag) ) { // we are deleted, do nothing
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// First byte we write is a flag-byte
|
||||
if( fwrite(&e->flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_serialize: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// First we write the length of the name field
|
||||
if( fwrite(&e->size.name_len, sizeof(e->size.name_len), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_serialize: failed to write name_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// now the name field itself
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("album_entry_serialize: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// the key-field (if present)
|
||||
if( e->key != NULL ) {
|
||||
length = strlen(e->key);
|
||||
} else {
|
||||
length = 0;
|
||||
}
|
||||
// length (always, 0 if not present)
|
||||
if( fwrite(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_serialize: failed to write length of key\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
if( e->key != NULL ) {
|
||||
// key itself
|
||||
if( fwrite(e->key, 1, length, fd) != length ) {
|
||||
DEBUGF("album_entry_serialize: failed to write key\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// Artist field
|
||||
if( fwrite(&e->artist, sizeof(e->artist), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_serialize: failed to write artist\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// count of songs
|
||||
if( fwrite(&e->size.song_count, sizeof(e->size.song_count), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_serialize: failed to write song_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// song[] itself
|
||||
if( fwrite(e->song, sizeof(*e->song), e->size.song_count, fd) != e->size.song_count ) {
|
||||
DEBUGF("album_entry_serialize: failed to write songs\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int album_entry_unserialize(struct album_entry **e, FILE *fd) {
|
||||
uint32_t length;
|
||||
unsigned char flag;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(fd != NULL);
|
||||
|
||||
// First byte we read are the flags
|
||||
if( fread(&flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// See what we have:
|
||||
if( ! FLAG_VALID(flag) ) {
|
||||
DEBUGF("album_entry_unserialize: flag-byte is invalid\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
// Allocate memory
|
||||
*e = new_album_entry(0, 0);
|
||||
if( *e == NULL ) {
|
||||
DEBUGF("album_entry_unserialize: could not create new album_entry\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
(*e)->flag = flag; // we had a valid entry, copy it over
|
||||
|
||||
// First we read the length of the name field
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read name_len\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(*e, length, 0, 0) ) {
|
||||
DEBUGF("album_entry_unserialize: failed to allocate memory for name\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread((*e)->name, 1, (*e)->size.name_len, fd) != (*e)->size.name_len ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read name\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( FLAG_DELETED(flag) ) {
|
||||
// all there is... free some memory
|
||||
if( do_resize(*e, 0, 0, 0) ) {
|
||||
DEBUGF("album_entry_unserialize: couldn't free() name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// maybe a key-field
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read length of key\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( length > 0 ) {
|
||||
// allocate memory
|
||||
if( ((*e)->key = malloc(length)) == NULL ) {
|
||||
DEBUGF("album_entry_unserialize: failed to allocate memory for key\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it
|
||||
if( fread((*e)->key, 1, length, fd) != length ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read key\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// next the artist field
|
||||
if( fread(&(*e)->artist, sizeof((*e)->artist), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read artist\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Next the count of songs
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read song_count\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(*e, (*e)->size.name_len, length, 0) ) {
|
||||
DEBUGF("album_entry_unserialize: failed to allocate memory for song[]\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread((*e)->song, sizeof(*(*e)->song), (*e)->size.song_count, fd) != (*e)->size.song_count ) {
|
||||
DEBUGF("album_entry_unserialize: failed to read songs\n");
|
||||
album_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int album_entry_write(FILE *fd, struct album_entry *e, struct album_size *s) {
|
||||
uint32_t i, be;
|
||||
char pad = 0x00;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
assert(fd != NULL);
|
||||
|
||||
if( FLAG_DELETED(e->flag) ) { // we are deleted, do nothing
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// resize-write to size *s
|
||||
// First check if we are not reducing the size...
|
||||
if( s != NULL && ( s->name_len < e->size.name_len || s->song_count < e->size.song_count ) ) {
|
||||
// just do it in 2 steps
|
||||
if( do_resize(e, s->name_len, s->song_count, 0) ) {
|
||||
DEBUGF("album_entry_write: failed to reduce size of entry, failing...\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
}
|
||||
|
||||
// album name
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("album_entry_write: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// pad the rest
|
||||
i = e->size.name_len;
|
||||
while( s != NULL && s->name_len > i) {
|
||||
if( fwrite(&pad, 1, 1, fd) == 1 ) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
DEBUGF("album_entry_write: failed to pad name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// artist
|
||||
be = BE32(e->artist);
|
||||
if( fwrite(&be, sizeof(be), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_write: failed to write artist\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// song offsets, but in BIG ENDIAN!
|
||||
// so we need to iterate over each item to convert it
|
||||
for(i=0; i<e->size.song_count; i++) {
|
||||
be = BE32(e->song[i]);
|
||||
if( fwrite(&be, sizeof(be), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_write: failed to write song[%d]\n", i);
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
// pad the rest
|
||||
be = BE32(0x00000000);
|
||||
for(; s != NULL && i<s->song_count; i++) {
|
||||
if( fwrite(&be, sizeof(be), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_write: failed to pad song[]\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int album_entry_compare(const struct album_entry *a, const struct album_entry *b) {
|
||||
assert(a != NULL);
|
||||
assert(b != NULL);
|
||||
assert(a->key != NULL);
|
||||
assert(b->key != NULL);
|
||||
return strcasecmp(a->key, b->key);
|
||||
}
|
||||
|
||||
struct album_size* new_album_size() {
|
||||
struct album_size *s;
|
||||
s = (struct album_size*)malloc(sizeof(struct album_size));
|
||||
if( s == NULL ) {
|
||||
DEBUGF("new_album_size: failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
s->name_len = 0;
|
||||
s->song_count = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline uint32_t album_size_get_length(const struct album_size *size) {
|
||||
assert(size != NULL);
|
||||
return size->name_len + 4 + 4*size->song_count;
|
||||
}
|
||||
|
||||
inline int album_size_max(struct album_size *s, const struct album_entry *e) {
|
||||
assert(s != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
s->name_len = ( s->name_len >= e->size.name_len ? s->name_len : e->size.name_len );
|
||||
s->song_count = ( s->song_count >= e->size.song_count ? s->song_count : e->size.song_count );
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int album_size_destruct(struct album_size *s) {
|
||||
assert(s != NULL);
|
||||
// nothing to do...
|
||||
free(s);
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int album_entry_add_song_mem(struct album_entry *e, struct album_size *s, const uint32_t song) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
if( do_resize(e, e->size.name_len, e->size.song_count+1, 0) ) {
|
||||
DEBUGF("album_entry_add_song_mem: failed to resize song[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
e->song[e->size.song_count-1] = song;
|
||||
|
||||
if( s != NULL) album_size_max(s, e); // can't fail
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int delete_serialized(FILE *fd, struct album_entry *e) {
|
||||
// the entry should be both, in memory and in file at the current location
|
||||
// this function will mark the file-entry as deleted
|
||||
uint32_t size;
|
||||
unsigned char flag;
|
||||
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// overwrite the beginning of the serialized data:
|
||||
flag = FLAG(1, 0); // set the delete flag, clear the spare flags
|
||||
|
||||
// First byte we write is the flag-byte to indicate this is a deleted
|
||||
if( fwrite(&flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_delete_serialized: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Then we write the length of the COMPLETE entry
|
||||
size = album_size_get_length(&e->size) + 4; // 4 = overhead for the song[]
|
||||
if( fwrite(&size, sizeof(size), 1, fd) != 1 ) {
|
||||
DEBUGF("album_entry_delete_serialized: failed to write len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int album_entry_add_song_file(FILE *fd, struct album_entry *e, struct album_size *s, const uint32_t song) {
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
DEBUGF("album_entry_add_song_file() called\n");
|
||||
|
||||
if( delete_serialized(fd, e) ) {
|
||||
DEBUGF("album_entry_add_song_file: could not mark as deleted\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NO_INPLACE_UPDATE;
|
||||
}
|
103
apps/tagdb/album.h
Normal file
103
apps/tagdb/album.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
#ifndef __ALBUM_H__
|
||||
#define __ALBUM_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
|
||||
struct album_entry {
|
||||
char* name; // album name
|
||||
char* key; // key for sorting/searching: album___artist___directory
|
||||
uint32_t artist; // pointer to artist
|
||||
uint32_t *song; // song-pointers
|
||||
struct album_size {
|
||||
uint32_t name_len; // length of this field (must be mulitple of 4)
|
||||
uint32_t song_count; // number of song pointers
|
||||
} size; // keeps the size of this thing
|
||||
unsigned char flag; // flags
|
||||
};
|
||||
|
||||
struct album_entry* new_album_entry(const uint32_t name_len, const uint32_t song_count);
|
||||
/* Creates a new album_entry with the specified sizes
|
||||
* Returns a pointer to the structure on success,
|
||||
* NULL when malloc() fails
|
||||
*/
|
||||
|
||||
int album_entry_destruct(struct album_entry *e);
|
||||
/* Destructs the given album_entry and free()'s it's memory
|
||||
* returns ERR_NONE on success (can never fail)
|
||||
*/
|
||||
|
||||
inline int album_entry_resize(struct album_entry *e, const uint32_t name_len, const uint32_t song_count);
|
||||
/* Change the size of the entry
|
||||
* returns ERR_NONE on succes
|
||||
* ERR_MALLOC when malloc() fails
|
||||
*/
|
||||
|
||||
int album_entry_serialize(FILE *fd, const struct album_entry *e);
|
||||
/* Serializes the entry in the file at the current position
|
||||
* returns ERR_NONE on success
|
||||
* ERR_FILE on fwrite() failure
|
||||
*/
|
||||
|
||||
int album_entry_unserialize(struct album_entry* *e, FILE *fd);
|
||||
/* Unserializes an entry from file into a new structure
|
||||
* The address of the structure is saved into *e
|
||||
* returns ERR_NONE on success
|
||||
* ERR_MALLOC on malloc() failure
|
||||
* ERR_FILE on fread() failure
|
||||
*/
|
||||
|
||||
int album_entry_write(FILE *fd, struct album_entry *e, struct album_size *s);
|
||||
/* Writes the entry to file in the final form
|
||||
* returns ERR_NONE on success
|
||||
* ERR_FILE on fwrite() failure
|
||||
* ERR_MALLOC when e could not be resized due to malloc() problems
|
||||
* If s is smaller than e, s is used!!!
|
||||
*/
|
||||
|
||||
inline int album_entry_compare(const struct album_entry *a, const struct album_entry *b);
|
||||
/* Compares 2 entries
|
||||
* When a < b it returns <0
|
||||
* a = b 0
|
||||
* a > b >0
|
||||
*/
|
||||
|
||||
struct album_size* new_album_size();
|
||||
/* Creates a new size structure
|
||||
* returns a pointer to the structure on success,
|
||||
* NULL on malloc() failure
|
||||
*/
|
||||
|
||||
inline uint32_t album_size_get_length(const struct album_size *size);
|
||||
/* Calculates the length of the entry when written by album_entry_write()
|
||||
* returns the length on success (can never fail)
|
||||
*/
|
||||
|
||||
inline int album_size_max(struct album_size *s, const struct album_entry *e);
|
||||
/* Updates the album_size structure to contain the maximal lengths of either
|
||||
* the original entry in s, or the entry e
|
||||
* returns ERR_NONE on success (can never fail)
|
||||
*/
|
||||
|
||||
int album_size_destruct(struct album_size *s);
|
||||
/* destructs the album_size structure
|
||||
* returns ERR_NONE on success (can never fail)
|
||||
*/
|
||||
|
||||
|
||||
int album_entry_add_song_mem(struct album_entry *e, struct album_size *s, const uint32_t song);
|
||||
/* Adds the song to the array
|
||||
* returns ERR_NONE on success
|
||||
* ERR_MALLOC on malloc() failure
|
||||
*/
|
||||
|
||||
int album_entry_add_song_file(FILE *fd, struct album_entry *e, struct album_size *s, const uint32_t song);
|
||||
/* Adds the song to the serialized entry in the file
|
||||
* When this fails, the entry is invalidated and the function returns
|
||||
* ERR_NO_INPLACE_UPDATE
|
||||
* returns ERR_NONE on success
|
||||
* ERR_NO_INPLACE_UPDATE (see above)
|
||||
* ERR_FILE on fwrite() failure
|
||||
*/
|
||||
|
||||
#endif
|
667
apps/tagdb/array_buffer.c
Normal file
667
apps/tagdb/array_buffer.c
Normal file
|
@ -0,0 +1,667 @@
|
|||
#include "malloc.h" // malloc() and free()
|
||||
|
||||
#include "array_buffer.h"
|
||||
#include "unique.h"
|
||||
|
||||
static int add_mem(struct array_buffer *b, void *e);
|
||||
static int add_file(struct array_buffer *b, void *e);
|
||||
|
||||
static int update_entry_mem(struct array_buffer *b, const uint32_t index, uint32_t item);
|
||||
static int update_entry_file(struct array_buffer *b, const uint32_t index, uint32_t item);
|
||||
|
||||
static int find_entry_mem(struct array_buffer *b, const void *needle, uint32_t *index);
|
||||
static int find_entry_file(struct array_buffer *b, const void *needle, uint32_t *index);
|
||||
|
||||
static int sort_mem(struct array_buffer *b);
|
||||
static int sort_mem_merge_blocks(uint32_t *dest, uint32_t *s1, uint32_t s1_l, uint32_t *s2, uint32_t s2_l, struct array_buffer *b);
|
||||
static int sort_mem_merge(uint32_t *dest, uint32_t *src, struct array_buffer *b, uint32_t blocksize);
|
||||
static int sort_file(struct array_buffer *b);
|
||||
|
||||
struct array_buffer* new_array_buffer( int (*cmp)(const void *a, const void *b),
|
||||
int (*serialize)(FILE *fd, const void *e),
|
||||
int (*unserialize)(void **e, FILE *fd),
|
||||
uint32_t (*get_length)(const void *size),
|
||||
int (*write)(FILE *fd, void *e, const void *size),
|
||||
int (*destruct)(void *e),
|
||||
char* file_name,
|
||||
void* max_size,
|
||||
int (*max_size_update)(void *max_size, const void *e),
|
||||
int (*max_size_destruct)(void *max_size),
|
||||
int (*add_item_mem)(void *e, void *s, uint32_t item),
|
||||
int (*add_item_file)(FILE *fd, void *e, void *s, uint32_t item),
|
||||
int (*pre_write)(void *e, void *s)
|
||||
) {
|
||||
struct array_buffer *b;
|
||||
b = (struct array_buffer*)malloc(sizeof(struct array_buffer));
|
||||
if( b == NULL ) {
|
||||
DEBUGF("new_array_buffer: failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b->count = 0;
|
||||
b->array = NULL;
|
||||
b->sort = NULL;
|
||||
|
||||
b->file_name = file_name;
|
||||
|
||||
b->fd = NULL;
|
||||
|
||||
b->cmp = cmp;
|
||||
b->serialize = serialize;
|
||||
b->unserialize = unserialize;
|
||||
b->get_length = get_length;
|
||||
b->write = write;
|
||||
b->destruct = destruct;
|
||||
|
||||
b->max_size = max_size;
|
||||
b->max_size_update = max_size_update;
|
||||
b->max_size_destruct = max_size_destruct;
|
||||
|
||||
b->add_item_mem = add_item_mem;
|
||||
b->add_item_file = add_item_file;
|
||||
|
||||
b->pre_write = pre_write;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
int array_buffer_destruct(struct array_buffer *b, const int free_file_name) {
|
||||
assert(b != NULL);
|
||||
|
||||
if( b->fd == NULL ) {
|
||||
if( b->destruct == NULL ) {
|
||||
DEBUGF("array_buffer_destruct: no destruct() function registered\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
//we have memory to clean up
|
||||
// iterate over all stored objects:
|
||||
for(; b->count > 0; b->count--) {
|
||||
if( b->destruct(b->array[b->count-1].mem) ) {
|
||||
DEBUGF("array_buffer_destruct: failed to destruct item[%u]\n", b->count-1);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(b->array);
|
||||
|
||||
if( b->fd != NULL ) {
|
||||
// we have a file to clean up
|
||||
if( fclose(b->fd) != 0 ) {
|
||||
DEBUGF("array_buffer_destruct: fclose() failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
b->fd = NULL;
|
||||
|
||||
// remove that file
|
||||
if( remove(b->file_name) != 0 ) {
|
||||
DEBUGF("array_buffer_destruct: remove() failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
if( free_file_name ) {
|
||||
free(b->file_name);
|
||||
b->file_name = NULL;
|
||||
}
|
||||
|
||||
free(b->sort);
|
||||
b->sort = NULL;
|
||||
|
||||
// free the max_size
|
||||
if( b->max_size != NULL ) {
|
||||
if( b->max_size_destruct == NULL ) {
|
||||
DEBUGF("array_buffer_destruct: no max_size_destruct() function registered\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( b->max_size_destruct(b->max_size) ) {
|
||||
DEBUGF("array_buffer_destruct: failed to destruct max_size\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
b->max_size = NULL;
|
||||
}
|
||||
|
||||
free(b);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int array_buffer_switch_to_file(struct array_buffer *b) {
|
||||
uint32_t i;
|
||||
long offset;
|
||||
|
||||
assert(b != NULL);
|
||||
|
||||
if(b->file_name == NULL) {
|
||||
DEBUGF("array_buffer_switch_to_file: no file_name, failing...\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
if( b->fd != NULL ) {
|
||||
DEBUGF("array_buffer_switch_to_file: already in file, failing...\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// function calls exist?
|
||||
if( b->serialize == NULL || b->unserialize == NULL ) {
|
||||
DEBUGF("array_buffer_switch_to_file: serialize() and/or unserialize() function(s) not registered\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
// since we got here, we are VERY short on memory
|
||||
// We cannot do any memory allocation before free()ing some
|
||||
// The filename is already allocated in the constructor
|
||||
|
||||
// open the file
|
||||
b->fd = fopen(b->file_name, "w+");
|
||||
if( b->fd == NULL ) {
|
||||
DEBUGF("array_buffer_switch_to_file: failed to fopen() file\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
for(i=0; i<b->count; i++) {
|
||||
offset = ftell(b->fd);
|
||||
if( offset == -1 ) {
|
||||
DEBUGF("array_buffer_switch_to_file: ftell() failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( b->serialize(b->fd, b->array[i].mem) ) {
|
||||
DEBUGF("array_buffer_switch_to_file: serialize() failed on item[%u], ignoring...\n", i);
|
||||
}
|
||||
b->destruct(b->array[i].mem);
|
||||
|
||||
b->array[i].file_offset = offset;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int add_mem(struct array_buffer *b, void *e) {
|
||||
assert(b != NULL);
|
||||
assert(e != NULL);
|
||||
|
||||
// just copy over the pointer
|
||||
b->array[b->count].mem = e;
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int add_file(struct array_buffer *b, void *e) {
|
||||
int rc;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(e != NULL);
|
||||
|
||||
if( fseek(b->fd, 0, SEEK_END) != 0 ) {
|
||||
DEBUGF("add_file: could not seek to end of file\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
if(( b->array[b->count].file_offset = ftell(b->fd) ) == -1) {
|
||||
DEBUGF("add_file: ftell() failed to get file_offset\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if(( rc = b->serialize(b->fd, e) )) {
|
||||
DEBUGF("add_file: could not serialize entry\n");
|
||||
return rc;
|
||||
}
|
||||
if( b->destruct(e) ) {
|
||||
DEBUGF("add_file: could not destruct entry, ignoring... (memory leak)\n");
|
||||
}
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int array_buffer_add(struct array_buffer *b, void *e, uint32_t *index) {
|
||||
void* temp;
|
||||
int rc;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(e != NULL);
|
||||
|
||||
// allow the object to update the max_size
|
||||
// Do this first, so if it fails we can just return without cleanup to do
|
||||
if( b->max_size_update != NULL ) {
|
||||
if(( rc = b->max_size_update(b->max_size, e) )) {
|
||||
DEBUGF("array_buffer_add: could not update max_size, failing...\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
// we need to enlarge the array[]
|
||||
temp = (void*)realloc(b->array, sizeof(*b->array)*(b->count+1));
|
||||
while( temp == NULL ) {
|
||||
DEBUGF("array_buffer_add: failed to enlarge index_map[]. Switching to file\n");
|
||||
if(( rc = array_buffer_switch_to_file(b) )) {
|
||||
DEBUGF("array_buffer_add: failed to switch to file, failing...\n");
|
||||
return rc;
|
||||
}
|
||||
// now retry
|
||||
temp = (void*)realloc(b->array, sizeof(*b->array)*(b->count+1));
|
||||
}
|
||||
b->array = (union entry*)temp;
|
||||
|
||||
if( b->fd == NULL ) { // we are in memory
|
||||
rc = add_mem(b, e);
|
||||
if( rc == ERR_MALLOC ) {
|
||||
DEBUGF("array_buffer_add: failed to add in memory due to malloc() trouble, switching to file\n");
|
||||
if(( rc = array_buffer_switch_to_file(b) )) {
|
||||
DEBUGF("array_buffer_add: failed to switch to file, failing...\n");
|
||||
return rc;
|
||||
}
|
||||
// fall out and catch next if
|
||||
}
|
||||
} // NOT else, so we can catch the fall-through
|
||||
if( b->fd != NULL) {
|
||||
if(( rc = add_file(b, e) )) {
|
||||
DEBUGF("array_buffer_add: failed to add in file, failing...\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
// count and index-stuff
|
||||
if(index != NULL) *index = b->count;
|
||||
b->count++;
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline uint32_t array_buffer_get_next_index(struct array_buffer *b) {
|
||||
assert( b != NULL );
|
||||
return b->count;
|
||||
}
|
||||
|
||||
static int update_entry_mem(struct array_buffer *b, const uint32_t index, const uint32_t item) {
|
||||
int rc;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(index < b->count);
|
||||
|
||||
if( (rc = b->add_item_mem(b->array[index].mem, b->max_size, item)) ) {
|
||||
DEBUGF("update_entry_mem: failed to update entry\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int update_entry_file(struct array_buffer *b, const uint32_t index, uint32_t item) {
|
||||
/* uint32_t i, index;
|
||||
void *e;
|
||||
int rc;
|
||||
long prev_file_offset;*/
|
||||
|
||||
assert(b != NULL);
|
||||
assert(index < b->count);
|
||||
|
||||
printf("TODO: update entry in file\n");
|
||||
|
||||
return 10; // TODO
|
||||
/*
|
||||
rewind(b->fd);
|
||||
|
||||
rc = ERR_NOTFOUND;
|
||||
for(i=0; i<b->count; i++) {
|
||||
prev_file_offset = ftell(b->fd); // keep this file-position
|
||||
if( prev_file_offset == -1 ) {
|
||||
DEBUGF("file_entry_add_file: ftell() failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( (rc = b->unserialize(&e, b->fd)) ) {
|
||||
DEBUGF("find_entry_add_file: unserialize failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( b->cmp(e, needle) == 0 ) { // found
|
||||
if( fseek(b->fd, prev_file_offset, SEEK_SET) ) {
|
||||
DEBUGF("file_entry_add_file: fseek() to entry[%u] failed\n", i);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
rc = b->add_item_file(b->fd, e, b->max_size, item);
|
||||
if( !( rc == ERR_NONE || rc == ERR_NO_INPLACE_UPDATE )) {
|
||||
DEBUGF("find_entry_add_mem: failed to add item\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
break; // stop looping
|
||||
}
|
||||
|
||||
b->destruct(e);
|
||||
}
|
||||
|
||||
// seek to the end
|
||||
if( fseek(b->fd, 0, SEEK_END) != 0) {
|
||||
DEBUGF("find_entry_add_file: fseek(SEEK_END) failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// We either succeded, deleted the entry or didn't find it:
|
||||
if( rc == ERR_NOTFOUND ) {
|
||||
return rc; // quit
|
||||
} else if( rc == ERR_NONE ) {
|
||||
b->destruct(e); // delete the entry and quit
|
||||
return rc;
|
||||
}
|
||||
|
||||
// we could not update inplace
|
||||
// the entry is deleted, update it and add it again
|
||||
if( (rc = b->add_item_mem(e, b->max_size, item)) ) {
|
||||
DEBUGF("find_entry_add_file: failed to add item in mem\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( (rc = array_buffer_add(b, e, &index) ) ) {
|
||||
DEBUGF("find_entry_add_file: failed to re-add item to array");
|
||||
return rc;
|
||||
}
|
||||
|
||||
// the entry is now re-added, but with another index number...
|
||||
// change the index_map to reflect this:
|
||||
b->index_map[i] = index;
|
||||
|
||||
return ERR_NONE;*/
|
||||
}
|
||||
|
||||
int array_buffer_entry_update(struct array_buffer *b, const uint32_t index, uint32_t item) {
|
||||
assert(b != NULL);
|
||||
|
||||
if(index >= b->count) {
|
||||
DEBUGF("array_buffer_entry_update: index out of bounds\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
if( b->fd == NULL ) {
|
||||
return update_entry_mem(b, index, item);
|
||||
} else {
|
||||
return update_entry_file(b, index, item);
|
||||
}
|
||||
}
|
||||
|
||||
static int find_entry_mem(struct array_buffer *b, const void *needle, uint32_t *index) {
|
||||
uint32_t i;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(needle != NULL);
|
||||
assert(index != NULL);
|
||||
|
||||
for(i=0; i<b->count; i++) {
|
||||
if( b->cmp(b->array[i].mem, needle) == 0 ) { // found
|
||||
*index = i;
|
||||
return ERR_NONE;
|
||||
}
|
||||
}
|
||||
return ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
static int find_entry_file(struct array_buffer *b, const void *needle, uint32_t *index) {
|
||||
uint32_t i;
|
||||
void *e;
|
||||
int rc;
|
||||
long prev_file_offset;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(needle != NULL);
|
||||
assert(index != NULL);
|
||||
|
||||
// We do this search in the order of the entries in file.
|
||||
// After we found one, we look for the index of that offset
|
||||
// (in memory).
|
||||
// This will (PROBABELY: TODO) be faster than random-access the file
|
||||
rewind(b->fd);
|
||||
|
||||
for(i=0; i<b->count; i++) {
|
||||
prev_file_offset = ftell(b->fd); // keep this file-position
|
||||
if( prev_file_offset == -1 ) {
|
||||
DEBUGF("file_entry_add_file: ftell() failed\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( (rc = b->unserialize(&e, b->fd)) ) {
|
||||
DEBUGF("find_entry_add_file: unserialize failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if( b->cmp(e, needle) == 0 ) { // found
|
||||
if( fseek(b->fd, prev_file_offset, SEEK_SET) ) {
|
||||
DEBUGF("file_entry_add_file: fseek() to entry[%u] failed\n", i);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
b->destruct(e);
|
||||
break; // out of the for() loop
|
||||
}
|
||||
|
||||
b->destruct(e);
|
||||
}
|
||||
|
||||
if( i == b->count ) {
|
||||
// we didn't find anything
|
||||
return ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
// we found an entry, look for the index number of that offset:
|
||||
for(i=0; i<b->count; i++) {
|
||||
if(prev_file_offset == b->array[i].file_offset) {
|
||||
// found
|
||||
*index = i;
|
||||
return ERR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// we should never get here
|
||||
DEBUGF("find_entry_file: found entry in file, but doens't match an index\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
int array_buffer_find_entry(struct array_buffer *b, const void *needle, uint32_t *index) {
|
||||
assert(b != NULL);
|
||||
assert(needle != NULL);
|
||||
assert(index != NULL); // TODO: if it is null, do the search but trash the index
|
||||
|
||||
if( b->fd == NULL ) {
|
||||
return find_entry_mem(b, needle, index);
|
||||
} else {
|
||||
return find_entry_file(b, needle, index);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static int sort_mem_merge_blocks(uint32_t *dest, const uint32_t *s1, const uint32_t s1_l, const uint32_t *s2, const uint32_t s2_l, struct array_buffer *b) {
|
||||
// merges the 2 blocks at s1 (with s1_l items) and s2 (with s2_l items)
|
||||
// together in dest
|
||||
uint32_t *s1_max, s2_max;
|
||||
|
||||
#define CMP(a, b) b->cmp( b->entry[a].mem, b->entry[b].mem )
|
||||
|
||||
s1_max = s1 + s1_l;
|
||||
s2_max = s2 + s2_l;
|
||||
while( s1 < s1_max || s2 < s2_max ) {
|
||||
while( s1 < s1_max && ( s2 == s2_max || CMP(s1, s2) <= 0 ) ) // s1 is smaller than s2 (or s2 is used up)
|
||||
*(dest++) = s1++; // copy and move to next
|
||||
while( s2 < s2_max && ( s1 == s1_max || CMP(s1, s2) > 0 ) ) // s2 smaller
|
||||
*(dest++) = s2++;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
#define MIN(a, b) ( (a) <= (b) ? (a) : (b) )
|
||||
static int sort_mem_merge(uint32_t *dest, uint32_t *src, struct array_buffer *b, uint32_t blocksize) {
|
||||
// does 1 merge from src[] into dest[]
|
||||
// asumes there are sorted blocks in src[] of size blocksize
|
||||
assert( dest != NULL);
|
||||
assert( src != NULL );
|
||||
|
||||
assert( b->count > blocksize );
|
||||
|
||||
// TODO
|
||||
}
|
||||
*/
|
||||
|
||||
static int sort_mem(struct array_buffer *b) {
|
||||
uint32_t *tmp, blocksize;
|
||||
|
||||
assert(b != NULL);
|
||||
|
||||
tmp = (uint32_t*)malloc(sizeof(uint32_t)*b->count);
|
||||
if( tmp == NULL ) {
|
||||
DEBUGF("sort_mem: could not malloc() for second sort[] array\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
for( blocksize = 1; blocksize < b->count; blocksize++) {
|
||||
b->sort[blocksize] = blocksize; // 1-1 map TODO
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int sort_file(struct array_buffer *b) {
|
||||
printf("TODO: file-sorting\n"); // TODO
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
int array_buffer_sort(struct array_buffer *b) {
|
||||
int rc;
|
||||
|
||||
assert(b != NULL);
|
||||
|
||||
b->sort = (uint32_t*)malloc(sizeof(uint32_t)*b->count);
|
||||
if( b->sort == NULL ) {
|
||||
DEBUGF("array_buffer_sort: could not malloc() sort[] array\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
if( b->fd == NULL ) { // in memory
|
||||
rc = sort_mem(b);
|
||||
if( rc == ERR_MALLOC ) {
|
||||
if(( rc = array_buffer_switch_to_file(b) )) {
|
||||
DEBUGF("array_buffer_sort: could not switch to file mode\n");
|
||||
return rc;
|
||||
}
|
||||
return sort_file(b);
|
||||
} else if( rc ) {
|
||||
DEBUGF("array_buffer_sort: could not sort array\n");
|
||||
return rc;
|
||||
}
|
||||
return ERR_NONE;
|
||||
} else {
|
||||
return sort_file(b);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t array_buffer_get_offset(struct array_buffer *b, const uint32_t index) {
|
||||
uint32_t offset;
|
||||
|
||||
assert(b != NULL);
|
||||
|
||||
if( index >= b->count ) {
|
||||
DEBUGF("array_buffer_get_offset: index out of bounds\n");
|
||||
return (uint32_t)0xffffffff;
|
||||
}
|
||||
|
||||
// what is the (max) length of 1 item
|
||||
if( b->get_length == NULL ) {
|
||||
DEBUGF("array_buffer_get_offset: get_length() function not registered\n");
|
||||
return (uint32_t)0xffffffff;
|
||||
}
|
||||
offset = b->get_length(b->max_size);
|
||||
|
||||
// multiply that by the number of items before me
|
||||
if( b->sort == NULL ) { // easy, we are unsorted
|
||||
offset *= index;
|
||||
} else {
|
||||
uint32_t i;
|
||||
for(i=0; i<b->count; i++) {
|
||||
if( b->sort[i] == index )
|
||||
break;
|
||||
}
|
||||
if( i == b->count ) {
|
||||
DEBUGF("array_buffer_get_offset: index does not appeat in sorted list\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
offset *= i; // that many items are before me
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint32_t array_buffer_get_length(struct array_buffer *b) {
|
||||
uint32_t length;
|
||||
|
||||
assert(b != NULL);
|
||||
|
||||
// what is the (max) length of 1 item
|
||||
if( b->get_length == NULL ) {
|
||||
DEBUGF("array_buffer_get_offset: get_length() function not registered\n");
|
||||
return (uint32_t)0xffffffff;
|
||||
}
|
||||
length = b->get_length(b->max_size);
|
||||
|
||||
// multiply that by the number of items
|
||||
length *= b->count;
|
||||
return length;
|
||||
}
|
||||
|
||||
int array_buffer_write(FILE *fd, struct array_buffer *b) {
|
||||
uint32_t i;
|
||||
int rc;
|
||||
|
||||
assert(b != NULL);
|
||||
assert(fd != NULL);
|
||||
|
||||
// check if the functions exist
|
||||
if( b->write == NULL ) {
|
||||
DEBUGF("array_buffer_write: write() function not registered\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
// if the array is in file
|
||||
// serialize and unserialize will exist, since they're checked
|
||||
// in the array_buffer_switch_to_file()
|
||||
|
||||
if( b->fd != NULL ) {
|
||||
rewind(b->fd); // seek to the beginning
|
||||
}
|
||||
|
||||
for(i=0; i<b->count; i++) { // for each element
|
||||
void* item;
|
||||
uint32_t j;
|
||||
|
||||
// go through the sort-array and see which item should go next
|
||||
if(b->sort != NULL) {
|
||||
j = b->sort[i];
|
||||
} else j = i;
|
||||
|
||||
// get the item in memory
|
||||
if( b->fd == NULL ) { // it already is im memory, fetch the pointer
|
||||
item = b->array[j].mem;
|
||||
} else {
|
||||
// since it's sorted, we shouldn't have to seek
|
||||
if( (rc = b->unserialize(&item, b->fd)) ) {
|
||||
DEBUGF("array_buffer_write: could not unserialize item[%u], failing...\n", i);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
if(b->pre_write != NULL && ( rc = b->pre_write(item, b->max_size) )) {
|
||||
DEBUGF("array_buffer_write: pre_write function failed, failing...\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
// write item to file
|
||||
if(( rc = b->write(fd, item, b->max_size) )) {
|
||||
DEBUGF("array_buffer_write: could not write item[%u], failing...\n", i);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// put it back where it came from
|
||||
if( b->fd != NULL ) {
|
||||
b->destruct(item);
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
159
apps/tagdb/array_buffer.h
Normal file
159
apps/tagdb/array_buffer.h
Normal file
|
@ -0,0 +1,159 @@
|
|||
#ifndef __ARRAY_BUFFER_H__
|
||||
#define __ARRAY_BUFFER_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct array_buffer {
|
||||
uint32_t count; // how much items doe we have?
|
||||
|
||||
union entry {
|
||||
void* mem;
|
||||
long file_offset;
|
||||
} *array; // where is the data?
|
||||
// This array will always point to the same data
|
||||
// after sorting the position of the data may be canged
|
||||
// but this array will also be canged accordingly
|
||||
|
||||
uint32_t *sort; // In what order should we put the entries on disk?
|
||||
|
||||
char* file_name; // filename
|
||||
FILE *fd; // file where entries are being kept. (NULL if in mem)
|
||||
|
||||
int (*cmp)(const void *a, const void *b); // compare a to b, should return:
|
||||
// a < b ==> <0
|
||||
// a = b ==> 0
|
||||
// a > b ==> >0
|
||||
|
||||
int (*serialize)(FILE *fd, const void *e); // serialize e into fd
|
||||
int (*unserialize)(void **e, FILE *fd); // unserialize the entry in fd
|
||||
|
||||
uint32_t (*get_length)(const void *size); // get's the length
|
||||
int (*write)(FILE *fd, void *e, const void *size); // write e to file
|
||||
|
||||
int (*destruct)(void *e); // destruct object
|
||||
|
||||
void *max_size; // keep the current maximal size
|
||||
int (*max_size_update)(void *max_size, const void *e); // update the max_size
|
||||
int (*max_size_destruct)(void *max_size); // destruct the size-object
|
||||
|
||||
int (*add_item_mem)(void *e, void *s, uint32_t item);
|
||||
int (*add_item_file)(FILE *fd, void *e, void *s, uint32_t item);
|
||||
|
||||
int (*pre_write)(void *e, void *s); // do whatever you want, just before the entry is wrtiiten
|
||||
};
|
||||
|
||||
struct array_buffer* new_array_buffer( int (*cmp)(const void *a, const void *b),
|
||||
int (*serialize)(FILE *fd, const void *e),
|
||||
int (*unserialize)(void **e, FILE *fd),
|
||||
uint32_t (*get_length)(const void *size),
|
||||
int (*write)(FILE *fd, void *e, const void *size),
|
||||
int (*destruct)(void *e),
|
||||
char* file_name,
|
||||
void* max_size,
|
||||
int (*max_size_update)(void *max_size, const void *e),
|
||||
int (*max_size_destruct)(void *max_size),
|
||||
int (*add_item_mem)(void *e, void *s, uint32_t item),
|
||||
int (*add_item_file)(FILE *fd, void *e, void *s, uint32_t item),
|
||||
int (*pre_write)(void *e, void *s)
|
||||
);
|
||||
/* This makes a new array_buffer
|
||||
* - cmp() is the compare function used to sort: after sort cmp(item[i], item[i+1])<=0
|
||||
* - serialize() should put the entry into the file at the current location, return 0 on success
|
||||
* - unserialize() should read an entry from file and return the entry in memory.
|
||||
* return 0 on success, 1 on malloc() failures, 2 on fread() errors,
|
||||
* anything else on other errors
|
||||
* - get_length() calculates the length of the entry as it will be written by write()
|
||||
* - write() should write the entry to file in it's final format
|
||||
* - destruct() should free all memory assigned to e (including e itself)
|
||||
*
|
||||
* - file_name should contain a filename that can be used as extra storage if needed
|
||||
* if malloc()'s fail, the array is automaticaly converted to file-mode
|
||||
* and array_buffer retries the operation.
|
||||
* by not setting file_name=NULL malloc() failures will result in call
|
||||
* failures
|
||||
*
|
||||
* - max_size may be an object to record the maximal size \
|
||||
* - max_size_update() will be called on each add() to update the max_size-structure | may be NULL
|
||||
* - max_size_destroy() should destroy the given max_size object /
|
||||
*
|
||||
* - add_item_mem() add item to the entry when it is in memory (may be NULL)
|
||||
* - add_item_file() add item to the serialized entry at the current file position.
|
||||
* the entry itself is also given in e for convenience.
|
||||
* If the add cannot be done in-place the function should
|
||||
* - invalidate the serialized entry
|
||||
* - return ERR_NO_INPLACE_UPDATE
|
||||
* The add will be done in memory and re-added to the end of the
|
||||
* array (mey be NULL)
|
||||
* both functions must update the s-structure to reflect the maximal entry
|
||||
*
|
||||
* - pre_write() is called right before the entry is written to disk in the write() call (may be NULL)
|
||||
*
|
||||
* It returns that buffer on succes, NULL otherwise
|
||||
* NULL indicates a memory-allocation failure
|
||||
*/
|
||||
|
||||
int array_buffer_destruct(struct array_buffer *b, const int free_file_name);
|
||||
/* Destructs the buffer:
|
||||
* - destructs all containing elements using the supplied destruct() function
|
||||
* - free()'s all allocations
|
||||
* - optionaly free()'s the file_name
|
||||
* - free()'s b itself
|
||||
*/
|
||||
|
||||
int array_buffer_switch_to_file(struct array_buffer *b);
|
||||
/* Asks the buffer to switch to file mode
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
inline uint32_t array_buffer_get_next_index(struct array_buffer *b);
|
||||
/* Returns the index that will be given to the next added entry
|
||||
*/
|
||||
|
||||
int array_buffer_add(struct array_buffer *b, void *e, uint32_t *index);
|
||||
/* Adds entry e to the buffer.
|
||||
* If index!=NULL *index will contain a unique number for the entry
|
||||
*
|
||||
* Returns 0 on succes, 1 otherwise
|
||||
* Once an entry is added, the caller should not use the pointer (e) anymore,
|
||||
* since array_buffer may swap the entry out to file
|
||||
*/
|
||||
|
||||
int array_buffer_entry_update(struct array_buffer *b, const uint32_t index, uint32_t item);
|
||||
/* Updates entry index with item, either in memory or in file, depending on the current
|
||||
* state of the array
|
||||
* Returns ERR_NONE on success
|
||||
* ERR_MALLOC on malloc() failure
|
||||
* ERR_FILE on fread(), fwrite(), fseek() problems
|
||||
*/
|
||||
|
||||
int array_buffer_find_entry(struct array_buffer *b, const void *needle, uint32_t *index);
|
||||
/* This looks for an entry that is equal to needle (i.e. that cmp(e, needle) returns 0)
|
||||
* Returns ERR_NONE on success (the entry is found)
|
||||
* ERR_NOTFOUNF when needle was not found,
|
||||
* ERR_MALLOC on malloc() failure
|
||||
* ERR_FILE on fread(), fwrite() of other file() failures
|
||||
*/
|
||||
|
||||
int array_buffer_sort(struct array_buffer *b);
|
||||
/*
|
||||
*/
|
||||
|
||||
uint32_t array_buffer_get_offset(struct array_buffer *b, const uint32_t index);
|
||||
/* Returns the offset of item[index] when it would be written by the
|
||||
* array_buffer_write() call.
|
||||
* Useful to get offsets after sorting!
|
||||
*/
|
||||
|
||||
uint32_t array_buffer_get_length(struct array_buffer *b);
|
||||
/* Returns the total number of bytes array_buffer_write()
|
||||
* would write to the file
|
||||
*/
|
||||
|
||||
int array_buffer_write(FILE *fd, struct array_buffer *b);
|
||||
/* Iterate over each element and write it to file
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
#endif
|
370
apps/tagdb/artist.c
Normal file
370
apps/tagdb/artist.c
Normal file
|
@ -0,0 +1,370 @@
|
|||
#include "malloc.h" // realloc() and free()
|
||||
#include <string.h> // strncasecmp()
|
||||
|
||||
#include "artist.h"
|
||||
|
||||
// how is our flag organized?
|
||||
#define FLAG(deleted, spare) ( 0xC0 | (deleted?0x10:0x00) | (spare & 0x0F) )
|
||||
#define FLAG_VALID(flag) ((flag & 0xE0) == 0xC0)
|
||||
#define FLAG_DELETED(flag) (flag & 0x10)
|
||||
#define FLAG_SPARE(flag) (flag & 0x0F)
|
||||
|
||||
static int do_resize(struct artist_entry *e, const uint32_t name_len, const uint32_t album_count, const int zero_fill);
|
||||
|
||||
struct artist_entry* new_artist_entry(const uint32_t name_len, const uint32_t album_count) {
|
||||
// start by allocating memory
|
||||
struct artist_entry *e = (struct artist_entry*)malloc(sizeof(struct artist_entry));
|
||||
if( e == NULL ) {
|
||||
DEBUGF("new_artist_entry: could not allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We begin empty
|
||||
e->name = NULL;
|
||||
e->size.name_len = 0;
|
||||
e->album = NULL;
|
||||
e->size.album_count = 0;
|
||||
e->flag = FLAG(0, 0);
|
||||
|
||||
// and resize to the requested size
|
||||
if( do_resize(e, name_len, album_count, 1) ) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
int artist_entry_destruct(struct artist_entry *e) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
free(e->name);
|
||||
free(e->album);
|
||||
|
||||
free(e);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_resize(struct artist_entry *e, const uint32_t name_len, const uint32_t album_count, const int zero_fill) {
|
||||
void* temp;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// begin with name
|
||||
if( name_len != e->size.name_len ) {
|
||||
temp = realloc(e->name, name_len);
|
||||
if(temp == NULL && name_len > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("artist_entry_resize: out of memory to resize name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->name = (char*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.name_len; i<name_len; i++)
|
||||
e->name[i] = (char)0x00;
|
||||
}
|
||||
|
||||
e->size.name_len = name_len;
|
||||
}
|
||||
|
||||
// now the album
|
||||
if( album_count != e->size.album_count ) {
|
||||
temp = realloc(e->album, album_count * sizeof(*e->album));
|
||||
if(temp == NULL && album_count > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("artist_entry_resize: out of memory to resize album[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->album = (uint32_t*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.album_count; i<album_count; i++)
|
||||
e->album[i] = (uint32_t)0x00000000;
|
||||
}
|
||||
|
||||
e->size.album_count = album_count;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_entry_resize(struct artist_entry *e, const uint32_t name_len, const uint32_t album_count) {
|
||||
return do_resize(e, name_len, album_count, 1);
|
||||
}
|
||||
|
||||
int artist_entry_serialize(FILE *fd, const struct artist_entry *e) {
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
if( FLAG_DELETED(e->flag) ) { // we are deleted, do nothing
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// First byte we write is a flag-byte to indicate this is a valid record
|
||||
if( fwrite(&e->flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_serialize: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// First we write the length of the name field
|
||||
if( fwrite(&e->size.name_len, sizeof(e->size.name_len), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_serialize: failed to write name_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// now the name field itself
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("artist_entry_serialize: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// count of albums
|
||||
if( fwrite(&e->size.album_count, sizeof(e->size.album_count), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_serialize: failed to write album_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// album[] itself
|
||||
if( fwrite(e->album, sizeof(*e->album), e->size.album_count, fd) != e->size.album_count ) {
|
||||
DEBUGF("artist_entry_serialize: failed to write albums\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_entry_unserialize(struct artist_entry **e, FILE *fd) {
|
||||
uint32_t length;
|
||||
unsigned char flag;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(fd != NULL);
|
||||
|
||||
// First byte we read is flag-byte
|
||||
if( fread(&flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to read flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// See what we have:
|
||||
if( ! FLAG_VALID(flag) ) {
|
||||
DEBUGF("artist_entry_unserialize: flag-byte not found\n");
|
||||
return ERR_INVALID;
|
||||
}
|
||||
|
||||
// Allocate memory
|
||||
*e = new_artist_entry(0, 0);
|
||||
if( *e == NULL ) {
|
||||
DEBUGF("artist_entry_unserialize: could not create new artist_entry\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
(*e)->flag = flag;
|
||||
|
||||
// First we read the length of the name field
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to read name_len\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize((*e), length, 0, 0) ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to allocate memory for name\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread((*e)->name, 1, (*e)->size.name_len, fd) != (*e)->size.name_len ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to read name\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( FLAG_DELETED(flag) ) {
|
||||
// all there is... free some memory
|
||||
if( do_resize(*e, 0, 0, 0) ) {
|
||||
DEBUGF("artist_entry_unserialize: couldn't free() name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// Next the count of albums
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to read album_count\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(*e, (*e)->size.name_len, length, 0) ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to allocate memory for album[]\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread((*e)->album, sizeof(*(*e)->album), (*e)->size.album_count, fd) != (*e)->size.album_count ) {
|
||||
DEBUGF("artist_entry_unserialize: failed to read albums\n");
|
||||
artist_entry_destruct(*e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_entry_write(FILE *fd, const struct artist_entry *e, const struct artist_size *s) {
|
||||
uint32_t i, be;
|
||||
char pad = 0x00;
|
||||
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
if( FLAG_DELETED(e->flag) ) { // we are deleted, do nothing
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// artist name
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("artist_entry_write: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// padd the rest
|
||||
i = e->size.name_len;
|
||||
while( s != NULL && s->name_len > i) {
|
||||
if( fwrite(&pad, 1, 1, fd) == 1 ) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
DEBUGF("artist_entry_write: failed to padd name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// album offsets, but in BIG ENDIAN!
|
||||
// so we need to iterate over each item to convert it
|
||||
for(i=0; i<e->size.album_count; i++) {
|
||||
be = BE32(e->album[i]);
|
||||
if( fwrite(&be, sizeof(be), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_write: failed to write album[%d]\n", i);
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
// padd the rest
|
||||
be = BE32(0x00000000);
|
||||
for(; s != NULL && i<s->album_count; i++) {
|
||||
if( fwrite(&be, sizeof(be), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_write: failed to padd album[]\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int artist_entry_compare(const struct artist_entry *a, const struct artist_entry *b) {
|
||||
assert(a != NULL);
|
||||
assert(b != NULL);
|
||||
if( a->name == NULL || b->name == NULL )
|
||||
return 1; // never match on no-names
|
||||
return strcasecmp(a->name, b->name);
|
||||
}
|
||||
|
||||
struct artist_size* new_artist_size() {
|
||||
struct artist_size *s;
|
||||
s = (struct artist_size*)malloc(sizeof(struct artist_size));
|
||||
if( s == NULL ) {
|
||||
DEBUGF("new_artist_size: failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
s->name_len = 0;
|
||||
s->album_count = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline uint32_t artist_size_get_length(const struct artist_size *size) {
|
||||
assert(size != NULL);
|
||||
return size->name_len + 4*size->album_count;
|
||||
}
|
||||
|
||||
inline int artist_size_max(struct artist_size *s, const struct artist_entry *e) {
|
||||
s->name_len = ( s->name_len >= e->size.name_len ? s->name_len : e->size.name_len );
|
||||
s->album_count = ( s->album_count >= e->size.album_count ? s->album_count : e->size.album_count );
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_size_destruct(struct artist_size *s) {
|
||||
assert(s != NULL);
|
||||
// nothing to do...
|
||||
free(s);
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_entry_add_album_mem(struct artist_entry *e, struct artist_size *s, const uint32_t album) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
if( do_resize(e, e->size.name_len, e->size.album_count+1, 0) ) {
|
||||
DEBUGF("artist_entry_add_song_mem: failed to resize album[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
e->album[e->size.album_count-1] = album;
|
||||
|
||||
if(s != NULL) artist_size_max(s, e); // can't fail
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int delete_serialized(FILE *fd, struct artist_entry *e) {
|
||||
// the entry should be both, in memory and in file at the current location
|
||||
// this function will mark the file-entry as deleted
|
||||
uint32_t size;
|
||||
unsigned char flag;
|
||||
// overwrite the beginning of the serialized data:
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
flag = FLAG(1, 0); // mark as deleted
|
||||
|
||||
// First byte we write is the flag-byte to indicate this is a deleted
|
||||
if( fwrite(&flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_delete_serialized: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Then we write the length of the COMPLETE entry
|
||||
size = artist_size_get_length(&e->size) + 4; // 4 = overhead for the album[]
|
||||
if( fwrite(&size, sizeof(size), 1, fd) != 1 ) {
|
||||
DEBUGF("artist_entry_delete_serialized: failed to write len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int artist_entry_add_album_file(FILE *fd, struct artist_entry *e, struct artist_size *s, const uint32_t album) {
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
DEBUGF("artist_entry_add_song_file() called\n");
|
||||
|
||||
if( delete_serialized(fd, e) ) {
|
||||
DEBUGF("artist_entry_add_album_file: could not mark as deleted\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NO_INPLACE_UPDATE;
|
||||
}
|
100
apps/tagdb/artist.h
Normal file
100
apps/tagdb/artist.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
#ifndef __ARTIST_H__
|
||||
#define __ARTIST_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct artist_entry {
|
||||
char* name; // artist name
|
||||
uint32_t *album; // album-pointers
|
||||
struct artist_size {
|
||||
uint32_t name_len; // length of this field (must be mulitple of 4)
|
||||
uint32_t album_count; // number of album pointers
|
||||
} size;
|
||||
unsigned char flag; // flags
|
||||
};
|
||||
|
||||
struct artist_entry* new_artist_entry(const uint32_t name_len, const uint32_t album_count);
|
||||
/* Creates a new artist_entry with the specified sizes
|
||||
* Returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
int artist_entry_destruct(struct artist_entry *e);
|
||||
/* Destructs the given artist_entry and free()'s it's memory
|
||||
* returns ERR_NONE on success (can't fail)
|
||||
*/
|
||||
|
||||
int artist_entry_resize(struct artist_entry *e, const uint32_t name_len, const uint32_t album_count);
|
||||
/* Change the size of the entry
|
||||
* returns ERR_NONE on succes
|
||||
* ERR_MALLOC on malloc() failure
|
||||
*/
|
||||
|
||||
int artist_entry_serialize(FILE *fd, const struct artist_entry *e);
|
||||
/* Serializes the entry in the file at the current position
|
||||
* returns ERR_NONE on success
|
||||
* ERR_FILE on fwrite() failure
|
||||
*/
|
||||
|
||||
int artist_entry_unserialize(struct artist_entry* *e, FILE *fd);
|
||||
/* Unserializes an entry from file into a new structure
|
||||
* The address of the structure is saved into *e
|
||||
* returns ERR_NONE on success
|
||||
* ERR_MALLOC on malloc() failure
|
||||
* ERR_FILE on fread() failure
|
||||
*/
|
||||
|
||||
int artist_entry_write(FILE *fd, const struct artist_entry *e, const struct artist_size *s);
|
||||
/* Writes the entry to file in the final form
|
||||
* returns ERR_NONE on success
|
||||
* ERR_FILE on fwrite() failure
|
||||
*/
|
||||
|
||||
inline int artist_entry_compare(const struct artist_entry *a, const struct artist_entry *b);
|
||||
/* Compares 2 entries
|
||||
* When a < b it returns <0
|
||||
* a = b 0
|
||||
* a > b >0
|
||||
*/
|
||||
|
||||
struct artist_size* new_artist_size();
|
||||
/* Creates a new size structure
|
||||
* returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
inline uint32_t artist_size_get_length(const struct artist_size *size);
|
||||
/* Calculates the length of the entry when written by artist_entry_write()
|
||||
* returns the length on success (can't fail)
|
||||
*/
|
||||
|
||||
inline int artist_size_max(struct artist_size *s, const struct artist_entry *e);
|
||||
/* Updates the artist_size structure to contain the maximal lengths of either
|
||||
* the original entry in s, or the entry e
|
||||
* returns ERR_NONE on success (can't fail)
|
||||
*/
|
||||
|
||||
int artist_size_destruct(struct artist_size *s);
|
||||
/* destructs the artist_size structure
|
||||
* returns ERR_NONE on success (can't fail)
|
||||
*/
|
||||
|
||||
|
||||
int artist_entry_add_album_mem(struct artist_entry *e, struct artist_size *s, const uint32_t album);
|
||||
/* Adds the album to the array
|
||||
* returns ERR_NONE on success
|
||||
* ERR_MALLOC on malloc() failure
|
||||
*/
|
||||
|
||||
int artist_entry_add_album_file(FILE *fd, struct artist_entry *e, struct artist_size *s, const uint32_t album);
|
||||
/* Adds the album to the serialized entry in the file
|
||||
* When this fails, the entry is invalidated and the function returns
|
||||
* ERR_NO_INPLACE_UPDATE
|
||||
* returns ERR_NONE on success
|
||||
* ERR_NO_INPLACE_UPDATE (see above)
|
||||
* ERR_FILE on fread()/fwrite() error
|
||||
*/
|
||||
|
||||
#endif
|
39
apps/tagdb/config.h
Normal file
39
apps/tagdb/config.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef __CONFIG_H // Include me only once
|
||||
#define __CONFIG_H
|
||||
|
||||
// DEBUGF will print in debug mode:
|
||||
#ifdef DEBUG
|
||||
#define DEBUGF(...) fprintf (stderr, __VA_ARGS__)
|
||||
#define DEBUGT(...) fprintf (stdout, __VA_ARGS__)
|
||||
#else //DEBUG
|
||||
#define DEBUGF(...)
|
||||
#endif //DEBUG
|
||||
|
||||
|
||||
#define OS_LINUX // architecture: LINUX, ROCKBOX, WINDOWS
|
||||
#define ROCKBOX_LITTLE_ENDIAN // we are intel... little-endian
|
||||
|
||||
|
||||
#ifdef ROCKBOX_LITTLE_ENDIAN
|
||||
#define BE32(_x_) ((( (_x_) & 0xff000000) >> 24) | \
|
||||
(( (_x_) & 0x00ff0000) >> 8) | \
|
||||
(( (_x_) & 0x0000ff00) << 8) | \
|
||||
(( (_x_) & 0x000000ff) << 24))
|
||||
#define BE16(_x_) ( (( (_x_) & 0xff00) >> 8) | (( (_x_) & 0x00ff) << 8))
|
||||
#else
|
||||
#define BE32(_x_) _x_
|
||||
#define BE16(_x_) _x_
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ERR_NONE 0 // no error
|
||||
#define ERR_NOTFOUND -1 // entry not found
|
||||
#define ERR_MALLOC 1 // memory allocation failed
|
||||
#define ERR_FILE 2 // file operation failed
|
||||
#define ERR_INVALID 3 // something is invalid
|
||||
#define ERR_NO_INPLACE_UPDATE 4 // can't update in this place
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#endif
|
603
apps/tagdb/db.c
Normal file
603
apps/tagdb/db.c
Normal file
|
@ -0,0 +1,603 @@
|
|||
#include <string.h> // strlen() strcpy() strcat()
|
||||
|
||||
#include "malloc.h"
|
||||
#include "db.h"
|
||||
#include "header.h"
|
||||
|
||||
#include "artist.h"
|
||||
#include "album.h"
|
||||
#include "song.h"
|
||||
#include "file.h"
|
||||
|
||||
#include "tag_dummy.h"
|
||||
|
||||
#define CEIL32BIT(x) ( ((x) + 3) & 0xfffffffc )
|
||||
#define CEIL32BIT_LEN(x) CEIL32BIT(strlen(x) + 1) // +1 because we want to store the \0 at least once
|
||||
|
||||
#define CATCH_MALLOC(condition) \
|
||||
while( condition ) { \
|
||||
int rc_catch_malloc = free_ram(); \
|
||||
if (rc_catch_malloc != ERR_NONE) { \
|
||||
DEBUGF("catch_malloc: " #condition ": could not free memory, failing...\n"); \
|
||||
return rc_catch_malloc; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CATCH_MALLOC_ERR(expr) CATCH_MALLOC( (expr) == ERR_MALLOC )
|
||||
#define CATCH_MALLOC_NULL(expr) CATCH_MALLOC( (expr) == NULL )
|
||||
// Loop the expression as long as it returns ERR_MALLOC (for CATCH_MALLOC_ERR)
|
||||
// or NULL (for CATCH_MALLOC_NULL)
|
||||
// on each failure, call free_ram() to free some ram. if free_ram() fails, return
|
||||
// the fail-code
|
||||
#define CATCH_ERR(expr) \
|
||||
CATCH_MALLOC_ERR(rc = expr); \
|
||||
if( rc != ERR_NONE ) { \
|
||||
DEBUGF("catch_err: " #expr ": failed\n"); \
|
||||
return rc; \
|
||||
}
|
||||
// Catches all errors: if it's a MALLOC one, try to free memory,
|
||||
// if it's another one, return the code
|
||||
|
||||
static int fill_artist_offsets(struct artist_entry *e, struct artist_size *max_s);
|
||||
static int fill_album_offsets(struct album_entry *e, struct album_size *max_s);
|
||||
static int fill_song_offsets(struct song_entry *e, struct song_size *max_s);
|
||||
static int fill_file_offsets(struct file_entry *e, struct file_size *max_s);
|
||||
|
||||
static int do_add(const struct tag_info *t);
|
||||
|
||||
static int tag_empty_get(struct tag_info *t);
|
||||
/* Adds "<no artist tag>" and "<no album tag>" if they're empty
|
||||
*/
|
||||
|
||||
static int free_ram();
|
||||
static char in_file = 0;
|
||||
|
||||
static int do_write(FILE *fd);
|
||||
|
||||
static struct array_buffer *artists;
|
||||
static struct array_buffer *albums;
|
||||
static struct array_buffer *songs;
|
||||
static struct array_buffer *files;
|
||||
static uint32_t artist_start=0, album_start=0, song_start=0, file_start=0;
|
||||
static uint32_t artist_entry_len, album_entry_len, song_entry_len, file_entry_len;
|
||||
static char *artists_file, *albums_file, *songs_file, *files_file;
|
||||
|
||||
int db_construct() {
|
||||
void *max_size;
|
||||
|
||||
// struct array_buffer* new_array_buffer( int (*cmp)(const void *a, const void *b),
|
||||
// int (*serialize)(FILE *fd, const void *e),
|
||||
// int (*unserialize)(void **e, FILE *fd),
|
||||
// uint32_t (*get_length)(const void *size),
|
||||
// int (*write)(FILE *fd, void *e, const void *size),
|
||||
// int (*destruct)(void *e),
|
||||
// char* file_name,
|
||||
// void* max_size,
|
||||
// int (*max_size_update)(void *max_size, const void *e),
|
||||
// int (*max_size_destruct)(void *max_size),
|
||||
// int (*add_item_mem)(void *e, void *s, uint32_t item),
|
||||
// int (*add_item_file)(FILE *fd, void *e, void *s, uint32_t item)
|
||||
// );
|
||||
|
||||
if(!( max_size = (void*)new_artist_size() )) {
|
||||
DEBUGF("new_db: new_artist_size() failed\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( artists = new_array_buffer( (int (*)(const void *a, const void *b)) artist_entry_compare,
|
||||
(int (*)(FILE *fd, const void *e)) artist_entry_serialize,
|
||||
(int (*)(void **e, FILE *fd)) artist_entry_unserialize,
|
||||
(uint32_t (*)(const void *size)) artist_size_get_length,
|
||||
(int (*)(FILE *fd, void *e, const void *size)) artist_entry_write,
|
||||
(int (*)(void *e)) artist_entry_destruct,
|
||||
NULL, // don't allow to switch to file
|
||||
max_size,
|
||||
(int (*)(void *max_size, const void *e)) artist_size_max,
|
||||
(int (*)(void *max_size)) artist_size_destruct,
|
||||
(int (*)(void *e, void *s, uint32_t item)) artist_entry_add_album_mem,
|
||||
(int (*)(FILE *fd, void *e, void *s, uint32_t item)) artist_entry_add_album_file,
|
||||
(int (*)(void *e, void *s)) fill_artist_offsets
|
||||
) )) {
|
||||
DEBUGF("new_db: new_array_buffer() failed on artists[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( artists_file = malloc(12) )) { // artists.tmp
|
||||
DEBUGF("new_db: could not malloc() for artists[] file_name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
strcpy(artists_file, "artists.tmp");
|
||||
|
||||
if(!( max_size = (void*)new_album_size() )) {
|
||||
DEBUGF("new_db: new_album_size() failed\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( albums = new_array_buffer( (int (*)(const void *a, const void *b)) album_entry_compare,
|
||||
(int (*)(FILE *fd, const void *e)) album_entry_serialize,
|
||||
(int (*)(void **e, FILE *fd)) album_entry_unserialize,
|
||||
(uint32_t (*)(const void *size)) album_size_get_length,
|
||||
(int (*)(FILE *fd, void *e, const void *size)) album_entry_write,
|
||||
(int (*)(void *e)) album_entry_destruct,
|
||||
NULL, // don't allow to switch to file
|
||||
max_size,
|
||||
(int (*)(void *max_size, const void *e)) album_size_max,
|
||||
(int (*)(void *max_size)) album_size_destruct,
|
||||
(int (*)(void *e, void *s, uint32_t item)) album_entry_add_song_mem,
|
||||
(int (*)(FILE *fd, void *e, void *s, uint32_t item)) album_entry_add_song_file,
|
||||
(int (*)(void *e, void *s)) fill_album_offsets
|
||||
) )) {
|
||||
DEBUGF("new_db: new_array_buffer() failed on albums[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( albums_file = malloc(11) )) { // albums.tmp
|
||||
DEBUGF("new_db: could not malloc() for albums[] file_name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
strcpy(albums_file, "albums.tmp");
|
||||
|
||||
if(!( max_size = (void*)new_song_size() )) {
|
||||
DEBUGF("new_db: new_song_size() failed\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( songs = new_array_buffer( (int (*)(const void *a, const void *b)) song_entry_compare,
|
||||
(int (*)(FILE *fd, const void *e)) song_entry_serialize,
|
||||
(int (*)(void **e, FILE *fd)) song_entry_unserialize,
|
||||
(uint32_t (*)(const void *size)) song_size_get_length,
|
||||
(int (*)(FILE *fd, void *e, const void *size)) song_entry_write,
|
||||
(int (*)(void *e)) song_entry_destruct,
|
||||
NULL, // may switch to file, but we'd like to know about it
|
||||
max_size,
|
||||
(int (*)(void *max_size, const void *e)) song_size_max,
|
||||
(int (*)(void *max_size)) song_size_destruct,
|
||||
NULL,
|
||||
NULL,
|
||||
(int (*)(void *e, void *s)) fill_song_offsets
|
||||
) )) {
|
||||
DEBUGF("new_db: new_array_buffer() failed on songs[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( songs_file = malloc(10) )) { // songs.tmp
|
||||
DEBUGF("new_db: could not malloc() for songs[] file_name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
strcpy(songs_file, "songs.tmp");
|
||||
|
||||
if(!( max_size = (void*)new_file_size() )) {
|
||||
DEBUGF("new_db: new_file_size() failed\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( files = new_array_buffer( (int (*)(const void *a, const void *b)) file_entry_compare,
|
||||
(int (*)(FILE *fd, const void *e)) file_entry_serialize,
|
||||
(int (*)(void **e, FILE *fd)) file_entry_unserialize,
|
||||
(uint32_t (*)(const void *size)) file_size_get_length,
|
||||
(int (*)(FILE *fd, void *e, const void *size)) file_entry_write,
|
||||
(int (*)(void *e)) file_entry_destruct,
|
||||
NULL,
|
||||
max_size,
|
||||
(int (*)(void *max_size, const void *e)) file_size_max,
|
||||
(int (*)(void *max_size)) file_size_destruct,
|
||||
NULL,
|
||||
NULL,
|
||||
(int (*)(void *e, void *s)) fill_file_offsets
|
||||
) )) {
|
||||
DEBUGF("new_db: new_array_buffer() failed on files[]\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
if(!( files_file = malloc(10) )) { // files.tmp
|
||||
DEBUGF("new_db: could not malloc() for files[] file_name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
strcpy(files_file, "files.tmp");
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int db_destruct() {
|
||||
int rc;
|
||||
|
||||
CATCH_ERR( array_buffer_destruct(artists, 1) );
|
||||
artists = NULL;
|
||||
free(artists_file);
|
||||
artists_file = NULL;
|
||||
|
||||
CATCH_ERR( array_buffer_destruct(albums, 1) );
|
||||
albums = NULL;
|
||||
free(albums_file);
|
||||
albums_file = NULL;
|
||||
|
||||
CATCH_ERR( array_buffer_destruct(songs, 1) );
|
||||
songs = NULL;
|
||||
free(songs_file);
|
||||
songs_file = NULL;
|
||||
|
||||
CATCH_ERR( array_buffer_destruct(files, 1) );
|
||||
files = NULL;
|
||||
free(files_file);
|
||||
files_file = NULL;
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_add(const struct tag_info *t) {
|
||||
struct artist_entry *artist; uint32_t artistn;
|
||||
struct album_entry *album; uint32_t albumn;
|
||||
struct song_entry *song; uint32_t songn;
|
||||
struct file_entry *file; uint32_t filen;
|
||||
int rc;
|
||||
|
||||
// create file
|
||||
CATCH_MALLOC_NULL( file = new_file_entry( CEIL32BIT( strlen(t->directory) + 1 + strlen(t->filename) + 1 ) ) ); // "dir"."/"."file"."\0"
|
||||
|
||||
// fill in file
|
||||
strcpy(file->name, t->directory);
|
||||
strcat(file->name, "/");
|
||||
strcat(file->name, t->filename);
|
||||
file->hash = 0xffffffff; // TODO
|
||||
file->song = songn = array_buffer_get_next_index(songs);
|
||||
file->rundb = 0xffffffff; // TODO
|
||||
|
||||
// add
|
||||
CATCH_ERR( array_buffer_add(files, file, &filen) );
|
||||
|
||||
// create artist
|
||||
CATCH_MALLOC_NULL( artist = new_artist_entry( CEIL32BIT_LEN(t->artist), 0) );
|
||||
// fill in
|
||||
strcpy(artist->name, t->artist);
|
||||
// see if it is already in
|
||||
CATCH_MALLOC_ERR( rc = array_buffer_find_entry(artists, artist, &artistn) );
|
||||
if( rc == ERR_NONE ) { // found it
|
||||
// remove our self-made one
|
||||
artist_entry_destruct(artist);
|
||||
artist = NULL;
|
||||
} else if( rc == ERR_NOTFOUND ) { // didn't find it
|
||||
// fill in the rest and add
|
||||
CATCH_ERR( artist_entry_resize(artist, artist->size.name_len, 1) );
|
||||
artist->album[0] = albumn = array_buffer_get_next_index(albums); // if artist isn't in, album will not be in either
|
||||
CATCH_ERR( array_buffer_add(artists, artist, &artistn) );
|
||||
// leave artist != NULL, to indicate that we made a new one
|
||||
} else { //error
|
||||
DEBUGF("do_add: could not search for artist in artists[]\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
// create album
|
||||
CATCH_MALLOC_NULL( album = new_album_entry(0,0) );
|
||||
// malloc for key
|
||||
CATCH_MALLOC_NULL( album->key = malloc( strlen(t->album) + 3 + strlen(t->artist) + 3 + strlen(t->directory) + 1 ) );
|
||||
// fill in
|
||||
strcpy(album->key, t->album);
|
||||
strcat(album->key, "___");
|
||||
strcat(album->key, t->artist);
|
||||
strcat(album->key, "___");
|
||||
strcat(album->key, t->directory);
|
||||
// see if it is already in
|
||||
CATCH_MALLOC_ERR( rc = array_buffer_find_entry(albums, album, &albumn) );
|
||||
if( rc == ERR_NONE ) { // found it
|
||||
assert(artist == NULL); // make sure artist was found; else we have trouble!
|
||||
// Remove our search-album and add the song to the already existing one
|
||||
album_entry_destruct(album);
|
||||
album = NULL;
|
||||
CATCH_ERR( array_buffer_entry_update(albums, albumn, songn) );
|
||||
} else if( rc == ERR_NOTFOUND ) { // didn't find it
|
||||
// fill in the rest of the info in this album and add it
|
||||
CATCH_ERR( album_entry_resize(album, CEIL32BIT_LEN(t->album), 1 ) );
|
||||
strcpy(album->name, t->album);
|
||||
album->artist = artistn;
|
||||
album->song[0] = songn;
|
||||
CATCH_ERR( array_buffer_add(albums, album, &albumn) );
|
||||
} else { // error
|
||||
DEBUGF("do_add: could not search for album in albums[]\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
if( album != NULL && artist == NULL ) {
|
||||
// we have a new album from an already existing artist
|
||||
// add it!
|
||||
CATCH_ERR( array_buffer_entry_update(artists, artistn, albumn) );
|
||||
}
|
||||
|
||||
|
||||
// song
|
||||
CATCH_MALLOC_NULL( song = new_song_entry( CEIL32BIT_LEN(t->song), CEIL32BIT_LEN(t->genre)) );
|
||||
// fill in
|
||||
strcpy(song->name, t->song);
|
||||
song->artist = artistn;
|
||||
song->album = albumn;
|
||||
song->file = filen;
|
||||
strcpy(song->genre, t->genre);
|
||||
song->bitrate = t->bitrate;
|
||||
song->year = t->year;
|
||||
song->playtime = t->playtime;
|
||||
song->track = t->track;
|
||||
song->samplerate = t->samplerate;
|
||||
// add
|
||||
CATCH_ERR( array_buffer_add(songs, song, NULL) );
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int tag_empty_get(struct tag_info *t) {
|
||||
assert( t != NULL );
|
||||
|
||||
if( t->song == NULL ) {
|
||||
CATCH_MALLOC_NULL( t->song = (char*)malloc(14) );
|
||||
strcpy(t->song, "<no song tag>");
|
||||
}
|
||||
if( t->genre == NULL ) {
|
||||
CATCH_MALLOC_NULL( t->genre = (char*)malloc(15) );
|
||||
strcpy(t->genre, "<no genre tag>");
|
||||
}
|
||||
if( t->artist == NULL ) {
|
||||
CATCH_MALLOC_NULL( t->artist = (char*)malloc(16) );
|
||||
strcpy(t->artist, "<no artist tag>");
|
||||
}
|
||||
if( t->album == NULL ) {
|
||||
CATCH_MALLOC_NULL( t->album = (char*)malloc(15) );
|
||||
strcpy(t->album, "<no album tag>");
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int db_add(char* file_path, const char* strip_path, const char* add_path) {
|
||||
char *basename, *dir;
|
||||
struct tag_info *t;
|
||||
int rc;
|
||||
|
||||
assert(file_path != NULL);
|
||||
|
||||
// Create a new tag_info structure
|
||||
CATCH_MALLOC_NULL( t = new_tag_info() );
|
||||
|
||||
// fill in the file_name
|
||||
basename = strrchr(file_path, '/'); // TODO: add \ for windows
|
||||
if( basename == NULL ) {
|
||||
basename = file_path; // no / in the path, so it's only a filename
|
||||
dir = NULL;
|
||||
} else {
|
||||
dir = file_path;
|
||||
basename[0] = '\0'; // set the / to \0 to split the string
|
||||
basename++; // skip past the /
|
||||
}
|
||||
CATCH_MALLOC_NULL( t->filename = malloc(strlen(basename)+1) ); // +1 for the '\0' termination
|
||||
strcpy(t->filename, basename);
|
||||
|
||||
// convert the path
|
||||
if( strip_path != NULL && strlen(strip_path) > 0) {
|
||||
if( dir == NULL || strncmp(file_path, strip_path, strlen(strip_path)) ) {
|
||||
printf("db_add: could not strip path from \"%s\"\n", file_path);
|
||||
} else {
|
||||
dir += strlen(strip_path); // skip the path to strip
|
||||
}
|
||||
}
|
||||
if( add_path != NULL ) {
|
||||
CATCH_MALLOC_NULL( t->directory = malloc( strlen(add_path) + strlen(dir) + 1 ) ); // +1 for '\0' termination
|
||||
strcpy(t->directory, add_path);
|
||||
strcat(t->directory, dir);
|
||||
} else {
|
||||
CATCH_MALLOC_NULL( t->directory = malloc( strlen(dir) + 1 ) );
|
||||
strcpy(t->directory, dir);
|
||||
}
|
||||
|
||||
// restore the file_path to it's original state
|
||||
if( dir != NULL) *(basename-1) = '/';
|
||||
|
||||
// So far we have:
|
||||
// filename
|
||||
// directory
|
||||
// try to get the rest from tag-information:
|
||||
//tag_id3v2_get(file_path, t);
|
||||
//tag_id3v1_get(file_path, t);
|
||||
tag_dummy(file_path, t);
|
||||
|
||||
// If it is still empty here, skip this file
|
||||
if( t->artist==NULL && t->song==NULL && t->album==NULL && t->genre==NULL) {
|
||||
tag_info_destruct(t); // we won't need it anymore
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
// fill in empty tags with "<no ... tag>"
|
||||
CATCH_ERR( tag_empty_get(t) );
|
||||
|
||||
// all filled in, now add it
|
||||
CATCH_ERR( do_add(t) );
|
||||
|
||||
tag_info_destruct(t); // we won't need it anymore
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int free_ram() {
|
||||
// put things in file that we won't need to search a lot:
|
||||
// files[] and songs[] are write only
|
||||
// artists[] and albums[] should stay in memory as long as possible
|
||||
// albums[] is updated for every song;
|
||||
// artists[] for every album: artists[] will be the first to loose ram...
|
||||
if(!( in_file & 0x01 )) { // files[] is still in ram
|
||||
in_file |= 0x01;
|
||||
// switch files[] to file-mode
|
||||
files->file_name = files_file;
|
||||
files_file = NULL; // since array_buffer will clean this up
|
||||
return array_buffer_switch_to_file(files);
|
||||
} else if(!( in_file & 0x02 )) { // song[] is still in ram
|
||||
in_file |= 0x02;
|
||||
// switch songs[] to file-mode
|
||||
songs->file_name = songs_file;
|
||||
songs_file = NULL; // since array_buffer will clean this up
|
||||
return array_buffer_switch_to_file(songs);
|
||||
} else if(!( in_file & 0x04 )) { // artists[] is still in ram
|
||||
in_file |= 0x04;
|
||||
// switch artists[] to file-mode
|
||||
artists->file_name = artists_file;
|
||||
artists_file = NULL; // since array_buffer will clean this up
|
||||
return array_buffer_switch_to_file(artists);
|
||||
} else if(!( in_file & 0x08 )) { // albums[] is still in ram
|
||||
in_file |= 0x08;
|
||||
// switch albums[] to file-mode
|
||||
albums->file_name = albums_file;
|
||||
albums_file = NULL; // since array_buffer will clean this up
|
||||
return array_buffer_switch_to_file(albums);
|
||||
} else {
|
||||
// all is already in file mode, sorry...
|
||||
DEBUGF("free_ram: everything is already in file-mode, cannot free more ram, sorry...\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
}
|
||||
|
||||
static int fill_artist_offsets(struct artist_entry *e, struct artist_size *max_s) {
|
||||
uint32_t i;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(album_start != 0);
|
||||
|
||||
for(i=0; i<e->size.album_count; i++) {
|
||||
e->album[i] = album_start + e->album[i] * album_entry_len;
|
||||
}
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int fill_album_offsets(struct album_entry *e, struct album_size *max_s) {
|
||||
uint32_t i;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(song_start != 0);
|
||||
|
||||
e->artist = artist_start + e->artist * artist_entry_len;
|
||||
for(i=0; i<e->size.song_count; i++) {
|
||||
e->song[i] = song_start + e->song[i] * song_entry_len;
|
||||
}
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int fill_song_offsets(struct song_entry *e, struct song_size *max_s) {
|
||||
|
||||
assert(e != NULL);
|
||||
assert(artist_start != 0);
|
||||
assert(album_start != 0);
|
||||
assert(file_start != 0);
|
||||
|
||||
e->artist = artist_start + e->artist * artist_entry_len;
|
||||
e->album = album_start + e->album * album_entry_len;
|
||||
e->file = file_start + e->file * file_entry_len;
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int fill_file_offsets(struct file_entry *e, struct file_size *max_s) {
|
||||
|
||||
assert(e != NULL);
|
||||
assert(song_start != 0);
|
||||
|
||||
e->song = song_start + e->song * song_entry_len;
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_write(FILE *fd) {
|
||||
int rc;
|
||||
struct header h;
|
||||
|
||||
assert(fd != NULL);
|
||||
|
||||
// make a header
|
||||
h.magic[0] = 'R'; h.magic[1] = 'D'; h.magic[2] = 'B';
|
||||
h.version = 0x03;
|
||||
|
||||
h.artist_start = artist_start = HEADER_SIZE;
|
||||
h.album_start = album_start = h.artist_start + array_buffer_get_length(artists); // TODO error check
|
||||
h.song_start = song_start = h.album_start + array_buffer_get_length(albums);
|
||||
h.file_start = file_start = h.song_start + array_buffer_get_length(songs);
|
||||
|
||||
h.artist_count = artists->count;
|
||||
h.album_count = albums->count;
|
||||
h.song_count = songs->count;
|
||||
h.file_count = files->count;
|
||||
|
||||
h.artist_len = ((struct artist_size*)artists->max_size)->name_len;
|
||||
h.album_len = ((struct album_size*)albums->max_size)->name_len;
|
||||
h.song_len = ((struct song_size*)songs->max_size)->name_len;
|
||||
h.genre_len = ((struct song_size*)songs->max_size)->genre_len;
|
||||
h.file_len = ((struct file_size*)files->max_size)->name_len;
|
||||
|
||||
artist_entry_len = artist_size_get_length(artists->max_size); // TODO error check
|
||||
album_entry_len = album_size_get_length(albums->max_size);
|
||||
song_entry_len = song_size_get_length(songs->max_size);
|
||||
file_entry_len = file_size_get_length(files->max_size);
|
||||
|
||||
h.song_array_count = ((struct album_size*)albums->max_size)->song_count;
|
||||
h.album_array_count = ((struct artist_size*)artists->max_size)->album_count;
|
||||
|
||||
h.flags.reserved = 0;
|
||||
h.flags.rundb_dirty = 1;
|
||||
|
||||
// write the header
|
||||
CATCH_ERR( header_write(fd, &h) );
|
||||
|
||||
// write the arrays
|
||||
CATCH_ERR( array_buffer_write(fd, artists) );
|
||||
CATCH_ERR( array_buffer_write(fd, albums) );
|
||||
CATCH_ERR( array_buffer_write(fd, songs) );
|
||||
CATCH_ERR( array_buffer_write(fd, files) );
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int db_write(FILE *fd) {
|
||||
int rc;
|
||||
// sort everything
|
||||
CATCH_ERR( array_buffer_sort(artists) );
|
||||
CATCH_ERR( array_buffer_sort(albums) );
|
||||
CATCH_ERR( array_buffer_sort(songs) );
|
||||
CATCH_ERR( array_buffer_sort(files) );
|
||||
|
||||
CATCH_ERR( do_write(fd) );
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
struct tag_info* new_tag_info() {
|
||||
struct tag_info *t;
|
||||
t = malloc(sizeof(struct tag_info));
|
||||
if( t == NULL ) {
|
||||
DEBUGF("new_tag_info: could not malloc() for tag_info\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
t->directory = NULL;
|
||||
t->filename = NULL;
|
||||
t->song = NULL;
|
||||
t->artist = NULL;
|
||||
t->album = NULL;
|
||||
t->genre = NULL;
|
||||
t->bitrate = 0;
|
||||
t->year = 0;
|
||||
t->playtime = 0;
|
||||
t->track = 0;
|
||||
t->samplerate = 0;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
int tag_info_destruct(struct tag_info *t) {
|
||||
assert(t != NULL);
|
||||
|
||||
free(t->directory);
|
||||
t->directory = NULL;
|
||||
free(t->filename);
|
||||
t->filename = NULL;
|
||||
free(t->song);
|
||||
t->song = NULL;
|
||||
free(t->artist);
|
||||
t->artist = NULL;
|
||||
free(t->album);
|
||||
t->album = NULL;
|
||||
free(t->genre);
|
||||
t->genre = NULL;
|
||||
t->bitrate = 0;
|
||||
t->year = 0;
|
||||
t->playtime = 0;
|
||||
t->track = 0;
|
||||
t->samplerate = 0;
|
||||
|
||||
free(t);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
37
apps/tagdb/db.h
Normal file
37
apps/tagdb/db.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef __DB_H__
|
||||
#define __DB_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "array_buffer.h"
|
||||
|
||||
struct tag_info {
|
||||
char* directory;
|
||||
char* filename; // \0 terminated string's
|
||||
char* song;
|
||||
char* artist;
|
||||
char* album;
|
||||
char* genre;
|
||||
uint16_t bitrate;
|
||||
uint16_t year;
|
||||
uint32_t playtime;
|
||||
uint16_t track;
|
||||
uint16_t samplerate;
|
||||
};
|
||||
|
||||
int db_construct();
|
||||
|
||||
int db_destruct();
|
||||
|
||||
int db_add(char* file_path, const char* strip_path, const char* add_path);
|
||||
|
||||
int db_sort();
|
||||
|
||||
int db_write(FILE *fd);
|
||||
|
||||
struct tag_info* new_tag_info();
|
||||
|
||||
int tag_info_destruct(struct tag_info *t);
|
||||
|
||||
#endif
|
268
apps/tagdb/file.c
Normal file
268
apps/tagdb/file.c
Normal file
|
@ -0,0 +1,268 @@
|
|||
#include "malloc.h" // realloc() and free()
|
||||
#include <string.h> // strncasecmp()
|
||||
|
||||
#include "file.h"
|
||||
|
||||
// how is our flag organized?
|
||||
#define FLAG ( 0xBF )
|
||||
#define FLAG_VALID(flag) (flag == 0xBF)
|
||||
|
||||
static int do_resize(struct file_entry *e, const uint32_t name_len, const int zero_fill);
|
||||
|
||||
struct file_entry* new_file_entry(const uint32_t name_len) {
|
||||
// Start my allocating memory
|
||||
struct file_entry *e = (struct file_entry*)malloc(sizeof(struct file_entry));
|
||||
if( e == NULL ) {
|
||||
DEBUGF("new_file_entry: could not allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We begin empty
|
||||
e->name = NULL;
|
||||
e->size.name_len = 0;
|
||||
|
||||
e->hash = 0;
|
||||
e->song = 0;
|
||||
e->rundb = 0;
|
||||
|
||||
e->flag = FLAG;
|
||||
|
||||
// and resize to the requested size
|
||||
if( do_resize(e, name_len, 1) ) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
int file_entry_destruct(struct file_entry *e) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
free(e->name);
|
||||
|
||||
free(e);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_resize(struct file_entry *e, const uint32_t name_len, const int zero_fill) {
|
||||
void* temp;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
if( name_len != e->size.name_len ) {
|
||||
temp = realloc(e->name, name_len);
|
||||
if(temp == NULL) {
|
||||
DEBUGF("file_entry_resize: out of memory to resize name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->name = (char*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.name_len; i<name_len; i++)
|
||||
e->name[i] = (char)0x00;
|
||||
}
|
||||
|
||||
e->size.name_len = name_len;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int file_entry_resize(struct file_entry *e, const uint32_t name_len) {
|
||||
return do_resize(e, name_len, 1);
|
||||
}
|
||||
|
||||
int file_entry_serialize(FILE *fd, const struct file_entry *e) {
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// First byte we write is a flag-byte to indicate this is a valid record
|
||||
if( fwrite(&e->flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_serialize: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// First we write the length of the name field
|
||||
if( fwrite(&e->size.name_len, sizeof(e->size.name_len), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_serialize: failed to write name_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// now the name field itself
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("file_entry_serialize: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// hash field
|
||||
if( fwrite(&e->hash, sizeof(e->hash), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_serialize: failed to write hash\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// song field
|
||||
if( fwrite(&e->song, sizeof(e->song), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_serialize: failed to write song\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// rundb field
|
||||
if( fwrite(&e->rundb, sizeof(e->rundb), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_serialize: failed to write rundb\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int file_entry_unserialize(struct file_entry **dest, FILE *fd) {
|
||||
uint32_t length;
|
||||
struct file_entry *e;
|
||||
|
||||
assert(dest != NULL);
|
||||
assert(fd != NULL);
|
||||
|
||||
// Allocate memory
|
||||
e = new_file_entry(0);
|
||||
if( e == NULL ) {
|
||||
DEBUGF("file_entry_unserialize: could not create new file_entry\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// First we read the length of the name field
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_unserialize: failed to read name_len\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(e, length, 0) ) {
|
||||
DEBUGF("file_entry_unserialize: failed to allocate memory for name\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("file_entry_unserialize: failed to read name\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// hash field
|
||||
if( fread(&e->hash, sizeof(e->hash), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_unserialize: failed to read hash\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// song field
|
||||
if( fread(&e->song, sizeof(e->song), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_unserialize: failed to read song\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// rundb field
|
||||
if( fread(&e->rundb, sizeof(e->rundb), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_unserialize: failed to read rundb\n");
|
||||
file_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
*dest = e;
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int file_entry_write(FILE *fd, struct file_entry *e, struct file_size *s) {
|
||||
uint32_t be32;
|
||||
char pad = 0x00;
|
||||
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// file name
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("file_entry_write: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// pad the rest
|
||||
be32 = e->size.name_len; // abuse be32 as counter
|
||||
while( s != NULL && s->name_len > be32) {
|
||||
if( fwrite(&pad, 1, 1, fd) == 1 ) {
|
||||
be32++;
|
||||
} else {
|
||||
DEBUGF("file_entry_write: failed to pad name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// hash
|
||||
be32 = BE32(e->hash);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_write: failed to write hash\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// song
|
||||
be32 = BE32(e->song);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_write: failed to write song\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// rundb
|
||||
be32 = BE32(e->rundb);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("file_entry_write: failed to write rundb\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int file_entry_compare(const struct file_entry *a, const struct file_entry *b) {
|
||||
assert(a != NULL);
|
||||
assert(b != NULL);
|
||||
return strncasecmp(a->name, b->name, (a->size.name_len <= b->size.name_len ? a->size.name_len : b->size.name_len) );
|
||||
}
|
||||
|
||||
struct file_size* new_file_size() {
|
||||
struct file_size *s;
|
||||
s = (struct file_size*)malloc(sizeof(struct file_size));
|
||||
if( s == NULL ) {
|
||||
DEBUGF("new_file_size: failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
s->name_len = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline uint32_t file_size_get_length(const struct file_size *size) {
|
||||
assert(size != NULL);
|
||||
return size->name_len + 12;
|
||||
}
|
||||
|
||||
inline int file_size_max(struct file_size *s, const struct file_entry *e) {
|
||||
assert(s != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
s->name_len = ( s->name_len >= e->size.name_len ? s->name_len : e->size.name_len );
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int file_size_destruct(struct file_size *s) {
|
||||
assert(s != NULL);
|
||||
// nothing to do...
|
||||
free(s);
|
||||
return ERR_NONE;
|
||||
}
|
84
apps/tagdb/file.h
Normal file
84
apps/tagdb/file.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
#ifndef __FILE_H__
|
||||
#define __FILE_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct file_entry {
|
||||
char* name; // song name
|
||||
|
||||
uint32_t hash;
|
||||
uint32_t song; // pointer to song
|
||||
uint32_t rundb; // pointer to rundb
|
||||
|
||||
struct file_size {
|
||||
uint32_t name_len; // must be mulitple of 4
|
||||
} size;
|
||||
unsigned char flag; // flags
|
||||
};
|
||||
|
||||
struct file_entry* new_file_entry(const uint32_t name_len);
|
||||
/* Creates a new file_entry with the specified sizes
|
||||
* Returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
int file_entry_destruct(struct file_entry *e);
|
||||
/* Destructs the given file_entry and free()'s it's memory
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
inline int file_entry_resize(struct file_entry *e, const uint32_t name_len);
|
||||
/* Change the size of the entry
|
||||
* returns 0 on succes, 1 on failure
|
||||
*/
|
||||
|
||||
int file_entry_serialize(FILE *fd, const struct file_entry *e);
|
||||
/* Serializes the entry in the file at the current position
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
int file_entry_unserialize(struct file_entry* *e, FILE *fd);
|
||||
/* Unserializes an entry from file into a new structure
|
||||
* The address of the structure is saved into *e
|
||||
* returns 0 on success
|
||||
* 1 on malloc() failure
|
||||
* 2 on fread() failure
|
||||
*/
|
||||
|
||||
int file_entry_write(FILE *fd, struct file_entry *e, struct file_size *s);
|
||||
/* Writes the entry to file in the final form
|
||||
* returns 0 (0) on success, 1 (1) on failure
|
||||
*/
|
||||
|
||||
inline int file_entry_compare(const struct file_entry *a, const struct file_entry *b);
|
||||
/* Compares 2 entries
|
||||
* When a < b it returns <0
|
||||
* a = b 0
|
||||
* a > b >0
|
||||
*/
|
||||
|
||||
struct file_size* new_file_size();
|
||||
/* Creates a new size structure
|
||||
* returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
inline uint32_t file_size_get_length(const struct file_size *size);
|
||||
/* Calculates the length of the entry when written by file_entry_write()
|
||||
* returns the length on success, 0xffffffff on failure
|
||||
*/
|
||||
|
||||
inline int file_size_max(struct file_size *s, const struct file_entry *e);
|
||||
/* Updates the file_size structure to contain the maximal lengths of either
|
||||
* the original entry in s, or the entry e
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
int file_size_destruct(struct file_size *s);
|
||||
/* destructs the file_size structure
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
#endif
|
121
apps/tagdb/header.c
Normal file
121
apps/tagdb/header.c
Normal file
|
@ -0,0 +1,121 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int header_write(FILE *fd, const struct header *h) {
|
||||
// Write the header to file
|
||||
uint32_t be;
|
||||
|
||||
if( fwrite(h->magic, 3, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write magic[3]\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
if( fwrite(&h->version, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write version\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->artist_start);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write artist_start\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->album_start);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write album_start\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->song_start);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write song_start\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->file_start);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write file_start\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
|
||||
be = BE32(h->artist_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write artist_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->album_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write album_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->song_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write song_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->file_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write file_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
|
||||
be = BE32(h->artist_len);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write artist_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->album_len);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write album_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->song_len);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write song_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->genre_len);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write genre_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->file_len);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write file_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
|
||||
be = BE32(h->song_array_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write song_array_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
be = BE32(h->album_array_count);
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write album_array_count\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
|
||||
be = BE32( (h->flags.reserved << 1) | (h->flags.rundb_dirty) );
|
||||
if( fwrite(&be, 4, 1, fd) != 1 ) {
|
||||
DEBUGF("header_write: failed to write flags\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
39
apps/tagdb/header.h
Normal file
39
apps/tagdb/header.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef __HEADER_H__
|
||||
#define __HEADER_H__
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define HEADER_SIZE 68
|
||||
|
||||
struct header {
|
||||
char magic[3]; // (four bytes: 'R' 'D' 'B' and a byte for version. This is version 2. (0x02)
|
||||
unsigned char version;
|
||||
|
||||
uint32_t artist_start; // File Offset to the artist table(starting from 0)
|
||||
uint32_t album_start; // File Offset to the album table(starting from 0)
|
||||
uint32_t song_start; // File Offset of the song table(starting from 0)
|
||||
uint32_t file_start; // File Offset to the filename table(starting from 0)
|
||||
|
||||
uint32_t artist_count; // Number of artists
|
||||
uint32_t album_count; // Number of albums
|
||||
uint32_t song_count; // Number of songs
|
||||
uint32_t file_count; // Number of File Entries, this is needed for the binary search.
|
||||
|
||||
uint32_t artist_len; // Max Length of the artist name field
|
||||
uint32_t album_len; // Max Length of the album name field
|
||||
uint32_t song_len; // Max Length of the song name field
|
||||
uint32_t genre_len; // Max Length of the genre field
|
||||
uint32_t file_len; // Max Length of the filename field.
|
||||
|
||||
uint32_t song_array_count; // Number of entries in songs-per-album array
|
||||
uint32_t album_array_count; // Number of entries in albums-per-artist array
|
||||
|
||||
struct {
|
||||
unsigned reserved : 31; // must be 0
|
||||
unsigned rundb_dirty : 1; // if the TagDatabase in unsynchronized with the RuntimeDatabase, 0 if synchronized.
|
||||
} flags;
|
||||
};
|
||||
|
||||
int header_write(FILE *fd, const struct header *header);
|
||||
|
||||
#endif
|
115
apps/tagdb/main.c
Normal file
115
apps/tagdb/main.c
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h> // strcmp()
|
||||
#include <dirent.h> // opendir() readdir() closedir()
|
||||
#include <sys/stat.h> // IS_DIR
|
||||
|
||||
#include "malloc.h"
|
||||
#include "db.h"
|
||||
|
||||
extern int out_of_memory;
|
||||
|
||||
// dir-is-album: all files in the dir ARE the same album, use the first name found.
|
||||
// dir-is-album-name: if no tag found, use the dir's instead of "<no album tag>"
|
||||
//
|
||||
// files in different dirs are ALWAYS different albums
|
||||
|
||||
static char* strip_path = NULL;
|
||||
static char* add_path = NULL;
|
||||
|
||||
static int iterate_dir(char* dir);
|
||||
/* Iterates over each item in the given directory
|
||||
* calls add_file() on each file
|
||||
* calls iterate_directory() on each directory (recursively)
|
||||
*/
|
||||
|
||||
static int iterate_dir(char* dir) {
|
||||
DIR *d;
|
||||
struct dirent *e;
|
||||
struct stat s;
|
||||
int rc;
|
||||
|
||||
assert(dir != NULL);
|
||||
|
||||
if(!( d = opendir(dir) )) {
|
||||
DEBUGF("iterate_dir: could not open directory \"%s\"\n", dir);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
while(( e = readdir(d) )) {
|
||||
char *path;
|
||||
|
||||
if( strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0 )
|
||||
continue; // we don't want to descend or loop around...
|
||||
|
||||
path = malloc(strlen(dir) + 1 + strlen(e->d_name) + 1); // "dir/d_name\0"
|
||||
if( path == NULL ) {
|
||||
DEBUGF("iterate_dir: could not malloc() directory-entry-name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
strcpy(path, dir);
|
||||
strcat(path, "/");
|
||||
strcat(path, e->d_name);
|
||||
#if defined OS_LINUX
|
||||
if( stat(path, &s) ) {
|
||||
DEBUGF("iterate_dir: could not stat(\"%s\")\n", path);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
if( S_ISDIR(s.st_mode) ) {
|
||||
#elif defined OS_ROCKBOX
|
||||
#error "Rockbox: not yet implemented: don't know how to list directory"
|
||||
if( false ) {
|
||||
#elif defined OS_WINDOWS
|
||||
if( false ) {
|
||||
#error "Windows: not yet implemented: don't know how to list directory"
|
||||
#else
|
||||
if( false ) {
|
||||
#error "No OS specified: don't know how to list directory"
|
||||
#endif
|
||||
if(( rc = iterate_dir(path) )) {
|
||||
closedir(d);
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
if(( rc = db_add(path, strip_path, add_path) )) {
|
||||
closedir(d);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
||||
if( closedir(d) ) {
|
||||
DEBUGF("iterate_dir: could not close directory \"%s\", ignoring...\n", dir);
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
FILE *fd;
|
||||
|
||||
if( argc != 2 ) {
|
||||
printf("usage: ./songdb dir\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
strip_path = "/home/niels/";
|
||||
add_path = "TEST/";
|
||||
|
||||
db_construct();
|
||||
|
||||
iterate_dir(argv[1]);
|
||||
|
||||
fd = fopen("xxx.db", "w");
|
||||
db_write(fd);
|
||||
fclose(fd);
|
||||
|
||||
db_destruct();
|
||||
|
||||
malloc_stats();
|
||||
|
||||
return 0;
|
||||
}
|
131
apps/tagdb/malloc.c
Normal file
131
apps/tagdb/malloc.c
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include "config.h"
|
||||
#include "malloc.h"
|
||||
|
||||
#undef malloc
|
||||
#undef free
|
||||
#undef realloc
|
||||
|
||||
#undef DEBUGF
|
||||
#define DEBUGF(...)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static size_t total=0;
|
||||
static size_t max_total=0;
|
||||
|
||||
struct size_array {
|
||||
void *ptr;
|
||||
size_t size;
|
||||
} sizes[1000];
|
||||
#define NOT_FOUND 1001
|
||||
static unsigned long count=0;
|
||||
|
||||
int out_of_memory = 1000000;
|
||||
|
||||
void *do_malloc(size_t size) {
|
||||
void *ret;
|
||||
if(total + size > out_of_memory) {
|
||||
DEBUGF("malloc(%d), total=%d: FAILED: simulating out-of-memory\n", size, total+size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = malloc(size);
|
||||
if( ret == NULL ) {
|
||||
DEBUGF("malloc(%d), total=%d FAILED\n", size, total+size);
|
||||
return NULL;
|
||||
} else {
|
||||
total += size;
|
||||
max_total = ( total > max_total ? total : max_total );
|
||||
sizes[count].ptr = ret;
|
||||
sizes[count].size = size;
|
||||
DEBUGF("malloc(%d), total=%d OK => 0x%08lx (%lu)\n", size, total, (unsigned long)ret, count);
|
||||
count++;
|
||||
if(count == NOT_FOUND) {
|
||||
fprintf(stderr, "MALLOC MEMORY FULL!!!!!!! FAILING\n");
|
||||
free(ret);
|
||||
count--;
|
||||
return NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long find(void* ptr) {
|
||||
unsigned long i;
|
||||
for(i=0; i<count; i++) {
|
||||
if( ptr == sizes[i].ptr ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
void do_free(void *ptr) {
|
||||
unsigned long i;
|
||||
|
||||
i = find(ptr);
|
||||
if( i == NOT_FOUND ) {
|
||||
DEBUGF("free(%08lx) (?) ptr unknown\n", (unsigned long)ptr);
|
||||
free(ptr);
|
||||
} else {
|
||||
total -= sizes[i].size;
|
||||
DEBUGF("free(%08lx) (%lu, %dbytes) => total=%u\n", (unsigned long)ptr, i, sizes[i].size, total);
|
||||
free(ptr);
|
||||
sizes[i].ptr = NULL; // delete
|
||||
sizes[i].size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void *do_realloc(void *ptr, size_t size) {
|
||||
void *ret;
|
||||
unsigned long i;
|
||||
|
||||
if( ptr == NULL ) {
|
||||
DEBUGF("realloc()=>");
|
||||
return do_malloc(size);
|
||||
}
|
||||
|
||||
i = find(ptr);
|
||||
|
||||
if( i == NOT_FOUND ) {
|
||||
DEBUGF("realloc(%08lx, %d) (?) ptr unknown ", (unsigned long)ptr, size);
|
||||
} else {
|
||||
DEBUGF("realloc(%08lx, %d) (%lu, %dbytes) => total=%d ", (unsigned long)ptr, size, i, sizes[i].size, total+size-sizes[i].size);
|
||||
}
|
||||
|
||||
if(total + size - sizes[i].size > out_of_memory) {
|
||||
DEBUGF("FAILED: simulating out-of-memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = realloc(ptr, size);
|
||||
if( ret == NULL && size != 0) { // realloc(x, 0) returns NULL, but is valid!
|
||||
DEBUGF("FAILED\n");
|
||||
} else {
|
||||
total += size - sizes[i].size;
|
||||
max_total = ( total > max_total ? total : max_total );
|
||||
sizes[i].ptr = ret; // update the ptr if realloc changed it
|
||||
sizes[i].size = size;
|
||||
DEBUGF("=> %08lx\n", (unsigned long)ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void malloc_stats() {
|
||||
unsigned long i, j;
|
||||
|
||||
printf("malloc stats:\n");
|
||||
printf(" Total number of allocated items: %lu\n", count);
|
||||
printf(" Current number of allocated items: ");
|
||||
j=0;
|
||||
for(i=0; i<count; i++) {
|
||||
if( sizes[i].ptr != NULL) {
|
||||
printf("%lu ", i);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
printf("=> %lu items\n", j);
|
||||
printf(" Maximum amount of allocated memory: %dbytes\n", max_total);
|
||||
printf(" Current amount of allocated memory: %dbytes\n", total);
|
||||
}
|
16
apps/tagdb/malloc.h
Normal file
16
apps/tagdb/malloc.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef __MALLOC_H__
|
||||
#define __MALLOC_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define malloc do_malloc
|
||||
#define free do_free
|
||||
#define realloc do_realloc
|
||||
|
||||
void *do_malloc(size_t size);
|
||||
void do_free(void *ptr);
|
||||
void *do_realloc(void *ptr, size_t size);
|
||||
|
||||
void malloc_stats();
|
||||
|
||||
#endif
|
218
apps/tagdb/parser.c
Normal file
218
apps/tagdb/parser.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
int errno;
|
||||
|
||||
int read_failure(FILE *fd) {
|
||||
fprintf(stderr, "Could not read from file: errno: %u ", errno);
|
||||
if( feof(fd) ) fprintf(stderr, "EOF");
|
||||
fprintf(stderr, "\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mem_failure() {
|
||||
fprintf(stderr, "Could not (re)allocate memory\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
FILE *fd;
|
||||
uint32_t artist_start, album_start, song_start, file_start;
|
||||
uint32_t artist_count, album_count, song_count, file_count;
|
||||
uint32_t artist_len, album_array_count;
|
||||
uint32_t album_len, song_array_count;
|
||||
uint32_t song_len, genre_len;
|
||||
uint32_t file_len;
|
||||
#define header_start 0
|
||||
#define header_len 68
|
||||
|
||||
uint32_t i, j;
|
||||
char *ct1 = NULL, *ct2 = NULL; // char temp 1 and 2
|
||||
uint32_t it = 0; // integer temp
|
||||
|
||||
// input validation
|
||||
if( argc != 2 ) {
|
||||
fprintf(stderr, "usage: parser dbfile\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// open file
|
||||
fd = fopen(argv[1], "r");
|
||||
if( fd == NULL ) {
|
||||
fprintf(stderr, "Could not open file \"%s\"\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// read the header
|
||||
ct1 = realloc(ct1, 4); if( ct1 == NULL ) return mem_failure();
|
||||
if( fread(ct1, 4, 1, fd) != 1 ) return read_failure(fd);
|
||||
if( ct1[0] != 'R' || ct1[1] != 'D' || ct1[2] != 'B' ) {
|
||||
printf("No header found\n");
|
||||
return 1;
|
||||
}
|
||||
if( ct1[3] != 0x03 ) {
|
||||
printf("Not version 3\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( fread(&artist_start, 4, 1, fd) != 1 ) return read_failure(fd); artist_start = BE32(artist_start);
|
||||
if( fread(&album_start, 4, 1, fd) != 1 ) return read_failure(fd); album_start = BE32(album_start);
|
||||
if( fread(&song_start, 4, 1, fd) != 1 ) return read_failure(fd); song_start = BE32(song_start);
|
||||
if( fread(&file_start, 4, 1, fd) != 1 ) return read_failure(fd); file_start = BE32(file_start);
|
||||
|
||||
if( fread(&artist_count, 4, 1, fd) != 1 ) return read_failure(fd); artist_count = BE32(artist_count);
|
||||
if( fread(&album_count, 4, 1, fd) != 1 ) return read_failure(fd); album_count = BE32(album_count);
|
||||
if( fread(&song_count, 4, 1, fd) != 1 ) return read_failure(fd); song_count = BE32(song_count);
|
||||
if( fread(&file_count, 4, 1, fd) != 1 ) return read_failure(fd); file_count = BE32(file_count);
|
||||
|
||||
if( fread(&artist_len, 4, 1, fd) != 1 ) return read_failure(fd); artist_len = BE32(artist_len);
|
||||
if( fread(&album_len, 4, 1, fd) != 1 ) return read_failure(fd); album_len = BE32(album_len);
|
||||
if( fread(&song_len, 4, 1, fd) != 1 ) return read_failure(fd); song_len = BE32(song_len);
|
||||
if( fread(&genre_len, 4, 1, fd) != 1 ) return read_failure(fd); genre_len = BE32(genre_len);
|
||||
if( fread(&file_len, 4, 1, fd) != 1 ) return read_failure(fd); file_len = BE32(file_len);
|
||||
|
||||
if( fread(&song_array_count, 4, 1, fd) != 1 ) return read_failure(fd); song_array_count = BE32(song_array_count);
|
||||
if( fread(&album_array_count, 4, 1, fd) != 1 ) return read_failure(fd); album_array_count = BE32(album_array_count);
|
||||
|
||||
if( fread(ct1, 4, 1, fd) != 1 ) return read_failure(fd);
|
||||
|
||||
// print header info
|
||||
printf("HEADER");
|
||||
printf("\n Artist start: 0x%08x = %u", artist_start, artist_start);
|
||||
if( artist_start != header_start + header_len )
|
||||
printf(" should be 0x%08x = %u", header_start + header_len, header_start + header_len);
|
||||
printf("\n Album start: 0x%08x = %u", album_start, album_start);
|
||||
if( album_start != artist_start + artist_count*(artist_len + 4*album_array_count) )
|
||||
printf(" should be 0x%08x = %u", artist_start + artist_count*(artist_len + 4*album_array_count),
|
||||
artist_start + artist_count*(artist_len + 4*album_array_count));
|
||||
printf("\n Song start: 0x%08x = %u", song_start, song_start);
|
||||
if( song_start != album_start + album_count*(album_len + 4 + 4*song_array_count) )
|
||||
printf(" should be 0x%08x = %u", album_start + album_count*(album_len + 4 + 4*song_array_count),
|
||||
album_start + album_count*(album_len + 4 + 4*song_array_count));
|
||||
printf("\n File start: 0x%08x = %u", file_start, file_start);
|
||||
if( file_start != song_start + song_count*(song_len + genre_len + 24) )
|
||||
printf(" should be 0x%08x = %u", song_start + song_count*(song_len + genre_len + 24),
|
||||
song_start + song_count*(song_len + genre_len + 24));
|
||||
|
||||
printf("\n Artist count: 0x%08x = %u\n", artist_count, artist_count);
|
||||
printf(" Album count: 0x%08x = %u\n", album_count, album_count);
|
||||
printf(" Song count: 0x%08x = %u\n", song_count, song_count);
|
||||
printf(" File count: 0x%08x = %u\n", file_count, file_count);
|
||||
|
||||
printf(" Artist len: 0x%08x = %u\n", artist_len, artist_len);
|
||||
printf(" Album len: 0x%08x = %u\n", album_len, album_len);
|
||||
printf(" Song len: 0x%08x = %u\n", song_len, song_len);
|
||||
printf(" Genre len: 0x%08x = %u\n", genre_len, genre_len);
|
||||
printf(" File len: 0x%08x = %u\n", file_len, file_len);
|
||||
|
||||
printf(" Song[] count: 0x%08x = %u\n", song_array_count, song_array_count);
|
||||
printf(" Album[] count: 0x%08x = %u\n", album_array_count, album_array_count);
|
||||
|
||||
printf(" Reserved: 0x%08x\n", ct1[0] & 0xFFFFFFFE);
|
||||
printf(" Rundb dirty: 0x%01x\n", ct1[3] & 0x01);
|
||||
|
||||
// iterate over artists:
|
||||
ct1 = realloc(ct1, artist_len); if( ct1 == NULL && artist_count!=0 ) return mem_failure();
|
||||
for(i=0; i < artist_count; i++) {
|
||||
printf("ARTIST %u/%u (offset 0x%08lx)\n", i, artist_count, (unsigned long)ftell(fd));
|
||||
|
||||
if( fread(ct1, artist_len, 1, fd) != 1 ) return read_failure(fd);
|
||||
printf(" Name: \"%s\"\n", ct1);
|
||||
|
||||
printf(" Albums:\n");
|
||||
for(j=0; j < album_array_count; j++) {
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Offset 0x%08x = ", it);
|
||||
if(it != 0) {
|
||||
printf("item %u\n", (it - album_start) / (album_len + 4 + 4*song_array_count));
|
||||
} else {
|
||||
printf("padding\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over albums:
|
||||
ct1 = realloc(ct1, album_len); if( ct1 == NULL && album_count!=0) return mem_failure();
|
||||
for(i=0; i < album_count; i++) {
|
||||
printf("ALBUM %u/%u (offset 0x%08lx)\n", i, album_count, (unsigned long)ftell(fd));
|
||||
|
||||
if( fread(ct1, album_len, 1, fd) != 1 ) return read_failure(fd);
|
||||
printf(" Name: \"%s\"\n", ct1);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Artist offset: 0x%08x = item %u\n", it, (it - artist_start) / (artist_len + 4*album_array_count));
|
||||
|
||||
printf(" Songs:\n");
|
||||
for(j=0; j < song_array_count; j++) {
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Offset 0x%08x = ", it);
|
||||
if(it != 0) {
|
||||
printf("item %u\n", (it - song_start) / (song_len + genre_len + 24));
|
||||
} else {
|
||||
printf("padding\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iterate over songs:
|
||||
ct1 = realloc(ct1, song_len); if( ct1 == NULL && song_count!=0) return mem_failure();
|
||||
ct2 = realloc(ct2, genre_len); if( ct2 == NULL && song_count!=0) return mem_failure();
|
||||
for(i=0; i < song_count; i++) {
|
||||
printf("SONG %u/%u (offset 0x%08lx)\n", i, song_count, (unsigned long)ftell(fd));
|
||||
|
||||
if( fread(ct1, song_len, 1, fd) != 1 ) return read_failure(fd);
|
||||
printf(" Name: \"%s\"\n", ct1);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Artist offset: 0x%08x = item %u\n", it, (it - artist_start) / (artist_len + 4*album_array_count));
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Album offset: 0x%08x = item %u\n", it, (it - album_start) / (album_len + 4 + 4*song_array_count));
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" File offset: 0x%08x = item %u\n", it, (it - file_start) / (file_len + 12));
|
||||
|
||||
if( fread(ct2, genre_len, 1, fd) != 1 ) return read_failure(fd);
|
||||
printf(" Genre: \"%s\"\n", ct2);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Bitrate: 0x%04x = %u\n", (it & 0xFFFF0000) >> 16, (it & 0xFFFF0000) >> 16);
|
||||
printf(" Year: 0x%04x = %u\n", it & 0x0000FFFF, it & 0x0000FFFF);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Playtime: 0x%08x = %u\n", it, it);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Track: 0x%04x = %u\n", (it & 0xFFFF0000) >> 16, (it & 0xFFFF0000) >> 16);
|
||||
printf(" Samplerate: 0x%04x = %u\n", it & 0x0000FFFF, it & 0x0000FFFF);
|
||||
}
|
||||
|
||||
// iterate over file:
|
||||
ct1 = realloc(ct1, file_len); if( ct1 == NULL && file_count!=0) return mem_failure();
|
||||
for(i=0; i < file_count; i++) {
|
||||
printf("FILE %u/%u (offset 0x%08lx)\n", i, file_count, (unsigned long)ftell(fd));
|
||||
|
||||
if( fread(ct1, file_len, 1, fd) != 1 ) return read_failure(fd);
|
||||
printf(" Name: \"%s\"\n", ct1);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Hash: 0x%08x = %u\n", it, it);
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Song offset: 0x%08x = item %u\n", it, (it - song_start) / (song_len + genre_len + 24));
|
||||
|
||||
if( fread(&it, 4, 1, fd) != 1 ) return read_failure(fd); it = BE32(it);
|
||||
printf(" Rundb offset: 0x%08x = %u\n", it, it);
|
||||
}
|
||||
|
||||
// close the file
|
||||
if( fclose(fd) != 0 ) {
|
||||
fprintf(stderr, "Could not close file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
450
apps/tagdb/song.c
Normal file
450
apps/tagdb/song.c
Normal file
|
@ -0,0 +1,450 @@
|
|||
#include "malloc.h" // realloc() and free()
|
||||
#include <string.h> // strncasecmp()
|
||||
|
||||
#include "song.h"
|
||||
|
||||
// how is our flag organized?
|
||||
#define FLAG ( 0xCF )
|
||||
#define FLAG_VALID(flag) (flag == 0xCF)
|
||||
|
||||
static int do_resize(struct song_entry *e, const uint32_t name_len, const uint32_t genre_len, const int zero_fill);
|
||||
|
||||
struct song_entry* new_song_entry(const uint32_t name_len, const uint32_t genre_len) {
|
||||
// Start my allocating memory
|
||||
struct song_entry *e = (struct song_entry*)malloc(sizeof(struct song_entry));
|
||||
if( e == NULL ) {
|
||||
DEBUGF("new_song_entry: could not allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We begin empty
|
||||
e->name = NULL;
|
||||
e->size.name_len = 0;
|
||||
|
||||
e->artist = 0;
|
||||
e->album = 0;
|
||||
e->file = 0;
|
||||
|
||||
e->genre = NULL;
|
||||
e->size.genre_len = 0;
|
||||
|
||||
e->bitrate = 0;
|
||||
e->year = 0;
|
||||
e->playtime = 0;
|
||||
e->track = 0;
|
||||
e->samplerate = 0;
|
||||
|
||||
e->flag = FLAG;
|
||||
|
||||
// and resize to the requested size
|
||||
if( do_resize(e, name_len, genre_len, 1) ) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
int song_entry_destruct(struct song_entry *e) {
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
free(e->name);
|
||||
free(e->genre);
|
||||
|
||||
free(e);
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
static int do_resize(struct song_entry *e, const uint32_t name_len, const uint32_t genre_len, const int zero_fill) {
|
||||
void* temp;
|
||||
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// begin with name
|
||||
if( name_len != e->size.name_len ) {
|
||||
temp = realloc(e->name, name_len);
|
||||
if(temp == NULL && name_len > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("song_entry_resize: out of memory to resize name\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->name = (char*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.name_len; i<name_len; i++)
|
||||
e->name[i] = (char)0x00;
|
||||
}
|
||||
|
||||
e->size.name_len = name_len;
|
||||
}
|
||||
|
||||
// now the genre
|
||||
if( genre_len != e->size.genre_len ) {
|
||||
temp = realloc(e->genre, genre_len);
|
||||
if(temp == NULL && genre_len > 0) { // if realloc(,0) don't complain about NULL-pointer
|
||||
DEBUGF("song_entry_resize: out of memory to resize genre\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
e->genre = (char*)temp;
|
||||
|
||||
// if asked, fill it with zero's
|
||||
if( zero_fill ) {
|
||||
uint32_t i;
|
||||
for(i=e->size.genre_len; i<genre_len; i++)
|
||||
e->genre[i] = (char)0x00;
|
||||
}
|
||||
|
||||
e->size.genre_len = genre_len;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int song_entry_resize(struct song_entry *e, const uint32_t name_len, const uint32_t genre_len) {
|
||||
return do_resize(e, name_len, genre_len, 1);
|
||||
}
|
||||
|
||||
int song_entry_serialize(FILE *fd, const struct song_entry *e) {
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// First byte we write is a flag-byte to indicate this is a valid record
|
||||
if( fwrite(&e->flag, 1, 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write flag-byte\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Write the length of the name field
|
||||
if( fwrite(&e->size.name_len, sizeof(e->size.name_len), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write name_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// now the name field itself
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("song_entry_serialize: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Artist field
|
||||
if( fwrite(&e->artist, sizeof(e->artist), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write artist\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Album field
|
||||
if( fwrite(&e->album, sizeof(e->album), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write album\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// File field
|
||||
if( fwrite(&e->file, sizeof(e->file), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write file\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// length of genre field
|
||||
if( fwrite(&e->size.genre_len, sizeof(e->size.genre_len), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write genre_len\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// genre itself
|
||||
if( fwrite(e->genre, 1, e->size.genre_len, fd) != e->size.genre_len ) {
|
||||
DEBUGF("song_entry_serialize: failed to write genre\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Bitrate field
|
||||
if( fwrite(&e->bitrate, sizeof(e->bitrate), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write bitrate\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Year field
|
||||
if( fwrite(&e->year, sizeof(e->year), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write year\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Playtime field
|
||||
if( fwrite(&e->playtime, sizeof(e->playtime), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write playtime\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Track field
|
||||
if( fwrite(&e->track, sizeof(e->track), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write track\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Samplerate field
|
||||
if( fwrite(&e->samplerate, sizeof(e->samplerate), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_serialize: failed to write samplerate\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int song_entry_unserialize(struct song_entry **dest, FILE *fd) {
|
||||
uint32_t length;
|
||||
struct song_entry* e;
|
||||
|
||||
assert(dest != NULL);
|
||||
assert(fd != NULL);
|
||||
|
||||
// Allocate memory
|
||||
e = new_song_entry(0, 0);
|
||||
if( e == NULL ) {
|
||||
DEBUGF("song_entry_unserialize: could not create new song_entry\n");
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// First we read the length of the name field
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read name_len\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(e, length, 0, 0) ) {
|
||||
DEBUGF("song_entry_unserialize: failed to allocate memory for name\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read name\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Artist field
|
||||
if( fread(&e->artist, sizeof(e->artist), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read artist\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Album field
|
||||
if( fread(&e->album, sizeof(e->album), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read album\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// File field
|
||||
if( fread(&e->file, sizeof(e->file), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read file\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Next the length of genre
|
||||
if( fread(&length, sizeof(length), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read genre_len\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// allocate memory for the upcomming name-field
|
||||
if( do_resize(e, e->size.name_len, length, 0) ) {
|
||||
DEBUGF("song_entry_unserialize: failed to allocate memory for song\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_MALLOC;
|
||||
}
|
||||
|
||||
// read it in
|
||||
if( fread(e->genre, 1, e->size.genre_len, fd) != e->size.genre_len ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read genre\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Bitrate field
|
||||
if( fread(&e->bitrate, sizeof(e->bitrate), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read bitrate\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Year field
|
||||
if( fread(&e->year, sizeof(e->year), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read year\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Playtime field
|
||||
if( fread(&e->playtime, sizeof(e->playtime), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read playtime\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Track field
|
||||
if( fread(&e->track, sizeof(e->track), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read track\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// Samplerate field
|
||||
if( fread(&e->samplerate, sizeof(e->samplerate), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_unserialize: failed to read samplerate\n");
|
||||
song_entry_destruct(e);
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
*dest = e;
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int song_entry_write(FILE *fd, struct song_entry *e, struct song_size *s) {
|
||||
uint32_t be32;
|
||||
uint16_t be16;
|
||||
char pad = 0x00;
|
||||
|
||||
assert(fd != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
|
||||
// song name
|
||||
if( fwrite(e->name, 1, e->size.name_len, fd) != e->size.name_len ) {
|
||||
DEBUGF("song_entry_write: failed to write name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// pad the rest (abuse be32 for counter)
|
||||
be32 = e->size.name_len;
|
||||
while( s != NULL && s->name_len > be32) {
|
||||
if( fwrite(&pad, 1, 1, fd) == 1 ) {
|
||||
be32++;
|
||||
} else {
|
||||
DEBUGF("genre_entry_write: failed to pad name\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// artist
|
||||
be32 = BE32(e->artist);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write artist\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// album
|
||||
be32 = BE32(e->album);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write album\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// file
|
||||
be32 = BE32(e->file);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write file\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// genre
|
||||
if( fwrite(e->genre, 1, e->size.genre_len, fd) != e->size.genre_len ) {
|
||||
DEBUGF("song_entry_write: failed to write genre\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
// pad the rest (abuse be32 for counter)
|
||||
be32 = e->size.genre_len;
|
||||
while( s != NULL && s->genre_len > be32) {
|
||||
if( fwrite(&pad, 1, 1, fd) == 1 ) {
|
||||
be32++;
|
||||
} else {
|
||||
DEBUGF("genre_entry_write: failed to pad genre\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
// bitrate
|
||||
be16 = BE16(e->bitrate);
|
||||
if( fwrite(&be16, sizeof(be16), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write bitrate\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// year
|
||||
be16 = BE16(e->year);
|
||||
if( fwrite(&be16, sizeof(be16), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write year\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// playtime
|
||||
be32 = BE32(e->playtime);
|
||||
if( fwrite(&be32, sizeof(be32), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write playtime\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// track
|
||||
be16 = BE16(e->track);
|
||||
if( fwrite(&be16, sizeof(be16), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write track\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
// samplerate
|
||||
be16 = BE16(e->samplerate);
|
||||
if( fwrite(&be16, sizeof(be16), 1, fd) != 1 ) {
|
||||
DEBUGF("song_entry_write: failed to write samplerate\n");
|
||||
return ERR_FILE;
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
inline int song_entry_compare(const struct song_entry *a, const struct song_entry *b) {
|
||||
assert(a != NULL);
|
||||
assert(b != NULL);
|
||||
return strncasecmp(a->name, b->name, (a->size.name_len <= b->size.name_len ? a->size.name_len : b->size.name_len) );
|
||||
}
|
||||
|
||||
struct song_size* new_song_size() {
|
||||
struct song_size *s;
|
||||
s = (struct song_size*)malloc(sizeof(struct song_size));
|
||||
if( s == NULL ) {
|
||||
DEBUGF("new_song_size: failed to allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
s->name_len = 0;
|
||||
s->genre_len = 0;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
inline uint32_t song_size_get_length(const struct song_size *size) {
|
||||
assert(size != NULL);
|
||||
return size->name_len + size->genre_len + 6*4;
|
||||
}
|
||||
|
||||
inline int song_size_max(struct song_size *s, const struct song_entry *e) {
|
||||
assert(s != NULL);
|
||||
assert(e != NULL);
|
||||
assert(FLAG_VALID(e->flag));
|
||||
s->name_len = ( s->name_len >= e->size.name_len ? s->name_len : e->size.name_len );
|
||||
s->genre_len = ( s->genre_len >= e->size.genre_len ? s->genre_len : e->size.genre_len );
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
int song_size_destruct(struct song_size *s) {
|
||||
assert(s != NULL);
|
||||
// nothing to do...
|
||||
free(s);
|
||||
return ERR_NONE;
|
||||
}
|
93
apps/tagdb/song.h
Normal file
93
apps/tagdb/song.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
#ifndef __SONG_H__
|
||||
#define __SONG_H__
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct song_entry {
|
||||
char* name; // song name
|
||||
|
||||
uint32_t artist; // pointer to artist
|
||||
uint32_t album; // pointer to album
|
||||
uint32_t file; // pointer to file
|
||||
|
||||
char* genre; // genre
|
||||
|
||||
uint16_t bitrate; // bitrate (-1 = VBR or unknown)
|
||||
uint16_t year;
|
||||
uint32_t playtime; // in seconds
|
||||
uint16_t track;
|
||||
uint16_t samplerate; // in Hz
|
||||
|
||||
struct song_size {
|
||||
uint32_t name_len; // must be mulitple of 4
|
||||
uint32_t genre_len; // must be multiple of 4
|
||||
} size;
|
||||
unsigned char flag; // flags
|
||||
};
|
||||
|
||||
struct song_entry* new_song_entry(const uint32_t name_len, const uint32_t genre_len);
|
||||
/* Creates a new song_entry with the specified sizes
|
||||
* Returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
int song_entry_destruct(struct song_entry *e);
|
||||
/* Destructs the given song_entry and free()'s it's memory
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
inline int song_entry_resize(struct song_entry *e, const uint32_t name_len, const uint32_t genre_len);
|
||||
/* Change the size of the entry
|
||||
* returns 0 on succes, 1 on failure
|
||||
*/
|
||||
|
||||
int song_entry_serialize(FILE *fd, const struct song_entry *e);
|
||||
/* Serializes the entry in the file at the current position
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
int song_entry_unserialize(struct song_entry* *e, FILE *fd);
|
||||
/* Unserializes an entry from file into a new structure
|
||||
* The address of the structure is saved into *e
|
||||
* returns 0 on success
|
||||
* 1 on malloc() failure
|
||||
* 2 on fread() failure
|
||||
*/
|
||||
|
||||
int song_entry_write(FILE *fd, struct song_entry *e, struct song_size *s);
|
||||
/* Writes the entry to file in the final form
|
||||
* returns 0 (0) on success, 1 (1) on failure
|
||||
*/
|
||||
|
||||
inline int song_entry_compare(const struct song_entry *a, const struct song_entry *b);
|
||||
/* Compares 2 entries
|
||||
* When a < b it returns <0
|
||||
* a = b 0
|
||||
* a > b >0
|
||||
*/
|
||||
|
||||
struct song_size* new_song_size();
|
||||
/* Creates a new size structure
|
||||
* returns a pointer to the structure on success,
|
||||
* NULL on failure
|
||||
*/
|
||||
|
||||
inline uint32_t song_size_get_length(const struct song_size *size);
|
||||
/* Calculates the length of the entry when written by song_entry_write()
|
||||
* returns the length on success, 0xffffffff on failure
|
||||
*/
|
||||
|
||||
inline int song_size_max(struct song_size *s, const struct song_entry *e);
|
||||
/* Updates the song_size structure to contain the maximal lengths of either
|
||||
* the original entry in s, or the entry e
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
int song_size_destruct(struct song_size *s);
|
||||
/* destructs the song_size structure
|
||||
* returns 0 on success, 1 on failure
|
||||
*/
|
||||
|
||||
#endif
|
11
apps/tagdb/tag_dummy.c
Normal file
11
apps/tagdb/tag_dummy.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "config.h"
|
||||
#include "malloc.h"
|
||||
|
||||
#include "tag_dummy.h"
|
||||
#include <string.h>
|
||||
|
||||
int tag_dummy(char *file, struct tag_info *t) {
|
||||
t->song = malloc(strlen(file)+1);
|
||||
strcpy(t->song, file);
|
||||
return ERR_NONE;
|
||||
}
|
3
apps/tagdb/tag_dummy.h
Normal file
3
apps/tagdb/tag_dummy.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "db.h"
|
||||
|
||||
int tag_dummy(char *file, struct tag_info *t);
|
16
apps/tagdb/unique.c
Normal file
16
apps/tagdb/unique.c
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "unique.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
char *create_unique_name(char *buffer, const char *prefix, const char *suffix, int digits) {
|
||||
static unsigned long i=0;
|
||||
|
||||
strcpy(buffer, prefix);
|
||||
sprintf(buffer+strlen(prefix), "%05lu", i);
|
||||
strcat(buffer, suffix);
|
||||
|
||||
i++;
|
||||
|
||||
return buffer;
|
||||
}
|
6
apps/tagdb/unique.h
Normal file
6
apps/tagdb/unique.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef __UNIQUE_H__
|
||||
#define __UNIQUE_H__
|
||||
|
||||
char *create_unique_name(char *buffer, const char *prefix, const char *suffix, int digits);
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue