1 /*
2  * Copyright © 2015-2016 Antti Lamminsalo
3  *
4  * This file is part of Orion.
5  *
6  * Orion is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with Orion.  If not, see <http://www.gnu.org/licenses/>.
13  */
14 
15 #include "channelmanager.h"
16 #include <QCoreApplication>
17 
ChannelManager()18 ChannelManager::ChannelManager() :
19     netman(NetworkManager::getInstance()),
20     settingsManager(SettingsManager::getInstance())
21 {
22     user_id = 0;
23     tempFavourites = 0;
24 
25     resultsModel = new ChannelListModel();
26     gamesModel = new GameListModel();
27 
28     //Setup followed channels model and it's signal chain
29     favouritesModel = createFollowedChannelsModel();
30 
31     favouritesProxy = new QSortFilterProxyModel();
32 
33     favouritesProxy->setSortRole(ChannelListModel::Roles::ViewersRole);
34     favouritesProxy->sort(0, Qt::DescendingOrder);
35     favouritesProxy->setSourceModel(favouritesModel);
36 
37     connect(netman, &NetworkManager::allStreamsOperationFinished, this, &ChannelManager::updateStreams);
38     connect(netman, &NetworkManager::featuredStreamsOperationFinished, this, &ChannelManager::addSearchResults);
39     connect(netman, &NetworkManager::gamesOperationFinished, this, &ChannelManager::addGames);
40     connect(netman, &NetworkManager::gameStreamsOperationFinished, this, &ChannelManager::addSearchResults);
41     connect(netman, &NetworkManager::searchChannelsOperationFinished, this, &ChannelManager::addSearchResults);
42     connect(netman, &NetworkManager::m3u8OperationFinished, this, &ChannelManager::foundPlaybackStream);
43     connect(netman, &NetworkManager::searchGamesOperationFinished, this, &ChannelManager::addGames);
44 
45     connect(netman, &NetworkManager::userOperationFinished, this, &ChannelManager::onUserUpdated);
46 
47     connect(netman, &NetworkManager::favouritesReplyFinished, this, &ChannelManager::addFollowedResults);
48 
49     connect(netman, &NetworkManager::networkAccessChanged, this, &ChannelManager::slotNetworkAccessChanged);
50     connect(settingsManager, &SettingsManager::accessTokenChanged, this, &ChannelManager::updateAccessToken);
51 
52     load();
53 
54     //Start polling timer, setting id as property
55     setProperty("pollTimer", startTimer(30000, Qt::VeryCoarseTimer));
56 
57 
58     if (SettingsManager::getInstance()->hasAccessToken()) {
59         updateAccessToken(SettingsManager::getInstance()->accessToken());
60     }
61 }
62 
getInstance()63 ChannelManager *ChannelManager::getInstance() {
64     static ChannelManager *instance = new ChannelManager();
65     return instance;
66 }
67 
~ChannelManager()68 ChannelManager::~ChannelManager(){
69     qDebug() << "Destroyer: ChannelManager";
70 
71     save();
72 
73     delete favouritesModel;
74     delete resultsModel;
75     delete gamesModel;
76     delete favouritesProxy;
77 }
78 
addToFavourites(const quint32 & id,const QString & serviceName,const QString & title,const QString & info,const QString & logo,const QString & preview,const QString & game,const qint32 & viewers,bool online)79 void ChannelManager::addToFavourites(const quint32 &id, const QString &serviceName, const QString &title,
80                                      const QString &info, const QString &logo, const QString &preview,
81                                      const QString &game, const qint32 &viewers, bool online)
82 {
83     if (!favouritesModel->find(id)){
84         Channel *channel = new Channel();
85         channel->setId(id);
86         channel->setServiceName(serviceName);
87         channel->setName(title);
88         channel->setInfo(info);
89         channel->setLogourl(logo);
90         channel->setPreviewurl(preview);
91         channel->setGame(game);
92         channel->setOnline(online);
93         channel->setViewers(viewers);
94         channel->setFavourite(true);
95 
96         if (isAccessTokenAvailable() && user_id != 0) {
97             netman->editUserFavourite(user_id, channel->getId(), true);
98         }
99 
100         favouritesModel->addChannel(channel);
101 
102         emit addedChannel(channel->getId());
103 
104         Channel *chan = resultsModel->find(channel->getId());
105         if (chan){
106             chan->setFavourite(true);
107             resultsModel->updateChannelForView(chan);
108         }
109 
110         if (!isAccessTokenAvailable())
111             save();
112     }
113 }
114 
searchGames(QString q,const quint32 & offset,const quint32 & limit)115 void ChannelManager::searchGames(QString q, const quint32 &offset, const quint32 &limit)
116 {
117     if (offset == 0 || !q.isEmpty())
118         gamesModel->clear();
119 
120     //If query is empty, search games by viewercount
121     if (q.isEmpty())
122         netman->getGames(offset, limit);
123 
124     //Else by queryword
125     else if (offset == 0)
126         netman->searchGames(q);
127 
128     emit gamesSearchStarted();
129 }
130 
username() const131 QString ChannelManager::username() const
132 {
133     return user_name;
134 }
135 
updateAccessToken(QString)136 void ChannelManager::updateAccessToken(QString /*accessToken*/)
137 {
138     if (isAccessTokenAvailable()) {
139         //Fetch display name for logged in user
140         netman->getUser();
141 
142         //move favs to tempfavs
143         if (!tempFavourites) {
144             tempFavourites = favouritesModel;
145 
146             favouritesModel = createFollowedChannelsModel();
147             favouritesProxy->setSourceModel(favouritesModel);
148         }
149     }
150 
151     else {
152         // if we just logged out there are user settings to clear
153         user_id = 0;
154         user_name = "";
155 
156         //Reload local favourites from memory
157         if (tempFavourites) {
158             delete favouritesModel;
159             favouritesModel = tempFavourites;
160             tempFavourites = 0;
161             favouritesProxy->setSourceModel(favouritesModel);
162         }
163 
164         emit login("", "");
165     }
166 
167     emit accessTokenUpdated();
168 }
169 
timerEvent(QTimerEvent * event)170 void ChannelManager::timerEvent(QTimerEvent *event)
171 {
172     checkFavourites();
173 }
174 
getFavouritesModel() const175 ChannelListModel *ChannelManager::getFavouritesModel() const
176 {
177     return favouritesModel;
178 }
179 
getFavouritesProxy() const180 QSortFilterProxyModel *ChannelManager::getFavouritesProxy() const
181 {
182     return favouritesProxy;
183 }
184 
getGamesModel() const185 GameListModel *ChannelManager::getGamesModel() const
186 {
187     return gamesModel;
188 }
189 
getResultsModel() const190 ChannelListModel *ChannelManager::getResultsModel() const
191 {
192     return resultsModel;
193 }
194 
load()195 void ChannelManager::load(){
196     QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
197 
198     int size = settings.beginReadArray("channels");
199     if (size > 0) {
200         QList<Channel*> _channels;
201 
202         for (int i = 0; i < size; i++) {
203             settings.setArrayIndex(i);
204             Channel * channel = new Channel(settings);
205             channel->setFavourite(true);
206             _channels.append(channel);
207         }
208 
209         favouritesModel->addAll(_channels);
210 
211         qDeleteAll(_channels);
212     }
213     settings.endArray();
214 }
215 
save()216 void ChannelManager::save()
217 {
218     QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
219 
220     if (!settings.isWritable())
221         qDebug() << "Error: settings file not writable";
222 
223     if (tempFavourites) {
224         delete favouritesModel;
225         favouritesModel = tempFavourites;
226         tempFavourites = 0;
227     }
228 
229     //Write channels
230     settings.beginWriteArray("channels");
231     for (int i=0; i < favouritesModel->count(); i++){
232         settings.setArrayIndex(i);
233         favouritesModel->getChannels().at(i)->writeToSettings(settings);
234     }
235     settings.endArray();
236 }
237 
238 
addToFavourites(const quint32 & id)239 void ChannelManager::addToFavourites(const quint32 &id){
240     Channel *channel = resultsModel->find(id);
241 
242     if (channel){
243 
244         if (isAccessTokenAvailable() && user_id != 0) {
245             netman->editUserFavourite(user_id, channel->getId(), true);
246         }
247 
248         channel->setFavourite(true);
249         favouritesModel->addChannel(new Channel(*channel));
250 
251         emit addedChannel(channel->getId());
252 
253         resultsModel->updateChannelForView(channel);
254 
255         if (!isAccessTokenAvailable())
256             save();
257     }
258 }
259 
removeFromFavourites(const quint32 & id)260 void ChannelManager::removeFromFavourites(const quint32 &id){
261     Channel *chan = favouritesModel->find(id);
262 
263     emit deletedChannel(chan->getId());
264 
265     if (isAccessTokenAvailable() && user_id != 0) {
266         netman->editUserFavourite(user_id, chan->getId(), false);
267     }
268 
269     favouritesModel->removeChannel(chan);
270 
271     chan = 0;
272 
273     //Update results
274     Channel* channel = resultsModel->find(id);
275     if (channel){
276 
277         channel->setFavourite(false);
278         resultsModel->updateChannelForView(channel);
279     }
280 
281     if (!isAccessTokenAvailable())
282         save();
283 }
284 
commaSeparatedChannelIds(const QList<Channel * > & channels)285 QString commaSeparatedChannelIds(const QList<Channel *> & channels) {
286     QStringList channelIdStrs;
287     foreach(Channel* channel, channels) {
288         channelIdStrs.append(QString::number(channel->getId()));
289     }
290     return channelIdStrs.join(',');
291 }
292 
checkStreams(const QList<Channel * > & list)293 void ChannelManager::checkStreams(const QList<Channel *> &list)
294 {
295     //Divide list to sublists for sanity
296     int pos = 0;
297 
298     while(pos < list.length()) {
299 
300         //Take sublist, max 50 items
301         QList<Channel*> sublist = list.mid(pos, 50);
302 
303         //Fetch channels
304         QString url = KRAKEN_API
305                 + QString("/streams?")
306                 + QString("limit=%1").arg(50) //Important!
307                 + QString("&channel=") + commaSeparatedChannelIds(sublist);
308         netman->getStreams(url);
309 
310         //Shift pos by 50
311         pos += sublist.length();
312     }
313 }
314 
checkFavourites()315 void ChannelManager::checkFavourites()
316 {
317     checkStreams(favouritesModel->getChannels());
318 }
319 
searchChannels(QString q,const quint32 & offset,const quint32 & limit,bool clear)320 void ChannelManager::searchChannels(QString q, const quint32 &offset, const quint32 &limit, bool clear)
321 {
322     if (clear)
323         resultsModel->clear();
324 
325     if (q.isEmpty()) {
326         netman->getFeaturedStreams();
327     }
328     else if (q.startsWith("/game ")){
329         q.replace("/game ", "");
330         netman->getStreamsForGame(q, offset, limit);
331 
332     } else {
333         netman->searchChannels(q, offset, limit);
334     }
335 
336     emit searchingStarted();
337 }
338 
addSearchResults(const QList<Channel * > & list,const int total)339 void ChannelManager::addSearchResults(const QList<Channel*> &list, const int total)
340 {
341     bool needsStreamCheck = false;
342 
343     foreach (Channel *channel, list){
344         if (favouritesModel->find(channel->getId()))
345             channel->setFavourite(true);
346 
347         if (!channel->isOnline())
348             needsStreamCheck = true;
349     }
350 
351     int numAdded = resultsModel->addAll(list);
352 
353     if (needsStreamCheck)
354         checkStreams(list);
355 
356     qDeleteAll(list);
357 
358     emit resultsUpdated(numAdded, total);
359 }
360 
findPlaybackStream(const QString & serviceName)361 void ChannelManager::findPlaybackStream(const QString &serviceName)
362 {
363     netman->getChannelPlaybackStream(serviceName);
364 }
365 
updateFavourites(const QList<Channel * > & list)366 void ChannelManager::updateFavourites(const QList<Channel*> &list)
367 {
368     foreach (Channel *c, list)
369         c->setFavourite(true);
370 
371     favouritesModel->updateChannels(list);
372     qDeleteAll(list);
373 }
374 
containsFavourite(const quint32 & q)375 bool ChannelManager::containsFavourite(const quint32 &q)
376 {
377     return favouritesModel->find(q) != nullptr;
378 }
379 
380 //Updates channel streams in all models
updateStreams(const QList<Channel * > & list)381 void ChannelManager::updateStreams(const QList<Channel*> &list)
382 {
383     favouritesModel->updateStreams(list);
384     resultsModel->updateStreams(list);
385     qDeleteAll(list);
386 }
387 
addGames(const QList<Game * > & list)388 void ChannelManager::addGames(const QList<Game*> &list)
389 {
390     gamesModel->addAll(list);
391 
392     qDeleteAll(list);
393 
394     emit gamesUpdated();
395 }
396 
notify(Channel * channel)397 void ChannelManager::notify(Channel *channel)
398 {
399     if (settingsManager->alert() && channel){
400 
401         if (!channel->isOnline() && !settingsManager->offlineNotifications())
402             //Skip offline notifications if set
403             return;
404 
405         emit pushNotification(channel->getName() + (channel->isOnline() ? " is now streaming" : " has gone offline"),
406                               channel->getInfo(),
407                               channel->getLogourl());
408     }
409 }
410 
notifyMultipleChannelsOnline(const QList<Channel * > & channels)411 void ChannelManager::notifyMultipleChannelsOnline(const QList<Channel*> &channels)
412 {
413     if (channels.size() == 1) {
414         //Only one channel, send the usual notification
415         notify(channels.at(0));
416     }
417 
418     else if (settingsManager->alert()) {
419         //Send multi-notification
420         QString str;
421 
422         foreach (Channel *c, channels) {
423 
424             //Omit channels after enough characters in message body
425             if (str.size() > 80) {
426                 str.append("...");
427                 break;
428             }
429 
430             str.append(!str.isEmpty() ? ", " : "");
431             str.append(c->getName());
432         }
433 
434         emit pushNotification("Channels are streaming", str, DEFAULT_LOGO_URL);
435     }
436 }
437 
438 //Login function
onUserUpdated(const QString & name,const quint64 userId)439 void ChannelManager::onUserUpdated(const QString &name, const quint64 userId)
440 {
441     user_name = name;
442     user_id = userId;
443     emit userNameUpdated(user_name);
444 
445     if (isAccessTokenAvailable()) {
446         emit login(user_name, settingsManager->accessToken());
447 
448         //Start using user followed channels
449         getFollowedChannels(FOLLOWED_FETCH_LIMIT, 0);
450     }
451 }
452 
getFollowedChannels(const quint32 & limit,const quint32 & offset)453 void ChannelManager::getFollowedChannels(const quint32& limit, const quint32& offset)
454 {
455     //if (offset == 0)
456     //favouritesModel->clear();
457 
458     netman->getUserFavourites(user_id, offset, limit);
459 }
460 
addFollowedResults(const QList<Channel * > & list,const quint32 offset,const quint32 total)461 void ChannelManager::addFollowedResults(const QList<Channel *> &list, const quint32 offset, const quint32 total)
462 {
463     //    qDebug() << "Merging channel data for " << list.size()
464     //             << " items with " << offset << " offset.";
465 
466     foreach (Channel *c, list)
467         c->setFavourite(true);
468 
469     favouritesModel->mergeAll(list);
470 
471     if (offset < total)
472         getFollowedChannels(FOLLOWED_FETCH_LIMIT, offset);
473 
474     checkStreams(list);
475 
476     qDeleteAll(list);
477 
478     emit followedUpdated();
479 }
480 
slotNetworkAccessChanged(bool up)481 void ChannelManager::slotNetworkAccessChanged(bool up)
482 {
483     if (up) {
484         if (isAccessTokenAvailable()) {
485             //Relogin
486             favouritesModel->clear();
487             netman->getUser();
488         }
489     } else {
490         qDebug() << "Network went down";
491         favouritesModel->setAllChannelsOffline();
492         resultsModel->setAllChannelsOffline();
493     }
494 }
495 
getUser_id() const496 quint64 ChannelManager::getUser_id() const
497 {
498     return user_id;
499 }
500 
createFollowedChannelsModel()501 ChannelListModel *ChannelManager::createFollowedChannelsModel()
502 {
503     ChannelListModel *model = new ChannelListModel();
504 
505     connect(model, &ChannelListModel::channelOnlineStateChanged, this, &ChannelManager::notify);
506     connect(model, &ChannelListModel::multipleChannelsChangedOnline, this, &ChannelManager::notifyMultipleChannelsOnline);
507 
508     return model;
509 }
510