File indexing completed on 2024-05-19 04:49:45
0001 /**************************************************************************************** 0002 * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "RhythmboxProvider.h" 0018 0019 #include "RhythmboxTrack.h" 0020 #include "core/support/Debug.h" 0021 0022 #include <QFile> 0023 #include <QMutexLocker> 0024 #include <QTemporaryFile> 0025 #include <QXmlStreamReader> 0026 #include <QXmlStreamWriter> 0027 0028 using namespace StatSyncing; 0029 0030 RhythmboxProvider::RhythmboxProvider( const QVariantMap &config, 0031 ImporterManager *importer ) 0032 : ImporterProvider( config, importer ) 0033 { 0034 } 0035 0036 RhythmboxProvider::~RhythmboxProvider() 0037 { 0038 } 0039 0040 qint64 0041 RhythmboxProvider::reliableTrackMetaData() const 0042 { 0043 return Meta::valTitle | Meta::valArtist | Meta::valAlbum | Meta::valTrackNr 0044 | Meta::valDiscNr; 0045 } 0046 0047 qint64 0048 RhythmboxProvider::writableTrackStatsData() const 0049 { 0050 return Meta::valRating | Meta::valLastPlayed | Meta::valPlaycount; 0051 } 0052 0053 QSet<QString> 0054 RhythmboxProvider::artists() 0055 { 0056 readXml( QString() ); 0057 QSet<QString> result; 0058 result.swap( m_artists ); 0059 return result; 0060 } 0061 0062 TrackList 0063 RhythmboxProvider::artistTracks( const QString &artistName ) 0064 { 0065 readXml( artistName ); 0066 TrackList result; 0067 result.swap( m_artistTracks ); 0068 return result; 0069 } 0070 0071 void 0072 RhythmboxProvider::readXml( const QString &byArtist ) 0073 { 0074 QFile dbFile( m_config.value( "dbPath" ).toString() ); 0075 if( dbFile.open( QIODevice::ReadOnly ) ) 0076 { 0077 QXmlStreamReader xml( &dbFile ); 0078 if( xml.readNextStartElement() ) 0079 { 0080 if( xml.name() == "rhythmdb" ) 0081 { 0082 if( xml.attributes().value("version") != "1.8" ) 0083 warning() << __PRETTY_FUNCTION__ << "unsupported database version"; 0084 0085 readRhythmdb( xml, byArtist ); 0086 } 0087 else 0088 xml.raiseError( "the database file is ill-formatted" ); 0089 } 0090 0091 if( xml.hasError() ) 0092 warning() << "There was an error reading" << dbFile.fileName() << ":" 0093 << xml.errorString(); 0094 } 0095 else 0096 warning() << __PRETTY_FUNCTION__ << "couldn't open" << dbFile.fileName(); 0097 } 0098 0099 void 0100 RhythmboxProvider::readRhythmdb( QXmlStreamReader &xml, const QString &byArtist ) 0101 { 0102 Q_ASSERT( xml.isStartElement() && xml.name() == "rhythmdb" ); 0103 0104 while( xml.readNextStartElement() ) 0105 { 0106 if( xml.name() == "entry" && xml.attributes().value( "type" ) == "song" ) 0107 readSong( xml, byArtist ); 0108 else 0109 xml.skipCurrentElement(); 0110 } 0111 } 0112 0113 void 0114 RhythmboxProvider::readSong( QXmlStreamReader &xml, const QString &byArtist ) 0115 { 0116 Q_ASSERT( xml.isStartElement() && xml.name() == "entry" ); 0117 0118 Meta::FieldHash metadata; 0119 QString currentArtist; 0120 QString location; 0121 0122 while( xml.readNextStartElement() ) 0123 { 0124 if( byArtist.isEmpty() && currentArtist.isEmpty() ) 0125 { 0126 if( xml.name() == "artist" ) 0127 currentArtist = readValue( xml ); 0128 else 0129 xml.skipCurrentElement(); 0130 } 0131 else if( currentArtist.isEmpty() || currentArtist == byArtist ) 0132 { 0133 if( xml.name() == "title" ) 0134 metadata.insert( Meta::valTitle, readValue( xml ) ); 0135 else if( xml.name() == "artist" ) 0136 { 0137 currentArtist = readValue( xml ); 0138 metadata.insert( Meta::valArtist, currentArtist ); 0139 } 0140 else if( xml.name() == "album" ) 0141 metadata.insert( Meta::valAlbum, readValue( xml ) ); 0142 else if( xml.name() == "track-number" ) 0143 metadata.insert( Meta::valTrackNr, readValue( xml ) ); 0144 else if( xml.name() == "disc-number" ) 0145 metadata.insert( Meta::valDiscNr, readValue( xml ) ); 0146 else if( xml.name() == "rating" ) 0147 metadata.insert( Meta::valRating, readValue( xml ) ); 0148 else if( xml.name() == "last-played" ) 0149 metadata.insert( Meta::valLastPlayed, readValue( xml ) ); 0150 else if( xml.name() == "play-count" ) 0151 metadata.insert( Meta::valPlaycount, readValue( xml ) ); 0152 else if( xml.name() == "location" ) 0153 location = readValue( xml ); 0154 else 0155 xml.skipCurrentElement(); 0156 } 0157 else 0158 xml.skipCurrentElement(); 0159 } 0160 0161 if( !byArtist.isEmpty() && currentArtist == byArtist ) 0162 { 0163 RhythmboxTrack *track = new RhythmboxTrack( location, metadata ); 0164 connect( track, &RhythmboxTrack::commitCalled, 0165 this, &RhythmboxProvider::trackUpdated, Qt::DirectConnection ); 0166 m_artistTracks << TrackPtr( track ); 0167 } 0168 else if( byArtist.isEmpty() ) 0169 m_artists << currentArtist; 0170 } 0171 0172 QString 0173 RhythmboxProvider::readValue( QXmlStreamReader &xml ) 0174 { 0175 return xml.readElementText(); 0176 } 0177 0178 void 0179 RhythmboxProvider::writeSong( QXmlStreamReader &reader, QXmlStreamWriter &writer, 0180 const QMap<QString, Meta::FieldHash> &dirtyData ) 0181 { 0182 Q_ASSERT( reader.isStartElement() && reader.name() == "entry" ); 0183 0184 Meta::FieldHash metadata; 0185 QString location; 0186 0187 writer.writeCurrentToken( reader ); 0188 while( !reader.isEndElement() || reader.name() != "entry" ) 0189 { 0190 reader.readNext(); 0191 0192 if( reader.error() ) 0193 { 0194 warning() << __PRETTY_FUNCTION__ << "Error reading song:" 0195 << reader.errorString(); 0196 return; 0197 } 0198 0199 if( reader.isWhitespace() ) 0200 continue; // autoformat will deal with whitespace for us 0201 0202 if( reader.isStartElement() ) 0203 { 0204 if( reader.name() == "rating" ) 0205 metadata.insert( Meta::valRating, readValue( reader ) ); 0206 else if( reader.name() == "last-played" ) 0207 metadata.insert( Meta::valLastPlayed, readValue( reader ) ); 0208 else if( reader.name() == "play-count" ) 0209 metadata.insert( Meta::valPlaycount, readValue( reader ) ); 0210 else if( reader.name() == "location" ) 0211 { 0212 location = readValue( reader ); 0213 writer.writeTextElement( "location", location ); 0214 } 0215 else 0216 writer.writeCurrentToken( reader ); 0217 } 0218 else if( !reader.isEndElement() || reader.name() != "entry" ) 0219 writer.writeCurrentToken( reader ); 0220 } 0221 0222 // Override read statistics 0223 if( dirtyData.contains( location ) ) 0224 metadata = dirtyData.value( location ); 0225 0226 if( metadata.value( Meta::valRating ).toInt() != 0 ) 0227 writer.writeTextElement( "rating", metadata.value( Meta::valRating ).toString() ); 0228 if( metadata.value( Meta::valLastPlayed ).toUInt() != 0 ) 0229 writer.writeTextElement( "last-played", 0230 metadata.value( Meta::valLastPlayed ).toString() ); 0231 if( metadata.value( Meta::valPlaycount ).toInt() != 0 ) 0232 writer.writeTextElement( "play-count", 0233 metadata.value( Meta::valPlaycount ).toString() ); 0234 0235 writer.writeCurrentToken( reader ); 0236 } 0237 0238 void 0239 RhythmboxProvider::trackUpdated( const QString &location, 0240 const Meta::FieldHash &statistics ) 0241 { 0242 QMutexLocker lock( &m_dirtyMutex ); 0243 m_dirtyData.insert( location, statistics ); 0244 } 0245 0246 void 0247 RhythmboxProvider::commitTracks() 0248 { 0249 QMutexLocker lock( &m_dirtyMutex ); 0250 if( m_dirtyData.empty() ) 0251 return; 0252 0253 QMap<QString, Meta::FieldHash> dirtyData; 0254 dirtyData.swap( m_dirtyData ); 0255 0256 QFile dbFile( m_config.value( "dbPath" ).toString() ); 0257 if( !dbFile.open( QIODevice::ReadOnly ) ) 0258 { 0259 warning() << __PRETTY_FUNCTION__ << dbFile.fileName() << "is not readable"; 0260 return; 0261 } 0262 0263 QTemporaryFile tmpFile; 0264 if( !tmpFile.open() ) 0265 { 0266 warning() << __PRETTY_FUNCTION__ << tmpFile.fileName() << "is not writable"; 0267 return; 0268 } 0269 0270 QXmlStreamReader reader( &dbFile ); 0271 QXmlStreamWriter writer( &tmpFile ); 0272 writer.setAutoFormatting( true ); 0273 writer.setAutoFormattingIndent( 2 ); 0274 0275 while( !reader.atEnd() ) 0276 { 0277 reader.readNext(); 0278 0279 if( reader.error() ) 0280 { 0281 warning() << __PRETTY_FUNCTION__ << "Error reading" << dbFile.fileName(); 0282 return; 0283 } 0284 0285 if( reader.isStartElement() && reader.name() == "entry" && 0286 reader.attributes().value( "type" ) == "song" ) 0287 writeSong( reader, writer, dirtyData ); 0288 else if( reader.isStartDocument() ) // writeCurrentToken doesn't add 'standalone' 0289 writer.writeStartDocument( reader.documentVersion().toString(), 0290 reader.isStandaloneDocument() ); 0291 else 0292 writer.writeCurrentToken( reader ); 0293 } 0294 0295 const QString dbName = dbFile.fileName(); 0296 QFile::remove( dbName + ".bak" ); 0297 dbFile.rename( dbName + ".bak" ); 0298 tmpFile.copy( dbName ); 0299 }