1 /*
2 This file is part of Choqok, the KDE micro-blogging client
3
4 Copyright (C) 2014 Eugene Shalygin <eugene.shalygin@gmail.com>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see http://www.gnu.org/licenses/
21
22 */
23
24 #include "longurl.h"
25
26 #include <QDebug>
27 #include <QJsonDocument>
28 #include <QSharedPointer>
29 #include <QTimer>
30
31 #include <KIO/TransferJob>
32 #include <KPluginFactory>
33
34 #include "postwidget.h"
35 #include "shortenmanager.h"
36
37 K_PLUGIN_FACTORY_WITH_JSON(LongUrlFactory, "choqok_longurl.json",
38 registerPlugin < LongUrl > ();)
39
40 const QString baseLongUrlDorComUrl = QLatin1String("http://api.longurl.org/v2/");
41
LongUrl(QObject * parent,const QList<QVariant> &)42 LongUrl::LongUrl(QObject *parent, const QList< QVariant > &)
43 : Choqok::Plugin(QLatin1String("choqok_longurl"), parent)
44 , state(Stopped), mServicesAreFetched(false)
45 {
46 sheduleSupportedServicesFetch();
47 connect(Choqok::UI::Global::SessionManager::self(),
48 SIGNAL(newPostWidgetAdded(Choqok::UI::PostWidget*,Choqok::Account*,QString)),
49 this,
50 SLOT(slotAddNewPostWidget(Choqok::UI::PostWidget*)));
51 }
52
~LongUrl()53 LongUrl::~LongUrl()
54 {
55 suspendJobs();
56 mData.clear();
57 mShortUrls.clear();
58 for (KJob *job: mParsingList.keys()) {
59 job->kill();
60 }
61 mParsingList.clear();
62 }
63
parse(QPointer<Choqok::UI::PostWidget> postToParse)64 void LongUrl::parse(QPointer< Choqok::UI::PostWidget > postToParse)
65 {
66 if (!postToParse) {
67 return;
68 }
69 QStringList redirectList, pureList = postToParse->urls();
70 QString content = postToParse->currentPost()->content;
71 for (int i = 0; i < pureList.count(); ++i) {
72 if (pureList[i].length() > 30) {
73 continue;
74 }
75 if (!pureList[i].startsWith(QLatin1String("http"), Qt::CaseInsensitive)) {
76 pureList[i].prepend(QLatin1String("http://"));
77 }
78 redirectList << pureList[i];
79 }
80 for (const QString &url: redirectList) {
81 KJob *job = sheduleParsing(url);
82 if (job) {
83 mParsingList.insert(job, postToParse);
84 job->start();
85 }
86 }
87 }
88
processJobResults(KJob * job)89 void LongUrl::processJobResults(KJob *job)
90 {
91 const QJsonDocument json = QJsonDocument::fromJson(mData[job]);
92 if (json.isNull()) {
93 return;
94 }
95 const QVariantMap m = json.toVariant().toMap();
96 const QUrl longUrl = m.value(QLatin1String("long-url")).toUrl();
97 replaceUrl(takeJob(job), QUrl(mShortUrls.take(job)), longUrl);
98 }
99
startParsing()100 void LongUrl::startParsing()
101 {
102 int i = 8;
103 while (!postsQueue.isEmpty() && i > 0) {
104 parse(postsQueue.dequeue());
105 --i;
106 }
107
108 if (postsQueue.isEmpty()) {
109 state = Stopped;
110 } else {
111 QTimer::singleShot(500, this, SLOT(startParsing()));
112 }
113 }
114
replaceUrl(LongUrl::PostWidgetPointer post,const QUrl & fromUrl,const QUrl & toUrl)115 void LongUrl::replaceUrl(LongUrl::PostWidgetPointer post, const QUrl &fromUrl, const QUrl &toUrl)
116 {
117 if (post) {
118 QString content = post->content();
119 QString fromUrlStr = fromUrl.url();
120 content.replace(QRegExp(QLatin1String("title='") + fromUrlStr + QLatin1Char('\'')), QLatin1String("title='") + toUrl.url() + QLatin1Char('\''));
121 content.replace(QRegExp(QLatin1String("href='") + fromUrlStr + QLatin1Char('\'')), QLatin1String("href='") + toUrl.url() + QLatin1Char('\''));
122 post->setContent(content);
123 Choqok::ShortenManager::self()->emitNewUnshortenedUrl(post, fromUrl, toUrl);
124 }
125 }
126
sheduleSupportedServicesFetch()127 void LongUrl::sheduleSupportedServicesFetch()
128 {
129 mServicesAreFetched = true;
130 mServicesData = QSharedPointer<QByteArray>(new QByteArray());
131 KIO::TransferJob *job = KIO::get(QUrl(baseLongUrlDorComUrl + QLatin1String("services?format=json")), KIO::NoReload, KIO::HideProgressInfo);
132 connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(servicesDataReceived(KIO::Job*,QByteArray)));
133 connect(job, SIGNAL(result(KJob*)), SLOT(servicesJobResult(KJob*)));
134 }
135
servicesDataReceived(KIO::Job * job,QByteArray data)136 void LongUrl::servicesDataReceived(KIO::Job *job, QByteArray data)
137 {
138 Q_UNUSED(job);
139 mServicesData->append(data);
140 }
141
servicesJobResult(KJob * job)142 void LongUrl::servicesJobResult(KJob *job)
143 {
144 if (!job->error()) {
145 const QJsonDocument json = QJsonDocument::fromJson(*mServicesData);
146 if (!json.isNull()) {
147 supportedServices = json.toVariant().toMap().uniqueKeys();
148 }
149 } else {
150 qCritical() << "Job Error:" << job->errorString();
151 }
152 mServicesAreFetched = false;
153 mServicesData.clear();
154 }
155
isServiceSupported(const QString & host)156 bool LongUrl::isServiceSupported(const QString &host)
157 {
158 return supportedServices.contains(host);
159 }
160
sheduleParsing(const QString & shortUrl)161 KJob *LongUrl::sheduleParsing(const QString &shortUrl)
162 {
163 QUrl url(shortUrl);
164 if (isServiceSupported(url.host())) {
165 QUrl request = QUrl(baseLongUrlDorComUrl + QLatin1String("expand"));
166 QUrlQuery requestQuery;
167 requestQuery.addQueryItem(QLatin1String("url"), url.url());
168 requestQuery.addQueryItem(QLatin1String("format"), QLatin1String("json"));
169 requestQuery.addQueryItem(QLatin1String("user-agent"), QLatin1String("Choqok"));
170 request.setQuery(requestQuery);
171
172 KIO::TransferJob *job = KIO::get(request, KIO::NoReload, KIO::HideProgressInfo);
173 mData.insert(job, QByteArray());
174 mShortUrls.insert(job, shortUrl);
175 connect(job, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(dataReceived(KIO::Job*,QByteArray)));
176 connect(job, SIGNAL(result(KJob*)), SLOT(jobResult(KJob*)));
177 return job;
178 }
179 return 0;
180 }
181
dataReceived(KIO::Job * job,QByteArray data)182 void LongUrl::dataReceived(KIO::Job *job, QByteArray data)
183 {
184 mData[job].append(data);
185 }
186
jobResult(KJob * job)187 void LongUrl::jobResult(KJob *job)
188 {
189 if (!job->error()) {
190 processJobResults(job);
191 }
192 mData.remove(job);
193 mShortUrls.remove(job);
194 mParsingList.remove(job);
195 }
196
slotAddNewPostWidget(Choqok::UI::PostWidget * newWidget)197 void LongUrl::slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget)
198 {
199 postsQueue.enqueue(newWidget);
200 if (state == Stopped && !mServicesAreFetched) {
201 state = Running;
202 QTimer::singleShot(1000, this, SLOT(startParsing()));
203 }
204 }
205
aboutToUnload()206 void LongUrl::aboutToUnload()
207 {
208 suspendJobs();
209 base::aboutToUnload();
210 }
211
suspendJobs()212 void LongUrl::suspendJobs()
213 {
214 for (KJob *job: mParsingList.keys()) {
215 job->suspend();
216 }
217 }
218
219 #include "longurl.moc"
220