]> git.sur5r.net Git - minitube/blob - src/paginatedvideosource.cpp
Fixed video order (with sync details)
[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
27 #include "video.h"
28 #include "networkaccess.h"
29
30 namespace The {
31 NetworkAccess* http();
32 QHash<QString, QAction*>* globalActions();
33 }
34
35 PaginatedVideoSource::PaginatedVideoSource(QObject *parent) : VideoSource(parent)
36   , tokenTimestamp(0)
37   , reloadingToken(false)
38   , currentMax(0)
39   , currentStartIndex(0)
40   , asyncDetails(false) { }
41
42 bool PaginatedVideoSource::hasMoreVideos() {
43     qDebug() << __PRETTY_FUNCTION__ << nextPageToken;
44     return !nextPageToken.isEmpty();
45 }
46
47 bool PaginatedVideoSource::maybeReloadToken(int max, int startIndex) {
48     // kind of hackish. Thank the genius who came up with this stateful stuff
49     // in a supposedly RESTful (aka stateless) API.
50
51     if (nextPageToken.isEmpty()) {
52         // previous request did not return a page token. Game over.
53         // emit gotVideos(QList<Video*>());
54         emit finished(0);
55         return true;
56     }
57
58     if (isPageTokenExpired()) {
59         reloadingToken = true;
60         currentMax = max;
61         currentStartIndex = startIndex;
62         reloadToken();
63         return true;
64     }
65     return false;
66 }
67
68 bool PaginatedVideoSource::setPageToken(const QString &value) {
69     tokenTimestamp = QDateTime::currentDateTime().toTime_t();
70     nextPageToken = value;
71
72     if (reloadingToken) {
73         reloadingToken = false;
74         loadVideos(currentMax, currentStartIndex);
75         currentMax = currentStartIndex = 0;
76         return true;
77     }
78
79     return false;
80 }
81
82 bool PaginatedVideoSource::isPageTokenExpired() {
83     uint now = QDateTime::currentDateTime().toTime_t();
84     return now - tokenTimestamp > 1800;
85 }
86
87 void PaginatedVideoSource::reloadToken() {
88     qDebug() << "Reloading pageToken";
89     QObject *reply = The::http()->get(lastUrl);
90     connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
91     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
92 }
93
94 void PaginatedVideoSource::loadVideoDetails(const QList<Video*> &videos) {
95     QString videoIds;
96     foreach (Video *video, videos) {
97         // TODO get video details from cache
98         if (!videoIds.isEmpty()) videoIds += ",";
99         videoIds += video->id();
100         this->videos = videos;
101         videoMap.insert(video->id(), video);
102     }
103
104     if (videoIds.isEmpty()) {
105         if (!asyncDetails) {
106             emit gotVideos(videos);
107             emit finished(videos.size());
108         }
109         return;
110     }
111
112     QUrl url = YT3::instance().method("videos");
113
114 #if QT_VERSION >= 0x050000
115     {
116         QUrl &u = url;
117         QUrlQuery url;
118 #endif
119
120         url.addQueryItem("part", "contentDetails,statistics");
121         url.addQueryItem("id", videoIds);
122
123 #if QT_VERSION >= 0x050000
124         u.setQuery(url);
125     }
126 #endif
127
128     QObject *reply = The::http()->get(url);
129     connect(reply, SIGNAL(data(QByteArray)), SLOT(parseVideoDetails(QByteArray)));
130     connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
131 }
132
133 void PaginatedVideoSource::parseVideoDetails(const QByteArray &bytes) {
134
135     QScriptEngine engine;
136     QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
137
138     QScriptValue items = json.property("items");
139     if (items.isArray()) {
140         QScriptValueIterator it(items);
141         while (it.hasNext()) {
142             it.next();
143             QScriptValue item = it.value();
144             if (!item.isObject()) continue;
145
146             // qDebug() << item.toString();
147
148             QString id = item.property("id").toString();
149             Video *video = videoMap.value(id);
150             if (!video) {
151                 qWarning() << "No video for id" << id;
152                 continue;
153             }
154
155             QString isoPeriod = item.property("contentDetails").property("duration").toString();
156             int duration = DataUtils::parseIsoPeriod(isoPeriod);
157             video->setDuration(duration);
158
159             uint viewCount = item.property("statistics").property("viewCount").toUInt32();
160             video->setViewCount(viewCount);
161
162             // TODO cache by etag?
163         }
164     }
165     if (!asyncDetails) {
166         emit gotVideos(videos);
167         emit finished(videos.size());
168     } else {
169         emit gotDetails();
170     }
171 }