2
Copyright (C) 2010 Kai Dombrowe <just89@gmx.de>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Lesser General Public
6
License as published by the Free Software Foundation; either
7
version 2.1 of the License, or (at your option) any later version.
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
Lesser General Public License for more details.
14
You should have received a copy of the GNU Lesser General Public
15
License along with this library; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
#include "youtubeprovider.h"
22
#include "responseparser.h"
25
#include <joschycore/abstractnetworklayer.h>
26
#include <joschycore/joschy_global.h>
27
#include <joschycore/video.h>
28
#include <joschycore/postfile.h>
29
#include <joschycore/abstractjob.h>
32
#include <QtCore/QUrl>
33
#include <QtCore/QFile>
34
#include <QtCore/QDir>
39
#define DEV_KEY "AI39si4PPp_RmxGSVs4cHH93rcG2e9vSRQU1vC0L3sfuy_ZHmtaAWZOdvSfBjmow3YSZfrerx"\
40
"jhsZGX0brUrdSLr5qvNchxeiQ"
46
YouTubeProvider::YouTubeProvider(QObject *parent)
47
:Joschy::AbstractProvider(parent)
54
YouTubeProvider::~YouTubeProvider()
62
QStringList YouTubeProvider::categorys() const
66
list << "Autos" << "Comedy" << "Education" << "Entertainment" << "Film";
67
list << "Games" << "Howto" << "Music" << "News" << "Nonprofit";
68
list << "People" << "Animals" << "Tech" << "Sports" << "Travel";
72
return m_categorys.values();
77
bool YouTubeProvider::isAuthenticated(const QString &login) const
80
return m_tokens.contains(login);
85
Joschy::ActionReply YouTubeProvider::authenticate(const QString &login, const QString &password)
89
if (login.isEmpty()) {
90
errorString = tr("Empty login");
93
if (password.isEmpty()) {
94
errorString = tr("Empty password");
97
Joschy::ActionReply reply;
98
if (!errorString.isEmpty()) {
99
reply.setErrorType(Plugin::InvalidArgumentError);
100
reply.setErrorString(errorString);
104
const QUrl url("https://www.google.com/youtube/accounts/ClientLogin");
106
QHash<QByteArray, QByteArray> meta;
107
meta.insert("Content-Type", "application/x-www-form-urlencoded");
108
meta.insert("errorPage", "false");
109
meta.insert("cookies", "none");
111
const QByteArray data = "Email="+login.toLatin1()+"&Passwd="+password.toLatin1()+"&service="\
112
"youtube&source=libjoschyyoutube";
114
const QString id = layer()->post(url, meta, data);
115
m_actions.insert(id, ResponseParser::AuthenticationType);
123
Joschy::ActionReply YouTubeProvider::upload(const QString &login, Joschy::Video *video)
126
Joschy::ActionReply reply;
128
if (!isAuthenticated(login)) {
129
reply.setErrorType(Plugin::NotAuthenticatedError);
130
reply.setErrorString(tr("You need to authenticate first"));
133
QFile file(video->url().toString());
134
if (!file.exists()) {
135
reply.setErrorType(Plugin::FileNotFoundError);
136
reply.setErrorString(tr("Video %1: No such file or directory").arg(video->url().toString()));
139
if (!file.open(QIODevice::ReadOnly)) {
140
reply.setErrorType(Plugin::CannotOpenError);
141
reply.setErrorString(file.errorString());
149
const bool bigVideo = (file.size() > (1024*1024)*10);
150
const QString title = video->title();
151
const QString description = video->description();
152
const QString category = m_categorys.key(video->category());
153
const QString keywords = video->keywords().join(", ");
154
const QString slug = file.fileName().right(file.fileName().lastIndexOf(QDir::separator())+1);
155
const QUrl url("http://uploads.gdata.youtube.com/feeds/api/users/"+login+"/uploads");
156
const QByteArray mime = "application/octet-stream"; // TODO
159
const QByteArray CRLF = "\r\n";
160
const QByteArray BOUNDARY = "f93dcbA3";
161
QString XML = "<?xml version=\"1.0\"?>"+CRLF+\
162
"<entry xmlns=\"http://www.w3.org/2005/Atom\""+CRLF+\
163
"xmlns:media=\"http://search.yahoo.com/mrss/\""+CRLF+\
164
"xmlns:yt=\"http://gdata.youtube.com/schemas/2007\">"+CRLF+\
165
"<media:group>"+CRLF+\
166
"<media:title type=\"plain\">%1</media:title>"+CRLF+\
167
"<media:description type=\"plain\">"+CRLF+\
169
"</media:description>"+CRLF+\
170
"<media:category"+CRLF+\
171
"scheme=\"http://gdata.youtube.com/schemas/2007/categories.cat\">%3"+CRLF+\
172
"</media:category>"+CRLF+\
173
"<media:keywords>%4</media:keywords>"+CRLF+\
174
"</media:group>"+CRLF+\
177
XML = XML.arg(title).arg(description).arg(category).arg(keywords);
180
postData.append("--"+BOUNDARY);
181
postData.append(CRLF);
182
postData.append("Content-Type: application/atom+xml; charset=UTF-8");
183
postData.append(CRLF);
184
postData.append(CRLF);
185
postData.append(XML.toLatin1());
186
postData.append(CRLF);
187
postData.append("--"+BOUNDARY);
188
postData.append(CRLF);
189
postData.append("Content-Type: "+mime);
190
postData.append(CRLF);
191
postData.append("Content-Transfer-Encoding: binary");
192
postData.append(CRLF);
193
postData.append(CRLF);
195
QByteArray bottom = CRLF;
196
bottom.append("--"+BOUNDARY+"--");
200
postData.append(file.readAll());
201
postData.append(bottom);
205
QHash<QByteArray, QByteArray> header;
206
header["Authorization"] = "GoogleLogin auth="+m_tokens[login].toLatin1();
207
header["GData-Version"] = "2";
208
header["X-GData-Key"] = "key="+QString(DEV_KEY).toLatin1();
209
header["Connection"] = "close";
210
header["Slug"] = slug.toLatin1();
211
header["Content-Type"] = "multipart/related; boundary=\""+BOUNDARY+"\"";
215
header["Content-Length"] = QString::number(postData.size()).toLatin1();
217
id = layer()->post(url, header, postData);
219
Joschy::PostFile *data = new Joschy::PostFile(file.fileName());
220
data->setData(postData, bottom);
222
header["Content-Length"] = QString::number(data->size()).toLatin1();
224
id = layer()->post(url, header, data);
225
m_postFiles[id] = data;
227
m_actions.insert(id, ResponseParser::UploadType);
235
Joschy::ActionReply YouTubeProvider::search(const QHash<QString, QVariant> &data)
238
Joschy::ActionReply reply;
241
if (data.contains("Start")) {
242
start = data.value("Start").toInt();
246
if (data.contains("Max")) {
247
max = data.value("Max").toInt();
250
const QString key = data.value("Key").toString();
251
const QString author = data.value("Author").toString();
252
const QString category = data.value("Category").toString();
254
if (start < 1 || max > 50 || key.isEmpty()) {
255
reply.setErrorType(Plugin::InvalidArgumentError);
256
reply.setErrorString(tr("Invalid argument"));
260
QUrl url("http://gdata.youtube.com/feeds/api/videos");
261
url.addQueryItem("q", key);
262
url.addQueryItem("start-index", QString::number(start));
263
url.addQueryItem("max-results", QString::number(max));
265
if (!author.isEmpty()) {
266
url.addQueryItem("author", author);
269
if (!category.isEmpty()) {
270
url.addQueryItem("category", category);
274
JOSCHY_DEBUG() << "url:" << url;
276
reply.setId(layer()->get(url));
278
m_actions[reply.id()] = ResponseParser::SearchType;
285
Joschy::ActionReply YouTubeProvider::updateThumbnail(const Joschy::Video &video, const QString &thumbnailDir)
288
Joschy::ActionReply reply;
290
if (video.thumbnailUrl().isEmpty()) {
291
reply.setErrorType(Plugin::InvalidArgumentError);
292
reply.setErrorString(tr("No thumbnail url given"));
296
QString dir = QDir::cleanPath(thumbnailDir);
297
if (!dir.endsWith(QDir::separator())) {
298
dir.append(QDir::separator());
301
const QString id = layer()->get(video.thumbnailUrl());
302
m_actions[id] = ResponseParser::UpdateThumbnailType;
303
m_thumbnails[id] = dir+video.thumbnail();
312
void YouTubeProvider::init()
315
QVariant var = load("YouTube-Categorys");
316
QHashIterator<QString, QVariant> it(var.toHash());
317
while (it.hasNext()) {
319
m_categorys[it.key()] = it.value().toString();
322
var = load("YouTube-CategoryDate");
323
QDate date = var.toDateTime().date();
325
if (m_categorys.isEmpty() || date.month() != QDate::currentDate().month()) { // update every month
326
JOSCHY_DEBUG() << "update categorys....";
333
void YouTubeProvider::updateCategorys()
336
const QUrl url("http://gdata.youtube.com/schemas/2007/categories.cat");
338
QHash<QByteArray, QByteArray> header;
339
header["Accept-Language"] = QLocale::system().name().toLatin1()+";";
341
const QString id = layer()->get(url, header);
342
m_actions[id] = ResponseParser::UpdateCategorysType;
347
void YouTubeProvider::parserFinished(Joschy::AbstractJob *job)
350
JOSCHY_DEBUG() << "parser finsihed....";
352
Joschy::ResponseParser *parser = static_cast<Joschy::ResponseParser*>(job);
353
const QString id = parser->id();
354
const ResponseParser::Type type = m_actions.take(id);
355
const QString errorString = parser->errorString();
356
const Plugin::ErrorType errorType = parser->errorType();
357
const bool hasError = parser->error();
361
emit error(id, errorType, errorString);
364
case ResponseParser::AuthenticationType: {
365
m_tokens.insert(parser->login(), parser->token());
366
emit authenticated(id);
369
case ResponseParser::UploadType: {
370
emit uploadFinished(id, parser->getVideo());
373
case ResponseParser::SearchType: {
374
QList<Joschy::Video> videos = parser->getVideos();
375
emit searchFinished(id, videos);
378
case ResponseParser::UpdateThumbnailType: {
379
const QString thumbnail = m_thumbnails.take(id);
381
QFile file(thumbnail);
382
if (!file.open(QIODevice::WriteOnly)) {
383
JOSCHY_DEBUG() << "open failed:" << thumbnail << file.errorString();
384
emit error(id, Plugin::CannotOpenError, file.errorString());
386
if (file.write(parser->image()) == -1) {
388
JOSCHY_DEBUG() << file.error() << file.errorString();
389
emit error(id, Plugin::UnknownError, file.errorString());
392
emit thumbnailUpdated(id);
397
case ResponseParser::UpdateCategorysType: {
398
m_categorys = parser->getCategorys();
400
QHash<QString, QVariant> hash;
401
QHashIterator<QString, QString> it(m_categorys);
402
while (it.hasNext()) {
404
hash[it.key()] = it.value();
407
save("YouTube-Categorys", QVariant(hash));
408
save("YouTube-CategoryDate", QVariant(QDateTime::currentDateTime()));
410
emit categorysChanged(categorys());
416
m_parser.removeAll(parser);
423
void YouTubeProvider::jobFinished(const QString &id, const QVariantMap &data,
424
const Joschy::Plugin::ErrorType &errorType,
425
const QString &errorString)
428
JOSCHY_DEBUG() << "job finished";
429
JOSCHY_DEBUG() << "Reply:" << data.value("Reply").toString();
430
JOSCHY_DEBUG() << "Content type:" << data.value("ContentType").toString();
431
JOSCHY_DEBUG() << "Status:" << data.value("Status").toString();
432
JOSCHY_DEBUG() << "error" << bool(errorType != Plugin::NoError);
434
if (m_postFiles.contains(id)) {
435
Joschy::PostFile *file = m_postFiles.take(id);
439
if (data.value("Canceled").toBool() || errorType == Joschy::Plugin::ActionCanceledError) {
440
m_actions.remove(id);
441
emit error(id, Plugin::ActionCanceledError, QString());
445
/* if (errorType != Joschy::Plugin::NoError) {
446
m_actions.remove(id);
447
emit error(id, errorType, errorString);
451
Joschy::ResponseParser *parser = new Joschy::ResponseParser(m_actions.value(id), id, data, this);
452
connect(parser, SIGNAL(finished(Joschy::AbstractJob*)), this, SLOT(parserFinished(Joschy::AbstractJob*)));
454
m_parser.append(parser);
455
switch (m_actions.value(id)) {
456
case ResponseParser::SearchType: parser->setRunInThread(true); break;
457
case ResponseParser::UpdateThumbnailType: parser->setPriority(AbstractJob::LowPriority); break;
458
case ResponseParser::UpdateCategorysType: parser->setPriority(AbstractJob::HighPriority); break;
462
Joschy::Scheduler::schedule(parser);
468
} // namespace Joschy
471
#include "youtubeprovider.moc"