You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/metabundle.cpp

1884 lines
64 KiB

// Max Howell <max.howell@methylblue.com>, (C) 2004
// Alexandre Pereira de Oliveira <aleprj@gmail.com>, (C) 2005, 2006
// Gábor Lehel <illissius@gmail.com>, (C) 2005, 2006
// Shane King <kde@dontletsstart.com>, (C) 2006
// Peter C. Ndikuwera <pndiku@gmail.com>, (C) 2006
// License: GNU General Public License V2
#define DEBUG_PREFIX "MetaBundle"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include "amarok.h"
#include "amarokconfig.h"
#include "debug.h"
#include "collectiondb.h"
#include "metabundlesaver.h"
#include <kapplication.h>
#include <kfilemetainfo.h>
#include <kio/global.h>
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kio/netaccess.h>
#include <kmdcodec.h>
#include <tqdeepcopy.h>
#include <tqfile.h> //decodePath()
#include <taglib/attachedpictureframe.h>
#include <taglib/fileref.h>
#include <taglib/id3v1genres.h> //used to load genre list
#include <taglib/mpegfile.h>
#include <taglib/tag.h>
#include <taglib/tstring.h>
#include <taglib/tlist.h>
#include <taglib/apetag.h>
#include <taglib/id3v2tag.h>
#include <taglib/id3v1tag.h>
#include <taglib/mpcfile.h>
#include <taglib/mpegfile.h>
#include <taglib/oggfile.h>
#include <taglib/oggflacfile.h>
#include <taglib/vorbisfile.h>
#include <taglib/flacfile.h>
#include <taglib/textidentificationframe.h>
#include <taglib/uniquefileidentifierframe.h>
#include <taglib/xiphcomment.h>
#include <config.h>
#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<TagLib::MPEG::File *>( fileref.file() ) ) {
if ( file->ID3v2Tag() )
loadImagesFromTag( *file->ID3v2Tag(), images );
} else if ( TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>( fileref.file() ) ) {
if ( file->ID3v2Tag() )
loadImagesFromTag( *file->ID3v2Tag(), images );
} else if ( TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File *>( fileref.file() ) ) {
TagLib::MP4::Tag *mp4tag = dynamic_cast<TagLib::MP4::Tag *>( 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<TagLib::MPEG::File *>( 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<TagLib::Ogg::Vorbis::File *>( 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<TagLib::FLAC::File *>( 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<TagLib::MP4::File *>( fileref.file() ) )
{
m_type = mp4;
TagLib::MP4::Tag *mp4tag = dynamic_cast<TagLib::MP4::Tag *>( 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<float*>(&m_score) = CollectionDB::instance()->getSongPercentage( m_url.path() );
return m_score;
}
int MetaBundle::rating( bool ensureCached ) const
{
if( m_rating == Undetermined && !ensureCached )
*const_cast<int*>(&m_rating) = CollectionDB::instance()->getSongRating( m_url.path() );
return m_rating;
}
int MetaBundle::playCount( bool ensureCached ) const
{
if( m_playCount == Undetermined && !ensureCached )
*const_cast<int*>(&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<uint*>(&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<int>( 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<int> &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<int>& 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<<Score | 1<<Rating | 1<<PlayCount | 1<<LastPlayed | 1<<Mood );
if (m_isSearchDirty || m_searchColumns != columnMask) {
// assert the size of ColumnMask is large enough. In the absence of
// a compile assert mechanism, this is pretty much as good for
// optimized code (ie, free)
if ( sizeof(ColumnMask) < (NUM_COLUMNS / 8) ) {
warning() << "ColumnMask is not big enough!\n";
}
// recompute search text
// There is potential for mishap here if matchesFast gets called from multiple
// threads, but it's *highly* unlikely that something bad will happen
m_isSearchDirty = false;
m_searchColumns = columnMask;
m_searchStr.setLength(0);
for (int i = 0; i < NUM_COLUMNS; i++) {
if ((columnMask & (1 << i)) > 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<int> &defaultColumns ) const
{
return matchesParsedExpression( ExpressionParser::parse( expression ), defaultColumns );
}
bool MetaBundle::matchesParsedExpression( const ParsedExpression &data, const TQValueList<int> &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() )
{