Merge lp:~mixxxdevelopers/mixxx/iTunes2 into lp:~mixxxdevelopers/mixxx/trunk

Proposed by RAFFI TEA
Status: Merged
Merged at revision: 2615
Proposed branch: lp:~mixxxdevelopers/mixxx/iTunes2
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 1687 lines (+776/-696)
9 files modified
mixxx/res/schema.xml (+27/-0)
mixxx/src/library/itunesfeature.cpp (+398/-33)
mixxx/src/library/itunesfeature.h (+18/-6)
mixxx/src/library/itunesplaylistmodel.cpp (+162/-233)
mixxx/src/library/itunesplaylistmodel.h (+29/-53)
mixxx/src/library/itunestrackmodel.cpp (+104/-302)
mixxx/src/library/itunestrackmodel.h (+36/-67)
mixxx/src/library/library.cpp (+1/-1)
mixxx/src/library/trackcollection.cpp (+1/-1)
To merge this branch: bzr merge lp:~mixxxdevelopers/mixxx/iTunes2
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
Review via email: mp+41225@code.launchpad.net

Description of the change

NOTE: SINCE TRUNK HAS CHANGED, I HAVE CREATED THIS BRANCH WHICH RESOLVES MERGING CONFLICTS.

This is are rewrite of the itunes feature.
It is now sql-based. A Sax parser is used to put all tracks
into database tables.

The branch has been tested with itunes music collections from IRC user theresajayne and me.
A collection of over 70000 files will now consume no main memory anymore. Before it took ~2GB.

PLEASE REVIEW....

To post a comment you must log in.
Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Since features_library has merged to trunk revision 7 is live and should not be modified. In my local copy I added a revision 8 that only creates the itunes table.

Also, the indentation in a lot of these files seems to be 2-space indent, which doesn't match the rest of the files' 4-space indent.

After loading an iTunes XML sent to me by Tobias a long time ago, Mixxx hard-locked up after I clicked iTunes and said 'Ok' to the question of whether or not to load my library. Not sure what the problem is, looking at it now.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Turns out the while(1) loops were infinite looping. I changed them to while (!xml.atEnd()) and that seemed to fix it. For what it's worth, my XML file causes a "Premature end of document." error. There isn't any UI feedback when that happens, which would be nice.

I'll push my edits to this branch. (that updates the merge request automatically)

lp:~mixxxdevelopers/mixxx/iTunes2 updated
2563. By RJ Skerry-Ryan

Adding updates. Split itunes SQL revision into revision 8 from 7 since 7 is now permanent. Add safety from infinite loops in parsing. Fix indentation to be 4-space indents.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

OK I fixed what I mentioned and added a GUI warning when the file fails to parse. It looks good to me.

review: Approve
lp:~mixxxdevelopers/mixxx/iTunes2 updated
2564. By RJ Skerry-Ryan

Adding a warning message when the iTunes library fails to load. Make failed parses of iTunes still select in the track model because it might have gotten partially through the file and showing some is better than showing none.

2565. By RJ Skerry-Ryan

Switch iTunes trackmodel and playlits model to use custom header states instead of the mixxx playlist one.

2566. By RJ Skerry-Ryan

Remove Traktor references.

Revision history for this message
RAFFI TEA (raffitea) wrote :

Tanks RJ for your review.

lp:~mixxxdevelopers/mixxx/iTunes2 updated
2567. By RJ Skerry-Ryan

Oops, fix multi-line string.

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

I'll let you do the honors -- go ahead and merge it into trunk :)

Revision history for this message
RAFFI TEA (raffitea) wrote :

Before I merge I have another question:

Both itunestrackmodel and itunesplaylistmodel are subclassed from BaseSqlTableModel. In BaseSqlTableModel we handle 'trackChanged' signals. Now assume we've an iTunes track that has incorrect playtime information. If BaseSqlTableModel captures 'trackChanged' signals for iTunes playlistsmodels I'm sure we run into problems? Will this ever happen?

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Hm, this is a good point. They shouldn't be inherited from BaseSQLTableModel
because BaseSqlTableModel assumes the track is in the Mixxx library. Maybe
we can split the Mixxx db specific stuff into a MixxxSqlTableModel that
inherits from BaseSqlTableModel ? Ugh, that seems like an ugly solution.
What do you think, Tobias?

RJ

On Sun, Nov 21, 2010 at 3:23 PM, RAFFI TEA <email address hidden>wrote:

> Before I merge I have another question:
>
> Both itunestrackmodel and itunesplaylistmodel are subclassed from
> BaseSqlTableModel. In BaseSqlTableModel we handle 'trackChanged' signals.
> Now assume we've an iTunes track that has incorrect playtime information. If
> BaseSqlTableModel captures 'trackChanged' signals for iTunes playlistsmodels
> I'm sure we run into problems? Will this ever happen?
> --
> https://code.launchpad.net/~mixxxdevelopers/mixxx/iTunes2/+merge/41225
> You are reviewing the proposed merge of lp:~mixxxdevelopers/mixxx/iTunes2
> into lp:mixxx.
>

Revision history for this message
RAFFI TEA (raffitea) wrote :

I think there are two options. (1) We follow RJ's proposal. However, we get a deeper class hierarchy, which is really not my favor. (2) We refactor BaseSqlTableModel. In particular, we move the connect statement in a separate method (e.g., setTrackChangeAwareness(bool) ). If the parameter is true, we connect the signal. This is probably the quickest way to realize.

Both suggestions are not the 'cleanest'. But I am more towards solution (2) because there's no real difference between native and non-native library features unless the latter are read-only models and trackChanged signals are unintended. This brings me to another idea: (3) We could disconnect the signal in BaseSqlTableModel::readOnlyFlags(QModelIndex ind). Basically, all non-native Mixxx library features return read-only model flags, e.g., iTunes models return BaseSqlTableModel::readOnlyFlags() in their flags() method.

What do you think RJ or Albert?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'mixxx/res/schema.xml'
--- mixxx/res/schema.xml 2010-11-17 21:02:24 +0000
+++ mixxx/res/schema.xml 2010-11-21 15:57:13 +0000
@@ -172,4 +172,31 @@
172 DELETE FROM settings WHERE name="mixxx.db.model.missing.header_state";172 DELETE FROM settings WHERE name="mixxx.db.model.missing.header_state";
173 </sql>173 </sql>
174 </revision>174 </revision>
175 <revision version="8">
176 <description>
177 Added iTunes tables
178 </description>
179 <sql>
180 CREATE TABLE IF NOT EXISTS itunes_library (
181 id INTEGER primary key,
182 artist varchar(48), title varchar(48),
183 album varchar(48), year varchar(16),
184 genre varchar(32), tracknumber varchar(3),
185 location varchar(512),
186 comment varchar(60),
187 duration integer,
188 bitrate integer,
189 bpm integer,
190 rating integer);
191
192 CREATE TABLE IF NOT EXISTS itunes_playlists (
193 id INTEGER primary key,
194 name varchar(100) UNIQUE);
195
196 CREATE TABLE IF NOT EXISTS itunes_playlist_tracks (
197 id INTEGER primary key AUTOINCREMENT,
198 playlist_id INTEGER REFERENCES itunes_playlist(id),
199 track_id INTEGER REFERENCES itunes_library(id));
200 </sql>
201 </revision>
175</schema>202</schema>
176203
=== modified file 'mixxx/src/library/itunesfeature.cpp'
--- mixxx/src/library/itunesfeature.cpp 2010-08-01 12:55:15 +0000
+++ mixxx/src/library/itunesfeature.cpp 2010-11-21 15:57:13 +0000
@@ -1,27 +1,29 @@
1#include <QMessageBox>1#include <QMessageBox>
2#include <QtDebug>2#include <QtDebug>
33#include <QXmlStreamReader>
4#include <QDesktopServices>
4#include "library/itunesfeature.h"5#include "library/itunesfeature.h"
56
6#include "library/itunestrackmodel.h"7#include "library/itunestrackmodel.h"
7#include "library/itunesplaylistmodel.h"8#include "library/itunesplaylistmodel.h"
8#include "library/proxytrackmodel.h"9
910
10ITunesFeature::ITunesFeature(QObject* parent)11ITunesFeature::ITunesFeature(QObject* parent, TrackCollection* pTrackCollection)
11 : LibraryFeature(parent) {12 : LibraryFeature(parent),
12 //Don't actually initialize these until the iTunes item in the sidebar is clicked.13 m_pTrackCollection(pTrackCollection),
13 m_pITunesTrackModel = NULL;14 m_database(pTrackCollection->getDatabase()) {
14 m_pITunesPlaylistModel = NULL;15 m_pITunesTrackModel = new ITunesTrackModel(this, m_pTrackCollection);
15 m_pTrackModelProxy = NULL;16 m_pITunesPlaylistModel = new ITunesPlaylistModel(this, m_pTrackCollection);
16 m_pPlaylistModelProxy = NULL;17 m_isActivated = false;
17}18}
1819
19ITunesFeature::~ITunesFeature() {20ITunesFeature::~ITunesFeature() {
2021 delete m_pITunesTrackModel;
22 delete m_pITunesPlaylistModel;
21}23}
2224
23bool ITunesFeature::isSupported() {25bool ITunesFeature::isSupported() {
24 return (QFile::exists(ITunesTrackModel::getiTunesMusicPath()));26 return QFile::exists(getiTunesMusicPath());
25}27}
2628
2729
@@ -36,7 +38,7 @@
36void ITunesFeature::activate() {38void ITunesFeature::activate() {
37 //qDebug("ITunesFeature::activate()");39 //qDebug("ITunesFeature::activate()");
3840
39 if (!m_pITunesTrackModel) {41 if (!m_isActivated) {
40 if (QMessageBox::question(42 if (QMessageBox::question(
41 NULL,43 NULL,
42 tr("Load iTunes Library?"),44 tr("Load iTunes Library?"),
@@ -46,31 +48,24 @@
46 == QMessageBox::Cancel) {48 == QMessageBox::Cancel) {
47 return;49 return;
48 }50 }
49 m_pITunesTrackModel = new ITunesTrackModel();51
50 m_pITunesPlaylistModel = new ITunesPlaylistModel(m_pITunesTrackModel);52 if (importLibrary(getiTunesMusicPath())) {
5153 m_isActivated = true;
52 // Use a ProxyTrackModel for search/sorting of iTunes tracks54 } else {
53 m_pTrackModelProxy = new ProxyTrackModel(m_pITunesTrackModel);55 QMessageBox::warning(
54 m_pTrackModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);56 NULL,
55 m_pTrackModelProxy->setSortCaseSensitivity(Qt::CaseInsensitive);57 tr("Error Loading iTunes Library"),
5658 tr("There was an error loading your iTunes library. Some of "
57 // Use a ProxyTrackModel for search/sorting of iTunes playlists59 "your iTunes tracks or playlists may not have loaded."));
58 m_pPlaylistModelProxy = new ProxyTrackModel(m_pITunesPlaylistModel);
59 m_pPlaylistModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
60 m_pPlaylistModelProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
61
62 QStringList list;
63 for (int i = 0; i < m_pITunesPlaylistModel->numPlaylists(); ++i) {
64 list << m_pITunesPlaylistModel->playlistTitle(i);
65 }60 }
6661
67 //Sort the playlists since in iTunes they are sorted, too.62 //Sort the playlists since in iTunes they are sorted, too.
68 list.sort();63 //list.sort();
6964
70 m_childModel.setStringList(list);65 m_childModel.setStringList(m_playlists);
71 }66 }
7267
73 emit(showTrackModel(m_pTrackModelProxy));68 emit(showTrackModel(m_pITunesTrackModel));
74}69}
7570
76void ITunesFeature::activateChild(const QModelIndex& index) {71void ITunesFeature::activateChild(const QModelIndex& index) {
@@ -78,7 +73,7 @@
78 QString playlist = index.data().toString();73 QString playlist = index.data().toString();
79 qDebug() << "Activating " << playlist;74 qDebug() << "Activating " << playlist;
80 m_pITunesPlaylistModel->setPlaylist(playlist);75 m_pITunesPlaylistModel->setPlaylist(playlist);
81 emit(showTrackModel(m_pPlaylistModelProxy));76 emit(showTrackModel(m_pITunesPlaylistModel));
82}77}
8378
84QAbstractItemModel* ITunesFeature::getChildModel() {79QAbstractItemModel* ITunesFeature::getChildModel() {
@@ -106,3 +101,373 @@
106bool ITunesFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {101bool ITunesFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
107 return false;102 return false;
108}103}
104
105QString ITunesFeature::getiTunesMusicPath() {
106 QString musicFolder;
107#if defined(__APPLE__)
108 musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "/iTunes/iTunes Music Library.xml";
109#elif defined(__WINDOWS__)
110 musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "\\iTunes\\iTunes Music Library.xml";
111#elif defined(__LINUX__)
112 musicFolder = QDir::homePath() + "/iTunes Music Library.xml";
113#else
114 musicFolder = "";
115#endif
116 qDebug() << "ITunesLibrary=[" << musicFolder << "]";
117 return musicFolder;
118}
119bool ITunesFeature::importLibrary(QString file) {
120 //Delete all table entries of iTunes feature
121 m_database.transaction();
122 clearTable("itunes_playlist_tracks");
123 clearTable("itunes_library");
124 clearTable("itunes_playlists");
125 m_database.commit();
126
127 qDebug() << "Import iTunes library";
128
129 m_database.transaction();
130
131 //Parse iTunes XML file using SAX (for performance)
132 QFile itunes_file(file);
133 if (!itunes_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
134 qDebug() << "Cannot open iTunes music collection";
135 return false;
136 }
137 QXmlStreamReader xml(&itunes_file);
138
139 while (!xml.atEnd()) {
140 xml.readNext();
141 if (xml.isStartElement()) {
142 if (xml.name() == "key") {
143 if (xml.readElementText() == "Tracks") {
144 parseTracks(xml);
145 qDebug() << "Finished Parsing TrackList";
146 } else if (xml.readElementText() == "Playlists") {
147 //parse all the playlists here and exit the loop afterwards
148 parsePlaylists(xml);
149 qDebug() << "Finished Parsing Playlists";
150 }
151 }
152 }
153 }
154
155 itunes_file.close();
156
157 // Even if an error occured, commit the transaction. The file may have been
158 // half-parsed.
159 m_database.commit();
160 m_pITunesTrackModel->select();
161
162 if (xml.hasError()) {
163 // do error handling
164 qDebug() << "Cannot process iTunes music collection";
165 qDebug() << "XML ERROR: " << xml.errorString();
166 return false;
167 }
168 return true;
169}
170
171void ITunesFeature::parseTracks(QXmlStreamReader &xml) {
172 QSqlQuery query(m_database);
173 query.prepare("INSERT INTO itunes_library (id, artist, title, album, year, genre, comment, tracknumber,"
174 "bpm, bitrate,"
175 "duration, location,"
176 "rating ) "
177 "VALUES (:id, :artist, :title, :album, :year, :genre, :comment, :tracknumber,"
178 ":bpm, :bitrate,"
179 ":duration, :location," ":rating )");
180
181
182 bool in_container_dictionary = false;
183 bool in_track_dictionary = false;
184
185 qDebug() << "Parse Tracks";
186
187 //read all sunsequent <dict> until we reach the closing ENTRY tag
188 while (!xml.atEnd()) {
189 xml.readNext();
190
191 if (xml.isStartElement()) {
192 if (xml.name() == "dict") {
193 if (!in_track_dictionary && !in_container_dictionary) {
194 in_container_dictionary = true;
195 continue;
196 } else if (in_container_dictionary && !in_track_dictionary) {
197 //We are in a <dict> tag that holds track information
198 in_track_dictionary = true;
199 //Parse track here
200 parseTrack(xml, query);
201 }
202 }
203 }
204
205 if (xml.isEndElement() && xml.name() == "dict") {
206 if (in_track_dictionary && in_container_dictionary) {
207 in_track_dictionary = false;
208 continue;
209 } else if (in_container_dictionary && !in_track_dictionary) {
210 // Done parsing tracks.
211 in_container_dictionary = false;
212 break;
213 }
214 }
215 }
216}
217
218void ITunesFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
219 //qDebug() << "----------------TRACK-----------------";
220 int id = -1;
221 QString title;
222 QString artist;
223 QString album;
224 QString year;
225 QString genre;
226 QString location;
227
228 int bpm = 0;
229 int bitrate = 0;
230
231 //duration of a track
232 int playtime = 0;
233 int rating = 0;
234 QString comment;
235 QString tracknumber;
236
237 while (!xml.atEnd()) {
238 xml.readNext();
239
240 if (xml.isStartElement()) {
241 if (xml.name() == "key") {
242 QString key = xml.readElementText();
243 QString content = "";
244
245 if (xml.readNextStartElement()) {
246 content = xml.readElementText();
247 }
248
249 //qDebug() << "Key: " << key << " Content: " << content;
250
251 if (key == "Track ID") {
252 id = content.toInt();
253 continue;
254 }
255 if (key == "Name") {
256 title = content;
257 continue;
258 }
259 if (key == "Artist") {
260 artist = content;
261 continue;
262 }
263 if (key == "Album") {
264 album = content;
265 continue;
266 }
267 if (key == "Genre") {
268 genre = content;
269 continue;
270 }
271 if (key == "BPM") {
272 bpm = content.toInt();
273 continue;
274 }
275 if (key == "Bit Rate") {
276 bitrate = content.toInt();
277 continue;
278 }
279 if (key == "Comments") {
280 comment = content;
281 continue;
282 }
283 if (key == "Total Time") {
284 playtime = (content.toInt() / 1000);
285 continue;
286 }
287 if (key == "Year") {
288 year = content;
289 continue;
290 }
291 if (key == "Location") {
292 QByteArray strlocbytes = content.toUtf8();
293 location = QUrl::fromEncoded(strlocbytes).toLocalFile();
294 /*
295 * Strip the crappy file://localhost/ from the URL and
296 * format URL as in method ITunesTrackModel::parseTrackNode(QDomNode songNode)
297 */
298#if defined(__WINDOWS__)
299 location.remove("//localhost/");
300#else
301 location.remove("//localhost");
302#endif
303 continue;
304 }
305 if (key == "Track Number") {
306 tracknumber = content;
307 continue;
308 }
309 if (key == "Rating") {
310 //value is an integer and ranges from 0 to 100
311 rating = (content.toInt() / 20);
312 continue;
313 }
314 }
315 }
316 //exit loop on closing </dict>
317 if (xml.isEndElement() && xml.name() == "dict") {
318 break;
319 }
320 }
321 /* If we reach the end of <dict>
322 * Save parsed track to database
323 */
324 query.bindValue(":id", id);
325 query.bindValue(":artist", artist);
326 query.bindValue(":title", title);
327 query.bindValue(":album", album);
328 query.bindValue(":genre", genre);
329 query.bindValue(":year", year);
330 query.bindValue(":duration", playtime);
331 query.bindValue(":location", location);
332 query.bindValue(":rating", rating);
333 query.bindValue(":comment", comment);
334 query.bindValue(":tracknumber", tracknumber);
335 query.bindValue(":bpm", bpm);
336 query.bindValue(":bitrate", bitrate);
337
338 bool success = query.exec();
339
340 if (!success) {
341 qDebug() << "SQL Error in itunesfeature.cpp: line" << __LINE__ << " " << query.lastError();
342 return;
343 }
344}
345
346void ITunesFeature::parsePlaylists(QXmlStreamReader &xml) {
347 qDebug() << "Parse Playlists";
348
349 QSqlQuery query_insert_to_playlists(m_database);
350 query_insert_to_playlists.prepare("INSERT INTO itunes_playlists (id, name) "
351 "VALUES (:id, :name)");
352
353 QSqlQuery query_insert_to_playlist_tracks(m_database);
354 query_insert_to_playlist_tracks.prepare("INSERT INTO itunes_playlist_tracks (playlist_id, track_id) "
355 "VALUES (:playlist_id, :track_id)");
356
357 while (!xml.atEnd()) {
358 xml.readNext();
359 //We process and iterate the <dict> tags holding playlist summary information here
360 if (xml.isStartElement() && xml.name() == "dict") {
361 parsePlaylist(xml, query_insert_to_playlists, query_insert_to_playlist_tracks);
362 continue;
363 }
364 if (xml.isEndElement()) {
365 if (xml.name() == "array")
366 break;
367 }
368 }
369}
370
371void ITunesFeature::parsePlaylist(QXmlStreamReader &xml, QSqlQuery &query_insert_to_playlists,
372 QSqlQuery &query_insert_to_playlist_tracks) {
373 //qDebug() << "Parse Playlist";
374
375 QString playlistname;
376 int playlist_id = -1;
377 int track_reference = -1;
378 //indicates that we haven't found the <
379 bool isSystemPlaylist = false;
380
381 QString key;
382
383
384 //We process and iterate the <dict> tags holding playlist summary information here
385 while (!xml.atEnd()) {
386 xml.readNext();
387
388 if (xml.isStartElement()) {
389
390 if (xml.name() == "key") {
391 QString key = xml.readElementText();
392 /*
393 * The rules are processed in sequence
394 * That is, XML is ordered.
395 * For iTunes Playlist names are always followed by the ID.
396 * Afterwars the playlist entries occur
397 */
398 if (key == "Name") {
399 xml.readNextStartElement();
400 playlistname = xml.readElementText();
401 continue;
402 }
403 //When parsing the ID, the playlistname has already been found
404 if (key == "Playlist ID") {
405 xml.readNextStartElement();
406 playlist_id = xml.readElementText().toInt();
407 continue;
408 }
409 //Hide playlists that are system playlists
410 if (key == "Master" || key == "Movies" || key == "TV Shows" || key == "Music" ||
411 key == "Books" || key == "Purchased") {
412 isSystemPlaylist = true;
413 continue;
414 }
415
416 if (key == "Playlist Items") {
417 //if the playlist is prebuild don't hit the database
418 if (isSystemPlaylist) continue;
419 query_insert_to_playlists.bindValue(":id", playlist_id);
420 query_insert_to_playlists.bindValue(":name", playlistname);
421
422 bool success = query_insert_to_playlists.exec();
423 if (!success) {
424 qDebug() << "SQL Error in ITunesTableModel.cpp: line" << __LINE__
425 << " " << query_insert_to_playlists.lastError();
426 return;
427 }
428 //for the child model
429 m_playlists << playlistname;
430
431 }
432 /*
433 * When processing playlist entries, playlist name and id have already been processed and persisted
434 */
435 if (key == "Track ID") {
436 track_reference = -1;
437
438 xml.readNextStartElement();
439 track_reference = xml.readElementText().toInt();
440
441 query_insert_to_playlist_tracks.bindValue(":playlist_id", playlist_id);
442 query_insert_to_playlist_tracks.bindValue(":track_id", track_reference);
443
444 //Insert tracks if we are not in a pre-build playlist
445 if (!isSystemPlaylist && !query_insert_to_playlist_tracks.exec()) {
446 qDebug() << "SQL Error in ITunesFeature.cpp: line" << __LINE__ << " "
447 << query_insert_to_playlist_tracks.lastError();
448 qDebug() << "trackid" << track_reference;
449 qDebug() << "playlistname; " << playlistname;
450 qDebug() << "-----------------";
451 }
452 }
453 }
454 }
455 if (xml.isEndElement()) {
456 if (xml.name() == "array") {
457 //qDebug() << "exit playlist";
458 break;
459 }
460 }
461 }
462}
463
464void ITunesFeature::clearTable(QString table_name) {
465 QSqlQuery query(m_database);
466 query.prepare("delete from "+table_name);
467 bool success = query.exec();
468
469 if (!success)
470 qDebug() << "Could not delete remove old entries from table " << table_name << " : " << query.lastError();
471 else
472 qDebug() << "iTunes table entries of '" << table_name <<"' have been cleared.";
473}
109474
=== modified file 'mixxx/src/library/itunesfeature.h'
--- mixxx/src/library/itunesfeature.h 2009-12-23 03:07:11 +0000
+++ mixxx/src/library/itunesfeature.h 2010-11-21 15:57:13 +0000
@@ -5,18 +5,20 @@
5#define ITUNESBOXFEATURE_H5#define ITUNESBOXFEATURE_H
66
7#include <QStringListModel>7#include <QStringListModel>
8#include <QtSql>
89
9#include "library/libraryfeature.h"10#include "library/libraryfeature.h"
11#include "library/trackcollection.h"
1012
11//class ITunesPlaylistModel;13//class ITunesPlaylistModel;
12class ITunesTrackModel;14class ITunesTrackModel;
13class ITunesPlaylistModel;15class ITunesPlaylistModel;
14class ProxyTrackModel;16
1517
16class ITunesFeature : public LibraryFeature {18class ITunesFeature : public LibraryFeature {
17 Q_OBJECT19 Q_OBJECT
18 public:20 public:
19 ITunesFeature(QObject* parent = NULL);21 ITunesFeature(QObject* parent, TrackCollection* pTrackCollection);
20 virtual ~ITunesFeature();22 virtual ~ITunesFeature();
21 static bool isSupported();23 static bool isSupported();
2224
@@ -30,18 +32,28 @@
3032
31 QAbstractItemModel* getChildModel();33 QAbstractItemModel* getChildModel();
3234
33public slots:35 public slots:
34 void activate();36 void activate();
35 void activateChild(const QModelIndex& index);37 void activateChild(const QModelIndex& index);
36 void onRightClick(const QPoint& globalPos);38 void onRightClick(const QPoint& globalPos);
37 void onRightClickChild(const QPoint& globalPos, QModelIndex index);39 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
3840
39private:41 private:
42 static QString getiTunesMusicPath();
43 bool importLibrary(QString file);
44 void parseTracks(QXmlStreamReader &xml);
45 void parseTrack(QXmlStreamReader &xml, QSqlQuery &query);
46 void parsePlaylists(QXmlStreamReader &xml);
47 void parsePlaylist(QXmlStreamReader &xml, QSqlQuery &query1, QSqlQuery &query2);
48 void clearTable(QString table_name);
49
40 ITunesTrackModel* m_pITunesTrackModel;50 ITunesTrackModel* m_pITunesTrackModel;
41 ITunesPlaylistModel* m_pITunesPlaylistModel;51 ITunesPlaylistModel* m_pITunesPlaylistModel;
42 ProxyTrackModel* m_pTrackModelProxy;
43 ProxyTrackModel* m_pPlaylistModelProxy;
44 QStringListModel m_childModel;52 QStringListModel m_childModel;
53 QStringList m_playlists;
54 TrackCollection* m_pTrackCollection;
55 QSqlDatabase &m_database;
56 bool m_isActivated;
45};57};
4658
47#endif /* ITUNESFEATURE_H */59#endif /* ITUNESFEATURE_H */
4860
=== modified file 'mixxx/src/library/itunesplaylistmodel.cpp'
--- mixxx/src/library/itunesplaylistmodel.cpp 2010-11-16 21:01:45 +0000
+++ mixxx/src/library/itunesplaylistmodel.cpp 2010-11-21 15:57:13 +0000
@@ -1,258 +1,187 @@
1// itunesplaylistmodel.cpp
2// Created 12/19/2009 by RJ Ryan (rryan@mit.edu)
3// Adapted from Philip Whelan's RhythmboxPlaylistModel
4
5#include <QtCore>1#include <QtCore>
6#include <QtGui>2#include <QtGui>
7#include <QtSql>3#include <QtSql>
8#include <QtDebug>4#include "library/trackcollection.h"
9#include <QtXmlPatterns/QXmlQuery>
10
11#include "library/itunesplaylistmodel.h"5#include "library/itunesplaylistmodel.h"
12#include "library/itunestrackmodel.h"
13#include "xmlparse.h"
14#include "trackinfoobject.h"
15#include "defs.h"
166
17#include "mixxxutils.cpp"7#include "mixxxutils.cpp"
188
19ITunesPlaylistModel::ITunesPlaylistModel(ITunesTrackModel *pTrackModel) :9ITunesPlaylistModel::ITunesPlaylistModel(QObject* parent,
20 TrackModel(QSqlDatabase::database("QSQLITE"), "mixxx.db.model.itunes_playlist"),10 TrackCollection* pTrackCollection)
21 m_pTrackModel(pTrackModel),11 : TrackModel(pTrackCollection->getDatabase(),
22 m_sCurrentPlaylist("")12 "mixxx.db.model.itunes_playlist"),
23{13 BaseSqlTableModel(parent, pTrackCollection, pTrackCollection->getDatabase()),
2414 m_pTrackCollection(pTrackCollection),
25}15 m_database(m_pTrackCollection->getDatabase())
2616{
27ITunesPlaylistModel::~ITunesPlaylistModel()17 connect(this, SIGNAL(doSearch(const QString&)), this, SLOT(slotSearch(const QString&)));
28{18}
29}19
3020ITunesPlaylistModel::~ITunesPlaylistModel() {
31Qt::ItemFlags ITunesPlaylistModel::flags ( const QModelIndex & index ) const
32{
33 Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
34
35 if (!index.isValid())
36 return Qt::ItemIsEnabled;
37
38 defaultFlags |= Qt::ItemIsDragEnabled;
39
40 return defaultFlags;
41}
42
43QMimeData* ITunesPlaylistModel::mimeData(const QModelIndexList &indexes) const {
44 QMimeData *mimeData = new QMimeData();
45 QList<QUrl> urls;
46
47 //Ok, so the list of indexes we're given contains separates indexes for
48 //each column, so even if only one row is selected, we'll have like 7 indexes.
49 //We need to only count each row once:
50 QList<int> rows;
51
52 foreach (QModelIndex index, indexes) {
53 if (index.isValid()) {
54 if (!rows.contains(index.row())) {
55 rows.push_back(index.row());
56 QUrl url = QUrl::fromLocalFile(getTrackLocation(index));
57 if (!url.isValid())
58 qDebug() << "ERROR invalid url\n";
59 else
60 urls.append(url);
61 }
62 }
63 }
64 mimeData->setUrls(urls);
65 return mimeData;
66}
67
68QVariant ITunesPlaylistModel::data ( const QModelIndex & index, int role ) const
69{
70 if ( m_sCurrentPlaylist == "" )
71 return QVariant();
72
73 if (!index.isValid())
74 return QVariant();
75
76 // OwenB - attempting to make this more efficient, don't create a new
77 // TIO for every row
78 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
79 // get track id
80 QList<QString> playlistTrackList = m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist];
81 QString id = playlistTrackList.at(index.row());
82
83 // use this to get DOM node from the TrackModel
84 QDomNode songNode = m_pTrackModel->getTrackNodeById(id);
85
86 // use node to return the data item that was asked for.
87 return m_pTrackModel->getTrackColumnData(songNode, index);
88 }
89
90 return QVariant();
91}
92
93bool ITunesPlaylistModel::isColumnInternal(int column) {
94 return false;
95}
96bool ITunesPlaylistModel::isColumnHiddenByDefault(int column) {
97 return false;
98}
99
100QVariant ITunesPlaylistModel::headerData ( int section, Qt::Orientation orientation, int role ) const
101{
102 /* Only respond to requests for column header display names */
103 if ( role != Qt::DisplayRole )
104 return QVariant();
105
106 if (orientation == Qt::Horizontal)
107 {
108 switch (section)
109 {
110 case ITunesPlaylistModel::COLUMN_ARTIST:
111 return QString(tr("Artist"));
112
113 case ITunesPlaylistModel::COLUMN_TITLE:
114 return QString(tr("Title"));
115
116 case ITunesPlaylistModel::COLUMN_ALBUM:
117 return QString(tr("Album"));
118
119 case ITunesPlaylistModel::COLUMN_DATE:
120 return QString(tr("Date"));
121
122 case ITunesPlaylistModel::COLUMN_BPM:
123 return QString(tr("BPM"));
124
125 case ITunesPlaylistModel::COLUMN_GENRE:
126 return QString(tr("Genre"));
127
128 case ITunesPlaylistModel::COLUMN_LOCATION:
129 return QString(tr("Location"));
130
131 case ITunesPlaylistModel::COLUMN_DURATION:
132 return QString(tr("Duration"));
133
134 default:
135 return QString(tr("Unknown"));
136 }
137 }
138
139 return QVariant();
140}
141
142int ITunesPlaylistModel::rowCount ( const QModelIndex & parent ) const
143{
144 // FIXME
145 //if ( !m_mPlaylists.containts(m_sCurrentPlaylist))
146 // return 0;
147
148 if (!m_pTrackModel || m_sCurrentPlaylist == "" )
149 return 0;
150
151 return m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist].size();
152}
153
154int ITunesPlaylistModel::columnCount(const QModelIndex& parent) const
155{
156 if (parent != QModelIndex()) //Some weird thing for table-based models.
157 return 0;
158 return ITunesPlaylistModel::NUM_COLUMNS;
159}21}
16022
161bool ITunesPlaylistModel::addTrack(const QModelIndex& index, QString location)23bool ITunesPlaylistModel::addTrack(const QModelIndex& index, QString location)
162{24{
163 //Should do nothing... hmmm25
164 return false;26 return false;
165}27}
16628
167/** Removes a track from the library track collection. */29TrackPointer ITunesPlaylistModel::getTrack(const QModelIndex& index) const
168void ITunesPlaylistModel::removeTrack(const QModelIndex& index)30{
169{31 QString artist = index.sibling(index.row(), fieldIndex("artist")).data().toString();
170 //Should do nothing... hmmm32 QString title = index.sibling(index.row(), fieldIndex("title")).data().toString();
171}33 QString album = index.sibling(index.row(), fieldIndex("album")).data().toString();
17234 QString year = index.sibling(index.row(), fieldIndex("year")).data().toString();
173void ITunesPlaylistModel::removeTracks(const QModelIndexList& indices)35 QString genre = index.sibling(index.row(), fieldIndex("genre")).data().toString();
174{36 float bpm = index.sibling(index.row(), fieldIndex("bpm")).data().toString().toFloat();
175}37
17638 QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
177void ITunesPlaylistModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex)39
178{40 TrackInfoObject* pTrack = new TrackInfoObject(location);
179 //Should do nothing... hmmm41 pTrack->setArtist(artist);
180}42 pTrack->setTitle(title);
18143 pTrack->setAlbum(album);
182QString ITunesPlaylistModel::getTrackLocation(const QModelIndex& index) const44 pTrack->setYear(year);
183{45 pTrack->setGenre(genre);
184 TrackPointer track = getTrack(index);46 pTrack->setBpm(bpm);
185 QString location = track->getLocation();47
186 // track is auto-deleted48 return TrackPointer(pTrack, &QObject::deleteLater);
49}
50
51QString ITunesPlaylistModel::getTrackLocation(const QModelIndex& index) const {
52 QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
187 return location;53 return location;
188}54}
18955
190TrackPointer ITunesPlaylistModel::getTrack(const QModelIndex& index) const56void ITunesPlaylistModel::removeTrack(const QModelIndex& index) {
191{57
192 int row = index.row();58}
19359
194 if (!m_pTrackModel ||60void ITunesPlaylistModel::removeTracks(const QModelIndexList& indices) {
195 !m_pTrackModel->m_mPlaylists.contains(m_sCurrentPlaylist)) {61
196 return TrackPointer();62}
197 }63
19864void ITunesPlaylistModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) {
199 // Qt should do this by reference for us so we aren't actually making a copy65
200 // of the list.66}
201 QList<QString> songIds = m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist];67
20268void ITunesPlaylistModel::search(const QString& searchText) {
203 if (row < 0 || row >= songIds.length()) {69 // qDebug() << "ITunesPlaylistModel::search()" << searchText
204 return TrackPointer();70 // << QThread::currentThread();
205 }71 emit(doSearch(searchText));
20672}
207 return m_pTrackModel->getTrackById(songIds.at(row));73
208}74void ITunesPlaylistModel::slotSearch(const QString& searchText) {
20975 if (!m_currentSearch.isNull() && m_currentSearch == searchText)
210QItemDelegate* ITunesPlaylistModel::delegateForColumn(const int i) {76 return;
211 return NULL;
212}
213
214QList<QString> ITunesPlaylistModel::getPlaylists()
215{
216 if (!m_pTrackModel) {
217 return QList<QString>();
218 }
219 return m_pTrackModel->m_mPlaylists.keys();
220}
221
222int ITunesPlaylistModel::numPlaylists() {
223 if (!m_pTrackModel) {
224 return 0;
225 }
226 return m_pTrackModel->m_mPlaylists.size();
227}
228
229QString ITunesPlaylistModel::playlistTitle(int n) {
230 if (!m_pTrackModel) {
231 return "";
232 }
233 return m_pTrackModel->m_mPlaylists.keys().at(n);
234}
235
236void ITunesPlaylistModel::setPlaylist(QString playlist)
237{
238 if (m_pTrackModel && m_pTrackModel->m_mPlaylists.contains(playlist))
239 m_sCurrentPlaylist = playlist;
240 else
241 m_sCurrentPlaylist = "";
242
243 // force the layout to update
244 emit(layoutChanged());
245}
246
247void ITunesPlaylistModel::search(const QString& searchText)
248{
249 m_currentSearch = searchText;77 m_currentSearch = searchText;
78
79 QString filter;
80 QSqlField search("search", QVariant::String);
81 search.setValue("%" + searchText + "%");
82 QString escapedText = database().driver()->formatValue(search);
83 filter = "(artist LIKE " + escapedText + " OR " +
84 "album LIKE " + escapedText + " OR " +
85 "title LIKE " + escapedText + ")";
86 setFilter(filter);
250}87}
25188
252const QString ITunesPlaylistModel::currentSearch() {89const QString ITunesPlaylistModel::currentSearch() {
253 return m_currentSearch;90 return m_currentSearch;
254}91}
25592
256const QList<int>& ITunesPlaylistModel::searchColumns() const {93bool ITunesPlaylistModel::isColumnInternal(int column) {
257 return m_pTrackModel->searchColumns();94 if (column == fieldIndex(LIBRARYTABLE_ID) ||
95 column == fieldIndex(LIBRARYTABLE_MIXXXDELETED) ||
96 column == fieldIndex(TRACKLOCATIONSTABLE_FSDELETED) ||
97 column == fieldIndex("name") ||
98 column == fieldIndex("track_id"))
99 return true;
100 return false;
101}
102
103QMimeData* ITunesPlaylistModel::mimeData(const QModelIndexList &indexes) const {
104 return NULL;
105}
106
107
108QItemDelegate* ITunesPlaylistModel::delegateForColumn(const int i) {
109 return NULL;
110}
111
112TrackModel::CapabilitiesFlags ITunesPlaylistModel::getCapabilities() const {
113 return NULL;
114}
115
116Qt::ItemFlags ITunesPlaylistModel::flags(const QModelIndex &index) const {
117 return readOnlyFlags(index);
118}
119
120void ITunesPlaylistModel::setPlaylist(QString playlist_path) {
121 int playlistId = -1;
122 QSqlQuery finder_query(m_database);
123 finder_query.prepare("SELECT id from itunes_playlists where name='"+playlist_path+"'");
124
125 if(finder_query.exec()){
126 while (finder_query.next()) {
127 playlistId = finder_query.value(finder_query.record().indexOf("id")).toInt();
128 }
129 }
130 else
131 qDebug() << "SQL Error in ITunesPlaylistModel.cpp: line" << __LINE__ << " " << finder_query.lastError();
132
133
134 QString playlistID = "ITunesPlaylist_" + QString("%1").arg(playlistId);
135 //Escape the playlist name
136 QSqlDriver* driver = m_pTrackCollection->getDatabase().driver();
137 QSqlField playlistNameField("name", QVariant::String);
138 playlistNameField.setValue(playlistID);
139
140 QSqlQuery query(m_database);
141 query.prepare("CREATE TEMPORARY VIEW IF NOT EXISTS "+ driver->formatValue(playlistNameField) + " AS "
142 "SELECT "
143 "itunes_library.id,"
144 "itunes_library.artist,"
145 "itunes_library.title,"
146 "itunes_library.album,"
147 "itunes_library.year,"
148 "itunes_library.genre,"
149 "itunes_library.tracknumber,"
150 "itunes_library.location,"
151 "itunes_library.comment,"
152 "itunes_library.rating,"
153 "itunes_library.duration,"
154 "itunes_library.bitrate,"
155 "itunes_library.bpm,"
156 "itunes_playlist_tracks.track_id, "
157 "itunes_playlists.name "
158 "FROM itunes_library "
159 "INNER JOIN itunes_playlist_tracks "
160 "ON itunes_playlist_tracks.track_id = itunes_library.id "
161 "INNER JOIN itunes_playlists "
162 "ON itunes_playlist_tracks.playlist_id = itunes_playlists.id "
163 "where itunes_playlists.name='"+playlist_path+"'"
164 );
165
166
167 if (!query.exec()) {
168
169 qDebug() << "Error creating temporary view for itunes playlists. ITunesPlaylistModel --> line: " << __LINE__ << " " << query.lastError();
170 qDebug() << "Executed Query: " << query.executedQuery();
171 return;
172 }
173 setTable(playlistID);
174
175 //removeColumn(fieldIndex("track_id"));
176 //removeColumn(fieldIndex("name"));
177 //removeColumn(fieldIndex("id"));
178
179 slotSearch("");
180
181 select(); //Populate the data model.
182 initHeaderData();
183}
184
185bool ITunesPlaylistModel::isColumnHiddenByDefault(int column) {
186 return false;
258}187}
259188
=== modified file 'mixxx/src/library/itunesplaylistmodel.h'
--- mixxx/src/library/itunesplaylistmodel.h 2010-11-16 21:01:45 +0000
+++ mixxx/src/library/itunesplaylistmodel.h 2010-11-21 15:57:13 +0000
@@ -1,77 +1,53 @@
1// itunestrackmodel.h1#ifndef ITUNES_PLAYLIST_MODEL_H
2// Created 12/19/2009 by RJ Ryan (rryan@mit.edu)2#define ITUNES_PLAYLIST_MODEL_H
3// Adapted from Phillip Whelan's RhythmboxPlaylistModel
4
5#ifndef ITUNESPLAYLISTMODEL_H
6#define ITUNESPLAYLISTMODEL_H
73
8#include <QtSql>4#include <QtSql>
9#include <QtXml>5#include <QItemDelegate>
6#include <QtCore>
10#include "trackmodel.h"7#include "trackmodel.h"
118#include "library/basesqltablemodel.h"
12class ITunesTrackModel;9#include "library/librarytablemodel.h"
1310#include "library/dao/playlistdao.h"
14class ITunesPlaylistModel : public QAbstractTableModel, public TrackModel {11#include "library/dao/trackdao.h"
1512
16 enum Columns {13class TrackCollection;
17 COLUMN_ARTIST = 0,14
18 COLUMN_TITLE,15class ITunesPlaylistModel : public BaseSqlTableModel, public virtual TrackModel
19 COLUMN_ALBUM,16{
20 COLUMN_DATE,
21 COLUMN_BPM,
22 COLUMN_GENRE,
23 COLUMN_LOCATION,
24 COLUMN_DURATION,
25 NUM_COLUMNS
26 };
27
28 Q_OBJECT17 Q_OBJECT
29
30 public:18 public:
31 ITunesPlaylistModel(ITunesTrackModel* pTrackModel);19 ITunesPlaylistModel(QObject* parent, TrackCollection* pTrackCollection);
32 virtual ~ITunesPlaylistModel();20 virtual ~ITunesPlaylistModel();
3321
34 //QAbstractTableModel stuff
35 virtual Qt::ItemFlags flags(const QModelIndex& index) const;
36 virtual QVariant data(const QModelIndex & index,
37 int role = Qt::DisplayRole) const;
38 virtual QMimeData* mimeData(const QModelIndexList &indexes) const;
39 virtual QVariant headerData(int section,
40 Qt::Orientation orientation,
41 int role = Qt::DisplayRole) const;
42 virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
43 virtual int columnCount(const QModelIndex& parent) const;
44
45 //Playlist Model stuff
46 virtual TrackPointer getTrack(const QModelIndex& index) const;22 virtual TrackPointer getTrack(const QModelIndex& index) const;
47 virtual QString getTrackLocation(const QModelIndex& index) const;23 virtual QString getTrackLocation(const QModelIndex& index) const;
48 virtual void search(const QString& searchText);24 virtual void search(const QString& searchText);
49 virtual const QString currentSearch();25 virtual const QString currentSearch();
50 virtual const QList<int>& searchColumns() const;
51 virtual bool isColumnInternal(int column);26 virtual bool isColumnInternal(int column);
52 virtual bool isColumnHiddenByDefault(int column);27 virtual bool isColumnHiddenByDefault(int column);
53 virtual void removeTrack(const QModelIndex& index);28 virtual void removeTrack(const QModelIndex& index);
54 virtual void removeTracks(const QModelIndexList& indices);29 virtual void removeTracks(const QModelIndexList& indices);
55 virtual bool addTrack(const QModelIndex& index, QString location);30 virtual bool addTrack(const QModelIndex& index, QString location);
56 virtual void moveTrack(const QModelIndex& sourceIndex,31 virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
57 const QModelIndex& destIndex);32
33 virtual Qt::ItemFlags flags(const QModelIndex &index) const;
34 QMimeData* mimeData(const QModelIndexList &indexes) const;
35
58 QItemDelegate* delegateForColumn(const int i);36 QItemDelegate* delegateForColumn(const int i);
5937 TrackModel::CapabilitiesFlags getCapabilities() const;
60 virtual QList<QString> getPlaylists();38 /** sets the playlist **/
61 virtual void setPlaylist(QString playlist);39 void setPlaylist(QString path_name);
6240
63 int numPlaylists();41 private slots:
64 QString playlistTitle(int n);42 void slotSearch(const QString& searchText);
6543
66 signals:44 signals:
67 void startedLoading();45 void doSearch(const QString& searchText);
68 void progressLoading(QString path);
69 void finishedLoading();
7046
71 private:47 private:
72 ITunesTrackModel* m_pTrackModel;48 TrackCollection* m_pTrackCollection;
73 QString m_sCurrentPlaylist;49 QSqlDatabase &m_database;
74 QString m_currentSearch;50 QString m_currentSearch;
75};51};
7652
77#endif /* ITUNESPLAYLISTMODEL_H */53#endif /* ITUNES_PLAYLIST_MODEL_H */
7854
=== modified file 'mixxx/src/library/itunestrackmodel.cpp'
--- mixxx/src/library/itunestrackmodel.cpp 2010-11-16 21:01:45 +0000
+++ mixxx/src/library/itunestrackmodel.cpp 2010-11-21 15:57:13 +0000
@@ -1,316 +1,118 @@
1#include <QtCore>1#include <QtCore>
2#include <QtGui>2#include <QtGui>
3#include <QtSql>3#include <QtSql>
4#include <QtDebug>4#include "library/trackcollection.h"
5#include <QSettings>5#include "library/itunestrackmodel.h"
6#include <QRegExp>
7#include <QtXmlPatterns/QXmlQuery>
8#include <QDesktopServices>
9
10#include "itunestrackmodel.h"
11#include "xmlparse.h"
12#include "trackinfoobject.h"
13#include "defs.h"
14#include "soundsourceproxy.h"
156
16#include "mixxxutils.cpp"7#include "mixxxutils.cpp"
178
18ITunesTrackModel::ITunesTrackModel()9ITunesTrackModel::ITunesTrackModel(QObject* parent,
19 : AbstractXmlTrackModel("mixxx.db.model.itunes") {10 TrackCollection* pTrackCollection)
20 QXmlQuery query;11 : TrackModel(pTrackCollection->getDatabase(),
21 QString res, playlistRes;12 "mixxx.db.model.itunes"),
22 QDomDocument itunesdb;13 BaseSqlTableModel(parent, pTrackCollection, pTrackCollection->getDatabase()),
2314 m_pTrackCollection(pTrackCollection),
24 QRegExp supportedFileRegex(SoundSourceProxy::supportedFileExtensionsRegex(),15 m_database(m_pTrackCollection->getDatabase()) {
25 Qt::CaseInsensitive);16 connect(this, SIGNAL(doSearch(const QString&)), this, SLOT(slotSearch(const QString&)));
2617 setTable("itunes_library");
27 QString itunesXmlPath = getiTunesMusicPath();18 initHeaderData();
2819}
29 QFile db(itunesXmlPath);20
30 if (!db.exists())21ITunesTrackModel::~ITunesTrackModel() {
31 return;22}
3223
33 if (!db.open(QIODevice::ReadOnly | QIODevice::Text))24bool ITunesTrackModel::addTrack(const QModelIndex& index, QString location) {
34 return;25 return false;
3526}
36 // Workaround for Bug #501916. Read the file, convert it to UTF-8 and then27
37 // load it.28TrackPointer ITunesTrackModel::getTrack(const QModelIndex& index) const {
38 QByteArray db_bytes_utf8 = QString(db.readAll()).toUtf8();29 QString artist = index.sibling(index.row(), fieldIndex("artist")).data().toString();
39 QBuffer buffer(&db_bytes_utf8);30 QString title = index.sibling(index.row(), fieldIndex("title")).data().toString();
40 if (!buffer.open(QIODevice::ReadOnly | QIODevice::Text))31 QString album = index.sibling(index.row(), fieldIndex("album")).data().toString();
41 return;32 QString year = index.sibling(index.row(), fieldIndex("year")).data().toString();
4233 QString genre = index.sibling(index.row(), fieldIndex("genre")).data().toString();
43 /*34 float bpm = index.sibling(index.row(), fieldIndex("bpm")).data().toString().toFloat();
44 * Use QXmlQuery to execute an XPath query. We add the version to35
45 * the XPath query to make sure it is the schema we expect.36 QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
46 *37
47 * TODO: filter /key='Track Type'/string='URL' (remote) files38 TrackInfoObject* pTrack = new TrackInfoObject(location);
48 */39 pTrack->setArtist(artist);
49 query.setFocus(&buffer);40 pTrack->setTitle(title);
50 query.setQuery("/plist[@version='1.0']/dict[key='Tracks']/dict/dict");41 pTrack->setAlbum(album);
51 if ( ! query.isValid())42 pTrack->setYear(year);
52 return;43 pTrack->setGenre(genre);
5344 pTrack->setBpm(bpm);
54 query.evaluateTo(&res);45
5546 return TrackPointer(pTrack, &QObject::deleteLater);
56 // Both ITunes and Rhythmbox parsing should occur in something completely47}
57 // separate from the TrackModels, but since we're in a rush, we're just48
58 // going to do playlist parsing here so we don't have to re-open the file49QString ITunesTrackModel::getTrackLocation(const QModelIndex& index) const {
59 // again.50 QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
60 query.setQuery("/plist[@version='1.0']/dict[key='Playlists']/array/dict");51 return location;
61 if (query.isValid()) {52}
62 query.evaluateTo(&playlistRes);53
63 }54void ITunesTrackModel::removeTrack(const QModelIndex& index) {
64 db.close();55
6556}
66 /*57
67 * Parse the result as an XML file. These shennanigans actually58void ITunesTrackModel::removeTracks(const QModelIndexList& indices) {
68 * reduce the load time from a minute to a matter of seconds.59
69 */60}
70 itunesdb.setContent("<plist version='1.0'>" + res + "</plist>");61
71 m_trackNodes = itunesdb.elementsByTagName("dict");62void ITunesTrackModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) {
7263
73 for (int i = 0; i < m_trackNodes.count(); i++) {64}
74 QDomNode n = m_trackNodes.at(i);65
75 QString trackId = findValueByKey(n, "Track ID");66void ITunesTrackModel::search(const QString& searchText) {
76 QString location = findValueByKey(n, "Location");67 // qDebug() << "ITunesTrackModel::search()" << searchText
7768 // << QThread::currentThread();
78 // Skip files we cannot play.69 emit(doSearch(searchText));
79 if (location.count(supportedFileRegex) == 0) {70}
80 continue;71
81 }72void ITunesTrackModel::slotSearch(const QString& searchText) {
8273 if (!m_currentSearch.isNull() && m_currentSearch == searchText)
83 m_mTracksByLocation[location] = n;74 return;
84 m_mTracksById[trackId] = n;75 m_currentSearch = searchText;
85 }76
8677 QString filter;
87 // Now process the playlist data.78 QSqlField search("search", QVariant::String);
88 QDomDocument playlistdb;79 search.setValue("%" + searchText + "%");
89 playlistdb.setContent("<plist version='1.0'>" + playlistRes + "</plist>");80 QString escapedText = database().driver()->formatValue(search);
90 QDomNodeList playlistNodes = playlistdb.documentElement().childNodes();81 filter = "(artist LIKE " + escapedText + " OR " +
91 for (int i = 0; i < playlistNodes.count(); i++) {82 "album LIKE " + escapedText + " OR " +
92 QDomNode n = playlistNodes.at(i);83 "title LIKE " + escapedText + ")";
9384 setFilter(filter);
94 // Get the playlist name85
95 QString name = findValueByKey(n, "Name");86}
96 qDebug() << "Found playlist" << name;87
9788const QString ITunesTrackModel::currentSearch() {
98 // Skip invisible playlists89 return m_currentSearch;
99 QDomElement visible = findNodeByKey(n, "Visible");
100 if (!visible.isNull() && visible.tagName() == "false") {
101 continue;
102 }
103
104 // Now traverse to the items list
105 QDomElement items = findNodeByKey(n, "Playlist Items");
106 if (items.isNull()) {
107 qDebug() << name << "has no items";
108 continue;
109 }
110
111 // Now extract the song ids that are members of this playlist
112 QDomNodeList playlistEntries = items.childNodes();
113 QList<QString> playlistSongIds;
114 for (int j = 0; j < playlistEntries.count(); j++) {
115 QDomNode entry = playlistEntries.at(j);
116 QString trackId = findValueByKey(entry, "Track ID");
117
118 // If the track index does not contain the given track, that means
119 // either the track is not playable in Mixxx, or the XML is
120 // inconsistent. In this case, don't show the track in the playlist.
121 if (!m_mTracksById.contains(trackId)) {
122 continue;
123 }
124
125 playlistSongIds.append(trackId);
126 }
127
128 // If there were no playable items in the playlist, don't show it.
129 if (playlistSongIds.count() == 0) {
130 qDebug() << name << "has no items";
131 continue;
132 }
133
134 // TODO(XXX) : Do we need to handle duplicate playlist names? If there
135 // are duplicates, only one will show up if we do this.
136 m_mPlaylists[name] = playlistSongIds;
137 }
138
139 qDebug() << "ITunesTrackModel: m_entryNodes size is" << m_trackNodes.size();
140
141 addColumnName(ITunesTrackModel::COLUMN_ARTIST, "Artist");
142 addColumnName(ITunesTrackModel::COLUMN_TITLE, "Title");
143 addColumnName(ITunesTrackModel::COLUMN_ALBUM, "Album");
144 addColumnName(ITunesTrackModel::COLUMN_DATE, "Date");
145 addColumnName(ITunesTrackModel::COLUMN_BPM, "BPM");
146 addColumnName(ITunesTrackModel::COLUMN_GENRE, "Genre");
147 addColumnName(ITunesTrackModel::COLUMN_LOCATION, "Location");
148 addColumnName(ITunesTrackModel::COLUMN_DURATION, "Duration");
149
150 addSearchColumn(ITunesTrackModel::COLUMN_ARTIST);
151 addSearchColumn(ITunesTrackModel::COLUMN_TITLE);
152 addSearchColumn(ITunesTrackModel::COLUMN_ALBUM);
153 addSearchColumn(ITunesTrackModel::COLUMN_GENRE);
154 addSearchColumn(ITunesTrackModel::COLUMN_LOCATION);
155}
156
157ITunesTrackModel::~ITunesTrackModel()
158{
159
160}
161
162QItemDelegate* ITunesTrackModel::delegateForColumn(const int i)
163{
164 return NULL;
165}
166
167QString ITunesTrackModel::findValueByKey(QDomNode dictNode, QString key) const
168{
169 QDomElement curElem;
170
171 curElem = dictNode.firstChildElement("key");
172 while(!curElem.isNull()) {
173 if ( curElem.text() == key ) {
174 QDomElement value;
175 value = curElem.nextSiblingElement();
176 QString textValue = value.text();
177 //Either iTunes lies about the text encoding in its XML file or
178 //Qt misdetects it. We workaround this explicitly sayi it's UTF-8.
179 textValue = QString::fromUtf8(textValue.toAscii().data(), textValue.size());
180 return textValue;
181 }
182
183 curElem = curElem.nextSiblingElement("key");
184 }
185
186 return QString();
187}
188
189QDomElement ITunesTrackModel::findNodeByKey(QDomNode dictNode, QString key) const
190{
191 QDomElement curElem;
192
193 curElem = dictNode.firstChildElement("key");
194 while(!curElem.isNull()) {
195 if ( curElem.text() == key ) {
196 return curElem.nextSiblingElement();
197 }
198 curElem = curElem.nextSiblingElement("key");
199 }
200
201 return QDomElement();
202}
203
204QVariant ITunesTrackModel::getTrackColumnData(QDomNode songNode, const QModelIndex& index) const
205{
206 QVariant value;
207 switch (index.column()) {
208 case ITunesTrackModel::COLUMN_ARTIST:
209 return findValueByKey(songNode, "Artist");
210 case ITunesTrackModel::COLUMN_TITLE:
211 return findValueByKey(songNode, "Name");
212 case ITunesTrackModel::COLUMN_ALBUM:
213 return findValueByKey(songNode,"Album");
214 case ITunesTrackModel::COLUMN_DATE:
215 return findValueByKey(songNode,"Year");
216 case ITunesTrackModel::COLUMN_BPM:
217 return findValueByKey(songNode,"BPM");
218 case ITunesTrackModel::COLUMN_GENRE:
219 return findValueByKey(songNode,"Genre");
220 case ITunesTrackModel::COLUMN_LOCATION: {
221 /*
222 * Strip the crappy file://localhost/ from the URL and
223 * format URL as in method ITunesTrackModel::parseTrackNode(QDomNode songNode)
224 */
225 QString strloc = findValueByKey(songNode,"Location");
226 QByteArray strlocbytes = strloc.toUtf8();
227 QString location = QUrl::fromEncoded(strlocbytes).toLocalFile();
228#if defined(__WINDOWS__)
229 return location.remove("//localhost/");
230#else
231 return location.remove("//localhost");
232#endif
233 }
234
235 case ITunesTrackModel::COLUMN_DURATION:
236 value = findValueByKey(songNode,"Total Time");
237 if (qVariantCanConvert<int>(value)) {
238 value = MixxxUtils::millisecondsToMinutes(qVariantValue<int>(value));
239 }
240 return value;
241 default:
242 return QVariant();
243 }
244}90}
24591
246bool ITunesTrackModel::isColumnInternal(int column) {92bool ITunesTrackModel::isColumnInternal(int column) {
93 if (column == fieldIndex(LIBRARYTABLE_ID) ||
94 column == fieldIndex(LIBRARYTABLE_MIXXXDELETED) ||
95 column == fieldIndex(TRACKLOCATIONSTABLE_FSDELETED))
96 return true;
247 return false;97 return false;
248}98}
99
100QMimeData* ITunesTrackModel::mimeData(const QModelIndexList &indexes) const {
101 return NULL;
102}
103
104QItemDelegate* ITunesTrackModel::delegateForColumn(const int i) {
105 return NULL;
106}
107
108TrackModel::CapabilitiesFlags ITunesTrackModel::getCapabilities() const {
109 return NULL;
110}
111
112Qt::ItemFlags ITunesTrackModel::flags(const QModelIndex &index) const {
113 return readOnlyFlags(index);
114}
115
249bool ITunesTrackModel::isColumnHiddenByDefault(int column) {116bool ITunesTrackModel::isColumnHiddenByDefault(int column) {
250 return false;117 return false;
251}118}
252
253TrackPointer ITunesTrackModel::parseTrackNode(QDomNode songNode) const
254{
255 QString strloc = findValueByKey(songNode,"Location");
256 QByteArray strlocbytes = strloc.toUtf8();
257 QUrl location = QUrl::fromEncoded(strlocbytes);
258
259 QString trackLocation;
260 //Strip the crappy localhost from the URL since Qt barfs on this :(
261#if defined(__WINDOWS__)
262 trackLocation = location.toLocalFile().remove("//localhost/");
263#else
264 trackLocation = location.toLocalFile().remove("//localhost");
265#endif
266 //pTrack->setLocation(QUrl(findValueByKey(songNode,"Location")).toLocalFile());
267
268 TrackInfoObject* pTrack = new TrackInfoObject(trackLocation);
269
270 pTrack->setArtist(findValueByKey(songNode, "Artist"));
271 pTrack->setTitle(findValueByKey(songNode, "Name"));
272 pTrack->setAlbum(findValueByKey(songNode,"Album"));
273 pTrack->setYear(findValueByKey(songNode,"Year"));
274 pTrack->setGenre(findValueByKey(songNode,"Genre"));
275 pTrack->setBpm(findValueByKey(songNode,"BPM").toFloat());
276
277 // ITunes stores time in total milliseconds
278 pTrack->setDuration(findValueByKey(songNode,"Total Time").toInt() / 1000);
279
280 // Let Qt handle deleting the track since it isn't owned by the library.
281 return TrackPointer(pTrack, &QObject::deleteLater);
282}
283
284TrackPointer ITunesTrackModel::getTrackById(QString id) {
285 if (!m_mTracksById.contains(id)) {
286 return TrackPointer();
287 }
288 return parseTrackNode(m_mTracksById[id]);
289}
290
291QString ITunesTrackModel::getiTunesMusicPath() {
292 QString musicFolder;
293#if defined(__APPLE__)
294 musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "/iTunes/iTunes Music Library.xml";
295#elif defined(__WINDOWS__)
296 musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "\\iTunes\\iTunes Music Library.xml";
297#elif defined(__LINUX__)
298 musicFolder = QDir::homePath() + "/.itunes.xml";
299#else
300 musicFolder = "";
301#endif
302 qDebug() << "ITunesLibrary=[" << musicFolder << "]";
303 return musicFolder;
304}
305
306//OwenB - for use by the playlistmodel
307QDomNode ITunesTrackModel::getTrackNodeById(const QString& id) const
308{
309 if ( !m_mTracksById.contains(id))
310 return QDomNode();
311
312 QDomNode songNode = m_mTracksById[id];
313 return songNode;
314}
315
316
317119
=== modified file 'mixxx/src/library/itunestrackmodel.h'
--- mixxx/src/library/itunestrackmodel.h 2010-11-16 21:01:45 +0000
+++ mixxx/src/library/itunestrackmodel.h 2010-11-21 15:57:13 +0000
@@ -1,82 +1,51 @@
1/***************************************************************************1#ifndef ITUNES_TABLE_MODEL_H
2 ituestrackmodel.h2#define ITUNES_TABLE_MODEL_H
3 -------------------
4 begin : 8/28/2009
5 copyright : (C) 2009 Phillip Whelan
6 email : pwhelan@mixxx.org
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#ifndef ITUNESTRACKMODEL_H
19#define ITUNESTRACKMODEL_H
203
21#include <QtSql>4#include <QtSql>
22#include <QtXml>5#include <QItemDelegate>
6#include <QtCore>
23#include "trackmodel.h"7#include "trackmodel.h"
24#include "abstractxmltrackmodel.h"8#include "library/basesqltablemodel.h"
25#include "itunesplaylistmodel.h"9#include "library/librarytablemodel.h"
2610#include "library/dao/playlistdao.h"
27class QSqlDatabase;11#include "library/dao/trackdao.h"
2812
29/**13class TrackCollection;
30 @author Phillip Whelan14
31*/15class ITunesTrackModel : public BaseSqlTableModel, public virtual TrackModel
32
33// Darn Jobs and his capitalization!
34class ITunesTrackModel : public AbstractXmlTrackModel
35{16{
36 enum Columns {
37 COLUMN_ARTIST = 0,
38 COLUMN_TITLE,
39 COLUMN_ALBUM,
40 COLUMN_DATE,
41 COLUMN_BPM,
42 COLUMN_GENRE,
43 COLUMN_LOCATION,
44 COLUMN_DURATION,
45 NUM_COLUMNS
46 };
47
48 Q_OBJECT17 Q_OBJECT
49 public:18 public:
50 ITunesTrackModel();19 ITunesTrackModel(QObject* parent, TrackCollection* pTrackCollection);
51 virtual ~ITunesTrackModel();20 virtual ~ITunesTrackModel();
52 virtual QItemDelegate* delegateForColumn(const int i);21
22 virtual TrackPointer getTrack(const QModelIndex& index) const;
23 virtual QString getTrackLocation(const QModelIndex& index) const;
24 virtual void search(const QString& searchText);
25 virtual const QString currentSearch();
53 virtual bool isColumnInternal(int column);26 virtual bool isColumnInternal(int column);
54 virtual bool isColumnHiddenByDefault(int column);27 virtual bool isColumnHiddenByDefault(int column);
55 static QString getiTunesMusicPath();28 virtual void removeTrack(const QModelIndex& index);
56 QDomNode getTrackNodeById(const QString& ) const;29 virtual void removeTracks(const QModelIndexList& indices);
5730 virtual bool addTrack(const QModelIndex& index, QString location);
58 public slots:31 virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
32
33 virtual Qt::ItemFlags flags(const QModelIndex &index) const;
34 QMimeData* mimeData(const QModelIndexList &indexes) const;
35
36 QItemDelegate* delegateForColumn(const int i);
37 TrackModel::CapabilitiesFlags getCapabilities() const;
38
39 private slots:
40 void slotSearch(const QString& searchText);
5941
60 signals:42 signals:
61 void startedLoading();43 void doSearch(const QString& searchText);
62 void progressLoading(QString path);
63 void finishedLoading();
64
65 protected:
66 virtual TrackPointer parseTrackNode(QDomNode node) const;
67 /* Implemented by AbstractXmlTrackModel implementations to return the data for song columns */
68 virtual QVariant getTrackColumnData(QDomNode node, const QModelIndex& index) const;
69 /* Called by AbstractXmlTrackModel implementations to enumerate their columns */
7044
71 private:45 private:
72 QString findValueByKey(QDomNode dictNode, QString key) const;46 TrackCollection* m_pTrackCollection;
73 QDomElement findNodeByKey(QDomNode dictNode, QString key) const;47 QSqlDatabase &m_database;
74 TrackPointer getTrackById(QString id);48 QString m_currentSearch;
75
76 QHash<QString, QDomNode> m_mTracksById;
77 QHash<QString, QList<QString> > m_mPlaylists;
78
79 friend class ITunesPlaylistModel;
80};49};
8150
82#endif51#endif /* ITUNES_TABLE_MODEL_H */
8352
=== modified file 'mixxx/src/library/library.cpp'
--- mixxx/src/library/library.cpp 2010-10-24 09:50:11 +0000
+++ mixxx/src/library/library.cpp 2010-11-21 15:57:13 +0000
@@ -62,7 +62,7 @@
62 if (RhythmboxFeature::isSupported())62 if (RhythmboxFeature::isSupported())
63 addFeature(new RhythmboxFeature(this));63 addFeature(new RhythmboxFeature(this));
64 if (ITunesFeature::isSupported())64 if (ITunesFeature::isSupported())
65 addFeature(new ITunesFeature(this));65 addFeature(new ITunesFeature(this, m_pTrackCollection));
6666
67 //Show the promo tracks view on first run, otherwise show the library67 //Show the promo tracks view on first run, otherwise show the library
68 if (firstRun) {68 if (firstRun) {
6969
=== modified file 'mixxx/src/library/trackcollection.cpp'
--- mixxx/src/library/trackcollection.cpp 2010-11-17 21:02:24 +0000
+++ mixxx/src/library/trackcollection.cpp 2010-11-21 15:57:13 +0000
@@ -68,7 +68,7 @@
68 return false;68 return false;
69 }69 }
7070
71 int requiredSchemaVersion = 7;71 int requiredSchemaVersion = 8;
72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,72 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
73 requiredSchemaVersion)) {73 requiredSchemaVersion)) {
74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),74 QMessageBox::warning(0, tr("Cannot upgrade database schema"),

Subscribers

People subscribed via source and target branches