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