3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
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.
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.
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/>.
21 #include "ytchannel.h"
22 #include "networkaccess.h"
30 #include "compatibility/qurlqueryhelper.h"
31 #include "compatibility/pathsservice.h"
34 NetworkAccess* http();
37 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
40 loadingThumbnail(false),
47 QHash<QString, YTChannel*> YTChannel::cache;
49 YTChannel* YTChannel::forId(const QString &channelId) {
50 if (channelId.isEmpty()) return 0;
52 if (cache.contains(channelId))
53 return cache.value(channelId);
55 QSqlDatabase db = Database::instance().getConnection();
57 query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
58 "from subscriptions where user_id=?");
59 query.bindValue(0, channelId);
60 bool success = query.exec();
61 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
63 YTChannel* channel = 0;
65 // Change userId to ChannelId
67 channel = new YTChannel(channelId);
68 channel->id = query.value(0).toInt();
69 channel->displayName = query.value(1).toString();
70 channel->description = query.value(2).toString();
71 channel->thumbnailUrl = query.value(3).toString();
72 channel->notifyCount = query.value(4).toInt();
73 channel->watched = query.value(5).toUInt();
74 channel->checked = query.value(6).toUInt();
75 channel->loaded = query.value(7).toUInt();
76 channel->thumbnail = QPixmap(channel->getThumbnailLocation());
77 channel->maybeLoadfromAPI();
78 cache.insert(channelId, channel);
84 void YTChannel::maybeLoadfromAPI() {
86 if (channelId.isEmpty()) return;
88 uint now = QDateTime::currentDateTime().toTime_t();
89 static const int refreshInterval = 60 * 60 * 24 * 10;
90 if (loaded > now - refreshInterval) return;
96 QUrl url = YT3::instance().method("channels");
98 QUrlQueryHelper urlHelper(url);
99 urlHelper.addQueryItem("id", channelId);
100 urlHelper.addQueryItem("part", "snippet");
105 QUrl url("http://gdata.youtube.com/feeds/api/users/" + channelId);
107 QUrlQueryHelper urlHelper(url);
108 urlHelper.addQueryItem("v", "2");
113 QObject *reply = The::http()->get(url);
114 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
115 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
120 void YTChannel::parseResponse(const QByteArray &bytes) {
121 QScriptEngine engine;
122 QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
123 QScriptValue items = json.property("items");
124 if (items.isArray()) {
125 QScriptValueIterator it(items);
126 while (it.hasNext()) {
128 QScriptValue item = it.value();
129 // For some reason the array has an additional element containing its size.
130 if (item.isObject()) {
131 QScriptValue snippet = item.property("snippet");
132 displayName = snippet.property("title").toString();
133 description = snippet.property("description").toString();
134 QScriptValue thumbnails = snippet.property("thumbnails");
135 thumbnailUrl = thumbnails.property("default").property("url").toString();
136 qDebug() << displayName << description << thumbnailUrl;
148 void YTChannel::parseResponse(const QByteArray &bytes) {
149 QXmlStreamReader xml(bytes);
150 xml.readNextStartElement();
151 if (xml.name() == QLatin1String("entry"))
152 while(xml.readNextStartElement()) {
153 const QStringRef n = xml.name();
154 if (n == QLatin1String("summary"))
155 description = xml.readElementText().simplified();
156 else if (n == QLatin1String("title"))
157 displayName = xml.readElementText();
158 else if (n == QLatin1String("thumbnail")) {
159 thumbnailUrl = xml.attributes().value("url").toString();
160 xml.skipCurrentElement();
161 } else if (n == QLatin1String("username"))
162 userName = xml.readElementText();
163 else xml.skipCurrentElement();
166 if (xml.hasError()) {
167 emit error(xml.errorString());
168 qWarning() << xml.errorString();
178 void YTChannel::loadThumbnail() {
179 if (loadingThumbnail) return;
180 if (thumbnailUrl.isEmpty()) return;
181 loadingThumbnail = true;
184 thumbnailUrl.replace(QLatin1String("https://"), QLatin1String("http://"));
187 QUrl url(thumbnailUrl);
188 QObject *reply = The::http()->get(url);
189 connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
190 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
193 const QString & YTChannel::getThumbnailDir() {
194 static const QString thumbDir = Paths::getCacheLocation() + "/channels/";
198 QString YTChannel::getThumbnailLocation() {
199 return getThumbnailDir() + channelId;
202 QString YTChannel::latestVideoId() {
203 QSqlDatabase db = Database::instance().getConnection();
205 query.prepare("select video_id from subscriptions_videos where user_id=? order by published desc limit 1");
206 query.bindValue(0, channelId);
207 bool success = query.exec();
208 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
209 if (!query.next()) return QString();
210 return query.value(0).toString();
213 void YTChannel::unsubscribe() {
214 YTChannel::unsubscribe(channelId);
217 void YTChannel::storeThumbnail(const QByteArray &bytes) {
218 thumbnail.loadFromData(bytes);
219 static const int maxWidth = 88;
222 dir.mkpath(getThumbnailDir());
224 if (thumbnail.width() > maxWidth) {
225 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
226 thumbnail.save(getThumbnailLocation(), "JPG");
228 QFile file(getThumbnailLocation());
229 if (!file.open(QIODevice::WriteOnly))
230 qWarning() << "Error opening file for writing" << file.fileName();
231 QDataStream stream(&file);
232 stream.writeRawData(bytes.constData(), bytes.size());
235 emit thumbnailLoaded();
236 loadingThumbnail = false;
239 void YTChannel::requestError(QNetworkReply *reply) {
240 emit error(reply->errorString());
241 qWarning() << reply->errorString();
243 loadingThumbnail = false;
246 void YTChannel::storeInfo() {
247 if (channelId.isEmpty()) return;
248 QSqlDatabase db = Database::instance().getConnection();
250 query.prepare("update subscriptions set "
251 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
253 qDebug() << userName;
254 query.bindValue(0, userName);
255 query.bindValue(1, displayName);
256 query.bindValue(2, description);
257 query.bindValue(3, thumbnailUrl);
258 query.bindValue(4, QDateTime::currentDateTime().toTime_t());
259 query.bindValue(5, channelId);
260 bool success = query.exec();
261 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
266 void YTChannel::subscribe(const QString &channelId) {
267 if (channelId.isEmpty()) return;
269 uint now = QDateTime::currentDateTime().toTime_t();
271 QSqlDatabase db = Database::instance().getConnection();
273 query.prepare("insert into subscriptions "
274 "(user_id,added,watched,checked,views,notify_count)"
275 " values (?,?,?,0,0,0)");
276 query.bindValue(0, channelId);
277 query.bindValue(1, now);
278 query.bindValue(2, now);
279 bool success = query.exec();
280 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
282 // This will call maybeLoadFromApi
283 YTChannel::forId(channelId);
286 void YTChannel::unsubscribe(const QString &channelId) {
287 if (channelId.isEmpty()) return;
288 QSqlDatabase db = Database::instance().getConnection();
290 query.prepare("delete from subscriptions where user_id=?");
291 query.bindValue(0, channelId);
292 bool success = query.exec();
293 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
295 query = QSqlQuery(db);
296 query.prepare("delete from subscriptions_videos where user_id=?");
297 query.bindValue(0, channelId);
298 success = query.exec();
299 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
301 YTChannel *user = cache.take(channelId);
302 if (user) user->deleteLater();
305 bool YTChannel::isSubscribed(const QString &channelId) {
306 if (!Database::exists()) return false;
307 if (channelId.isEmpty()) return false;
308 QSqlDatabase db = Database::instance().getConnection();
310 query.prepare("select count(*) from subscriptions where user_id=?");
311 query.bindValue(0, channelId);
312 bool success = query.exec();
313 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
315 return query.value(0).toInt() > 0;
319 void YTChannel::updateChecked() {
320 if (channelId.isEmpty()) return;
322 uint now = QDateTime::currentDateTime().toTime_t();
325 QSqlDatabase db = Database::instance().getConnection();
327 query.prepare("update subscriptions set checked=? where user_id=?");
328 query.bindValue(0, now);
329 query.bindValue(1, channelId);
330 bool success = query.exec();
331 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
334 void YTChannel::updateWatched() {
335 if (channelId.isEmpty()) return;
337 uint now = QDateTime::currentDateTime().toTime_t();
340 emit notifyCountChanged();
342 QSqlDatabase db = Database::instance().getConnection();
344 query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
345 query.bindValue(0, now);
346 query.bindValue(1, channelId);
347 bool success = query.exec();
348 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
351 void YTChannel::storeNotifyCount(int count) {
352 if (notifyCount != count)
353 emit notifyCountChanged();
356 QSqlDatabase db = Database::instance().getConnection();
358 query.prepare("update subscriptions set notify_count=? where user_id=?");
359 query.bindValue(0, count);
360 query.bindValue(1, channelId);
361 bool success = query.exec();
362 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
365 bool YTChannel::updateNotifyCount() {
366 QSqlDatabase db = Database::instance().getConnection();
368 query.prepare("select count(*) from subscriptions_videos "
369 "where channel_id=? and added>? and published>? and watched=0");
370 query.bindValue(0, id);
371 query.bindValue(1, watched);
372 query.bindValue(2, watched);
373 bool success = query.exec();
374 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
376 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
379 int count = query.value(0).toInt();
380 storeNotifyCount(count);
381 return count != notifyCount;