Merge lp:~jamesh/mediascanner2/model-auto-update into lp:mediascanner2

Proposed by James Henstridge on 2014-08-07
Status: Merged
Approved by: Jussi Pakkanen on 2014-08-14
Approved revision: 270
Merged at revision: 261
Proposed branch: lp:~jamesh/mediascanner2/model-auto-update
Merge into: lp:mediascanner2
Diff against target: 1081 lines (+287/-172)
21 files modified
debian/control (+0/-1)
src/daemon/InvalidationSender.cc (+28/-15)
src/daemon/InvalidationSender.hh (+7/-2)
src/daemon/scannerdaemon.cc (+44/-5)
src/qml/Ubuntu/MediaScanner/AlbumsModel.cc (+1/-8)
src/qml/Ubuntu/MediaScanner/ArtistsModel.cc (+4/-9)
src/qml/Ubuntu/MediaScanner/CMakeLists.txt (+1/-1)
src/qml/Ubuntu/MediaScanner/GenresModel.cc (+1/-8)
src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.cc (+12/-0)
src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.hh (+6/-0)
src/qml/Ubuntu/MediaScanner/SongsModel.cc (+1/-8)
src/qml/Ubuntu/MediaScanner/SongsSearchModel.cc (+1/-6)
src/qml/Ubuntu/MediaScanner/StreamingModel.cc (+49/-10)
src/qml/Ubuntu/MediaScanner/StreamingModel.hh (+17/-4)
src/qml/Ubuntu/MediaScanner/plugin.qmltypes (+11/-1)
test/basic.cc (+0/-2)
test/qml/tst_albumsmodel.qml (+26/-27)
test/qml/tst_artistsmodel.qml (+22/-20)
test/qml/tst_genresmodel.qml (+11/-4)
test/qml/tst_songsearchmodel.qml (+14/-6)
test/qml/tst_songsmodel.qml (+31/-35)
To merge this branch: bzr merge lp:~jamesh/mediascanner2/model-auto-update
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve on 2014-08-12
Jussi Pakkanen (community) 2014-08-07 Approve on 2014-08-11
Review via email: mp+229903@code.launchpad.net

Commit message

Automatically update models when the daemon sends the InvalidateResults D-Bus signal. Add a status property to models to let loading progress be tracked. Rename rowCount property to count, keeping the old name around for compatibility.

Description of the change

Automatically update the QML models when the scanner daemon sends out an invalidation signal.

I am reusing the signal sent for the benefit of the dash, and as part of the changes brought the broadcast code in-process so that it could be matched with a well known bus name.

I also added a StreamingModel::status property to replace the "filled" signal (which was only being used in tests), following the model of the QtQuick Image's property of the same name. This should make it easier to provide progress feedback in the UI.

Lastly, I've renamed the "rowCount" property to "count" to match e.g. SortFilterModel from the Ubuntu UI toolkit. The old name has been retained for backward compatibility though.

To post a comment you must log in.
Jussi Pakkanen (jpakkane) wrote :

Looking good. Only nitpick:

ModelStatus status_;

Why the trailing underscore? No other variable in the class has decoration. If it's this:

void StreamingModel::setStatus(StreamingModel::ModelStatus status)

then change the argument to new_status or something similar.

Regardless of that, this looks fine.

review: Approve
James Henstridge (jamesh) wrote :

It was the StreamingModel::status() method, actually. I guess that could be renamed to getStatus() instead.

270. By James Henstridge on 2014-08-12

Rename method and member based on Jussi's review.

Can the music app devs be notified when this hits a silo so we can do some testing?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/control'
--- debian/control 2014-07-30 08:38:15 +0000
+++ debian/control 2014-08-12 05:42:14 +0000
@@ -52,7 +52,6 @@
52Pre-Depends: ${misc:Pre-Depends},52Pre-Depends: ${misc:Pre-Depends},
53Depends: gstreamer1.0-plugins-base,53Depends: gstreamer1.0-plugins-base,
54 gstreamer1.0-plugins-good,54 gstreamer1.0-plugins-good,
55# For dbus-send.
56 dbus,55 dbus,
57 ${misc:Depends},56 ${misc:Depends},
58 ${shlibs:Depends},57 ${shlibs:Depends},
5958
=== modified file 'src/daemon/InvalidationSender.cc'
--- src/daemon/InvalidationSender.cc 2014-03-06 02:49:18 +0000
+++ src/daemon/InvalidationSender.cc 2014-08-12 05:42:14 +0000
@@ -22,13 +22,19 @@
22#include<cstdlib>22#include<cstdlib>
23#include<cstdio>23#include<cstdio>
24#include <glib.h>24#include <glib.h>
25#include <gio/gio.h>
2526
26using namespace std;27using namespace std;
2728
28// timer delay in seconds29// timer delay in seconds
29const unsigned int DELAY = 1;30const unsigned int DELAY = 1;
3031
31InvalidationSender::InvalidationSender() : enabled(true), timeout_id(0) {32static const char SCOPES_DBUS_IFACE[] = "com.canonical.unity.scopes";
33static const char SCOPES_DBUS_PATH[] = "/com/canonical/unity/scopes";
34static const char SCOPES_INVALIDATE_RESULTS[] = "InvalidateResults";
35
36InvalidationSender::InvalidationSender() :
37 bus(nullptr, g_object_unref), timeout_id(0) {
32}38}
3339
34InvalidationSender::~InvalidationSender() {40InvalidationSender::~InvalidationSender() {
@@ -37,8 +43,12 @@
37 }43 }
38}44}
3945
46void InvalidationSender::setBus(GDBusConnection *bus) {
47 this->bus.reset(static_cast<GDBusConnection*>(g_object_ref(bus)));
48}
49
40void InvalidationSender::invalidate() {50void InvalidationSender::invalidate() {
41 if (!enabled) {51 if (!bus) {
42 return;52 return;
43 }53 }
44 if (timeout_id != 0) {54 if (timeout_id != 0) {
@@ -49,22 +59,25 @@
4959
50int InvalidationSender::callback(void *data) {60int InvalidationSender::callback(void *data) {
51 auto invalidator = static_cast<InvalidationSender*>(data);61 auto invalidator = static_cast<InvalidationSender*>(data);
62 GError *error = nullptr;
5263
53 string invocation("dbus-send /com/canonical/unity/scopes ");64 if (!g_dbus_connection_emit_signal(
54 invocation += "com.canonical.unity.scopes.InvalidateResults string:";65 invalidator->bus.get(), nullptr,
55 const string m_invoc = invocation + "mediascanner-music";66 SCOPES_DBUS_PATH, SCOPES_DBUS_IFACE, SCOPES_INVALIDATE_RESULTS,
56 const string v_invoc = invocation + "mediascanner-video";67 g_variant_new("(s)", "mediascanner-music"), &error)) {
57 if(system(m_invoc.c_str()) != 0) {68 fprintf(stderr, "Could not invalidate music scope results: %s\n", error->message);
58 fprintf(stderr, "Could not invalidate music scope results.\n");69 g_error_free(error);
70 error = nullptr;
59 }71 }
60 if(system(v_invoc.c_str()) != 0) {72 if (!g_dbus_connection_emit_signal(
61 fprintf(stderr, "Could not invalidate video scope results.\n");73 invalidator->bus.get(), nullptr,
74 SCOPES_DBUS_PATH, SCOPES_DBUS_IFACE, SCOPES_INVALIDATE_RESULTS,
75 g_variant_new("(s)", "mediascanner-video"), &error)) {
76 fprintf(stderr, "Could not invalidate video scope results: %s\n", error->message);
77 g_error_free(error);
78 error = nullptr;
62 }79 }
6380
64 invalidator->timeout_id = 0;81 invalidator->timeout_id = 0;
65 return FALSE;82 return G_SOURCE_REMOVE;
66}
67
68void InvalidationSender::disable() {
69 enabled = false;
70}83}
7184
=== modified file 'src/daemon/InvalidationSender.hh'
--- src/daemon/InvalidationSender.hh 2014-03-06 02:49:18 +0000
+++ src/daemon/InvalidationSender.hh 2014-08-12 05:42:14 +0000
@@ -20,6 +20,10 @@
20#ifndef INVALIDATIONSENDER_HH20#ifndef INVALIDATIONSENDER_HH
21#define INVALIDATIONSENDER_HH21#define INVALIDATIONSENDER_HH
2222
23#include <memory>
24
25typedef struct _GDBusConnection GDBusConnection;
26
23/**27/**
24 * A class that sends a broadcast signal that the state of media28 * A class that sends a broadcast signal that the state of media
25 * files has changed.29 * files has changed.
@@ -33,11 +37,12 @@
33 InvalidationSender& operator=(const InvalidationSender &o) = delete;37 InvalidationSender& operator=(const InvalidationSender &o) = delete;
3438
35 void invalidate();39 void invalidate();
36 void disable();40 void setBus(GDBusConnection *bus);
41
37private:42private:
38 static int callback(void *data);43 static int callback(void *data);
3944
40 bool enabled;45 std::unique_ptr<GDBusConnection, void(*)(void*)> bus;
41 unsigned int timeout_id;46 unsigned int timeout_id;
42};47};
4348
4449
=== modified file 'src/daemon/scannerdaemon.cc'
--- src/daemon/scannerdaemon.cc 2014-07-25 09:31:02 +0000
+++ src/daemon/scannerdaemon.cc 2014-08-12 05:42:14 +0000
@@ -32,6 +32,7 @@
3232
33#include<glib.h>33#include<glib.h>
34#include<glib-unix.h>34#include<glib-unix.h>
35#include<gio/gio.h>
35#include<gst/gst.h>36#include<gst/gst.h>
3637
37#include "../mediascanner/MediaFile.hh"38#include "../mediascanner/MediaFile.hh"
@@ -46,6 +47,8 @@
4647
47using namespace mediascanner;48using namespace mediascanner;
4849
50static const char BUS_NAME[] = "com.canonical.MediaScanner2.Daemon";
51
49class ScannerDaemon final {52class ScannerDaemon final {
50public:53public:
51 ScannerDaemon();54 ScannerDaemon();
@@ -54,6 +57,7 @@
5457
55private:58private:
5659
60 void setupBus();
57 void setupSignals();61 void setupSignals();
58 void setupMountWatcher();62 void setupMountWatcher();
59 void readFiles(MediaStore &store, const string &subdir, const MediaType type);63 void readFiles(MediaStore &store, const string &subdir, const MediaType type);
@@ -61,12 +65,13 @@
61 void removeDir(const string &dir);65 void removeDir(const string &dir);
62 static gboolean sourceCallback(int, GIOCondition, gpointer data);66 static gboolean sourceCallback(int, GIOCondition, gpointer data);
63 static gboolean signalCallback(gpointer data);67 static gboolean signalCallback(gpointer data);
68 static void busNameLostCallback(GDBusConnection *connection, const char *name, gpointer data);
64 void processEvents();69 void processEvents();
65 void addMountedVolumes();70 void addMountedVolumes();
6671
67 int mountfd;72 int mountfd;
68 unique_ptr<GSource,void(*)(GSource*)> mount_source;73 unique_ptr<GSource,void(*)(GSource*)> mount_source;
69 int sigint_id, sigterm_id;74 unsigned int sigint_id = 0, sigterm_id = 0;
70 string mountDir;75 string mountDir;
71 string cachedir;76 string cachedir;
72 unique_ptr<MediaStore> store;77 unique_ptr<MediaStore> store;
@@ -74,6 +79,8 @@
74 map<string, unique_ptr<SubtreeWatcher>> subtrees;79 map<string, unique_ptr<SubtreeWatcher>> subtrees;
75 InvalidationSender invalidator;80 InvalidationSender invalidator;
76 unique_ptr<GMainLoop,void(*)(GMainLoop*)> main_loop;81 unique_ptr<GMainLoop,void(*)(GMainLoop*)> main_loop;
82 unique_ptr<GDBusConnection,void(*)(void*)> session_bus;
83 unsigned int bus_name_id = 0;
77};84};
7885
79static std::string getCurrentUser() {86static std::string getCurrentUser() {
@@ -88,8 +95,10 @@
88}95}
8996
90ScannerDaemon::ScannerDaemon() :97ScannerDaemon::ScannerDaemon() :
91 mount_source(nullptr, g_source_unref), sigint_id(0), sigterm_id(0),98 mount_source(nullptr, g_source_unref),
92 main_loop(g_main_loop_new(nullptr, FALSE), g_main_loop_unref) {99 main_loop(g_main_loop_new(nullptr, FALSE), g_main_loop_unref),
100 session_bus(nullptr, g_object_unref) {
101 setupBus();
93 mountDir = string("/media/") + getCurrentUser();102 mountDir = string("/media/") + getCurrentUser();
94 unique_ptr<MediaStore> tmp(new MediaStore(MS_READ_WRITE, "/media/"));103 unique_ptr<MediaStore> tmp(new MediaStore(MS_READ_WRITE, "/media/"));
95 store = move(tmp);104 store = move(tmp);
@@ -127,13 +136,43 @@
127 if (mount_source) {136 if (mount_source) {
128 g_source_destroy(mount_source.get());137 g_source_destroy(mount_source.get());
129 }138 }
139 if (bus_name_id != 0) {
140 g_bus_unown_name(bus_name_id);
141 }
130 close(mountfd);142 close(mountfd);
131}143}
132144
145void ScannerDaemon::busNameLostCallback(GDBusConnection *, const char *name,
146 gpointer data) {
147 ScannerDaemon *daemon = static_cast<ScannerDaemon*>(data);
148 fprintf(stderr, "Exiting due to loss of control of bus name %s\n", name);
149 daemon->bus_name_id = 0;
150 g_main_loop_quit(daemon->main_loop.get());
151}
152
153void ScannerDaemon::setupBus() {
154 GError *error = nullptr;
155 session_bus.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error));
156 if (!session_bus) {
157 string errortxt(error->message);
158 g_error_free(error);
159 string msg = "Failed to connect to session bus: ";
160 msg += errortxt;
161 throw runtime_error(msg);
162 }
163 invalidator.setBus(session_bus.get());
164
165 bus_name_id = g_bus_own_name_on_connection(
166 session_bus.get(), BUS_NAME, static_cast<GBusNameOwnerFlags>(
167 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
168 G_BUS_NAME_OWNER_FLAGS_REPLACE),
169 nullptr, &ScannerDaemon::busNameLostCallback, this, nullptr);
170}
171
133gboolean ScannerDaemon::signalCallback(gpointer data) {172gboolean ScannerDaemon::signalCallback(gpointer data) {
134 ScannerDaemon *daemon = static_cast<ScannerDaemon*>(data);173 ScannerDaemon *daemon = static_cast<ScannerDaemon*>(data);
135 g_main_loop_quit(daemon->main_loop.get());174 g_main_loop_quit(daemon->main_loop.get());
136 return TRUE;175 return G_SOURCE_CONTINUE;
137}176}
138177
139void ScannerDaemon::setupSignals() {178void ScannerDaemon::setupSignals() {
@@ -201,7 +240,7 @@
201gboolean ScannerDaemon::sourceCallback(int, GIOCondition, gpointer data) {240gboolean ScannerDaemon::sourceCallback(int, GIOCondition, gpointer data) {
202 ScannerDaemon *daemon = static_cast<ScannerDaemon*>(data);241 ScannerDaemon *daemon = static_cast<ScannerDaemon*>(data);
203 daemon->processEvents();242 daemon->processEvents();
204 return TRUE;243 return G_SOURCE_CONTINUE;
205}244}
206245
207void ScannerDaemon::setupMountWatcher() {246void ScannerDaemon::setupMountWatcher() {
208247
=== modified file 'src/qml/Ubuntu/MediaScanner/AlbumsModel.cc'
--- src/qml/Ubuntu/MediaScanner/AlbumsModel.cc 2014-07-10 06:44:15 +0000
+++ src/qml/Ubuntu/MediaScanner/AlbumsModel.cc 2014-08-12 05:42:14 +0000
@@ -18,7 +18,6 @@
18 */18 */
1919
20#include "AlbumsModel.hh"20#include "AlbumsModel.hh"
21#include <exception>
22#include <QDebug>21#include <QDebug>
2322
24using namespace mediascanner::qml;23using namespace mediascanner::qml;
@@ -102,12 +101,6 @@
102 auto limit_filter = filter;101 auto limit_filter = filter;
103 limit_filter.setLimit(limit);102 limit_filter.setLimit(limit);
104 limit_filter.setOffset(offset);103 limit_filter.setOffset(offset);
105 std::vector<mediascanner::Album> albums;
106 try {
107 albums = store->listAlbums(limit_filter);
108 } catch (const std::exception &e) {
109 qWarning() << "Failed to retrieve album list:" << e.what();
110 }
111 return std::unique_ptr<StreamingModel::RowData>(104 return std::unique_ptr<StreamingModel::RowData>(
112 new AlbumRowData(std::move(albums)));105 new AlbumRowData(store->listAlbums(limit_filter)));
113}106}
114107
=== modified file 'src/qml/Ubuntu/MediaScanner/ArtistsModel.cc'
--- src/qml/Ubuntu/MediaScanner/ArtistsModel.cc 2014-07-10 06:01:58 +0000
+++ src/qml/Ubuntu/MediaScanner/ArtistsModel.cc 2014-08-12 05:42:14 +0000
@@ -18,7 +18,6 @@
18 */18 */
1919
20#include "ArtistsModel.hh"20#include "ArtistsModel.hh"
21#include <exception>
22#include <QDebug>21#include <QDebug>
2322
24using namespace mediascanner::qml;23using namespace mediascanner::qml;
@@ -104,14 +103,10 @@
104 limit_filter.setLimit(limit);103 limit_filter.setLimit(limit);
105 limit_filter.setOffset(offset);104 limit_filter.setOffset(offset);
106 std::vector<std::string> artists;105 std::vector<std::string> artists;
107 try {106 if (album_artists) {
108 if (album_artists) {107 artists = store->listAlbumArtists(limit_filter);
109 artists = store->listAlbumArtists(limit_filter);108 } else {
110 } else {109 artists = store->listArtists(limit_filter);
111 artists = store->listArtists(limit_filter);
112 }
113 } catch (const std::exception &e) {
114 qWarning() << "Failed to retrieve artist list:" << e.what();
115 }110 }
116 return std::unique_ptr<StreamingModel::RowData>(111 return std::unique_ptr<StreamingModel::RowData>(
117 new ArtistRowData(std::move(artists)));112 new ArtistRowData(std::move(artists)));
118113
=== modified file 'src/qml/Ubuntu/MediaScanner/CMakeLists.txt'
--- src/qml/Ubuntu/MediaScanner/CMakeLists.txt 2014-07-10 03:02:27 +0000
+++ src/qml/Ubuntu/MediaScanner/CMakeLists.txt 2014-08-12 05:42:14 +0000
@@ -19,7 +19,7 @@
19)19)
2020
21set_target_properties(mediascanner-qml PROPERTIES AUTOMOC TRUE)21set_target_properties(mediascanner-qml PROPERTIES AUTOMOC TRUE)
22qt5_use_modules(mediascanner-qml Qml Concurrent)22qt5_use_modules(mediascanner-qml Qml Concurrent DBus)
23target_link_libraries(mediascanner-qml mediascanner ms-dbus)23target_link_libraries(mediascanner-qml mediascanner ms-dbus)
2424
25install(25install(
2626
=== modified file 'src/qml/Ubuntu/MediaScanner/GenresModel.cc'
--- src/qml/Ubuntu/MediaScanner/GenresModel.cc 2014-07-10 06:01:58 +0000
+++ src/qml/Ubuntu/MediaScanner/GenresModel.cc 2014-08-12 05:42:14 +0000
@@ -18,7 +18,6 @@
18 */18 */
1919
20#include "GenresModel.hh"20#include "GenresModel.hh"
21#include <exception>
22#include <QDebug>21#include <QDebug>
2322
24using namespace mediascanner::qml;23using namespace mediascanner::qml;
@@ -70,14 +69,8 @@
70 auto limit_filter = filter;69 auto limit_filter = filter;
71 limit_filter.setLimit(limit);70 limit_filter.setLimit(limit);
72 limit_filter.setOffset(offset);71 limit_filter.setOffset(offset);
73 std::vector<std::string> genres;
74 try {
75 genres = store->listGenres(limit_filter);
76 } catch (const std::exception &e) {
77 qWarning() << "Failed to retrieve genre list:" << e.what();
78 }
79 return std::unique_ptr<StreamingModel::RowData>(72 return std::unique_ptr<StreamingModel::RowData>(
80 new GenreRowData(std::move(genres)));73 new GenreRowData(store->listGenres(limit_filter)));
81}74}
8275
83void GenresModel::appendRows(std::unique_ptr<StreamingModel::RowData> &&row_data) {76void GenresModel::appendRows(std::unique_ptr<StreamingModel::RowData> &&row_data) {
8477
=== modified file 'src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.cc'
--- src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.cc 2014-06-24 13:43:25 +0000
+++ src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.cc 2014-08-12 05:42:14 +0000
@@ -21,6 +21,7 @@
21#include <cstdlib>21#include <cstdlib>
22#include <cstring>22#include <cstring>
23#include <exception>23#include <exception>
24#include <QDBusConnection>
24#include <QDebug>25#include <QDebug>
25#include <QQmlEngine>26#include <QQmlEngine>
2627
@@ -49,6 +50,13 @@
49 } else {50 } else {
50 store.reset(new mediascanner::MediaStore(MS_READ_ONLY));51 store.reset(new mediascanner::MediaStore(MS_READ_ONLY));
51 }52 }
53
54 QDBusConnection::sessionBus().connect(
55 "com.canonical.MediaScanner2.Daemon",
56 "/com/canonical/unity/scopes",
57 "com.canonical.unity.scopes", "InvalidateResults",
58 QStringList{"mediascanner-music"}, "s",
59 this, SLOT(resultsInvalidated()));
52}60}
5361
54QList<QObject*> MediaStoreWrapper::query(const QString &q, MediaType type) {62QList<QObject*> MediaStoreWrapper::query(const QString &q, MediaType type) {
@@ -75,3 +83,7 @@
75 QQmlEngine::setObjectOwnership(wrapper, QQmlEngine::JavaScriptOwnership);83 QQmlEngine::setObjectOwnership(wrapper, QQmlEngine::JavaScriptOwnership);
76 return wrapper;84 return wrapper;
77}85}
86
87void MediaStoreWrapper::resultsInvalidated() {
88 Q_EMIT updated();
89}
7890
=== modified file 'src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.hh'
--- src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.hh 2014-07-10 03:02:27 +0000
+++ src/qml/Ubuntu/MediaScanner/MediaStoreWrapper.hh 2014-08-12 05:42:14 +0000
@@ -49,6 +49,12 @@
49 Q_INVOKABLE mediascanner::qml::MediaFileWrapper *lookup(const QString &filename);49 Q_INVOKABLE mediascanner::qml::MediaFileWrapper *lookup(const QString &filename);
5050
51 std::shared_ptr<mediascanner::MediaStoreBase> store;51 std::shared_ptr<mediascanner::MediaStoreBase> store;
52
53Q_SIGNALS:
54 void updated();
55
56private Q_SLOTS:
57 void resultsInvalidated();
52};58};
5359
54}60}
5561
=== modified file 'src/qml/Ubuntu/MediaScanner/SongsModel.cc'
--- src/qml/Ubuntu/MediaScanner/SongsModel.cc 2014-07-10 06:44:15 +0000
+++ src/qml/Ubuntu/MediaScanner/SongsModel.cc 2014-08-12 05:42:14 +0000
@@ -18,7 +18,6 @@
18 */18 */
1919
20#include "SongsModel.hh"20#include "SongsModel.hh"
21#include <exception>
22#include <QDebug>21#include <QDebug>
2322
24using namespace mediascanner::qml;23using namespace mediascanner::qml;
@@ -123,12 +122,6 @@
123 auto limit_filter = filter;122 auto limit_filter = filter;
124 limit_filter.setLimit(limit);123 limit_filter.setLimit(limit);
125 limit_filter.setOffset(offset);124 limit_filter.setOffset(offset);
126 std::vector<mediascanner::MediaFile> songs;
127 try {
128 songs = store->listSongs(limit_filter);
129 } catch (const std::exception &e) {
130 qWarning() << "Failed to retrieve song list:" << e.what();
131 }
132 return std::unique_ptr<StreamingModel::RowData>(125 return std::unique_ptr<StreamingModel::RowData>(
133 new MediaFileRowData(std::move(songs)));126 new MediaFileRowData(store->listSongs(limit_filter)));
134}127}
135128
=== modified file 'src/qml/Ubuntu/MediaScanner/SongsSearchModel.cc'
--- src/qml/Ubuntu/MediaScanner/SongsSearchModel.cc 2014-07-10 06:44:15 +0000
+++ src/qml/Ubuntu/MediaScanner/SongsSearchModel.cc 2014-08-12 05:42:14 +0000
@@ -18,7 +18,6 @@
18 */18 */
1919
20#include "SongsSearchModel.hh"20#include "SongsSearchModel.hh"
21#include <exception>
22#include <QDebug>21#include <QDebug>
2322
24using namespace mediascanner::qml;23using namespace mediascanner::qml;
@@ -42,11 +41,7 @@
42 std::vector<mediascanner::MediaFile> songs;41 std::vector<mediascanner::MediaFile> songs;
43 // No batching support, so only send results for the zero offset.42 // No batching support, so only send results for the zero offset.
44 if (offset == 0) {43 if (offset == 0) {
45 try {44 songs = store->query(query.toStdString(), mediascanner::AudioMedia);
46 songs = store->query(query.toStdString(), mediascanner::AudioMedia);
47 } catch (const std::exception &e) {
48 qWarning() << "Failed to retrieve song search results:" << e.what();
49 }
50 }45 }
51 return std::unique_ptr<StreamingModel::RowData>(46 return std::unique_ptr<StreamingModel::RowData>(
52 new MediaFileRowData(std::move(songs)));47 new MediaFileRowData(std::move(songs)));
5348
=== modified file 'src/qml/Ubuntu/MediaScanner/StreamingModel.cc'
--- src/qml/Ubuntu/MediaScanner/StreamingModel.cc 2014-08-05 10:47:07 +0000
+++ src/qml/Ubuntu/MediaScanner/StreamingModel.cc 2014-08-12 05:42:14 +0000
@@ -20,6 +20,7 @@
20#include "StreamingModel.hh"20#include "StreamingModel.hh"
2121
22#include <cassert>22#include <cassert>
23#include <exception>
2324
24#include <QEvent>25#include <QEvent>
25#include <QCoreApplication>26#include <QCoreApplication>
@@ -35,14 +36,22 @@
35class AdditionEvent : public QEvent {36class AdditionEvent : public QEvent {
36private:37private:
37 std::unique_ptr<StreamingModel::RowData> rows;38 std::unique_ptr<StreamingModel::RowData> rows;
39 bool error = false;
38 int generation;40 int generation;
3941
40public:42public:
41 AdditionEvent(std::unique_ptr<StreamingModel::RowData> &&rows, int generation) :43 AdditionEvent(int generation) :
42 QEvent(AdditionEvent::additionEventType()), rows(std::move(rows)), generation(generation) {44 QEvent(AdditionEvent::additionEventType()), generation(generation) {
43 }45 }
4446
47 void setRows(std::unique_ptr<StreamingModel::RowData> &&r) {
48 rows = std::move(r);
49 }
50 void setError(bool e) {
51 error = e;
52 }
45 std::unique_ptr<StreamingModel::RowData>& getRows() { return rows; }53 std::unique_ptr<StreamingModel::RowData>& getRows() { return rows; }
54 bool getError() const { return error; }
46 int getGeneration() const { return generation; }55 int getGeneration() const { return generation; }
4756
48 static QEvent::Type additionEventType()57 static QEvent::Type additionEventType()
@@ -62,8 +71,14 @@
62 if(model->shouldWorkerStop()) {71 if(model->shouldWorkerStop()) {
63 return;72 return;
64 }73 }
65 QScopedPointer<AdditionEvent> e(74 QScopedPointer<AdditionEvent> e(new AdditionEvent(generation));
66 new AdditionEvent(model->retrieveRows(store, BATCH_SIZE, offset), generation));75 try {
76 e->setRows(model->retrieveRows(store, BATCH_SIZE, offset));
77 } catch (const std::exception &exc) {
78 qWarning() << "Failed to retrieve rows:" << exc.what();
79 e->setError(true);
80 return;
81 }
67 cursize = e->getRows()->size();82 cursize = e->getRows()->size();
68 if (model->shouldWorkerStop()) {83 if (model->shouldWorkerStop()) {
69 return;84 return;
@@ -75,7 +90,8 @@
7590
76}91}
7792
78StreamingModel::StreamingModel(QObject *parent) : QAbstractListModel(parent), generation(0) {93StreamingModel::StreamingModel(QObject *parent) :
94 QAbstractListModel(parent), generation(0), status(Ready) {
79}95}
8096
81StreamingModel::~StreamingModel() {97StreamingModel::~StreamingModel() {
@@ -90,9 +106,10 @@
90void StreamingModel::updateModel() {106void StreamingModel::updateModel() {
91 if (store.isNull()) {107 if (store.isNull()) {
92 query_future = QFuture<void>();108 query_future = QFuture<void>();
93 Q_EMIT filled();109 setStatus(Ready);
94 return;110 return;
95 }111 }
112 setStatus(Loading);
96 setWorkerStop(false);113 setWorkerStop(false);
97 query_future = QtConcurrent::run(runQuery, ++generation, this, store->store);114 query_future = QtConcurrent::run(runQuery, ++generation, this, store->store);
98}115}
@@ -114,35 +131,57 @@
114 return true;131 return true;
115 }132 }
116133
134 if (ae->getError()) {
135 setStatus(Error);
136 return true;
137 }
138
117 auto &newrows = ae->getRows();139 auto &newrows = ae->getRows();
118 bool lastBatch = newrows->size() < BATCH_SIZE;140 bool lastBatch = newrows->size() < BATCH_SIZE;
119 beginInsertRows(QModelIndex(), rowCount(), rowCount()+newrows->size()-1);141 beginInsertRows(QModelIndex(), rowCount(), rowCount()+newrows->size()-1);
120 appendRows(std::move(newrows));142 appendRows(std::move(newrows));
121 endInsertRows();143 endInsertRows();
122 Q_EMIT rowCountChanged();144 Q_EMIT countChanged();
123 if (lastBatch) {145 if (lastBatch) {
124 Q_EMIT filled();146 setStatus(Ready);
125 }147 }
126 return true;148 return true;
127}149}
128150
129MediaStoreWrapper *StreamingModel::getStore() {151MediaStoreWrapper *StreamingModel::getStore() const {
130 return store.data();152 return store.data();
131}153}
132154
133void StreamingModel::setStore(MediaStoreWrapper *store) {155void StreamingModel::setStore(MediaStoreWrapper *store) {
134 if (this->store != store) {156 if (this->store != store) {
157 if (this->store) {
158 disconnect(this->store, &MediaStoreWrapper::updated,
159 this, &StreamingModel::invalidate);
160 }
135 this->store = store;161 this->store = store;
162 if (store) {
163 connect(this->store, &MediaStoreWrapper::updated,
164 this, &StreamingModel::invalidate);
165 }
136 invalidate();166 invalidate();
137 }167 }
138}168}
139169
170StreamingModel::ModelStatus StreamingModel::getStatus() const {
171 return status;
172}
173
174void StreamingModel::setStatus(StreamingModel::ModelStatus status) {
175 this->status = status;
176 Q_EMIT statusChanged();
177}
178
140void StreamingModel::invalidate() {179void StreamingModel::invalidate() {
141 setWorkerStop(true);180 setWorkerStop(true);
142 query_future.waitForFinished();181 query_future.waitForFinished();
143 beginResetModel();182 beginResetModel();
144 clearBacking();183 clearBacking();
145 endResetModel();184 endResetModel();
146 Q_EMIT rowCountChanged();185 Q_EMIT countChanged();
147 updateModel();186 updateModel();
148}187}
149188
=== modified file 'src/qml/Ubuntu/MediaScanner/StreamingModel.hh'
--- src/qml/Ubuntu/MediaScanner/StreamingModel.hh 2014-07-17 10:17:23 +0000
+++ src/qml/Ubuntu/MediaScanner/StreamingModel.hh 2014-08-12 05:42:14 +0000
@@ -34,9 +34,18 @@
3434
35class StreamingModel : public QAbstractListModel {35class StreamingModel : public QAbstractListModel {
36 Q_OBJECT36 Q_OBJECT
37 Q_ENUMS(ModelStatus)
37 Q_PROPERTY(mediascanner::qml::MediaStoreWrapper* store READ getStore WRITE setStore)38 Q_PROPERTY(mediascanner::qml::MediaStoreWrapper* store READ getStore WRITE setStore)
38 Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)39 Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
40 Q_PROPERTY(int rowCount READ rowCount NOTIFY countChanged)
41 Q_PROPERTY(ModelStatus status READ getStatus NOTIFY statusChanged)
39public:42public:
43 enum ModelStatus {
44 Ready,
45 Loading,
46 Error
47 };
48
40 explicit StreamingModel(QObject *parent=nullptr);49 explicit StreamingModel(QObject *parent=nullptr);
41 virtual ~StreamingModel();50 virtual ~StreamingModel();
4251
@@ -56,9 +65,12 @@
56 virtual void clearBacking() = 0;65 virtual void clearBacking() = 0;
5766
58protected:67protected:
59 MediaStoreWrapper *getStore();68 MediaStoreWrapper *getStore() const;
60 void setStore(MediaStoreWrapper *store);69 void setStore(MediaStoreWrapper *store);
6170
71 ModelStatus getStatus() const;
72 void setStatus(ModelStatus status);
73
62private:74private:
63 void updateModel();75 void updateModel();
64 void setWorkerStop(bool new_stop_status) noexcept { stopflag.store(new_stop_status, std::memory_order_release); }76 void setWorkerStop(bool new_stop_status) noexcept { stopflag.store(new_stop_status, std::memory_order_release); }
@@ -67,10 +79,11 @@
67 QFuture<void> query_future;79 QFuture<void> query_future;
68 int generation;80 int generation;
69 std::atomic<bool> stopflag;81 std::atomic<bool> stopflag;
82 ModelStatus status;
7083
71Q_SIGNALS:84Q_SIGNALS:
72 void rowCountChanged();85 void countChanged();
73 void filled();86 void statusChanged();
7487
75public Q_SLOTS:88public Q_SLOTS:
7689
7790
=== modified file 'src/qml/Ubuntu/MediaScanner/plugin.qmltypes'
--- src/qml/Ubuntu/MediaScanner/plugin.qmltypes 2014-07-10 06:46:09 +0000
+++ src/qml/Ubuntu/MediaScanner/plugin.qmltypes 2014-08-12 05:42:14 +0000
@@ -124,6 +124,7 @@
124 "AllMedia": 255124 "AllMedia": 255
125 }125 }
126 }126 }
127 Signal { name: "updated" }
127 Method {128 Method {
128 name: "query"129 name: "query"
129 type: "QList<QObject*>"130 type: "QList<QObject*>"
@@ -157,9 +158,18 @@
157 Component {158 Component {
158 name: "mediascanner::qml::StreamingModel"159 name: "mediascanner::qml::StreamingModel"
159 prototype: "QAbstractListModel"160 prototype: "QAbstractListModel"
161 Enum {
162 name: "ModelStatus"
163 values: {
164 "Ready": 0,
165 "Loading": 1,
166 "Error": 2
167 }
168 }
160 Property { name: "store"; type: "mediascanner::qml::MediaStoreWrapper"; isPointer: true }169 Property { name: "store"; type: "mediascanner::qml::MediaStoreWrapper"; isPointer: true }
170 Property { name: "count"; type: "int"; isReadonly: true }
161 Property { name: "rowCount"; type: "int"; isReadonly: true }171 Property { name: "rowCount"; type: "int"; isReadonly: true }
162 Signal { name: "filled" }172 Property { name: "status"; type: "ModelStatus"; isReadonly: true }
163 Method { name: "invalidate" }173 Method { name: "invalidate" }
164 Method {174 Method {
165 name: "get"175 name: "get"
166176
=== modified file 'test/basic.cc'
--- test/basic.cc 2014-05-05 09:19:17 +0000
+++ test/basic.cc 2014-08-12 05:42:14 +0000
@@ -99,7 +99,6 @@
99 MediaStore store(":memory:", MS_READ_WRITE);99 MediaStore store(":memory:", MS_READ_WRITE);
100 MetadataExtractor extractor;100 MetadataExtractor extractor;
101 InvalidationSender invalidator;101 InvalidationSender invalidator;
102 invalidator.disable();
103 SubtreeWatcher watcher(store, extractor, invalidator);102 SubtreeWatcher watcher(store, extractor, invalidator);
104 watcher.addDir(subdir);103 watcher.addDir(subdir);
105 ASSERT_EQ(store.size(), 0);104 ASSERT_EQ(store.size(), 0);
@@ -122,7 +121,6 @@
122 MediaStore store(":memory:", MS_READ_WRITE);121 MediaStore store(":memory:", MS_READ_WRITE);
123 MetadataExtractor extractor;122 MetadataExtractor extractor;
124 InvalidationSender invalidator;123 InvalidationSender invalidator;
125 invalidator.disable();
126 SubtreeWatcher watcher(store, extractor, invalidator);124 SubtreeWatcher watcher(store, extractor, invalidator);
127 ASSERT_EQ(watcher.directoryCount(), 0);125 ASSERT_EQ(watcher.directoryCount(), 0);
128 watcher.addDir(testdir);126 watcher.addDir(testdir);
129127
=== modified file 'test/qml/tst_albumsmodel.qml'
--- test/qml/tst_albumsmodel.qml 2014-07-10 06:44:15 +0000
+++ test/qml/tst_albumsmodel.qml 2014-08-12 05:42:14 +0000
@@ -15,27 +15,26 @@
15 }15 }
1616
17 SignalSpy {17 SignalSpy {
18 id: modelFilled18 id: modelStatus
19 target: model19 target: model
20 signalName: "filled"20 signalName: "statusChanged"
21 }21 }
2222
23 TestCase {23 TestCase {
24 name: "AlbumsModelTests"24 name: "AlbumsModelTests"
2525
26 function waitForReady() {
27 while (model.status == AlbumsModel.Loading) {
28 modelStatus.wait();
29 }
30 compare(model.status, AlbumsModel.Ready);
31 }
32
26 function cleanup() {33 function cleanup() {
27 if (model.artist != undefined) {34 model.artist = undefined;
28 model.artist = undefined;35 model.albumArtist = undefined;
29 modelFilled.wait();36 model.genre = undefined;
30 }37 waitForReady();
31 if (model.albumArtist != undefined) {
32 model.albumArtist = undefined;
33 modelFilled.wait();
34 }
35 if (model.genre != undefined) {
36 model.genre = undefined;
37 modelFilled.wait();
38 }
39 }38 }
4039
41 function test_initial_state() {40 function test_initial_state() {
@@ -43,7 +42,7 @@
43 compare(model.albumArtist, undefined);42 compare(model.albumArtist, undefined);
44 compare(model.genre, undefined);43 compare(model.genre, undefined);
4544
46 compare(model.rowCount, 4);45 compare(model.count, 4);
47 compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples");46 compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples");
48 compare(model.get(0, AlbumsModel.RoleArtist), "Spiderbait");47 compare(model.get(0, AlbumsModel.RoleArtist), "Spiderbait");
4948
@@ -67,40 +66,40 @@
6766
68 function test_artist() {67 function test_artist() {
69 model.artist = "The John Butler Trio";68 model.artist = "The John Butler Trio";
70 modelFilled.wait();69 waitForReady();
71 compare(model.rowCount, 2);70 compare(model.count, 2);
7271
73 compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising");72 compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising");
74 compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio");73 compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio");
7574
76 model.artist = "unknown";75 model.artist = "unknown";
77 modelFilled.wait();76 waitForReady();
78 compare(model.rowCount, 0);77 compare(model.count, 0);
79 }78 }
8079
81 function test_album_artist() {80 function test_album_artist() {
82 model.albumArtist = "The John Butler Trio";81 model.albumArtist = "The John Butler Trio";
83 modelFilled.wait();82 waitForReady();
84 compare(model.rowCount, 2);83 compare(model.count, 2);
8584
86 compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising");85 compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising");
87 compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio");86 compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio");
8887
89 model.albumArtist = "unknown";88 model.albumArtist = "unknown";
90 modelFilled.wait();89 waitForReady();
91 compare(model.rowCount, 0);90 compare(model.count, 0);
92 }91 }
9392
94 function test_genre() {93 function test_genre() {
95 model.genre = "rock";94 model.genre = "rock";
96 modelFilled.wait();95 waitForReady();
97 compare(model.rowCount, 2);96 compare(model.count, 2);
98 compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples");97 compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples");
99 compare(model.get(1, AlbumsModel.RoleTitle), "Spiderbait");98 compare(model.get(1, AlbumsModel.RoleTitle), "Spiderbait");
10099
101 model.genre = "unknown";100 model.genre = "unknown";
102 modelFilled.wait();101 waitForReady();
103 compare(model.rowCount, 0);102 compare(model.count, 0);
104 }103 }
105104
106 }105 }
107106
=== modified file 'test/qml/tst_artistsmodel.qml'
--- test/qml/tst_artistsmodel.qml 2014-07-10 06:01:58 +0000
+++ test/qml/tst_artistsmodel.qml 2014-08-12 05:42:14 +0000
@@ -15,31 +15,33 @@
15 }15 }
1616
17 SignalSpy {17 SignalSpy {
18 id: modelFilled18 id: modelStatus
19 target: model19 target: model
20 signalName: "filled"20 signalName: "statusChanged"
21 }21 }
2222
23 TestCase {23 TestCase {
24 name: "ArtistsModelTests"24 name: "ArtistsModelTests"
2525
26 function waitForReady() {
27 while (model.status == ArtistsModel.Loading) {
28 modelStatus.wait();
29 }
30 compare(model.status, ArtistsModel.Ready);
31 }
32
26 function cleanup() {33 function cleanup() {
27 if (model.albumArtists != false) {34 model.albumArtists = false;
28 model.albumArtists = false;35 model.genre = undefined;
29 modelFilled.wait();36 waitForReady();
30 }
31 if (model.genre != undefined) {
32 model.genre = undefined;
33 modelFilled.wait();
34 }
35 }37 }
3638
37 function test_initial_state() {39 function test_initial_state() {
38 compare(model.albumArtists, false);40 compare(model.albumArtists, false);
39 compare(model.genre, undefined);41 compare(model.genre, undefined);
4042
41 //modelFilled.wait();43 waitForReady();
42 compare(model.rowCount, 2);44 compare(model.count, 2);
43 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");45 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");
44 compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio");46 compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio");
45 }47 }
@@ -54,8 +56,8 @@
5456
55 function test_album_artists() {57 function test_album_artists() {
56 model.albumArtists = true;58 model.albumArtists = true;
57 modelFilled.wait();59 waitForReady();
58 compare(model.rowCount, 2);60 compare(model.count, 2);
5961
60 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");62 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");
61 compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio");63 compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio");
@@ -63,18 +65,18 @@
6365
64 function test_genre() {66 function test_genre() {
65 model.genre = "rock";67 model.genre = "rock";
66 modelFilled.wait();68 waitForReady();
67 compare(model.rowCount, 1);69 compare(model.count, 1);
68 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");70 compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait");
6971
70 model.genre = "roots";72 model.genre = "roots";
71 modelFilled.wait();73 waitForReady();
72 compare(model.rowCount, 1);74 compare(model.count, 1);
73 compare(model.get(0, ArtistsModel.RoleArtist), "The John Butler Trio");75 compare(model.get(0, ArtistsModel.RoleArtist), "The John Butler Trio");
7476
75 model.genre = "unknown";77 model.genre = "unknown";
76 modelFilled.wait();78 waitForReady();
77 compare(model.rowCount, 0);79 compare(model.count, 0);
78 }80 }
7981
80 }82 }
8183
=== modified file 'test/qml/tst_genresmodel.qml'
--- test/qml/tst_genresmodel.qml 2014-07-10 06:01:58 +0000
+++ test/qml/tst_genresmodel.qml 2014-08-12 05:42:14 +0000
@@ -15,20 +15,27 @@
15 }15 }
1616
17 SignalSpy {17 SignalSpy {
18 id: modelFilled18 id: modelStatus
19 target: model19 target: model
20 signalName: "filled"20 signalName: "statusChanged"
21 }21 }
2222
23 TestCase {23 TestCase {
24 name: "GenresModelTests"24 name: "GenresModelTests"
2525
26 function waitForReady() {
27 while (model.status == GenresModel.Loading) {
28 modelStatus.wait();
29 }
30 compare(model.status, GenresModel.Ready);
31 }
32
26 function cleanup() {33 function cleanup() {
27 }34 }
2835
29 function test_initial_state() {36 function test_initial_state() {
30 modelFilled.wait();37 waitForReady();
31 compare(model.rowCount, 2);38 compare(model.count, 2);
32 compare(model.get(0, ArtistsModel.RoleGenre), "rock");39 compare(model.get(0, ArtistsModel.RoleGenre), "rock");
33 compare(model.get(1, ArtistsModel.RoleGenre), "roots");40 compare(model.get(1, ArtistsModel.RoleGenre), "roots");
34 }41 }
3542
=== modified file 'test/qml/tst_songsearchmodel.qml'
--- test/qml/tst_songsearchmodel.qml 2014-07-10 07:19:57 +0000
+++ test/qml/tst_songsearchmodel.qml 2014-08-12 05:42:14 +0000
@@ -15,21 +15,29 @@
15 }15 }
1616
17 SignalSpy {17 SignalSpy {
18 id: modelFilled18 id: modelStatus
19 target: model19 target: model
20 signalName: "filled"20 signalName: "statusChanged"
21 }21 }
2222
23 TestCase {23 TestCase {
24 name: "SongsSearchModelTests"24 name: "SongsSearchModelTests"
25
26 function waitForReady() {
27 while (model.status == SongsSearchModel.Loading) {
28 modelStatus.wait();
29 }
30 compare(model.status, SongsSearchModel.Ready);
31 }
32
25 function test_search() {33 function test_search() {
26 // By default, the model lists all songs.34 // By default, the model lists all songs.
27 modelFilled.wait();35 waitForReady();
28 compare(model.rowCount, 7, "songs_model.rowCount == 7");36 compare(model.count, 7, "songs_model.count == 7");
2937
30 model.query = "revolution";38 model.query = "revolution";
31 modelFilled.wait();39 waitForReady();
32 compare(model.rowCount, 1, "songs_model.rowCount == 1");40 compare(model.count, 1, "songs_model.count == 1");
33 compare(model.get(0, SongsSearchModel.RoleTitle), "Revolution");41 compare(model.get(0, SongsSearchModel.RoleTitle), "Revolution");
34 }42 }
35 }43 }
3644
=== modified file 'test/qml/tst_songsmodel.qml'
--- test/qml/tst_songsmodel.qml 2014-07-10 06:44:15 +0000
+++ test/qml/tst_songsmodel.qml 2014-08-12 05:42:14 +0000
@@ -15,31 +15,27 @@
15 }15 }
1616
17 SignalSpy {17 SignalSpy {
18 id: modelFilled18 id: modelStatus
19 target: model19 target: model
20 signalName: "filled"20 signalName: "statusChanged"
21 }21 }
2222
23 TestCase {23 TestCase {
24 name: "SongsModelTests"24 name: "SongsModelTests"
2525
26 function waitForReady() {
27 while (model.status == SongsModel.Loading) {
28 modelStatus.wait();
29 }
30 compare(model.status, SongsModel.Ready);
31 }
32
26 function cleanup() {33 function cleanup() {
27 if (model.artist != undefined) {34 model.artist = undefined;
28 model.artist = undefined;35 model.albumArtist = undefined;
29 modelFilled.wait();36 model.album = undefined;
30 }37 model.genre = undefined;
31 if (model.albumArtist != undefined) {38 waitForReady();
32 model.albumArtist = undefined;
33 modelFilled.wait();
34 }
35 if (model.album != undefined) {
36 model.album = undefined;
37 modelFilled.wait();
38 }
39 if (model.genre != undefined) {
40 model.genre = undefined;
41 modelFilled.wait();
42 }
43 }39 }
4440
45 function test_initial_state() {41 function test_initial_state() {
@@ -47,7 +43,7 @@
47 compare(model.albumArtist, undefined);43 compare(model.albumArtist, undefined);
48 compare(model.album, undefined);44 compare(model.album, undefined);
4945
50 compare(model.rowCount, 7);46 compare(model.count, 7);
51 compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony")47 compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony")
52 compare(model.get(0, SongsModel.RoleAlbum), "Ivy and the Big Apples");48 compare(model.get(0, SongsModel.RoleAlbum), "Ivy and the Big Apples");
53 compare(model.get(0, SongsModel.RoleAuthor), "Spiderbait");49 compare(model.get(0, SongsModel.RoleAuthor), "Spiderbait");
@@ -70,55 +66,55 @@
7066
71 function test_artist() {67 function test_artist() {
72 model.artist = "The John Butler Trio";68 model.artist = "The John Butler Trio";
73 modelFilled.wait();69 waitForReady();
74 compare(model.rowCount, 4);70 compare(model.count, 4);
7571
76 compare(model.get(0, SongsModel.RoleTitle), "Revolution");72 compare(model.get(0, SongsModel.RoleTitle), "Revolution");
77 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");73 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");
7874
79 model.artist = "unknown";75 model.artist = "unknown";
80 modelFilled.wait();76 waitForReady();
81 compare(model.rowCount, 0);77 compare(model.count, 0);
82 }78 }
8379
84 function test_album_artist() {80 function test_album_artist() {
85 model.albumArtist = "The John Butler Trio";81 model.albumArtist = "The John Butler Trio";
86 modelFilled.wait();82 waitForReady();
87 compare(model.rowCount, 4);83 compare(model.count, 4);
8884
89 compare(model.get(0, SongsModel.RoleTitle), "Revolution");85 compare(model.get(0, SongsModel.RoleTitle), "Revolution");
90 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");86 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");
9187
92 model.albumArtist = "unknown";88 model.albumArtist = "unknown";
93 modelFilled.wait();89 waitForReady();
94 compare(model.rowCount, 0);90 compare(model.count, 0);
95 }91 }
9692
97 function test_album() {93 function test_album() {
98 model.album = "Sunrise Over Sea";94 model.album = "Sunrise Over Sea";
99 modelFilled.wait();95 waitForReady();
100 compare(model.rowCount, 2);96 compare(model.count, 2);
10197
102 compare(model.get(0, SongsModel.RoleTitle), "Peaches & Cream");98 compare(model.get(0, SongsModel.RoleTitle), "Peaches & Cream");
103 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");99 compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio");
104100
105 model.albumArtist = "unknown";101 model.albumArtist = "unknown";
106 modelFilled.wait();102 waitForReady();
107 compare(model.rowCount, 0);103 compare(model.count, 0);
108 }104 }
109105
110 function test_genre() {106 function test_genre() {
111 model.genre = "rock";107 model.genre = "rock";
112 modelFilled.wait();108 waitForReady();
113 compare(model.rowCount, 3);109 compare(model.count, 3);
114110
115 compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony");111 compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony");
116 compare(model.get(1, SongsModel.RoleTitle), "Straight Through The Sun");112 compare(model.get(1, SongsModel.RoleTitle), "Straight Through The Sun");
117 compare(model.get(2, SongsModel.RoleTitle), "It's Beautiful");113 compare(model.get(2, SongsModel.RoleTitle), "It's Beautiful");
118114
119 model.albumArtist = "unknown";115 model.albumArtist = "unknown";
120 modelFilled.wait();116 waitForReady();
121 compare(model.rowCount, 0);117 compare(model.count, 0);
122 }118 }
123119
124 }120 }

Subscribers

People subscribed via source and target branches