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