1 /***************************************************************************
2 copyright : (C) 2004 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
6 /***************************************************************************
7 * This library is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU Lesser General Public License version *
9 * 2.1 as published by the Free Software Foundation. *
11 * This library is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the Free Software *
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 *
20 ***************************************************************************/
21 #define DEBUG_PREFIX "KTRM"
25 #include "config-amarok.h"
28 #include "ContextStatusBar.h"
31 #include <KProtocolManager>
34 #include <QDomDocument>
35 #include <QDomElement>
46 #define HAVE_TUNEPIMP 0
51 #if HAVE_TUNEPIMP >= 5
52 #include <tunepimp-0.5/tp_c.h>
54 #include <tunepimp/tp_c.h>
61 #if HAVE_TUNEPIMP >= 4
62 static void TRMNotifyCallback(tunepimp_t pimp
, void *data
, TPCallbackEnum type
, int fileId
, TPFileStatus status
);
64 static void TRMNotifyCallback(tunepimp_t pimp
, void *data
, TPCallbackEnum type
, int fileId
);
69 * This represents the main TunePimp instance and handles incoming requests.
72 class KTRMRequestHandler
75 static KTRMRequestHandler
*instance()
79 static KTRMRequestHandler handler
;
84 int startLookup(KTRMLookup
*lookup
)
87 if(!m_fileMap
.contains(lookup
->file())) {
88 #if HAVE_TUNEPIMP >= 4
89 id
= tp_AddFile(m_pimp
, QFile::encodeName(lookup
->file()), 0);
91 id
= tp_AddFile(m_pimp
, QFile::encodeName(lookup
->file()));
93 m_fileMap
.insert(lookup
->file(), id
);
96 id
= m_fileMap
[lookup
->file()];
97 tp_IdentifyAgain(m_pimp
, id
);
99 m_lookupMap
[id
] = lookup
;
103 void endLookup(KTRMLookup
*lookup
)
105 tp_ReleaseTrack(m_pimp
, tp_GetTrack(m_pimp
, lookup
->fileId()));
106 tp_Remove(m_pimp
, lookup
->fileId());
107 m_lookupMapMutex
.lock();
108 m_lookupMap
.remove(lookup
->fileId());
109 m_fileMap
.remove( lookup
->file() );
110 m_lookupMapMutex
.unlock();
113 bool lookupMapContains(int fileId
) const
115 m_lookupMapMutex
.lock();
116 bool contains
= m_lookupMap
.contains(fileId
);
117 m_lookupMapMutex
.unlock();
121 KTRMLookup
*lookup(int fileId
) const
123 m_lookupMapMutex
.lock();
124 KTRMLookup
*l
= m_lookupMap
[fileId
];
125 m_lookupMapMutex
.unlock();
129 void removeFromLookupMap(int fileId
)
131 m_lookupMapMutex
.lock();
132 m_lookupMap
.remove(fileId
);
133 m_lookupMapMutex
.unlock();
136 const tunepimp_t
&tunePimp() const
144 m_pimp
= tp_New("KTRM", "0.1");
145 //tp_SetDebug(m_pimp, true);
146 #if HAVE_TUNEPIMP < 5
147 tp_SetTRMCollisionThreshold(m_pimp
, 100);
148 tp_SetAutoFileLookup(m_pimp
,true);
150 tp_SetAutoSaveThreshold(m_pimp
, -1);
151 tp_SetMoveFiles(m_pimp
, false);
152 tp_SetRenameFiles(m_pimp
, false);
153 #if HAVE_TUNEPIMP >= 4
154 tp_SetFileNameEncoding(m_pimp
, "UTF-8");
156 tp_SetUseUTF8(m_pimp
, true);
158 tp_SetNotifyCallback(m_pimp
, TRMNotifyCallback
, 0);
160 #if HAVE_TUNEPIMP < 5
161 KProtocolManager::reparseConfiguration();
163 if(KProtocolManager::useProxy()) {
164 QString noProxiesFor
= KProtocolManager::noProxyFor();
165 QStringList noProxies
= QStringList::split(QRegExp("[',''\t'' ']"), noProxiesFor
);
166 bool useProxy
= true;
170 tp_GetServer(m_pimp
, server
, 255, &port
);
171 QString tunepimpHost
= QString(server
);
172 QString tunepimpHostWithPort
= (tunepimpHost
+ ":%1").arg(port
);
174 for(QStringList::ConstIterator it
= noProxies
.constBegin(); it
!= noProxies
.constEnd(); ++it
) {
175 QString normalizedHost
= KNetwork::KResolver::normalizeDomain(*it
);
176 if(normalizedHost
== tunepimpHost
||
177 tunepimpHost
.endsWith('.' + normalizedHost
)) {
182 if(normalizedHost
== tunepimpHostWithPort
||
183 tunepimpHostWithPort
.endsWith('.' + normalizedHost
)) {
189 if(KProtocolManager::useReverseProxy())
190 useProxy
= !useProxy
;
193 KUrl proxy
= KProtocolManager::proxyFor("http");
194 tp_SetProxy(m_pimp
, proxy
.host().toLatin1(), short(proxy
.port()));
198 tp_SetMusicDNSClientId(m_pimp
, "0c6019606b1d8a54d0985e448f3603ca");
202 ~KTRMRequestHandler()
209 QMap
<int, KTRMLookup
*> m_lookupMap
;
210 QMap
<QString
, int> m_fileMap
;
211 mutable QMutex m_lookupMapMutex
;
216 * A custom event type used for signalling that a TRM lookup is finished.
219 class KTRMEvent
: public QEvent
230 KTRMEvent(int fileId
, Status status
) :
231 QEvent( Type( KTRMEventType
) ),
240 Status
status() const
245 static const int KTRMEventType
= User
+ 1984; // random, unique, event id
253 * A helper class to intercept KTRMQueryEvents and call recognized() (from the GUI
254 * thread) for the lookup.
257 class KTRMEventHandler
: public QObject
260 static void send(int fileId
, KTRMEvent::Status status
)
262 KApplication::postEvent(instance(), new KTRMEvent(fileId
, status
));
266 KTRMEventHandler() : QObject() {}
268 static KTRMEventHandler
*instance()
272 static KTRMEventHandler handler
;
277 virtual void customEvent(QEvent
*event
)
279 if(!event
->type() == KTRMEvent::KTRMEventType
)
282 KTRMEvent
*e
= static_cast<KTRMEvent
*>(event
);
287 if(!KTRMRequestHandler::instance()->lookupMapContains(e
->fileId())) {
292 KTRMLookup
*lookup
= KTRMRequestHandler::instance()->lookup(e
->fileId());
293 #if HAVE_TUNEPIMP >= 4
294 if ( e
->status() != KTRMEvent::Unrecognized
)
296 KTRMRequestHandler::instance()->removeFromLookupMap(e
->fileId());
300 switch(e
->status()) {
301 case KTRMEvent::Recognized
:
302 lookup
->recognized();
304 case KTRMEvent::Unrecognized
:
305 lookup
->unrecognized();
307 case KTRMEvent::Collision
:
310 case KTRMEvent::PuidGenerated
:
311 lookup
->puidGenerated();
313 case KTRMEvent::Error
:
321 * Callback function for TunePimp lookup events.
323 #if HAVE_TUNEPIMP >= 4
324 static void TRMNotifyCallback(tunepimp_t
/*pimp*/, void */
*data*/
, TPCallbackEnum type
, int fileId
, TPFileStatus status
)
326 static void TRMNotifyCallback(tunepimp_t pimp
, void */
*data*/
, TPCallbackEnum type
, int fileId
)
329 if(type
!= tpFileChanged
)
332 #if HAVE_TUNEPIMP < 4
333 track_t track
= tp_GetTrack(pimp
, fileId
);
334 TPFileStatus status
= tr_GetStatus(track
);
336 //debug() << "Status is: " << status;
340 KTRMEventHandler::send(fileId
, KTRMEvent::Recognized
);
343 KTRMEventHandler::send(fileId
, KTRMEvent::Unrecognized
);
345 #if HAVE_TUNEPIMP >= 5
349 KTRMEventHandler::send(fileId
, KTRMEvent::PuidGenerated
);
353 #if HAVE_TUNEPIMP >= 4
356 KTRMEventHandler::send(fileId
, KTRMEvent::Collision
);
360 KTRMEventHandler::send(fileId
, KTRMEvent::Error
);
365 #if HAVE_TUNEPIMP < 4
366 tp_ReleaseTrack(pimp
, track
);
370 ////////////////////////////////////////////////////////////////////////////////
371 // KTRMResult implementation
372 ////////////////////////////////////////////////////////////////////////////////
374 class KTRMResult::KTRMResultPrivate
377 KTRMResultPrivate() : track(0), year(0), relevance(0) {}
385 bool operator== (const KTRMResultPrivate
&r
) const;
388 bool KTRMResult::KTRMResultPrivate::operator==(const KTRMResultPrivate
&r
) const
392 artist
== r
.artist
&&
396 relevance
== r
.relevance
400 ////////////////////////////////////////////////////////////////////////////////
401 // KTRMResult public methods
402 ////////////////////////////////////////////////////////////////////////////////
404 KTRMResult::KTRMResult()
407 d
= new KTRMResultPrivate
;
411 KTRMResult::KTRMResult(const KTRMResult
&result
)
414 d
= new KTRMResultPrivate(*result
.d
);
420 KTRMResult::~KTRMResult()
427 QString
KTRMResult::title() const
436 QString
KTRMResult::artist() const
445 QString
KTRMResult::album() const
454 int KTRMResult::track() const
463 int KTRMResult::year() const
472 bool KTRMResult::operator<(const KTRMResult
&r
) const
475 return r
.d
->relevance
< d
->relevance
;
482 bool KTRMResult::operator>(const KTRMResult
&r
) const
485 return r
.d
->relevance
> d
->relevance
;
492 KTRMResult
&KTRMResult::operator= (const KTRMResult
&r
)
495 d
= new KTRMResultPrivate(*r
.d
);
502 bool KTRMResult::operator== (const KTRMResult
&r
) const
513 bool KTRMResult::isEmpty() const
516 return d
->title
.isEmpty() && d
->artist
.isEmpty() && d
->album
.isEmpty() &&
517 d
->track
== 0 && d
->year
== 0;
523 ////////////////////////////////////////////////////////////////////////////////
524 // KTRMLookup implementation
525 ////////////////////////////////////////////////////////////////////////////////
527 class KTRMLookup::KTRMLookupPrivate
530 KTRMLookupPrivate() :
534 KTRMResultList results
;
539 ////////////////////////////////////////////////////////////////////////////////
540 // KTRMLookup public methods
541 ////////////////////////////////////////////////////////////////////////////////
543 KTRMLookup::KTRMLookup(const QString
&file
, bool autoDelete
)
547 d
= new KTRMLookupPrivate
;
549 d
->autoDelete
= autoDelete
;
550 d
->fileId
= KTRMRequestHandler::instance()->startLookup(this);
553 Q_UNUSED(autoDelete
);
557 KTRMLookup::~KTRMLookup()
560 KTRMRequestHandler::instance()->endLookup(this);
565 QString
KTRMLookup::file() const
574 int KTRMLookup::fileId() const
583 void KTRMLookup::recognized()
590 metadata_t
*metaData
= md_New();
591 track_t track
= tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d
->fileId
);
593 tr_GetServerMetadata(track
, metaData
);
597 result
.d
->title
= QString::fromUtf8(metaData
->track
);
598 result
.d
->artist
= QString::fromUtf8(metaData
->artist
);
599 result
.d
->album
= QString::fromUtf8(metaData
->album
);
600 result
.d
->track
= metaData
->trackNum
;
601 result
.d
->year
= metaData
->releaseYear
;
603 d
->results
.append(result
);
611 void KTRMLookup::unrecognized()
615 #if HAVE_TUNEPIMP >= 4
619 track_t track
= tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d
->fileId
);
621 #if HAVE_TUNEPIMP >= 5
622 tr_GetPUID(track
, trm
, 255);
624 tr_GetTRM(track
, trm
, 255);
627 tr_SetStatus(track
, ePending
);
628 tp_Wake(KTRMRequestHandler::instance()->tunePimp(), track
);
633 tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track
);
643 void KTRMLookup::collision()
645 #if HAVE_TUNEPIMP && HAVE_TUNEPIMP < 5
648 track_t track
= tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d
->fileId
);
651 debug() << "invalid track number";
656 int resultCount
= tr_GetNumResults(track
);
658 QStringList strList
= QStringList::split ( '/', d
->file
);
660 metadata_t
*mdata
= md_New();
661 strList
.append( QString::fromUtf8(mdata
->track
) );
662 strList
.append( QString::fromUtf8(mdata
->artist
) );
663 strList
.append( QString::fromUtf8(mdata
->album
) );
666 if(resultCount
> 0) {
668 result_t
*results
= new result_t
[resultCount
];
669 tr_GetResults(track
, &type
, results
, &resultCount
);
676 debug() << "eArtistList";
679 debug() << "eAlbumList";
683 debug() << "eTrackList";
684 albumtrackresult_t
**tracks
= reinterpret_cast<albumtrackresult_t
**>( results
);
687 for(int i
= 0; i
< resultCount
; i
++) {
690 result
.d
->title
= QString::fromUtf8(tracks
[i
]->name
);
691 #if HAVE_TUNEPIMP >= 4
692 result
.d
->artist
= QString::fromUtf8(tracks
[i
]->artist
.name
);
693 result
.d
->album
= QString::fromUtf8(tracks
[i
]->album
.name
);
694 result
.d
->year
= tracks
[i
]->album
.releaseYear
;
696 result
.d
->artist
= QString::fromUtf8(tracks
[i
]->artist
->name
);
697 result
.d
->album
= QString::fromUtf8(tracks
[i
]->album
->name
);
698 result
.d
->year
= tracks
[i
]->album
->releaseYear
;
700 result
.d
->track
= tracks
[i
]->trackNum
;
701 result
.d
->relevance
=
702 4 * stringSimilarity(strList
,result
.d
->title
) +
703 2 * stringSimilarity(strList
,result
.d
->artist
) +
704 1 * stringSimilarity(strList
,result
.d
->album
);
706 if(!d
->results
.contains(result
)) d
->results
.append(result
);
711 debug() << "eMatchedTrack";
719 tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track
);
726 void KTRMLookup::puidGenerated()
728 #if HAVE_TUNEPIMP >= 5
731 char puid
[255] = {0};
732 track_t track
= tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d
->fileId
);
735 tr_GetPUID(track
, puid
, 255);
738 tp_ReleaseTrack(KTRMRequestHandler::instance()->tunePimp(), track
);
741 KIO::Job
*job
= KIO::storedGet( QString( "http://musicbrainz.org/ws/1/track/?type=xml&puid=%1" ).arg( puid
) , KIO::NoReload
, KIO::HideProgressInfo
);
743 Amarok::ContextStatusBar::instance()->newProgressOperation( job
)
744 .setDescription( i18n( "MusicBrainz Lookup" ) );
746 connect( job
, SIGNAL( result( KIO::Job
* ) ), SLOT( lookupResult( KIO::Job
* ) ) );
750 void KTRMLookup::lookupResult( KIO::Job
* job
)
752 #if HAVE_TUNEPIMP >= 5
754 if ( !job
->error() == 0 ) {
755 warning() << "[MusicBrainzLookup] KIO error! errno: " << job
->error();
756 Amarok::ContextStatusBar::instance()->longMessage( "Couldn't connect to MusicBrainz server." );
760 KIO::StoredTransferJob
* const storedJob
= static_cast<KIO::StoredTransferJob
*>( job
);
761 QString xml
= QString::fromUtf8( storedJob
->data().data(), storedJob
->data().size() );
766 if( !doc
.setContent( xml
) ) {
767 warning() << "[MusicBrainzLookup] Invalid XML";
768 Amarok::ContextStatusBar::instance()->longMessage( "MusicBrainz returned invalid content." );
773 e
= doc
.namedItem( "metadata" ).toElement().namedItem( "track-list" ).toElement();
775 QStringList strList
= QStringList::split ( '/', d
->file
);
777 QDomNode n
= e
.namedItem("track");
778 for( ; !n
.isNull(); n
= n
.nextSibling() ) {
779 QDomElement track
= n
.toElement();
782 result
.d
->title
= track
.namedItem( "title" ).toElement().text();
783 result
.d
->artist
= track
.namedItem( "artist" ).toElement().namedItem( "name" ).toElement().text();
784 QDomNode releaseNode
= track
.namedItem("release-list").toElement().namedItem("release");
785 for( ; !releaseNode
.isNull(); releaseNode
= releaseNode
.nextSibling() ) {
786 KTRMResult
tmpResult( result
);
787 QDomElement release
= releaseNode
.toElement();
789 tmpResult
.d
->album
= release
.namedItem( "title" ).toElement().text();
790 QDomNode tracklistN
= release
.namedItem( "track-list" );
791 if ( !tracklistN
.isNull() ) {
792 QDomElement tracklist
= tracklistN
.toElement();
793 if ( !tracklist
.attribute( "offset" ).isEmpty() )
794 tmpResult
.d
->track
= tracklist
.attribute( "offset" ).toInt() + 1;
796 //tmpResult.d->year = ???;
797 tmpResult
.d
->relevance
=
798 4 * stringSimilarity(strList
,tmpResult
.d
->title
) +
799 2 * stringSimilarity(strList
,tmpResult
.d
->artist
) +
800 1 * stringSimilarity(strList
,tmpResult
.d
->album
);
801 if( !d
->results
.contains( tmpResult
) )
802 d
->results
.append( tmpResult
);
814 void KTRMLookup::error()
818 track_t track
= tp_GetTrack(KTRMRequestHandler::instance()->tunePimp(), d
->fileId
);
820 tr_GetError( track
, error
, 1000);
821 debug() << "Error: " << error
;
822 d
->errorString
= error
;
828 KTRMResultList
KTRMLookup::results() const
833 return KTRMResultList();
837 ////////////////////////////////////////////////////////////////////////////////
838 // KTRMLookup protected methods
839 ////////////////////////////////////////////////////////////////////////////////
841 void KTRMLookup::finished()
845 emit
sigResult( results(), d
->errorString
);
852 ////////////////////////////////////////////////////////////////////////////////
853 // Helper Functions used for sorting MusicBrainz results
854 ////////////////////////////////////////////////////////////////////////////////
855 double stringSimilarity(QStringList
&l
, QString
&s
)
857 double max
= 0, current
= 0;
858 for ( QStringList::Iterator it
= l
.begin(); it
!= l
.end(); ++it
) {
859 if( max
< (current
= stringSimilarity((*it
),s
)))
865 double stringSimilarity(QString s1
, QString s2
)
867 s1
.remove( QRegExp("[\\s\\t\\r\\n]") );
868 s2
.remove( QRegExp("[\\s\\t\\r\\n]") );
871 int p1
= 0, p2
= 0, x1
= 0, x2
= 0;
872 int l1
= s1
.length(), l2
= s2
.length(), l3
= l1
+ l2
;
873 QChar c1
= 0, c2
= 0;
875 while(p1
< l1
&& p2
< l2
) {
876 c1
= s1
.at(p1
); c2
= s2
.at(p2
);
877 if( c1
.toUpper() == c2
.toUpper()) {
882 x1
= s1
.indexOf(c2
,p1
,Qt::CaseInsensitive
);
883 x2
= s2
.indexOf(c1
,p2
,Qt::CaseInsensitive
);
885 if( (x1
== x2
|| -1 == x1
) || (-1 != x2
&& x1
> x2
) )
891 return l3
? (double)(nCommon
*2) / (double)(l3
) : 1;