consider .cues when scanning the library

persist the new 'beginning' marker of songs
fixed a bug where %allsongstables script would not update the attached databases
This commit is contained in:
Paweł Bara 2010-12-28 15:36:01 +00:00
parent 6fbe173d80
commit da5f941333
11 changed files with 107 additions and 37 deletions

View File

@ -293,5 +293,6 @@
<file>schema/schema-22.sql</file>
<file>schema/jamendo.sql</file>
<file>schema/schema-23.sql</file>
<file>schema/schema-24.sql</file>
</qresource>
</RCC>

View File

@ -35,7 +35,8 @@ CREATE TABLE jamendo.songs (
forced_compilation_off INTEGER NOT NULL DEFAULT 0,
effective_compilation NOT NULL DEFAULT 0,
skipcount NOT NULL DEFAULT 0,
score NOT NULL DEFAULT 0
score NOT NULL DEFAULT 0,
beginning NOT NULL DEFAULT 0
);
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(

View File

@ -0,0 +1,3 @@
ALTER TABLE %allsongstables ADD COLUMN beginning INTEGER NOT NULL DEFAULT 0;
UPDATE schema_version SET version=24;

View File

@ -31,7 +31,7 @@
#include <QVariant>
const char* Database::kDatabaseFilename = "clementine.db";
const int Database::kSchemaVersion = 23;
const int Database::kSchemaVersion = 24;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;
@ -536,10 +536,25 @@ void Database::ExecCommands(const QString &schema, QSqlDatabase &db) {
QStringList Database::SongsTables(QSqlDatabase& db) const {
QStringList ret;
// look for the tables in the main db
foreach (const QString& table, db.tables()) {
if (table == "songs" || table.endsWith("_songs"))
ret << table;
}
// look for the tables in attached dbs
foreach (const QString& key, attached_databases_.keys()) {
QSqlQuery q(QString("SELECT NAME FROM %1.sqlite_master"
" WHERE type='table' AND name='songs' OR name LIKE '%songs'").arg(key), db);
if (q.exec()) {
while(q.next()) {
QString tab_name = key + "." + q.value(0).toString();
ret << tab_name;
}
}
}
return ret;
}

View File

@ -93,7 +93,7 @@ const QStringList Song::kColumns = QStringList()
<< "mtime" << "ctime" << "filesize" << "sampler" << "art_automatic"
<< "art_manual" << "filetype" << "playcount" << "lastplayed" << "rating"
<< "forced_compilation_on" << "forced_compilation_off"
<< "effective_compilation" << "skipcount" << "score";
<< "effective_compilation" << "skipcount" << "score" << "beginning";
const QString Song::kColumnSpec = Song::kColumns.join(", ");
const QString Song::kBindSpec = Prepend(":", Song::kColumns).join(", ");
@ -466,8 +466,6 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
d->comment_ = tostr(col + 11);
d->compilation_ = q.value(col + 12).toBool();
// TODO: this should be replaced by beginning and end
set_length(toint(col + 13));
d->bitrate_ = toint(col + 14);
d->samplerate_ = toint(col + 15);
@ -494,9 +492,12 @@ void Song::InitFromQuery(const SqlRow& q, int col) {
// effective_compilation = 30
d->skipcount_ = q.value(col + 31).isNull() ? 0 : q.value(col + 31).toInt();
d->score_ = q.value(col + 32).isNull() ? 0 : q.value(col + 32).toInt();
// TODO: 'end' instead of 'length'
d->beginning_ = q.value(col + 33).isNull() ? 0 : q.value(col + 33).toInt();
set_length(toint(col + 13));
#undef tostr
#undef toint
#undef tofloat
@ -926,7 +927,7 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":comment", strval(d->comment_));
query->bindValue(":compilation", d->compilation_ ? 1 : 0);
// TODO: replace this with beginning and end
// TODO: replace this with 'end'
query->bindValue(":length", intval(length()));
query->bindValue(":bitrate", intval(d->bitrate_));
query->bindValue(":samplerate", intval(d->samplerate_));
@ -952,9 +953,10 @@ void Song::BindToQuery(QSqlQuery *query) const {
query->bindValue(":effective_compilation", is_compilation() ? 1 : 0);
query->bindValue(":skipcount", d->skipcount_);
query->bindValue(":score", d->score_);
query->bindValue(":beginning", d->beginning_);
#undef intval
#undef notnullintval
}
@ -1042,7 +1044,8 @@ bool Song::IsMetadataEqual(const Song& other) const {
d->genre_ == other.d->genre_ &&
d->comment_ == other.d->comment_ &&
d->compilation_ == other.d->compilation_ &&
// this should be replaced by beginning and end
d->beginning_ == other.d->beginning_ &&
// this should be replaced by 'end'
length() == other.length() &&
d->bitrate_ == other.d->bitrate_ &&
d->samplerate_ == other.d->samplerate_ &&

View File

@ -18,6 +18,7 @@
#include "librarywatcher.h"
#include "librarybackend.h"
#include "core/taskmanager.h"
#include "playlistparsers/cueparser.h"
#include <QFileSystemWatcher>
#include <QDirIterator>
@ -26,12 +27,15 @@
#include <QDateTime>
#include <QTimer>
#include <QSettings>
#include <QSet>
#include <taglib/fileref.h>
#include <taglib/tag.h>
// TODO: test removing a folder with cues
// TODO: what about .cue vs it's media file changes?
QStringList LibraryWatcher::sValidImages;
QStringList LibraryWatcher::sValidPlaylists;
const char* LibraryWatcher::kSettingsGroup = "LibraryWatcher";
@ -44,14 +48,14 @@ LibraryWatcher::LibraryWatcher(QObject* parent)
monitor_(true),
rescan_timer_(new QTimer(this)),
rescan_paused_(false),
total_watches_(0)
total_watches_(0),
cue_parser_(new CueParser(backend_, this))
{
rescan_timer_->setInterval(1000);
rescan_timer_->setSingleShot(true);
if (sValidImages.isEmpty()) {
sValidImages << "jpg" << "png" << "gif" << "jpeg";
sValidPlaylists << "m3u" << "pls";
}
ReloadSettings();
@ -278,6 +282,8 @@ void LibraryWatcher::ScanSubdirectory(
// Ask the database for a list of files in this directory
SongList songs_in_db = t->FindSongsInSubdirectory(path);
QSet<QString> cues_processed;
// Now compare the list from the database with the list of files on disk
foreach (const QString& file, files_on_disk) {
if (stop_requested_) return;
@ -304,6 +310,7 @@ void LibraryWatcher::ScanSubdirectory(
changed = true;
}
// TODO: cues
if (changed) {
qDebug() << file << "changed";
@ -334,18 +341,42 @@ void LibraryWatcher::ScanSubdirectory(
}
} else {
// The song is on disk but not in the DB
SongList song_list;
Song song;
song.InitFromFile(file, t->dir());
if (!song.is_valid())
QString matching_cue = NoExtensionPart(file) + ".cue";
// don't process the same cue many times
if(cues_processed.contains(matching_cue))
continue;
// it's a cue - create virtual tracks
if(QFile::exists(matching_cue)) {
QFile cue(matching_cue);
cue.open(QIODevice::ReadOnly);
song_list = cue_parser_->Load(&cue, path);
cues_processed << matching_cue;
// it's a normal media file
} else {
Song song;
song.InitFromFile(file, -1);
if (!song.is_valid())
continue;
song_list << song;
}
qDebug() << file << "created";
// choose an image for the song(s)
QString image = ImageForSong(file, album_art);
// Choose an image for the song
if (song.art_automatic().isEmpty())
song.set_art_automatic(ImageForSong(file, album_art));
foreach (Song song, song_list) {
song.set_directory_id(t->dir());
if (song.art_automatic().isEmpty())
song.set_art_automatic(image);
t->new_songs << song;
t->new_songs << song;
}
}
}

View File

@ -28,6 +28,7 @@
class QFileSystemWatcher;
class QTimer;
class CueParser;
class LibraryBackend;
class TaskManager;
@ -126,6 +127,7 @@ class LibraryWatcher : public QObject {
private:
static bool FindSongByPath(const SongList& list, const QString& path, Song* out);
inline static QString NoExtensionPart( const QString &fileName );
inline static QString ExtensionPart( const QString &fileName );
inline static QString DirectoryPart( const QString &fileName );
QString PickBestImage(const QStringList& images);
@ -161,14 +163,18 @@ class LibraryWatcher : public QObject {
int total_watches_;
CueParser* cue_parser_;
static QStringList sValidImages;
static QStringList sValidPlaylists;
#ifdef Q_OS_DARWIN
static const int kMaxWatches = 100;
#endif
};
inline QString LibraryWatcher::NoExtensionPart( const QString &fileName ) {
return fileName.contains( '.' ) ? fileName.section( '.', 0, -2 ) : "";
}
// Thanks Amarok
inline QString LibraryWatcher::ExtensionPart( const QString &fileName ) {
return fileName.contains( '.' ) ? fileName.mid( fileName.lastIndexOf('.') + 1 ).toLower() : "";

View File

@ -93,19 +93,21 @@ QFuture<PlaylistItemPtr> PlaylistBackend::GetPlaylistItems(int playlist) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QSqlQuery q("SELECT songs.ROWID, " + Song::JoinSpec("songs") + ","
" magnatune_songs.ROWID, " + Song::JoinSpec("magnatune_songs") + ","
" jamendo_songs.ROWID, " + Song::JoinSpec("jamendo_songs") + ","
" p.type, p.url, p.title, p.artist, p.album, p.length,"
" p.radio_service"
" FROM playlist_items AS p"
" LEFT JOIN songs"
" ON p.library_id = songs.ROWID"
" LEFT JOIN magnatune_songs"
" ON p.library_id = magnatune_songs.ROWID"
" LEFT JOIN jamendo.songs AS jamendo_songs"
" ON p.library_id = jamendo_songs.ROWID"
" WHERE p.playlist = :playlist", db);
QString query = "SELECT songs.ROWID, " + Song::JoinSpec("songs") + ","
" magnatune_songs.ROWID, " + Song::JoinSpec("magnatune_songs") + ","
" jamendo_songs.ROWID, " + Song::JoinSpec("jamendo_songs") + ","
" p.type, p.url, p.title, p.artist, p.album, p.length,"
" p.radio_service"
" FROM playlist_items AS p"
" LEFT JOIN songs"
" ON p.library_id = songs.ROWID"
" LEFT JOIN magnatune_songs"
" ON p.library_id = magnatune_songs.ROWID"
" LEFT JOIN jamendo.songs AS jamendo_songs"
" ON p.library_id = jamendo_songs.ROWID"
" WHERE p.playlist = :playlist";
QSqlQuery q(query, db);
q.bindValue(":playlist", playlist);
q.exec();
if (db_->CheckErrors(q.lastError()))

View File

@ -175,13 +175,14 @@ SongList CueParser::Load(QIODevice* device, const QDir& dir) const {
if (!ParseTrackLocation(entry.file, dir, &current)) {
qWarning() << "failed to parse location in .cue file from " << dir_path;
} else {
Song song = LoadLibrarySong(current.filename());
// look for the section in library
Song song = LoadLibrarySong(current.filename(), IndexToMarker(entry.index));
if (!song.is_valid()) {
song.InitFromFile(current.filename(), -1);
}
// overwrite the stuff, we may have read from the file, using
// the .cue's metadata
// overwrite the stuff, we may have read from the file or library, using
// the current .cue metadata
if(i + 1 < entries.size()) {
// incorrect indices?
if(!UpdateSong(entry, entries.at(i + 1).index, &song)) {

View File

@ -79,7 +79,7 @@ QString ParserBase::MakeUrl(const QString& filename_or_url) const {
return QUrl::fromLocalFile(filename_or_url).toString();
}
Song ParserBase::LoadLibrarySong(const QString& filename_or_url) const {
Song ParserBase::LoadLibrarySong(const QString& filename_or_url, int beginning) const {
if (!library_)
return Song();
@ -93,6 +93,7 @@ Song ParserBase::LoadLibrarySong(const QString& filename_or_url) const {
LibraryQuery query;
query.SetColumnSpec("%songs_table.ROWID, " + Song::kColumnSpec);
query.AddWhere("filename", info.canonicalFilePath());
query.AddWhere("beginning", beginning);
Song song;
if (library_->ExecQuery(&query) && query.Next()) {
@ -100,3 +101,7 @@ Song ParserBase::LoadLibrarySong(const QString& filename_or_url) const {
}
return song;
}
Song ParserBase::LoadLibrarySong(const QString& filename_or_url) const {
return LoadLibrarySong(filename_or_url, 0);
}

View File

@ -57,6 +57,8 @@ protected:
// a song with that path. If one is found, returns it, otherwise returns an
// invalid song.
Song LoadLibrarySong(const QString& filename_or_url) const;
// Just like the method above, but looks for a SECTION of a song.
Song LoadLibrarySong(const QString& filename_or_url, int beginning) const;
private:
LibraryBackendInterface* library_;