#include #include #include #include #include #include #include #include #include #include #include /* TEMP */ #include #include "mple.h" #include "mple_v2.h" #include "common.h" /* * scan for any available devices. currently only scans for v1. The * MAGIC parameter is a string that HAL identifies the device with, * e.g. "NETWORK WALKMAN". */ GList *nw_get_devs( gchar *magic ) { GList *retval = NULL; retval = mple_get_nw_devs( magic, retval ); /* add omg scan */ return retval; } /* * initialise the device mounted at DEVPATH using API */ nw_device_t *nw_dev_init( gchar *devpath, nw_api_t api ) { nw_device_t *nw = NULL; gchar *test = NULL; switch( api ) { case NW_GUESS: dprintf( stderr, "guessing device type for %s: ", devpath ); /* XXX needs more work as it doesn't deal with case-sensitivity very well and also depends on the device having been written to at least once (i.e. the omgaudio dir is present) */ test = g_strdup_printf( "%s/omgaudio", devpath ); if ( g_file_test( test, G_FILE_TEST_EXISTS )) { nw = mple_v2_dev_init( devpath ); } else { g_free( test ); test = g_strdup_printf( "%s/OMGAUDIO", devpath ); if ( g_file_test( test, G_FILE_TEST_EXISTS )) { nw = mple_v2_dev_init( devpath ); } } g_free( test ); if ( nw == NULL ) { if (( nw = mple_dev_init( devpath )) == NULL ) { nw = mple_v2_dev_init( devpath ); } } if ( nw != NULL ) { dprintf( stderr, "%s\n", nw->api == NW_MPLE ? "v1" : "v2" ); } break; case NW_MPLE: nw = mple_dev_init( devpath ); break; case NW_MPLE_V2: nw = mple_v2_dev_init( devpath ); break; default: break; } return nw; } gboolean nw_dev_sync( nw_device_t *dev ) { if ( dev->dev_sync != NULL ) { return dev->dev_sync( dev ); } else { errno = ENOSYS; return FALSE; } } /* * cleanup function for the attributes hashtable */ static void nw_free_attr( gpointer key, gpointer value, gpointer userdata ) { nw_attr_t *attr = value; dprintf( stderr, "freeing %s attribute\n", attr->name ? attr->name : "unknown" ); switch( attr->type ) { case NW_STRING: g_free( attr->data.gc ); break; case NW_POINTER: g_free( attr->data.ptr ); break; case NW_BOOLEAN: case NW_FUNCTION: break; default: fprintf( stderr, "don't know how to free whatever this is (%s)\n", attr->name ? attr->name : "no name" ); break; } if ( attr->name != NULL ) { g_free( attr->name ); } g_free( value ); } static void nw_dump_attr( gpointer key, gpointer value, gpointer userdata ) { nw_attr_t *attr = value; dprintf( stderr, "- '%s' (", attr->name ); switch( attr->type ) { case NW_STRING: dprintf( stderr, "string, \"%s\")\n", attr->data.gc ); break; case NW_POINTER: dprintf( stderr, "pointer, %p)\n", attr->data.ptr ); break; case NW_BOOLEAN: dprintf( stderr, "boolean)\n" ); break; case NW_FUNCTION: dprintf( stderr, "function)\n" ); break; default: dprintf( stderr, "unknown)\n" ); break; } } /* * free up the specified device DEV */ void nw_dev_free( nw_device_t *dev ) { dprintf( stderr, "%s: enter\n", __FUNCTION__ ); if ( dev != NULL ) { if ( dev->dirty != FALSE ) { dprintf( stderr, "%s: device needs flushing!\n", __FUNCTION__ ); nw_dev_sync( dev ); } /* Call private cleanup */ if ( dev->dev_free != NULL ) { dev->dev_free( dev ); } if ( dev->folderlist ) { GList *node = dev->folderlist; nw_folder_t *nfptr; while( node ) { nfptr = node->data; if ( nfptr->tracklist != NULL ) { GList *tnode = nfptr->tracklist; while( tnode ) { nw_track_t *ntptr = tnode->data; nw_track_free( ntptr ); tnode = g_list_next( tnode ); } g_list_free( nfptr->tracklist ); } if ( nfptr->name != NULL ) { g_free( nfptr->name ); } g_free( nfptr ); node = g_list_next( node ); } g_list_free( dev->folderlist ); dev->folderlist = NULL; } if ( dev->attributes != NULL ) { g_hash_table_foreach( dev->attributes, nw_free_attr, NULL ); g_hash_table_destroy( dev->attributes ); } g_free( dev ); } dprintf( stderr, "%s: exit\n", __FUNCTION__ ); } /* * Get/Set attributes */ gboolean nw_set_attr( nw_device_t *dev, gchar *key, nw_attr_t *data ) { if ( dev == NULL ) { errno = EINVAL; return FALSE; } if ( dev->attributes == NULL ) { dev->attributes = g_hash_table_new( g_str_hash, g_str_equal ); if ( dev->attributes == NULL ) { return FALSE; } } /* a bit silly */ if ( data == NULL ) { data = g_new0( nw_attr_t, 1 ); data->type = NW_POINTER; data->data.ptr = NULL; } if ( data->name == NULL ) { data->name = g_strdup( key ); } /* replace might be a bad idea here - does it free the original data? */ g_hash_table_replace( dev->attributes, key, data ); return TRUE; } nw_attr_t *nw_get_attr( nw_device_t *dev, gchar *key ) { gpointer retval = NULL; if ( dev == NULL ) { errno = EINVAL; return NULL; } if ( dev->attributes == NULL ) { errno = ENOENT; return NULL; } retval = g_hash_table_lookup( dev->attributes, key ); if ( retval == NULL ) { errno = ENOENT; #ifdef DEBUG /* dump what's in the table if we're debugging */ dprintf( stderr, "failed to find '%s' in attributes, perhaps you meant one of these:\n", key ); g_hash_table_foreach( dev->attributes, nw_dump_attr, NULL ); #endif } else { errno = 0; } return retval; } gboolean nw_set_boolean( nw_device_t *dev, gchar *key, gboolean value ) { nw_attr_t *data = g_new0( nw_attr_t, 1 ); if ( strcmp( key, "autosync" ) == 0 ) { dev->autosync = value; return TRUE; } else { data->type = NW_BOOLEAN; data->data.gb = value; return nw_set_attr( dev, key, data ); } } gboolean nw_get_boolean( nw_device_t *dev, gchar *key ) { nw_attr_t *data; if ( strcmp( key, "autosync" )) { return dev->autosync; } else { data = nw_get_attr( dev, key ); if ( data != NULL ) { return data->data.gb; } return FALSE; } } void nw_set_progress_func( nw_device_t *dev, void (*progress)( double percent, void *context )) { dev->progress = progress; } /* * NWFILE stuff */ NWFILE *nw_fopen( nw_device_t *dev, guint32 folder, guint32 tracknum, const gchar *filename, gchar *mode ) { if ( dev == NULL ) { errno = EINVAL; return NULL; } if ( tracknum == 0 ) { if ( dev->get_next_track_number ) { tracknum = dev->get_next_track_number( dev ); if ( !tracknum ) { dprintf( stderr, "failed to get a track number: %s\n", strerror( errno )); return NULL; } } else { errno = EINVAL; return NULL; } } if ( dev->fopen != NULL ) { return dev->fopen( dev, folder, tracknum, filename, mode ); } else { errno = ENOSYS; return NULL; } } int nw_fclose( NWFILE *nfp ) { if ( nfp == NULL ) { errno = EINVAL; return 0; } if ( nfp->tptr != NULL && nfp->tptr->device != NULL ) { if ( nfp->tptr->device->fclose != NULL ) { return nfp->tptr->device->fclose( nfp ); } else { errno = ENOSYS; return 0; } } errno = EINVAL; return 0; } int nw_stat( nw_device_t *dev, guint32 tracknum, struct stat *buf ) { if ( dev->stat != NULL ) { return dev->stat( dev, tracknum, buf ); } else { errno = ENOSYS; return -1; } } int nw_fseek( NWFILE *nfp, long offset, int whence ) { if ( nfp == NULL ) { errno = EINVAL; return 0; } if ( nfp->tptr != NULL && nfp->tptr->device != NULL ) { if ( nfp->tptr->device->fseek != NULL ) { return nfp->tptr->device->fseek( nfp, offset, whence ); } else { errno = ENOSYS; return -1; } } errno = EINVAL; return -1; } long nw_ftell( NWFILE *nfp ) { if ( nfp == NULL ) { errno = EINVAL; return 0; } if ( nfp->tptr != NULL && nfp->tptr->device != NULL ) { if ( nfp->tptr->device->ftell != NULL ) { return nfp->tptr->device->ftell( nfp ); } else { errno = ENOSYS; return -1; } } errno = EINVAL; return -1; } size_t nw_fread( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { if ( nfp == NULL ) { errno = EINVAL; return 0; } if ( nfp->tptr != NULL && nfp->tptr->device != NULL ) { if ( nfp->tptr->device->fread != NULL ) { return nfp->tptr->device->fread( ptr, size, nmemb, nfp ); } else { errno = ENOSYS; return 0; } } errno = EINVAL; return 0; } size_t nw_fwrite( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { size_t retval = 0; guint8 *buffer = NULL; signed long taglen; struct frame *frm; if ( nfp == NULL ) { errno = EBADF; goto out; } if ( nfp->tptr != NULL && nfp->tptr->device != NULL ) { if ( nfp->tptr->device->fwrite != NULL ) { nfp->buffer = g_realloc( nfp->buffer, nfp->bufsize + ( size * nmemb )); memcpy( &nfp->buffer[nfp->bufsize], ptr, size * nmemb ); nfp->bufsize += ( size * nmemb ); nfp->tptr->device->dirty = TRUE; if ( !nfp->midframe ) { if ( nfp->bufsize < ID3_TAG_QUERYSIZE ) { /* we need more data! */ goto fake_ok_out; } /* scan for ID3 tagging Note, this doesn't actually reset the data pointer; the idea is to just scan the stream for ID3 tagging, then go back and rescan the same block of data for MP3 frames below. This allows for the possibility of some insane setup like [MP3][ID3][MP3], which I'm not even sure is valid, but be flexible about what you accept and all that. */ for ( buffer = nfp->buffer; buffer < nfp->buffer + nfp->bufsize - ID3_TAG_QUERYSIZE; buffer++ ) { if (( taglen = id3_tag_query( buffer, nfp->bufsize - ( buffer - nfp->buffer ))) != 0 ) { if ( taglen < 0 ) { /* this isn't catered for yet. what we'd need to do is: - save our place in the file - seek backwards taglen bytes - deobfuscate - remove bytes from stream The problem is that since the data isn't mp3 frames, it's probably not there to be retrieved. */ fprintf( stderr, "found id3v2.4 footer, which we don't deal with\n" ); } else { /* we may not have got all the tagging, but I want to make sure it's really a tag and not a fortuitious sequence of bytes. However, I'm not clear on how to do this! */ /* this doesn't appear to be a problem, as it happens, or if it is the mp3 code doesn't cater for it either */ if ( buffer + taglen > nfp->buffer + nfp->bufsize ) { dprintf( stderr, "not enough data to retrieve tags (need %d more)\n", ( buffer + taglen ) - ( nfp->buffer + nfp->bufsize )); goto fake_ok_out; } else { struct id3_tag *tag; tag = id3_tag_parse( buffer, taglen ); if ( tag == NULL || id3_tag_findframe( tag, "", 0 ) == NULL ) { dprintf( stderr, "no/null tag found\n" ); if ( tag != NULL ) { id3_tag_delete( tag ); tag = NULL; } continue; } libnw_update_metadata( nfp->tptr, tag ); id3_tag_delete( tag ); buffer += taglen; } } } } } /* now write to the file */ /* load up the buffer */ nfp->mp3f->fb = nfp->buffer; nfp->mp3f->fb_ptr = 0; nfp->mp3f->fb_size = nfp->bufsize; /* Read the frames, counting as we go */ while( mpg123_read_frame( nfp->mp3f, &frm )) { gchar hdrblock[4]; /* if we get at least one frame, then it's an MP3 file. */ nfp->format_ok = TRUE; /* however, it also needs to have a 44k1 sampling and a 8-320kbit encoding rate */ /* XXX verify that this also applies to v2 - might need a "check valid frame" function */ if (( mpg123_freqs[frm->sampling_frequency] != 44100 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] < 8 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] > 320 )) { if ( mpg123_freqs[frm->sampling_frequency] != 44100 ) { dprintf( stderr, "%s: invalid sample frequency %d Hz\n", __FUNCTION__, mpg123_freqs[frm->sampling_frequency] ); } else { dprintf( stderr, "%s: invalid bitrate %d\n", __FUNCTION__, tabsel_123[lsf( frm )][2][bitrate_index( frm )] ); } nfp->format_ok = FALSE; errno = EINVAL; goto bailout; } /* pull these out of the first frame */ if ( nfp->flen == 0 ) { nfp->flen = framesize( frm ); nfp->frate = tabsel_123[lsf( frm )][2][bitrate_index( frm )]; } nfp->frames++; nfp->framerates[15 - bitrate_index( frm )]++; /* write out the header - damned mpg123 code... */ hdrblock[0] = header(frm)[3]; hdrblock[1] = header(frm)[2]; hdrblock[2] = header(frm)[1]; hdrblock[3] = header(frm)[0]; if ( nfp->tptr->device->fwrite( hdrblock, 4, 1, nfp ) != 1 ) { goto bailout; } if ( nfp->tptr->device->fwrite( nfp->mp3f->bsbuf, framesize( frm ), 1, nfp ) != 1 ) { goto bailout; } } /* check for incomplete transfer of data */ if ( nfp->mp3f->fb_ptr != nfp->mp3f->fb_size ) { guint32 left = nfp->mp3f->fb_size - nfp->mp3f->fb_ptr; memmove( nfp->buffer, &nfp->buffer[nfp->bufsize - left], left ); nfp->buffer = g_realloc( nfp->buffer, left ); nfp->bufsize = left; /* not 100% sure this is in the right place - ideally we should only set it if we know there's a partial frame waiting, rather than an ID3 tag */ nfp->midframe = TRUE; goto fake_ok_out; } else { nfp->midframe = FALSE; } g_free( nfp->buffer ); nfp->buffer = NULL; nfp->bufsize = 0; /* lie to the caller about how we did */ fake_ok_out: retval = size * nmemb; out: return retval; /* fatal error with data stacked ends up here */ bailout: g_free( nfp->buffer ); nfp->buffer = NULL; nfp->bufsize = 0; goto out; } else { errno = ENOSYS; return 0; } } errno = EINVAL; return 0; } /****************************************************************************** * FOLDER MANIPULATION *****************************************************************************/ GList *nw_parse_directory( nw_device_t *dev ) { if ( dev->parse_directory != NULL ) { return dev->parse_directory( dev ); } else { errno = ENOSYS; return NULL; } } GList *nw_get_folderlist( nw_device_t *dev ) { return dev->folderlist; } /* * find on DEVICE FOLDER (starting from START) and return its * (1-based) position in the device; returns zero if not found. */ guint32 nw_get_folder( nw_device_t *dev, gchar *name, guint32 start ) { GList *node; guint32 retval = 0; errno = ENOENT; if ( start == 0 ) { errno = EINVAL; return 0; } if ( dev->folderlist == NULL ) { if ( dev->autosync && dev->parse_directory( dev ) == NULL ) { return 0; } } for ( node = g_list_nth( dev->folderlist, start - 1 ); node != NULL; node = node->next ) { retval++; if ( memcmp( name, ((nw_folder_t *)node->data)->name, strlen( name )) == 0 ) { errno = 0; return retval; } } return 0; } /* * add a folder to the DEVICE with name NAME at position POS. * POS == 0 means use the first available position. */ guint32 nw_add_folder( nw_device_t *dev, gchar *name, guint32 fpos ) { nw_folder_t *newfolder; if ( dev->folderlist == NULL ) { if ( dev->autosync && dev->parse_directory( dev ) == NULL ) { fpos = 0; if ( errno && errno != ENOENT ) { dprintf( stderr, "error reading directory: %s\n", strerror( errno )); goto out; } else { /* empty device */ } } } /* * I'm not actually sure about this; I think it's okay to have * multiple folders with the same name on the device, but it makes * it harder for filesystem-like stuff to work. */ #ifdef YAY_WE_RE_A_FILESYSTEM if ( nw_get_folder( dev, name, 1 )) { errno = EEXIST; goto out; } #endif newfolder = g_new0( nw_folder_t, 1 ); newfolder->device = dev; newfolder->name = g_strdup( name ); newfolder->tracklist = NULL; if (( fpos > g_list_length( dev->folderlist )) || ( fpos == 0 )) { dev->folderlist = g_list_append( dev->folderlist, newfolder ); fpos = g_list_length( dev->folderlist ); } else { dev->folderlist = g_list_insert( dev->folderlist, newfolder, fpos - 1 ); } dev->dirty = TRUE; /* update local data */ if ( dev->add_folder != NULL ) { dev->add_folder( dev, name, fpos ); } if ( dev->autosync && !nw_dev_sync( dev )) { /* holy crap, you're in trouble now... */ fpos = 0; goto out; } out: return fpos; } /* * Nuke a folder on DEVICE at POS * Caution: will delete folder contents also! */ gboolean nw_del_folder( nw_device_t *dev, guint32 pos ) { GList *node; if (( dev == NULL ) || ( pos == 0 )) { errno = EINVAL; return FALSE; } if (( node = dev->folderlist ) == NULL ) { if ( dev->autosync ) { if (( node = dev->parse_directory( dev )) == NULL ) { return FALSE; } } else { errno = ENOENT; return FALSE; } } if ( g_list_nth( node, pos - 1 ) != NULL ) { nw_folder_t *fptr = g_list_nth_data( node, pos - 1 ); while( g_list_length( fptr->tracklist )) { nw_track_t *tptr = fptr->tracklist->data; nw_del_track( dev, tptr->tracknum ); } /* do private ops */ if ( dev->del_folder != NULL && dev->del_folder( dev, pos ) == FALSE ) { return FALSE; } dev->folderlist = g_list_remove( dev->folderlist, fptr ); if ( fptr->name != NULL ) { g_free( fptr->name ); fptr->name = NULL; } g_free( fptr ); dev->dirty = TRUE; } if ( dev->autosync ) { return nw_dev_sync( dev ); } else { return TRUE; /* what mple_dev_sync would return */ } errno = ENOENT; return FALSE; } /* * move on DEV folder FOLDER to position POS */ guint32 nw_mv_folder( nw_device_t *dev, guint32 folder, guint32 pos ) { GList *node; if (( dev == NULL ) || ( folder == 0 )) { errno = EINVAL; return 0; } if (( node = dev->folderlist ) == NULL ) { if ( dev->autosync ) { if (( node = dev->parse_directory( dev )) == NULL ) { return 0; } } else { errno = ENOENT; return 0; } } if ( pos > g_list_length( dev->folderlist )) { pos = 0; } if ( g_list_nth( node, folder - 1 ) != NULL ) { nw_folder_t *fptr = g_list_nth_data( node, folder - 1 ); dev->folderlist = g_list_remove( dev->folderlist, fptr ); if ( pos == 0 ) { dev->folderlist = g_list_append( dev->folderlist, fptr ); pos = g_list_length( dev->folderlist ); } else { dev->folderlist = g_list_insert( dev->folderlist, fptr, pos - 1 ); } dev->dirty = TRUE; if ( dev->autosync ) { if ( nw_dev_sync( dev )) { return pos; } } else { return pos; } } else { errno = ENOENT; } return 0; } /* * Rename on DEV folder FOLDER to NEWNAME */ guint32 nw_ren_folder( nw_device_t *dev, guint32 folder, gchar *newname ) { GList *node; if (( dev == NULL ) || ( folder == 0 ) || ( newname == NULL )) { errno = EINVAL; goto out; } if (( node = dev->folderlist ) == NULL ) { if ( dev->autosync ) { if (( node = dev->parse_directory( dev )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } if ( g_list_nth( node, folder - 1 ) != NULL ) { nw_folder_t *fptr = g_list_nth_data( node, folder - 1 ); if ( fptr->name ) { g_free( fptr->name ); } fptr->name = g_strdup( newname ); dev->dirty = TRUE; if ( dev->ren_folder != NULL ) { dev->ren_folder( dev, folder, newname ); } if ( dev->autosync ) { if ( nw_dev_sync( dev )) { return folder; } } else { return folder; } } else { errno = ENOENT; } out: return 0; } /* * On DEV, add to folder FOLDER track TRACK at position POS */ guint32 nw_add_to_folder( nw_device_t *dev, guint32 folder, nw_track_t *track, guint32 tracknum, guint32 pos ) { nw_folder_t *fptr = NULL; dprintf( stderr, "%s: enter, track = %p\n", __FUNCTION__, track ); /* validate the parameters */ if (( dev == NULL ) || ( folder == 0 ) || ( track == NULL ) || ( tracknum == 0 )) { errno = EINVAL; pos = 0; goto out; } if ( dev->folderlist == NULL ) { if ( dev->autosync ) { if ( dev->parse_directory( dev ) == NULL ) { pos = 0; goto out; } } else { pos = 0; errno = ENOENT; goto out; } } if ( g_list_nth( dev->folderlist, folder - 1 ) == NULL ) { pos = 0; errno = ENOENT; goto out; } else { fptr = g_list_nth_data( dev->folderlist, folder - 1 ); } track->tracknum = tracknum; track->device = dev; track->parent = fptr; if (( pos == 0 ) || ( pos > g_list_length( fptr->tracklist ))) { fptr->tracklist = g_list_append( fptr->tracklist, track ); pos = g_list_length( fptr->tracklist ); dprintf( stderr, "appended track, pos is %d\n", pos ); } else { fptr->tracklist = g_list_insert( fptr->tracklist, track, pos - 1 ); dprintf( stderr, "inserted track, pos is %d\n", pos ); } if ( dev->add_to_folder && !dev->add_to_folder( dev, folder, track, tracknum, pos )) { pos = 0; /* xxx remove from g_list */ goto out; } dev->dirty = TRUE; if ( dev->autosync && !nw_dev_sync( dev )) { pos = 0; goto out; } out: dprintf( stderr, "%s: exit pos=%d errno=%d\n", __FUNCTION__, pos, errno ); return pos; } gchar *nw_get_folder_name( nw_folder_t *folder ) { return folder->name; } void nw_set_folder_name( nw_folder_t *folder, gchar *name ) { if ( folder->name != NULL ) { g_free( folder->name ); } folder->name = g_strdup( name ); } GList *nw_get_folder_tracklist( nw_folder_t *folder ) { return folder->tracklist; } guint32 nw_track_time( nw_track_t *track ) { nw_device_t *dev = track->device; return dev->get_track_time( track ); } guint32 nw_track_size( nw_track_t *track ) { nw_device_t *dev = track->device; return dev->get_track_size( track ); } guint32 nw_track_frames( nw_track_t *track ) { nw_device_t *dev = track->device; return dev->get_track_frames( track ); } /* * Add to DEV track FILENAME with track data TRACK, put in folder * FOLDER at pos POS */ guint32 nw_add_track( nw_device_t *dev, gchar *filename, nw_track_t *track, guint32 folder, guint32 pos ) { NWFILE *nwp = NULL; FILE *fp = NULL; guint8 *buffer = NULL; guint32 nbytes; guint32 total = 0; guint32 tracknum = 0; struct stat statbuf; dprintf( stderr, "%s: add %s to folder %d at position %d\n", __FUNCTION__, filename, folder, pos ); if ( stat( filename, &statbuf ) == -1 ) { dprintf( stderr, "%s: stat %s failed (%s)\n", __FUNCTION__, filename, strerror( errno )); goto out; } fp = fopen( filename, "rb" ); if ( fp == NULL ) { dprintf( stderr, "%s: FILE open failed (%s)\n", __FUNCTION__, strerror( errno )); goto out; } nwp = nw_fopen( dev, folder, 0, filename, "wb" ); if ( nwp == NULL ) { dprintf( stderr, "%s: NWFILE open failed (%s)\n", __FUNCTION__, strerror( errno )); goto out; } tracknum = nwp->tptr->tracknum; buffer = g_malloc0( 1024 ); while(( nbytes = fread( buffer, 1, 1024, fp )) > 0 ) { guint32 written = 0; while( written != nbytes ) { gint32 try = nw_fwrite( &buffer[written], 1, nbytes - written, nwp ); /* per definition, fwrite() returning 0 is an error */ if ( try > 0 ) { total += try; if ( dev->progress != NULL ) { nw_attr_t *context_attr = nw_get_attr( dev, "context" ); dev->progress( (double)total / (double)statbuf.st_size, context_attr ? context_attr->data.ptr : NULL ); } } else { dprintf( stderr, "%s: error writing %s (%s)\n", __FUNCTION__, filename, strerror( errno )); goto out; } written += try; } } out: if ( buffer != NULL ) { g_free( buffer ); buffer = NULL; } if ( nwp != NULL ) { nw_fclose( nwp ); } if ( fp != NULL ) { fclose( fp ); } dprintf( stderr, "%s: exit, tracknum is %d\n", __FUNCTION__, tracknum ); return tracknum; } /* * delete from device DEV track number TRACKNUM */ gboolean nw_del_track( nw_device_t *dev, guint32 tracknum ) { nw_folder_t *fptr; nw_track_t *tptr; int retval = FALSE; dprintf( stderr, "%s: enter: nuking track %d\n", __FUNCTION__, tracknum ); tptr = nw_get_track_ptr( dev, tracknum ); if ( tptr == NULL ) { dprintf( stderr, "no such track\n" ); goto out; } dev->dirty = TRUE; if ( dev->del_track && !dev->del_track( dev, tracknum )) { dprintf( stderr, "api call to del_track failed: %s\n", strerror( errno )); goto out; } /* now clean up the list */ fptr = tptr->parent; fptr->tracklist = g_list_remove( fptr->tracklist, tptr ); nw_track_free( tptr ); if ( !dev->autosync || nw_dev_sync( dev )) { if ( !dev->autosync ) { dprintf( stderr, "not syncing changes\n" ); } } else { /* flag error? wtf */ if ( dev->autosync ) { dprintf( stderr, "api call to dev_sync failed: %s\n", strerror( errno )); } } retval = TRUE; out: dprintf( stderr, "%s: exit %s\n", __FUNCTION__, retval == TRUE ? "true" : "false" ); return retval; } /* * on DEV, move track TRACKNUM to FOLDER, position POS */ guint32 nw_mv_track( nw_device_t *dev, guint32 tracknum, guint32 folder, guint32 pos ) { GList *node; guint16 retval = 0; if (( dev == NULL ) || ( tracknum == 0 )) { errno = EINVAL; goto out; } if (( node = dev->folderlist ) == NULL ) { if ( dev->autosync ) { if (( node = dev->parse_directory( dev )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } /* find the track */ while( node ) { GList *tnode; nw_folder_t *fptr = node->data; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { nw_track_t *tptr = tnode->data; nw_folder_t *nptr = fptr; if ( tptr->tracknum == tracknum ) { if ( folder != 0 ) { if ( g_list_nth( dev->folderlist, folder - 1 ) != NULL ) { nptr = g_list_nth_data( dev->folderlist, folder - 1 ); tptr->parent = nptr; } else { errno = ENOENT; goto out; } } fptr->tracklist = g_list_remove( fptr->tracklist, tptr ); if ( pos == 0 ) { nptr->tracklist = g_list_append( nptr->tracklist, tptr ); pos = g_list_length( nptr->tracklist ); } else { nptr->tracklist = g_list_insert( nptr->tracklist, tptr, pos - 1); } dev->dirty = TRUE; if ( dev->autosync ) { if ( !nw_dev_sync( dev )) { goto out; } } retval = pos; goto out; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } out: return retval; } /* * on DEV, rename track TRACKNUM according to data in TRACK. More of a * metadata update than an actual rename. */ guint32 nw_ren_track( nw_device_t *dev, guint32 tracknum, nw_track_t *track ) { GList *node; guint32 retval = 0; if (( dev == NULL ) || ( tracknum == 0 ) || ( track == NULL )) { errno = EINVAL; goto out; } if (( node = dev->folderlist ) == NULL ) { if ( dev->autosync ) { if (( node = dev->parse_directory( dev )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } /* find the track */ while( node ) { GList *tnode; nw_folder_t *fptr = node->data; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { nw_track_t *tptr = tnode->data; if ( tptr->tracknum == tracknum ) { if ( track->filename ) { if ( tptr->filename ) { g_free( tptr->filename ); } tptr->filename = g_strdup( track->filename ); } /* XXX fixme: nick tags from track and push them into tptr */ dev->dirty = TRUE; if ( dev->autosync ) { if ( !nw_dev_sync( dev )) { goto out; } } retval = tracknum; goto out; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } out: return retval; } /* utility stuff */ /* * find the track pointer on device DEV for track TRACKNUM */ nw_track_t *nw_get_track_ptr( nw_device_t *dev, guint32 tracknum ) { GList *node; nw_track_t *tptr; nw_folder_t *fptr; dprintf( stderr, "%s: enter: getting pointer for track %d\n", __FUNCTION__, tracknum ); if (( dev == NULL ) || ( tracknum == 0 )) { errno = EINVAL; goto out; } if ( dev->folderlist == NULL ) { if ( dev->autosync ) { if ( dev->parse_directory( dev ) == NULL ) { goto out; } } else { dprintf( stderr, "no folderlist AND no autosync\n" ); errno = ENOENT; goto out; } } node = dev->folderlist; while( node ) { GList *tnode; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { tptr = tnode->data; if ( tptr->tracknum == tracknum ) { dprintf( stderr, "%s: exit, found it @ %p\n", __FUNCTION__, tptr); return tptr; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } errno = ENOENT; out: dprintf( stderr, "%s: exit, %s\n", __FUNCTION__, strerror( errno )); return NULL; } /* * extract ID3 metadata from file FILENAME and return a nw_track_t * structure containing the filename, artist & name */ nw_track_t *nw_get_metadata_NAME( char *filename ) { nw_track_t *metadata = NULL; FILE *mp3 = fopen( filename, "rb" ); if ( mp3 != NULL ) { metadata = nw_get_metadata_FILE( mp3 ); if ( metadata == NULL ) { metadata = g_new0( nw_track_t, 1 ); dprintf( stderr, "metadata fetch for %s failed: %s\n", filename, strerror( errno )); } if ( metadata->filename == NULL ) { metadata->filename = g_strdup( g_basename( filename )); } fclose( mp3 ); } else { dprintf( stderr, "Failed to open %s: %s\n", filename, strerror( errno )); } return metadata; } /* * extract ID3 metadata from filehandle MP3 and return a nw_track_t * structure containing the artist & name */ nw_track_t *nw_get_metadata_FILE( FILE *mp3 ) { guint8 buffer[BUFSIZ], *tagbuf = NULL; nw_track_t *metadata = NULL; signed long taglen, id31_fudge; if ( mp3 == NULL ) { errno = EINVAL; goto out; } if ( fseek( mp3, -128, SEEK_END ) != 0 ) { goto out; } if ( fread( &buffer, 1, 128, mp3 ) != 128 ) { goto out; } if (( taglen = id3_tag_query( buffer, 128 )) != 0 ) { tagbuf = g_malloc0( 128 ); memcpy( tagbuf, buffer, 128 ); } if ( fseek( mp3, 0, SEEK_SET ) != 0 ) { goto out; } if ( fread( &buffer, 1, 10, mp3 ) != 10 ) { goto out; } id31_fudge = taglen; if (( taglen = id3_tag_query( buffer, 10 )) > 0 ) { gpointer tmp; tmp = g_realloc( tagbuf, taglen ); tagbuf = tmp; if ( taglen > 10 ) { memcpy( tagbuf, buffer, 10 ); fread( &tagbuf[10], 1, taglen - 10, mp3 ); } } else { taglen = id31_fudge; } /* at this point, any errors are parse errors in the ID3 data, which we're not overly concerned about */ metadata = g_new0( nw_track_t, 1 ); if ( taglen > 0 ) { struct id3_tag *tag; tag = id3_tag_parse( tagbuf, taglen ); libnw_update_metadata( metadata, tag ); id3_tag_delete( tag ); } out: if ( tagbuf != NULL ) { g_free( tagbuf ); } return metadata; } /* * update TRACK's metadata from TAG * * newer versions of ID3 data override older versions, i.e. a v3.1 tag * will be replaced by a v3.2 tag */ void libnw_update_metadata( nw_track_t *track, struct id3_tag *tag ) { struct id3_tag *olddata, *newdata, *nukedata = NULL; struct id3_frame *frame, *oldframe; int idx, oldidx; dprintf( stderr, "%s: enter %p %p\n", __FUNCTION__, track, tag ); if ( track->id3tag != NULL ) { if ( id3_tag_version( tag ) > id3_tag_version( track->id3tag )) { dprintf( stderr, "%s: existing tag is older than update tag\n", __FUNCTION__ ); olddata = track->id3tag; nukedata = track->id3tag; newdata = tag; dprintf( stderr, "%s: creating id3tag\n", __FUNCTION__ ); track->id3tag = id3_tag_new(); } else { olddata = tag; newdata = track->id3tag; } } else { dprintf( stderr, "%s: creating empty id3tag\n", __FUNCTION__ ); track->id3tag = id3_tag_new(); olddata = NULL; newdata = tag; } /* iterate over tags */ for ( idx = 0; ( frame = id3_tag_findframe( tag, "", idx )); idx++ ) { gboolean used = FALSE; /* whether we did something with the tag or not */ union id3_field *field = id3_frame_field( frame, 1 ); const id3_ucs4_t *text = id3_field_getstrings( field, 0 ); gchar *rtext = NULL; if ( text ) { rtext = (gchar *)id3_ucs4_utf8duplicate( text ); } dprintf( stderr, "%s: new tag ver %d.%d frame %s %s\n", __FUNCTION__, ID3_TAG_VERSION_MAJOR( id3_tag_version( tag )), ID3_TAG_VERSION_MINOR( id3_tag_version( tag )), frame->id, text ? rtext : "no text" ); if ( rtext ) { g_free( rtext ); rtext = NULL; } if ( olddata != NULL ) { for ( oldidx = 0; ( oldframe = id3_tag_findframe( track->id3tag, frame->id, oldidx )); oldidx++ ) { field = id3_frame_field( frame, 1 ); text = id3_field_getstrings( field, 0 ); if ( text ) { rtext = (gchar *)id3_ucs4_utf8duplicate( text ); } dprintf( stderr, "%s: existing tag ver %d.%d frame %s %s\n", __FUNCTION__, ID3_TAG_VERSION_MAJOR( id3_tag_version( track->id3tag )), ID3_TAG_VERSION_MINOR( id3_tag_version( track->id3tag )), oldframe->id, text ? rtext : "no text" ); if ( rtext ) { g_free( rtext ); rtext = NULL; } if ( olddata == track->id3tag ) { dprintf( stderr, "%s: nuking existing\n", __FUNCTION__ ); id3_tag_detachframe( track->id3tag, oldframe ); /* probably invalidates my iterator? */ used = TRUE; } } } if ( !used ) { id3_tag_attachframe( track->id3tag, frame ); } id3_frame_delete( frame ); } if ( nukedata != NULL ) { dprintf( stderr, "%s: nuking %p\n", __FUNCTION__, nukedata ); id3_tag_delete( nukedata ); } } id3_utf8_t *nw_get_tag( nw_track_t *track, gchar *tagname ) { id3_utf8_t *retval = NULL; if ( track != NULL ) { /* ultimately need to go trawling in ID3 tags */ if ( !strcmp( tagname, NW_FILENAME )) { retval = (id3_utf8_t *)g_strdup( track->filename ); } else { if ( track->id3tag == NULL ) { dprintf( stderr, "%s: no id3data (2)\n", __FUNCTION__ ); } else { struct id3_frame *frame; frame = id3_tag_findframe( track->id3tag, tagname, 0 ); if ( frame != NULL ) { union id3_field *field = id3_frame_field( frame, 1 ); const id3_ucs4_t *text = id3_field_getstrings( field, 0 ); if ( text != NULL ) { retval = id3_ucs4_utf8duplicate( text ); } } } } } if ( retval == NULL ) { dprintf( stderr, "%s: no data found in %p for %s\n", __FUNCTION__, track->id3tag, tagname ); } else { dprintf( stderr, "%s: %s = %s (%p)\n", __FUNCTION__, tagname, retval, track->id3tag ); } return retval; } guint16 nw_get_tracknum( nw_track_t *track ) { return track->tracknum; } /* * ideally should take a generic tag union (from libid3tag) */ void nw_set_tag( nw_track_t *track, gchar *tagname, id3_utf8_t *value ) { if ( !strcmp( tagname, NW_FILENAME )) { if ( track->filename != NULL ) { g_free( track->filename ); } track->filename = g_strdup(( gchar * )value ); } else if ( !strcmp( tagname, NW_FULLPATH )) { if ( track->fullpath != NULL ) { g_free( track->fullpath ); } track->fullpath = g_strdup(( gchar * )value ); } else { struct id3_frame *frame; id3_ucs4_t *val; if ( track->id3tag == NULL ) { dprintf( stderr, "%s: creating id3tag\n", __FUNCTION__ ); track->id3tag = id3_tag_new(); } frame = id3_frame_new( tagname ); id3_field_settextencoding( &frame->fields[0], ID3_FIELD_TEXTENCODING_UTF_8 ); val = id3_utf8_ucs4duplicate( value ); id3_field_addstring( &frame->fields[1], val ); g_free( val ); /* nuke existing frames with this tag - maybe call update metadata? */ { struct id3_frame *f; for ( ; ( f = id3_tag_findframe( track->id3tag, tagname, 0 )); ) { id3_tag_detachframe( track->id3tag, f ); } } id3_tag_attachframe( track->id3tag, frame ); id3_frame_delete( frame ); } dprintf( stderr, "%s: set %s in %p to %s\n", __FUNCTION__, tagname, track->id3tag, value ); } void nw_track_free( nw_track_t *track ) { if ( track != NULL ) { if ( track->filename != NULL ) { g_free( track->filename ); track->filename = NULL; } if ( track->fullpath != NULL ) { g_free( track->fullpath ); track->fullpath = NULL; } if ( track->id3tag != NULL ) { id3_tag_delete( track->id3tag ); dprintf( stderr, "%s: nuking id3tag\n", __FUNCTION__ ); track->id3tag = NULL; } g_free( track ); track = NULL; } }