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
1=== modified file 'mixxx/res/schema.xml'
2--- mixxx/res/schema.xml 2010-11-17 21:02:24 +0000
3+++ mixxx/res/schema.xml 2010-11-21 15:57:13 +0000
4@@ -172,4 +172,31 @@
5 DELETE FROM settings WHERE name="mixxx.db.model.missing.header_state";
6 </sql>
7 </revision>
8+ <revision version="8">
9+ <description>
10+ Added iTunes tables
11+ </description>
12+ <sql>
13+ CREATE TABLE IF NOT EXISTS itunes_library (
14+ id INTEGER primary key,
15+ artist varchar(48), title varchar(48),
16+ album varchar(48), year varchar(16),
17+ genre varchar(32), tracknumber varchar(3),
18+ location varchar(512),
19+ comment varchar(60),
20+ duration integer,
21+ bitrate integer,
22+ bpm integer,
23+ rating integer);
24+
25+ CREATE TABLE IF NOT EXISTS itunes_playlists (
26+ id INTEGER primary key,
27+ name varchar(100) UNIQUE);
28+
29+ CREATE TABLE IF NOT EXISTS itunes_playlist_tracks (
30+ id INTEGER primary key AUTOINCREMENT,
31+ playlist_id INTEGER REFERENCES itunes_playlist(id),
32+ track_id INTEGER REFERENCES itunes_library(id));
33+ </sql>
34+ </revision>
35 </schema>
36
37=== modified file 'mixxx/src/library/itunesfeature.cpp'
38--- mixxx/src/library/itunesfeature.cpp 2010-08-01 12:55:15 +0000
39+++ mixxx/src/library/itunesfeature.cpp 2010-11-21 15:57:13 +0000
40@@ -1,27 +1,29 @@
41 #include <QMessageBox>
42 #include <QtDebug>
43-
44+#include <QXmlStreamReader>
45+#include <QDesktopServices>
46 #include "library/itunesfeature.h"
47
48 #include "library/itunestrackmodel.h"
49 #include "library/itunesplaylistmodel.h"
50-#include "library/proxytrackmodel.h"
51-
52-ITunesFeature::ITunesFeature(QObject* parent)
53- : LibraryFeature(parent) {
54- //Don't actually initialize these until the iTunes item in the sidebar is clicked.
55- m_pITunesTrackModel = NULL;
56- m_pITunesPlaylistModel = NULL;
57- m_pTrackModelProxy = NULL;
58- m_pPlaylistModelProxy = NULL;
59+
60+
61+ITunesFeature::ITunesFeature(QObject* parent, TrackCollection* pTrackCollection)
62+ : LibraryFeature(parent),
63+ m_pTrackCollection(pTrackCollection),
64+ m_database(pTrackCollection->getDatabase()) {
65+ m_pITunesTrackModel = new ITunesTrackModel(this, m_pTrackCollection);
66+ m_pITunesPlaylistModel = new ITunesPlaylistModel(this, m_pTrackCollection);
67+ m_isActivated = false;
68 }
69
70 ITunesFeature::~ITunesFeature() {
71-
72+ delete m_pITunesTrackModel;
73+ delete m_pITunesPlaylistModel;
74 }
75
76 bool ITunesFeature::isSupported() {
77- return (QFile::exists(ITunesTrackModel::getiTunesMusicPath()));
78+ return QFile::exists(getiTunesMusicPath());
79 }
80
81
82@@ -36,7 +38,7 @@
83 void ITunesFeature::activate() {
84 //qDebug("ITunesFeature::activate()");
85
86- if (!m_pITunesTrackModel) {
87+ if (!m_isActivated) {
88 if (QMessageBox::question(
89 NULL,
90 tr("Load iTunes Library?"),
91@@ -46,31 +48,24 @@
92 == QMessageBox::Cancel) {
93 return;
94 }
95- m_pITunesTrackModel = new ITunesTrackModel();
96- m_pITunesPlaylistModel = new ITunesPlaylistModel(m_pITunesTrackModel);
97-
98- // Use a ProxyTrackModel for search/sorting of iTunes tracks
99- m_pTrackModelProxy = new ProxyTrackModel(m_pITunesTrackModel);
100- m_pTrackModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
101- m_pTrackModelProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
102-
103- // Use a ProxyTrackModel for search/sorting of iTunes playlists
104- m_pPlaylistModelProxy = new ProxyTrackModel(m_pITunesPlaylistModel);
105- m_pPlaylistModelProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
106- m_pPlaylistModelProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
107-
108- QStringList list;
109- for (int i = 0; i < m_pITunesPlaylistModel->numPlaylists(); ++i) {
110- list << m_pITunesPlaylistModel->playlistTitle(i);
111+
112+ if (importLibrary(getiTunesMusicPath())) {
113+ m_isActivated = true;
114+ } else {
115+ QMessageBox::warning(
116+ NULL,
117+ tr("Error Loading iTunes Library"),
118+ tr("There was an error loading your iTunes library. Some of "
119+ "your iTunes tracks or playlists may not have loaded."));
120 }
121
122 //Sort the playlists since in iTunes they are sorted, too.
123- list.sort();
124+ //list.sort();
125
126- m_childModel.setStringList(list);
127+ m_childModel.setStringList(m_playlists);
128 }
129
130- emit(showTrackModel(m_pTrackModelProxy));
131+ emit(showTrackModel(m_pITunesTrackModel));
132 }
133
134 void ITunesFeature::activateChild(const QModelIndex& index) {
135@@ -78,7 +73,7 @@
136 QString playlist = index.data().toString();
137 qDebug() << "Activating " << playlist;
138 m_pITunesPlaylistModel->setPlaylist(playlist);
139- emit(showTrackModel(m_pPlaylistModelProxy));
140+ emit(showTrackModel(m_pITunesPlaylistModel));
141 }
142
143 QAbstractItemModel* ITunesFeature::getChildModel() {
144@@ -106,3 +101,373 @@
145 bool ITunesFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) {
146 return false;
147 }
148+
149+QString ITunesFeature::getiTunesMusicPath() {
150+ QString musicFolder;
151+#if defined(__APPLE__)
152+ musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "/iTunes/iTunes Music Library.xml";
153+#elif defined(__WINDOWS__)
154+ musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "\\iTunes\\iTunes Music Library.xml";
155+#elif defined(__LINUX__)
156+ musicFolder = QDir::homePath() + "/iTunes Music Library.xml";
157+#else
158+ musicFolder = "";
159+#endif
160+ qDebug() << "ITunesLibrary=[" << musicFolder << "]";
161+ return musicFolder;
162+}
163+bool ITunesFeature::importLibrary(QString file) {
164+ //Delete all table entries of iTunes feature
165+ m_database.transaction();
166+ clearTable("itunes_playlist_tracks");
167+ clearTable("itunes_library");
168+ clearTable("itunes_playlists");
169+ m_database.commit();
170+
171+ qDebug() << "Import iTunes library";
172+
173+ m_database.transaction();
174+
175+ //Parse iTunes XML file using SAX (for performance)
176+ QFile itunes_file(file);
177+ if (!itunes_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
178+ qDebug() << "Cannot open iTunes music collection";
179+ return false;
180+ }
181+ QXmlStreamReader xml(&itunes_file);
182+
183+ while (!xml.atEnd()) {
184+ xml.readNext();
185+ if (xml.isStartElement()) {
186+ if (xml.name() == "key") {
187+ if (xml.readElementText() == "Tracks") {
188+ parseTracks(xml);
189+ qDebug() << "Finished Parsing TrackList";
190+ } else if (xml.readElementText() == "Playlists") {
191+ //parse all the playlists here and exit the loop afterwards
192+ parsePlaylists(xml);
193+ qDebug() << "Finished Parsing Playlists";
194+ }
195+ }
196+ }
197+ }
198+
199+ itunes_file.close();
200+
201+ // Even if an error occured, commit the transaction. The file may have been
202+ // half-parsed.
203+ m_database.commit();
204+ m_pITunesTrackModel->select();
205+
206+ if (xml.hasError()) {
207+ // do error handling
208+ qDebug() << "Cannot process iTunes music collection";
209+ qDebug() << "XML ERROR: " << xml.errorString();
210+ return false;
211+ }
212+ return true;
213+}
214+
215+void ITunesFeature::parseTracks(QXmlStreamReader &xml) {
216+ QSqlQuery query(m_database);
217+ query.prepare("INSERT INTO itunes_library (id, artist, title, album, year, genre, comment, tracknumber,"
218+ "bpm, bitrate,"
219+ "duration, location,"
220+ "rating ) "
221+ "VALUES (:id, :artist, :title, :album, :year, :genre, :comment, :tracknumber,"
222+ ":bpm, :bitrate,"
223+ ":duration, :location," ":rating )");
224+
225+
226+ bool in_container_dictionary = false;
227+ bool in_track_dictionary = false;
228+
229+ qDebug() << "Parse Tracks";
230+
231+ //read all sunsequent <dict> until we reach the closing ENTRY tag
232+ while (!xml.atEnd()) {
233+ xml.readNext();
234+
235+ if (xml.isStartElement()) {
236+ if (xml.name() == "dict") {
237+ if (!in_track_dictionary && !in_container_dictionary) {
238+ in_container_dictionary = true;
239+ continue;
240+ } else if (in_container_dictionary && !in_track_dictionary) {
241+ //We are in a <dict> tag that holds track information
242+ in_track_dictionary = true;
243+ //Parse track here
244+ parseTrack(xml, query);
245+ }
246+ }
247+ }
248+
249+ if (xml.isEndElement() && xml.name() == "dict") {
250+ if (in_track_dictionary && in_container_dictionary) {
251+ in_track_dictionary = false;
252+ continue;
253+ } else if (in_container_dictionary && !in_track_dictionary) {
254+ // Done parsing tracks.
255+ in_container_dictionary = false;
256+ break;
257+ }
258+ }
259+ }
260+}
261+
262+void ITunesFeature::parseTrack(QXmlStreamReader &xml, QSqlQuery &query) {
263+ //qDebug() << "----------------TRACK-----------------";
264+ int id = -1;
265+ QString title;
266+ QString artist;
267+ QString album;
268+ QString year;
269+ QString genre;
270+ QString location;
271+
272+ int bpm = 0;
273+ int bitrate = 0;
274+
275+ //duration of a track
276+ int playtime = 0;
277+ int rating = 0;
278+ QString comment;
279+ QString tracknumber;
280+
281+ while (!xml.atEnd()) {
282+ xml.readNext();
283+
284+ if (xml.isStartElement()) {
285+ if (xml.name() == "key") {
286+ QString key = xml.readElementText();
287+ QString content = "";
288+
289+ if (xml.readNextStartElement()) {
290+ content = xml.readElementText();
291+ }
292+
293+ //qDebug() << "Key: " << key << " Content: " << content;
294+
295+ if (key == "Track ID") {
296+ id = content.toInt();
297+ continue;
298+ }
299+ if (key == "Name") {
300+ title = content;
301+ continue;
302+ }
303+ if (key == "Artist") {
304+ artist = content;
305+ continue;
306+ }
307+ if (key == "Album") {
308+ album = content;
309+ continue;
310+ }
311+ if (key == "Genre") {
312+ genre = content;
313+ continue;
314+ }
315+ if (key == "BPM") {
316+ bpm = content.toInt();
317+ continue;
318+ }
319+ if (key == "Bit Rate") {
320+ bitrate = content.toInt();
321+ continue;
322+ }
323+ if (key == "Comments") {
324+ comment = content;
325+ continue;
326+ }
327+ if (key == "Total Time") {
328+ playtime = (content.toInt() / 1000);
329+ continue;
330+ }
331+ if (key == "Year") {
332+ year = content;
333+ continue;
334+ }
335+ if (key == "Location") {
336+ QByteArray strlocbytes = content.toUtf8();
337+ location = QUrl::fromEncoded(strlocbytes).toLocalFile();
338+ /*
339+ * Strip the crappy file://localhost/ from the URL and
340+ * format URL as in method ITunesTrackModel::parseTrackNode(QDomNode songNode)
341+ */
342+#if defined(__WINDOWS__)
343+ location.remove("//localhost/");
344+#else
345+ location.remove("//localhost");
346+#endif
347+ continue;
348+ }
349+ if (key == "Track Number") {
350+ tracknumber = content;
351+ continue;
352+ }
353+ if (key == "Rating") {
354+ //value is an integer and ranges from 0 to 100
355+ rating = (content.toInt() / 20);
356+ continue;
357+ }
358+ }
359+ }
360+ //exit loop on closing </dict>
361+ if (xml.isEndElement() && xml.name() == "dict") {
362+ break;
363+ }
364+ }
365+ /* If we reach the end of <dict>
366+ * Save parsed track to database
367+ */
368+ query.bindValue(":id", id);
369+ query.bindValue(":artist", artist);
370+ query.bindValue(":title", title);
371+ query.bindValue(":album", album);
372+ query.bindValue(":genre", genre);
373+ query.bindValue(":year", year);
374+ query.bindValue(":duration", playtime);
375+ query.bindValue(":location", location);
376+ query.bindValue(":rating", rating);
377+ query.bindValue(":comment", comment);
378+ query.bindValue(":tracknumber", tracknumber);
379+ query.bindValue(":bpm", bpm);
380+ query.bindValue(":bitrate", bitrate);
381+
382+ bool success = query.exec();
383+
384+ if (!success) {
385+ qDebug() << "SQL Error in itunesfeature.cpp: line" << __LINE__ << " " << query.lastError();
386+ return;
387+ }
388+}
389+
390+void ITunesFeature::parsePlaylists(QXmlStreamReader &xml) {
391+ qDebug() << "Parse Playlists";
392+
393+ QSqlQuery query_insert_to_playlists(m_database);
394+ query_insert_to_playlists.prepare("INSERT INTO itunes_playlists (id, name) "
395+ "VALUES (:id, :name)");
396+
397+ QSqlQuery query_insert_to_playlist_tracks(m_database);
398+ query_insert_to_playlist_tracks.prepare("INSERT INTO itunes_playlist_tracks (playlist_id, track_id) "
399+ "VALUES (:playlist_id, :track_id)");
400+
401+ while (!xml.atEnd()) {
402+ xml.readNext();
403+ //We process and iterate the <dict> tags holding playlist summary information here
404+ if (xml.isStartElement() && xml.name() == "dict") {
405+ parsePlaylist(xml, query_insert_to_playlists, query_insert_to_playlist_tracks);
406+ continue;
407+ }
408+ if (xml.isEndElement()) {
409+ if (xml.name() == "array")
410+ break;
411+ }
412+ }
413+}
414+
415+void ITunesFeature::parsePlaylist(QXmlStreamReader &xml, QSqlQuery &query_insert_to_playlists,
416+ QSqlQuery &query_insert_to_playlist_tracks) {
417+ //qDebug() << "Parse Playlist";
418+
419+ QString playlistname;
420+ int playlist_id = -1;
421+ int track_reference = -1;
422+ //indicates that we haven't found the <
423+ bool isSystemPlaylist = false;
424+
425+ QString key;
426+
427+
428+ //We process and iterate the <dict> tags holding playlist summary information here
429+ while (!xml.atEnd()) {
430+ xml.readNext();
431+
432+ if (xml.isStartElement()) {
433+
434+ if (xml.name() == "key") {
435+ QString key = xml.readElementText();
436+ /*
437+ * The rules are processed in sequence
438+ * That is, XML is ordered.
439+ * For iTunes Playlist names are always followed by the ID.
440+ * Afterwars the playlist entries occur
441+ */
442+ if (key == "Name") {
443+ xml.readNextStartElement();
444+ playlistname = xml.readElementText();
445+ continue;
446+ }
447+ //When parsing the ID, the playlistname has already been found
448+ if (key == "Playlist ID") {
449+ xml.readNextStartElement();
450+ playlist_id = xml.readElementText().toInt();
451+ continue;
452+ }
453+ //Hide playlists that are system playlists
454+ if (key == "Master" || key == "Movies" || key == "TV Shows" || key == "Music" ||
455+ key == "Books" || key == "Purchased") {
456+ isSystemPlaylist = true;
457+ continue;
458+ }
459+
460+ if (key == "Playlist Items") {
461+ //if the playlist is prebuild don't hit the database
462+ if (isSystemPlaylist) continue;
463+ query_insert_to_playlists.bindValue(":id", playlist_id);
464+ query_insert_to_playlists.bindValue(":name", playlistname);
465+
466+ bool success = query_insert_to_playlists.exec();
467+ if (!success) {
468+ qDebug() << "SQL Error in ITunesTableModel.cpp: line" << __LINE__
469+ << " " << query_insert_to_playlists.lastError();
470+ return;
471+ }
472+ //for the child model
473+ m_playlists << playlistname;
474+
475+ }
476+ /*
477+ * When processing playlist entries, playlist name and id have already been processed and persisted
478+ */
479+ if (key == "Track ID") {
480+ track_reference = -1;
481+
482+ xml.readNextStartElement();
483+ track_reference = xml.readElementText().toInt();
484+
485+ query_insert_to_playlist_tracks.bindValue(":playlist_id", playlist_id);
486+ query_insert_to_playlist_tracks.bindValue(":track_id", track_reference);
487+
488+ //Insert tracks if we are not in a pre-build playlist
489+ if (!isSystemPlaylist && !query_insert_to_playlist_tracks.exec()) {
490+ qDebug() << "SQL Error in ITunesFeature.cpp: line" << __LINE__ << " "
491+ << query_insert_to_playlist_tracks.lastError();
492+ qDebug() << "trackid" << track_reference;
493+ qDebug() << "playlistname; " << playlistname;
494+ qDebug() << "-----------------";
495+ }
496+ }
497+ }
498+ }
499+ if (xml.isEndElement()) {
500+ if (xml.name() == "array") {
501+ //qDebug() << "exit playlist";
502+ break;
503+ }
504+ }
505+ }
506+}
507+
508+void ITunesFeature::clearTable(QString table_name) {
509+ QSqlQuery query(m_database);
510+ query.prepare("delete from "+table_name);
511+ bool success = query.exec();
512+
513+ if (!success)
514+ qDebug() << "Could not delete remove old entries from table " << table_name << " : " << query.lastError();
515+ else
516+ qDebug() << "iTunes table entries of '" << table_name <<"' have been cleared.";
517+}
518
519=== modified file 'mixxx/src/library/itunesfeature.h'
520--- mixxx/src/library/itunesfeature.h 2009-12-23 03:07:11 +0000
521+++ mixxx/src/library/itunesfeature.h 2010-11-21 15:57:13 +0000
522@@ -5,18 +5,20 @@
523 #define ITUNESBOXFEATURE_H
524
525 #include <QStringListModel>
526+#include <QtSql>
527
528 #include "library/libraryfeature.h"
529+#include "library/trackcollection.h"
530
531 //class ITunesPlaylistModel;
532 class ITunesTrackModel;
533 class ITunesPlaylistModel;
534-class ProxyTrackModel;
535+
536
537 class ITunesFeature : public LibraryFeature {
538 Q_OBJECT
539 public:
540- ITunesFeature(QObject* parent = NULL);
541+ ITunesFeature(QObject* parent, TrackCollection* pTrackCollection);
542 virtual ~ITunesFeature();
543 static bool isSupported();
544
545@@ -30,18 +32,28 @@
546
547 QAbstractItemModel* getChildModel();
548
549-public slots:
550+ public slots:
551 void activate();
552 void activateChild(const QModelIndex& index);
553 void onRightClick(const QPoint& globalPos);
554 void onRightClickChild(const QPoint& globalPos, QModelIndex index);
555
556-private:
557+ private:
558+ static QString getiTunesMusicPath();
559+ bool importLibrary(QString file);
560+ void parseTracks(QXmlStreamReader &xml);
561+ void parseTrack(QXmlStreamReader &xml, QSqlQuery &query);
562+ void parsePlaylists(QXmlStreamReader &xml);
563+ void parsePlaylist(QXmlStreamReader &xml, QSqlQuery &query1, QSqlQuery &query2);
564+ void clearTable(QString table_name);
565+
566 ITunesTrackModel* m_pITunesTrackModel;
567 ITunesPlaylistModel* m_pITunesPlaylistModel;
568- ProxyTrackModel* m_pTrackModelProxy;
569- ProxyTrackModel* m_pPlaylistModelProxy;
570 QStringListModel m_childModel;
571+ QStringList m_playlists;
572+ TrackCollection* m_pTrackCollection;
573+ QSqlDatabase &m_database;
574+ bool m_isActivated;
575 };
576
577 #endif /* ITUNESFEATURE_H */
578
579=== modified file 'mixxx/src/library/itunesplaylistmodel.cpp'
580--- mixxx/src/library/itunesplaylistmodel.cpp 2010-11-16 21:01:45 +0000
581+++ mixxx/src/library/itunesplaylistmodel.cpp 2010-11-21 15:57:13 +0000
582@@ -1,258 +1,187 @@
583-// itunesplaylistmodel.cpp
584-// Created 12/19/2009 by RJ Ryan (rryan@mit.edu)
585-// Adapted from Philip Whelan's RhythmboxPlaylistModel
586-
587 #include <QtCore>
588 #include <QtGui>
589 #include <QtSql>
590-#include <QtDebug>
591-#include <QtXmlPatterns/QXmlQuery>
592-
593+#include "library/trackcollection.h"
594 #include "library/itunesplaylistmodel.h"
595-#include "library/itunestrackmodel.h"
596-#include "xmlparse.h"
597-#include "trackinfoobject.h"
598-#include "defs.h"
599
600 #include "mixxxutils.cpp"
601
602-ITunesPlaylistModel::ITunesPlaylistModel(ITunesTrackModel *pTrackModel) :
603- TrackModel(QSqlDatabase::database("QSQLITE"), "mixxx.db.model.itunes_playlist"),
604- m_pTrackModel(pTrackModel),
605- m_sCurrentPlaylist("")
606-{
607-
608-}
609-
610-ITunesPlaylistModel::~ITunesPlaylistModel()
611-{
612-}
613-
614-Qt::ItemFlags ITunesPlaylistModel::flags ( const QModelIndex & index ) const
615-{
616- Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
617-
618- if (!index.isValid())
619- return Qt::ItemIsEnabled;
620-
621- defaultFlags |= Qt::ItemIsDragEnabled;
622-
623- return defaultFlags;
624-}
625-
626-QMimeData* ITunesPlaylistModel::mimeData(const QModelIndexList &indexes) const {
627- QMimeData *mimeData = new QMimeData();
628- QList<QUrl> urls;
629-
630- //Ok, so the list of indexes we're given contains separates indexes for
631- //each column, so even if only one row is selected, we'll have like 7 indexes.
632- //We need to only count each row once:
633- QList<int> rows;
634-
635- foreach (QModelIndex index, indexes) {
636- if (index.isValid()) {
637- if (!rows.contains(index.row())) {
638- rows.push_back(index.row());
639- QUrl url = QUrl::fromLocalFile(getTrackLocation(index));
640- if (!url.isValid())
641- qDebug() << "ERROR invalid url\n";
642- else
643- urls.append(url);
644- }
645- }
646- }
647- mimeData->setUrls(urls);
648- return mimeData;
649-}
650-
651-QVariant ITunesPlaylistModel::data ( const QModelIndex & index, int role ) const
652-{
653- if ( m_sCurrentPlaylist == "" )
654- return QVariant();
655-
656- if (!index.isValid())
657- return QVariant();
658-
659- // OwenB - attempting to make this more efficient, don't create a new
660- // TIO for every row
661- if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
662- // get track id
663- QList<QString> playlistTrackList = m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist];
664- QString id = playlistTrackList.at(index.row());
665-
666- // use this to get DOM node from the TrackModel
667- QDomNode songNode = m_pTrackModel->getTrackNodeById(id);
668-
669- // use node to return the data item that was asked for.
670- return m_pTrackModel->getTrackColumnData(songNode, index);
671- }
672-
673- return QVariant();
674-}
675-
676-bool ITunesPlaylistModel::isColumnInternal(int column) {
677- return false;
678-}
679-bool ITunesPlaylistModel::isColumnHiddenByDefault(int column) {
680- return false;
681-}
682-
683-QVariant ITunesPlaylistModel::headerData ( int section, Qt::Orientation orientation, int role ) const
684-{
685- /* Only respond to requests for column header display names */
686- if ( role != Qt::DisplayRole )
687- return QVariant();
688-
689- if (orientation == Qt::Horizontal)
690- {
691- switch (section)
692- {
693- case ITunesPlaylistModel::COLUMN_ARTIST:
694- return QString(tr("Artist"));
695-
696- case ITunesPlaylistModel::COLUMN_TITLE:
697- return QString(tr("Title"));
698-
699- case ITunesPlaylistModel::COLUMN_ALBUM:
700- return QString(tr("Album"));
701-
702- case ITunesPlaylistModel::COLUMN_DATE:
703- return QString(tr("Date"));
704-
705- case ITunesPlaylistModel::COLUMN_BPM:
706- return QString(tr("BPM"));
707-
708- case ITunesPlaylistModel::COLUMN_GENRE:
709- return QString(tr("Genre"));
710-
711- case ITunesPlaylistModel::COLUMN_LOCATION:
712- return QString(tr("Location"));
713-
714- case ITunesPlaylistModel::COLUMN_DURATION:
715- return QString(tr("Duration"));
716-
717- default:
718- return QString(tr("Unknown"));
719- }
720- }
721-
722- return QVariant();
723-}
724-
725-int ITunesPlaylistModel::rowCount ( const QModelIndex & parent ) const
726-{
727- // FIXME
728- //if ( !m_mPlaylists.containts(m_sCurrentPlaylist))
729- // return 0;
730-
731- if (!m_pTrackModel || m_sCurrentPlaylist == "" )
732- return 0;
733-
734- return m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist].size();
735-}
736-
737-int ITunesPlaylistModel::columnCount(const QModelIndex& parent) const
738-{
739- if (parent != QModelIndex()) //Some weird thing for table-based models.
740- return 0;
741- return ITunesPlaylistModel::NUM_COLUMNS;
742+ITunesPlaylistModel::ITunesPlaylistModel(QObject* parent,
743+ TrackCollection* pTrackCollection)
744+ : TrackModel(pTrackCollection->getDatabase(),
745+ "mixxx.db.model.itunes_playlist"),
746+ BaseSqlTableModel(parent, pTrackCollection, pTrackCollection->getDatabase()),
747+ m_pTrackCollection(pTrackCollection),
748+ m_database(m_pTrackCollection->getDatabase())
749+{
750+ connect(this, SIGNAL(doSearch(const QString&)), this, SLOT(slotSearch(const QString&)));
751+}
752+
753+ITunesPlaylistModel::~ITunesPlaylistModel() {
754 }
755
756 bool ITunesPlaylistModel::addTrack(const QModelIndex& index, QString location)
757 {
758- //Should do nothing... hmmm
759+
760 return false;
761 }
762
763-/** Removes a track from the library track collection. */
764-void ITunesPlaylistModel::removeTrack(const QModelIndex& index)
765-{
766- //Should do nothing... hmmm
767-}
768-
769-void ITunesPlaylistModel::removeTracks(const QModelIndexList& indices)
770-{
771-}
772-
773-void ITunesPlaylistModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex)
774-{
775- //Should do nothing... hmmm
776-}
777-
778-QString ITunesPlaylistModel::getTrackLocation(const QModelIndex& index) const
779-{
780- TrackPointer track = getTrack(index);
781- QString location = track->getLocation();
782- // track is auto-deleted
783+TrackPointer ITunesPlaylistModel::getTrack(const QModelIndex& index) const
784+{
785+ QString artist = index.sibling(index.row(), fieldIndex("artist")).data().toString();
786+ QString title = index.sibling(index.row(), fieldIndex("title")).data().toString();
787+ QString album = index.sibling(index.row(), fieldIndex("album")).data().toString();
788+ QString year = index.sibling(index.row(), fieldIndex("year")).data().toString();
789+ QString genre = index.sibling(index.row(), fieldIndex("genre")).data().toString();
790+ float bpm = index.sibling(index.row(), fieldIndex("bpm")).data().toString().toFloat();
791+
792+ QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
793+
794+ TrackInfoObject* pTrack = new TrackInfoObject(location);
795+ pTrack->setArtist(artist);
796+ pTrack->setTitle(title);
797+ pTrack->setAlbum(album);
798+ pTrack->setYear(year);
799+ pTrack->setGenre(genre);
800+ pTrack->setBpm(bpm);
801+
802+ return TrackPointer(pTrack, &QObject::deleteLater);
803+}
804+
805+QString ITunesPlaylistModel::getTrackLocation(const QModelIndex& index) const {
806+ QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
807 return location;
808 }
809
810-TrackPointer ITunesPlaylistModel::getTrack(const QModelIndex& index) const
811-{
812- int row = index.row();
813-
814- if (!m_pTrackModel ||
815- !m_pTrackModel->m_mPlaylists.contains(m_sCurrentPlaylist)) {
816- return TrackPointer();
817- }
818-
819- // Qt should do this by reference for us so we aren't actually making a copy
820- // of the list.
821- QList<QString> songIds = m_pTrackModel->m_mPlaylists[m_sCurrentPlaylist];
822-
823- if (row < 0 || row >= songIds.length()) {
824- return TrackPointer();
825- }
826-
827- return m_pTrackModel->getTrackById(songIds.at(row));
828-}
829-
830-QItemDelegate* ITunesPlaylistModel::delegateForColumn(const int i) {
831- return NULL;
832-}
833-
834-QList<QString> ITunesPlaylistModel::getPlaylists()
835-{
836- if (!m_pTrackModel) {
837- return QList<QString>();
838- }
839- return m_pTrackModel->m_mPlaylists.keys();
840-}
841-
842-int ITunesPlaylistModel::numPlaylists() {
843- if (!m_pTrackModel) {
844- return 0;
845- }
846- return m_pTrackModel->m_mPlaylists.size();
847-}
848-
849-QString ITunesPlaylistModel::playlistTitle(int n) {
850- if (!m_pTrackModel) {
851- return "";
852- }
853- return m_pTrackModel->m_mPlaylists.keys().at(n);
854-}
855-
856-void ITunesPlaylistModel::setPlaylist(QString playlist)
857-{
858- if (m_pTrackModel && m_pTrackModel->m_mPlaylists.contains(playlist))
859- m_sCurrentPlaylist = playlist;
860- else
861- m_sCurrentPlaylist = "";
862-
863- // force the layout to update
864- emit(layoutChanged());
865-}
866-
867-void ITunesPlaylistModel::search(const QString& searchText)
868-{
869+void ITunesPlaylistModel::removeTrack(const QModelIndex& index) {
870+
871+}
872+
873+void ITunesPlaylistModel::removeTracks(const QModelIndexList& indices) {
874+
875+}
876+
877+void ITunesPlaylistModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) {
878+
879+}
880+
881+void ITunesPlaylistModel::search(const QString& searchText) {
882+ // qDebug() << "ITunesPlaylistModel::search()" << searchText
883+ // << QThread::currentThread();
884+ emit(doSearch(searchText));
885+}
886+
887+void ITunesPlaylistModel::slotSearch(const QString& searchText) {
888+ if (!m_currentSearch.isNull() && m_currentSearch == searchText)
889+ return;
890 m_currentSearch = searchText;
891+
892+ QString filter;
893+ QSqlField search("search", QVariant::String);
894+ search.setValue("%" + searchText + "%");
895+ QString escapedText = database().driver()->formatValue(search);
896+ filter = "(artist LIKE " + escapedText + " OR " +
897+ "album LIKE " + escapedText + " OR " +
898+ "title LIKE " + escapedText + ")";
899+ setFilter(filter);
900 }
901
902 const QString ITunesPlaylistModel::currentSearch() {
903 return m_currentSearch;
904 }
905
906-const QList<int>& ITunesPlaylistModel::searchColumns() const {
907- return m_pTrackModel->searchColumns();
908+bool ITunesPlaylistModel::isColumnInternal(int column) {
909+ if (column == fieldIndex(LIBRARYTABLE_ID) ||
910+ column == fieldIndex(LIBRARYTABLE_MIXXXDELETED) ||
911+ column == fieldIndex(TRACKLOCATIONSTABLE_FSDELETED) ||
912+ column == fieldIndex("name") ||
913+ column == fieldIndex("track_id"))
914+ return true;
915+ return false;
916+}
917+
918+QMimeData* ITunesPlaylistModel::mimeData(const QModelIndexList &indexes) const {
919+ return NULL;
920+}
921+
922+
923+QItemDelegate* ITunesPlaylistModel::delegateForColumn(const int i) {
924+ return NULL;
925+}
926+
927+TrackModel::CapabilitiesFlags ITunesPlaylistModel::getCapabilities() const {
928+ return NULL;
929+}
930+
931+Qt::ItemFlags ITunesPlaylistModel::flags(const QModelIndex &index) const {
932+ return readOnlyFlags(index);
933+}
934+
935+void ITunesPlaylistModel::setPlaylist(QString playlist_path) {
936+ int playlistId = -1;
937+ QSqlQuery finder_query(m_database);
938+ finder_query.prepare("SELECT id from itunes_playlists where name='"+playlist_path+"'");
939+
940+ if(finder_query.exec()){
941+ while (finder_query.next()) {
942+ playlistId = finder_query.value(finder_query.record().indexOf("id")).toInt();
943+ }
944+ }
945+ else
946+ qDebug() << "SQL Error in ITunesPlaylistModel.cpp: line" << __LINE__ << " " << finder_query.lastError();
947+
948+
949+ QString playlistID = "ITunesPlaylist_" + QString("%1").arg(playlistId);
950+ //Escape the playlist name
951+ QSqlDriver* driver = m_pTrackCollection->getDatabase().driver();
952+ QSqlField playlistNameField("name", QVariant::String);
953+ playlistNameField.setValue(playlistID);
954+
955+ QSqlQuery query(m_database);
956+ query.prepare("CREATE TEMPORARY VIEW IF NOT EXISTS "+ driver->formatValue(playlistNameField) + " AS "
957+ "SELECT "
958+ "itunes_library.id,"
959+ "itunes_library.artist,"
960+ "itunes_library.title,"
961+ "itunes_library.album,"
962+ "itunes_library.year,"
963+ "itunes_library.genre,"
964+ "itunes_library.tracknumber,"
965+ "itunes_library.location,"
966+ "itunes_library.comment,"
967+ "itunes_library.rating,"
968+ "itunes_library.duration,"
969+ "itunes_library.bitrate,"
970+ "itunes_library.bpm,"
971+ "itunes_playlist_tracks.track_id, "
972+ "itunes_playlists.name "
973+ "FROM itunes_library "
974+ "INNER JOIN itunes_playlist_tracks "
975+ "ON itunes_playlist_tracks.track_id = itunes_library.id "
976+ "INNER JOIN itunes_playlists "
977+ "ON itunes_playlist_tracks.playlist_id = itunes_playlists.id "
978+ "where itunes_playlists.name='"+playlist_path+"'"
979+ );
980+
981+
982+ if (!query.exec()) {
983+
984+ qDebug() << "Error creating temporary view for itunes playlists. ITunesPlaylistModel --> line: " << __LINE__ << " " << query.lastError();
985+ qDebug() << "Executed Query: " << query.executedQuery();
986+ return;
987+ }
988+ setTable(playlistID);
989+
990+ //removeColumn(fieldIndex("track_id"));
991+ //removeColumn(fieldIndex("name"));
992+ //removeColumn(fieldIndex("id"));
993+
994+ slotSearch("");
995+
996+ select(); //Populate the data model.
997+ initHeaderData();
998+}
999+
1000+bool ITunesPlaylistModel::isColumnHiddenByDefault(int column) {
1001+ return false;
1002 }
1003
1004=== modified file 'mixxx/src/library/itunesplaylistmodel.h'
1005--- mixxx/src/library/itunesplaylistmodel.h 2010-11-16 21:01:45 +0000
1006+++ mixxx/src/library/itunesplaylistmodel.h 2010-11-21 15:57:13 +0000
1007@@ -1,77 +1,53 @@
1008-// itunestrackmodel.h
1009-// Created 12/19/2009 by RJ Ryan (rryan@mit.edu)
1010-// Adapted from Phillip Whelan's RhythmboxPlaylistModel
1011-
1012-#ifndef ITUNESPLAYLISTMODEL_H
1013-#define ITUNESPLAYLISTMODEL_H
1014+#ifndef ITUNES_PLAYLIST_MODEL_H
1015+#define ITUNES_PLAYLIST_MODEL_H
1016
1017 #include <QtSql>
1018-#include <QtXml>
1019+#include <QItemDelegate>
1020+#include <QtCore>
1021 #include "trackmodel.h"
1022-
1023-class ITunesTrackModel;
1024-
1025-class ITunesPlaylistModel : public QAbstractTableModel, public TrackModel {
1026-
1027- enum Columns {
1028- COLUMN_ARTIST = 0,
1029- COLUMN_TITLE,
1030- COLUMN_ALBUM,
1031- COLUMN_DATE,
1032- COLUMN_BPM,
1033- COLUMN_GENRE,
1034- COLUMN_LOCATION,
1035- COLUMN_DURATION,
1036- NUM_COLUMNS
1037- };
1038-
1039+#include "library/basesqltablemodel.h"
1040+#include "library/librarytablemodel.h"
1041+#include "library/dao/playlistdao.h"
1042+#include "library/dao/trackdao.h"
1043+
1044+class TrackCollection;
1045+
1046+class ITunesPlaylistModel : public BaseSqlTableModel, public virtual TrackModel
1047+{
1048 Q_OBJECT
1049-
1050 public:
1051- ITunesPlaylistModel(ITunesTrackModel* pTrackModel);
1052+ ITunesPlaylistModel(QObject* parent, TrackCollection* pTrackCollection);
1053 virtual ~ITunesPlaylistModel();
1054
1055- //QAbstractTableModel stuff
1056- virtual Qt::ItemFlags flags(const QModelIndex& index) const;
1057- virtual QVariant data(const QModelIndex & index,
1058- int role = Qt::DisplayRole) const;
1059- virtual QMimeData* mimeData(const QModelIndexList &indexes) const;
1060- virtual QVariant headerData(int section,
1061- Qt::Orientation orientation,
1062- int role = Qt::DisplayRole) const;
1063- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
1064- virtual int columnCount(const QModelIndex& parent) const;
1065-
1066- //Playlist Model stuff
1067 virtual TrackPointer getTrack(const QModelIndex& index) const;
1068 virtual QString getTrackLocation(const QModelIndex& index) const;
1069 virtual void search(const QString& searchText);
1070 virtual const QString currentSearch();
1071- virtual const QList<int>& searchColumns() const;
1072 virtual bool isColumnInternal(int column);
1073 virtual bool isColumnHiddenByDefault(int column);
1074 virtual void removeTrack(const QModelIndex& index);
1075 virtual void removeTracks(const QModelIndexList& indices);
1076 virtual bool addTrack(const QModelIndex& index, QString location);
1077- virtual void moveTrack(const QModelIndex& sourceIndex,
1078- const QModelIndex& destIndex);
1079+ virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
1080+
1081+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
1082+ QMimeData* mimeData(const QModelIndexList &indexes) const;
1083+
1084 QItemDelegate* delegateForColumn(const int i);
1085-
1086- virtual QList<QString> getPlaylists();
1087- virtual void setPlaylist(QString playlist);
1088-
1089- int numPlaylists();
1090- QString playlistTitle(int n);
1091+ TrackModel::CapabilitiesFlags getCapabilities() const;
1092+ /** sets the playlist **/
1093+ void setPlaylist(QString path_name);
1094+
1095+ private slots:
1096+ void slotSearch(const QString& searchText);
1097
1098 signals:
1099- void startedLoading();
1100- void progressLoading(QString path);
1101- void finishedLoading();
1102+ void doSearch(const QString& searchText);
1103
1104 private:
1105- ITunesTrackModel* m_pTrackModel;
1106- QString m_sCurrentPlaylist;
1107+ TrackCollection* m_pTrackCollection;
1108+ QSqlDatabase &m_database;
1109 QString m_currentSearch;
1110 };
1111
1112-#endif /* ITUNESPLAYLISTMODEL_H */
1113+#endif /* ITUNES_PLAYLIST_MODEL_H */
1114
1115=== modified file 'mixxx/src/library/itunestrackmodel.cpp'
1116--- mixxx/src/library/itunestrackmodel.cpp 2010-11-16 21:01:45 +0000
1117+++ mixxx/src/library/itunestrackmodel.cpp 2010-11-21 15:57:13 +0000
1118@@ -1,316 +1,118 @@
1119 #include <QtCore>
1120 #include <QtGui>
1121 #include <QtSql>
1122-#include <QtDebug>
1123-#include <QSettings>
1124-#include <QRegExp>
1125-#include <QtXmlPatterns/QXmlQuery>
1126-#include <QDesktopServices>
1127-
1128-#include "itunestrackmodel.h"
1129-#include "xmlparse.h"
1130-#include "trackinfoobject.h"
1131-#include "defs.h"
1132-#include "soundsourceproxy.h"
1133+#include "library/trackcollection.h"
1134+#include "library/itunestrackmodel.h"
1135
1136 #include "mixxxutils.cpp"
1137
1138-ITunesTrackModel::ITunesTrackModel()
1139- : AbstractXmlTrackModel("mixxx.db.model.itunes") {
1140- QXmlQuery query;
1141- QString res, playlistRes;
1142- QDomDocument itunesdb;
1143-
1144- QRegExp supportedFileRegex(SoundSourceProxy::supportedFileExtensionsRegex(),
1145- Qt::CaseInsensitive);
1146-
1147- QString itunesXmlPath = getiTunesMusicPath();
1148-
1149- QFile db(itunesXmlPath);
1150- if (!db.exists())
1151- return;
1152-
1153- if (!db.open(QIODevice::ReadOnly | QIODevice::Text))
1154- return;
1155-
1156- // Workaround for Bug #501916. Read the file, convert it to UTF-8 and then
1157- // load it.
1158- QByteArray db_bytes_utf8 = QString(db.readAll()).toUtf8();
1159- QBuffer buffer(&db_bytes_utf8);
1160- if (!buffer.open(QIODevice::ReadOnly | QIODevice::Text))
1161- return;
1162-
1163- /*
1164- * Use QXmlQuery to execute an XPath query. We add the version to
1165- * the XPath query to make sure it is the schema we expect.
1166- *
1167- * TODO: filter /key='Track Type'/string='URL' (remote) files
1168- */
1169- query.setFocus(&buffer);
1170- query.setQuery("/plist[@version='1.0']/dict[key='Tracks']/dict/dict");
1171- if ( ! query.isValid())
1172- return;
1173-
1174- query.evaluateTo(&res);
1175-
1176- // Both ITunes and Rhythmbox parsing should occur in something completely
1177- // separate from the TrackModels, but since we're in a rush, we're just
1178- // going to do playlist parsing here so we don't have to re-open the file
1179- // again.
1180- query.setQuery("/plist[@version='1.0']/dict[key='Playlists']/array/dict");
1181- if (query.isValid()) {
1182- query.evaluateTo(&playlistRes);
1183- }
1184- db.close();
1185-
1186- /*
1187- * Parse the result as an XML file. These shennanigans actually
1188- * reduce the load time from a minute to a matter of seconds.
1189- */
1190- itunesdb.setContent("<plist version='1.0'>" + res + "</plist>");
1191- m_trackNodes = itunesdb.elementsByTagName("dict");
1192-
1193- for (int i = 0; i < m_trackNodes.count(); i++) {
1194- QDomNode n = m_trackNodes.at(i);
1195- QString trackId = findValueByKey(n, "Track ID");
1196- QString location = findValueByKey(n, "Location");
1197-
1198- // Skip files we cannot play.
1199- if (location.count(supportedFileRegex) == 0) {
1200- continue;
1201- }
1202-
1203- m_mTracksByLocation[location] = n;
1204- m_mTracksById[trackId] = n;
1205- }
1206-
1207- // Now process the playlist data.
1208- QDomDocument playlistdb;
1209- playlistdb.setContent("<plist version='1.0'>" + playlistRes + "</plist>");
1210- QDomNodeList playlistNodes = playlistdb.documentElement().childNodes();
1211- for (int i = 0; i < playlistNodes.count(); i++) {
1212- QDomNode n = playlistNodes.at(i);
1213-
1214- // Get the playlist name
1215- QString name = findValueByKey(n, "Name");
1216- qDebug() << "Found playlist" << name;
1217-
1218- // Skip invisible playlists
1219- QDomElement visible = findNodeByKey(n, "Visible");
1220- if (!visible.isNull() && visible.tagName() == "false") {
1221- continue;
1222- }
1223-
1224- // Now traverse to the items list
1225- QDomElement items = findNodeByKey(n, "Playlist Items");
1226- if (items.isNull()) {
1227- qDebug() << name << "has no items";
1228- continue;
1229- }
1230-
1231- // Now extract the song ids that are members of this playlist
1232- QDomNodeList playlistEntries = items.childNodes();
1233- QList<QString> playlistSongIds;
1234- for (int j = 0; j < playlistEntries.count(); j++) {
1235- QDomNode entry = playlistEntries.at(j);
1236- QString trackId = findValueByKey(entry, "Track ID");
1237-
1238- // If the track index does not contain the given track, that means
1239- // either the track is not playable in Mixxx, or the XML is
1240- // inconsistent. In this case, don't show the track in the playlist.
1241- if (!m_mTracksById.contains(trackId)) {
1242- continue;
1243- }
1244-
1245- playlistSongIds.append(trackId);
1246- }
1247-
1248- // If there were no playable items in the playlist, don't show it.
1249- if (playlistSongIds.count() == 0) {
1250- qDebug() << name << "has no items";
1251- continue;
1252- }
1253-
1254- // TODO(XXX) : Do we need to handle duplicate playlist names? If there
1255- // are duplicates, only one will show up if we do this.
1256- m_mPlaylists[name] = playlistSongIds;
1257- }
1258-
1259- qDebug() << "ITunesTrackModel: m_entryNodes size is" << m_trackNodes.size();
1260-
1261- addColumnName(ITunesTrackModel::COLUMN_ARTIST, "Artist");
1262- addColumnName(ITunesTrackModel::COLUMN_TITLE, "Title");
1263- addColumnName(ITunesTrackModel::COLUMN_ALBUM, "Album");
1264- addColumnName(ITunesTrackModel::COLUMN_DATE, "Date");
1265- addColumnName(ITunesTrackModel::COLUMN_BPM, "BPM");
1266- addColumnName(ITunesTrackModel::COLUMN_GENRE, "Genre");
1267- addColumnName(ITunesTrackModel::COLUMN_LOCATION, "Location");
1268- addColumnName(ITunesTrackModel::COLUMN_DURATION, "Duration");
1269-
1270- addSearchColumn(ITunesTrackModel::COLUMN_ARTIST);
1271- addSearchColumn(ITunesTrackModel::COLUMN_TITLE);
1272- addSearchColumn(ITunesTrackModel::COLUMN_ALBUM);
1273- addSearchColumn(ITunesTrackModel::COLUMN_GENRE);
1274- addSearchColumn(ITunesTrackModel::COLUMN_LOCATION);
1275-}
1276-
1277-ITunesTrackModel::~ITunesTrackModel()
1278-{
1279-
1280-}
1281-
1282-QItemDelegate* ITunesTrackModel::delegateForColumn(const int i)
1283-{
1284- return NULL;
1285-}
1286-
1287-QString ITunesTrackModel::findValueByKey(QDomNode dictNode, QString key) const
1288-{
1289- QDomElement curElem;
1290-
1291- curElem = dictNode.firstChildElement("key");
1292- while(!curElem.isNull()) {
1293- if ( curElem.text() == key ) {
1294- QDomElement value;
1295- value = curElem.nextSiblingElement();
1296- QString textValue = value.text();
1297- //Either iTunes lies about the text encoding in its XML file or
1298- //Qt misdetects it. We workaround this explicitly sayi it's UTF-8.
1299- textValue = QString::fromUtf8(textValue.toAscii().data(), textValue.size());
1300- return textValue;
1301- }
1302-
1303- curElem = curElem.nextSiblingElement("key");
1304- }
1305-
1306- return QString();
1307-}
1308-
1309-QDomElement ITunesTrackModel::findNodeByKey(QDomNode dictNode, QString key) const
1310-{
1311- QDomElement curElem;
1312-
1313- curElem = dictNode.firstChildElement("key");
1314- while(!curElem.isNull()) {
1315- if ( curElem.text() == key ) {
1316- return curElem.nextSiblingElement();
1317- }
1318- curElem = curElem.nextSiblingElement("key");
1319- }
1320-
1321- return QDomElement();
1322-}
1323-
1324-QVariant ITunesTrackModel::getTrackColumnData(QDomNode songNode, const QModelIndex& index) const
1325-{
1326- QVariant value;
1327- switch (index.column()) {
1328- case ITunesTrackModel::COLUMN_ARTIST:
1329- return findValueByKey(songNode, "Artist");
1330- case ITunesTrackModel::COLUMN_TITLE:
1331- return findValueByKey(songNode, "Name");
1332- case ITunesTrackModel::COLUMN_ALBUM:
1333- return findValueByKey(songNode,"Album");
1334- case ITunesTrackModel::COLUMN_DATE:
1335- return findValueByKey(songNode,"Year");
1336- case ITunesTrackModel::COLUMN_BPM:
1337- return findValueByKey(songNode,"BPM");
1338- case ITunesTrackModel::COLUMN_GENRE:
1339- return findValueByKey(songNode,"Genre");
1340- case ITunesTrackModel::COLUMN_LOCATION: {
1341- /*
1342- * Strip the crappy file://localhost/ from the URL and
1343- * format URL as in method ITunesTrackModel::parseTrackNode(QDomNode songNode)
1344- */
1345- QString strloc = findValueByKey(songNode,"Location");
1346- QByteArray strlocbytes = strloc.toUtf8();
1347- QString location = QUrl::fromEncoded(strlocbytes).toLocalFile();
1348-#if defined(__WINDOWS__)
1349- return location.remove("//localhost/");
1350-#else
1351- return location.remove("//localhost");
1352-#endif
1353- }
1354-
1355- case ITunesTrackModel::COLUMN_DURATION:
1356- value = findValueByKey(songNode,"Total Time");
1357- if (qVariantCanConvert<int>(value)) {
1358- value = MixxxUtils::millisecondsToMinutes(qVariantValue<int>(value));
1359- }
1360- return value;
1361- default:
1362- return QVariant();
1363- }
1364+ITunesTrackModel::ITunesTrackModel(QObject* parent,
1365+ TrackCollection* pTrackCollection)
1366+ : TrackModel(pTrackCollection->getDatabase(),
1367+ "mixxx.db.model.itunes"),
1368+ BaseSqlTableModel(parent, pTrackCollection, pTrackCollection->getDatabase()),
1369+ m_pTrackCollection(pTrackCollection),
1370+ m_database(m_pTrackCollection->getDatabase()) {
1371+ connect(this, SIGNAL(doSearch(const QString&)), this, SLOT(slotSearch(const QString&)));
1372+ setTable("itunes_library");
1373+ initHeaderData();
1374+}
1375+
1376+ITunesTrackModel::~ITunesTrackModel() {
1377+}
1378+
1379+bool ITunesTrackModel::addTrack(const QModelIndex& index, QString location) {
1380+ return false;
1381+}
1382+
1383+TrackPointer ITunesTrackModel::getTrack(const QModelIndex& index) const {
1384+ QString artist = index.sibling(index.row(), fieldIndex("artist")).data().toString();
1385+ QString title = index.sibling(index.row(), fieldIndex("title")).data().toString();
1386+ QString album = index.sibling(index.row(), fieldIndex("album")).data().toString();
1387+ QString year = index.sibling(index.row(), fieldIndex("year")).data().toString();
1388+ QString genre = index.sibling(index.row(), fieldIndex("genre")).data().toString();
1389+ float bpm = index.sibling(index.row(), fieldIndex("bpm")).data().toString().toFloat();
1390+
1391+ QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
1392+
1393+ TrackInfoObject* pTrack = new TrackInfoObject(location);
1394+ pTrack->setArtist(artist);
1395+ pTrack->setTitle(title);
1396+ pTrack->setAlbum(album);
1397+ pTrack->setYear(year);
1398+ pTrack->setGenre(genre);
1399+ pTrack->setBpm(bpm);
1400+
1401+ return TrackPointer(pTrack, &QObject::deleteLater);
1402+}
1403+
1404+QString ITunesTrackModel::getTrackLocation(const QModelIndex& index) const {
1405+ QString location = index.sibling(index.row(), fieldIndex("location")).data().toString();
1406+ return location;
1407+}
1408+
1409+void ITunesTrackModel::removeTrack(const QModelIndex& index) {
1410+
1411+}
1412+
1413+void ITunesTrackModel::removeTracks(const QModelIndexList& indices) {
1414+
1415+}
1416+
1417+void ITunesTrackModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) {
1418+
1419+}
1420+
1421+void ITunesTrackModel::search(const QString& searchText) {
1422+ // qDebug() << "ITunesTrackModel::search()" << searchText
1423+ // << QThread::currentThread();
1424+ emit(doSearch(searchText));
1425+}
1426+
1427+void ITunesTrackModel::slotSearch(const QString& searchText) {
1428+ if (!m_currentSearch.isNull() && m_currentSearch == searchText)
1429+ return;
1430+ m_currentSearch = searchText;
1431+
1432+ QString filter;
1433+ QSqlField search("search", QVariant::String);
1434+ search.setValue("%" + searchText + "%");
1435+ QString escapedText = database().driver()->formatValue(search);
1436+ filter = "(artist LIKE " + escapedText + " OR " +
1437+ "album LIKE " + escapedText + " OR " +
1438+ "title LIKE " + escapedText + ")";
1439+ setFilter(filter);
1440+
1441+}
1442+
1443+const QString ITunesTrackModel::currentSearch() {
1444+ return m_currentSearch;
1445 }
1446
1447 bool ITunesTrackModel::isColumnInternal(int column) {
1448+ if (column == fieldIndex(LIBRARYTABLE_ID) ||
1449+ column == fieldIndex(LIBRARYTABLE_MIXXXDELETED) ||
1450+ column == fieldIndex(TRACKLOCATIONSTABLE_FSDELETED))
1451+ return true;
1452 return false;
1453 }
1454+
1455+QMimeData* ITunesTrackModel::mimeData(const QModelIndexList &indexes) const {
1456+ return NULL;
1457+}
1458+
1459+QItemDelegate* ITunesTrackModel::delegateForColumn(const int i) {
1460+ return NULL;
1461+}
1462+
1463+TrackModel::CapabilitiesFlags ITunesTrackModel::getCapabilities() const {
1464+ return NULL;
1465+}
1466+
1467+Qt::ItemFlags ITunesTrackModel::flags(const QModelIndex &index) const {
1468+ return readOnlyFlags(index);
1469+}
1470+
1471 bool ITunesTrackModel::isColumnHiddenByDefault(int column) {
1472 return false;
1473 }
1474-
1475-TrackPointer ITunesTrackModel::parseTrackNode(QDomNode songNode) const
1476-{
1477- QString strloc = findValueByKey(songNode,"Location");
1478- QByteArray strlocbytes = strloc.toUtf8();
1479- QUrl location = QUrl::fromEncoded(strlocbytes);
1480-
1481- QString trackLocation;
1482- //Strip the crappy localhost from the URL since Qt barfs on this :(
1483-#if defined(__WINDOWS__)
1484- trackLocation = location.toLocalFile().remove("//localhost/");
1485-#else
1486- trackLocation = location.toLocalFile().remove("//localhost");
1487-#endif
1488- //pTrack->setLocation(QUrl(findValueByKey(songNode,"Location")).toLocalFile());
1489-
1490- TrackInfoObject* pTrack = new TrackInfoObject(trackLocation);
1491-
1492- pTrack->setArtist(findValueByKey(songNode, "Artist"));
1493- pTrack->setTitle(findValueByKey(songNode, "Name"));
1494- pTrack->setAlbum(findValueByKey(songNode,"Album"));
1495- pTrack->setYear(findValueByKey(songNode,"Year"));
1496- pTrack->setGenre(findValueByKey(songNode,"Genre"));
1497- pTrack->setBpm(findValueByKey(songNode,"BPM").toFloat());
1498-
1499- // ITunes stores time in total milliseconds
1500- pTrack->setDuration(findValueByKey(songNode,"Total Time").toInt() / 1000);
1501-
1502- // Let Qt handle deleting the track since it isn't owned by the library.
1503- return TrackPointer(pTrack, &QObject::deleteLater);
1504-}
1505-
1506-TrackPointer ITunesTrackModel::getTrackById(QString id) {
1507- if (!m_mTracksById.contains(id)) {
1508- return TrackPointer();
1509- }
1510- return parseTrackNode(m_mTracksById[id]);
1511-}
1512-
1513-QString ITunesTrackModel::getiTunesMusicPath() {
1514- QString musicFolder;
1515-#if defined(__APPLE__)
1516- musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "/iTunes/iTunes Music Library.xml";
1517-#elif defined(__WINDOWS__)
1518- musicFolder = QDesktopServices::storageLocation(QDesktopServices::MusicLocation) + "\\iTunes\\iTunes Music Library.xml";
1519-#elif defined(__LINUX__)
1520- musicFolder = QDir::homePath() + "/.itunes.xml";
1521-#else
1522- musicFolder = "";
1523-#endif
1524- qDebug() << "ITunesLibrary=[" << musicFolder << "]";
1525- return musicFolder;
1526-}
1527-
1528-//OwenB - for use by the playlistmodel
1529-QDomNode ITunesTrackModel::getTrackNodeById(const QString& id) const
1530-{
1531- if ( !m_mTracksById.contains(id))
1532- return QDomNode();
1533-
1534- QDomNode songNode = m_mTracksById[id];
1535- return songNode;
1536-}
1537-
1538-
1539
1540=== modified file 'mixxx/src/library/itunestrackmodel.h'
1541--- mixxx/src/library/itunestrackmodel.h 2010-11-16 21:01:45 +0000
1542+++ mixxx/src/library/itunestrackmodel.h 2010-11-21 15:57:13 +0000
1543@@ -1,82 +1,51 @@
1544-/***************************************************************************
1545- ituestrackmodel.h
1546- -------------------
1547- begin : 8/28/2009
1548- copyright : (C) 2009 Phillip Whelan
1549- email : pwhelan@mixxx.org
1550-***************************************************************************/
1551-
1552-/***************************************************************************
1553- * *
1554- * This program is free software; you can redistribute it and/or modify *
1555- * it under the terms of the GNU General Public License as published by *
1556- * the Free Software Foundation; either version 2 of the License, or *
1557- * (at your option) any later version. *
1558- * *
1559- ***************************************************************************/
1560-
1561-#ifndef ITUNESTRACKMODEL_H
1562-#define ITUNESTRACKMODEL_H
1563+#ifndef ITUNES_TABLE_MODEL_H
1564+#define ITUNES_TABLE_MODEL_H
1565
1566 #include <QtSql>
1567-#include <QtXml>
1568+#include <QItemDelegate>
1569+#include <QtCore>
1570 #include "trackmodel.h"
1571-#include "abstractxmltrackmodel.h"
1572-#include "itunesplaylistmodel.h"
1573-
1574-class QSqlDatabase;
1575-
1576-/**
1577- @author Phillip Whelan
1578-*/
1579-
1580-// Darn Jobs and his capitalization!
1581-class ITunesTrackModel : public AbstractXmlTrackModel
1582+#include "library/basesqltablemodel.h"
1583+#include "library/librarytablemodel.h"
1584+#include "library/dao/playlistdao.h"
1585+#include "library/dao/trackdao.h"
1586+
1587+class TrackCollection;
1588+
1589+class ITunesTrackModel : public BaseSqlTableModel, public virtual TrackModel
1590 {
1591- enum Columns {
1592- COLUMN_ARTIST = 0,
1593- COLUMN_TITLE,
1594- COLUMN_ALBUM,
1595- COLUMN_DATE,
1596- COLUMN_BPM,
1597- COLUMN_GENRE,
1598- COLUMN_LOCATION,
1599- COLUMN_DURATION,
1600- NUM_COLUMNS
1601- };
1602-
1603 Q_OBJECT
1604 public:
1605- ITunesTrackModel();
1606+ ITunesTrackModel(QObject* parent, TrackCollection* pTrackCollection);
1607 virtual ~ITunesTrackModel();
1608- virtual QItemDelegate* delegateForColumn(const int i);
1609+
1610+ virtual TrackPointer getTrack(const QModelIndex& index) const;
1611+ virtual QString getTrackLocation(const QModelIndex& index) const;
1612+ virtual void search(const QString& searchText);
1613+ virtual const QString currentSearch();
1614 virtual bool isColumnInternal(int column);
1615 virtual bool isColumnHiddenByDefault(int column);
1616- static QString getiTunesMusicPath();
1617- QDomNode getTrackNodeById(const QString& ) const;
1618-
1619- public slots:
1620+ virtual void removeTrack(const QModelIndex& index);
1621+ virtual void removeTracks(const QModelIndexList& indices);
1622+ virtual bool addTrack(const QModelIndex& index, QString location);
1623+ virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex);
1624+
1625+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
1626+ QMimeData* mimeData(const QModelIndexList &indexes) const;
1627+
1628+ QItemDelegate* delegateForColumn(const int i);
1629+ TrackModel::CapabilitiesFlags getCapabilities() const;
1630+
1631+ private slots:
1632+ void slotSearch(const QString& searchText);
1633
1634 signals:
1635- void startedLoading();
1636- void progressLoading(QString path);
1637- void finishedLoading();
1638-
1639- protected:
1640- virtual TrackPointer parseTrackNode(QDomNode node) const;
1641- /* Implemented by AbstractXmlTrackModel implementations to return the data for song columns */
1642- virtual QVariant getTrackColumnData(QDomNode node, const QModelIndex& index) const;
1643- /* Called by AbstractXmlTrackModel implementations to enumerate their columns */
1644+ void doSearch(const QString& searchText);
1645
1646 private:
1647- QString findValueByKey(QDomNode dictNode, QString key) const;
1648- QDomElement findNodeByKey(QDomNode dictNode, QString key) const;
1649- TrackPointer getTrackById(QString id);
1650-
1651- QHash<QString, QDomNode> m_mTracksById;
1652- QHash<QString, QList<QString> > m_mPlaylists;
1653-
1654- friend class ITunesPlaylistModel;
1655+ TrackCollection* m_pTrackCollection;
1656+ QSqlDatabase &m_database;
1657+ QString m_currentSearch;
1658 };
1659
1660-#endif
1661+#endif /* ITUNES_TABLE_MODEL_H */
1662
1663=== modified file 'mixxx/src/library/library.cpp'
1664--- mixxx/src/library/library.cpp 2010-10-24 09:50:11 +0000
1665+++ mixxx/src/library/library.cpp 2010-11-21 15:57:13 +0000
1666@@ -62,7 +62,7 @@
1667 if (RhythmboxFeature::isSupported())
1668 addFeature(new RhythmboxFeature(this));
1669 if (ITunesFeature::isSupported())
1670- addFeature(new ITunesFeature(this));
1671+ addFeature(new ITunesFeature(this, m_pTrackCollection));
1672
1673 //Show the promo tracks view on first run, otherwise show the library
1674 if (firstRun) {
1675
1676=== modified file 'mixxx/src/library/trackcollection.cpp'
1677--- mixxx/src/library/trackcollection.cpp 2010-11-17 21:02:24 +0000
1678+++ mixxx/src/library/trackcollection.cpp 2010-11-21 15:57:13 +0000
1679@@ -68,7 +68,7 @@
1680 return false;
1681 }
1682
1683- int requiredSchemaVersion = 7;
1684+ int requiredSchemaVersion = 8;
1685 if (!SchemaManager::upgradeToSchemaVersion(m_pConfig, m_db,
1686 requiredSchemaVersion)) {
1687 QMessageBox::warning(0, tr("Cannot upgrade database schema"),

Subscribers

People subscribed via source and target branches