diff --git a/data/data.qrc b/data/data.qrc
index 32add3588..84cb9fbbc 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -293,5 +293,6 @@
schema/schema-22.sql
schema/jamendo.sql
schema/schema-23.sql
+ schema/schema-24.sql
diff --git a/data/schema/jamendo.sql b/data/schema/jamendo.sql
index af34e7537..db23e703f 100644
--- a/data/schema/jamendo.sql
+++ b/data/schema/jamendo.sql
@@ -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(
diff --git a/data/schema/schema-24.sql b/data/schema/schema-24.sql
new file mode 100644
index 000000000..d010867f5
--- /dev/null
+++ b/data/schema/schema-24.sql
@@ -0,0 +1,3 @@
+ALTER TABLE %allsongstables ADD COLUMN beginning INTEGER NOT NULL DEFAULT 0;
+
+UPDATE schema_version SET version=24;
diff --git a/src/core/database.cpp b/src/core/database.cpp
index 5128b182e..058d0fb14 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -31,7 +31,7 @@
#include
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;
}
diff --git a/src/core/song.cpp b/src/core/song.cpp
index c1fc47270..91e7921bb 100644
--- a/src/core/song.cpp
+++ b/src/core/song.cpp
@@ -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_ &&
diff --git a/src/library/librarywatcher.cpp b/src/library/librarywatcher.cpp
index 4afc197ef..2c34fff44 100644
--- a/src/library/librarywatcher.cpp
+++ b/src/library/librarywatcher.cpp
@@ -18,6 +18,7 @@
#include "librarywatcher.h"
#include "librarybackend.h"
#include "core/taskmanager.h"
+#include "playlistparsers/cueparser.h"
#include
#include
@@ -26,12 +27,15 @@
#include
#include
#include
+#include
#include
#include
+// 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 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;
+ }
}
}
diff --git a/src/library/librarywatcher.h b/src/library/librarywatcher.h
index 6ccaabb64..212ea1beb 100644
--- a/src/library/librarywatcher.h
+++ b/src/library/librarywatcher.h
@@ -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() : "";
diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp
index 2bd2a6985..92942a2cc 100644
--- a/src/playlist/playlistbackend.cpp
+++ b/src/playlist/playlistbackend.cpp
@@ -93,19 +93,21 @@ QFuture 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()))
diff --git a/src/playlistparsers/cueparser.cpp b/src/playlistparsers/cueparser.cpp
index fc3bb295a..0561c120b 100644
--- a/src/playlistparsers/cueparser.cpp
+++ b/src/playlistparsers/cueparser.cpp
@@ -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)) {
diff --git a/src/playlistparsers/parserbase.cpp b/src/playlistparsers/parserbase.cpp
index 6812e2c5b..cfe947b89 100644
--- a/src/playlistparsers/parserbase.cpp
+++ b/src/playlistparsers/parserbase.cpp
@@ -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);
+}
diff --git a/src/playlistparsers/parserbase.h b/src/playlistparsers/parserbase.h
index e04a3a06b..c112c84fb 100644
--- a/src/playlistparsers/parserbase.h
+++ b/src/playlistparsers/parserbase.h
@@ -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_;