// Max Howell , (C) 2004 // Alexandre Pereira de Oliveira , (C) 2005, 2006 // Gábor Lehel , (C) 2005, 2006 // Shane King , (C) 2006 // Peter C. Ndikuwera , (C) 2006 // License: GNU General Public License V2 #define DEBUG_PREFIX "MetaBundle" #include #include #include #include #include #include #include #include "amarok.h" #include "amarokconfig.h" #include "debug.h" #include "collectiondb.h" #include "metabundlesaver.h" #include #include #include #include #include #include #include #include #include //decodePath() #include #include #include //used to load genre list #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MP4V2 #include "metadata/mp4/mp4file.h" #include "metadata/mp4/mp4tag.h" #else #include "metadata/m4a/mp4file.h" #include "metadata/m4a/mp4itunestag.h" #endif #include "lastfm.h" #include "metabundle.h" #include "podcastbundle.h" namespace Amarok { KURL detachedKURL( const KURL &url ) { KURL urlCopy; if (!url.isEmpty()) urlCopy = KURL(url.url()); return urlCopy; } } MetaBundle::EmbeddedImage::EmbeddedImage( const TagLib::ByteVector& data, const TagLib::String& description ) : m_description( TStringToQString( description ) ) { m_data.duplicate( data.data(), data.size() ); } const TQCString &MetaBundle::EmbeddedImage::hash() const { if( m_hash.isEmpty() ) { m_hash = KMD5( m_data ).hexDigest(); } return m_hash; } bool MetaBundle::EmbeddedImage::save( const TQDir& dir ) const { TQFile file( dir.filePath( hash() ) ); if( file.open( IO_WriteOnly | IO_Raw ) ) { const TQ_LONG s = file.writeBlock( m_data.data(), m_data.size() ); if( s >= 0 && TQ_ULONG( s ) == m_data.size() ) { debug() << "EmbeddedImage::save " << file.name() << endl; return true; } file.remove(); } debug() << "EmbeddedImage::save failed! " << file.name() << endl; return false; } /// These are untranslated and used for storing/retrieving XML playlist const TQString &MetaBundle::exactColumnName( int c ) //static { // construct static qstrings to avoid constructing them all the time static TQString columns[] = { "Filename", "Title", "Artist", "AlbumArtist", "Composer", "Year", "Album", "DiscNumber", "Track", "BPM", "Genre", "Comment", "Directory", "Type", "Length", "Bitrate", "SampleRate", "Score", "Rating", "PlayCount", "LastPlayed", "Mood", "Filesize" }; static TQString error( "ERROR" ); if ( c >= 0 && c < NUM_COLUMNS ) return columns[c]; else return error; } const TQString MetaBundle::prettyColumnName( int index ) //static { switch( index ) { case Filename: return i18n( "Filename" ); case Title: return i18n( "Title" ); case Artist: return i18n( "Artist" ); case AlbumArtist:return i18n( "Album Artist"); case Composer: return i18n( "Composer" ); case Year: return i18n( "Year" ); case Album: return i18n( "Album" ); case DiscNumber: return i18n( "Disc Number" ); case Track: return i18n( "Track" ); case Bpm: return i18n( "BPM" ); case Genre: return i18n( "Genre" ); case Comment: return i18n( "Comment" ); case Directory: return i18n( "Directory" ); case Type: return i18n( "Type" ); case Length: return i18n( "Length" ); case Bitrate: return i18n( "Bitrate" ); case SampleRate: return i18n( "Sample Rate" ); case Score: return i18n( "Score" ); case Rating: return i18n( "Rating" ); case PlayCount: return i18n( "Play Count" ); case LastPlayed: return i18n( "Column name", "Last Played" ); case Mood: return i18n( "Mood" ); case Filesize: return i18n( "File Size" ); } return "This is a bug."; } int MetaBundle::columnIndex( const TQString &name ) { for( int i = 0; i < NUM_COLUMNS; ++i ) if( exactColumnName( i ).lower() == name.lower() ) return i; return -1; } MetaBundle::MetaBundle() : m_uniqueId( TQString() ) , m_year( Undetermined ) , m_discNumber( Undetermined ) , m_track( Undetermined ) , m_bpm( Undetermined ) , m_bitrate( Undetermined ) , m_length( Undetermined ) , m_sampleRate( Undetermined ) , m_score( Undetermined ) , m_rating( Undetermined ) , m_playCount( Undetermined ) , m_lastPlay( abs( Undetermined ) ) , m_filesize( Undetermined ) , m_moodbar( 0 ) , m_type( other ) , m_exists( true ) , m_isValidMedia( true ) , m_isCompilation( false ) , m_notCompilation( false ) , m_safeToSave( false ) , m_waitingOnKIO( 0 ) , m_tempSavePath( TQString() ) , m_origRenamedSavePath( TQString() ) , m_tempSaveDigest( 0 ) , m_saveFileref( 0 ) , m_podcastBundle( 0 ) , m_lastFmBundle( 0 ) , m_isSearchDirty(true) , m_searchColumns( Undetermined ) { init(); } MetaBundle::MetaBundle( const KURL &url, bool noCache, TagLib::AudioProperties::ReadStyle readStyle, EmbeddedImageList* images ) : m_url( url ) , m_uniqueId( TQString() ) , m_year( Undetermined ) , m_discNumber( Undetermined ) , m_track( Undetermined ) , m_bpm( Undetermined ) , m_bitrate( Undetermined ) , m_length( Undetermined ) , m_sampleRate( Undetermined ) , m_score( Undetermined ) , m_rating( Undetermined ) , m_playCount( Undetermined ) , m_lastPlay( abs( Undetermined ) ) , m_filesize( Undetermined ) , m_moodbar( 0 ) , m_type( other ) , m_exists( isFile() && TQFile::exists( url.path() ) ) , m_isValidMedia( false ) , m_isCompilation( false ) , m_notCompilation( false ) , m_safeToSave( false ) , m_waitingOnKIO( 0 ) , m_tempSavePath( TQString() ) , m_origRenamedSavePath( TQString() ) , m_tempSaveDigest( 0 ) , m_saveFileref( 0 ) , m_podcastBundle( 0 ) , m_lastFmBundle( 0 ) , m_isSearchDirty(true) , m_searchColumns( Undetermined ) { if ( exists() ) { if ( !noCache ) m_isValidMedia = CollectionDB::instance()->bundleForUrl( this ); if ( !isValidMedia() || ( !m_podcastBundle && m_length <= 0 ) ) readTags( readStyle, images ); } else { // if it's a podcast we might get some info this way CollectionDB::instance()->bundleForUrl( this ); m_bitrate = m_length = m_sampleRate = Unavailable; } } //StreamProvider ctor MetaBundle::MetaBundle( const TQString& title, const TQString& streamUrl, const int bitrate, const TQString& genre, const TQString& streamName, const KURL& url ) : m_url ( url ) , m_genre ( genre ) , m_streamName( streamName ) , m_streamUrl ( streamUrl ) , m_uniqueId( TQString() ) , m_year( 0 ) , m_discNumber( 0 ) , m_track( 0 ) , m_bpm( Undetermined ) , m_bitrate( bitrate ) , m_length( Irrelevant ) , m_sampleRate( Unavailable ) , m_score( Undetermined ) , m_rating( Undetermined ) , m_playCount( Undetermined ) , m_lastPlay( abs( Undetermined ) ) , m_filesize( Undetermined ) , m_moodbar( 0 ) , m_type( other ) , m_exists( true ) , m_isValidMedia( false ) , m_isCompilation( false ) , m_notCompilation( false ) , m_safeToSave( false ) , m_waitingOnKIO( 0 ) , m_tempSavePath( TQString() ) , m_origRenamedSavePath( TQString() ) , m_tempSaveDigest( 0 ) , m_saveFileref( 0 ) , m_podcastBundle( 0 ) , m_lastFmBundle( 0 ) , m_isSearchDirty( true ) , m_searchColumns( Undetermined ) { if( title.contains( '-' ) ) { m_title = TQString(title.section( '-', 1, 1 )).stripWhiteSpace(); m_artist = TQString(title.section( '-', 0, 0 )).stripWhiteSpace(); } else { m_title = title; m_artist = streamName; //which is sort of correct.. } } MetaBundle::MetaBundle( const MetaBundle &bundle ) : m_moodbar( 0 ) { *this = bundle; } MetaBundle::~MetaBundle() { delete m_podcastBundle; delete m_lastFmBundle; if( m_moodbar != 0 ) delete m_moodbar; } MetaBundle& MetaBundle::operator=( const MetaBundle& bundle ) { m_url = bundle.m_url; m_title = bundle.m_title; m_artist = bundle.m_artist; m_albumArtist = bundle.m_albumArtist; m_composer = bundle.m_composer; m_album = bundle.m_album; m_comment = bundle.m_comment; m_genre = bundle.m_genre; m_streamName = bundle.m_streamName; m_streamUrl = bundle.m_streamUrl; m_uniqueId = bundle.m_uniqueId; m_year = bundle.m_year; m_discNumber = bundle.m_discNumber; m_track = bundle.m_track; m_bpm = bundle.m_bpm; m_bitrate = bundle.m_bitrate; m_length = bundle.m_length; m_sampleRate = bundle.m_sampleRate; m_score = bundle.m_score; m_rating = bundle.m_rating; m_playCount = bundle.m_playCount; m_lastPlay = bundle.m_lastPlay; m_filesize = bundle.m_filesize; m_type = bundle.m_type; m_exists = bundle.m_exists; m_isValidMedia = bundle.m_isValidMedia; m_isCompilation = bundle.m_isCompilation; m_notCompilation = bundle.m_notCompilation; m_safeToSave = bundle.m_safeToSave; m_waitingOnKIO = bundle.m_waitingOnKIO; m_tempSavePath = bundle.m_tempSavePath; m_origRenamedSavePath = bundle.m_origRenamedSavePath; m_tempSaveDigest = bundle.m_tempSaveDigest; m_saveFileref = bundle.m_saveFileref; if( bundle.m_moodbar != 0) { if( m_moodbar == 0 ) m_moodbar = new Moodbar( this ); *m_moodbar = *bundle.m_moodbar; } else { // If m_moodbar != 0, it's initialized for a reason // Deleting it makes the PrettySlider code more ugly, // since it'd have to reconnect the jobEvent() signal. if( m_moodbar != 0 ) m_moodbar->reset(); } // delete m_podcastBundle; why does this crash Amarok? apparently m_podcastBundle isn't always initialized. m_podcastBundle = 0; if( bundle.m_podcastBundle ) setPodcastBundle( *bundle.m_podcastBundle ); // delete m_lastFmBundle; same as above m_lastFmBundle = 0; if( bundle.m_lastFmBundle ) setLastFmBundle( *bundle.m_lastFmBundle ); m_isSearchDirty = true; return *this; } bool MetaBundle::checkExists() { m_exists = !isFile() || TQFile::exists( url().path() ); return m_exists; } bool MetaBundle::operator==( const MetaBundle& bundle ) const { return uniqueId() == bundle.uniqueId() && //first, since if using IDs will return faster artist() == bundle.artist() && albumArtist() == bundle.albumArtist() && title() == bundle.title() && composer() == bundle.composer() && album() == bundle.album() && year() == bundle.year() && comment() == bundle.comment() && genre() == bundle.genre() && track() == bundle.track() && discNumber() == bundle.discNumber() && bpm() == bundle.bpm() && length() == bundle.length() && bitrate() == bundle.bitrate() && sampleRate() == bundle.sampleRate(); // FIXME: check for size equality? } void MetaBundle::clear() { *this = MetaBundle(); } void MetaBundle::init( TagLib::AudioProperties *ap ) { if ( ap ) { m_bitrate = ap->bitrate(); m_length = ap->length(); m_sampleRate = ap->sampleRate(); } else m_bitrate = m_length = m_sampleRate = Undetermined; } void MetaBundle::init( const KFileMetaInfo& info ) { if( info.isValid() && !info.isEmpty() ) { m_artist = info.item( "Artist" ).string(); m_album = info.item( "Album" ).string(); m_comment = info.item( "Comment" ).string(); m_genre = info.item( "Genre" ).string(); m_year = info.item( "Year" ).string().toInt(); m_track = info.item( "Track" ).string().toInt(); m_bitrate = info.item( "Bitrate" ).value().toInt(); m_length = info.item( "Length" ).value().toInt(); m_sampleRate = info.item( "Sample Rate" ).value().toInt(); // For title, check if it is valid. If not, use prettyTitle. // @see bug:83650 const KFileMetaInfoItem itemtitle = info.item( "Title" ); m_title = itemtitle.isValid() ? itemtitle.string() : prettyTitle( m_url.fileName() ); const KFileMetaInfoItem itemid = info.item( "Unique ID" ); m_uniqueId = itemid.isValid() ? itemid.string() : TQString(); // because whoever designed KMetaInfoItem is a donkey #define makeSane( x ) if( x == "---" ) x = null; TQString null; makeSane( m_artist ); makeSane( m_album ); makeSane( m_comment ); makeSane( m_genre ); makeSane( m_title ); #undef makeSane m_isValidMedia = true; } else { m_bitrate = m_length = m_sampleRate = m_filesize = Undetermined; m_isValidMedia = false; } } void MetaBundle::embeddedImages( MetaBundle::EmbeddedImageList& images ) const { if ( isFile() ) { TagLib::FileRef fileref = TagLib::FileRef( TQFile::encodeName( url().path() ), false ); if ( !fileref.isNull() ) { if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) { if ( file->ID3v2Tag() ) loadImagesFromTag( *file->ID3v2Tag(), images ); } else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) { if ( file->ID3v2Tag() ) loadImagesFromTag( *file->ID3v2Tag(), images ); } else if ( TagLib::MP4::File *file = dynamic_cast( fileref.file() ) ) { TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); if( mp4tag && mp4tag->cover().size() ) { images.push_back( EmbeddedImage( mp4tag->cover(), "" ) ); } } } } } void MetaBundle::readTags( TagLib::AudioProperties::ReadStyle readStyle, EmbeddedImageList* images ) { if( !isFile() ) return; const TQString path = url().path(); TagLib::FileRef fileref; TagLib::Tag *tag = 0; fileref = TagLib::FileRef( TQFile::encodeName( path ), true, readStyle ); if( !fileref.isNull() ) { setUniqueId( readUniqueId( &fileref ) ); m_filesize = TQFile( path ).size(); tag = fileref.tag(); if ( tag ) { #define strip( x ) TQString(TStringToQString( x )).stripWhiteSpace() setTitle( strip( tag->title() ) ); setArtist( strip( tag->artist() ) ); setAlbum( strip( tag->album() ) ); setComment( strip( tag->comment() ) ); setGenre( strip( tag->genre() ) ); setYear( tag->year() ); setTrack( tag->track() ); #undef strip m_isValidMedia = true; } /* As mpeg implementation on TagLib uses a Tag class that's not defined on the headers, we have to cast the files, not the tags! */ TQString disc; TQString compilation; if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) { m_type = mp3; if ( file->ID3v2Tag() ) { if ( !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty() ) disc = TQString(TStringToQString( file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString() )).stripWhiteSpace(); if ( !file->ID3v2Tag()->frameListMap()["TBPM"].isEmpty() ) setBpm( TQString(TStringToQString( file->ID3v2Tag()->frameListMap()["TBPM"].front()->toString() )).stripWhiteSpace().toFloat() ); if ( !file->ID3v2Tag()->frameListMap()["TCOM"].isEmpty() ) setComposer( TQString(TStringToQString( file->ID3v2Tag()->frameListMap()["TCOM"].front()->toString() )).stripWhiteSpace() ); if ( !file->ID3v2Tag()->frameListMap()["TPE2"].isEmpty() ) // non-standard: Apple, Microsoft setAlbumArtist( TQString(TStringToQString( file->ID3v2Tag()->frameListMap()["TPE2"].front()->toString() )).stripWhiteSpace() ); if ( !file->ID3v2Tag()->frameListMap()["TCMP"].isEmpty() ) compilation = TQString(TStringToQString( file->ID3v2Tag()->frameListMap()["TCMP"].front()->toString() )).stripWhiteSpace(); if(images) { loadImagesFromTag( *file->ID3v2Tag(), *images ); } } } else if ( TagLib::Ogg::Vorbis::File *file = dynamic_cast( fileref.file() ) ) { m_type = ogg; if ( file->tag() ) { if ( !file->tag()->fieldListMap()[ "COMPOSER" ].isEmpty() ) setComposer( TQString(TStringToQString( file->tag()->fieldListMap()["COMPOSER"].front() )).stripWhiteSpace() ); if ( !file->tag()->fieldListMap()[ "BPM" ].isEmpty() ) setBpm( TQString(TStringToQString( file->tag()->fieldListMap()["BPM"].front() )).stripWhiteSpace().toFloat() ); if ( !file->tag()->fieldListMap()[ "DISCNUMBER" ].isEmpty() ) disc = TQString(TStringToQString( file->tag()->fieldListMap()["DISCNUMBER"].front() )).stripWhiteSpace(); if ( !file->tag()->fieldListMap()[ "COMPILATION" ].isEmpty() ) compilation = TQString(TStringToQString( file->tag()->fieldListMap()["COMPILATION"].front() )).stripWhiteSpace(); } } else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) { m_type = flac; if ( file->xiphComment() ) { if ( !file->xiphComment()->fieldListMap()[ "COMPOSER" ].isEmpty() ) setComposer( TQString(TStringToQString( file->xiphComment()->fieldListMap()["COMPOSER"].front() )).stripWhiteSpace() ); if ( !file->xiphComment()->fieldListMap()[ "BPM" ].isEmpty() ) setBpm( TQString(TStringToQString( file->xiphComment()->fieldListMap()["BPM"].front() )).stripWhiteSpace().toFloat() ); if ( !file->xiphComment()->fieldListMap()[ "DISCNUMBER" ].isEmpty() ) disc = TQString(TStringToQString( file->xiphComment()->fieldListMap()["DISCNUMBER"].front() )).stripWhiteSpace(); if ( !file->xiphComment()->fieldListMap()[ "COMPILATION" ].isEmpty() ) compilation = TQString(TStringToQString( file->xiphComment()->fieldListMap()["COMPILATION"].front() )).stripWhiteSpace(); } if ( images && file->ID3v2Tag() ) { loadImagesFromTag( *file->ID3v2Tag(), *images ); } } else if ( TagLib::MP4::File *file = dynamic_cast( fileref.file() ) ) { m_type = mp4; TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); if( mp4tag ) { setComposer( TQString(TStringToQString( mp4tag->composer() )) ); setBpm( TQString::number( mp4tag->bpm() ).toFloat() ); disc = TQString::number( mp4tag->disk() ); compilation = TQString::number( mp4tag->compilation() ); if ( images && mp4tag->cover().size() ) { images->push_back( EmbeddedImage( mp4tag->cover(), "" ) ); } } } if ( !disc.isEmpty() ) { int i = disc.find ('/'); if ( i != -1 ) // disc.right( i ).toInt() is total number of discs, we don't use this at the moment setDiscNumber( disc.left( i ).toInt() ); else setDiscNumber( disc.toInt() ); } if ( compilation.isEmpty() ) { // well, it wasn't set, but if the artist is VA assume it's a compilation if ( artist().string() == i18n( "Various Artists" ) ) setCompilation( CompilationYes ); } else { int i = compilation.toInt(); if ( i == CompilationNo ) setCompilation( CompilationNo ); else if ( i == CompilationYes ) setCompilation( CompilationYes ); } init( fileref.audioProperties() ); } //FIXME disabled for beta4 as it's simpler to not got 100 bug reports //else if( KMimeType::findByUrl( m_url )->is( "audio" ) ) // init( KFileMetaInfo( m_url, TQString(), KFileMetaInfo::Everything ) ); } void MetaBundle::updateFilesize() { if( !isFile() ) { m_filesize = Undetermined; return; } const TQString path = url().path(); m_filesize = TQFile( path ).size(); } float MetaBundle::score( bool ensureCached ) const { if( m_score == Undetermined && !ensureCached ) //const_cast is ugly, but other option was mutable, and then we lose const correctness checking //everywhere else *const_cast(&m_score) = CollectionDB::instance()->getSongPercentage( m_url.path() ); return m_score; } int MetaBundle::rating( bool ensureCached ) const { if( m_rating == Undetermined && !ensureCached ) *const_cast(&m_rating) = CollectionDB::instance()->getSongRating( m_url.path() ); return m_rating; } int MetaBundle::playCount( bool ensureCached ) const { if( m_playCount == Undetermined && !ensureCached ) *const_cast(&m_playCount) = CollectionDB::instance()->getPlayCount( m_url.path() ); return m_playCount; } uint MetaBundle::lastPlay( bool ensureCached ) const { if( (int)m_lastPlay == abs(Undetermined) && !ensureCached ) *const_cast(&m_lastPlay) = CollectionDB::instance()->getLastPlay( m_url.path() ).toTime_t(); return m_lastPlay; } void MetaBundle::copyFrom( const MetaBundle &bundle ) { setTitle( bundle.title() ); setArtist( bundle.artist() ); setAlbumArtist( bundle.albumArtist() ); setComposer( bundle.composer() ); setAlbum( bundle.album() ); setYear( bundle.year() ); setDiscNumber( bundle.discNumber() ); setBpm( bundle.bpm() ); setComment( bundle.comment() ); setGenre( bundle.genre() ); setTrack( bundle.track() ); setLength( bundle.length() ); setBitrate( bundle.bitrate() ); setSampleRate( bundle.sampleRate() ); setScore( bundle.score() ); setRating( bundle.rating() ); setPlayCount( bundle.playCount() ); setLastPlay( bundle.lastPlay() ); setFileType( bundle.fileType() ); setFilesize( bundle.filesize() ); if( bundle.m_podcastBundle ) setPodcastBundle( *bundle.m_podcastBundle ); else { delete m_podcastBundle; m_podcastBundle = 0; } if( bundle.m_lastFmBundle ) setLastFmBundle( *bundle.m_lastFmBundle ); else { delete m_lastFmBundle; m_lastFmBundle = 0; } } void MetaBundle::copyFrom( const PodcastEpisodeBundle &peb ) { setPodcastBundle( peb ); setTitle( peb.title() ); setArtist( peb.author() ); PodcastChannelBundle pcb; if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) ) { if( !pcb.title().isEmpty() ) setAlbum( pcb.title() ); } setGenre( TQString ( "Podcast" ) ); } void MetaBundle::setExactText( int column, const TQString &newText ) { switch( column ) { case Title: setTitle( newText ); break; case Artist: setArtist( newText ); break; case AlbumArtist: setAlbumArtist( newText ); break; case Composer: setComposer( newText ); break; case Year: setYear( newText.toInt() ); break; case Album: setAlbum( newText ); break; case DiscNumber: setDiscNumber( newText.toInt() ); break; case Track: setTrack( newText.toInt() ); break; case Bpm: setBpm( newText.toFloat() ); break; case Genre: setGenre( newText ); break; case Comment: setComment( newText ); break; case Length: setLength( newText.toInt() ); break; case Bitrate: setBitrate( newText.toInt() ); break; case SampleRate: setSampleRate( newText.toInt() ); break; case Score: setScore( newText.toFloat() ); break; case Rating: setRating( newText.toInt() ); break; case PlayCount: setPlayCount( newText.toInt() ); break; case LastPlayed: setLastPlay( newText.toInt() ); break; case Filesize: setFilesize( newText.toInt() ); break; case Type: setFileType( newText.toInt() ); break; default: warning() << "Tried to set the text of an immutable or nonexistent column! [" << column << endl; } } TQString MetaBundle::exactText( int column, bool ensureCached ) const { switch( column ) { case Filename: return filename(); case Title: return title(); case Artist: return artist(); case AlbumArtist: return albumArtist(); case Composer: return composer(); case Year: return TQString::number( year() ); case Album: return album(); case DiscNumber: return TQString::number( discNumber() ); case Track: return TQString::number( track() ); case Bpm: return TQString::number( bpm() ); case Genre: return genre(); case Comment: return comment(); case Directory: return directory(); case Type: return TQString::number( fileType() ); case Length: return TQString::number( length() ); case Bitrate: return TQString::number( bitrate() ); case SampleRate: return TQString::number( sampleRate() ); case Score: return TQString::number( score( ensureCached ) ); case Rating: return TQString::number( rating( ensureCached ) ); case PlayCount: return TQString::number( playCount( ensureCached ) ); case LastPlayed: return TQString::number( lastPlay( ensureCached ) ); case Filesize: return TQString::number( filesize() ); case Mood: return TQString(); default: warning() << "Tried to get the text of a nonexistent column! [" << column << endl; } return TQString(); //shouldn't happen } TQString MetaBundle::prettyText( int column ) const { TQString text; switch( column ) { case Filename: text = isFile() ? MetaBundle::prettyTitle(filename()) : url().prettyURL(); break; case Title: text = title().isEmpty() ? MetaBundle::prettyTitle( filename() ) : title(); break; case Artist: text = artist(); break; case AlbumArtist: text = albumArtist(); break; case Composer: text = composer(); break; case Year: text = year() ? TQString::number( year() ) : TQString(); break; case Album: text = album(); break; case DiscNumber: text = discNumber() ? TQString::number( discNumber() ) : TQString(); break; case Bpm: text = bpm() ? TQString::number( bpm() ) : TQString(); break; case Track: text = track() ? TQString::number( track() ) : TQString(); break; case Genre: text = genre(); break; case Comment: text = comment(); break; case Directory: text = url().isEmpty() ? TQString() : directory(); break; case Type: text = url().isEmpty() ? TQString() : type(); break; case Length: text = prettyLength( length(), true ); break; case Bitrate: text = prettyBitrate( bitrate() ); break; case SampleRate: text = prettySampleRate(); break; case Score: text = TQString::number( static_cast( score() ) ); break; case Rating: text = prettyRating(); break; case PlayCount: text = TQString::number( playCount() ); break; case LastPlayed: text = Amarok::verboseTimeSince( lastPlay() ); break; case Filesize: text = prettyFilesize(); break; case Mood: text = moodbar_const().state() == Moodbar::JobRunning ? i18n( "Calculating..." ) : moodbar_const().state() == Moodbar::JobQueued ? i18n( "Queued..." ) : TQString(); break; default: warning() << "Tried to get the text of a nonexistent column!" << endl; break; } return text.stripWhiteSpace(); } bool MetaBundle::matchesSimpleExpression( const TQString &expression, const TQValueList &columns ) const { const TQStringList terms = TQStringList::split( ' ', expression.lower() ); bool matches = true; for( uint x = 0; matches && x < terms.count(); ++x ) { uint y = 0, n = columns.count(); for(; y < n; ++y ) if ( prettyText( columns[y] ).lower().contains( terms[x] ) ) break; matches = ( y < n ); } return matches; } void MetaBundle::reactToChanges( const TQValueList& columns) { // mark search dirty if we need to for (uint i = 0; !m_isSearchDirty && i < columns.count(); i++) if ((m_searchColumns & (1 << columns[i])) > 0) m_isSearchDirty = true; } bool MetaBundle::matchesFast(const TQStringList &terms, ColumnMask columnMask) const { // simple search for rating, last played, etc. makes no sense and it hurts us a // lot if we have to fetch it from the db. so zero them out columnMask &= ~( 1< 0) { if (!m_searchStr.isEmpty()) m_searchStr += ' '; m_searchStr += prettyText(i).lower(); } } } // now search for (uint i = 0; i < terms.count(); i++) { if (!m_searchStr.contains(terms[i])) return false; } return true; } bool MetaBundle::matchesExpression( const TQString &expression, const TQValueList &defaultColumns ) const { return matchesParsedExpression( ExpressionParser::parse( expression ), defaultColumns ); } bool MetaBundle::matchesParsedExpression( const ParsedExpression &data, const TQValueList &defaults ) const { for( uint i = 0, n = data.count(); i < n; ++i ) //check each part for matchiness { bool b = false; //whether at least one matches for( uint ii = 0, count = data[i].count(); ii < count; ++ii ) { expression_element e = data[i][ii]; int column = -1; if( !e.field.isEmpty() ) { TQString field = e.field.lower(); column = columnIndex( field ); if( column == -1 ) { column = -2; if( field == "size" ) field = "filesize"; else if( field == "filetype" ) field = "type"; else if( field == "disc" ) field = "discnumber"; else column = -1; if( column == -2 ) column = columnIndex( field ); } } if( column >= 0 ) //a field was specified and it exists { TQString q = e.text, v = prettyText( column ).lower(), w = q.lower(); //q = query, v = contents of the field, w = match against it bool condition; //whether it matches, not taking e.negateation into account bool numeric; switch( column ) { case Year: case DiscNumber: case Track: case Bpm: case Bitrate: case SampleRate: case Score: case PlayCount: case LastPlayed: case Filesize: numeric = true; break; default: numeric = false; } if( column == Filesize ) { v = TQString::number( filesize() ); if( w.endsWith( "m" ) ) w = TQString::number( w.left( w.length()-1 ).toLong() * 1024 * 1024 ); else if( w.endsWith( "k" ) ) w = TQString::number( w.left( w.length()-1 ).toLong() * 1024 ); } if( e.match == expression_element::More ) { if( numeric ) condition = v.toInt() > w.toInt(); else if( column == Rating ) condition = v.toFloat() > w.toFloat(); else if( column == Length ) { int g = v.find( ':' ), h = w.find( ':' ); condition = v.left( g ).toInt() > w.left( h ).toInt() || ( v.left( g ).toInt() == w.left( h ).toInt() && v.mid( g + 1 ).toInt() > w.mid( h + 1 ).toInt() ); } else condition = v > w; //compare the strings } else if( e.match == expression_element::Less ) { if( numeric ) condition = v.toInt() < w.toInt(); else if( column == Rating ) condition = v.toFloat() < w.toFloat(); else if( column == Length ) { int g = v.find( ':' ), h = w.find( ':' ); condition = v.left( g ).toInt() < w.left( h ).toInt() || ( v.left( g ).toInt() == w.left( h ).toInt() && v.mid( g + 1 ).toInt() < w.mid( h + 1 ).toInt() ); } else condition = v < w; } else { if( numeric ) condition = v.toInt() == w.toInt(); else if( column == Rating ) condition = v.toFloat() == w.toFloat(); else if( column == Length ) { int g = v.find( ':' ), h = w.find( ':' ); condition = v.left( g ).toInt() == w.left( h ).toInt() && v.mid( g + 1 ).toInt() == w.mid( h + 1 ).toInt(); } else condition = v.contains( q, false ); } if( condition == ( e.negate ? false : true ) ) { b = true; break; } } else //check just the default fields { for( int it = 0, end = defaults.size(); it != end; ++it ) { b = prettyText( defaults[it] ).contains( e.text, false ) == ( e.negate ? false : true ); if( ( e.negate && !b ) || ( !e.negate && b ) ) break; } if( b ) break; } } if( !b ) return false; } return true; } TQString MetaBundle::prettyTitle() const { TQString s = artist(); //NOTE this gets regressed often, please be careful! // whatever you do, handle the stream case, streams have no artist but have an excellent title //FIXME doesn't work for resume playback if( s.isEmpty() ) s = title(); else s = i18n("%1 - %2").arg( artist(), title() ); if( s.isEmpty() ) s = prettyTitle( filename() ); return s; } TQString MetaBundle::veryNiceTitle() const { TQString s; //NOTE I'm not sure, but the notes and FIXME's in the prettyTitle function should be fixed now. // If not then they do apply to this function also! if( !title().isEmpty() ) { if( !artist().isEmpty() ) s = i18n( "%1 by %2" ).arg( title(), artist() ); else s = title(); } else { s = prettyTitle( filename() ); } return s; } TQString MetaBundle::prettyTitle( const TQString &filename ) //static { TQString s = filename; //just so the code is more readable //remove .part extension if it exists if (s.endsWith( ".part" )) s = s.left( s.length() - 5 ); //remove file extension, s/_/ /g and decode %2f-like sequences s = s.left( s.findRev( '.' ) ).replace( '_', ' ' ); s = KURL::decode_string( s ); return s; } TQString MetaBundle::prettyLength( int seconds, bool showHours ) //static { if( seconds > 0 ) return prettyTime( seconds, showHours ); if( seconds == Undetermined ) return "?"; if( seconds == Irrelevant ) return "-"; return TQString(); //Unavailable = "" } TQString MetaBundle::prettyTime( uint seconds, bool showHours ) //static { TQString s = TQChar( ':' ); s.append( zeroPad( seconds % 60 ) ); //seconds seconds /= 60; if( showHours && seconds >= 60) { s.prepend( zeroPad( seconds % 60 ) ); //minutes s.prepend( ':' ); seconds /= 60; } //don't zeroPad the last one, as it can be greater than 2 digits s.prepend( TQString::number( seconds ) ); //hours or minutes depending on above if block return s; } TQString MetaBundle::veryPrettyTime( int time ) { if( time == Undetermined ) return i18n( "?" ); if( time == Irrelevant ) return i18n( "-" ); TQStringList s; s << TQString::number( time % 60 ); //seconds time /= 60; if( time ) s << TQString::number( time % 60 ); //minutes time /= 60; if( time ) s << TQString::number( time % 24 ); //hours time /= 24; if( time ) s << TQString::number( time ); //days switch( s.count() ) { case 1: return i18n( "seconds", "%1s" ).arg( s[0] ); case 2: return i18n( "minutes, seconds", "%2m %1s" ).arg( s[0], s[1] ); case 3: return i18n( "hours, minutes, seconds", "%3h %2m %1s" ).arg( s[0], s[1], s[2] ); case 4: return i18n( "days, hours, minutes, seconds", "%4d %3h %2m %1s" ).arg( s[0], s[1], s[2], s[3] ); default: return "omg bug!"; } } TQString MetaBundle::fuzzyTime( int time ) { TQString s; int secs=0, min=0, hr=0, day=0, week=0; if( time == Undetermined ) return i18n( "?" ); if( time == Irrelevant ) return i18n( "-" ); secs = time % 60; //seconds time /= 60; min = time % 60; //minutes time /= 60; hr = time % 24 ; //hours time /= 24; day = time % 7 ; //days time /= 7; week = time; //weeks if( week && hr >= 12 ) { day++; if( day == 7 ) { week++; day = 0; } } else if( day && min >= 30 ) { hr++; if( hr == 24 ) { day++; hr = 0; } } else if( hr && secs >= 30 ) { min++; if( min == 60 ) { hr++; min = 0; } } TQString weeks = i18n( "1 week %1", "%n weeks %1", week ); TQString days = i18n( "1 day %1", "%n days %1", day ); TQString hours = i18n( "1 hour", "%n hours", hr ); if( week ) return weeks.arg( day ? days.arg("") : "" ).simplifyWhiteSpace(); else if ( day ) return days.arg( hr ? hours : "" ).simplifyWhiteSpace(); else if ( hr ) return i18n( "%1:%2 hours" ).arg( hr ).arg( zeroPad( min ) ); else return i18n( "%1:%2").arg( min ).arg( zeroPad( secs ) ); } TQString MetaBundle::prettyBitrate( int i ) { //the point here is to force sharing of these strings returned from prettyBitrate() static const TQString bitrateStore[9] = { "?", "32", "64", "96", "128", "160", "192", "224", "256" }; return (i >=0 && i <= 256 && i % 32 == 0) ? bitrateStore[ i / 32 ] : prettyGeneric( "%1", i ); } TQString MetaBundle::prettyFilesize( int s ) { return KIO::convertSize( s ); } TQString MetaBundle::prettyRating( int r, bool trailingzero ) //static { if( trailingzero ) return TQString::number( float( r ) / 2, 'f', 1 ); else return r ? TQString::number( float( r ) / 2 ) : TQString(); } TQString MetaBundle::ratingDescription( int r ) { switch( r ) { case 1: return i18n( "Awful" ); case 2: return i18n( "Bad" ); case 3: return i18n( "Barely tolerable" ); case 4: return i18n( "Tolerable" ); case 5: return i18n( "Okay" ); case 6: return i18n( "Good" ); case 7: return i18n( "Very good" ); case 8: return i18n( "Excellent" ); case 9: return i18n( "Amazing" ); case 10: return i18n( "Favorite" ); case 0: default: return i18n( "Not rated" ); // assume weird values as not rated } return "if you can see this, then that's a bad sign."; } TQStringList MetaBundle::ratingList() { TQString s = i18n( "rating - description", "%1 - %2" ); TQStringList list; list += ratingDescription( 0 ); for ( int i = 1; i<=10; i++ ) list += s.arg( prettyRating( i, true ) ).arg( ratingDescription( i ) ); return list; } TQStringList MetaBundle::genreList() //static { TQStringList list; TagLib::StringList genres = TagLib::ID3v1::genreList(); for( TagLib::StringList::ConstIterator it = genres.begin(), end = genres.end(); it != end; ++it ) list += TStringToQString( (*it) ); list.sort(); return list; } void MetaBundle::setExtendedTag( TagLib::File *file, int tag, const TQString value ) { const char *id = 0; if ( m_type == mp3 ) { switch( tag ) { case ( composerTag ): id = "TCOM"; break; case ( discNumberTag ): id = "TPOS"; break; case ( bpmTag ): id = "TBPM"; break; case ( compilationTag ): id = "TCMP"; break; case ( albumArtistTag ): id = "TPE2"; break; // non-standard: Apple, Microsoft } fprintf(stderr, "Setting extended tag %s to %s\n", id, value.utf8().data()); TagLib::MPEG::File *mpegFile = dynamic_cast( file ); if ( mpegFile && mpegFile->ID3v2Tag() ) { if ( value.isEmpty() ) mpegFile->ID3v2Tag()->removeFrames( id ); else { if( !mpegFile->ID3v2Tag()->frameListMap()[id].isEmpty() ) mpegFile->ID3v2Tag()->frameListMap()[id].front()->setText( QStringToTString( value ) ); else { TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame( id, TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding() ); frame->setText( QStringToTString( value ) ); mpegFile->ID3v2Tag()->addFrame( frame ); } } } } else if ( m_type == ogg ) { switch( tag ) { case ( composerTag ): id = "COMPOSER"; break; case ( discNumberTag ): id = "DISCNUMBER"; break; case ( bpmTag ): id = "BPM"; break; case ( compilationTag ): id = "COMPILATION"; break; case ( albumArtistTag ): id = "ALBUMARTIST"; break; // non-standard: Amarok } TagLib::Ogg::Vorbis::File *oggFile = dynamic_cast( file ); if ( oggFile && oggFile->tag() ) { value.isEmpty() ? oggFile->tag()->removeField( id ): oggFile->tag()->addField( id, QStringToTString( value ), true ); } } else if ( m_type == flac ) { switch( tag ) { case ( composerTag ): id = "COMPOSER"; break; case ( discNumberTag ): id = "DISCNUMBER"; break; case ( bpmTag ): id = "BPM"; break; case ( compilationTag ): id = "COMPILATION"; break; case ( albumArtistTag ): id = "ALBUMARTIST"; break; // non-standard: Amarok } TagLib::FLAC::File *flacFile = dynamic_cast( file ); if ( flacFile && flacFile->xiphComment() ) { value.isEmpty() ? flacFile->xiphComment()->removeField( id ): flacFile->xiphComment()->addField( id, QStringToTString( value ), true ); } } else if ( m_type == mp4 ) { TagLib::MP4::Tag *mp4tag = dynamic_cast( file->tag() ); if( mp4tag ) { switch( tag ) { case ( composerTag ): mp4tag->setComposer( QStringToTString( value ) ); break; case ( discNumberTag ): mp4tag->setDisk( value.toInt() ); case ( bpmTag ): mp4tag->setBpm( value.toInt() ); // mp4 doesn't support float bpm case ( compilationTag ): mp4tag->setCompilation( value.toInt() == CompilationYes ); } } } } void MetaBundle::setPodcastBundle( const PodcastEpisodeBundle &peb ) { delete m_podcastBundle; m_podcastBundle = new PodcastEpisodeBundle; *m_podcastBundle = peb; } void MetaBundle::setLastFmBundle( const LastFm::Bundle &last ) { delete m_lastFmBundle; // m_lastFmBundle = new LastFm::Bundle(last); m_lastFmBundle = new LastFm::Bundle; *m_lastFmBundle = last; } void MetaBundle::loadImagesFromTag( const TagLib::ID3v2::Tag &tag, EmbeddedImageList& images ) const { TagLib::ID3v2::FrameList l = tag.frameListMap()[ "APIC" ]; foreachType( TagLib::ID3v2::FrameList, l ) { debug() << "Found APIC frame" << endl; TagLib::ID3v2::AttachedPictureFrame *ap = static_cast( *it ); const TagLib::ByteVector &imgVector = ap->picture(); debug() << "Size of image: " << imgVector.size() << " byte" << endl; // ignore APIC frames without picture and those with obviously bogus size if( imgVector.size() > 0 && imgVector.size() < 10000000 /*10MB*/ ) { images.push_back( EmbeddedImage( imgVector, ap->description() ) ); } } } bool MetaBundle::safeSave() { bool noproblem; MetaBundleSaver mbs( this ); TagLib::FileRef* fileref = mbs.prepareToSave(); if( !fileref ) { debug() << "Could not get a fileref!" << endl; mbs.cleanupSave(); return false; } noproblem = save( fileref ); if( !noproblem ) { debug() << "MetaBundle::save() didn't work!" << endl; mbs.cleanupSave(); return false; } noproblem = mbs.doSave(); if( !noproblem ) { debug() << "Something failed during the save, cleaning up and exiting!" << endl; mbs.cleanupSave(); return false; } setUniqueId( readUniqueId() ); if( CollectionDB::instance()->isFileInCollection( url().path() ) ) CollectionDB::instance()->doAFTStuff( this, false ); noproblem = mbs.cleanupSave(); return noproblem; } bool MetaBundle::save( TagLib::FileRef* fileref ) { DEBUG_BLOCK if( !isFile() ) return false; //Set default codec to UTF-8 (see bugs 111246 and 111232) TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding(TagLib::String::UTF8); bool passedin = fileref; bool returnval = false; TagLib::FileRef* f; if( !passedin ) f = new TagLib::FileRef( TQFile::encodeName( url().path() ), false ); else f = fileref; if ( f && !f->isNull() ) { TagLib::Tag * t = f->tag(); if ( t ) { // f.tag() can return null if the file couldn't be opened for writing t->setTitle( QStringToTString( title().stripWhiteSpace() ) ); t->setArtist( QStringToTString( artist().string().stripWhiteSpace() ) ); t->setAlbum( QStringToTString( album().string().stripWhiteSpace() ) ); t->setTrack( track() ); t->setYear( year() ); t->setComment( QStringToTString( comment().string().stripWhiteSpace() ) ); t->setGenre( QStringToTString( genre().string().stripWhiteSpace() ) ); if ( hasExtendedMetaInformation() ) { setExtendedTag( f->file(), albumArtistTag, albumArtist() ); setExtendedTag( f->file(), composerTag, composer().string().stripWhiteSpace() ); setExtendedTag( f->file(), discNumberTag, discNumber() ? TQString::number( discNumber() ) : TQString() ); setExtendedTag( f->file(), bpmTag, bpm() ? TQString::number( bpm() ) : TQString() ); if ( compilation() != CompilationUnknown ) setExtendedTag( f->file(), compilationTag, TQString::number( compilation() ) ); } if( !passedin ) { returnval = f->save(); setUniqueId( readUniqueId() ); if( returnval && CollectionDB::instance()->isFileInCollection( url().path() ) ) CollectionDB::instance()->doAFTStuff( this, false ); } else returnval = true; } } if ( !passedin ) delete f; return returnval; } // QT's version encodeAttr is 1) private to TQDom, and 2) a litte slow. This // one can be made public if needed. It happens to be on a critical path // (each char of playlist / undo save). TQStyleSheet::escape does not deal with // unicode chars illegal for XML. There's a lot of junk in those tags static inline void xmlEncode(TQTextStream &stream, const TQString &str) { TQString tmp; const TQString *cur = &str; uint i = 0; while ( i < cur->length() ) { uint uc = (*cur)[i].unicode(); // we try to accumulate unescaped chars before writing to stream const char *escaped; // careful about the order of tests, common before less common if ( 'a' <= uc && uc <= 'z' || '0' <= uc && uc <= '9' || 'A' <= uc && uc <= 'Z' ) // most common case escaped = NULL; else if ( uc == '<' ) escaped = "<"; else if ( uc == '>' ) escaped = ">"; else if ( uc == '&' ) escaped = "&"; else if ( uc == '"' ) escaped = """; else { // see if it's a XML-valid unicode char at all if ( (0x20 <= uc && uc <= 0xD7FF || 0xE000 <= uc && uc <= 0xFFFD || uc == 0x9 || uc == 0xA || uc == 0xD) ) // fairly common, other ascii chars escaped = NULL; else escaped = ""; // special char, will write later } if ( escaped ) { // flush previous unescaped chars if ( i > 0 ) stream << cur->left(i); tmp = cur->right(cur->length() - i - 1); cur = &tmp; i = 0; // now write escaped string if ( escaped[0] ) stream << escaped; else stream << "&#x" << TQString::number(uc, 16) << ';'; } else i++; } if (!cur->isEmpty()) stream << *cur; } bool MetaBundle::save(TQTextStream &stream, const TQStringList &attributes) const { // TQDom is too slow to use here stream << " \n"; // end of for (int i = 0; i < NUM_COLUMNS; ++i) { if ( i == Filename ) continue; // the file name is already in the URL const TQString &tag = exactColumnName( i ); // 2 indents stream << " <" << tag << ">"; xmlEncode( stream, exactText( i, true ) ); stream << "\n"; } stream << " \n"; return true; } void MetaBundle::setUrl( const KURL &url ) { TQValueList changes; for( int i = 0; i < NUM_COLUMNS; ++i ) changes << i; aboutToChange( changes ); m_url = url; reactToChanges( changes ); setUniqueId(); } void MetaBundle::setPath( const TQString &path ) { TQValueList changes; for( int i = 0; i < NUM_COLUMNS; ++i ) changes << i; aboutToChange( changes ); m_url.setPath( path ); reactToChanges( changes ); setUniqueId(); } void MetaBundle::setUniqueId() { if( !isFile() ) return; m_uniqueId = CollectionDB::instance()->uniqueIdFromUrl( url() ); } void MetaBundle::setUniqueId( const TQString &id ) { //WARNING WARNING WARNING //Don't call this function if you don't know what you're doing! m_uniqueId = id; } const TagLib::ByteVector MetaBundle::readUniqueIdHelper( TagLib::FileRef fileref ) const { if ( TagLib::MPEG::File *file = dynamic_cast( fileref.file() ) ) { if( file->ID3v2Tag() ) return file->ID3v2Tag()->render(); else if( file->ID3v1Tag() ) return file->ID3v1Tag()->render(); else if( file->APETag() ) return file->APETag()->render(); } else if ( TagLib::Ogg::Vorbis::File *file = dynamic_cast( fileref.file() ) ) { if( file->tag() ) return file->tag()->render(); } else if ( TagLib::FLAC::File *file = dynamic_cast( fileref.file() ) ) { if( file->ID3v2Tag() ) return file->ID3v2Tag()->render(); else if( file->ID3v1Tag() ) return file->ID3v1Tag()->render(); else if( file->xiphComment() ) return file->xiphComment()->render(); } else if ( TagLib::Ogg::FLAC::File *file = dynamic_cast( fileref.file() ) ) { if( file->tag() ) return file->tag()->render(); } else if ( TagLib::MPC::File *file = dynamic_cast( fileref.file() ) ) { if( file->ID3v1Tag() ) return file->ID3v1Tag()->render(); else if( file->APETag() ) return file->APETag()->render(); } TagLib::ByteVector bv; return bv; } TQString MetaBundle::readUniqueId( TagLib::FileRef* fileref ) { //This is used in case we don't get given a fileref TagLib::FileRef tmpfileref; if( !fileref && isFile() ) { const TQString path = url().path(); //Make it get cleaned up at the end of the function automagically tmpfileref = TagLib::FileRef( TQFile::encodeName( path ), true, TagLib::AudioProperties::Fast ); fileref = &tmpfileref; } if( !fileref || fileref->isNull() ) return TQString(); TagLib::ByteVector bv = readUniqueIdHelper( *fileref ); //get our unique id KMD5 md5( 0, 0 ); TQFile qfile( url().path() ); char databuf[8192]; int readlen = 0; TQCString size = 0; TQString returnval; md5.update( bv.data(), bv.size() ); if( qfile.open( IO_Raw | IO_ReadOnly ) ) { if( ( readlen = qfile.readBlock( databuf, 8192 ) ) > 0 ) { md5.update( databuf, readlen ); md5.update( size.setNum( (ulong)qfile.size() ) ); return TQString( md5.hexDigest().data() ); } else return TQString(); } return TQString(); } int MetaBundle::getRand() { //KRandom supposedly exists in SVN, although it's not checked out on my machine, and it's certainly not in 3.3, so I'm just going to steal its code unsigned int seed; int fd = open("/dev/urandom", O_RDONLY); if (fd < 0 || ::read(fd, &seed, sizeof(seed)) != sizeof(seed)) { // No /dev/urandom... try something else. srand(getpid()); seed = rand()+time(0); } if (fd >= 0) close(fd); srand(seed); return rand(); } TQString MetaBundle::getRandomString( int size, bool numbersOnly ) { if( size != 8 ) { debug() << "Wrong size passed in!" << endl; return TQString(); } TQString str; //do a memory op once, much faster than doing multiple later, especially since we know how big it will be str.reserve( size ); int i = getRand(); //seed it i = 0; while (size--) { // check your ASCII tables // we want characters you can see...93 is the range from ! to ~ int r=rand() % 94; // shift the value to the visible characters r+=33; // we don't want ", %, ', <, >, \, `, or & // so that we don't have issues with escaping/quoting in TQStrings, // and so that we don't have <> in our XML files where they might cause issues // hopefully this list is final, as once users really start using this // it will be a pain to change...however, there is an ATF version in CollectionDB // which will help if this ever needs to change // In addition we can change our vendor string while ( r==34 || r==37 || r == 38 || r==39 || r==60 ||r == 62 || r==92 || r==96 ) r++; if( numbersOnly && ( r < 48 || r > 57 ) ) { size++; continue; } str[i++] = char(r); // this next comment kept in for fun, as it was from the source of KRandomString, where I got // most of this code from to start with :-) // so what if I work backwards? } return str; } void MetaBundle::setTitle( const TQString &title ) { aboutToChange( Title ); m_title = title; reactToChange( Title ); } void MetaBundle::setArtist( const AtomicString &artist ) { aboutToChange( Artist ); m_artist = artist; reactToChange( Artist ); } void MetaBundle::setAlbum( const AtomicString &album ) { aboutToChange( Album ); m_album = album; reactToChange( Album ); } void MetaBundle::setComment( const AtomicString &comment ) { aboutToChange( Comment ); m_comment = comment; reactToChange( Comment ); } void MetaBundle::setGenre( const AtomicString &genre ) { aboutToChange( Genre ); m_genre = genre; reactToChange( Genre ); } void MetaBundle::setYear( int year) { aboutToChange( Year ); m_year = year; reactToChange( Year ); } void MetaBundle::setTrack( int track ) { aboutToChange( Track ); m_track = track; reactToChange( Track ); } void MetaBundle::setCompilation( int compilation ) { switch( compilation ) { case CompilationYes: m_isCompilation = true; m_notCompilation = false; break; case CompilationNo: m_isCompilation = false; m_notCompilation = true; break; case CompilationUnknown: m_isCompilation = m_notCompilation = false; break; } } void MetaBundle::setLength( int length ) { aboutToChange( Length ); m_length = length; reactToChange( Length ); } void MetaBundle::setBitrate( int bitrate ) { aboutToChange( Bitrate ); m_bitrate = bitrate; reactToChange( Bitrate ); } void MetaBundle::setSampleRate( int sampleRate ) { aboutToChange( SampleRate ); m_sampleRate = sampleRate; reactToChange( SampleRate ); } void MetaBundle::setDiscNumber( int discnumber ) { aboutToChange( DiscNumber ); m_discNumber = discnumber; reactToChange( DiscNumber ); } void MetaBundle::setBpm( float bpm ) { aboutToChange( Bpm ); m_bpm = bpm; reactToChange( Bpm ); } void MetaBundle::setComposer( const AtomicString &composer ) { aboutToChange( Composer ); m_composer = composer; reactToChange( Composer ); } void MetaBundle::setAlbumArtist( const AtomicString &albumArtist ) { aboutToChange( AlbumArtist ); m_albumArtist = albumArtist; reactToChange( AlbumArtist ); } void MetaBundle::setPlayCount( int playcount ) { aboutToChange( PlayCount ); m_playCount = playcount; reactToChange( PlayCount ); } void MetaBundle::setLastPlay( uint lastplay ) { aboutToChange( LastPlayed ); m_lastPlay = lastplay; reactToChange( LastPlayed ); } void MetaBundle::setRating( int rating ) { aboutToChange( Rating ); m_rating = rating; reactToChange( Rating ); } void MetaBundle::setScore( float score ) { aboutToChange( Score ); m_score = score; reactToChange( Score ); } void MetaBundle::setFilesize( int bytes ) { aboutToChange( Filesize ); m_filesize = bytes; reactToChange( Filesize ); } void MetaBundle::setFileType( int type ) { m_type = type; } void MetaBundle::detach() { // FIXME: we'd do that, but unfortunately it does not exist //m_url.detach(); m_url = Amarok::detachedKURL( m_url ); m_title = TQDeepCopy(m_title); m_artist = m_artist.deepCopy(); m_albumArtist = m_albumArtist.deepCopy(); m_album = m_album.deepCopy(); m_comment = m_comment.deepCopy(); m_composer = m_composer.deepCopy(); m_genre = m_genre.deepCopy(); m_streamName = TQDeepCopy(m_streamName); m_streamUrl = TQDeepCopy(m_streamUrl); if( m_moodbar != 0 ) m_moodbar->detach(); m_uniqueId = TQDeepCopy( m_uniqueId ); if ( m_podcastBundle ) setPodcastBundle( TQDeepCopy( *m_podcastBundle ) ); if ( m_lastFmBundle ) setLastFmBundle( TQDeepCopy( *m_lastFmBundle ) ); } void PodcastEpisodeBundle::detach() { m_url = Amarok::detachedKURL( m_url ); m_localUrl = Amarok::detachedKURL( m_localUrl ); m_parent = Amarok::detachedKURL( m_parent ); m_author = TQDeepCopy(m_author); m_title = TQDeepCopy(m_title); m_subtitle = TQDeepCopy(m_subtitle); m_description = TQDeepCopy(m_subtitle); m_date = TQDeepCopy(m_date); m_type = TQDeepCopy(m_type); m_guid = TQDeepCopy(m_guid); }