]> git.sur5r.net Git - minitube/blob - src/paginatedvideosource.cpp
fe98ba664a2d1c501b55466355f79a8a1a4816e5
[minitube] / src / paginatedvideosource.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Minitube 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 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "paginatedvideosource.h"
22
23 #include "yt3.h"
24 #include "yt3listparser.h"
25 #include "datautils.h"
26 #include "compatibility/qurlqueryhelper.h"
27
28 #include "video.h"
29 #include "networkaccess.h"
30
31 namespace The {
32 NetworkAccess* http();
33 QHash<QString, QAction*>* globalActions();
34 }
35
36 PaginatedVideoSource::PaginatedVideoSource(QObject *parent) : VideoSource(parent)
37   , tokenTimestamp(0)
38   , currentMax(0)
39   , currentStartIndex(0)
40   , reloadingToken(false)
41   , asyncDetails(false) { }
42
43 bool PaginatedVideoSource::hasMoreVideos() {
44     qDebug() << __PRETTY_FUNCTION__ << nextPageToken;
45     return !nextPageToken.isEmpty();
46 }
47
48 bool PaginatedVideoSource::maybeReloadToken(int max, int startIndex) {
49     // kind of hackish. Thank the genius who came up with this stateful stuff
50     // in a supposedly RESTful (aka stateless) API.
51
52     if (nextPageToken.isEmpty()) {
53         // previous request did not return a page token. Game over.
54         // emit gotVideos(QList<Video*>());
55         emit finished(0);
56         return true;
57     }
58
59     if (isPageTokenExpired()) {
60         reloadingToken = true;
61         currentMax = max;
62         currentStartIndex = startIndex;
63         reloadToken();
64         return true;
65     }
66     return false;
67 }
68
69 bool PaginatedVideoSource::setPageToken(const QString &value) {
70     tokenTimestamp = QDateTime::currentDateTime().toTime_t();
71     nextPageToken = value;
72
73     if (reloadingToken) {
74         reloadingToken = false;
75         loadVideos(currentMax, currentStartIndex);
76         currentMax = currentStartIndex = 0;
77         return true;
78     }
79
80     return false;
81 }
82
83 bool PaginatedVideoSource::isPageTokenExpired() {
84     uint now = QDateTime::currentDateTime().toTime_t();
85     return now - tokenTimestamp > 1800;
86 }
87
88 void PaginatedVideoSource::reloadToken() {
89     qDebug() << "Reloading pageToken";
90     QObject *reply = The::http()->get(lastUrl);
91     connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
92     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
93 }
94
95 void PaginatedVideoSource::loadVideoDetails(const QList<Video*> &videos) {
96     QString videoIds;
97     foreach (Video *video, videos) {
98         // TODO get video details from cache
99         if (!videoIds.isEmpty()) videoIds += ",";
100         videoIds += video->id();
101         this->videos = videos;
102         videoMap.insert(video->id(), video);
103     }
104
105     if (videoIds.isEmpty()) {
106         if (!asyncDetails) {
107             emit gotVideos(videos);
108             emit finished(videos.size());
109         }
110         return;
111     }
112
113     QUrl url = YT3::instance().method("videos");
114     {
115         QUrlQueryHelper urlHelper(url);
116         urlHelper.addQueryItem("part", "contentDetails,statistics");
117         urlHelper.addQueryItem("id", videoIds);
118     }
119
120     QObject *reply = The::http()->get(url);
121     connect(reply, SIGNAL(data(QByteArray)), SLOT(parseVideoDetails(QByteArray)));
122     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
123 }
124
125 void PaginatedVideoSource::parseVideoDetails(const QByteArray &bytes) {
126
127     QScriptEngine engine;
128     QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
129
130     QScriptValue items = json.property("items");
131     if (items.isArray()) {
132         QScriptValueIterator it(items);
133         while (it.hasNext()) {
134             it.next();
135             QScriptValue item = it.value();
136             if (!item.isObject()) continue;
137
138             // qDebug() << item.toString();
139
140             QString id = item.property("id").toString();
141             Video *video = videoMap.value(id);
142             if (!video) {
143                 qWarning() << "No video for id" << id;
144                 continue;
145             }
146
147             QString isoPeriod = item.property("contentDetails").property("duration").toString();
148             int duration = DataUtils::parseIsoPeriod(isoPeriod);
149             video->setDuration(duration);
150
151             uint viewCount = item.property("statistics").property("viewCount").toUInt32();
152             video->setViewCount(viewCount);
153
154             // TODO cache by etag?
155         }
156     }
157     if (!asyncDetails) {
158         emit gotVideos(videos);
159         emit finished(videos.size());
160     } else {
161         emit gotDetails();
162     }
163 }