1 /****************************************************************************************
2  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #ifndef STATSYNCING_CONTROLLER_H
18 #define STATSYNCING_CONTROLLER_H
19 
20 #include "amarok_export.h"
21 // for CollectionManager::CollectionStatus that cannot be fwd-declared
22 #include "core-impl/collections/support/CollectionManager.h"
23 
24 #include <QPointer>
25 #include <QDateTime>
26 #include <QMap>
27 
28 #include <KLocalizedString>
29 
30 class QTimer;
31 
32 namespace StatSyncing
33 {
34     class Config;
35     class CreateProviderDialog;
36     class Process;
37     class Provider;
38     typedef QSharedPointer<Provider> ProviderPtr;
39     typedef QList<ProviderPtr> ProviderPtrList;
40     class ProviderFactory;
41     class ScrobblingService;
42     typedef QSharedPointer<ScrobblingService> ScrobblingServicePtr;
43 
44     /**
45      * A singleton class that controls statistics synchronization and related tasks.
46      */
47     class AMAROK_EXPORT Controller : public QObject
48     {
49         Q_OBJECT
50 
51         public:
52             explicit Controller( QObject *parent = nullptr );
53             ~Controller() override;
54 
55             /**
56              * Return a list of Meta::val* fields that statistics synchronization can
57              * actually synchronize.
58              */
59             static QList<qint64> availableFields();
60 
61             /**
62              * Register a StatSyncing::Provider with StatSyncing controller. This makes
63              * it possible to synchronize provider with other providers. You don't need
64              * to call this for Collections that are registered through CollectionManager
65              * (and marked as enabled there) as it is done automatically.
66              */
67             virtual void registerProvider( const ProviderPtr &provider );
68 
69             /**
70              * Forget about StatSyncing::Provider @p provider.
71              * @param provider the provider
72              */
73             virtual void unregisterProvider( const ProviderPtr &provider );
74 
75             /**
76              * Handle plugin factories derived from ProviderFactory, used for creating
77              * multiple provider instances. This method is called by Amarok's plugin
78              * infrastructure.
79              */
80             void setFactories( const QList<QSharedPointer<Plugins::PluginFactory> > &factories );
81 
82             /**
83              * Returns true if any instantiatable provider types are registered with the
84              * controller.
85              */
86             bool hasProviderFactories() const;
87 
88             /**
89              * Returns true if the provider identified by @param id is configurable
90              */
91             bool providerIsConfigurable( const QString &id ) const;
92 
93             /**
94              * Returns a configuration dialog for a provider identified by @param id .
95              * @returns 0 if there's no provider identified by id or the provider is not
96              * configurable, otherwise a pointer to the dialog constructed as a child of
97              * The::mainWindow
98              */
99             QWidget *providerConfigDialog( const QString &id ) const;
100 
101             /**
102              * Returns a provider creation dialog, prepopulated with registered provider
103              * types.
104              * @returns a pointer to the dialog constructed as a child of The::mainWindow,
105              * and is a subclass of KAssistantDialog.
106              *
107              * @see StatSyncing::CreateProviderDialog
108              */
109             QWidget *providerCreationDialog() const;
110 
111             /**
112              * Register ScrobblingService with StatSyncing controller. Controller then
113              * listens to EngineController and calls scrobble() etc. when user plays
114              * tracks. Also allows scrobbling for tracks played on just connected iPods.
115              *
116              * @param service
117              */
118             void registerScrobblingService( const ScrobblingServicePtr &service );
119 
120             /**
121              * Forget about ScrobblingService @param service
122              */
123             void unregisterScrobblingService( const ScrobblingServicePtr &service );
124 
125             /**
126              * Return a list of currently registered scrobbling services (in arbitrary
127              * order).
128              */
129             QList<ScrobblingServicePtr> scrobblingServices() const;
130 
131             /**
132              * Return StatSyncing configuration object that describes enabled and
133              * disabled statsyncing providers. You may not cache the pointer.
134              */
135             Config *config();
136 
137         public Q_SLOTS:
138             /**
139              * Start the whole synchronization machinery. This call returns quickly,
140              * way before the synchronization is finished.
141              */
142             void synchronize();
143 
144             /**
145              * Scrobble a track using all registered scrobbling services. They may check
146              * certain criteria such as track length and refuse to scrobble the track.
147              *
148              * @param track track to scrobble
149              * @param playedFraction fraction which has been actually played, or a number
150              *                       greater than 1 if the track was played multiple times
151              *                       (for example on a media device)
152              * @param time time when it was played, invalid QDateTime signifies that the
153              *             track has been played just now. This is the default when the
154              *             parameter is omitted.
155              */
156             void scrobble( const Meta::TrackPtr &track, double playedFraction = 1.0,
157                            const QDateTime &time = QDateTime() );
158 
159         Q_SIGNALS:
160             /**
161              * Emitted when a track passed to scrobble() is successfully queued for
162              * scrobbling submission. This signal is emitted for every scrobbling service.
163              * For each service, you either get this or scrobbleFailed().
164              */
165             void trackScrobbled( const ScrobblingServicePtr &service, const Meta::TrackPtr &track );
166 
167             /**
168              * Emitted when a scrobbling service @p service was unable to scrobble() a track.
169              *
170              * @param service the service
171              * @param track the track
172              * @param error is a ScrobblingService::ScrobbleError enum value.
173              */
174             void scrobbleFailed( const ScrobblingServicePtr &service, const Meta::TrackPtr &track, int error );
175 
176         private Q_SLOTS:
177             /**
178              * Creates new instance of provider type identified by @param type
179              * with configuration stored in @param config.
180              */
181             void createProvider( const QString &type, const QVariantMap &config );
182 
183             /**
184              * Reconfigures provider identified by @param id with configuration
185              * stored in @param config.
186              */
187             void reconfigureProvider( const QString &id, const QVariantMap &config );
188 
189             /**
190              * Can only be connected to provider changed() signal
191              */
192             void slotProviderUpdated();
193             /**
194              * Wait a few seconds and if no collectionUpdate() signal arrives until then,
195              * start synchronization. Otherwise postpone the synchronization for a few
196              * seconds.
197              */
198             void delayedStartSynchronization();
199             void slotCollectionAdded( Collections::Collection* collection,
200                                       CollectionManager::CollectionStatus status );
201             void slotCollectionRemoved( const QString &id );
202             void startNonInteractiveSynchronization();
203             void synchronizeWithMode( int mode );
204 
205             void slotTrackFinishedPlaying( const Meta::TrackPtr &track, double playedFraction );
206             void slotResetLastSubmittedNowPlayingTrack();
207             void slotUpdateNowPlayingWithCurrentTrack();
208 
209         private:
210             Q_DISABLE_COPY( Controller )
211 
212             ProviderPtr findRegisteredProvider( const QString &id ) const;
213 
214             /**
215              * Return true if important metadata of both tracks is equal.
216              */
217             bool tracksVirtuallyEqual( const Meta::TrackPtr &first, const Meta::TrackPtr &second );
218             QMap<QString, QSharedPointer<ProviderFactory> > m_providerFactories;
219 
220             // synchronization-related
221             ProviderPtrList m_providers;
222             QPointer<Process> m_currentProcess;
223             QTimer *m_startSyncingTimer;
224             Config *m_config;
225 
226             /**
227              * When a new collection appears, StatSyncing::Controller will automatically
228              * trigger synchronization. It however waits s_syncingTriggerTimeout
229              * milliseconds to let the collection settle down. Moreover, if the newly
230              * added collection emits updated(), the timeout will run from start again.
231              *
232              * (reason: e.g. iPod Collection appears quickly, but with no tracks, which
233              * are added gradually as they are parsed. This "ensures" we only start
234              * syncing as soon as all tracks are parsed.)
235              */
236             static const int s_syncingTriggerTimeout;
237 
238             // scrobbling-related
239             QList<ScrobblingServicePtr> m_scrobblingServices;
240             QTimer *m_updateNowPlayingTimer;
241             Meta::TrackPtr m_lastSubmittedNowPlayingTrack;
242     };
243 
244 } // namespace StatSyncing
245 
246 #endif // STATSYNCING_CONTROLLER_H
247