2 Copyright 2013-2015 Mats Sjöberg
4 This file is part of the Pumpa programme.
6 Pumpa is free software: you can redistribute it and/or modify it
7 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 Pumpa is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Pumpa. If not, see <http://www.gnu.org/licenses/>.
22 #include <QInputDialog>
25 #include <QDesktopServices>
31 #include "filedownloader.h"
33 #include "editprofiledialog.h"
35 //------------------------------------------------------------------------------
37 PumpApp::PumpApp(PumpaSettings
* settings
, QString locale
, QWidget
* parent
) :
43 m_messageWindow(NULL
),
44 m_editProfileDialog(NULL
),
50 if (m_locale
.isEmpty())
53 QASpell::setLocale(m_locale
);
58 QASActor::setHiddenAuthors(m_s
->hideAuthors());
60 // for old users set use_markdown=true, false for new installs
61 if (!m_s
->firstStart() && !m_s
->contains("General/use_markdown")) {
62 qDebug() << "Setting Markdown on by default for old users.";
63 m_s
->useMarkdown(true);
66 m_settingsDialog
= new PumpaSettingsDialog(m_s
, this);
67 connect(m_settingsDialog
, SIGNAL(newAccount()),
68 this, SLOT(launchOAuthWizard()));
70 QString linkColorStr
= m_s
->linkColor();
71 if (!linkColorStr
.isEmpty()) {
72 QColor
linkColor(linkColorStr
);
73 if (linkColor
.isValid()) {
74 QPalette
pal(qApp
->palette());
75 pal
.setColor(QPalette::Link
, linkColor
);
76 pal
.setColor(QPalette::LinkVisited
, linkColor
);
77 qApp
->setPalette(pal
);
79 qDebug() << "[ERROR] cannot parse link_color \"" + linkColorStr
+ "\"";
83 m_nam
= new QNetworkAccessManager(this);
84 connect(m_nam
, SIGNAL(sslErrors(QNetworkReply
*, QList
<QSslError
>)),
85 this, SLOT(onSslErrors(QNetworkReply
*, QList
<QSslError
>)));
87 m_oam
= new KQOAuthManager(this);
88 connect(m_oam
, SIGNAL(authorizedRequestReady(QByteArray
, int)),
89 this, SLOT(onAuthorizedRequestReady(QByteArray
, int)));
90 connect(m_oam
, SIGNAL(sslErrors(QNetworkReply
*, QList
<QSslError
>)),
91 this, SLOT(onSslErrors(QNetworkReply
*, QList
<QSslError
>)));
93 m_fdm
= FileDownloadManager::getManager(this);
99 m_dbus
= new QDBusInterface("org.freedesktop.Notifications",
100 "/org/freedesktop/Notifications",
101 "org.freedesktop.Notifications");
102 if (!m_dbus
->isValid()) {
103 qDebug() << "Unable to to connect to org.freedesktop.Notifications "
110 connect(m_s
, SIGNAL(trayIconChanged()), this, SLOT(updateTrayIcon()));
112 m_notifyMap
= new QSignalMapper(this);
114 int max_tl
= m_s
->maxTimelineItems();
115 int max_fh
= m_s
->maxFirehoseItems();
117 m_tabWidget
= new TabWidget(this);
118 m_tabWidget
->setFocusPolicy(Qt::NoFocus
);
120 m_inboxWidget
= new CollectionWidget(this, max_tl
);
121 connectCollection(m_inboxWidget
);
123 m_inboxMinorWidget
= new CollectionWidget(this, max_tl
);
124 connectCollection(m_inboxMinorWidget
);
126 m_directMajorWidget
= new CollectionWidget(this, max_tl
);
127 connectCollection(m_directMajorWidget
);
129 m_directMinorWidget
= new CollectionWidget(this, max_tl
);
130 connectCollection(m_directMinorWidget
);
132 m_favouritesWidget
= new ObjectListWidget(m_tabWidget
);
133 connectCollection(m_favouritesWidget
);
134 m_favouritesWidget
->hide();
136 m_followersWidget
= new ObjectListWidget(m_tabWidget
);
137 connectCollection(m_followersWidget
);
138 m_followersWidget
->hide();
140 m_followingWidget
= new ObjectListWidget(m_tabWidget
);
141 connectCollection(m_followingWidget
, false);
142 m_followingWidget
->hide();
144 m_userActivitiesWidget
= new CollectionWidget(this, max_tl
);
145 connectCollection(m_userActivitiesWidget
, false);
146 m_userActivitiesWidget
->hide();
148 m_firehoseWidget
= new CollectionWidget(this, max_fh
, 0);
149 connectCollection(m_firehoseWidget
);
150 m_firehoseWidget
->hide();
152 connect(m_inboxMinorWidget
, SIGNAL(hasNewObjects()),
153 this, SLOT(onNewMinorObjects()));
154 connect(m_directMinorWidget
, SIGNAL(hasNewObjects()),
155 this, SLOT(onNewMinorObjects()));
157 connect(m_tabWidget
, SIGNAL(currentChanged(int)),
158 this, SLOT(tabSelected(int)));
160 m_tabWidget
->addTab(m_inboxWidget
, tr("&Inbox"));
161 m_tabWidget
->addTab(m_directMinorWidget
, tr("&Mentions"));
162 m_tabWidget
->addTab(m_directMajorWidget
, tr("&Direct"));
163 m_tabWidget
->addTab(m_inboxMinorWidget
, tr("Mean&while"));
164 // m_tabWidget->addTab(m_firehoseWidget, tr("Fi&rehose"), true, true);
166 m_notifyMap
->setMapping(m_inboxWidget
, FEED_INBOX
);
167 m_notifyMap
->setMapping(m_directMinorWidget
, FEED_MENTIONS
);
168 m_notifyMap
->setMapping(m_directMajorWidget
, FEED_DIRECT
);
169 m_notifyMap
->setMapping(m_inboxMinorWidget
, FEED_MEANWHILE
);
171 connect(m_notifyMap
, SIGNAL(mapped(int)),
172 this, SLOT(timelineHighlighted(int)));
174 m_loadIcon
= new QLabel(this);
175 m_loadMovie
= new QMovie(":/images/loader.gif", QByteArray(), this);
176 statusBar()->addPermanentWidget(m_loadIcon
);
178 setWindowTitle(CLIENT_FANCY_NAME
);
179 setWindowIcon(QIcon(CLIENT_ICON
));
180 setCentralWidget(m_tabWidget
);
182 // oaRequest->setEnableDebugOutput(true);
193 //------------------------------------------------------------------------------
195 PumpApp::~PumpApp() {
198 m_s
->hideAuthors(QASActor::getHiddenAuthors());
201 //------------------------------------------------------------------------------
203 void PumpApp::launchOAuthWizard() {
204 qApp
->setQuitOnLastWindowClosed(false);
207 m_wiz
= new OAuthWizard(m_nam
, m_oam
, this);
208 connect(m_wiz
, SIGNAL(clientRegistered(QString
, QString
, QString
, QString
)),
209 this, SLOT(onClientRegistered(QString
, QString
, QString
, QString
)));
210 connect(m_wiz
, SIGNAL(accessTokenReceived(QString
, QString
)),
211 this, SLOT(onAccessTokenReceived(QString
, QString
)));
212 connect(m_wiz
, SIGNAL(accepted()), this, SLOT(show()));
213 connect(m_wiz
, SIGNAL(rejected()), this, SLOT(wizardCancelled()));
219 //------------------------------------------------------------------------------
221 QString
certSubjectInfo(const QSslCertificate
& cert
) {
223 return cert
.subjectInfo(QSslCertificate::CommonName
).join(" ");
225 return cert
.subjectInfo(QSslCertificate::CommonName
);
229 QString
certIssuerInfo(const QSslCertificate
& cert
) {
231 return cert
.issuerInfo(QSslCertificate::CommonName
).join(" ");
233 return cert
.issuerInfo(QSslCertificate::CommonName
);
237 //------------------------------------------------------------------------------
239 void PumpApp::onSslErrors(QNetworkReply
* reply
, QList
<QSslError
> errors
) {
240 if (m_s
->ignoreSslErrors()) {
241 reply
->ignoreSslErrors();
247 infoText
+= "URL: " + reply
->url().toString() + "\n";
249 for (int i
=0; i
<errors
.size(); i
++) {
250 infoText
+= tr("SSL Error: ") + errors
[i
].errorString() + ".\n";
253 QString(tr("\n%1 is unable to verify the identity of the server. "
254 "This error could mean that someone is trying to impersonate the "
255 "server, or that the server's administrator has made an error.\n")).
256 arg(CLIENT_FANCY_NAME
);
259 QSslCertificate cert
= errors
[0].certificate();
260 if (!cert
.isNull()) {
261 detailText
= tr("SSL Server certificate.\n") +
262 tr("Issued to: ") + certSubjectInfo(cert
) + "\n" +
263 tr("Issued by: ") + certIssuerInfo(cert
) + "\n" +
264 tr("Effective: ") + cert
.effectiveDate().toString() + "\n" +
265 tr("Expires: ") + cert
.expiryDate().toString() + "\n" +
266 tr("MD5 digest: ") + cert
.digest().toHex() + "\n";
269 qDebug() << infoText
;
270 qDebug() << detailText
;
273 msgBox
.setText(tr("<b>Untrusted SSL connection!</b>"));
274 msgBox
.setIcon(QMessageBox::Critical
);
275 msgBox
.setInformativeText(infoText
);
276 if (!detailText
.isEmpty())
277 msgBox
.setDetailedText(detailText
);
278 msgBox
.setStandardButtons(QMessageBox::Ignore
| QMessageBox::Abort
);
279 msgBox
.setDefaultButton(QMessageBox::Abort
);
281 if (msgBox
.exec() == QMessageBox::Ignore
) {
282 reply
->ignoreSslErrors();
287 //------------------------------------------------------------------------------
289 void PumpApp::startPumping() {
290 resetActivityStreams();
292 QString webFinger
= siteUrlToAccountId(m_s
->userName(), m_s
->siteUrl());
294 setWindowTitle(QString("%1 - %2").arg(CLIENT_FANCY_NAME
).arg(webFinger
));
296 // Setup endpoints for our timeline widgets
297 m_inboxWidget
->setEndpoint(inboxEndpoint("major"), this, QAS_FOLLOW
);
298 m_inboxMinorWidget
->setEndpoint(inboxEndpoint("minor"), this);
299 m_directMajorWidget
->setEndpoint(inboxEndpoint("direct/major"), this);
300 m_directMinorWidget
->setEndpoint(inboxEndpoint("direct/minor"), this);
301 m_followersWidget
->setEndpoint(apiUrl(apiUser("followers")), this);
302 m_followingWidget
->setEndpoint(apiUrl(apiUser("following")), this,
304 m_favouritesWidget
->setEndpoint(apiUrl(apiUser("favorites")), this);
305 m_firehoseWidget
->setEndpoint(m_s
->firehoseUrl(), this);
306 m_userActivitiesWidget
->setEndpoint(apiUrl(apiUser("feed")), this);
309 m_recipientLists
.clear();
311 addPublicRecipient(m_recipientLists
);
313 QVariantMap followersJson
;
314 followersJson
["displayName"] = tr("Followers");
315 followersJson
["objectType"] = "collection";
316 followersJson
["id"] = apiUrl(apiUser("followers"));
317 m_recipientLists
.append(QASObject::getObject(followersJson
, this));
319 request(apiUser("profile"), QAS_SELF_PROFILE
);
320 request(apiUser("lists/person"), QAS_SELF_LISTS
);
326 //------------------------------------------------------------------------------
328 void PumpApp::connectCollection(ASWidget
* w
, bool highlight
) {
329 connect(w
, SIGNAL(request(QString
, int)), this, SLOT(request(QString
, int)));
330 connect(w
, SIGNAL(newReply(QASObject
*, QASObjectList
*, QASObjectList
*)),
331 this, SLOT(newNote(QASObject
*, QASObjectList
*, QASObjectList
*)));
332 connect(w
, SIGNAL(linkHovered(const QString
&)),
333 this, SLOT(statusMessage(const QString
&)));
334 connect(w
, SIGNAL(like(QASObject
*)), this, SLOT(onLike(QASObject
*)));
335 connect(w
, SIGNAL(share(QASObject
*)), this, SLOT(onShare(QASObject
*)));
337 connect(w
, SIGNAL(highlightMe()), m_notifyMap
, SLOT(map()));
338 connect(w
, SIGNAL(showContext(QASObject
*)),
339 this, SLOT(onShowContext(QASObject
*)));
340 connect(w
, SIGNAL(follow(QString
, bool)), this, SLOT(follow(QString
, bool)));
341 connect(w
, SIGNAL(deleteObject(QASObject
*)),
342 this, SLOT(onDeleteObject(QASObject
*)));
343 connect(w
, SIGNAL(editObject(QASObject
*)),
344 this, SLOT(onEditObject(QASObject
*)));
347 //------------------------------------------------------------------------------
349 void PumpApp::onClientRegistered(QString userName
, QString siteUrl
,
350 QString clientId
, QString clientSecret
) {
351 m_s
->userName(userName
);
352 m_s
->siteUrl(siteUrl
);
353 m_s
->clientId(clientId
);
354 m_s
->clientSecret(clientSecret
);
357 //------------------------------------------------------------------------------
359 void PumpApp::onAccessTokenReceived(QString token
, QString tokenSecret
) {
361 m_s
->tokenSecret(tokenSecret
);
367 //------------------------------------------------------------------------------
369 bool PumpApp::haveOAuth() {
370 return !m_s
->clientId().isEmpty() &&
371 !m_s
->clientSecret().isEmpty() &&
372 !m_s
->token().isEmpty() &&
373 !m_s
->tokenSecret().isEmpty();
376 //------------------------------------------------------------------------------
378 void PumpApp::tabSelected(int index
) {
379 m_tabWidget
->deHighlightTab(index
);
380 resetNotifications();
381 m_closeTabAction
->setEnabled(m_tabWidget
->closable(index
));
384 //------------------------------------------------------------------------------
386 void PumpApp::timerEvent(QTimerEvent
* event
) {
387 if (event
->timerId() != m_timerId
)
391 if (m_timerCount
>= m_s
->reloadTime()) {
399 //------------------------------------------------------------------------------
401 void PumpApp::resetTimer() {
403 killTimer(m_timerId
);
404 m_timerId
= startTimer(60*1000); // one minute timer
408 //------------------------------------------------------------------------------
410 void PumpApp::debugAction() {
411 checkMemory("debug");
412 qDebug() << "inbox" << m_inboxWidget
->count();
413 qDebug() << "meanwhile" << m_inboxMinorWidget
->count();
414 qDebug() << "firehose" << m_firehoseWidget
->count();
419 //------------------------------------------------------------------------------
421 void PumpApp::refreshTimeLabels() {
422 m_inboxWidget
->refreshTimeLabels();
423 m_directMinorWidget
->refreshTimeLabels();
424 m_directMajorWidget
->refreshTimeLabels();
425 m_inboxMinorWidget
->refreshTimeLabels();
426 m_firehoseWidget
->refreshTimeLabels();
427 for (int i
=0; i
<m_contextWidgets
.size(); ++i
)
428 m_contextWidgets
[i
]->refreshTimeLabels();
431 //------------------------------------------------------------------------------
433 void PumpApp::statusMessage(const QString
& msg
) {
434 statusBar()->showMessage(msg
);
437 //------------------------------------------------------------------------------
439 void PumpApp::notifyMessage(QString msg
) {
441 // qDebug() << "[STATUS]:" << msg;
444 //------------------------------------------------------------------------------
446 void PumpApp::timelineHighlighted(int feed
) {
447 bool doTrayIcon
= (feed
& m_s
->highlightFeeds()) && m_trayIcon
;
448 bool doPopup
= feed
& m_s
->popupFeeds();
450 // If we don't do any notifications don't even bother...
451 if (!doTrayIcon
&& !doPopup
)
454 // We highlight the tray icon and generate popups only on certain
455 // actions, so we first need to filter the list of new activities.
456 QList
<QASActivity
*> acts
;
458 CollectionWidget
* cw
=
459 qobject_cast
<CollectionWidget
*>(m_notifyMap
->mapping(feed
));
460 if (!cw
) // if it wasn't a regular timeline we ignore it
463 // We just keep posts, i.e. new notes or comments.
464 // Other possibilities would be: follow favorite like
465 QStringList keepVerbs
;
468 // Filter: keep only activities that have a verb in keepVerbs
469 const QList
<QASAbstractObject
*>& ol
= cw
->newObjects();
470 for (int i
=0; i
<ol
.count(); ++i
) {
471 QASActivity
* act
= qobject_cast
<QASActivity
*>(ol
.at(0));
472 if (act
&& keepVerbs
.contains(act
->verb()))
479 // Highlight tray icon.
481 m_trayIcon
->setIcon(QIcon(":/images/pumpa_glow.png"));
483 // Popup notifications.
486 for (int i
=0; i
<acts
.size(); i
++)
487 if (!acts
.at(i
)->skipNotify())
494 QString(tr("You have %Ln new notification(s).", 0, actsCount
));
496 // If there's only a single post activity we'll make the
497 // notification more informative.
498 QASActivity
* act
= acts
.at(0);
499 QASObject
* obj
= act
->object();
500 QASActor
* actor
= act
->actor();
501 if (actsCount
== 1 && act
->verb() == "post" && obj
&& actor
) {
502 QString actorName
= actor
->displayNameOrWebFinger();
503 if (obj
->type() == "comment")
504 msg
= QString(tr("%1 commented: ")).arg(actorName
);
506 msg
= QString(tr("%1 wrote: ")).arg(actorName
);
507 msg
+= "\"" + obj
->excerpt() + "\"";
509 sendNotification(CLIENT_FANCY_NAME
, msg
);
513 //------------------------------------------------------------------------------
515 void PumpApp::onNewMinorObjects() {
516 CollectionWidget
* cw
= qobject_cast
<CollectionWidget
*>(sender());
520 const QList
<QASAbstractObject
*>& newObjects
= cw
->newObjects();
523 for (int i
=0; i
<newObjects
.size(); ++i
) {
524 QASActivity
* act
= qobject_cast
<QASActivity
*>(newObjects
.at(i
));
525 if (act
&& act
->object() && act
->object()->inReplyTo()) {
526 QASObject
* irtObj
= act
->object()->inReplyTo();
527 if (irtObj
->url().isEmpty() || isShown(irtObj
))
528 refreshObject(irtObj
);
533 //------------------------------------------------------------------------------
535 void PumpApp::resetNotifications() {
537 m_trayIcon
->setIcon(QIcon(CLIENT_ICON
));
538 m_tabWidget
->deHighlightTab();
541 //------------------------------------------------------------------------------
543 bool PumpApp::sendNotification(QString summary
, QString text
) {
545 if (m_dbus
&& m_dbus
->isValid()) {
547 // https://developer.gnome.org/notification-spec/
548 QList
<QVariant
> args
;
549 args
.append(CLIENT_NAME
); // Application Name
550 args
.append(0123U); // Replaces ID (0U)
551 args
.append(QString()); // Notification Icon
552 args
.append(summary
); // Summary
553 args
.append(text
); // Body
554 args
.append(QStringList()); // Actions
557 // for hints to make icon, see
558 // https://dev.visucore.com/bitcoin/doxygen/notificator_8cpp_source.html
562 m_dbus
->callWithArgumentList(QDBus::NoBlock
, "Notify", args
);
567 if (QSystemTrayIcon::supportsMessages() && m_trayIcon
) {
568 m_trayIcon
->showMessage(CLIENT_FANCY_NAME
, summary
+" "+text
);
572 qDebug() << "[NOTIFY]" << summary
<< text
;
576 //------------------------------------------------------------------------------
578 void PumpApp::errorMessage(QString msg
) {
579 statusMessage(tr("Error: ") + msg
);
580 qDebug() << "[ERROR]:" << msg
;
583 //------------------------------------------------------------------------------
585 void PumpApp::updateTrayIcon() {
586 bool useTray
= m_s
->useTrayIcon() && QSystemTrayIcon::isSystemTrayAvailable();
589 qApp
->setQuitOnLastWindowClosed(false);
596 QString toolTip
= CLIENT_FANCY_NAME
;
597 if (!m_s
->userName().isEmpty())
598 toolTip
+= " - " + siteUrlToAccountId(m_s
->userName(), m_s
->siteUrl());
599 m_trayIcon
->setToolTip(toolTip
);
602 qApp
->setQuitOnLastWindowClosed(true);
608 //------------------------------------------------------------------------------
610 void PumpApp::createTrayIcon() {
611 m_trayIconMenu
= new QMenu(this);
612 m_trayIconMenu
->addAction(newNoteAction
);
613 // m_trayIconMenu->addAction(newPictureAction);
614 m_trayIconMenu
->addSeparator();
615 m_trayIconMenu
->addAction(m_showHideAction
);
616 m_trayIconMenu
->addAction(exitAction
);
618 m_trayIcon
= new QSystemTrayIcon(QIcon(CLIENT_ICON
));
619 connect(m_trayIcon
, SIGNAL(activated(QSystemTrayIcon::ActivationReason
)),
620 this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason
)));
621 m_trayIcon
->setContextMenu(m_trayIconMenu
);
622 m_trayIcon
->setToolTip(CLIENT_FANCY_NAME
);
626 //------------------------------------------------------------------------------
628 void PumpApp::trayIconActivated(QSystemTrayIcon::ActivationReason reason
) {
629 if (reason
== QSystemTrayIcon::Trigger
) {
630 m_trayIcon
->setIcon(QIcon(CLIENT_ICON
));
635 //------------------------------------------------------------------------------
637 QString
PumpApp::showHideText(bool visible
) {
638 return QString(tr("%1 &Window")).arg(visible
? tr("Hide") : tr("Show") );
641 //------------------------------------------------------------------------------
643 void PumpApp::toggleVisible() {
644 setVisible(!isVisible());
645 m_showHideAction
->setText(showHideText());
650 //------------------------------------------------------------------------------
652 void PumpApp::createActions() {
653 exitAction
= new QAction(tr("E&xit"), this);
654 exitAction
->setShortcut(tr("Ctrl+Q"));
655 connect(exitAction
, SIGNAL(triggered()), this, SLOT(exit()));
657 openPrefsAction
= new QAction(tr("Preferences"), this);
658 connect(openPrefsAction
, SIGNAL(triggered()), this, SLOT(preferences()));
660 reloadAction
= new QAction(tr("&Reload timeline"), this);
661 reloadAction
->setShortcut(tr("Ctrl+R"));
662 connect(reloadAction
, SIGNAL(triggered()),
663 this, SLOT(reload()));
665 loadOlderAction
= new QAction(tr("Load older in timeline"), this);
666 loadOlderAction
->setShortcut(tr("Ctrl+O"));
667 connect(loadOlderAction
, SIGNAL(triggered()), this, SLOT(loadOlder()));
669 followAction
= new QAction(tr("F&ollow an account"), this);
670 followAction
->setShortcut(tr("Ctrl+L"));
671 connect(followAction
, SIGNAL(triggered()), this, SLOT(followDialog()));
673 profileAction
= new QAction(tr("Your &profile"), this);
674 connect(profileAction
, SIGNAL(triggered()), this, SLOT(editProfile()));
676 aboutAction
= new QAction(tr("&About"), this);
677 connect(aboutAction
, SIGNAL(triggered()), this, SLOT(about()));
679 aboutQtAction
= new QAction(tr("About &Qt"), this);
680 connect(aboutQtAction
, SIGNAL(triggered()), qApp
, SLOT(aboutQt()));
682 reportBugAction
= new QAction(tr("Report &bug online"), this);
683 connect(reportBugAction
, SIGNAL(triggered()), this, SLOT(reportBug()));
685 newNoteAction
= new QAction(tr("New &Note"), this);
686 newNoteAction
->setShortcut(tr("Ctrl+N"));
687 connect(newNoteAction
, SIGNAL(triggered()), this, SLOT(newNote()));
689 m_debugAction
= new QAction("Debug", this);
690 m_debugAction
->setShortcut(tr("Ctrl+D"));
691 connect(m_debugAction
, SIGNAL(triggered()), this, SLOT(debugAction()));
692 addAction(m_debugAction
);
694 m_closeTabAction
= new QAction(tr("Close tab"), this);
695 m_closeTabAction
->setShortcut(tr("Ctrl+W"));
696 connect(m_closeTabAction
, SIGNAL(triggered()), this, SLOT(closeTab()));
697 m_closeTabAction
->setEnabled(false);
699 m_firehoseAction
= new QAction(tr("Firehose"), this);
700 connect(m_firehoseAction
, SIGNAL(triggered()), this, SLOT(showFirehose()));
702 m_followersAction
= new QAction(tr("Followers"), this);
703 connect(m_followersAction
, SIGNAL(triggered()), this, SLOT(showFollowers()));
705 m_followingAction
= new QAction(tr("Following"), this);
706 connect(m_followingAction
, SIGNAL(triggered()), this, SLOT(showFollowing()));
708 m_favouritesAction
= new QAction(tr("Favorites"), this);
709 connect(m_favouritesAction
, SIGNAL(triggered()),
710 this, SLOT(showFavourites()));
712 m_userActivitiesAction
= new QAction(tr("Activities"), this);
713 connect(m_userActivitiesAction
, SIGNAL(triggered()),
714 this, SLOT(showUserActivities()));
716 m_showHideAction
= new QAction(showHideText(true), this);
717 connect(m_showHideAction
, SIGNAL(triggered()), this, SLOT(toggleVisible()));
720 //------------------------------------------------------------------------------
722 void PumpApp::createMenu() {
723 fileMenu
= new QMenu(tr("&Pumpa"), this);
724 fileMenu
->addAction(newNoteAction
);
725 fileMenu
->addSeparator();
726 fileMenu
->addAction(followAction
);
727 fileMenu
->addAction(profileAction
);
728 fileMenu
->addAction(reloadAction
);
729 fileMenu
->addAction(loadOlderAction
);
730 fileMenu
->addSeparator();
731 fileMenu
->addAction(openPrefsAction
);
732 fileMenu
->addSeparator();
733 fileMenu
->addAction(exitAction
);
734 menuBar()->addMenu(fileMenu
);
736 m_tabsMenu
= new QMenu(tr("&Tabs"), this);
737 m_tabsMenu
->addAction(m_userActivitiesAction
);
738 m_tabsMenu
->addAction(m_favouritesAction
);
739 m_tabsMenu
->addAction(m_followersAction
);
740 m_tabsMenu
->addAction(m_followingAction
);
741 m_tabsMenu
->addAction(m_firehoseAction
);
742 m_tabsMenu
->addAction(m_closeTabAction
);
743 menuBar()->addMenu(m_tabsMenu
);
745 helpMenu
= new QMenu(tr("&Help"), this);
746 helpMenu
->addAction(aboutAction
);
747 helpMenu
->addAction(aboutQtAction
);
748 helpMenu
->addSeparator();
749 helpMenu
->addAction(reportBugAction
);
750 menuBar()->addMenu(helpMenu
);
753 //------------------------------------------------------------------------------
755 void PumpApp::preferences() {
756 m_settingsDialog
->exec();
759 //------------------------------------------------------------------------------
761 void PumpApp::wizardCancelled() {
762 qApp
->setQuitOnLastWindowClosed(true);
767 //------------------------------------------------------------------------------
769 void PumpApp::exit() {
773 //------------------------------------------------------------------------------
775 void PumpApp::about() {
776 static const QString GPL
=
777 tr("<p>Pumpa is free software: you can redistribute it and/or modify it "
778 "under the terms of the GNU General Public License as published by "
779 "the Free Software Foundation, either version 3 of the License, or "
780 "(at your option) any later version.</p>"
781 "<p>Pumpa is distributed in the hope that it will be useful, but "
782 "WITHOUT ANY WARRANTY; without even the implied warranty of "
783 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU "
784 "General Public License for more details.</p>"
785 "<p>You should have received a copy of the GNU General Public License "
786 "along with Pumpa. If not, see "
787 "<a href=\"http://www.gnu.org/licenses/\">http://www.gnu.org/licenses/</a>."
789 static const QString credits
=
790 tr("<p>The <a href=\"https://github.com/kypeli/kQOAuth\">kQOAuth library"
791 "</a> is copyrighted by <a href=\"http://www.johanpaul.com/\">Johan "
792 "Paul</a> and licensed under LGPL 2.1.</p>"
793 "<p>The <a href=\"https://github.com/vmg/sundown\">sundown Markdown "
794 "library</a> is copyrighted by Natacha Porté, Vicent Marti and "
795 "others, and <a href=\"https://github.com/vmg/sundown#license\">"
796 "permissively licensed</a>.</p>"
797 "<p>The Pumpa logo was "
798 "<a href=\"http://opengameart.org/content/fruit-and-veggie-inventory\">"
799 "created by Joshua Taylor</a> for the "
800 "<a href=\"http://lpc.opengameart.org/\">Liberated Pixel Cup</a>."
801 "The logo is copyrighted by the artist and is dual licensed under the "
802 "CC-BY-SA 3.0 license and the GNU GPL 3.0.");
805 QString("<p><b>%1 %2</b> - %3<br/><a href=\"%4\">%4</a><br/>"
806 + tr("Copyright © 2013-2015 Mats Sjöberg")
807 + " - <a href=\"https://pump.saz.im/sazius\">sazius@pump.saz.im</a>."
809 + tr("<p>Report bugs and feature requests at "
810 "<a href=\"%5\">%5</a>.</p>"))
811 .arg(CLIENT_FANCY_NAME
)
813 .arg(tr("A simple Qt-based pump.io client."))
815 .arg(BUGTRACKER_URL
);
817 QMessageBox::about(this, QString(tr("About %1")).arg(CLIENT_FANCY_NAME
),
818 mainText
+ GPL
+ credits
);
821 //------------------------------------------------------------------------------
823 void PumpApp::reportBug() {
824 QDesktopServices::openUrl(QString(BUGTRACKER_URL
));
827 //------------------------------------------------------------------------------
829 void PumpApp::addPublicRecipient(RecipientList
& rl
) {
830 QVariantMap publicJson
;
831 publicJson
["displayName"] = tr("Public");
832 publicJson
["objectType"] = "collection";
833 publicJson
["id"] = PUBLIC_RECIPIENT_ID
;
834 rl
.append(QASObject::getObject(publicJson
, this));
837 //------------------------------------------------------------------------------
839 void PumpApp::newNote(QASObject
* obj
, QASObjectList
* to
, QASObjectList
* cc
,
841 if (!m_messageWindow
) {
842 m_messageWindow
= new MessageWindow(m_s
, &m_recipientLists
, this);
843 connect(m_messageWindow
,
844 SIGNAL(sendMessage(QString
, QString
, RecipientList
, RecipientList
)),
846 SLOT(postNote(QString
, QString
, RecipientList
, RecipientList
)));
847 connect(m_messageWindow
, SIGNAL(sendImage(QString
, QString
, QString
,
848 RecipientList
, RecipientList
)),
849 this, SLOT(postImage(QString
, QString
, QString
,
850 RecipientList
, RecipientList
)));
851 connect(m_messageWindow
, SIGNAL(sendReply(QASObject
*, QString
,
852 RecipientList
, RecipientList
)),
853 this, SLOT(postReply(QASObject
*, QString
,
854 RecipientList
, RecipientList
)));
855 connect(m_messageWindow
, SIGNAL(sendEdit(QASObject
*, QString
, QString
)),
856 this, SLOT(postEdit(QASObject
*, QString
, QString
)));
857 m_messageWindow
->setCompletions(&m_completions
);
860 m_messageWindow
->editMessage(obj
);
862 m_messageWindow
->newMessage(obj
, to
, cc
);
863 m_messageWindow
->show();
866 //------------------------------------------------------------------------------
868 void PumpApp::reload() {
873 //------------------------------------------------------------------------------
875 void PumpApp::fetchAll(bool all
) {
876 m_inboxWidget
->fetchNewer();
877 if (m_inboxWidget
->linksInitialised())
878 m_inboxWidget
->refresh();
879 m_directMinorWidget
->fetchNewer();
880 m_directMajorWidget
->fetchNewer();
881 m_inboxMinorWidget
->fetchNewer();
883 if (tabShown(m_firehoseWidget
))
884 m_firehoseWidget
->fetchNewer();
886 for (int i
=0; i
<m_contextWidgets
.size(); ++i
)
887 m_contextWidgets
[i
]->fetchNewer();
889 // These will be reloaded even if not shown, if all=true
890 if (all
|| tabShown(m_followersWidget
))
891 m_followersWidget
->fetchNewer();
892 if (all
|| tabShown(m_followingWidget
))
893 m_followingWidget
->fetchNewer();
894 if (all
|| tabShown(m_favouritesWidget
))
895 m_favouritesWidget
->fetchNewer();
896 if (all
|| tabShown(m_userActivitiesWidget
))
897 m_userActivitiesWidget
->fetchNewer();
900 //------------------------------------------------------------------------------
902 void PumpApp::loadOlder() {
904 qobject_cast
<ASWidget
*>(m_tabWidget
->currentWidget());
909 //------------------------------------------------------------------------------
911 bool PumpApp::isShown(QASAbstractObject
* obj
) {
912 // check context widgets first
913 for (int i
=0; i
<m_contextWidgets
.size(); ++i
)
914 if (m_contextWidgets
[i
]->hasObject(obj
))
917 // check all other tab widgets
918 return m_inboxWidget
->hasObject(obj
) ||
919 m_directMinorWidget
->hasObject(obj
) ||
920 m_directMajorWidget
->hasObject(obj
) ||
921 m_inboxMinorWidget
->hasObject(obj
) ||
922 (tabShown(m_firehoseWidget
) && m_firehoseWidget
->hasObject(obj
)) ||
923 (tabShown(m_followersWidget
) && m_followersWidget
->hasObject(obj
)) ||
924 (tabShown(m_followingWidget
) && m_followingWidget
->hasObject(obj
)) ||
925 (tabShown(m_favouritesWidget
) && m_favouritesWidget
->hasObject(obj
)) ||
926 (tabShown(m_userActivitiesWidget
) &&
927 m_userActivitiesWidget
->hasObject(obj
));
930 //------------------------------------------------------------------------------
932 QString
PumpApp::inboxEndpoint(QString path
) {
933 if (m_s
->siteUrl().isEmpty()) {
934 errorMessage(tr("Site not configured yet!"));
937 return m_s
->siteUrl() + "/api/user/" + m_s
->userName() + "/inbox/" + path
;
940 //------------------------------------------------------------------------------
942 void PumpApp::onLike(QASObject
* obj
) {
943 feed(obj
->liked() ? "unlike" : "like", obj
->toJson(),
944 QAS_ACTIVITY
| QAS_TOGGLE_LIKE
);
947 //------------------------------------------------------------------------------
949 void PumpApp::onShare(QASObject
* obj
) {
950 feed("share", obj
->toJson(), QAS_ACTIVITY
| QAS_REFRESH
);
953 //------------------------------------------------------------------------------
955 void PumpApp::errorBox(QString msg
) {
956 QMessageBox::critical(this, CLIENT_FANCY_NAME
, msg
, QMessageBox::Ok
);
959 //------------------------------------------------------------------------------
961 bool PumpApp::webFingerFromString(QString text
, QString
& username
,
963 if (text
.startsWith("https://") || text
.startsWith("http://")) {
964 int slashPos
= text
.lastIndexOf('/');
966 text
= siteUrlToAccountId(text
.mid(slashPos
+1), text
.left(slashPos
));
969 return splitWebfingerId(text
, username
, server
);
972 //------------------------------------------------------------------------------
974 void PumpApp::followDialog() {
977 QString defaultText
= "evan@e14n.com";
978 QString cbText
= QApplication::clipboard()->text();
979 if (cbText
.contains('@') || cbText
.startsWith("https://") ||
980 cbText
.startsWith("http://"))
981 defaultText
= cbText
;
984 QInputDialog::getText(this, tr("Follow pump.io user"),
985 tr("Enter webfinger ID of person to follow: "),
986 QLineEdit::Normal
, defaultText
, &ok
);
988 if (!ok
|| text
.isEmpty())
991 QString username
, server
;
994 if (!webFingerFromString(text
, username
, server
))
995 error
= tr("Sorry, that doesn't even look like a webfinger ID!");
997 QASObject
* obj
= QASObject::getObject("acct:" + username
+ "@" + server
);
998 QASActor
* actor
= obj
? obj
->asActor() : NULL
;
999 if (actor
&& actor
->followed())
1000 error
= tr("Sorry, you are already following that person!");
1002 if (!error
.isEmpty())
1003 return errorBox(error
);
1005 testUserAndFollow(username
, server
);
1008 //------------------------------------------------------------------------------
1010 void PumpApp::editProfile() {
1011 request(apiUser("profile"), QAS_EDIT_PROFILE
);
1014 //------------------------------------------------------------------------------
1016 void PumpApp::editProfileDialog() {
1017 if (!m_editProfileDialog
) {
1018 m_editProfileDialog
= new EditProfileDialog(this);
1019 connect(m_editProfileDialog
, SIGNAL(profileEdited(QASActor
*, QString
)),
1020 this, SLOT(onProfileEdited(QASActor
*, QString
)));
1022 m_editProfileDialog
->setProfile(m_selfActor
);
1023 m_editProfileDialog
->show();
1026 //------------------------------------------------------------------------------
1028 void PumpApp::onProfileEdited(QASActor
* profile
, QString newImageFile
) {
1031 m_profile
["objectType"] = "person";
1032 m_profile
["displayName"] = profile
->displayName();
1033 m_profile
["summary"] = profile
->summary();
1035 QVariantMap jsonLoc
;
1036 jsonLoc
["objectType"] = "place";
1037 jsonLoc
["displayName"] = profile
->location();
1038 m_profile
["location"] = jsonLoc
;
1040 if (newImageFile
.isEmpty()) {
1043 postAvatarImage(newImageFile
);
1047 //------------------------------------------------------------------------------
1049 void PumpApp::uploadProfile() {
1050 request(apiUser("profile"), QAS_SELF_PROFILE
| QAS_REFRESH
,
1051 KQOAuthRequest::PUT
, m_profile
);
1054 //------------------------------------------------------------------------------
1056 void PumpApp::testUserAndFollow(QString username
, QString server
) {
1057 QString fingerUrl
= QString("%1/.well-known/webfinger?resource=%2@%1").
1058 arg(server
).arg(username
);
1060 QNetworkRequest
rec(QUrl("https://" + fingerUrl
));
1061 QNetworkReply
* reply
= m_nam
->head(rec
);
1062 connect(reply
, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow()));
1063 qDebug() << "testUserAndFollow" << fingerUrl
;
1065 // isn't this an ugly yet fancy hack? :-)
1066 reply
->setProperty("pumpa_redirects", 0);
1069 //------------------------------------------------------------------------------
1071 void PumpApp::userTestDoneAndFollow() {
1074 QNetworkReply
*reply
= qobject_cast
<QNetworkReply
*>(sender());
1075 QUrl url
= reply
->url();
1078 QUrlQuery
replyQuery(url
.query());
1079 QString userId
= replyQuery
.queryItemValue("resource");
1081 QString userId
= url
.queryItemValue("resource");
1084 int redirs
= reply
->property("pumpa_redirects").toInt();
1086 qDebug() << "userTestDoneAndFollow" << url
<< redirs
;
1089 if (reply
->error() != QNetworkReply::NoError
) {
1091 url
.setScheme("http");
1092 QNetworkRequest
rec(url
);
1093 QNetworkReply
* r
= m_nam
->head(rec
);
1094 r
->setProperty("pumpa_redirects", ++redirs
);
1095 connect(r
, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow()));
1098 return errorBox(tr("Invalid user: ") + userId
);
1102 QUrl loc
= reply
->header(QNetworkRequest::LocationHeader
).toUrl();
1103 if (loc
.isValid()) {
1105 return errorBox(tr("Invalid user (cannot check site): ") + userId
);
1106 reply
->deleteLater();
1108 QNetworkRequest
rec(loc
);
1109 QNetworkReply
* r
= m_nam
->head(rec
);
1110 r
->setProperty("pumpa_redirects", ++redirs
);
1111 connect(r
, SIGNAL(finished()), this, SLOT(userTestDoneAndFollow()));
1115 follow("acct:" + userId
, true);
1118 //------------------------------------------------------------------------------
1120 bool PumpApp::tabShown(ASWidget
* aw
) const {
1121 return aw
&& m_tabWidget
->indexOf(aw
) != -1;
1124 //------------------------------------------------------------------------------
1126 void PumpApp::onShowContext(QASObject
* obj
) {
1127 ContextWidget
* cw
= new ContextWidget(this);
1128 connectCollection(cw
);
1130 m_tabWidget
->addTab(cw
, tr("&Context"), true, true);
1132 m_tabWidget
->setCurrentWidget(cw
);
1134 m_contextWidgets
.append(cw
);
1137 //------------------------------------------------------------------------------
1139 void PumpApp::closeTab() {
1141 qobject_cast
<ContextWidget
*>(m_tabWidget
->closeCurrentTab());
1143 int i
= m_contextWidgets
.indexOf(cw
);
1145 m_contextWidgets
.removeAt(i
);
1150 //------------------------------------------------------------------------------
1152 void PumpApp::showFirehose() {
1153 if (!tabShown(m_firehoseWidget
))
1154 m_tabWidget
->addTab(m_firehoseWidget
, tr("Fi&rehose"), true, true);
1155 m_tabWidget
->setCurrentWidget(m_firehoseWidget
);
1156 m_firehoseWidget
->fetchNewer();
1159 //------------------------------------------------------------------------------
1161 void PumpApp::showFollowers() {
1162 if (!tabShown(m_followersWidget
))
1163 m_tabWidget
->addTab(m_followersWidget
, tr("&Followers"), true, true);
1164 m_tabWidget
->setCurrentWidget(m_followersWidget
);
1165 m_followersWidget
->fetchNewer();
1168 //------------------------------------------------------------------------------
1170 void PumpApp::showFollowing() {
1171 if (!tabShown(m_followingWidget
))
1172 m_tabWidget
->addTab(m_followingWidget
, tr("F&ollowing"), false, true);
1173 m_tabWidget
->setCurrentWidget(m_followingWidget
);
1174 m_followingWidget
->fetchNewer();
1177 //------------------------------------------------------------------------------
1179 void PumpApp::showFavourites() {
1180 if (!tabShown(m_favouritesWidget
))
1181 m_tabWidget
->addTab(m_favouritesWidget
, tr("F&avorites"), false, true);
1182 m_tabWidget
->setCurrentWidget(m_favouritesWidget
);
1183 m_favouritesWidget
->fetchNewer();
1186 //------------------------------------------------------------------------------
1188 void PumpApp::showUserActivities() {
1189 if (!tabShown(m_userActivitiesWidget
))
1190 m_tabWidget
->addTab(m_userActivitiesWidget
, tr("A&ctivities"), false, true);
1191 m_tabWidget
->setCurrentWidget(m_userActivitiesWidget
);
1192 m_userActivitiesWidget
->fetchNewer();
1195 //------------------------------------------------------------------------------
1197 void PumpApp::postNote(QString content
, QString title
,
1198 RecipientList to
, RecipientList cc
) {
1199 if (content
.isEmpty())
1203 obj
["objectType"] = "note";
1204 obj
["content"] = addTextMarkup(content
, m_s
->useMarkdown());
1206 QString ptitle
= processTitle(title
, false);
1207 if (!ptitle
.isEmpty())
1208 obj
["displayName"] = ptitle
;
1210 feed("post", obj
, QAS_OBJECT
| QAS_REFRESH
| QAS_POST
, to
, cc
);
1213 //------------------------------------------------------------------------------
1215 void PumpApp::postEdit(QASObject
* obj
, QString content
, QString title
) {
1217 json
["id"] = obj
->id();
1218 json
["objectType"] = obj
->type();
1219 json
["content"] = addTextMarkup(content
, m_s
->useMarkdown());
1221 QString ptitle
= processTitle(title
, false);
1222 if (!ptitle
.isEmpty())
1223 json
["displayName"] = ptitle
;
1225 feed("update", json
, QAS_OBJECT
| QAS_REFRESH
| QAS_POST
);
1228 //------------------------------------------------------------------------------
1230 void PumpApp::postImage(QString msg
,
1235 m_imageObject
.clear();
1236 m_imageObject
["content"] = addTextMarkup(msg
, m_s
->useMarkdown());
1237 m_imageObject
["displayName"] = processTitle(title
, false);
1242 uploadFile(imageFile
);
1245 //------------------------------------------------------------------------------
1247 void PumpApp::postAvatarImage(QString imageFile
) {
1248 m_imageObject
.clear();
1250 m_imageTo
= RecipientList();
1251 m_imageCc
= RecipientList();
1253 addPublicRecipient(m_imageCc
);
1255 uploadFile(imageFile
, QAS_AVATAR_UPLOAD
);
1258 //------------------------------------------------------------------------------
1260 void PumpApp::uploadFile(QString filename
, int flags
) {
1261 QString lcfn
= filename
.toLower();
1263 if (lcfn
.endsWith(".jpg") || lcfn
.endsWith(".jpeg"))
1264 mimeType
= "image/jpeg";
1265 else if (lcfn
.endsWith(".png"))
1266 mimeType
= "image/png";
1267 else if (lcfn
.endsWith(".gif"))
1268 mimeType
= "image/gif";
1270 qDebug() << "Cannot determine mime type of file" << filename
;
1275 if (!fp
.open(QIODevice::ReadOnly
)) {
1276 qDebug() << "Unable to read file" << filename
;
1280 QByteArray ba
= fp
.readAll();
1282 KQOAuthRequest
* oaRequest
= initRequest(apiUrl(apiUser("uploads")),
1283 KQOAuthRequest::POST
);
1284 oaRequest
->setContentType(mimeType
);
1285 oaRequest
->setContentLength(ba
.size());
1286 oaRequest
->setRawData(ba
);
1288 if (m_uploadDialog
== NULL
) {
1289 m_uploadDialog
= new QProgressDialog("Uploading image...", "Abort", 0, 100,
1291 m_uploadDialog
->setWindowModality(Qt::WindowModal
);
1292 connect(m_uploadDialog
, SIGNAL(canceled()), this, SLOT(uploadCanceled()));
1294 m_uploadDialog
->reset();
1296 m_uploadDialog
->setValue(0);
1297 m_uploadDialog
->show();
1299 flags
= QAS_IMAGE_UPLOAD
| flags
;
1300 m_uploadRequest
= executeRequest(oaRequest
, flags
);
1301 connect(m_uploadRequest
, SIGNAL(uploadProgress(qint64
, qint64
)),
1302 this, SLOT(uploadProgress(qint64
, qint64
)));
1305 //------------------------------------------------------------------------------
1307 void PumpApp::updatePostedImage(QVariantMap obj
, int flags
) {
1308 m_imageObject
.unite(obj
);
1310 // Work-around for https://github.com/e14n/pump.io/issues/885
1311 // Thanks to Owen Shepherd for pointing this out!
1313 to
.append(m_selfActor
);
1315 feed("update", m_imageObject
, QAS_IMAGE_UPDATE
| flags
, to
);
1318 //------------------------------------------------------------------------------
1320 void PumpApp::postImageActivity(QVariantMap
, int flags
) {
1322 flags
= QAS_REFRESH
;
1324 flags
|= QAS_ACTIVITY
| QAS_POST
;
1326 feed("post", m_imageObject
, flags
, m_imageTo
, m_imageCc
);
1329 //------------------------------------------------------------------------------
1331 void PumpApp::uploadProgress(qint64 bytesSent
, qint64 bytesTotal
) {
1332 if (!m_uploadDialog
|| bytesTotal
<= 0)
1335 m_uploadDialog
->setValue((100*bytesSent
)/bytesTotal
);
1338 //------------------------------------------------------------------------------
1340 void PumpApp::uploadCanceled(bool abortRequest
) {
1341 if (m_uploadRequest
&& abortRequest
) {
1343 qDebug() << "[DEBUG] aborting upload...";
1345 m_uploadRequest
->abort();
1347 m_uploadRequest
= NULL
;
1349 m_imageObject
.clear();
1350 m_uploadDialog
->reset();
1353 //------------------------------------------------------------------------------
1355 void PumpApp::postReply(QASObject
* replyToObj
, QString content
,
1356 RecipientList to
, RecipientList cc
) {
1357 if (content
.isEmpty())
1361 obj
["objectType"] = "comment";
1362 obj
["content"] = addTextMarkup(content
, m_s
->useMarkdown());
1364 QVariantMap noteObj
;
1365 noteObj
["id"] = replyToObj
->id();
1366 noteObj
["objectType"] = replyToObj
->type();
1367 obj
["inReplyTo"] = noteObj
;
1369 feed("post", obj
, QAS_ACTIVITY
| QAS_REFRESH
| QAS_POST
, to
, cc
);
1372 //------------------------------------------------------------------------------
1374 void PumpApp::follow(QString acctId
, bool follow
) {
1377 obj
["objectType"] = "person";
1379 int mode
= QAS_ACTIVITY
;
1383 mode
|= QAS_UNFOLLOW
;
1385 feed(follow
? "follow" : "stop-following", obj
, mode
);
1388 //------------------------------------------------------------------------------
1390 void PumpApp::onDeleteObject(QASObject
* obj
) {
1392 json
["id"] = obj
->id();
1393 json
["objectType"] = obj
->type();
1395 feed("delete", json
, QAS_ACTIVITY
);
1398 //------------------------------------------------------------------------------
1400 void PumpApp::onEditObject(QASObject
* obj
) {
1401 newNote(obj
, NULL
, NULL
, true);
1404 //------------------------------------------------------------------------------
1406 void PumpApp::addRecipient(QVariantMap
& data
, QString name
, RecipientList to
) {
1410 QVariantList recList
;
1412 for (int i
=0; i
<to
.size(); ++i
) {
1413 QASObject
* obj
= to
.at(i
);
1416 rec
["objectType"] = obj
->type();
1417 rec
["id"] = obj
->id();
1418 if (!obj
->proxyUrl().isEmpty()) {
1419 QVariantMap pump_io
;
1420 pump_io
["proxyURL"] = obj
->proxyUrl();
1421 rec
["pump_io"] = pump_io
;
1424 recList
.append(rec
);
1427 data
[name
] = recList
;
1430 //------------------------------------------------------------------------------
1432 void PumpApp::feed(QString verb
, QVariantMap object
, int response_id
,
1433 RecipientList to
, RecipientList cc
) {
1434 QString endpoint
= "api/user/" + m_s
->userName() + "/feed";
1437 data
["verb"] = verb
;
1438 data
["object"] = object
;
1440 addRecipient(data
, "to", to
);
1441 addRecipient(data
, "cc", cc
);
1443 request(endpoint
, response_id
, KQOAuthRequest::POST
, data
);
1446 //------------------------------------------------------------------------------
1448 QString
PumpApp::apiUrl(QString endpoint
) {
1449 QString ret
= endpoint
;
1450 if (!ret
.startsWith("http")) {
1453 ret
= m_s
->siteUrl() + ret
;
1458 //------------------------------------------------------------------------------
1460 QString
PumpApp::apiUser(QString path
) {
1461 return QString("api/user/%1/%2").arg(m_s
->userName()).arg(path
);
1464 //------------------------------------------------------------------------------
1466 KQOAuthRequest
* PumpApp::initRequest(QString endpoint
,
1467 KQOAuthRequest::RequestHttpMethod method
) {
1468 KQOAuthRequest
* oaRequest
= new KQOAuthRequest(this);
1469 oaRequest
->initRequest(KQOAuthRequest::AuthorizedRequest
, QUrl(endpoint
));
1470 oaRequest
->setConsumerKey(m_s
->clientId());
1471 oaRequest
->setConsumerSecretKey(m_s
->clientSecret());
1472 oaRequest
->setToken(m_s
->token());
1473 oaRequest
->setTokenSecret(m_s
->tokenSecret());
1474 oaRequest
->setHttpMethod(method
);
1475 oaRequest
->setTimeout(60000); // one minute time-out
1479 //------------------------------------------------------------------------------
1481 void PumpApp::request(QString endpoint
, int response_id
,
1482 KQOAuthRequest::RequestHttpMethod method
,
1484 endpoint
= apiUrl(endpoint
);
1486 bool firehose
= (endpoint
== m_s
->firehoseUrl());
1487 if (!endpoint
.startsWith(m_s
->siteUrl()) && !firehose
) {
1489 qDebug() << "[DEBUG] dropping request for" << endpoint
;
1495 qDebug() << (method
== KQOAuthRequest::GET
? "[GET]" :
1496 method
== KQOAuthRequest::POST
? "[POST]" : "[PUT]")
1497 << response_id
<< ":" << endpoint
;
1500 QStringList epl
= endpoint
.split("?");
1501 KQOAuthRequest
* oaRequest
= initRequest(epl
[0], method
);
1503 // I have no idea why this is the only way that seems to
1504 // work. Incredibly frustrating and ugly :-/
1505 if (epl
.size() > 1) {
1506 KQOAuthParameters params
;
1507 QStringList parts
= epl
[1].split("&");
1508 for (int i
=0; i
<parts
.size(); i
++) {
1509 QStringList ps
= parts
[i
].split("=");
1510 params
.insert(ps
[0], QUrl::fromPercentEncoding(ps
[1].toLatin1()));
1512 oaRequest
->setAdditionalParameters(params
);
1515 if (method
== KQOAuthRequest::POST
|| method
== KQOAuthRequest::PUT
) {
1516 QByteArray ba
= serializeJson(data
);
1517 oaRequest
->setRawData(ba
);
1518 oaRequest
->setContentType("application/json");
1519 oaRequest
->setContentLength(ba
.size());
1521 qDebug() << "DATA" << oaRequest
->rawData();
1525 executeRequest(oaRequest
, response_id
);
1528 notifyMessage(tr("Loading ..."));
1532 //------------------------------------------------------------------------------
1534 QNetworkReply
* PumpApp::executeRequest(KQOAuthRequest
* request
,
1536 int id
= m_nextRequestId
++;
1538 if (m_nextRequestId
> 32000) { // bound to be smaller than any MAX_INT
1539 m_nextRequestId
= 0;
1540 while (m_requestMap
.contains(m_nextRequestId
))
1544 m_requestMap
.insert(id
, qMakePair(request
, response_id
));
1545 m_oam
->executeAuthorizedRequest(request
, id
);
1547 return m_oam
->getReply(request
);
1550 //------------------------------------------------------------------------------
1552 void PumpApp::followActor(QASActor
* actor
, bool doFollow
) {
1553 actor
->setFollowed(doFollow
);
1555 QString from
= QString("%1 (%2)").arg(actor
->displayName()).
1556 arg(actor
->webFinger());
1558 if (from
.isEmpty() || from
.startsWith("http://") ||
1559 from
.startsWith("https://"))
1563 m_completions
.insert(from
, actor
);
1565 m_completions
.remove(from
);
1568 //------------------------------------------------------------------------------
1570 void PumpApp::onAuthorizedRequestReady(QByteArray response
, int rid
) {
1571 KQOAuthManager::KQOAuthError lastError
= m_oam
->lastError();
1573 QPair
<KQOAuthRequest
*, int> rp
= m_requestMap
.take(rid
);
1574 KQOAuthRequest
* request
= rp
.first
;
1576 QString reqUrl
= request
->requestEndpoint().toString();
1578 #ifdef DEBUG_NET_MOAR
1579 qDebug() << "[DEBUG] request done [" << rid
<< id
<< "]" << reqUrl
1580 << response
.count() << "bytes";
1582 #ifdef DEBUG_NET_EVEN_MOAR
1583 qDebug() << "[DEBUG]" << response
;
1586 request
->deleteLater();
1588 if (m_requestMap
.isEmpty()) {
1590 notifyMessage(tr("Ready!"));
1592 #ifdef DEBUG_NET_MOAR
1594 qDebug() << "[DEBUG] Still waiting for requests:";
1595 QMapIterator
<int, requestInfo_t
> i(m_requestMap
);
1596 while (i
.hasNext()) {
1598 requestInfo_t ri
= i
.value();
1599 qDebug() << " " << ri
.first
->requestEndpoint() << ri
.second
;
1604 int sid
= id
& 0xFF;
1607 if (id
& QAS_POST
) {
1608 errorMessage(tr("Unable to post message!"));
1609 m_messageWindow
->show();
1610 } else if (sid
== QAS_IMAGE_UPLOAD
) {
1611 uploadCanceled(false);
1612 errorMessage(tr("Unable to upload image!"));
1613 } else if (sid
== QAS_OBJECT
) {
1614 qDebug() << "[WARNING] unable to fetch context for object.";
1616 errorMessage(QString(tr("Network or authorisation error [%1/%2] %3.")).
1617 arg(m_oam
->lastError()).arg(id
).arg(reqUrl
));
1620 qDebug() << "[ERROR]" << response
;
1625 QVariantMap json
= parseJson(response
);
1626 if (sid
== QAS_NULL
)
1629 if (sid
== QAS_COLLECTION
) {
1630 QASCollection::getCollection(json
, this, id
);
1631 } else if (sid
== QAS_ACTIVITY
) {
1632 QASActivity
* act
= QASActivity::getActivity(json
, this);
1633 if (act
) { // if not a broken activity
1634 QASObject
* obj
= act
->object();
1636 if ((id
& QAS_AVATAR_UPLOAD
) && obj
) {
1638 QVariantMap jsonImage
;
1639 jsonImage
["url"] = obj
->imageUrl();
1640 jsonImage
["width"] = 96;
1641 jsonImage
["height"] = 96;
1642 m_profile
["image"] = jsonImage
;
1647 if ((id
& QAS_TOGGLE_LIKE
) && obj
)
1650 if ((id
& QAS_FOLLOW
) || (id
& QAS_UNFOLLOW
)) {
1651 QASActor
* actor
= obj
? obj
->asActor() : NULL
;
1653 bool doFollow
= (id
& QAS_FOLLOW
);
1654 followActor(actor
, doFollow
);
1655 notifyMessage(QString(doFollow
? tr("Successfully followed ") :
1656 tr("Successfully unfollowed ")) +
1657 actor
->displayNameOrWebFinger());
1661 } else if (sid
== QAS_OBJECTLIST
) {
1662 QASObjectList
* ol
= QASObjectList::getObjectList(json
, this, id
);
1663 if (ol
&& (id
& QAS_FOLLOW
)) {
1664 for (size_t i
=0; i
<ol
->size(); ++i
) {
1665 QASActor
* actor
= ol
->at(i
)->asActor();
1670 if (ol
->nextLink().isEmpty())
1671 QASActor::setFollowedKnown();
1673 } else if (sid
== QAS_OBJECT
) {
1674 QASObject::getObject(json
, this);
1675 } else if (sid
== QAS_ACTORLIST
) {
1676 QASActorList::getActorList(json
, this);
1677 } else if (sid
== QAS_SELF_PROFILE
|| sid
== QAS_EDIT_PROFILE
) {
1678 m_selfActor
= QASActor::getActor(json
, this);
1679 m_selfActor
->setYou();
1680 if (sid
== QAS_EDIT_PROFILE
)
1681 editProfileDialog();
1682 } else if (sid
== QAS_SELF_LISTS
) {
1683 QASObjectList
* lists
= QASObjectList::getObjectList(json
, this, id
);
1684 for (size_t i
=0; i
<lists
->size(); ++i
) {
1685 m_recipientLists
.append(lists
->at(i
));
1687 } else if (sid
== QAS_IMAGE_UPLOAD
) {
1688 m_uploadDialog
->reset();
1689 updatePostedImage(json
, id
& QAS_AVATAR_UPLOAD
);
1690 } else if (sid
== QAS_IMAGE_UPDATE
) {
1691 postImageActivity(json
, id
& QAS_AVATAR_UPLOAD
);
1694 if ((id
& QAS_POST
) && m_messageWindow
&& !m_messageWindow
->isVisible())
1695 m_messageWindow
->clear();
1697 if (id
& QAS_REFRESH
) {
1702 //------------------------------------------------------------------------------
1703 // FIXME: this shouldn't be implemented in millions of places
1705 void PumpApp::refreshObject(QASAbstractObject
* obj
) {
1709 QDateTime now
= QDateTime::currentDateTime();
1710 QDateTime lr
= obj
->lastRefreshed();
1712 if (lr
.isNull() || lr
.secsTo(now
) > 10) {
1713 obj
->lastRefreshed(now
);
1714 request(obj
->apiLink(), obj
->asType());
1718 //------------------------------------------------------------------------------
1720 void PumpApp::setLoading(bool on
) {
1721 if (!m_loadIcon
|| m_isLoading
== on
)
1727 m_loadIcon
->setMovie(NULL
);
1728 m_loadIcon
->setPixmap(QPixmap(":/images/empty.gif"));
1729 } else if (m_loadMovie
->isValid()) {
1730 // m_loadIcon->setPixmap(QPixmap());
1731 m_loadIcon
->setMovie(m_loadMovie
);
1732 m_loadMovie
->start();