Merge lp:~mixxxdevelopers/mixxx/iTunes2 into lp:~mixxxdevelopers/mixxx/trunk
- iTunes2
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
RJ Skerry-Ryan | Approve | ||
Review via email: mp+41225@code.launchpad.net |
Commit message
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....
RJ Skerry-Ryan (rryan) wrote : | # |
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)
- 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.
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.
- 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.
RAFFI TEA (raffitea) wrote : | # |
Tanks RJ for your review.
- 2567. By RJ Skerry-Ryan
-
Oops, fix multi-line string.
RJ Skerry-Ryan (rryan) wrote : | # |
I'll let you do the honors -- go ahead and merge it into trunk :)
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?
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:/
> You are reviewing the proposed merge of lp:~mixxxdevelopers/mixxx/iTunes2
> into lp:mixxx.
>
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., setTrackChangeA
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 BaseSqlTableMod
What do you think RJ or Albert?
Preview Diff
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"), |
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.