diff --git a/playlistitem.h b/playlistitem.h index 0455fe4b..a41930a6 100644 --- a/playlistitem.h +++ b/playlistitem.h @@ -1,231 +1,232 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -#ifndef PLAYLISTITEM_H -#define PLAYLISTITEM_H +#ifndef JUK_PLAYLISTITEM_H +#define JUK_PLAYLISTITEM_H -#include #include +#include #include #include #include #include #include "tagguesser.h" #include "filehandle.h" class Playlist; class PlaylistItem; class CollectionListItem; class CollectionList; typedef QList PlaylistItemList; /** * Items for the Playlist and the baseclass for CollectionListItem. * The constructors and destructor are protected and new items should be * created via Playlist::createItem(). Items should be removed by * Playlist::clear(), Playlist::deleteFromDisk(), Playlist::clearItem() or * Playlist::clearItem(). */ class PlaylistItem : public QTreeWidgetItem { friend class Playlist; friend class SearchPlaylist; friend class UpcomingPlaylist; friend class CollectionList; friend class CollectionListItem; friend class Pointer; public: enum ColumnType { TrackColumn = 0, ArtistColumn = 1, AlbumColumn = 2, CoverColumn = 3, TrackNumberColumn = 4, GenreColumn = 5, YearColumn = 6, LengthColumn = 7, BitrateColumn = 8, CommentColumn = 9, FileNameColumn = 10, FullPathColumn = 11 }; /** * A helper class to implement guarded pointer semantics. */ class Pointer { public: Pointer() : m_item(0) {} Pointer(PlaylistItem *item); Pointer(const Pointer &p); ~Pointer(); Pointer &operator=(PlaylistItem *item); bool operator==(const Pointer &p) const { return m_item == p.m_item; } bool operator!=(const Pointer &p) const { return m_item != p.m_item; } PlaylistItem *operator->() const { return m_item; } PlaylistItem &operator*() const { return *m_item; } operator PlaylistItem*() const { return m_item; } static void clear(PlaylistItem *item); private: PlaylistItem *m_item; static QMap > m_map; }; friend class Pointer; static int lastColumn() { return FullPathColumn; } void setFile(const FileHandle &file); void setFile(const QString &file); FileHandle file() const; virtual const QPixmap *pixmap(int column) const; virtual QString text(int column) const; virtual void setText(int column, const QString &text); void setPlaying(bool playing = true, bool master = true); virtual void setSelected(bool selected); void guessTagInfo(TagGuesser::Type type); Playlist *playlist() const; virtual CollectionListItem *collectionItem() { return m_collectionItem; } /** * This is an identifier for the playlist item which will remain unique * throughout the process lifetime. It stays constant once the PlaylistItem * is created. */ quint32 trackId() const { return m_trackId; } /** * The widths of items are cached when they're updated for us in computations * in the "weighted" listview column width mode. */ QVector cachedWidths() const; /** * This just refreshes from the in memory data. This may seem pointless at * first, but this data is shared between all of the list view items that are * based on the same file, so if another one of those items changes its data * it is important to refresh the others. */ virtual void refresh(); /** * This rereads the tag from disk. This affects all PlaylistItems based on * the same file. */ virtual void refreshFromDisk(); /** * Asks the item's playlist to remove the item (which uses deleteLater()). */ virtual void clear(); /** * Returns properly casted item below this one. */ PlaylistItem *itemBelow() { return static_cast(treeWidget()->itemBelow(this)); } /** * Returns properly casted item above this one. */ PlaylistItem *itemAbove() { return static_cast(treeWidget()->itemAbove(this)); } /** * Returns a reference to the list of the currnetly playing items, with the * first being the "master" item (i.e. the item from which the next track is * chosen). */ static const PlaylistItemList &playingItems() { return m_playingItems; } protected: /** * Items should always be created using Playlist::createItem() or through a * subclass or friend class. */ PlaylistItem(CollectionListItem *item, Playlist *parent); PlaylistItem(CollectionListItem *item, Playlist *parent, QTreeWidgetItem *after); /** * This is the constructor that shold be used by subclasses. */ PlaylistItem(CollectionList *parent); /** * See the class documentation for an explanation of construction and deletion * of PlaylistItems. */ virtual ~PlaylistItem(); //virtual void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align); //virtual void paintFocus(QPainter *, const QColorGroup &, const QRect &) {} virtual int compare(QTreeWidgetItem *item, int column, bool ascending) const; int compare(const PlaylistItem *firstItem, const PlaylistItem *secondItem, int column, bool ascending) const; bool isValid() const; void setTrackId(quint32 id); /** * Shared data between all PlaylistItems from the same track (incl. the CollectionItem * representing said track. */ - struct Data : public KShared + struct Data : public QSharedData { Data() {} Data(const QFileInfo &info, const QString &path) : fileHandle(info, path) {} Data(const QString &path) : fileHandle(path) {} FileHandle fileHandle; QVector metadata; ///< Artist, album, or genre tags. Other columns unfilled QVector cachedWidths; }; - KSharedPtr data() const { return d; } + using DataPtr = QExplicitlySharedDataPointer; + DataPtr data() const { return d; } private: - KSharedPtr d; + DataPtr d; void setup(CollectionListItem *item); CollectionListItem *m_collectionItem; quint32 m_trackId; bool m_watched; static PlaylistItemList m_playingItems; }; inline QDebug operator<<(QDebug s, const PlaylistItem &item) { if(&item == 0) s << "(nil)"; else s << item.text(PlaylistItem::TrackColumn); return s; } #endif // vim: set et sw=4 tw=0 sta: diff --git a/scrobbler.cpp b/scrobbler.cpp index afbf1a8b..b46220ed 100644 --- a/scrobbler.cpp +++ b/scrobbler.cpp @@ -1,344 +1,343 @@ /** * Copyright (C) 2012 Martin Sandsmark * Copyright (C) 2014 Arnold Dumas * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "scrobbler.h" #include #include #include #include #include #include #include #include #include #include #include -#include #include "tag.h" Scrobbler::Scrobbler(QObject* parent) : QObject(parent) , m_networkAccessManager(0) , m_wallet(0) { QByteArray sessionKey; m_wallet = Scrobbler::openKWallet(); if (m_wallet) { m_wallet->readEntry("SessionKey", sessionKey); } else { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey.append(config.readEntry("SessionKey", "")); } if(sessionKey.isEmpty()) getAuthToken(); } Scrobbler::~Scrobbler() { delete m_wallet; } bool Scrobbler::isScrobblingEnabled() { QString username, password; if (KWallet::Wallet::folderDoesNotExist(KWallet::Wallet::LocalWallet(), "JuK")) { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); username = config.readEntry("Username", ""); password = config.readEntry("Password", ""); } else { KWallet::Wallet* wallet = Scrobbler::openKWallet(); if (wallet) { QMap scrobblingCredentials; wallet->readMap("Scrobbling", scrobblingCredentials); if (scrobblingCredentials.contains("Username") && scrobblingCredentials.contains("Password")) { username = scrobblingCredentials["Username"]; password = scrobblingCredentials["Password"]; } delete wallet; } } return (!username.isEmpty() && !password.isEmpty()); } KWallet::Wallet* Scrobbler::openKWallet() // static { const QString walletFolderName = "JuK"; KWallet::Wallet* wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); if (wallet) { if (!wallet->hasFolder(walletFolderName)) { if (!wallet->createFolder(walletFolderName)) { delete wallet; return 0; } } if (!wallet->setFolder(walletFolderName)) { delete wallet; return 0; } } return wallet; } QByteArray Scrobbler::md5(QByteArray data) { return QCryptographicHash::hash(data, QCryptographicHash::Md5) .toHex().rightJustified(32, '0').toLower(); } void Scrobbler::sign(QMap< QString, QString >& params) { params["api_key"] = "3e6ecbd7284883089e8f2b5b53b0aecd"; QString s; QMapIterator i(params); while(i.hasNext()) { i.next(); s += i.key() + i.value(); } s += "2cab3957b1f70d485e9815ac1ac94096"; //shared secret params["api_sig"] = md5(s.toUtf8()); } void Scrobbler::getAuthToken(QString username, QString password) { kDebug() << "Getting new auth token for user:" << username; QByteArray authToken = md5((username + md5(password.toUtf8())).toUtf8()); QMap params; params["method"] = "auth.getMobileSession"; params["authToken"] = authToken; params["username"] = username; QUrl url("http://ws.audioscrobbler.com/2.0/?"); sign(params); foreach(QString key, params.keys()) { url.addQueryItem(key, params[key]); } if (!m_networkAccessManager) m_networkAccessManager = new QNetworkAccessManager(this); QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url)); connect(reply, SIGNAL(finished()), this, SLOT(handleAuthenticationReply())); } void Scrobbler::getAuthToken() { QString username, password; if (m_wallet) { QMap scrobblingCredentials; m_wallet->readMap("Scrobbling", scrobblingCredentials); if (scrobblingCredentials.contains("Username") && scrobblingCredentials.contains("Password")) { username = scrobblingCredentials["Username"]; password = scrobblingCredentials["Password"]; } } else { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); username = config.readEntry("Username", ""); password = config.readEntry("Password", ""); } if(username.isEmpty() || password.isEmpty()) return; getAuthToken(username, password); } void Scrobbler::handleAuthenticationReply() { QNetworkReply* reply = qobject_cast(sender()); kDebug() << "got authentication reply"; if(reply->error() != QNetworkReply::NoError) { emit invalidAuth(); kWarning() << "Error while getting authentication reply" << reply->errorString(); return; } QDomDocument doc; QByteArray data = reply->readAll(); doc.setContent(data); QString sessionKey = doc.documentElement() .firstChildElement("session") .firstChildElement("key").text(); if(sessionKey.isEmpty()) { emit invalidAuth(); kWarning() << "Unable to get session key" << data; return; } if (m_wallet) { m_wallet->writeEntry("SessionKey", sessionKey.toUtf8()); } else { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); config.writeEntry("SessionKey", sessionKey); } emit validAuth(); } void Scrobbler::nowPlaying(const FileHandle& file) { QString sessionKey; if (m_wallet) { QByteArray sessionKeyByteArray; m_wallet->readEntry("SessionKey", sessionKeyByteArray); sessionKey = QString::fromLatin1(sessionKeyByteArray); } else { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey = config.readEntry("SessionKey", ""); } if (!m_file.isNull()) { scrobble(); // Update time-played info for last track } QMap params; params["method"] = "track.updateNowPlaying"; params["sk"] = sessionKey; params["track"] = file.tag()->title(); params["artist"] = file.tag()->artist(); params["album"] = file.tag()->album(); params["trackNumber"] = QString::number(file.tag()->track()); params["duration"] = QString::number(file.tag()->seconds()); sign(params); post(params); m_file = file; // May be FileHandle::null() m_playbackTimer = QDateTime::currentDateTime(); } void Scrobbler::scrobble() { QString sessionKey; if (m_wallet) { QByteArray sessionKeyByteArray; m_wallet->readEntry("SessionKey", sessionKeyByteArray); sessionKey = QString::fromLatin1(sessionKeyByteArray); } else { KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey = config.readEntry("SessionKey", ""); } if(sessionKey.isEmpty()) { getAuthToken(); return; } int halfDuration = m_file.tag()->seconds() / 2; int timeElapsed = m_playbackTimer.secsTo(QDateTime::currentDateTime()); if (timeElapsed < 30 || timeElapsed < halfDuration) { return; // API says not to scrobble if the user didn't play long enough } kDebug() << "Scrobbling" << m_file.tag()->title(); QMap params; params["method"] = "track.scrobble"; params["sk"] = sessionKey; params["track"] = m_file.tag()->title(); params["artist"] = m_file.tag()->artist(); params["album"] = m_file.tag()->album(); params["timestamp"] = QString::number(m_playbackTimer.toTime_t()); params["trackNumber"] = QString::number(m_file.tag()->track()); params["duration"] = QString::number(m_file.tag()->seconds()); sign(params); post(params); } void Scrobbler::post(QMap ¶ms) { if(!m_networkAccessManager) { return; } QUrl url("http://ws.audioscrobbler.com/2.0/"); QByteArray data; foreach(QString key, params.keys()) { data += QUrl::toPercentEncoding(key) + '=' + QUrl::toPercentEncoding(params[key]) + '&'; } QNetworkRequest req(url); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkReply *reply = m_networkAccessManager->post(req, data); connect(reply, SIGNAL(finished()), this, SLOT(handleResults())); } void Scrobbler::handleResults() { QNetworkReply* reply = qobject_cast(sender()); QByteArray data = reply->readAll(); if(data.contains("code=\"9\"")) // We need a new token getAuthToken(); }