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"
33 NetworkAccess* http();
36 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
39 loadingThumbnail(false),
46 QHash<QString, YTChannel*> YTChannel::cache;
48 YTChannel* YTChannel::forId(const QString &channelId) {
49 if (channelId.isEmpty()) return 0;
51 if (cache.contains(channelId))
52 return cache.value(channelId);
54 QSqlDatabase db = Database::instance().getConnection();
56 query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
57 "from subscriptions where user_id=?");
58 query.bindValue(0, channelId);
59 bool success = query.exec();
60 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
62 YTChannel* channel = 0;
64 // Change userId to ChannelId
66 channel = new YTChannel(channelId);
67 channel->id = query.value(0).toInt();
68 channel->displayName = query.value(1).toString();
69 channel->description = query.value(2).toString();
70 channel->thumbnailUrl = query.value(3).toString();
71 channel->notifyCount = query.value(4).toInt();
72 channel->watched = query.value(5).toUInt();
73 channel->checked = query.value(6).toUInt();
74 channel->loaded = query.value(7).toUInt();
75 channel->thumbnail = QPixmap(channel->getThumbnailLocation());
76 channel->maybeLoadfromAPI();
77 cache.insert(channelId, channel);
83 void YTChannel::maybeLoadfromAPI() {
85 if (channelId.isEmpty()) return;
87 uint now = QDateTime::currentDateTime().toTime_t();
88 static const int refreshInterval = 60 * 60 * 24 * 10;
89 if (loaded > now - refreshInterval) return;
95 QUrl url = YT3::instance().method("channels");
97 QUrlQueryHelper urlHelper(url);
98 urlHelper.addQueryItem("id", channelId);
99 urlHelper.addQueryItem("part", "snippet");
104 QUrl url("http://gdata.youtube.com/feeds/api/users/" + channelId);
106 QUrlQueryHelper urlHelper(url);
107 urlHelper.addQueryItem("v", "2");
112 QObject *reply = The::http()->get(url);
113 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
114 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
119 void YTChannel::parseResponse(const QByteArray &bytes) {
120 QScriptEngine engine;
121 QScriptValue json = engine.evaluate("(" + QString::fromUtf8(bytes) + ")");
122 QScriptValue items = json.property("items");
123 if (items.isArray()) {
124 QScriptValueIterator it(items);
125 while (it.hasNext()) {
127 QScriptValue item = it.value();
128 // For some reason the array has an additional element containing its size.
129 if (item.isObject()) {
130 QScriptValue snippet = item.property("snippet");
131 displayName = snippet.property("title").toString();
132 description = snippet.property("description").toString();
133 QScriptValue thumbnails = snippet.property("thumbnails");
134 thumbnailUrl = thumbnails.property("default").property("url").toString();
135 qDebug() << displayName << description << thumbnailUrl;
147 void YTChannel::parseResponse(const QByteArray &bytes) {
148 QXmlStreamReader xml(bytes);
149 xml.readNextStartElement();
150 if (xml.name() == QLatin1String("entry"))
151 while(xml.readNextStartElement()) {
152 const QStringRef n = xml.name();
153 if (n == QLatin1String("summary"))
154 description = xml.readElementText().simplified();
155 else if (n == QLatin1String("title"))
156 displayName = xml.readElementText();
157 else if (n == QLatin1String("thumbnail")) {
158 thumbnailUrl = xml.attributes().value("url").toString();
159 xml.skipCurrentElement();
160 } else if (n == QLatin1String("username"))
161 userName = xml.readElementText();
162 else xml.skipCurrentElement();
165 if (xml.hasError()) {
166 emit error(xml.errorString());
167 qWarning() << xml.errorString();
177 void YTChannel::loadThumbnail() {
178 if (loadingThumbnail) return;
179 if (thumbnailUrl.isEmpty()) return;
180 loadingThumbnail = true;
183 thumbnailUrl.replace(QLatin1String("https://"), QLatin1String("http://"));
186 QUrl url(thumbnailUrl);
187 QObject *reply = The::http()->get(url);
188 connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
189 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
192 const QString & YTChannel::getThumbnailDir() {
193 static const QString thumbDir =
194 #if QT_VERSION >= 0x050000
195 QStandardPaths::writableLocation(QStandardPaths::DataLocation)
197 QDesktopServices::storageLocation(QDesktopServices::DataLocation)
203 QString YTChannel::getThumbnailLocation() {
204 return getThumbnailDir() + channelId;
207 void YTChannel::unsubscribe() {
208 YTChannel::unsubscribe(channelId);
211 void YTChannel::storeThumbnail(const QByteArray &bytes) {
212 thumbnail.loadFromData(bytes);
213 static const int maxWidth = 88;
216 dir.mkpath(getThumbnailDir());
218 if (thumbnail.width() > maxWidth) {
219 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
220 thumbnail.save(getThumbnailLocation(), "JPG");
222 QFile file(getThumbnailLocation());
223 if (!file.open(QIODevice::WriteOnly))
224 qWarning() << "Error opening file for writing" << file.fileName();
225 QDataStream stream(&file);
226 stream.writeRawData(bytes.constData(), bytes.size());
229 emit thumbnailLoaded();
230 loadingThumbnail = false;
233 void YTChannel::requestError(QNetworkReply *reply) {
234 emit error(reply->errorString());
235 qWarning() << reply->errorString();
237 loadingThumbnail = false;
240 void YTChannel::storeInfo() {
241 if (channelId.isEmpty()) return;
242 QSqlDatabase db = Database::instance().getConnection();
244 query.prepare("update subscriptions set "
245 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
247 qDebug() << userName;
248 query.bindValue(0, userName);
249 query.bindValue(1, displayName);
250 query.bindValue(2, description);
251 query.bindValue(3, thumbnailUrl);
252 query.bindValue(4, QDateTime::currentDateTime().toTime_t());
253 query.bindValue(5, channelId);
254 bool success = query.exec();
255 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
260 void YTChannel::subscribe(const QString &channelId) {
261 if (channelId.isEmpty()) return;
263 uint now = QDateTime::currentDateTime().toTime_t();
265 QSqlDatabase db = Database::instance().getConnection();
267 query.prepare("insert into subscriptions "
268 "(user_id,added,watched,checked,views,notify_count)"
269 " values (?,?,?,0,0,0)");
270 query.bindValue(0, channelId);
271 query.bindValue(1, now);
272 query.bindValue(2, now);
273 bool success = query.exec();
274 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
276 // This will call maybeLoadFromApi
277 YTChannel::forId(channelId);
280 void YTChannel::unsubscribe(const QString &channelId) {
281 if (channelId.isEmpty()) return;
282 QSqlDatabase db = Database::instance().getConnection();
284 query.prepare("delete from subscriptions where user_id=?");
285 query.bindValue(0, channelId);
286 bool success = query.exec();
287 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
289 query = QSqlQuery(db);
290 query.prepare("delete from subscriptions_videos where user_id=?");
291 query.bindValue(0, channelId);
292 success = query.exec();
293 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
295 YTChannel *user = cache.take(channelId);
296 if (user) user->deleteLater();
299 bool YTChannel::isSubscribed(const QString &channelId) {
300 if (!Database::exists()) return false;
301 if (channelId.isEmpty()) return false;
302 QSqlDatabase db = Database::instance().getConnection();
304 query.prepare("select count(*) from subscriptions where user_id=?");
305 query.bindValue(0, channelId);
306 bool success = query.exec();
307 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
309 return query.value(0).toInt() > 0;
313 void YTChannel::updateChecked() {
314 if (channelId.isEmpty()) return;
316 uint now = QDateTime::currentDateTime().toTime_t();
319 QSqlDatabase db = Database::instance().getConnection();
321 query.prepare("update subscriptions set checked=? where user_id=?");
322 query.bindValue(0, now);
323 query.bindValue(1, channelId);
324 bool success = query.exec();
325 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
328 void YTChannel::updateWatched() {
329 if (channelId.isEmpty()) return;
331 uint now = QDateTime::currentDateTime().toTime_t();
334 emit notifyCountChanged();
336 QSqlDatabase db = Database::instance().getConnection();
338 query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
339 query.bindValue(0, now);
340 query.bindValue(1, channelId);
341 bool success = query.exec();
342 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
345 void YTChannel::storeNotifyCount(int count) {
346 if (notifyCount != count)
347 emit notifyCountChanged();
350 QSqlDatabase db = Database::instance().getConnection();
352 query.prepare("update subscriptions set notify_count=? where user_id=?");
353 query.bindValue(0, count);
354 query.bindValue(1, channelId);
355 bool success = query.exec();
356 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
359 bool YTChannel::updateNotifyCount() {
360 QSqlDatabase db = Database::instance().getConnection();
362 query.prepare("select count(*) from subscriptions_videos "
363 "where channel_id=? and added>? and published>? and watched=0");
364 query.bindValue(0, id);
365 query.bindValue(1, watched);
366 query.bindValue(2, watched);
367 bool success = query.exec();
368 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
370 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
373 int count = query.value(0).toInt();
374 storeNotifyCount(count);
375 return count != notifyCount;