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"
23 #include "httputils.h"
29 #include "iconutils.h"
31 YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
34 loadingThumbnail(false),
41 QHash<QString, YTChannel*> YTChannel::cache;
43 YTChannel* YTChannel::forId(const QString &channelId) {
44 if (channelId.isEmpty()) return 0;
46 auto i = cache.constFind(channelId);
47 if (i != cache.constEnd()) return i.value();
49 QSqlDatabase db = Database::instance().getConnection();
51 query.prepare("select id,name,description,thumb_url,notify_count,watched,checked,loaded "
52 "from subscriptions where user_id=?");
53 query.bindValue(0, channelId);
54 bool success = query.exec();
55 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
57 YTChannel* channel = 0;
59 // Change userId to ChannelId
61 channel = new YTChannel(channelId);
62 channel->id = query.value(0).toInt();
63 channel->displayName = query.value(1).toString();
64 channel->description = query.value(2).toString();
65 channel->thumbnailUrl = query.value(3).toString();
66 channel->notifyCount = query.value(4).toInt();
67 channel->watched = query.value(5).toUInt();
68 channel->checked = query.value(6).toUInt();
69 channel->loaded = query.value(7).toUInt();
70 channel->thumbnail = QPixmap(channel->getThumbnailLocation());
71 channel->thumbnail.setDevicePixelRatio(IconUtils::maxSupportedPixelRatio());
72 channel->maybeLoadfromAPI();
73 cache.insert(channelId, channel);
79 void YTChannel::maybeLoadfromAPI() {
81 if (channelId.isEmpty()) return;
83 uint now = QDateTime::currentDateTime().toTime_t();
84 static const int refreshInterval = 60 * 60 * 24 * 10;
85 if (loaded > now - refreshInterval) return;
89 QUrl url = YT3::instance().method("channels");
91 q.addQueryItem("id", channelId);
92 q.addQueryItem("part", "snippet");
95 QObject *reply = HttpUtils::yt().get(url);
96 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResponse(QByteArray)));
97 connect(reply, SIGNAL(error(QString)), SLOT(requestError(QString)));
100 void YTChannel::parseResponse(const QByteArray &bytes) {
101 QJsonDocument doc = QJsonDocument::fromJson(bytes);
102 QJsonObject obj = doc.object();
103 const QJsonArray items = obj["items"].toArray();
104 for (const QJsonValue &v : items) {
105 QJsonObject item = v.toObject();
106 QJsonObject snippet = item["snippet"].toObject();
107 displayName = snippet["title"].toString();
108 description = snippet["description"].toString();
109 QJsonObject thumbnails = snippet["thumbnails"].toObject();
110 thumbnailUrl = thumbnails["medium"].toObject()["url"].toString();
111 qDebug() << displayName << description << thumbnailUrl;
119 void YTChannel::loadThumbnail() {
120 if (loadingThumbnail) return;
121 if (thumbnailUrl.isEmpty()) return;
122 loadingThumbnail = true;
124 QUrl url(thumbnailUrl);
125 QObject *reply = HttpUtils::yt().get(url);
126 connect(reply, SIGNAL(data(QByteArray)), SLOT(storeThumbnail(QByteArray)));
127 connect(reply, SIGNAL(error(QString)), SLOT(requestError(QString)));
130 const QString & YTChannel::getThumbnailDir() {
131 static const QString thumbDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/channels/";
135 QString YTChannel::getThumbnailLocation() {
136 return getThumbnailDir() + channelId;
139 QString YTChannel::latestVideoId() {
140 QSqlDatabase db = Database::instance().getConnection();
142 query.prepare("select video_id from subscriptions_videos where user_id=? order by published desc limit 1");
143 query.bindValue(0, channelId);
144 bool success = query.exec();
145 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
146 if (!query.next()) return QString();
147 return query.value(0).toString();
150 void YTChannel::unsubscribe() {
151 YTChannel::unsubscribe(channelId);
154 void YTChannel::storeThumbnail(const QByteArray &bytes) {
155 thumbnail.loadFromData(bytes);
156 qreal maxRatio = IconUtils::maxSupportedPixelRatio();
157 thumbnail.setDevicePixelRatio(maxRatio);
158 const int maxWidth = 88 * maxRatio;
161 dir.mkpath(getThumbnailDir());
163 if (thumbnail.width() > maxWidth) {
164 thumbnail = thumbnail.scaledToWidth(maxWidth, Qt::SmoothTransformation);
165 thumbnail.setDevicePixelRatio(maxRatio);
166 thumbnail.save(getThumbnailLocation(), "JPG");
168 QFile file(getThumbnailLocation());
169 if (!file.open(QIODevice::WriteOnly))
170 qWarning() << "Error opening file for writing" << file.fileName();
171 QDataStream stream(&file);
172 stream.writeRawData(bytes.constData(), bytes.size());
175 emit thumbnailLoaded();
176 loadingThumbnail = false;
179 void YTChannel::requestError(const QString &message) {
181 qWarning() << message;
183 loadingThumbnail = false;
186 void YTChannel::storeInfo() {
187 if (channelId.isEmpty()) return;
188 QSqlDatabase db = Database::instance().getConnection();
190 query.prepare("update subscriptions set "
191 "user_name=?, name=?, description=?, thumb_url=?, loaded=? "
193 qDebug() << userName;
194 query.bindValue(0, userName);
195 query.bindValue(1, displayName);
196 query.bindValue(2, description);
197 query.bindValue(3, thumbnailUrl);
198 query.bindValue(4, QDateTime::currentDateTime().toTime_t());
199 query.bindValue(5, channelId);
200 bool success = query.exec();
201 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
206 void YTChannel::subscribe(const QString &channelId) {
207 if (channelId.isEmpty()) return;
209 uint now = QDateTime::currentDateTime().toTime_t();
211 QSqlDatabase db = Database::instance().getConnection();
213 query.prepare("insert into subscriptions "
214 "(user_id,added,watched,checked,views,notify_count)"
215 " values (?,?,?,0,0,0)");
216 query.bindValue(0, channelId);
217 query.bindValue(1, now);
218 query.bindValue(2, now);
219 bool success = query.exec();
220 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
222 // This will call maybeLoadFromApi
223 YTChannel::forId(channelId);
226 void YTChannel::unsubscribe(const QString &channelId) {
227 if (channelId.isEmpty()) return;
228 QSqlDatabase db = Database::instance().getConnection();
230 query.prepare("delete from subscriptions where user_id=?");
231 query.bindValue(0, channelId);
232 bool success = query.exec();
233 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
235 query = QSqlQuery(db);
236 query.prepare("delete from subscriptions_videos where user_id=?");
237 query.bindValue(0, channelId);
238 success = query.exec();
239 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
241 YTChannel *user = cache.take(channelId);
242 if (user) user->deleteLater();
245 bool YTChannel::isSubscribed(const QString &channelId) {
246 if (!Database::exists()) return false;
247 if (channelId.isEmpty()) return false;
248 QSqlDatabase db = Database::instance().getConnection();
250 query.prepare("select count(*) from subscriptions where user_id=?");
251 query.bindValue(0, channelId);
252 bool success = query.exec();
253 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
255 return query.value(0).toInt() > 0;
259 void YTChannel::updateChecked() {
260 if (channelId.isEmpty()) return;
262 uint now = QDateTime::currentDateTime().toTime_t();
265 QSqlDatabase db = Database::instance().getConnection();
267 query.prepare("update subscriptions set checked=? where user_id=?");
268 query.bindValue(0, now);
269 query.bindValue(1, channelId);
270 bool success = query.exec();
271 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
274 void YTChannel::updateWatched() {
275 if (channelId.isEmpty()) return;
277 uint now = QDateTime::currentDateTime().toTime_t();
280 emit notifyCountChanged();
282 QSqlDatabase db = Database::instance().getConnection();
284 query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
285 query.bindValue(0, now);
286 query.bindValue(1, channelId);
287 bool success = query.exec();
288 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
291 void YTChannel::storeNotifyCount(int count) {
292 if (notifyCount != count)
293 emit notifyCountChanged();
296 QSqlDatabase db = Database::instance().getConnection();
298 query.prepare("update subscriptions set notify_count=? where user_id=?");
299 query.bindValue(0, count);
300 query.bindValue(1, channelId);
301 bool success = query.exec();
302 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
305 bool YTChannel::updateNotifyCount() {
306 QSqlDatabase db = Database::instance().getConnection();
308 query.prepare("select count(*) from subscriptions_videos "
309 "where channel_id=? and added>? and published>? and watched=0");
310 query.bindValue(0, id);
311 query.bindValue(1, watched);
312 query.bindValue(2, watched);
313 bool success = query.exec();
314 if (!success) qWarning() << query.lastQuery() << query.lastError().text();
316 qWarning() << __PRETTY_FUNCTION__ << "Count failed";
319 int count = query.value(0).toInt();
320 storeNotifyCount(count);
321 return count != notifyCount;