Mixxx
|
00001 #include <QMessageBox> 00002 #include <QtDebug> 00003 #include <QStringList> 00004 00005 #include "library/rhythmbox/rhythmboxtrackmodel.h" 00006 #include "library/rhythmbox/rhythmboxplaylistmodel.h" 00007 #include "library/rhythmbox/rhythmboxfeature.h" 00008 #include "library/treeitem.h" 00009 00010 RhythmboxFeature::RhythmboxFeature(QObject* parent, TrackCollection* pTrackCollection) 00011 : LibraryFeature(parent), 00012 m_pTrackCollection(pTrackCollection), 00013 m_cancelImport(false) { 00014 QString tableName = "rhythmbox_library"; 00015 QString idColumn = "id"; 00016 QStringList columns; 00017 columns << "id" 00018 << "artist" 00019 << "title" 00020 << "album" 00021 << "year" 00022 << "genre" 00023 << "tracknumber" 00024 << "location" 00025 << "comment" 00026 << "rating" 00027 << "duration" 00028 << "bitrate" 00029 << "bpm"; 00030 pTrackCollection->addTrackSource(QString("rhythmbox"), QSharedPointer<BaseTrackCache>( 00031 new BaseTrackCache(m_pTrackCollection, tableName, idColumn, 00032 columns, false))); 00033 00034 m_pRhythmboxTrackModel = new RhythmboxTrackModel(this, m_pTrackCollection); 00035 m_pRhythmboxPlaylistModel = new RhythmboxPlaylistModel(this, m_pTrackCollection); 00036 m_isActivated = false; 00037 m_title = tr("Rhythmbox"); 00038 00039 if (!m_database.isOpen()) { 00040 m_database = QSqlDatabase::addDatabase("QSQLITE", "RHYTHMBOX_SCANNER"); 00041 m_database.setHostName("localhost"); 00042 m_database.setDatabaseName(MIXXX_DB_PATH); 00043 m_database.setUserName("mixxx"); 00044 m_database.setPassword("mixxx"); 00045 00046 //Open the database connection in this thread. 00047 if (!m_database.open()) { 00048 qDebug() << "Failed to open database for Rhythmbox scanner." << m_database.lastError(); 00049 } 00050 } 00051 connect(&m_track_watcher, SIGNAL(finished()), 00052 this, SLOT(onTrackCollectionLoaded()), 00053 Qt::QueuedConnection); 00054 00055 } 00056 00057 RhythmboxFeature::~RhythmboxFeature() { 00058 // stop import thread, if still running 00059 m_cancelImport = true; 00060 m_track_future.waitForFinished(); 00061 delete m_pRhythmboxTrackModel; 00062 delete m_pRhythmboxPlaylistModel; 00063 } 00064 00065 bool RhythmboxFeature::isSupported() { 00066 return (QFile::exists(QDir::homePath() + "/.gnome2/rhythmbox/rhythmdb.xml") || 00067 QFile::exists(QDir::homePath() + "/.local/share/rhythmbox/rhythmdb.xml")); 00068 } 00069 00070 QVariant RhythmboxFeature::title() { 00071 return m_title; 00072 } 00073 00074 QIcon RhythmboxFeature::getIcon() { 00075 return QIcon(":/images/library/ic_library_rhythmbox.png"); 00076 } 00077 00078 TreeItemModel* RhythmboxFeature::getChildModel() { 00079 return &m_childModel; 00080 } 00081 00082 void RhythmboxFeature::activate() { 00083 qDebug() << "RhythmboxFeature::activate()"; 00084 00085 if(!m_isActivated){ 00086 m_isActivated = true; 00087 /* Ususally the maximum number of threads 00088 * is > 2 depending on the CPU cores 00089 * Unfortunately, within VirtualBox 00090 * the maximum number of allowed threads 00091 * is 1 at all times We'll need to increase 00092 * the number to > 1, otherwise importing the music collection 00093 * takes place when the GUI threads terminates, i.e., on 00094 * Mixxx shutdown. 00095 */ 00096 QThreadPool::globalInstance()->setMaxThreadCount(4); //Tobias decided to use 4 00097 m_track_future = QtConcurrent::run(this, &RhythmboxFeature::importMusicCollection); 00098 m_track_watcher.setFuture(m_track_future); 00099 m_title = "(loading) Rhythmbox"; 00100 //calls a slot in the sidebar model such that 'Rhythmbox (isLoading)' is displayed. 00101 emit (featureIsLoading(this)); 00102 } 00103 else 00104 emit(showTrackModel(m_pRhythmboxTrackModel)); 00105 00106 } 00107 00108 void RhythmboxFeature::activateChild(const QModelIndex& index) { 00109 //qDebug() << "RhythmboxFeature::activateChild()" << index; 00110 QString playlist = index.data().toString(); 00111 qDebug() << "Activating " << playlist; 00112 m_pRhythmboxPlaylistModel->setPlaylist(playlist); 00113 emit(showTrackModel(m_pRhythmboxPlaylistModel)); 00114 } 00115 00116 void RhythmboxFeature::onRightClick(const QPoint& globalPos) { 00117 } 00118 00119 void RhythmboxFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) { 00120 } 00121 00122 bool RhythmboxFeature::dropAccept(QUrl url) { 00123 return false; 00124 } 00125 00126 bool RhythmboxFeature::dropAcceptChild(const QModelIndex& index, QUrl url) { 00127 return false; 00128 } 00129 00130 bool RhythmboxFeature::dragMoveAccept(QUrl url) { 00131 return false; 00132 } 00133 00134 bool RhythmboxFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) { 00135 return false; 00136 } 00137 00138 TreeItem* RhythmboxFeature::importMusicCollection() 00139 { 00140 qDebug() << "importMusicCollection Thread Id: " << QThread::currentThread(); 00141 /* 00142 * Try and open the Rhythmbox DB. An API call which tells us where 00143 * the file is would be nice. 00144 */ 00145 QFile db(QDir::homePath() + "/.gnome2/rhythmbox/rhythmdb.xml"); 00146 if ( ! db.exists()) { 00147 db.setFileName(QDir::homePath() + "/.local/share/rhythmbox/rhythmdb.xml"); 00148 if ( ! db.exists()) 00149 return false; 00150 } 00151 00152 if (!db.open(QIODevice::ReadOnly | QIODevice::Text)) 00153 return false; 00154 00155 //Delete all table entries of Traktor feature 00156 m_database.transaction(); 00157 clearTable("rhythmbox_playlist_tracks"); 00158 clearTable("rhythmbox_library"); 00159 clearTable("rhythmbox_playlists"); 00160 m_database.commit(); 00161 00162 m_database.transaction(); 00163 QSqlQuery query(m_database); 00164 query.prepare("INSERT INTO rhythmbox_library (artist, title, album, year, genre, comment, tracknumber," 00165 "bpm, bitrate," 00166 "duration, location," 00167 "rating ) " 00168 "VALUES (:artist, :title, :album, :year, :genre, :comment, :tracknumber," 00169 ":bpm, :bitrate," 00170 ":duration, :location, :rating )"); 00171 00172 00173 QXmlStreamReader xml(&db); 00174 while (!xml.atEnd() && !m_cancelImport) { 00175 xml.readNext(); 00176 if (xml.isStartElement() && xml.name() == "entry") { 00177 QXmlStreamAttributes attr = xml.attributes(); 00178 //Check if we really parse a track and not album art information 00179 if(attr.value("type").toString() == "song"){ 00180 importTrack(xml, query); 00181 } 00182 } 00183 } 00184 m_database.commit(); 00185 00186 if (xml.hasError()) { 00187 // do error handling 00188 qDebug() << "Cannot process Rhythmbox music collection"; 00189 qDebug() << "XML ERROR: " << xml.errorString(); 00190 return false; 00191 } 00192 00193 db.close(); 00194 if (m_cancelImport) { 00195 return NULL; 00196 } 00197 return importPlaylists(); 00198 } 00199 00200 TreeItem* RhythmboxFeature::importPlaylists() 00201 { 00202 QFile db(QDir::homePath() + "/.gnome2/rhythmbox/playlists.xml"); 00203 if ( ! db.exists()) { 00204 db.setFileName(QDir::homePath() + "/.local/share/rhythmbox/playlists.xml"); 00205 if (!db.exists()) 00206 return NULL; 00207 } 00208 //Open file 00209 if (!db.open(QIODevice::ReadOnly | QIODevice::Text)) 00210 return NULL; 00211 00212 QSqlQuery query_insert_to_playlists(m_database); 00213 query_insert_to_playlists.prepare("INSERT INTO rhythmbox_playlists (id, name) " 00214 "VALUES (:id, :name)"); 00215 00216 QSqlQuery query_insert_to_playlist_tracks(m_database); 00217 query_insert_to_playlist_tracks.prepare( 00218 "INSERT INTO rhythmbox_playlist_tracks (playlist_id, track_id, position) " 00219 "VALUES (:playlist_id, :track_id, :position)"); 00220 //The tree structure holding the playlists 00221 TreeItem* rootItem = new TreeItem(); 00222 00223 QXmlStreamReader xml(&db); 00224 while (!xml.atEnd() && !m_cancelImport) { 00225 xml.readNext(); 00226 if (xml.isStartElement() && xml.name() == "playlist") { 00227 QXmlStreamAttributes attr = xml.attributes(); 00228 00229 //Only parse non build-in playlists 00230 if(attr.value("type").toString() == "static"){ 00231 QString playlist_name = attr.value("name").toString(); 00232 00233 //Construct the childmodel 00234 TreeItem * item = new TreeItem(playlist_name,playlist_name, this, rootItem); 00235 rootItem->appendChild(item); 00236 00237 //Execute SQL statement 00238 query_insert_to_playlists.bindValue(":name", playlist_name); 00239 00240 bool success = query_insert_to_playlists.exec(); 00241 if(!success){ 00242 qDebug() << "SQL Error in RhythmboxFeature.cpp: line" << __LINE__ << " " 00243 << query_insert_to_playlists.lastError(); 00244 return NULL; 00245 } 00246 //get playlist_id 00247 int playlist_id = query_insert_to_playlists.lastInsertId().toInt(); 00248 00249 //Process playlist entries 00250 importPlaylist(xml, query_insert_to_playlist_tracks, playlist_id); 00251 00252 } 00253 } 00254 } 00255 00256 m_database.commit(); 00257 00258 00259 if (xml.hasError()) { 00260 // do error handling 00261 qDebug() << "Cannot process Rhythmbox music collection"; 00262 qDebug() << "XML ERROR: " << xml.errorString(); 00263 return NULL; 00264 } 00265 db.close(); 00266 00267 return rootItem; 00268 00269 } 00270 00271 void RhythmboxFeature::importTrack(QXmlStreamReader &xml, QSqlQuery &query) 00272 { 00273 QString title; 00274 QString artist; 00275 QString album; 00276 QString year; 00277 QString genre; 00278 QString location; 00279 00280 int bpm = 0; 00281 int bitrate = 0; 00282 00283 //duration of a track 00284 int playtime = 0; 00285 int rating = 0; 00286 QString comment; 00287 QString tracknumber; 00288 00289 while (!xml.atEnd()) { 00290 xml.readNext(); 00291 if (xml.isStartElement()) { 00292 if(xml.name() == "title"){ 00293 title = xml.readElementText(); 00294 continue; 00295 } 00296 if(xml.name() == "artist"){ 00297 artist = xml.readElementText(); 00298 continue; 00299 } 00300 if(xml.name() == "genre"){ 00301 genre = xml.readElementText(); 00302 continue; 00303 } 00304 if(xml.name() == "album"){ 00305 album = xml.readElementText(); 00306 continue; 00307 } 00308 if(xml.name() == "track-number"){ 00309 tracknumber = xml.readElementText(); 00310 continue; 00311 } 00312 if(xml.name() == "duration"){ 00313 playtime = xml.readElementText().toInt();; 00314 continue; 00315 } 00316 if(xml.name() == "bitrate"){ 00317 bitrate = xml.readElementText().toInt(); 00318 continue; 00319 } 00320 if(xml.name() == "beats-per-minute"){ 00321 bpm = xml.readElementText().toInt(); 00322 continue; 00323 } 00324 if(xml.name() == "comment"){ 00325 comment = xml.readElementText(); 00326 continue; 00327 } 00328 if(xml.name() == "location"){ 00329 location = xml.readElementText(); 00330 location.remove("file://"); 00331 QByteArray strlocbytes = location.toUtf8(); 00332 QUrl locationUrl = QUrl::fromEncoded(strlocbytes); 00333 location = locationUrl.toLocalFile(); 00334 continue; 00335 } 00336 } 00337 //exit the loop if we reach the closing <entry> tag 00338 if (xml.isEndElement() && xml.name() == "entry") { 00339 break; 00340 } 00341 00342 } 00343 query.bindValue(":artist", artist); 00344 query.bindValue(":title", title); 00345 query.bindValue(":album", album); 00346 query.bindValue(":genre", genre); 00347 query.bindValue(":year", year); 00348 query.bindValue(":duration", playtime); 00349 query.bindValue(":location", location); 00350 query.bindValue(":rating", rating); 00351 query.bindValue(":comment", comment); 00352 query.bindValue(":tracknumber", tracknumber); 00353 query.bindValue(":bpm", bpm); 00354 query.bindValue(":bitrate", bitrate); 00355 00356 bool success = query.exec(); 00357 00358 if (!success) { 00359 qDebug() << "SQL Error in rhythmboxfeature.cpp: line" << __LINE__ << " " << query.lastError(); 00360 return; 00361 } 00362 00363 } 00364 00366 void RhythmboxFeature::importPlaylist(QXmlStreamReader &xml, QSqlQuery &query_insert_to_playlist_tracks, int playlist_id) 00367 { 00368 int playlist_position = 1; 00369 while(!xml.atEnd()) 00370 { 00371 //read next XML element 00372 xml.readNext(); 00373 if(xml.isStartElement() && xml.name() == "location") 00374 { 00375 QString location = xml.readElementText(); 00376 location.remove("file://"); 00377 QByteArray strlocbytes = location.toUtf8(); 00378 QUrl locationUrl = QUrl::fromEncoded(strlocbytes); 00379 location = locationUrl.toLocalFile(); 00380 00381 //get the ID of the file in the rhythmbox_library table 00382 int track_id = -1; 00383 QSqlQuery finder_query(m_database); 00384 finder_query.prepare("select id from rhythmbox_library where location=:path"); 00385 finder_query.bindValue(":path", location); 00386 bool success = finder_query.exec(); 00387 00388 00389 if(success){ 00390 while (finder_query.next()) { 00391 track_id = finder_query.value(finder_query.record().indexOf("id")).toInt(); 00392 } 00393 } 00394 else 00395 qDebug() << "SQL Error in RhythmboxFeature.cpp: line" << __LINE__ << " " << finder_query.lastError(); 00396 00397 query_insert_to_playlist_tracks.bindValue(":playlist_id", playlist_id); 00398 query_insert_to_playlist_tracks.bindValue(":track_id", track_id); 00399 query_insert_to_playlist_tracks.bindValue(":position", playlist_position++); 00400 success = query_insert_to_playlist_tracks.exec(); 00401 00402 if(!success){ 00403 qDebug() << "SQL Error in RhythmboxFeature.cpp: line" << __LINE__ << " " 00404 << query_insert_to_playlist_tracks.lastError(); 00405 qDebug() << "trackid" << track_id; 00406 qDebug() << "playlis ID " << playlist_id; 00407 qDebug() << "-----------------"; 00408 } 00409 } 00410 // Exit the the loop if we reach the closing <playlist> tag 00411 if (xml.isEndElement() && xml.name() == "playlist") { 00412 break; 00413 } 00414 } 00415 } 00416 00417 void RhythmboxFeature::clearTable(QString table_name) 00418 { 00419 qDebug() << "clearTable Thread Id: " << QThread::currentThread(); 00420 QSqlQuery query(m_database); 00421 query.prepare("delete from "+table_name); 00422 bool success = query.exec(); 00423 00424 if (!success) { 00425 qDebug() << "Could not delete remove old entries from table " 00426 << table_name << " : " << query.lastError(); 00427 } else { 00428 qDebug() << "Rhythmbox table entries of '" << table_name 00429 << "' have been cleared."; 00430 } 00431 } 00432 00433 void RhythmboxFeature::onTrackCollectionLoaded() { 00434 TreeItem* root = m_track_future.result(); 00435 if (root) { 00436 m_childModel.setRootItem(root); 00437 00438 // Tell the rhythmbox track source that it should re-build its index. 00439 m_pTrackCollection->getTrackSource("rhythmbox")->buildIndex(); 00440 00441 //m_pRhythmboxTrackModel->select(); 00442 } else { 00443 qDebug() << "Rhythmbox Playlists loaded: false"; 00444 } 00445 00446 // calls a slot in the sidebarmodel such that 'isLoading' is removed from 00447 // the feature title. 00448 m_title = tr("Rhythmbox"); 00449 emit(featureLoadingFinished(this)); 00450 activate(); 00451 } 00452 void RhythmboxFeature::onLazyChildExpandation(const QModelIndex &index){ 00453 //Nothing to do because the childmodel is not of lazy nature. 00454 } 00455