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.
1976 lines
63 KiB
1976 lines
63 KiB
// (c) 2004 Mark Kretschmann <markey@web.de>
|
|
// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
|
|
// (c) 2004 Sami Nieminen <sami.nieminen@iki.fi>
|
|
// (c) 2005 Ian Monroe <ian@monroe.nu>
|
|
// See COPYING file for licensing information.
|
|
|
|
#define DEBUG_PREFIX "CollectionDB"
|
|
|
|
#include "app.h"
|
|
#include "amarok.h"
|
|
#include "amarokconfig.h"
|
|
#include "config.h"
|
|
#include "debug.h"
|
|
#include "collectionbrowser.h" //updateTags()
|
|
#include "collectiondb.h"
|
|
#include "collectionreader.h"
|
|
#include "coverfetcher.h"
|
|
#include "enginecontroller.h"
|
|
#include "metabundle.h" //updateTags()
|
|
#include "playlist.h"
|
|
#include "playlistbrowser.h"
|
|
#include "pluginmanager.h"
|
|
#include "scrobbler.h"
|
|
#include "statusbar.h"
|
|
#include "threadweaver.h"
|
|
|
|
#include <tqfile.h>
|
|
#include <tqimage.h>
|
|
#include <tqtimer.h>
|
|
|
|
#include <kapplication.h>
|
|
#include <kconfig.h>
|
|
#include <kglobal.h>
|
|
#include <kinputdialog.h> //setupCoverFetcher()
|
|
#include <kio/job.h>
|
|
#include <klineedit.h> //setupCoverFetcher()
|
|
#include <klocale.h>
|
|
#include <kmdcodec.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kurl.h>
|
|
#include <kio/netaccess.h>
|
|
|
|
#include <cmath> //DbConnection::sqlite_power()
|
|
#include <ctime> //query()
|
|
#include <unistd.h> //usleep()
|
|
|
|
#include <taglib/mpegfile.h>
|
|
#include <taglib/mpegfile.h>
|
|
#include <taglib/id3v2tag.h>
|
|
#include <taglib/attachedpictureframe.h>
|
|
#include <taglib/tbytevector.h>
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// CLASS CollectionDB
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
CollectionDB* CollectionDB::instance()
|
|
{
|
|
static CollectionDB db;
|
|
return &db;
|
|
}
|
|
|
|
|
|
CollectionDB::CollectionDB()
|
|
: EngineObserver( EngineController::instance() )
|
|
, m_cacheDir( amaroK::saveLocation() )
|
|
, m_coverDir( amaroK::saveLocation() )
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
// create cover dir, if it doesn't exist.
|
|
if( !m_coverDir.exists( "albumcovers", false ) )
|
|
m_coverDir.mkdir( "albumcovers", false );
|
|
m_coverDir.cd( "albumcovers" );
|
|
|
|
// create image cache dir, if it doesn't exist.
|
|
if( !m_cacheDir.exists( "albumcovers/cache", false ) )
|
|
m_cacheDir.mkdir( "albumcovers/cache", false );
|
|
m_cacheDir.cd( "albumcovers/cache" );
|
|
|
|
// Load DBEngine plugin
|
|
TQString query = "[X-KDE-Amarok-plugintype] == 'dbengine' and [X-KDE-Amarok-name] != '%1'";
|
|
KTrader::OfferList offers = PluginManager::query( query.arg( "sqlite-dbengine" ) );
|
|
m_dbEngine = (DBEngine*) PluginManager::createFromService( offers.first() );
|
|
|
|
//<OPEN DATABASE>
|
|
initialize();
|
|
//</OPEN DATABASE>
|
|
|
|
// TODO: Should write to config in dtor, but it crashes...
|
|
KConfig* config = amaroK::config( "Collection Browser" );
|
|
config->writeEntry( "Database Version", DATABASE_VERSION );
|
|
config->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION );
|
|
|
|
startTimer( MONITOR_INTERVAL * 1000 );
|
|
|
|
connect( Scrobbler::instance(), TQT_SIGNAL( similarArtistsFetched( const TQString&, const TQStringList& ) ),
|
|
this, TQT_SLOT( similarArtistsFetched( const TQString&, const TQStringList& ) ) );
|
|
}
|
|
|
|
|
|
CollectionDB::~CollectionDB()
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
destroy();
|
|
|
|
// This crashes so it's done at the end of ctor.
|
|
// KConfig* const config = amaroK::config( "Collection Browser" );
|
|
// config->writeEntry( "Database Version", DATABASE_VERSION );
|
|
// config->writeEntry( "Database Stats Version", DATABASE_STATS_VERSION );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
DbConnection
|
|
*CollectionDB::getStaticDbConnection()
|
|
{
|
|
return m_dbConnPool->getDbConnection();
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::returnStaticDbConnection( DbConnection *conn )
|
|
{
|
|
m_dbConnPool->putDbConnection( conn );
|
|
}
|
|
|
|
|
|
/**
|
|
* Executes a SQL query on the already opened database
|
|
* @param statement SQL program to execute. Only one SQL statement is allowed.
|
|
* @return The queried data, or TQStringList() on error.
|
|
*/
|
|
QStringList
|
|
CollectionDB::query( const TQString& statement, DbConnection *conn )
|
|
{
|
|
if ( DEBUG )
|
|
debug() << "Query-start: " << statement << endl;
|
|
|
|
clock_t start = clock();
|
|
|
|
DbConnection *dbConn;
|
|
if ( conn != NULL )
|
|
{
|
|
dbConn = conn;
|
|
}
|
|
else
|
|
{
|
|
dbConn = m_dbConnPool->getDbConnection();
|
|
}
|
|
|
|
TQStringList values = dbConn->query( statement );
|
|
|
|
if ( conn == NULL )
|
|
{
|
|
m_dbConnPool->putDbConnection( dbConn );
|
|
}
|
|
|
|
if ( DEBUG )
|
|
{
|
|
clock_t finish = clock();
|
|
const double duration = (double) (finish - start) / CLOCKS_PER_SEC;
|
|
debug() << "SQL-query (" << duration << "s): " << statement << endl;
|
|
}
|
|
return values;
|
|
}
|
|
|
|
|
|
/**
|
|
* Executes a SQL insert on the already opened database
|
|
* @param statement SQL statement to execute. Only one SQL statement is allowed.
|
|
* @return The rowid of the inserted item.
|
|
*/
|
|
int
|
|
CollectionDB::insert( const TQString& statement, const TQString& table, DbConnection *conn )
|
|
{
|
|
if ( DEBUG )
|
|
debug() << "insert-start: " << statement << endl;
|
|
|
|
clock_t start = clock();
|
|
|
|
DbConnection *dbConn;
|
|
if ( conn != NULL )
|
|
{
|
|
dbConn = conn;
|
|
}
|
|
else
|
|
{
|
|
dbConn = m_dbConnPool->getDbConnection();
|
|
}
|
|
|
|
int id = dbConn->insert( statement, table );
|
|
|
|
if ( conn == NULL )
|
|
{
|
|
m_dbConnPool->putDbConnection( dbConn );
|
|
}
|
|
|
|
if ( DEBUG )
|
|
{
|
|
clock_t finish = clock();
|
|
const double duration = (double) (finish - start) / CLOCKS_PER_SEC;
|
|
debug() << "SQL-insert (" << duration << "s): " << statement << endl;
|
|
}
|
|
return id;
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::isEmpty()
|
|
{
|
|
TQStringList values;
|
|
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql)
|
|
{
|
|
values = query( "SELECT COUNT( url ) FROM tags OFFSET 0 LIMIT 1;" );
|
|
}
|
|
else
|
|
{
|
|
values = query( "SELECT COUNT( url ) FROM tags LIMIT 0, 1;" );
|
|
}
|
|
|
|
return values.isEmpty() ? true : values.first() == "0";
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::isValid()
|
|
{
|
|
TQStringList values1;
|
|
TQStringList values2;
|
|
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
values1 = query( "SELECT COUNT( url ) FROM tags OFFSET 0 LIMIT 1;" );
|
|
values2 = query( "SELECT COUNT( url ) FROM statistics OFFSET 0 LIMIT 1;" );
|
|
}
|
|
else
|
|
{
|
|
values1 = query( "SELECT COUNT( url ) FROM tags LIMIT 0, 1;" );
|
|
values2 = query( "SELECT COUNT( url ) FROM statistics LIMIT 0, 1;" );
|
|
}
|
|
|
|
//TODO? this returns true if value1 or value2 is not empty. Shouldn't this be and (&&)???
|
|
return !values1.isEmpty() || !values2.isEmpty();
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::createTables( DbConnection *conn )
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
//create tag table
|
|
query( TQString( "CREATE %1 TABLE tags%2 ("
|
|
"url " + textColumnType() + ","
|
|
"dir " + textColumnType() + ","
|
|
"createdate INTEGER,"
|
|
"album INTEGER,"
|
|
"artist INTEGER,"
|
|
"genre INTEGER,"
|
|
"title " + textColumnType() + ","
|
|
"year INTEGER,"
|
|
"comment " + textColumnType() + ","
|
|
"track NUMERIC(4),"
|
|
"bitrate INTEGER,"
|
|
"length INTEGER,"
|
|
"samplerate INTEGER,"
|
|
"sampler BOOL );" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" ), conn );
|
|
|
|
TQString albumAutoIncrement = "";
|
|
TQString artistAutoIncrement = "";
|
|
TQString genreAutoIncrement = "";
|
|
TQString yearAutoIncrement = "";
|
|
if ( m_dbConnPool->getDbConnectionType() == DbConnection::postgresql )
|
|
{
|
|
query( TQString( "CREATE SEQUENCE album_seq;" ), conn );
|
|
query( TQString( "CREATE SEQUENCE artist_seq;" ), conn );
|
|
query( TQString( "CREATE SEQUENCE genre_seq;" ), conn );
|
|
query( TQString( "CREATE SEQUENCE year_seq;" ), conn );
|
|
|
|
albumAutoIncrement = TQString("DEFAULT nextval('album_seq')");
|
|
artistAutoIncrement = TQString("DEFAULT nextval('artist_seq')");
|
|
genreAutoIncrement = TQString("DEFAULT nextval('genre_seq')");
|
|
yearAutoIncrement = TQString("DEFAULT nextval('year_seq')");
|
|
}
|
|
else if ( m_dbConnPool->getDbConnectionType() == DbConnection::mysql )
|
|
{
|
|
albumAutoIncrement = "AUTO_INCREMENT";
|
|
artistAutoIncrement = "AUTO_INCREMENT";
|
|
genreAutoIncrement = "AUTO_INCREMENT";
|
|
yearAutoIncrement = "AUTO_INCREMENT";
|
|
}
|
|
//create album table
|
|
query( TQString( "CREATE %1 TABLE album%2 ("
|
|
"id INTEGER PRIMARY KEY %3,"
|
|
"name " + textColumnType() + ");" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( albumAutoIncrement ), conn );
|
|
|
|
//create artist table
|
|
query( TQString( "CREATE %1 TABLE artist%2 ("
|
|
"id INTEGER PRIMARY KEY %3,"
|
|
"name " + textColumnType() + ");" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( artistAutoIncrement ), conn );
|
|
|
|
//create genre table
|
|
query( TQString( "CREATE %1 TABLE genre%2 ("
|
|
"id INTEGER PRIMARY KEY %3,"
|
|
"name " + textColumnType() +");" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( genreAutoIncrement ), conn );
|
|
|
|
//create year table
|
|
query( TQString( "CREATE %1 TABLE year%2 ("
|
|
"id INTEGER PRIMARY KEY %3,"
|
|
"name " + textColumnType() + ");" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( yearAutoIncrement ), conn );
|
|
|
|
//create images table
|
|
query( TQString( "CREATE %1 TABLE images%2 ("
|
|
"path " + textColumnType() + ","
|
|
"artist " + textColumnType() + ","
|
|
"album " + textColumnType() + ");" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" ), conn );
|
|
|
|
// create directory statistics table
|
|
query( TQString( "CREATE %1 TABLE directories%2 ("
|
|
"dir " + textColumnType() + " UNIQUE,"
|
|
"changedate INTEGER );" )
|
|
.arg( conn ? "TEMPORARY" : "" )
|
|
.arg( conn ? "_temp" : "" ), conn );
|
|
|
|
|
|
//create indexes
|
|
query( TQString( "CREATE INDEX album_idx%1 ON album%2( name );" )
|
|
.arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "CREATE INDEX artist_idx%1 ON artist%2( name );" )
|
|
.arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "CREATE INDEX genre_idx%1 ON genre%2( name );" )
|
|
.arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "CREATE INDEX year_idx%1 ON year%2( name );" )
|
|
.arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn );
|
|
|
|
if ( !conn )
|
|
{
|
|
// create related artists cache
|
|
query( TQString( "CREATE TABLE related_artists ("
|
|
"artist " + textColumnType() + ","
|
|
"suggestion " + textColumnType() + ","
|
|
"changedate INTEGER );" ) );
|
|
|
|
query( "CREATE INDEX url_tag ON tags( url );" );
|
|
query( "CREATE INDEX album_tag ON tags( album );" );
|
|
query( "CREATE INDEX artist_tag ON tags( artist );" );
|
|
query( "CREATE INDEX genre_tag ON tags( genre );" );
|
|
query( "CREATE INDEX year_tag ON tags( year );" );
|
|
query( "CREATE INDEX sampler_tag ON tags( sampler );" );
|
|
|
|
query( "CREATE INDEX images_album ON images( album );" );
|
|
query( "CREATE INDEX images_artist ON images( artist );" );
|
|
|
|
query( "CREATE INDEX directories_dir ON directories( dir );" );
|
|
query( "CREATE INDEX related_artists_artist ON related_artists( artist );" );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::dropTables( DbConnection *conn )
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
query( TQString( "DROP TABLE tags%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE album%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE artist%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE genre%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE year%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE images%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "DROP TABLE directories%1;" ).arg( conn ? "_temp" : "" ), conn );
|
|
if ( !conn )
|
|
{
|
|
query( TQString( "DROP TABLE related_artists;" ) );
|
|
}
|
|
|
|
if ( m_dbConnPool->getDbConnectionType() == DbConnection::postgresql )
|
|
{
|
|
if (conn == NULL) {
|
|
query( TQString( "DROP SEQUENCE album_seq;" ), conn );
|
|
query( TQString( "DROP SEQUENCE artist_seq;" ), conn );
|
|
query( TQString( "DROP SEQUENCE genre_seq;" ), conn );
|
|
query( TQString( "DROP SEQUENCE year_seq;" ), conn );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::clearTables( DbConnection *conn )
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
TQString clearCommand = "DELETE FROM";
|
|
if ( m_dbConnPool->getDbConnectionType() == DbConnection::mysql )
|
|
{
|
|
// TRUNCATE TABLE is faster than DELETE FROM TABLE, so use it when supported.
|
|
clearCommand = "TRUNCATE TABLE";
|
|
}
|
|
|
|
query( TQString( "%1 tags%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 album%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 artist%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 genre%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 year%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 images%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
query( TQString( "%1 directories%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn );
|
|
if ( !conn )
|
|
{
|
|
query( TQString( "%1 related_artists;" ).arg( clearCommand ) );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::moveTempTables( DbConnection *conn )
|
|
{
|
|
insert( "INSERT INTO tags SELECT * FROM tags_temp;", NULL, conn );
|
|
insert( "INSERT INTO album SELECT * FROM album_temp;", NULL, conn );
|
|
insert( "INSERT INTO artist SELECT * FROM artist_temp;", NULL, conn );
|
|
insert( "INSERT INTO genre SELECT * FROM genre_temp;", NULL, conn );
|
|
insert( "INSERT INTO year SELECT * FROM year_temp;", NULL, conn );
|
|
insert( "INSERT INTO images SELECT * FROM images_temp;", NULL, conn );
|
|
insert( "INSERT INTO directories SELECT * FROM directories_temp;", NULL, conn );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::createStatsTable()
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
// create music statistics database
|
|
query( TQString( "CREATE TABLE statistics ("
|
|
"url " + textColumnType() + " UNIQUE,"
|
|
"createdate INTEGER,"
|
|
"accessdate INTEGER,"
|
|
"percentage FLOAT,"
|
|
"playcounter INTEGER );" ) );
|
|
|
|
query( "CREATE INDEX url_stats ON statistics( url );" );
|
|
query( "CREATE INDEX percentage_stats ON statistics( percentage );" );
|
|
query( "CREATE INDEX playcounter_stats ON statistics( playcounter );" );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::dropStatsTable()
|
|
{
|
|
DEBUG_FUNC_INFO
|
|
|
|
query( "DROP TABLE statistics;" );
|
|
}
|
|
|
|
|
|
uint
|
|
CollectionDB::artistID( TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn )
|
|
{
|
|
// lookup cache
|
|
if ( m_cacheArtist == value )
|
|
return m_cacheArtistID;
|
|
|
|
uint id = IDFromValue( "artist", value, autocreate, temporary, updateSpelling, conn );
|
|
|
|
// cache values
|
|
m_cacheArtist = value;
|
|
m_cacheArtistID = id;
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::artistValue( uint id )
|
|
{
|
|
// lookup cache
|
|
if ( m_cacheArtistID == id )
|
|
return m_cacheArtist;
|
|
|
|
TQString value = valueFromID( "artist", id );
|
|
|
|
// cache values
|
|
m_cacheArtist = value;
|
|
m_cacheArtistID = id;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
|
|
uint
|
|
CollectionDB::albumID( TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn )
|
|
{
|
|
// lookup cache
|
|
if ( m_cacheAlbum == value )
|
|
return m_cacheAlbumID;
|
|
|
|
uint id = IDFromValue( "album", value, autocreate, temporary, updateSpelling, conn );
|
|
|
|
// cache values
|
|
m_cacheAlbum = value;
|
|
m_cacheAlbumID = id;
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::albumValue( uint id )
|
|
{
|
|
// lookup cache
|
|
if ( m_cacheAlbumID == id )
|
|
return m_cacheAlbum;
|
|
|
|
TQString value = valueFromID( "album", id );
|
|
|
|
// cache values
|
|
m_cacheAlbum = value;
|
|
m_cacheAlbumID = id;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
uint
|
|
CollectionDB::genreID( TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn )
|
|
{
|
|
return IDFromValue( "genre", value, autocreate, temporary, updateSpelling, conn );
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::genreValue( uint id )
|
|
{
|
|
return valueFromID( "genre", id );
|
|
}
|
|
|
|
|
|
uint
|
|
CollectionDB::yearID( TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn )
|
|
{
|
|
return IDFromValue( "year", value, autocreate, temporary, updateSpelling, conn );
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::yearValue( uint id )
|
|
{
|
|
return valueFromID( "year", id );
|
|
}
|
|
|
|
|
|
uint
|
|
CollectionDB::IDFromValue( TQString name, TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn )
|
|
{
|
|
if ( temporary )
|
|
name.append( "_temp" );
|
|
else
|
|
conn = NULL;
|
|
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT id, name FROM %1 WHERE name LIKE '%2';" )
|
|
.arg( name )
|
|
.arg( CollectionDB::instance()->escapeString( value ) ), conn );
|
|
|
|
if ( updateSpelling && !values.isEmpty() && ( values[1] != value ) )
|
|
{
|
|
query( TQString( "UPDATE %1 SET id = %2, name = '%3' WHERE id = %4;" )
|
|
.arg( name )
|
|
.arg( values.first() )
|
|
.arg( CollectionDB::instance()->escapeString( value ) )
|
|
.arg( values.first() ), conn );
|
|
}
|
|
|
|
//check if item exists. if not, should we autocreate it?
|
|
uint id;
|
|
if ( values.isEmpty() && autocreate )
|
|
{
|
|
id = insert( TQString( "INSERT INTO %1 ( name ) VALUES ( '%2' );" )
|
|
.arg( name )
|
|
.arg( CollectionDB::instance()->escapeString( value ) ), name, conn );
|
|
|
|
return id;
|
|
}
|
|
|
|
return values.isEmpty() ? 0 : values.first().toUInt();
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::valueFromID( TQString table, uint id )
|
|
{
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT name FROM %1 WHERE id=%2;" )
|
|
.arg( table )
|
|
.arg( id ) );
|
|
|
|
|
|
return values.isEmpty() ? 0 : values.first();
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::albumSongCount( const TQString &artist_id, const TQString &album_id )
|
|
{
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT COUNT( url ) FROM tags WHERE album = %1 AND artist = %2;" )
|
|
.arg( album_id )
|
|
.arg( artist_id ) );
|
|
return values.first();
|
|
}
|
|
|
|
bool
|
|
CollectionDB::albumIsCompilation( const TQString &album_id )
|
|
{
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT sampler FROM tags WHERE sampler=%1 AND album=%2" )
|
|
.arg( CollectionDB::instance()->boolT() )
|
|
.arg( album_id ) );
|
|
|
|
return (values.count() != 0);
|
|
}
|
|
|
|
QStringList
|
|
CollectionDB::albumTracks( const TQString &artist_id, const TQString &album_id )
|
|
{
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
return query( TQString( "SELECT tags.url, tags.track AS __discard FROM tags, year WHERE tags.album = %1 AND "
|
|
"( tags.sampler = %2 OR tags.artist = %3 ) AND year.id = tags.year "
|
|
"ORDER BY tags.track;" )
|
|
.arg( album_id )
|
|
.arg( boolT() )
|
|
.arg( artist_id ) );
|
|
}
|
|
else
|
|
{
|
|
return query( TQString( "SELECT tags.url FROM tags, year WHERE tags.album = %1 AND "
|
|
"( tags.sampler = 1 OR tags.artist = %2 ) AND year.id = tags.year "
|
|
"ORDER BY tags.track;" )
|
|
.arg( album_id )
|
|
.arg( artist_id ) );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::addImageToAlbum( const TQString& image, TQValueList< QPair<TQString, TQString> > info, DbConnection *conn )
|
|
{
|
|
for ( TQValueList< QPair<TQString, TQString> >::ConstIterator it = info.begin(); it != info.end(); ++it )
|
|
{
|
|
if ( (*it).first.isEmpty() || (*it).second.isEmpty() )
|
|
continue;
|
|
|
|
debug() << "Added image for album: " << (*it).first << " - " << (*it).second << ": " << image << endl;
|
|
insert( TQString( "INSERT INTO images%1 ( path, artist, album ) VALUES ( '%1', '%2', '%3' );" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( escapeString( image ) )
|
|
.arg( escapeString( (*it).first ) )
|
|
.arg( escapeString( (*it).second ) ), NULL, conn );
|
|
}
|
|
}
|
|
|
|
QImage
|
|
CollectionDB::fetchImage(const KURL& url, TQString &/*tmpFile*/)
|
|
{
|
|
if(url.protocol() != "file")
|
|
{
|
|
TQString tmpFile;
|
|
KIO::NetAccess::download( url, tmpFile, 0); //TODO set 0 to the window, though it probably doesn't really matter
|
|
return TQImage(tmpFile);
|
|
}
|
|
else
|
|
{
|
|
return TQImage( url.path() );
|
|
}
|
|
|
|
}
|
|
bool
|
|
CollectionDB::setAlbumImage( const TQString& artist, const TQString& album, const KURL& url )
|
|
{
|
|
TQString tmpFile;
|
|
bool success = setAlbumImage( artist, album, fetchImage(url, tmpFile) );
|
|
KIO::NetAccess::removeTempFile( tmpFile ); //only removes file if it was created with NetAccess
|
|
return success;
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::setAlbumImage( const TQString& artist, const TQString& album, TQImage img, const TQString& amazonUrl )
|
|
{
|
|
debug() << "Saving cover for: " << artist << " - " << album << endl;
|
|
|
|
//show a wait cursor for the duration
|
|
amaroK::OverrideCursor keep;
|
|
|
|
// remove existing album covers
|
|
removeAlbumImage( artist, album );
|
|
|
|
TQDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) );
|
|
TQCString key = md5sum( artist, album );
|
|
|
|
// Save Amazon product page URL as embedded string, for later retreival
|
|
if ( !amazonUrl.isEmpty() )
|
|
img.setText( "amazon-url", 0, amazonUrl );
|
|
|
|
return img.save( largeCoverDir.filePath( key ), "PNG");
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::findImageByMetabundle( MetaBundle trackInformation, uint width )
|
|
{
|
|
if( width == 1 ) width = AmarokConfig::coverPreviewSize();
|
|
|
|
TQCString widthKey = makeWidthKey( width );
|
|
TQCString tagKey = md5sum( trackInformation.artist(), trackInformation.album() );
|
|
TQDir tagCoverDir( amaroK::saveLocation( "albumcovers/tagcover/" ) );
|
|
|
|
//FIXME: the cached versions will never be refreshed
|
|
if ( tagCoverDir.exists( widthKey + tagKey ) )
|
|
{
|
|
// cached version
|
|
return tagCoverDir.filePath( widthKey + tagKey );
|
|
} else
|
|
{
|
|
// look into the tag
|
|
TagLib::MPEG::File f( TQFile::encodeName( trackInformation.url().path() ) );
|
|
TagLib::ID3v2::Tag *tag = f.ID3v2Tag();
|
|
|
|
if ( tag )
|
|
{
|
|
TagLib::ID3v2::FrameList l = f.ID3v2Tag()->frameListMap()[ "APIC" ];
|
|
if ( !l.isEmpty() )
|
|
{
|
|
debug() << "Found APIC frame(s)" << endl;
|
|
TagLib::ID3v2::Frame *f = l.front();
|
|
TagLib::ID3v2::AttachedPictureFrame *ap = (TagLib::ID3v2::AttachedPictureFrame*)f;
|
|
|
|
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*/ )
|
|
return TQString();
|
|
|
|
TQImage image;
|
|
if( image.loadFromData((const uchar*)imgVector.data(), imgVector.size()) )
|
|
{
|
|
if ( width > 1 )
|
|
{
|
|
image.smoothScale( width, width, TQImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + tagKey ), "PNG" );
|
|
return m_cacheDir.filePath( widthKey + tagKey );
|
|
} else
|
|
{
|
|
image.save( tagCoverDir.filePath( tagKey ), "PNG" );
|
|
return tagCoverDir.filePath( tagKey );
|
|
}
|
|
} // image.isNull
|
|
} // apic list is empty
|
|
} // tag is empty
|
|
} // caching
|
|
|
|
return TQString();
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::findImageByArtistAlbum( const TQString &artist, const TQString &album, uint width )
|
|
{
|
|
TQCString widthKey = makeWidthKey( width );
|
|
|
|
if ( artist.isEmpty() && album.isEmpty() )
|
|
return notAvailCover( width );
|
|
else
|
|
{
|
|
TQCString key = md5sum( artist, album );
|
|
|
|
// check cache for existing cover
|
|
if ( m_cacheDir.exists( widthKey + key ) )
|
|
return m_cacheDir.filePath( widthKey + key );
|
|
else
|
|
{
|
|
// we need to create a scaled version of this cover
|
|
TQDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) );
|
|
if ( largeCoverDir.exists( key ) )
|
|
if ( width > 1 )
|
|
{
|
|
TQImage img( largeCoverDir.filePath( key ) );
|
|
img.smoothScale( width, width, TQImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + key ), "PNG" );
|
|
|
|
return m_cacheDir.filePath( widthKey + key );
|
|
}
|
|
else
|
|
return largeCoverDir.filePath( key );
|
|
}
|
|
|
|
// no amazon cover found, let's try to find a cover in the song's directory
|
|
return getImageForAlbum( artist, album, width );
|
|
}
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::albumImage( const TQString &artist, const TQString &album, uint width )
|
|
{
|
|
TQString s;
|
|
// we aren't going to need a 1x1 size image. this is just a quick hack to be able to show full size images.
|
|
if ( width == 1 ) width = AmarokConfig::coverPreviewSize();
|
|
|
|
s = findImageByArtistAlbum( artist, album, width );
|
|
if ( s == notAvailCover( width ) )
|
|
return findImageByArtistAlbum( "", album, width );
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::albumImage( const uint artist_id, const uint album_id, const uint width )
|
|
{
|
|
return albumImage( artistValue( artist_id ), albumValue( album_id ), width );
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::albumImage( MetaBundle trackInformation, uint width )
|
|
{
|
|
TQString path = findImageByMetabundle( trackInformation, width );
|
|
if( path.isEmpty() )
|
|
path =albumImage( trackInformation.artist(), trackInformation.album(), width );
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
QCString
|
|
CollectionDB::makeWidthKey( uint width )
|
|
{
|
|
return TQString::number( width ).local8Bit() + "@";
|
|
}
|
|
|
|
// get image from path
|
|
QString
|
|
CollectionDB::getImageForAlbum( const TQString& artist, const TQString& album, uint width )
|
|
{
|
|
if ( width == 1 ) width = AmarokConfig::coverPreviewSize();
|
|
TQCString widthKey = TQString::number( width ).local8Bit() + "@";
|
|
|
|
if ( album.isEmpty() )
|
|
return notAvailCover( width );
|
|
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT path FROM images WHERE artist LIKE '%1' AND album LIKE '%2' ORDER BY path;" )
|
|
.arg( escapeString( artist ) )
|
|
.arg( escapeString( album ) ) );
|
|
|
|
if ( !values.isEmpty() )
|
|
{
|
|
TQString image( values.first() );
|
|
uint matches = 0;
|
|
uint maxmatches = 0;
|
|
for ( uint i = 0; i < values.count(); i++ )
|
|
{
|
|
matches = values[i].contains( "front", false ) + values[i].contains( "cover", false ) + values[i].contains( "folder", false );
|
|
if ( matches > maxmatches )
|
|
{
|
|
image = values[i];
|
|
maxmatches = matches;
|
|
}
|
|
}
|
|
|
|
TQCString key = md5sum( artist, album, image );
|
|
|
|
if ( width > 1 )
|
|
{
|
|
if ( !m_cacheDir.exists( widthKey + key ) )
|
|
{
|
|
TQImage img = TQImage( image );
|
|
img.smoothScale( width, width, TQImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + key ), "PNG" );
|
|
}
|
|
|
|
return m_cacheDir.filePath( widthKey + key );
|
|
}
|
|
else //large image
|
|
{
|
|
return image;
|
|
}
|
|
}
|
|
|
|
return notAvailCover( width );
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::removeAlbumImage( const TQString &artist, const TQString &album )
|
|
{
|
|
TQCString widthKey = "*@";
|
|
TQCString key = md5sum( artist, album );
|
|
|
|
// remove scaled versions of images
|
|
TQStringList scaledList = m_cacheDir.entryList( widthKey + key );
|
|
if ( scaledList.count() > 0 )
|
|
for ( uint i = 0; i < scaledList.count(); i++ )
|
|
TQFile::remove( m_cacheDir.filePath( scaledList[ i ] ) );
|
|
|
|
// remove large, original image
|
|
TQDir largeCoverDir( amaroK::saveLocation( "albumcovers/large/" ) );
|
|
|
|
if ( largeCoverDir.exists( key ) && TQFile::remove( largeCoverDir.filePath( key ) ) ) {
|
|
emit coverRemoved( artist, album );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CollectionDB::removeAlbumImage( const uint artist_id, const uint album_id )
|
|
{
|
|
return removeAlbumImage( artistValue( artist_id ), albumValue( album_id ) );
|
|
}
|
|
|
|
|
|
QString
|
|
CollectionDB::notAvailCover( int width )
|
|
{
|
|
if ( !width ) width = AmarokConfig::coverPreviewSize();
|
|
TQString widthKey = TQString::number( width ) + "@";
|
|
|
|
if( m_cacheDir.exists( widthKey + "nocover.png" ) )
|
|
return m_cacheDir.filePath( widthKey + "nocover.png" );
|
|
else
|
|
{
|
|
TQImage nocover( locate( "data", "amarok/images/nocover.png" ) );
|
|
nocover.smoothScale( width, width, TQImage::ScaleMin ).save( m_cacheDir.filePath( widthKey + "nocover.png" ), "PNG" );
|
|
return m_cacheDir.filePath( widthKey + "nocover.png" );
|
|
}
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::artistList( bool withUnknowns, bool withCompilations )
|
|
{
|
|
QueryBuilder qb;
|
|
qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
|
|
|
|
if ( !withUnknowns )
|
|
qb.excludeMatch( QueryBuilder::tabArtist, i18n( "Unknown" ) );
|
|
if ( !withCompilations )
|
|
qb.setOptions( QueryBuilder::optNoCompilations );
|
|
|
|
qb.setOptions( QueryBuilder::optRemoveDuplicates );
|
|
qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
|
|
return qb.run();
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::albumList( bool withUnknowns, bool withCompilations )
|
|
{
|
|
QueryBuilder qb;
|
|
qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
|
|
|
|
if ( !withUnknowns )
|
|
qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
|
|
if ( !withCompilations )
|
|
qb.setOptions( QueryBuilder::optNoCompilations );
|
|
|
|
qb.setOptions( QueryBuilder::optRemoveDuplicates );
|
|
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
|
|
return qb.run();
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::genreList( bool withUnknowns, bool withCompilations )
|
|
{
|
|
QueryBuilder qb;
|
|
qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName );
|
|
|
|
if ( !withUnknowns )
|
|
qb.excludeMatch( QueryBuilder::tabGenre, i18n( "Unknown" ) );
|
|
if ( !withCompilations )
|
|
qb.setOptions( QueryBuilder::optNoCompilations );
|
|
|
|
qb.setOptions( QueryBuilder::optRemoveDuplicates );
|
|
qb.sortBy( QueryBuilder::tabGenre, QueryBuilder::valName );
|
|
return qb.run();
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::yearList( bool withUnknowns, bool withCompilations )
|
|
{
|
|
QueryBuilder qb;
|
|
qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
|
|
|
|
if ( !withUnknowns )
|
|
qb.excludeMatch( QueryBuilder::tabYear, i18n( "Unknown" ) );
|
|
if ( !withCompilations )
|
|
qb.setOptions( QueryBuilder::optNoCompilations );
|
|
|
|
qb.setOptions( QueryBuilder::optRemoveDuplicates );
|
|
qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName );
|
|
return qb.run();
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::albumListOfArtist( const TQString &artist, bool withUnknown, bool withCompilations )
|
|
{
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql)
|
|
{
|
|
return query( "SELECT DISTINCT album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE "
|
|
"tags.album = album.id AND tags.artist = artist.id "
|
|
"AND artist.name = '" + escapeString( artist ) + "' " +
|
|
( withUnknown ? TQString::null : "AND album.name <> '' " ) +
|
|
( withCompilations ? TQString::null : "AND tags.sampler = " + boolF() ) +
|
|
" ORDER BY lower( album.name );" );
|
|
}
|
|
else
|
|
{
|
|
return query( "SELECT DISTINCT album.name FROM tags, album, artist WHERE "
|
|
"tags.album = album.id AND tags.artist = artist.id "
|
|
"AND artist.name = '" + escapeString( artist ) + "' " +
|
|
( withUnknown ? TQString::null : "AND album.name <> '' " ) +
|
|
( withCompilations ? TQString::null : "AND tags.sampler = " + boolF() ) +
|
|
" ORDER BY lower( album.name );" );
|
|
}
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::artistAlbumList( bool withUnknown, bool withCompilations )
|
|
{
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql)
|
|
{
|
|
return query( "SELECT DISTINCT artist.name, album.name, lower( album.name ) AS __discard FROM tags, album, artist WHERE "
|
|
"tags.album = album.id AND tags.artist = artist.id " +
|
|
( withUnknown ? TQString::null : "AND album.name <> '' AND artist.name <> '' " ) +
|
|
( withCompilations ? TQString::null : "AND tags.sampler = " + boolF() ) +
|
|
" ORDER BY lower( album.name );" );
|
|
}
|
|
else
|
|
{
|
|
return query( "SELECT DISTINCT artist.name, album.name FROM tags, album, artist WHERE "
|
|
"tags.album = album.id AND tags.artist = artist.id " +
|
|
( withUnknown ? TQString::null : "AND album.name <> '' AND artist.name <> '' " ) +
|
|
( withCompilations ? TQString::null : "AND tags.sampler = " + boolF() ) +
|
|
" ORDER BY lower( album.name );" );
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::addSong( MetaBundle* bundle, const bool incremental, DbConnection *conn )
|
|
{
|
|
if ( !TQFileInfo( bundle->url().path() ).isReadable() ) return false;
|
|
|
|
TQString command = "INSERT INTO tags_temp "
|
|
"( url, dir, createdate, album, artist, genre, year, title, comment, track, sampler, length, bitrate, samplerate ) "
|
|
"VALUES ('";
|
|
|
|
TQString artist = bundle->artist();
|
|
TQString title = bundle->title();
|
|
if ( title.isEmpty() )
|
|
{
|
|
title = bundle->url().fileName();
|
|
if ( bundle->url().fileName().find( '-' ) > 0 )
|
|
{
|
|
if ( artist.isEmpty() ) artist = bundle->url().fileName().section( '-', 0, 0 ).stripWhiteSpace();
|
|
title = bundle->url().fileName().section( '-', 1 ).stripWhiteSpace();
|
|
title = title.left( title.findRev( '.' ) ).stripWhiteSpace();
|
|
if ( title.isEmpty() ) title = bundle->url().fileName();
|
|
}
|
|
}
|
|
bundle->setArtist( artist );
|
|
bundle->setTitle( title );
|
|
|
|
command += escapeString( bundle->url().path() ) + "','";
|
|
command += escapeString( bundle->url().directory() ) + "',";
|
|
command += TQString::number( TQFileInfo( bundle->url().path() ).lastModified().toTime_t() ) + ",";
|
|
|
|
command += escapeString( TQString::number( albumID( bundle->album(), true, !incremental, false, conn ) ) ) + ",";
|
|
command += escapeString( TQString::number( artistID( bundle->artist(), true, !incremental, false, conn ) ) ) + ",";
|
|
command += escapeString( TQString::number( genreID( bundle->genre(), true, !incremental, false, conn ) ) ) + ",'";
|
|
command += escapeString( TQString::number( yearID( bundle->year(), true, !incremental, false, conn ) ) ) + "','";
|
|
|
|
command += escapeString( bundle->title() ) + "','";
|
|
command += escapeString( bundle->comment() ) + "', ";
|
|
command += ( bundle->track().isEmpty() ? "NULL" : escapeString( bundle->track() ) ) + " , ";
|
|
command += artist == i18n( "Various Artists" ) ? boolT() + "," : boolF() + ",";
|
|
|
|
// NOTE any of these may be -1 or -2, this is what we want
|
|
// see MetaBundle::Undetermined
|
|
command += TQString::number( bundle->length() ) + ",";
|
|
command += TQString::number( bundle->bitrate() ) + ",";
|
|
command += TQString::number( bundle->sampleRate() ) + ")";
|
|
|
|
//FIXME: currently there's no way to check if an INSERT query failed or not - always return true atm.
|
|
// Now it might be possible as insert returns the rowid.
|
|
insert( command, NULL, conn );
|
|
return true;
|
|
}
|
|
|
|
|
|
static void
|
|
fillInBundle( TQStringList values, MetaBundle &bundle )
|
|
{
|
|
//TODO use this whenever possible
|
|
|
|
// crash prevention
|
|
while( values.count() != 10 )
|
|
values += "IF YOU CAN SEE THIS THERE IS A BUG!";
|
|
|
|
TQStringList::ConstIterator it = values.begin();
|
|
|
|
bundle.setAlbum ( *it ); ++it;
|
|
bundle.setArtist ( *it ); ++it;
|
|
bundle.setGenre ( *it ); ++it;
|
|
bundle.setTitle ( *it ); ++it;
|
|
bundle.setYear ( *it ); ++it;
|
|
bundle.setComment ( *it ); ++it;
|
|
bundle.setTrack ( *it ); ++it;
|
|
bundle.setBitrate ( (*it).toInt() ); ++it;
|
|
bundle.setLength ( (*it).toInt() ); ++it;
|
|
bundle.setSampleRate( (*it).toInt() );
|
|
}
|
|
|
|
bool
|
|
CollectionDB::bundleForUrl( MetaBundle* bundle )
|
|
{
|
|
TQStringList values = query( TQString(
|
|
"SELECT album.name, artist.name, genre.name, tags.title, "
|
|
"year.name, tags.comment, tags.track, tags.bitrate, tags.length, "
|
|
"tags.samplerate "
|
|
"FROM tags, album, artist, genre, year "
|
|
"WHERE album.id = tags.album AND artist.id = tags.artist AND "
|
|
"genre.id = tags.genre AND year.id = tags.year AND tags.url = '%1';" )
|
|
.arg( escapeString( bundle->url().path() ) ) );
|
|
|
|
if ( !values.empty() )
|
|
fillInBundle( values, *bundle );
|
|
|
|
return !values.isEmpty();
|
|
}
|
|
|
|
|
|
TQValueList<MetaBundle>
|
|
CollectionDB::bundlesByUrls( const KURL::List& urls )
|
|
{
|
|
typedef TQValueList<MetaBundle> BundleList;
|
|
BundleList bundles;
|
|
TQStringList paths;
|
|
QueryBuilder qb;
|
|
|
|
for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(), last = urls.fromLast(); it != end; ++it )
|
|
{
|
|
// non file stuff won't exist in the db, but we still need to
|
|
// re-insert it into the list we return, just with no tags assigned
|
|
paths += (*it).protocol() == "file" ? (*it).path() : (*it).url();
|
|
|
|
if( paths.count() == 50 || it == last )
|
|
{
|
|
qb.clear();
|
|
|
|
qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
|
|
qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
|
|
qb.addReturnValue( QueryBuilder::tabGenre, QueryBuilder::valName );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
|
|
qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valComment );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valBitrate );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valSamplerate );
|
|
qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
|
|
|
|
qb.addURLFilters( paths );
|
|
qb.setOptions( QueryBuilder::optRemoveDuplicates );
|
|
|
|
const TQStringList values = qb.run();
|
|
|
|
BundleList buns50;
|
|
MetaBundle b;
|
|
foreach( values )
|
|
{
|
|
b.setAlbum ( *it );
|
|
b.setArtist ( *++it );
|
|
b.setGenre ( *++it );
|
|
b.setTitle ( *++it );
|
|
b.setYear ( *++it );
|
|
b.setComment ( *++it );
|
|
b.setTrack ( *++it );
|
|
b.setBitrate ( (*++it).toInt() );
|
|
b.setLength ( (*++it).toInt() );
|
|
b.setSampleRate( (*++it).toInt() );
|
|
b.setPath ( *++it );
|
|
|
|
buns50.append( b );
|
|
}
|
|
|
|
// we get no guarantee about the order that the database
|
|
// will return our values, and sqlite indeed doesn't return
|
|
// them in the desired order :( (MySQL does though)
|
|
foreach( paths ) {
|
|
for( BundleList::Iterator jt = buns50.begin(), end = buns50.end(); jt != end; ++jt )
|
|
if ( (*jt).url().path() == (*it) ) {
|
|
bundles += *jt;
|
|
buns50.remove( jt );
|
|
goto success;
|
|
}
|
|
|
|
// if we get here, we didn't find an entry
|
|
debug() << "No bundle recovered for: " << *it << endl;
|
|
b = MetaBundle();
|
|
b.setUrl( KURL::fromPathOrURL(*it) );
|
|
bundles += b;
|
|
|
|
success: ;
|
|
}
|
|
|
|
paths.clear();
|
|
}
|
|
}
|
|
|
|
return bundles;
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::addAudioproperties( const MetaBundle& bundle )
|
|
{
|
|
query( TQString( "UPDATE tags SET bitrate='%1', length='%2', samplerate='%3' WHERE url='%4';" )
|
|
.arg( bundle.bitrate() )
|
|
.arg( bundle.length() )
|
|
.arg( bundle.sampleRate() )
|
|
.arg( escapeString( bundle.url().path() ) ) );
|
|
}
|
|
|
|
|
|
int
|
|
CollectionDB::addSongPercentage( const TQString &url, int percentage )
|
|
{
|
|
float score;
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT playcounter, createdate, percentage FROM statistics "
|
|
"WHERE url = '%1';" )
|
|
.arg( escapeString( url ) ) );
|
|
|
|
// check boundaries
|
|
if ( percentage > 100 ) percentage = 100;
|
|
if ( percentage < 1 ) percentage = 1;
|
|
|
|
if ( !values.isEmpty() )
|
|
{
|
|
// entry exists, increment playcounter and update accesstime
|
|
score = ( ( values[2].toDouble() * values.first().toInt() ) + percentage ) / ( values.first().toInt() + 1 );
|
|
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
query( TQString( "UPDATE statistics SET percentage=%1, playcounter=%2+1 WHERE url='%3';" )
|
|
.arg( score )
|
|
.arg( values[0] + " + 1" )
|
|
.arg( escapeString( url ) ) );
|
|
}
|
|
else
|
|
{
|
|
query( TQString( "REPLACE INTO statistics ( url, createdate, accessdate, percentage, playcounter ) "
|
|
"VALUES ( '%1', %2, %3, %4, %5 );" )
|
|
.arg( escapeString( url ) )
|
|
.arg( values[1] )
|
|
.arg( TQDateTime::currentDateTime().toTime_t() )
|
|
.arg( score )
|
|
.arg( values[0] + " + 1" ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// entry didn't exist yet, create a new one
|
|
score = ( ( 50 + percentage ) / 2 );
|
|
|
|
insert( TQString( "INSERT INTO statistics ( url, createdate, accessdate, percentage, playcounter ) "
|
|
"VALUES ( '%1', %2, %3, %4, 1 );" )
|
|
.arg( escapeString( url ) )
|
|
.arg( TQDateTime::currentDateTime().toTime_t() )
|
|
.arg( TQDateTime::currentDateTime().toTime_t() )
|
|
.arg( score ), NULL );
|
|
}
|
|
|
|
int iscore = getSongPercentage( url );
|
|
emit scoreChanged( url, iscore );
|
|
return iscore;
|
|
}
|
|
|
|
|
|
int
|
|
CollectionDB::getSongPercentage( const TQString &url )
|
|
{
|
|
TQStringList values = query( TQString( "SELECT round( percentage + 0.4 ) FROM statistics WHERE url = '%1';" )
|
|
.arg( escapeString( url ) ) );
|
|
|
|
if( values.count() )
|
|
return values.first().toInt();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::setSongPercentage( const TQString &url , int percentage )
|
|
{
|
|
TQStringList values =
|
|
query( TQString(
|
|
"SELECT playcounter, createdate, accessdate FROM statistics WHERE url = '%1';" )
|
|
.arg( escapeString( url ) ) );
|
|
|
|
// check boundaries
|
|
if ( percentage > 100 ) percentage = 100;
|
|
if ( percentage < 1 ) percentage = 1;
|
|
|
|
if ( !values.isEmpty() )
|
|
{
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
query( TQString( "UPDATE statistics SET percentage=%1 WHERE url='%2';" )
|
|
.arg( percentage )
|
|
.arg( escapeString( url ) ) );
|
|
}
|
|
else
|
|
{
|
|
// entry exists
|
|
query( TQString( "REPLACE INTO statistics ( url, createdate, accessdate, percentage, playcounter ) "
|
|
"VALUES ( '%1', '%2', '%3', %4, %5 );" )
|
|
.arg( escapeString( url ) )
|
|
.arg( values[1] )
|
|
.arg( values[2] )
|
|
.arg( percentage )
|
|
.arg( values[0] ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
insert( TQString( "INSERT INTO statistics ( url, createdate, accessdate, percentage, playcounter ) "
|
|
"VALUES ( '%1', %2, %3, %4, 0 );" )
|
|
.arg( escapeString( url ) )
|
|
.arg( TQDateTime::currentDateTime().toTime_t() )
|
|
.arg( TQDateTime::currentDateTime().toTime_t() )
|
|
.arg( percentage ), NULL );
|
|
}
|
|
|
|
emit scoreChanged( url, percentage );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::updateDirStats( TQString path, const long datetime, DbConnection *conn )
|
|
{
|
|
if ( path.endsWith( "/" ) )
|
|
path = path.left( path.length() - 1 );
|
|
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
query( TQString( "UPDATE directories%1 SET changedate=%2 WHERE dir='%3';")
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( datetime )
|
|
.arg( escapeString( path ) ), conn );
|
|
}
|
|
else
|
|
{
|
|
query( TQString( "REPLACE INTO directories%1 ( dir, changedate ) VALUES ( '%3', %2 );" )
|
|
.arg( conn ? "_temp" : "" )
|
|
.arg( datetime )
|
|
.arg( escapeString( path ) ),
|
|
conn );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::removeSongsInDir( TQString path )
|
|
{
|
|
if ( path.endsWith( "/" ) )
|
|
path = path.left( path.length() - 1 );
|
|
|
|
query( TQString( "DELETE FROM tags WHERE dir = '%1';" )
|
|
.arg( escapeString( path ) ) );
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::isDirInCollection( TQString path )
|
|
{
|
|
if ( path.endsWith( "/" ) )
|
|
path = path.left( path.length() - 1 );
|
|
|
|
TQStringList values =
|
|
query( TQString( "SELECT changedate FROM directories WHERE dir = '%1';" )
|
|
.arg( escapeString( path ) ) );
|
|
|
|
return !values.isEmpty();
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionDB::isFileInCollection( const TQString &url )
|
|
{
|
|
TQStringList values =
|
|
query( TQString( "SELECT url FROM tags WHERE url = '%1';" )
|
|
.arg( escapeString( url ) ) );
|
|
|
|
return !values.isEmpty();
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::removeSongs( const KURL::List& urls )
|
|
{
|
|
for( KURL::List::ConstIterator it = urls.begin(), end = urls.end(); it != end; ++it )
|
|
{
|
|
query( TQString( "DELETE FROM tags WHERE url = '%1';" )
|
|
.arg( escapeString( (*it).path() ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
QStringList
|
|
CollectionDB::similarArtists( const TQString &artist, uint count )
|
|
{
|
|
TQStringList values;
|
|
|
|
if (m_dbConnPool->getDbConnectionType() == DbConnection::postgresql) {
|
|
values = query( TQString( "SELECT suggestion FROM related_artists WHERE artist = '%1' OFFSET 0 LIMIT %2;" )
|
|
.arg( escapeString( artist ) ).arg( count ) );
|
|
}
|
|
else
|
|
{
|
|
values = query( TQString( "SELECT suggestion FROM related_artists WHERE artist = '%1' LIMIT 0, %2;" )
|
|
.arg( escapeString( artist ) ).arg( count ) );
|
|
}
|
|
|
|
if ( values.isEmpty() )
|
|
Scrobbler::instance()->similarArtists( artist );
|
|
|
|
return values;
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::checkCompilations( const TQString &path, const bool temporary, DbConnection *conn )
|
|
{
|
|
TQStringList albums;
|
|
TQStringList artists;
|
|
TQStringList dirs;
|
|
|
|
albums = query( TQString( "SELECT DISTINCT album.name FROM tags_temp, album%1 AS album WHERE tags_temp.dir = '%2' AND album.id = tags_temp.album;" )
|
|
.arg( temporary ? "_temp" : "" )
|
|
.arg( escapeString( path ) ), conn );
|
|
|
|
for ( uint i = 0; i < albums.count(); i++ )
|
|
{
|
|
if ( albums[ i ].isEmpty() ) continue;
|
|
|
|
const uint album_id = albumID( albums[ i ], false, temporary, false, conn );
|
|
artists = query( TQString( "SELECT DISTINCT artist.name FROM tags_temp, artist%1 AS artist WHERE tags_temp.album = '%2' AND tags_temp.artist = artist.id;" )
|
|
.arg( temporary ? "_temp" : "" )
|
|
.arg( album_id ), conn );
|
|
dirs = query( TQString( "SELECT DISTINCT dir FROM tags_temp WHERE album = '%1';" )
|
|
.arg( album_id ), conn );
|
|
|
|
if ( artists.count() > dirs.count() )
|
|
{
|
|
debug() << "Detected compilation: " << albums[ i ] << " - " << artists.count() << ":" << dirs.count() << endl;
|
|
query( TQString( "UPDATE tags_temp SET sampler = %1 WHERE album = '%2';" )
|
|
.arg(boolT()).arg( album_id ), conn );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::setCompilation( const TQString &album, const bool enabled, const bool updateView )
|
|
{
|
|
query( TQString( "UPDATE tags, album SET tags.sampler = %1 WHERE tags.album = album.id AND album.name = '%2';" )
|
|
.arg( enabled ? "1" : "0" )
|
|
.arg( escapeString( album ) ) );
|
|
|
|
// Update the Collection-Browser view,
|
|
// using TQTimer to make sure we don't manipulate the GUI from a thread
|
|
if ( updateView )
|
|
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::removeDirFromCollection( TQString path )
|
|
{
|
|
if ( path.endsWith( "/" ) )
|
|
path = path.left( path.length() - 1 );
|
|
|
|
query( TQString( "DELETE FROM directories WHERE dir = '%1';" )
|
|
.arg( escapeString( path ) ) );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::updateTags( const TQString &url, const MetaBundle &bundle, const bool updateView )
|
|
{
|
|
TQString command = "UPDATE tags SET ";
|
|
command += "title = '" + escapeString( bundle.title() ) + "', ";
|
|
command += "artist = " + TQString::number( artistID( bundle.artist(), true, false, true ) ) + ", ";
|
|
command += "album = " + TQString::number( albumID( bundle.album(), true, false, true ) ) + ", ";
|
|
command += "genre = " + TQString::number( genreID( bundle.genre(), true, false, true ) ) + ", ";
|
|
command += "year = " + TQString::number( yearID( bundle.year(), true, false, true ) ) + ", ";
|
|
if ( !bundle.track().isEmpty() )
|
|
command += "track = " + bundle.track() + ", ";
|
|
command += "comment = '" + escapeString( bundle.comment() ) + "' ";
|
|
command += "WHERE url = '" + escapeString( url ) + "';";
|
|
|
|
query( command );
|
|
|
|
if ( EngineController::instance()->bundle().url() == bundle.url() )
|
|
{
|
|
debug() << "Current song edited, updating widgets: " << bundle.title() << endl;
|
|
EngineController::instance()->currentTrackMetaDataChanged( bundle );
|
|
}
|
|
|
|
// Update the Collection-Browser view,
|
|
// using TQTimer to make sure we don't manipulate the GUI from a thread
|
|
if ( updateView )
|
|
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::updateURL( const TQString &url, const bool updateView )
|
|
{
|
|
// don't use the KURL ctor as it checks the db first
|
|
MetaBundle bundle;
|
|
bundle.setPath( url );
|
|
bundle.readTags( TagLib::AudioProperties::Fast );
|
|
|
|
updateTags( url, bundle, updateView );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::applySettings()
|
|
{
|
|
bool recreateConnections = false;
|
|
if ( AmarokConfig::databaseEngine().toInt() != m_dbConnPool->getDbConnectionType() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql )
|
|
{
|
|
// Using MySQL, so check if MySQL settings were changed
|
|
const MySqlConfig *config =
|
|
static_cast<const MySqlConfig*> ( m_dbConnPool->getDbConfig() );
|
|
if ( AmarokConfig::mySqlHost() != config->host() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
else if ( AmarokConfig::mySqlPort() != config->port() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
else if ( AmarokConfig::mySqlDbName() != config->database() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
else if ( AmarokConfig::mySqlUser() != config->username() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
else if ( AmarokConfig::mySqlPassword() != config->password() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
}
|
|
else if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql )
|
|
{
|
|
const PostgresqlConfig *config =
|
|
static_cast<const PostgresqlConfig*> ( m_dbConnPool->getDbConfig() );
|
|
if ( AmarokConfig::postgresqlConninfo() != config->conninfo() )
|
|
{
|
|
recreateConnections = true;
|
|
}
|
|
}
|
|
if ( recreateConnections )
|
|
{
|
|
debug()
|
|
<< "Database engine settings changed: "
|
|
<< "recreating DbConnections" << endl;
|
|
// If Database engine was changed, recreate DbConnections.
|
|
destroy();
|
|
initialize();
|
|
CollectionView::instance()->renderView();
|
|
emit databaseEngineChanged();
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
QCString
|
|
CollectionDB::md5sum( const TQString& artist, const TQString& album, const TQString& file )
|
|
{
|
|
KMD5 context( artist.lower().local8Bit() + album.lower().local8Bit() + file.local8Bit() );
|
|
// debug() << "MD5 SUM for " << artist << ", " << album << ": " << context.hexDigest() << endl;
|
|
return context.hexDigest();
|
|
}
|
|
|
|
|
|
void CollectionDB::engineTrackEnded( int finalPosition, int trackLength )
|
|
{
|
|
//This is where percentages are calculated
|
|
//TODO statistics are not calculated when currentTrack doesn't exist
|
|
|
|
// Don't update statistics if song has been played for less than 15 seconds
|
|
// if ( finalPosition < 15000 ) return;
|
|
|
|
const KURL url = EngineController::instance()->bundle().url();
|
|
if ( url.path().isEmpty() ) return;
|
|
|
|
// sanity check
|
|
if ( finalPosition > trackLength || finalPosition <= 0 )
|
|
finalPosition = trackLength;
|
|
|
|
int pct = (int) ( ( (double) finalPosition / (double) trackLength ) * 100 );
|
|
|
|
// increase song counter & calculate new statistics
|
|
addSongPercentage( url.path(), pct );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::timerEvent( TQTimerEvent* )
|
|
{
|
|
if ( AmarokConfig::monitorChanges() )
|
|
scanMonitor();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC SLOTS
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
CollectionDB::fetchCover( TQWidget* parent, const TQString& artist, const TQString& album, bool noedit ) //SLOT
|
|
{
|
|
#ifdef AMAZON_SUPPORT
|
|
debug() << "Fetching cover for " << artist << " - " << album << endl;
|
|
|
|
CoverFetcher* fetcher = new CoverFetcher( parent, artist, album );
|
|
connect( fetcher, TQT_SIGNAL(result( CoverFetcher* )), TQT_SLOT(coverFetcherResult( CoverFetcher* )) );
|
|
fetcher->setUserCanEditQuery( !noedit );
|
|
fetcher->startFetch();
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::scanMonitor() //SLOT
|
|
{
|
|
scanModifiedDirs();
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::startScan() //SLOT
|
|
{
|
|
TQStringList folders = MountPointManager::instance()->collectionFolders();
|
|
|
|
if ( folders.isEmpty() ) {
|
|
dropTables();
|
|
createTables();
|
|
}
|
|
else if( PlaylistBrowser::instance() )
|
|
{
|
|
emit scanStarted();
|
|
|
|
ThreadWeaver::instance()->queueJob( new CollectionReader( this, folders ) );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::stopScan() //SLOT
|
|
{
|
|
ThreadWeaver::instance()->abortAllJobsNamed( "CollectionReader" );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE SLOTS
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
CollectionDB::dirDirty( const TQString& path )
|
|
{
|
|
debug() << k_funcinfo << "Dirty: " << path << endl;
|
|
|
|
ThreadWeaver::instance()->queueJob( new CollectionReader( this, path ) );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::coverFetcherResult( CoverFetcher *fetcher )
|
|
{
|
|
if( fetcher->wasError() ) {
|
|
error() << fetcher->errors() << endl;
|
|
emit coverFetcherError( fetcher->errors().front() );
|
|
}
|
|
|
|
else {
|
|
setAlbumImage( fetcher->artist(), fetcher->album(), fetcher->image(), fetcher->amazonURL() );
|
|
emit coverFetched( fetcher->artist(), fetcher->album() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This query is fairly slow with sqlite, and often happens just
|
|
* after the OSD is shown. Threading it restores responsivity.
|
|
*/
|
|
class SimilarArtistsInsertionJob : public ThreadWeaver::DependentJob
|
|
{
|
|
virtual bool doJob()
|
|
{
|
|
CollectionDB::instance()->query( TQString( "DELETE FROM related_artists WHERE artist = '%1';" ).arg( escapedArtist ) );
|
|
|
|
const TQString sql = "INSERT INTO related_artists ( artist, suggestion, changedate ) VALUES ( '%1', '%2', 0 );";
|
|
foreach( suggestions )
|
|
CollectionDB::instance()->insert( sql
|
|
.arg( escapedArtist )
|
|
.arg( CollectionDB::instance()->escapeString( *it ) ), NULL );
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void completeJob() { emit CollectionDB::instance()->similarArtistsFetched( artist ); }
|
|
|
|
const TQString artist;
|
|
const TQString escapedArtist;
|
|
const TQStringList suggestions;
|
|
|
|
public:
|
|
SimilarArtistsInsertionJob( CollectionDB *parent, const TQString &s, const TQStringList &list )
|
|
: ThreadWeaver::DependentJob( parent, "SimilarArtistsInsertionJob" )
|
|
, artist( s )
|
|
, escapedArtist( parent->escapeString( s ) )
|
|
, suggestions( list )
|
|
{}
|
|
};
|
|
|
|
void
|
|
CollectionDB::similarArtistsFetched( const TQString& artist, const TQStringList& suggestions )
|
|
{
|
|
debug() << "Received similar artists\n";
|
|
|
|
ThreadWeaver::instance()->queueJob( new SimilarArtistsInsertionJob( this, artist, suggestions ) );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void
|
|
CollectionDB::initialize()
|
|
{
|
|
m_dbConnPool = new DbConnectionPool();
|
|
DbConnection *dbConn = m_dbConnPool->getDbConnection();
|
|
m_dbConnPool->putDbConnection( dbConn );
|
|
|
|
KConfig* config = amaroK::config( "Collection Browser" );
|
|
if(!dbConn->isConnected())
|
|
amaroK::MessageQueue::instance()->addMessage(dbConn->lastError());
|
|
if ( !dbConn->isInitialized() || !isValid() )
|
|
{
|
|
createTables();
|
|
createStatsTable();
|
|
}
|
|
else
|
|
{
|
|
//remove database file if version is incompatible
|
|
if ( config->readNumEntry( "Database Version", 0 ) != DATABASE_VERSION )
|
|
{
|
|
debug() << "Rebuilding database!" << endl;
|
|
dropTables();
|
|
createTables();
|
|
}
|
|
if ( config->readNumEntry( "Database Stats Version", 0 ) != DATABASE_STATS_VERSION )
|
|
{
|
|
debug() << "Rebuilding stats-database!" << endl;
|
|
dropStatsTable();
|
|
createStatsTable();
|
|
}
|
|
}
|
|
|
|
m_dbConnPool->createDbConnections();
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::destroy()
|
|
{
|
|
delete m_dbConnPool;
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::scanModifiedDirs()
|
|
{
|
|
//we check if a job is pending because we don't want to abort incremental collection readings
|
|
if ( !ThreadWeaver::instance()->isJobPending( "CollectionReader" ) && PlaylistBrowser::instance() ) {
|
|
emit scanStarted();
|
|
ThreadWeaver::instance()->onlyOneJob( new IncrementalCollectionReader( this ) );
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CollectionDB::customEvent( TQCustomEvent *e )
|
|
{
|
|
if ( e->type() == (int)CollectionReader::JobFinishedEvent )
|
|
emit scanDone( static_cast<ThreadWeaver::Job*>(e)->wasSuccessful() );
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// CLASS DbConnectionPool
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DbConnectionPool::DbConnectionPool() : m_semaphore( POOL_SIZE )
|
|
{
|
|
#ifdef USE_MYSQL
|
|
if ( AmarokConfig::databaseEngine().toInt() == DbConnection::mysql )
|
|
m_dbConnType = DbConnection::mysql;
|
|
else
|
|
#endif
|
|
#ifdef USE_POSTGRESQL
|
|
if ( AmarokConfig::databaseEngine().toInt() == DbConnection::postgresql )
|
|
m_dbConnType = DbConnection::postgresql;
|
|
else
|
|
#endif
|
|
m_dbConnType = DbConnection::sqlite;
|
|
|
|
m_semaphore += POOL_SIZE;
|
|
DbConnection *dbConn;
|
|
|
|
#ifdef USE_MYSQL
|
|
if ( m_dbConnType == DbConnection::mysql )
|
|
{
|
|
m_dbConfig =
|
|
new MySqlConfig(
|
|
AmarokConfig::mySqlHost(),
|
|
AmarokConfig::mySqlPort(),
|
|
AmarokConfig::mySqlDbName(),
|
|
AmarokConfig::mySqlUser(),
|
|
AmarokConfig::mySqlPassword() );
|
|
dbConn = new MySqlConnection( static_cast<MySqlConfig*> ( m_dbConfig ) );
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef USE_POSTGRESQL
|
|
if ( m_dbConnType == DbConnection::postgresql )
|
|
{
|
|
m_dbConfig =
|
|
new PostgresqlConfig(
|
|
AmarokConfig::postgresqlConninfo() );
|
|
dbConn = new PostgresqlConnection( static_cast<PostgresqlConfig*> ( m_dbConfig ) );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
m_dbConfig = new SqliteConfig( "collection.db" );
|
|
dbConn = new SqliteConnection( static_cast<SqliteConfig*> ( m_dbConfig ) );
|
|
}
|
|
enqueue( dbConn );
|
|
m_semaphore--;
|
|
debug() << "Available db connections: " << m_semaphore.available() << endl;
|
|
}
|
|
|
|
|
|
DbConnectionPool::~DbConnectionPool()
|
|
{
|
|
m_semaphore += POOL_SIZE;
|
|
DbConnection *conn;
|
|
bool vacuum = true;
|
|
|
|
while ( ( conn = dequeue() ) != 0 )
|
|
{
|
|
if ( m_dbConnType == DbConnection::sqlite && vacuum )
|
|
{
|
|
vacuum = false;
|
|
debug() << "Running VACUUM" << endl;
|
|
conn->query( "VACUUM; ");
|
|
}
|
|
|
|
delete conn;
|
|
}
|
|
|
|
delete m_dbConfig;
|
|
}
|
|
|
|
|
|
void DbConnectionPool::createDbConnections()
|
|
{
|
|
for ( int i = 0; i < POOL_SIZE - 1; i++ )
|
|
{
|
|
DbConnection *dbConn;
|
|
|
|
#ifdef USE_MYSQL
|
|
if ( m_dbConnType == DbConnection::mysql )
|
|
dbConn = new MySqlConnection( static_cast<MySqlConfig*> ( m_dbConfig ) );
|
|
else
|
|
#endif
|
|
#ifdef USE_POSTGRESQL
|
|
if ( m_dbConnType == DbConnection::postgresql )
|
|
dbConn = new PostgresqlConnection( static_cast<PostgresqlConfig*> ( m_dbConfig ) );
|
|
else
|
|
#endif
|
|
dbConn = new SqliteConnection( static_cast<SqliteConfig*> ( m_dbConfig ) );
|
|
enqueue( dbConn );
|
|
m_semaphore--;
|
|
}
|
|
debug() << "Available db connections: " << m_semaphore.available() << endl;
|
|
}
|
|
|
|
|
|
DbConnection *DbConnectionPool::getDbConnection()
|
|
{
|
|
m_semaphore++;
|
|
return dequeue();
|
|
}
|
|
|
|
|
|
void DbConnectionPool::putDbConnection( const DbConnection *conn )
|
|
{
|
|
enqueue( conn );
|
|
m_semaphore--;
|
|
}
|
|
|
|
|
|
#include "collectiondb.moc"
|