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:
parent
6fbe173d80
commit
da5f941333
|
@ -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>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE %allsongstables ADD COLUMN beginning INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE schema_version SET version=24;
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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_ &&
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() : "";
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -175,13 +175,14 @@ SongList CueParser::Load(QIODevice* device, const QDir& dir) const {
|
|||
if (!ParseTrackLocation(entry.file, dir, ¤t)) {
|
||||
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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
|
|
Loading…
Reference in New Issue