diff --git a/data/data.qrc b/data/data.qrc index cad3b156c..378a0cfea 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -86,5 +86,6 @@ edit-redo.png edit-undo.png magnatune.png + schema-8.sql diff --git a/data/schema-8.sql b/data/schema-8.sql new file mode 100644 index 000000000..3e4fe08b1 --- /dev/null +++ b/data/schema-8.sql @@ -0,0 +1,40 @@ +/* Schema should be kept identical to the "songs" table, even though most of + it isn't used by magnatune */ +CREATE TABLE magnatune_songs ( + title TEXT, + album TEXT, + artist TEXT, + albumartist TEXT, + composer TEXT, + track INTEGER, + disc INTEGER, + bpm REAL, + year INTEGER, + genre TEXT, + comment TEXT, + compilation INTEGER, + + length INTEGER, + bitrate INTEGER, + samplerate INTEGER, + + directory INTEGER NOT NULL, + filename TEXT NOT NULL, + mtime INTEGER NOT NULL, + ctime INTEGER NOT NULL, + filesize INTEGER NOT NULL, + + sampler INTEGER NOT NULL DEFAULT 0, + art_automatic TEXT, + art_manual TEXT, + filetype INTEGER NOT NULL DEFAULT 0, + playcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER, + rating INTEGER, + forced_compilation_on INTEGER NOT NULL DEFAULT 0, + forced_compilation_off INTEGER NOT NULL DEFAULT 0, + effective_compilation NOT NULL DEFAULT 0 +); + +UPDATE schema_version SET version=8; + diff --git a/src/database.cpp b/src/database.cpp index db5f4e338..83fe46eab 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -27,7 +27,7 @@ #include const char* Database::kDatabaseFilename = "clementine.db"; -const int Database::kSchemaVersion = 7; +const int Database::kSchemaVersion = 8; int (*Database::_sqlite3_create_function) ( sqlite3*, const char*, int, int, void*, diff --git a/src/librarybackend.cpp b/src/librarybackend.cpp index ba165d21a..40172b3e9 100644 --- a/src/librarybackend.cpp +++ b/src/librarybackend.cpp @@ -228,12 +228,14 @@ void LibraryBackend::AddOrUpdateSongs(const SongList& songs) { // Do a sanity check first - make sure the song's directory still exists // This is to fix a possible race condition when a directory is removed // while LibraryWatcher is scanning it. - check_dir.bindValue(":id", song.directory_id()); - check_dir.exec(); - if (db_->CheckErrors(check_dir.lastError())) continue; + if (!dirs_table_.isEmpty()) { + check_dir.bindValue(":id", song.directory_id()); + check_dir.exec(); + if (db_->CheckErrors(check_dir.lastError())) continue; - if (!check_dir.next()) - continue; // Directory didn't exist + if (!check_dir.next()) + continue; // Directory didn't exist + } if (song.id() == -1) { diff --git a/src/magnatuneservice.cpp b/src/magnatuneservice.cpp index 1cf7c4a58..a8c2d156d 100644 --- a/src/magnatuneservice.cpp +++ b/src/magnatuneservice.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -36,10 +37,20 @@ const char* MagnatuneService::kDatabaseUrl = MagnatuneService::MagnatuneService(RadioModel* parent) : RadioService(kServiceName, parent), root_(NULL), - library_backend_(new LibraryBackend(parent->db(), "songs", "", "", this)), + library_backend_(new LibraryBackend(parent->db(), "magnatune_songs", "", "", this)), library_model_(new LibraryModel(library_backend_, this)), + library_sort_model_(new QSortFilterProxyModel(this)), + total_song_count_(0), network_(new QNetworkAccessManager(this)) { + connect(library_backend_, SIGNAL(TotalSongCountUpdated(int)), + SLOT(UpdateTotalSongCount(int))); + + library_sort_model_->setSourceModel(library_model_); + library_sort_model_->setSortRole(LibraryModel::Role_SortText); + library_sort_model_->setDynamicSortFilter(true); + library_sort_model_->sort(0); + library_model_->Init(); } @@ -49,7 +60,7 @@ RadioItem* MagnatuneService::CreateRootItem(RadioItem *parent) { model()->merged_model()->AddSubModel( model()->index(root_->row, 0, model()->ItemToIndex(parent)), - library_model_); + library_sort_model_); return root_; } @@ -57,7 +68,8 @@ RadioItem* MagnatuneService::CreateRootItem(RadioItem *parent) { void MagnatuneService::LazyPopulate(RadioItem *item) { switch (item->type) { case RadioItem::Type_Service: - ReloadDatabase(); + if (total_song_count_ == 0) + ReloadDatabase(); break; default: @@ -103,18 +115,22 @@ void MagnatuneService::ReloadDatabaseFinished() { return; } + SongList songs; + QXmlStreamReader reader(&gzip); while (!reader.atEnd()) { reader.readNext(); if (reader.tokenType() == QXmlStreamReader::StartElement && reader.name() == "Track") { - ReadTrack(reader); + songs << ReadTrack(reader); } } + + library_backend_->AddOrUpdateSongs(songs); } -void MagnatuneService::ReadTrack(QXmlStreamReader& reader) { +Song MagnatuneService::ReadTrack(QXmlStreamReader& reader) { QXmlStreamAttributes attributes = reader.attributes(); Song song; @@ -126,5 +142,11 @@ void MagnatuneService::ReadTrack(QXmlStreamReader& reader) { song.set_year(attributes.value("year").toString().toInt()); song.set_filename(attributes.value("url").toString()); - qDebug() << song.artist() << song.album() << song.title(); + // We need to set these to satisfy the database constraints + song.set_directory_id(0); + song.set_mtime(0); + song.set_ctime(0); + song.set_filesize(0); + + return song; } diff --git a/src/magnatuneservice.h b/src/magnatuneservice.h index 2fa667d7b..46178299a 100644 --- a/src/magnatuneservice.h +++ b/src/magnatuneservice.h @@ -22,6 +22,7 @@ #include "radioservice.h" class QNetworkAccessManager; +class QSortFilterProxyModel; class LibraryBackend; class LibraryModel; @@ -41,16 +42,20 @@ class MagnatuneService : public RadioService { void StartLoading(const QUrl &url); private slots: + void UpdateTotalSongCount(int count) { total_song_count_ = count; } void ReloadDatabase(); void ReloadDatabaseFinished(); private: - void ReadTrack(QXmlStreamReader& reader); + Song ReadTrack(QXmlStreamReader& reader); private: RadioItem* root_; LibraryBackend* library_backend_; LibraryModel* library_model_; + QSortFilterProxyModel* library_sort_model_; + + int total_song_count_; QNetworkAccessManager* network_; }; diff --git a/src/mergedproxymodel.cpp b/src/mergedproxymodel.cpp index b20d3df0f..cc7b97827 100644 --- a/src/mergedproxymodel.cpp +++ b/src/mergedproxymodel.cpp @@ -121,8 +121,8 @@ QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_pa void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end) { - beginInsertRows(GetActualSourceParent( - source_parent, static_cast(sender())), + beginInsertRows(mapFromSource(GetActualSourceParent( + source_parent, static_cast(sender()))), start, end); } @@ -132,8 +132,8 @@ void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) { void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end) { - beginRemoveRows(GetActualSourceParent( - source_parent, static_cast(sender())), + beginRemoveRows(mapFromSource(GetActualSourceParent( + source_parent, static_cast(sender()))), start, end); } @@ -201,8 +201,14 @@ int MergedProxyModel::rowCount(const QModelIndex &parent) const { QModelIndex source_parent = mapToSource(parent); const QAbstractItemModel* child_model = merge_points_.key(source_parent); - if (child_model) + if (child_model) { + // Query the source model but disregard what it says, so it gets a chance + // to lazy load + source_parent.model()->rowCount(source_parent); + return child_model->rowCount(QModelIndex()); + } + return source_parent.model()->rowCount(source_parent); } @@ -225,7 +231,8 @@ bool MergedProxyModel::hasChildren(const QModelIndex &parent) const { const QAbstractItemModel* child_model = merge_points_.key(source_parent); if (child_model) - return child_model->hasChildren(QModelIndex()); + return child_model->hasChildren(QModelIndex()) || + source_parent.model()->hasChildren(source_parent); return source_parent.model()->hasChildren(source_parent); } diff --git a/tests/mergedproxymodel_test.cpp b/tests/mergedproxymodel_test.cpp index 6be23e812..cf6bd9bd9 100644 --- a/tests/mergedproxymodel_test.cpp +++ b/tests/mergedproxymodel_test.cpp @@ -19,6 +19,7 @@ #include "mergedproxymodel.h" #include +#include class MergedProxyModelTest : public ::testing::Test { protected: @@ -67,7 +68,7 @@ TEST_F(MergedProxyModelTest, Merged) { QStandardItemModel submodel; submodel.appendRow(new QStandardItem("two")); - merged_.AddModel(source_.index(0, 0, QModelIndex()), &submodel); + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); ASSERT_EQ(1, merged_.rowCount(QModelIndex())); QModelIndex one_i = merged_.index(0, 0, QModelIndex()); @@ -82,3 +83,79 @@ TEST_F(MergedProxyModelTest, Merged) { EXPECT_EQ(0, merged_.rowCount(two_i)); EXPECT_FALSE(merged_.hasChildren(two_i)); } + +TEST_F(MergedProxyModelTest, SourceInsert) { + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsInserted(QModelIndex,int,int))); + + source_.appendRow(new QStandardItem("one")); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_FALSE(before_spy[0][0].value().isValid()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_FALSE(after_spy[0][0].value().isValid()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); +} + +TEST_F(MergedProxyModelTest, SourceRemove) { + source_.appendRow(new QStandardItem("one")); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + source_.removeRow(0, QModelIndex()); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_FALSE(before_spy[0][0].value().isValid()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_FALSE(after_spy[0][0].value().isValid()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); +} + +TEST_F(MergedProxyModelTest, SubInsert) { + source_.appendRow(new QStandardItem("one")); + QStandardItemModel submodel; + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsInserted(QModelIndex,int,int))); + + submodel.appendRow(new QStandardItem("two")); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_EQ("one", before_spy[0][0].value().data()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_EQ("one", after_spy[0][0].value().data()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); +} + +TEST_F(MergedProxyModelTest, SubRemove) { + source_.appendRow(new QStandardItem("one")); + QStandardItemModel submodel; + merged_.AddSubModel(source_.index(0, 0, QModelIndex()), &submodel); + + submodel.appendRow(new QStandardItem("two")); + + QSignalSpy before_spy(&merged_, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy after_spy(&merged_, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + submodel.removeRow(0, QModelIndex()); + + ASSERT_EQ(1, before_spy.count()); + ASSERT_EQ(1, after_spy.count()); + EXPECT_EQ("one", before_spy[0][0].value().data()); + EXPECT_EQ(0, before_spy[0][1].toInt()); + EXPECT_EQ(0, before_spy[0][2].toInt()); + EXPECT_EQ("one", after_spy[0][0].value().data()); + EXPECT_EQ(0, after_spy[0][1].toInt()); + EXPECT_EQ(0, after_spy[0][2].toInt()); +} diff --git a/tests/test_utils.h b/tests/test_utils.h index 0e48e9d5b..84e5f586f 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -19,6 +19,9 @@ #include +#include +#include + class QNetworkRequest; class QString; class QUrl; @@ -39,4 +42,6 @@ void PrintTo(const ::QVariant& var, std::ostream& os); #define EXPOSE_SIGNAL2(n, t1, t2) \ void Emit##n(const t1& a1, const t2& a2) { emit n(a1, a2); } +Q_DECLARE_METATYPE(QModelIndex); + #endif // TEST_UTILS_H