/*
 *   This file is part of Dianara
 *   Copyright 2012-2019  JanKusanagi JRR <jancoding@gmx.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .
 */

#include "pumpcontroller.h"

PumpController::PumpController(QObject *parent) :  QObject(parent)
{
    m_userAgentString = QByteArrayLiteral("Dianara/1.4.3");

    m_serverScheme = QStringLiteral("https://"); // Default, unless --nohttps is used

    m_postsPerPageMain = 20;
    m_postsPerPageOther = 10;

    m_proxyUsesAuth = false;

    m_ignoreSslErrors = false;
    m_ignoreSslInImages = false;

    m_silentFollows = false;
    m_silentListsHandling = false;
    m_silentLikes = false;

    qDebug() << "PumpController: about to initialize QOAuth.\n"
             << "** If you get a crash now, make sure your build of QOAuth is "
                "built with the same Qt version as Dianara.";
    m_qoauth = new QOAuth::Interface(this);
    m_qoauth->setRequestTimeout(60000); // 1 minute timeout

    QSettings settings;
    m_clientId = settings.value("clientID").toString();
    m_clientSecret = settings.value("clientSecret").toString();
    m_qoauth->setConsumerKey(m_clientId.toLocal8Bit());
    m_qoauth->setConsumerSecret(m_clientSecret.toLocal8Bit());


    m_applicationAuthorized = settings.value("isApplicationAuthorized",
                                             false).toBool();
    if (m_applicationAuthorized)
    {
        qDebug() << "Dianara is already authorized for user ID:"
                 << settings.value("userID").toString();

        m_token = settings.value("token").toString().toLocal8Bit();
        m_tokenSecret = settings.value("tokenSecret").toString().toLocal8Bit();

        qDebug() << "Using token" << m_token;
        qDebug() << "And token secret"
                 << m_tokenSecret.left(5) + "********** (hidden)";
    }


    // Handle responses to HTTP requests
    connect(&m_nam, &QNetworkAccessManager::finished,
            this, &PumpController::requestFinished);

    // Handle SSL errors; ignore for images if option is set,
    // or for everything if --ignoresslerrors is used
    connect(&m_nam, &QNetworkAccessManager::sslErrors,
            this, &PumpController::sslErrorsHandler);


    m_userFollowingCount = 0;
    m_userFollowersCount = 0;

    m_initialDataStep = 0;
    m_initialDataAttempts = 0;


    m_initialDataTimer = new QTimer(this);
    m_initialDataTimer->setSingleShot(false); // Triggered constantly until stopped
    connect(m_initialDataTimer, &QTimer::timeout,
            this, &PumpController::getInitialData);


    m_webfingerCheckTimer = new QTimer(this);
    m_webfingerCheckTimer->setSingleShot(true);
    connect(m_webfingerCheckTimer, &QTimer::timeout,
            this, &PumpController::onValidationTimeout);

    m_webfingerCheckTimedOut = false;


    qDebug() << "PumpController created";
}


PumpController::~PumpController()
{
    qDebug() << "PumpController destroyed";
}


void PumpController::setProxyConfig(QNetworkProxy::ProxyType proxyType,
                                    QString hostname, int port,
                                    bool useAuth,
                                    QString user, QString password)
{
    QNetworkProxy proxy;
    proxy.setType(proxyType);
    proxy.setHostName(hostname);
    proxy.setPort(port);
    if (useAuth)
    {
        proxy.setUser(user);
        proxy.setPassword(password);

        // If using auth, and proxy type is set
        if (proxyType != QNetworkProxy::NoProxy)
        {
            m_proxyUsesAuth = true;
        }
    }
    m_nam.setProxy(proxy);
    m_qoauth->networkAccessManager()->setProxy(proxy);

    qDebug() << "Proxy config applied:" << hostname << port << user;
}

bool PumpController::needsProxyPassword()
{
    if (m_proxyUsesAuth && m_nam.proxy().password().isEmpty())
    {
        return true;
    }

    return false;
}

void PumpController::setProxyPassword(QString password)
{
    QNetworkProxy proxy = m_nam.proxy();
    proxy.setPassword(password);
    m_nam.setProxy(proxy);
    m_qoauth->networkAccessManager()->setProxy(proxy);
}


void PumpController::updateApiUrls()
{
    m_apiBaseUrl = m_serverScheme + m_serverDomain
                   + "/api/user/" + m_userName;
    qDebug() << "Base API URL is:" << m_apiBaseUrl;

    this->m_apiFeedUrl = m_apiBaseUrl + "/feed"; // Outbox
}



void PumpController::setPostsPerPageMain(int ppp) // TODO: replace with globalObject query
{
    m_postsPerPageMain = ppp;
    qDebug() << "PumpController: setting postsPerPage (main) to"
             << m_postsPerPageMain;
}

void PumpController::setPostsPerPageOther(int ppp)
{
    m_postsPerPageOther = ppp;
    qDebug() << "PumpController: setting postsPerPage (other) to"
             << m_postsPerPageOther;
}



/*
 * Set new user ID (user@domain.tld) and clear OAuth-related tokens/secrets
 *
 */
void PumpController::setNewUserId(QString userId)
{
    m_userId = userId;
    const QStringList splittedUserId = m_userId.split("@");
    m_userName = splittedUserId.first();    // Get username, before @
    m_serverDomain = splittedUserId.last(); // Get server domain, after @
    qDebug() << "Server to connect:" << m_serverDomain
             << "; with username:" << m_userName;

    m_clientId.clear();
    m_clientSecret.clear();
    m_token.clear();
    m_tokenSecret.clear();

    m_postsEverSeen.clear();


    m_applicationAuthorized = false;
    emit this->authorizationStatusChanged(m_applicationAuthorized);
}




/*
 * Get "pumpserver.org" and "user" from "user@pumpserver.org",
 * set OAuth token from Account dialog
 *
 */
void PumpController::setUserCredentials(QString userId)
{
    m_initialDataTimer->stop(); // Just in case it was running before


    m_userId = userId;
    const QStringList splittedUserId = m_userId.split("@");
    m_userName = splittedUserId.first();
    m_serverDomain = splittedUserId.last();
    qDebug() << "New user ID is:" << m_userId;

    this->updateApiUrls();


    emit this->authorizationStatusChanged(m_applicationAuthorized);

    m_haveProfile = false;
    m_haveFollowing = false;
    m_haveFollowers = false;
    m_havePersonLists = false;
    m_haveMainTL = false;
    m_haveDirectTL = false;
    m_haveActivityTL = false;
    m_haveFavoritesTL = false;
    m_haveMainMF = false;
    m_haveDirectMF = false;
    m_haveActivityMF = false;

    m_initialDataStep = 0;
    m_initialDataAttempts = 0;

    // This will call getUserProfile(), getContactList(), several getFeed()...
    if (m_applicationAuthorized)
    {
        m_initialDataTimer->start(2000);  // Start 2 seconds after setting the ID
                                          // (mainly on program startup)
        emit logMessage(tr("Authorized to use account %1. Getting initial data.")
                        .arg(m_userId));
    }
    else
    {
        emit logMessage(tr("There is no authorized account."));
    }
}



QString PumpController::currentUserId()
{
    return m_userId;
}

QString PumpController::currentUsername()
{
    return m_userName;
}

QString PumpController::currentServerScheme()
{
    return m_serverScheme;
}

QString PumpController::currentServerDomain()
{
    return m_serverDomain;
}

QString PumpController::currentFollowersUrl()
{
    return m_userFollowersUrl;
}

int PumpController::currentFollowingCount()
{
    return m_userFollowingCount;
}

int PumpController::currentFollowersCount()
{
    return m_userFollowersCount;
}

bool PumpController::currentlyAuthorized()
{
    return m_applicationAuthorized;
}




/*
 * Get any user's profile (not only our own)
 *
 * GET https://pumpserver.example/api/user/username
 *
 */
void PumpController::getUserProfile(QString userId)
{
    const QStringList splittedUserId = userId.split("@");

    QString url = m_serverScheme + splittedUserId.last()
                  + "/api/user/" + splittedUserId.first();

    QNetworkRequest userProfileRequest = this->prepareRequest(url,
                                                              QOAuth::GET,
                                                              UserProfileRequest);
    m_nam.get(userProfileRequest);

    qDebug() << "Requested user profile:" << userProfileRequest.url().toString();
}



/*
 * Update user's profile
 *
 */
void PumpController::updateUserProfile(QString avatarUrl, QString fullName,
                                       QString hometown, QString bio)
{
    QString url = m_apiBaseUrl + "/profile";
    QNetworkRequest updateProfileRequest = this->prepareRequest(url,
                                                                QOAuth::PUT,
                                                                UpdateProfileRequest);

    QVariantMap jsonVariantImage;
    jsonVariantImage.insert("url", avatarUrl);
    jsonVariantImage.insert("width",  90);   // FIXME: don't hardcode this
    jsonVariantImage.insert("height", 90);   // get values from actual pixmap

    QVariantMap jsonVariantLocation;
    jsonVariantLocation.insert("objectType", "place");
    jsonVariantLocation.insert("displayName", hometown);

    QVariantMap jsonVariant;
    jsonVariant.insert("objectType", "person");

    if (!avatarUrl.isEmpty()) // Only add image object if a new image was uploaded
    {
        jsonVariant.insert("image", jsonVariantImage);
    }
    jsonVariant.insert("displayName", fullName);
    jsonVariant.insert("location", jsonVariantLocation);
    jsonVariant.insert("summary", bio);


    emit currentJobChanged(tr("Updating profile..."));

    QByteArray data = this->prepareJSON(jsonVariant);
    m_nam.put(updateProfileRequest, data);
    qDebug() << "Updating user profile" << fullName << hometown;
}


/*
 * Update user's e-mail, used for notifications and such.
 * This might change in the future.
 *
 */
void PumpController::updateUserEmail(QString newEmail, QString password)
{
    QNetworkRequest updateEmailRequest = this->prepareRequest(m_apiBaseUrl,
                                                              QOAuth::PUT,
                                                              UpdateEmailRequest);

    QVariantMap jsonEmailVariant;
    jsonEmailVariant.insert("email",    newEmail);

    // Changing the e-mail requires providing username (inmutable) and password
    jsonEmailVariant.insert("nickname", m_userName);
    jsonEmailVariant.insert("password", password);

    QByteArray data = this->prepareJSON(jsonEmailVariant);
    m_nam.put(updateEmailRequest, data);
}


/*
 * Add an avatar URL to the queue of pending
 *
 */
void PumpController::enqueueAvatarForDownload(QString url)
{
    if (QFile::exists(MiscHelpers::getCachedAvatarFilename(url))
     || m_pendingAvatarsList.contains(url))
    {
        qDebug() << "PumpController() Using cached avatar, or it is pending download...";
    }
    else
    {
        m_pendingAvatarsList.append(url);
        this->getAvatar(url);
        qDebug() << "PumpController() Avatar not cached, downloading" << url;
    }
}



/*
 * Add the URL of an image to the queue of pending-download
 *
 * connect() signal/slot if necessary to refresh when done
 *
 */
void PumpController::enqueueImageForDownload(QString url)
{
    if (QFile::exists(MiscHelpers::getCachedImageFilename(url))
     || m_pendingImagesList.contains(url))
    {
        qDebug() << "PumpController::enqueueImageForDownload(), "
                    "Using cached image, or requested image is pending download...";
    }
    else
    {
        m_pendingImagesList.append(url);
        this->getImage(url);

        qDebug() << "PumpController::enqueueImageForDownload(), "
                    "image not cached, downloading" << url;
    }
}




void PumpController::getAvatar(QString avatarUrl)
{
    if (avatarUrl.isEmpty())
    {
        return;
    }

    qDebug() << "Getting avatar" << avatarUrl;

    QNetworkRequest avatarRequest(QUrl((const QString)avatarUrl));
    avatarRequest.setRawHeader(QByteArrayLiteral("User-Agent"), m_userAgentString);
    avatarRequest.setAttribute(QNetworkRequest::User,
                               QVariant(AvatarRequest));

    m_nam.get(avatarRequest);
}



void PumpController::getImage(QString imageUrl)
{
    if (imageUrl.isEmpty())
    {
        return;
    }

    QNetworkRequest imageRequest = this->prepareRequest(imageUrl,
                                                        QOAuth::GET,
                                                        ImageRequest);
    /*
     * Follow HTTP redirects. Only for Qt 5.6 and newer.
     * Won't work if redirection is from https:// to http:// URLs.
     *
     * TODO -- FIXME: This attribute was obsoleted by
     *                QNetworkRequest::RedirectPolicyAttribute in Qt 5.9
     */
    imageRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    imageRequest.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User + 1),
                              QVariant(imageUrl)); // Track original URL

    m_nam.get(imageRequest);
    qDebug() << "getImage() imageRequest sent";
}


QNetworkReply *PumpController::getMedia(QString mediaUrl)
{
    QNetworkRequest mediaRequest = this->prepareRequest(mediaUrl,
                                                        QOAuth::GET,
                                                        MediaRequest);

    qDebug() << "getMedia() sending mediaRequest for:" << mediaUrl;
    return m_nam.get(mediaRequest);
}



void PumpController::notifyAvatarStored(QString avatarUrl, QString avatarFilename)
{
    m_pendingAvatarsList.removeAll(avatarUrl);

    emit avatarStored(avatarUrl, avatarFilename);
}

void PumpController::notifyImageStored(QString imageUrl)
{
    m_pendingImagesList.removeAll(imageUrl);

    emit imageStored(imageUrl);
}


void PumpController::notifyImageFailed(QString imageUrl)
{
    m_pendingImagesList.removeAll(imageUrl);

    emit imageFailed(imageUrl);
}




/*
 * GET https://pumpserver.example/api/user/username/following or /followers
 *
 */
void PumpController::getContactList(QString listType, int offset)
{
    qDebug() << "Getting contact list, type" << listType << "; offset:" << offset;

    QString url = m_apiBaseUrl + "/" + listType;

    QOAuth::ParamMap paramMap;
    paramMap.insert("count",  "200"); // 200 each time (max allowed by API)
    paramMap.insert("offset", QString("%1").arg(offset).toLocal8Bit());


    QNetworkRequest contactListRequest;
    if (listType == "following")
    {
        contactListRequest = this->prepareRequest(url,
                                                  QOAuth::GET,
                                                  FollowingListRequest,
                                                  paramMap);
        if (offset == 0)
        {
            m_totalReceivedFollowing = 0;
            m_followingIdList.clear();
            this->showStatusMessageAndLogIt(tr("Getting list of 'Following'..."));
        }
    }
    else
    {
        contactListRequest = this->prepareRequest(url,
                                                  QOAuth::GET,
                                                  FollowersListRequest,
                                                  paramMap);
        if (offset == 0)
        {
            m_totalReceivedFollowers = 0;
        }
        this->showStatusMessageAndLogIt(tr("Getting list of 'Followers'..."));
    }

    m_nam.get(contactListRequest);
}


void PumpController::getSiteUserList()
{
    QString url = m_serverScheme + m_serverDomain + "/api/users";

    QOAuth::ParamMap paramMap;
    paramMap.insert("count", "50"); // 50 users instead of the server default

    QNetworkRequest siteUsersRequest = this->prepareRequest(url,
                                                            QOAuth::GET,
                                                            SiteUserListRequest,
                                                            paramMap);

    emit currentJobChanged(tr("Getting site users for %1...",
                              "%1 is a server name").arg(m_serverDomain));
    m_nam.get(siteUsersRequest);
}


/*
 * Check if a user ID is in the "following" list
 *
 */
bool PumpController::userInFollowing(QString contactId)
{
    if (m_followingIdList.contains(contactId))
    {
        return true;
    }
    else
    {
        return false;
    }
}


void PumpController::updateInternalFollowingIdList(QStringList idList)
{
    m_followingIdList.append(idList);
}

void PumpController::removeFromInternalFollowingList(QString id)
{
    m_followingIdList.removeAll(id);
}



/*
 * GET https://pumpserver.example/api/user/username/lists/person
 *
 */
void PumpController::getListsList()
{
    qDebug() << "Getting list of lists";

    QString url = m_apiBaseUrl + "/lists/person";

    QOAuth::ParamMap paramMap;
    paramMap.insert("count",  "200"); // Get up to 200 lists

    QNetworkRequest listsListRequest = this->prepareRequest(url, QOAuth::GET,
                                                            ListsListRequest,
                                                            paramMap);

    emit currentJobChanged(tr("Getting list of person lists..."));

    m_nam.get(listsListRequest);
}


/*
 * Create a person list
 *
 */
void PumpController::createPersonList(QString name, QString description)
{
    qDebug() << "PumpController() creating person list:" << name;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       CreatePersonListRequest);

    QVariantList jsonVariantObjectTypes;
    jsonVariantObjectTypes << "person";

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "collection");
    jsonVariantObject.insert("objectTypes", jsonVariantObjectTypes);
    jsonVariantObject.insert("displayName", name);
    jsonVariantObject.insert("content", description);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "create");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;


    emit currentJobChanged(tr("Creating person list..."));
    m_nam.post(postRequest, data);
}


void PumpController::deletePersonList(QString listId)
{
    qDebug() << "PumpController::deletePersonList() deleting list" << listId;
    QNetworkRequest deleteRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                         QOAuth::POST,
                                                         DeletePersonListRequest);
    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "collection");
    jsonVariantObject.insert("id", listId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "delete");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;

    emit currentJobChanged(tr("Deleting person list..."));
    m_nam.post(deleteRequest, data);
}


void PumpController::getPersonList(QString url)
{
    qDebug() << "Getting a person list:" << url;

    QOAuth::ParamMap paramMap;
    paramMap.insert("count",  "200"); // Get 200 members // FIXME: could be more

    QNetworkRequest personListRequest = this->prepareRequest(url, QOAuth::GET,
                                                             PersonListRequest,
                                                             paramMap);
    emit currentJobChanged(tr("Getting a person list..."));

    m_nam.get(personListRequest);
}



/*
 * Add a new member to a list
 *
 */
void PumpController::addPersonToList(QString listId, QString personId)
{
    qDebug() << "PumpController() adding person to list:"
             << personId << listId;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       AddMemberToListRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "person");
    jsonVariantObject.insert("id", personId);

    QVariantMap jsonVariantTarget;
    jsonVariantTarget.insert("objectType", "collection");
    jsonVariantTarget.insert("id", listId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "add");
    jsonVariant.insert("object", jsonVariantObject);
    jsonVariant.insert("target", jsonVariantTarget);
    if (m_silentListsHandling) // In 'private' mode, address to the same as the object
    {
        QVariantList jsonAudienceTo;
        jsonAudienceTo.append(jsonVariantObject); // To: only the specific user
        jsonVariant.insert("to", jsonAudienceTo);
    }


    const QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;


    emit currentJobChanged(tr("Adding person to list..."));
    m_nam.post(postRequest, data);
}



/*
 * Remove member from a list
 *
 */
void PumpController::removePersonFromList(QString listId, QString personId)
{
    qDebug() << "PumpController() removing person from list:" << personId << listId;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       RemoveMemberFromListRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "person");
    jsonVariantObject.insert("id", personId);

    QVariantMap jsonVariantTarget;
    jsonVariantTarget.insert("objectType", "collection");
    jsonVariantTarget.insert("id", listId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "remove");
    jsonVariant.insert("object", jsonVariantObject);
    jsonVariant.insert("target", jsonVariantTarget);
    if (m_silentListsHandling) // 'private' mode
    {
        QVariantList jsonAudienceTo;
        jsonAudienceTo.append(jsonVariantObject);
        jsonVariant.insert("to", jsonAudienceTo);
    }


    const QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;

    emit currentJobChanged(tr("Removing person from list..."));
    m_nam.post(postRequest, data);
}



/*
 * Create a group where users can join and post messages for the other
 * members, similar to the StatusNet groups.
 *
 */
void PumpController::createGroup(QString name,
                                 QString summary,
                                 QString description)
{
    qDebug() << "PumpController() creating group:" << name;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       CreateGroupRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "group");
    if (!name.isEmpty())
    {
        jsonVariantObject.insert("displayName", name);
    }
    jsonVariantObject.insert("summary", summary);
    jsonVariantObject.insert("content", description);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "create");
    jsonVariant.insert("object", jsonVariantObject);


    // Audience, To:Public // Groups created public ATM -- FIXME
    QVariantMap jsonVariantPublic;
    jsonVariantPublic.insert("objectType", "collection");
    jsonVariantPublic.insert("id", "http://activityschema.org/collection/public");

    QVariantList jsonVariantAudience;
    jsonVariantAudience.append(jsonVariantPublic);
    jsonVariant.insert("to", jsonVariantAudience); // To: Public


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;

    emit currentJobChanged(tr("Creating group..."));
    m_nam.post(postRequest, data);
}


/*
 * Join a group to be able to send messages to it.
 * Joining based on ID.
 *
 */
void PumpController::joinGroup(QString id)
{
    qDebug() << "PumpController() joining group:" << id;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       JoinGroupRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "group");
    jsonVariantObject.insert("id", id);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "join");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;

    emit currentJobChanged(tr("Joining group..."));
    m_nam.post(postRequest, data);
}


void PumpController::leaveGroup(QString id)
{
    qDebug() << "PumpController() leaving group:" << id;

    QNetworkRequest postRequest = this->prepareRequest(this->m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       LeaveGroupRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "group");
    jsonVariantObject.insert("id", id);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "leave");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;

    emit currentJobChanged(tr("Leaving group..."));
    m_nam.post(postRequest, data);
}




/*
 * Get list of people who liked a specific post
 *
 */
void PumpController::getPostLikes(QString postLikesUrl)
{
    qDebug() << "Getting likes for post" << postLikesUrl;

    emit currentJobChanged(tr("Getting likes..."));

    QOAuth::ParamMap paramMap;
    paramMap.insert("count", "100"); // TMP, up to 100 likes

    QNetworkRequest likesRequest = this->prepareRequest(postLikesUrl,
                                                        QOAuth::GET,
                                                        PostLikesRequest,
                                                        paramMap);
    m_nam.get(likesRequest);
}



/*
 * Get comments for one specific post
 *
 * GET https://pumpserver.example/api/#objectType#/#id#/replies
 * or proxyed URL. URL is given by the post itself
 *
 */
void PumpController::getPostComments(QString postCommentsUrl, QString postId)
{
    qDebug() << "Getting comments for post" << postCommentsUrl;

    if (!urlIsInOurHost(postCommentsUrl)) // Post in another server and no proxyURL
    {
        emit currentJobChanged(tr("The comments for this post cannot be loaded "
                                  "due to missing data on the server."));
        return;
    }

    emit currentJobChanged(tr("Getting comments..."));

    QOAuth::ParamMap paramMap;
    paramMap.insert("count", "200"); // TMP, up to 200 comments / FIXME?

    QNetworkRequest commentsRequest = this->prepareRequest(postCommentsUrl,
                                                           QOAuth::GET,
                                                           PostCommentsRequest,
                                                           paramMap);
    commentsRequest.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User + 1),
                                 QVariant(postId));

    m_nam.get(commentsRequest);
}


/*
 * Get list of people who shared a specific post
 *
 */
void PumpController::getPostShares(QString postSharesUrl)
{
    // TODO
}




/*
 * Get a feed
 *
 * Major timelines: "Timeline", "Messages", "Activity" and "Favorites"
 *
 * GET https://pumpserver.example/api/username/inbox/major
 *
 * /inbox/direct/major = Direct timeline, posts with the user's address in
 *                       the "To:" field, that is, sent explicitly to the user
 *
 * /feed/major = Activity timeline, user's own posts
 *
 * /favorites = Favorites timeline, posts where user clicked "like"
 *
 * Minor feeds: "Meanwhile", "Mentions" and "Actions"
 *
 * GET https://pumpserver.example/api/username/inbox/minor
 *                                            /inbox/direct/minor
 *                                            /feed/minor
 *
 */
void PumpController::getFeed(PumpController::requestTypes feedType,
                             int itemCount, QString url, int feedOffset)
{
    // Name of the feed and API path
    QStringList feedNameAndPath = this->getFeedNameAndPath(feedType);
    qDebug() << "PumpController::getFeed() " << feedNameAndPath;

    emit currentJobChanged(tr("Getting '%1'...",
                              "%1 is the name of a feed")
                           .arg(feedNameAndPath.first()));

    QOAuth::ParamMap paramMap;
    paramMap.insert("count", QString("%1").arg(itemCount).toLocal8Bit());
    if (feedOffset != 0)
    {
        paramMap.insert("offset", QString("%1").arg(feedOffset).toLocal8Bit());
    }

    if (url.isEmpty())
    {
        url = m_apiBaseUrl + feedNameAndPath.last();
    }
    else
    {
        if (url.contains("?")) // Only if there are parameters in the URL
        {
            // FIXME: This should be made safer
            QStringList splitUrl = url.split("?");
            url = splitUrl.first();
            QStringList splitParams = splitUrl.last().split("=");

            paramMap.insert(splitParams.first().toUtf8(),   // 'before' or 'since'
                            splitParams.last().toUtf8());   // An activity ID
        }
    }

    QNetworkRequest feedRequest = this->prepareRequest(url, QOAuth::GET,
                                                       feedType,
                                                       paramMap);
/*
    qDebug() << "PumpController::getFeed() ****\nfeedRequest:\n";
    qDebug() << feedRequest.rawHeader("Authorization");
    qDebug() << "\n*\n\nURL: " << feedRequest.url().toString() << "\n\n*******";
*/
    m_nam.get(feedRequest);
}

QStringList PumpController::getFeedNameAndPath(int feedType)
{
    QStringList nameAndPath;

    switch (feedType)
    {
    case MainTimelineRequest:
        nameAndPath = QStringList{ tr("Timeline"),
                                   QStringLiteral("/inbox/major") };
        break;
    case DirectTimelineRequest:
        nameAndPath = QStringList{ tr("Messages"),
                                   QStringLiteral("/inbox/direct/major") };
        break;
    case ActivityTimelineRequest:
        nameAndPath = QStringList{ tr("Activity"),
                                   QStringLiteral("/feed/major") };
        break;
    case FavoritesTimelineRequest:
        nameAndPath = QStringList{ tr("Favorites"),
                                   QStringLiteral("/favorites") };
        break;

    case MinorFeedMainRequest:
        nameAndPath = QStringList{ tr("Meanwhile"),
                                   QStringLiteral("/inbox/minor") };
        break;
    case MinorFeedDirectRequest:
        nameAndPath = QStringList{ tr("Mentions"),
                                   QStringLiteral("/inbox/direct/minor") };
        break;
    case MinorFeedActivityRequest:
        nameAndPath = QStringList{ tr("Actions"),
                                   QStringLiteral("/feed/minor") };
        break;

    case UserTimelineRequest:
        nameAndPath = QStringList{ tr("User timeline"), QString() }; //// FIXME?
        break;

    default:
        nameAndPath = QStringList{ QString(), QString() };
        qDebug() << "PumpController::getFeedNameAndPath() wrong feed type!";
    }

    return nameAndPath;
}


QString PumpController::getFeedApiUrl(int feedType)
{
    QString fullFeedUrl = m_apiBaseUrl;
    fullFeedUrl.append(this->getFeedNameAndPath(feedType).last());

    return fullFeedUrl;
}



/*
 * Prepare a QNetworkRequest with OAuth header, content type and user agent.
 *
 */
QNetworkRequest PumpController::prepareRequest(QString url,
                                               QOAuth::HttpMethod method,
                                               requestTypes requestType,
                                               QOAuth::ParamMap paramMap,
                                               QString contentTypeString)
{
    QByteArray authHeader = m_qoauth->createParametersString(url,
                                                             method,
                                                             m_token,
                                                             m_tokenSecret,
                                                             QOAuth::HMAC_SHA1,
                                                             paramMap,
                                                             QOAuth::ParseForHeaderArguments);
    //qDebug() << "QOAuth::error()" << m_qoauth->error() << " (200=OK)";

    QNetworkRequest request;

    // Don't append inline parameters if they're empty, that can mess up things
    if (!paramMap.isEmpty())
    {
        url.append(m_qoauth->inlineParameters(paramMap,
                                              QOAuth::ParseForInlineQuery));
    }

    request.setUrl(QUrl(url));

    // Only add Authorization header if we're requesting something in our server
    if (request.url().host() == m_serverDomain)
    {
        // Un-percent-encode the URL; needed to avoid "invalid signature" error
        // when parameters have special chars like % or /
        url = QByteArray::fromPercentEncoding(url.toLocal8Bit());
        request.setUrl(QUrl(url));

        request.setRawHeader(QByteArrayLiteral("Authorization"), authHeader);
    }

    if (!contentTypeString.isEmpty())
    {
        request.setHeader(QNetworkRequest::ContentTypeHeader,
                          contentTypeString);
    }

    request.setRawHeader(QByteArrayLiteral("User-Agent"), m_userAgentString);

    request.setAttribute(QNetworkRequest::User, QVariant(requestType));

    return request;
}


/*
 * Generate JSON plaintext from a VarianMap.
 *
 */
QByteArray PumpController::prepareJSON(QVariantMap jsonVariantMap)
{
    QJsonDocument jsonDocument;
    jsonDocument = QJsonDocument::fromVariant(jsonVariantMap);

    return jsonDocument.toJson();
}


/*
 * Read JSON and generate a QVariantMap
 *
 */
QVariantMap PumpController::parseJSON(QByteArray rawData, bool *parsedOk)
{
    QVariantMap jsonData;
    bool parsed;
    QJsonDocument jsonDocument;

    jsonDocument = QJsonDocument::fromJson(rawData);
    jsonData = jsonDocument.toVariant().toMap();
    parsed = !jsonDocument.isNull(); // tmp? FIXME
    *parsedOk = parsed;

    return jsonData;
}


/*
 * Upload a file to the /uploads feed for the user
 *
 * Used to upload pictures, audio, video and misc files
 *
 */
QNetworkReply *PumpController::uploadFile(QString filename,
                                          QString contentType,
                                          requestTypes uploadType)
{
    qDebug() << "PumpController::uploadFile()" << filename << contentType;

    QString url = m_apiBaseUrl + "/uploads";
    QNetworkRequest postRequest = this->prepareRequest(url,
                                                       QOAuth::POST,
                                                       uploadType,
                                                       QOAuth::ParamMap(),
                                                       contentType);
    QFile file(filename);
    file.open(QIODevice::ReadOnly);
    QByteArray data = file.readAll(); // FIXME: This can use a lot of RAM with big files
    file.close();

    this->showStatusMessageAndLogIt(tr("Uploading %1",
                                       "1=filename").arg(filename)
                                    + QString(" (%1, %2)")
                                      .arg(contentType)
                                      .arg(MiscHelpers::fileSizeString(filename)));

    return m_nam.post(postRequest, data);
}



bool PumpController::urlIsInOurHost(QString url)
{
    return (QUrl(url).host() == m_serverDomain);
}


void PumpController::addCommentUrlToSeenList(QString id, QString url)
{
    if (urlIsInOurHost(url))
    {
        m_postsEverSeen.insert(id, url);
    }
}


QString PumpController::commentsUrlForPost(QString id)
{
    return m_postsEverSeen.value(id).toString();
}




void PumpController::showTransientMessage(QString message)
{
    emit transientStatusBarMessage(message);
}



void PumpController::showStatusMessageAndLogIt(QString message, QString url)
{
    emit currentJobChanged(message);
    emit logMessage(message, url);
}



/*
 * Show a message in the status bar with a snippet from a post or comment,
 * or its title, and add it to the log.
 *
 */
void PumpController::showObjectSnippetAndLogIt(QString message,
                                               QVariantMap jsonMap,
                                               QString messageWhenTitled)
{
    QString title = jsonMap.value("object").toMap()
                           .value("displayName").toString().trimmed();

    QString snippet;
    if (title.isEmpty())
    {
        QString content = jsonMap.value("object").toMap()
                                 .value("content").toString();
        snippet = MiscHelpers::htmlToPlainText(content, 40); // Limit to 40 chars
    }
    else
    {
        if (!messageWhenTitled.isEmpty())
        {
            message = messageWhenTitled;
        }

        snippet = title;
    }

    emit currentJobChanged(message.arg("\"" + snippet + "\""));
    emit logMessage(message.arg("<b>&quot;" + snippet + "&quot;</b>"));
}


void PumpController::setIgnoreSslErrors(bool state)
{
    m_ignoreSslErrors = state;
    m_qoauth->setIgnoreSslErrors(state);
}


void PumpController::setIgnoreSslInImages(bool state)
{
    m_ignoreSslInImages = state;
}

void PumpController::setNoHttpsMode()
{
    m_serverScheme = "http://"; // Instead of the default https://
    this->updateApiUrls(); // Update API base URL with this scheme
}



void PumpController::setSilentFollows(bool state)
{
    m_silentFollows = state;
}

void PumpController::setSilentLists(bool state)
{
    m_silentListsHandling = state;
}

void PumpController::setSilentLikes(bool state)
{
    m_silentLikes = state;
}



void PumpController::updatePostsEverSeen(QVariantMap postMap)
{
    m_postsEverSeen = postMap;
}


QVariantMap PumpController::getPostsEverSeen()
{
    return m_postsEverSeen;
}



/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
/*********************************** SLOTS *********************************/
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/
/***************************************************************************/




void PumpController::requestFinished(QNetworkReply *reply)
{
    const int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)
                               .toInt();
    const int requestType = reply->request()
                                  .attribute(QNetworkRequest::User).toInt();
    const QString requestExtraString = reply->request()
                                             .attribute(QNetworkRequest::Attribute(QNetworkRequest::User + 1))
                                             .toString();
    const QString replyUrl = reply->url().toString();
    const QString replyHost = reply->url().host();
    const QString replyErrorString = reply->errorString();
    const QString replyServerVersion = reply->rawHeader(QByteArrayLiteral("Server"));
    const QByteArray replyFirstLineRaw = reply->readLine(); // As of Pump.io 1.x, errors have <br>'s
    const QByteArray replyData = replyFirstLineRaw + reply->readAll();

    QString replyFirstLine;
    QString prettyLogMessage;


    qDebug() << "Request finished. HTTP code:" << httpCode;
    qDebug() << "Size:" << replyData.length() << "bytes; URL:" << replyUrl;
    qDebug() << "Request type:" << requestType;

    // We got all necessary data, clean up
    reply->deleteLater();


    // Special control after sending a post or a comment
    if (httpCode != 200) // If not OK
    {
        replyFirstLine = QString(replyFirstLineRaw);
        int endOfReplyFirstLine = replyFirstLine.indexOf(QStringLiteral("<br>"),
                                                         0,
                                                         Qt::CaseInsensitive);
        if (endOfReplyFirstLine > 0)
        {
            replyFirstLine.truncate(endOfReplyFirstLine); // Cut at first <br>
        }

        if (replyData.startsWith(QByteArrayLiteral("{"))) // Looks like JSON containing an error message
        {
            qDebug() << "Parsing JSON error message";
            bool jsonErrorParsed = false;
            QVariantMap jsonError = this->parseJSON(replyData, &jsonErrorParsed);

            if (jsonErrorParsed)
            {
                // Since it's a JSON-encoded error message, use it instead of
                // the first data line; These can have Unicode symbols
                QString jsonErrorString = jsonError.value("error").toString()
                                                                  .trimmed();
                if (!jsonErrorString.isEmpty())
                {
                    replyFirstLine = jsonErrorString;
                }
            }
        }
        else if (replyData.startsWith(QByteArrayLiteral("<"))) // Looks like HTML
        {
            qDebug() << "Parsing HTML error message";

            QTextDocument replyHtml;
            replyHtml.setHtml(replyData);
            QString htmlErrorString = replyHtml.toPlainText();

            int firstBreakPos = htmlErrorString.indexOf("\n");
            if (firstBreakPos > 0)
            {
                htmlErrorString.truncate(firstBreakPos);
            }

            if (!htmlErrorString.isEmpty())
            {
                replyFirstLine = htmlErrorString;
            }
        }


        if (requestType == PublishPostRequest)
        {
            emit postPublishingFailed();
        }
        else if (requestType == UpdatePostRequest)
        {
            emit postPublishingFailed();  // kinda TMP
        }
        else if (requestType == CommentPostRequest)
        {
            emit commentPostingFailed(requestExtraString);
        }
        else if (requestType == UpdateCommentRequest)
        {
            emit commentPostingFailed(requestExtraString); // also, kinda TMP
        }
        else if (requestType == UploadMediaForPostRequest)
        {
            emit postPublishingFailed(); // FIXME?
        }
        else if (requestType == MediaRequest)
        {
            emit downloadFailed(replyUrl);
        }
        else if (requestType == ImageRequest)
        {
            this->notifyImageFailed(replyUrl);
        }
        else if (requestType == MainTimelineRequest
              || requestType == DirectTimelineRequest
              || requestType == ActivityTimelineRequest
              || requestType == FavoritesTimelineRequest)
        {
            emit currentJobChanged(tr("Error loading timeline!")); // FIXME: gets overwritten immediately
            emit timelineFailed(requestType);
        }
        else if (requestType == UserTimelineRequest)  // FIXME, merge with previous?
        {
            emit userTimelineFailed();
        }
        else if (requestType == MinorFeedMainRequest
              || requestType == MinorFeedDirectRequest
              || requestType == MinorFeedActivityRequest)
        {
            emit currentJobChanged(tr("Error loading minor feed!")); // FIXME: gets overwritten immediately
            emit minorFeedFailed(requestType);
        }
        else if (requestType == PostCommentsRequest)
        {
            emit commentsNotReceived(requestExtraString);
        }
        else if (requestType == CheckContactRequest)
        {
            m_webfingerCheckTimer->stop(); // Timeout checking is irrelevant at this point

            qDebug() << "Contact ID is not valid, or server is down:" << replyUrl;
            this->showStatusMessageAndLogIt(tr("Unable to verify the address!")
                                            + " ("
                                            + m_addressPendingToFollow
                                            + ")");

            emit contactVerified(m_addressPendingToFollow,
                                 httpCode,
                                 m_webfingerCheckTimedOut,
                                 replyServerVersion);

            m_addressPendingToFollow.clear();

            return; // Avoid showing ugly 404 error
        }
    }


    const QString httpErrorString = tr("HTTP error",
                                       "For the following HTTP error codes"
                                       "you can check "
                                       "http://en.wikipedia.org/wiki/List_of_HTTP_status_codes "
                                       "in your language") + ": ";
    QString errorTypeString;

    switch (httpCode)
    {
    //////////////////////////////////////////////// First, handle error codes
    case 504:
        errorTypeString = httpErrorString
                        + tr("Gateway Timeout", "HTTP 504 error string")
                        + " (504)";
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 504: Gateway Timeout.";
        qDebug() << "Data:  " << replyData;
        return;

    case 503:
        errorTypeString = httpErrorString
                        + tr("Service Unavailable", "HTTP 503 error string")
                        +  " (503) - " + replyFirstLine;

        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);

        // if not just an image, it's important, so popup notification too
        if (requestType != ImageRequest && requestType != AvatarRequest)
        {
            emit showErrorNotification(errorTypeString + "\n"
                                       + replyUrl);
        }

        qDebug() << "HTTP 503: Service Unavailable.";
        qDebug() << "Data:  " << replyData;
        return;

    case 502:
        errorTypeString = httpErrorString
                        + tr("Bad Gateway", "HTTP 502 error string")
                        +  " (502) - " + replyFirstLine;
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 502: Bad Gateway.";
        qDebug() << "Data:  " << replyData;
        return;

    case 501:
        errorTypeString = httpErrorString
                        + tr("Not Implemented", "HTTP 501 error string")
                        + " (501) - " + replyFirstLine;
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 501: Not Implemented.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 500:
        errorTypeString = httpErrorString
                        + tr("Internal Server Error", "HTTP 500 error string")
                        + " (500) - " + replyFirstLine;

        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);

        // if not just an image, it's important, so popup notification too
        if (requestType != ImageRequest && requestType != AvatarRequest)
        {
            emit showErrorNotification(errorTypeString + "\n"
                                       + replyUrl);
        }

        qDebug() << "HTTP 500: Internal Server Error.";
        qDebug() << "Data:  " << replyData;
        return;


    case 410:
        errorTypeString = httpErrorString
                        + tr("Gone", "HTTP 410 error string")
                        + " (410) " + replyFirstLine;
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 410: Gone.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 404:
        errorTypeString = httpErrorString
                        + tr("Not Found", "HTTP 404 error string")
                        + " (404)";
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 404: Not Found.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 403:
        errorTypeString = httpErrorString
                        + tr("Forbidden", "HTTP 403 error string")
                        + " (403) - " + replyFirstLine;
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 403: Forbidden.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 401:
        errorTypeString = httpErrorString
                        + tr("Unauthorized", "HTTP 401 error string")
                        + " (401) - " + replyFirstLine;
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 401: Unauthorized.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;


    case 400:
        errorTypeString = httpErrorString
                        + tr("Bad Request", "HTTP 400 error string")
                        + " (400) - " + replyFirstLine;

        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);

        // if not just an image, it's important, so popup notification too
        if (requestType != ImageRequest && requestType != AvatarRequest)
        {
            emit showErrorNotification(errorTypeString + "\n"
                                       + replyUrl);
        }

        qDebug() << "HTTP 400: Bad Request.";
        qDebug() << "Data:  " << replyData;
        return;

    case 302:
        errorTypeString = httpErrorString
                        + tr("Moved Temporarily", "HTTP 302 error string")
                        + " (302)";
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 302: Moved Temporarily.";
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 301:
        errorTypeString = httpErrorString
                        + tr("Moved Permanently", "HTTP 301 error string")
                        + " (301)";
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "HTTP 301: Moved Permanently.";
        // TMP - FIXME: useful?
        //qDebug() << "-- To: " << reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
        qDebug() << "Data:  " << replyFirstLineRaw;
        return;

    case 0: // Other kinds of network errors
        errorTypeString = tr("Error connecting to %1").arg(replyHost);
        emit currentJobChanged(errorTypeString + ": " + replyErrorString);
        emit logMessage(errorTypeString + ": " + replyErrorString, replyUrl);

        qDebug() << "Error connecting to" << replyHost << ": "
                 << replyErrorString;
        return;

    // Other HTTP codes
    default:
        errorTypeString = tr("Unhandled HTTP error code %1").arg(httpCode);
        emit currentJobChanged(errorTypeString + ": " + replyUrl);
        emit logMessage(errorTypeString, replyUrl);
        qDebug() << "Unhandled HTTP error " << httpCode;
        qDebug() << "Data:  " << replyData;
        return;


    //////////////////////////////////////// The good one!
    case 200:
        qDebug() << "HTTP 200: OK!";
    }


    // At this point, httpCode should be 200 = OK



    // Stuff for JSON parsing
    bool jsonParsedOK = false;
    QVariantMap jsonData;
    QVariantList jsonDataList;


    // Unless it was an AvatarRequest, ImageRequest or MediaRequest,
    // it should be JSON, so parse it
    if (requestType != AvatarRequest
     && requestType != ImageRequest
     && requestType != MediaRequest)
    {
        jsonData = this->parseJSON(replyData, &jsonParsedOK);

        qDebug() << "JSON data size (items):" << jsonData.size();
        qDebug() << "Keys:" << jsonData.keys();
    }


    //////////////////////////////////////////////////////////////////

    switch (requestType)
    {

    case ClientRegistrationRequest:
        qDebug() << "Client Registration was requested";

        qDebug() << "Raw JSON:" << jsonData;

        if (jsonParsedOK && jsonData.size() > 0)
        {
            m_clientId = jsonData.value("client_id").toString();
            m_clientSecret = jsonData.value("client_secret").toString();

            // FIXME: error control, etc.
            // check if jsonData.keys().contains("client_id") !!

            QSettings settings;
            settings.setValue("clientID",     m_clientId);
            settings.setValue("clientSecret", m_clientSecret);
            settings.sync();

            this->getToken();
        }

        break;



    case UserProfileRequest:
        qDebug() << "A user profile was requested";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            QVariantMap profileMap = jsonData.value("profile").toMap();
            if (profileMap.value("id").toString() == "acct:" + m_userId)
            {
                qDebug() << "Received OWN profile; keys:" << profileMap.keys();
                qDebug() << "Links from profile:" << profileMap.value("links").toMap().keys();
                qDebug() << "Lists from profile:" << profileMap.value("lists").toMap()
                                                               .value("totalItems").toInt();
                qDebug() << "Lists URL:" << profileMap.value("lists").toMap()
                                                      .value("url").toString();
                m_haveProfile = true;

                QString profImageUrl = profileMap.value("image").toMap()
                                                 .value("url").toString();
                QString profDisplayName = profileMap.value("displayName")
                                                    .toString();
                QString profLocation = profileMap.value("location").toMap()
                                                 .value("displayName").toString();
                QString profSummary = profileMap.value("summary").toString();

                QString userEmail = jsonData.value("email").toString();
                qDebug() << "E-mail configured for the account:" << userEmail;

                emit profileReceived(profImageUrl, profDisplayName,
                                     profLocation, profSummary,
                                     userEmail);

                // Store also the user's followers URL, for posting to Followers
                m_userFollowersUrl = profileMap.value("followers").toMap()
                                               .value("url").toString();

                m_userFollowingCount = profileMap.value("following").toMap()
                                                 .value("totalItems").toInt();
                m_userFollowersCount = profileMap.value("followers").toMap()
                                                 .value("totalItems").toInt();

                qDebug() << "Following count:" << m_userFollowingCount;
                qDebug() << "Followers count:" << m_userFollowersCount;

                this->logMessage(tr("Server version: %1")
                                 .arg(replyServerVersion));

                this->showStatusMessageAndLogIt(tr("Profile received.")
                                                + " "
                                                + tr("Followers")
                                                + QString(": %1;")
                                                  .arg(QLocale::system()
                                                       .toString(m_userFollowersCount))
                                                + " "
                                                + tr("Following")
                                                + QString(": %1")
                                                  .arg(QLocale::system()
                                                       .toString(m_userFollowingCount)));
            }
            else
            {
                qDebug() << "Expected profile information; got:";
                qDebug() << replyData;
            }
        }

        break;


    case UpdateProfileRequest:
        this->showStatusMessageAndLogIt(tr("Profile updated."));
        this->getUserProfile(m_userId);

        emit userDidSomething();

        break;


    case UpdateEmailRequest:
        this->showStatusMessageAndLogIt(tr("E-mail updated: %1")
                                        .arg(jsonData.value("email")
                                                     .toString()));

        this->getUserProfile(m_userId); // TMP, FIXME...

        break;



    ////////////////////////////////////
    case PublishPostRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QString objectId = jsonData.value("object").toMap()
                                       .value("id").toString();
            qDebug() << "Post ID:" << objectId;

            QString objectType = jsonData.value("object").toMap()
                                         .value("objectType").toString();
            if (objectType == "image"
             || objectType == "audio"
             || objectType == "video"
             || objectType == "file")
            {
                QString translatedObjectType = ASObject::getTranslatedType(objectType);
                this->showStatusMessageAndLogIt(tr("%1 published successfully. "
                                                   "Updating post content...",
                                                   "%1 is the type of object: "
                                                   "note, image...")
                                                .arg(translatedObjectType));

                // Update the object with title and description
                // (workaround for the non-title-non-description issue)
                this->updatePost(objectId,
                                 objectType,
                                 m_currentPostDescription,
                                 m_currentPostTitle);
            }
            else
            {
                // Not a a media post, notify "posted OK"
                this->showObjectSnippetAndLogIt(tr("Untitled post %1 "
                                                   "published successfully.",
                                                   "%1 is a piece of the post"),
                                                jsonData,
                                                tr("Post %1 published successfully.",
                                                   "%1 is the title of the post"));
                emit postPublished();
                qDebug() << "Non-media post published correctly";
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;



    case PublishAvatarRequest:
        this->showStatusMessageAndLogIt(tr("Avatar published successfully."));
        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QString imageUrl = jsonData.value("object").toMap().value("image")
                                                       .toMap().value("url").toString();
            qDebug() << "Avatar post ID:" << imageUrl;
            // FIXME: get "pump_io: fullImage: url" too

            if (jsonData["object"].toMap().value("objectType").toString() == "image")
            {
                emit avatarUploaded(imageUrl);
            }
            else
            {
                qDebug() << "Avatar uploaded, but type is not IMAGE!";
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;


    ////////////////////////////////////
    case UpdatePostRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            this->showObjectSnippetAndLogIt(tr("Untitled post %1 "
                                               "updated successfully.",
                                               "%1 is a piece of the post"),
                                            jsonData,
                                            tr("Post %1 updated successfully.",
                                               "%1 is the title of the post"));
        }

        emit postPublished();
        break;

    case UpdateCommentRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            this->showObjectSnippetAndLogIt(tr("Comment %1 updated successfully.",
                                               "%1 is a piece of the comment"),
                                            jsonData);
        }

        emit commentPosted(requestExtraString);
        emit userDidSomething();
        break;




    ///////////////////////////////////// If liking a post was requested
    case LikePostRequest:
        this->showStatusMessageAndLogIt(tr("Message liked or unliked "
                                           "successfully."));
        emit likeSet();
        emit userDidSomething();
        break;


    ///////////////////////////////////// If the likes for a post were requested
    case PostLikesRequest:
        qDebug() << "Likes for a post were requested" << replyUrl;

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";
            emit currentJobChanged(tr("Likes received."));

            jsonDataList = jsonData["items"].toList();
            qDebug() << "Number of items in comments list:" << jsonDataList.size();

            emit likesReceived(jsonDataList, replyUrl);
        }
        else
        {
            qDebug() << "Error parsing received comment JSON data!";
            qDebug() << "Raw data:" << replyData;
        }
        break;



    ///////////////////////////////////// If commenting on a post was requested
    case CommentPostRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            this->showObjectSnippetAndLogIt(tr("Comment %1 posted successfully.",
                                               "%1 is a piece of the comment"),
                                            jsonData);
        }

        emit commentPosted(requestExtraString); // This will be caught by Commenter()
        emit userDidSomething();
        break;



    ///////////////////////////////////// If the comments for a post were requested
    case PostCommentsRequest:
        qDebug() << "Comments for a post were requested" << replyUrl;

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";
            jsonDataList = jsonData["items"].toList();

            int commentCount = jsonDataList.size();
            qDebug() << "Number of items in comments list:" << commentCount;

            if (commentCount == 1)
            {
                emit currentJobChanged(tr("1 comment received."));
            }
            else
            {
                emit currentJobChanged(tr("%1 comments received.")
                                       .arg(commentCount));
            }

            emit commentsReceived(jsonDataList, replyUrl);
        }
        else
        {
            qDebug() << "Error parsing received comment JSON data!";
            qDebug() << "Raw data:" << replyData;
        }
        break;



    case SharePostRequest:
        qDebug() << "Post shared OK";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            // FIXME: this should be done using ASActivity+ASObject
            QVariantMap authorMap = jsonData.value("object").toMap().value("author").toMap();
            QString authorName = authorMap.value("displayName").toString();
            if (authorName.isEmpty())
            {
                authorName = ASPerson::cleanupId(authorMap.value("id").toString());
            }
            this->showStatusMessageAndLogIt(tr("Post by %1 shared successfully.",
                                               "1=author of the post we are sharing")
                                            .arg(authorName));

            emit userDidSomething();
        }

        break;

    case PostSharesRequest:
        // TODO
        break;



    /////////////////////////////////////////////// If a feed was requested
    case MainTimelineRequest:
        // just jump to the next
    case DirectTimelineRequest:
        // just jump to next
    case ActivityTimelineRequest:
        // just... yeah, jump
    case FavoritesTimelineRequest:
        // and jump!
    case MinorFeedMainRequest:
        // jump!
    case MinorFeedDirectRequest:
        // jump, jump, jump!!
    case MinorFeedActivityRequest:
        // everybody jump!
    case UserTimelineRequest:
        prettyLogMessage = tr("Received '%1'.",
                              "%1 is the name of a feed")
                           .arg(this->getFeedNameAndPath(requestType).first());

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            jsonDataList = jsonData["items"].toList();
            qDebug() << "Number of items in this feed block:"
                     << jsonDataList.size();
            if (jsonDataList.size() > 0)
            {
                prettyLogMessage.append(" " + tr("Adding items..."));
            }

            // Emit this before the other signals
            emit currentJobChanged(prettyLogMessage);


            int totalItems = jsonData.value("totalItems").toInt();
            QString previousLink = jsonData.value("links").toMap()
                                           .value("prev").toMap()
                                           .value("href").toString();
            QString nextLink = jsonData.value("links").toMap()
                                       .value("next").toMap()
                                       .value("href").toString();

            if (requestType == MainTimelineRequest)
            {
                qDebug() << "It was the main timeline";
                m_haveMainTL = true;
                emit mainTimelineReceived(jsonDataList, previousLink,
                                          nextLink, totalItems);
            }
            else if (requestType == DirectTimelineRequest)
            {
                qDebug() << "It was the direct messages timeline";
                m_haveDirectTL = true;
                emit directTimelineReceived(jsonDataList, previousLink,
                                            nextLink, totalItems);
            }
            else if (requestType == ActivityTimelineRequest)
            {
                qDebug() << "It was the own activity timeline";
                m_haveActivityTL = true;
                emit activityTimelineReceived(jsonDataList, previousLink,
                                              nextLink, totalItems);
            }
            else if (requestType == FavoritesTimelineRequest)
            {
                qDebug() << "It was the favorites timeline";
                m_haveFavoritesTL = true;
                emit favoritesTimelineReceived(jsonDataList, previousLink,
                                               nextLink, totalItems);
            }
            else if (requestType == UserTimelineRequest)
            {
                QString timelineUrl = jsonData.value("url").toString();
                qDebug() << "It was a user timeline:" << timelineUrl;
                emit userTimelineReceived(jsonDataList, previousLink,
                                          nextLink, totalItems,
                                          timelineUrl);
            }
            else if (requestType == MinorFeedMainRequest)
            {
                qDebug() << "It was the Meanwhile feed";
                m_haveMainMF = true;
                emit minorFeedMainReceived(jsonDataList, previousLink,
                                           nextLink, totalItems);
            }
            else if (requestType == MinorFeedDirectRequest)
            {
                qDebug() << "It was the Mentions feed";
                m_haveDirectMF = true;
                emit minorFeedDirectReceived(jsonDataList, previousLink,
                                             nextLink, totalItems);
            }
            else if (requestType == MinorFeedActivityRequest)
            {
                qDebug() << "It was the Actions feed";
                m_haveActivityMF = true;
                emit minorFeedActivityReceived(jsonDataList, previousLink,
                                               nextLink, totalItems);
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }
        break;



    case DeletePostRequest:
        this->showStatusMessageAndLogIt(tr("Message deleted successfully."));
        emit userDidSomething();

        break;


    case CheckContactRequest:
        m_webfingerCheckTimer->stop(); // No need to check timeout at this point

        qDebug() << "Contact ID seems OK:" << replyUrl;
        emit contactVerified(m_addressPendingToFollow, httpCode,
                             m_webfingerCheckTimedOut, replyServerVersion);

        m_addressPendingToFollow.clear();

        break;


    case FollowContactRequest:
        // Just jump to next
    case UnfollowContactRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            ASPerson *contact = new ASPerson(jsonData.value("object").toMap(),
                                             this);

            if (requestType == FollowContactRequest)
            {
                prettyLogMessage = tr("Following %1 (%2) successfully.",
                                      "%1 is a person's name, %2 is the ID")
                                   .arg(contact->getName())
                                   .arg(contact->getId());

                emit contactFollowed(contact);
                emit followingListChanged();
            }
            else
            {
                prettyLogMessage = tr("Stopped following %1 (%2) successfully.",
                                      "%1 is a person's name, %2 is the ID")
                                   .arg(contact->getName())
                                   .arg(contact->getId());

                emit contactUnfollowed(contact);
                emit followingListChanged();
            }

            this->showStatusMessageAndLogIt(prettyLogMessage);
            emit userDidSomething();
        }

        break;


    case FollowingListRequest:
        // just go to the next
    case FollowersListRequest:
        qDebug() << "A contact list was requested";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QVariant contactsVariant = jsonData.value("items");
            int totalCollectionCount = jsonData.value("totalItems").toInt();
            if (contactsVariant.type() == QVariant::List)
            {
                qDebug() << "Parsed a List, listing contacts...";

                int receivedItemsCount = contactsVariant.toList().size();
                QString batchInfoString;

                if (requestType == FollowingListRequest)
                {
                    m_userFollowingCount = totalCollectionCount;
                    m_totalReceivedFollowing += receivedItemsCount;

                    emit contactListReceived("following",
                                             contactsVariant.toList(),
                                             m_totalReceivedFollowing);

                    batchInfoString = QString(" (%1/%2)")
                                      .arg(m_totalReceivedFollowing)
                                      .arg(m_userFollowingCount);

                    if (m_totalReceivedFollowing >= totalCollectionCount)
                    {
                        m_haveFollowing = true;
                        this->showStatusMessageAndLogIt(tr("List of 'following' "
                                                           "completely received.")
                                                        + batchInfoString);
                        emit followingListChanged();
                    }
                    else
                    {
                        emit currentJobChanged(tr("Partial list of 'following' "
                                                  "received.")
                                               + batchInfoString);
                        qDebug() << "Partial following received:"
                                 << batchInfoString;
                    }
                }
                else // == FollowersListRequest
                {
                    m_userFollowersCount = totalCollectionCount;
                    m_totalReceivedFollowers += receivedItemsCount;

                    emit contactListReceived("followers",
                                             contactsVariant.toList(),
                                             m_totalReceivedFollowers);

                    batchInfoString = QString(" (%1/%2)")
                                      .arg(m_totalReceivedFollowers)
                                      .arg(m_userFollowersCount);

                    if (m_totalReceivedFollowers >= totalCollectionCount)
                    {
                        m_haveFollowers = true;
                        this->showStatusMessageAndLogIt(tr("List of 'followers' "
                                                           "completely received.")
                                                        + batchInfoString);
                    }
                    else
                    {
                        emit currentJobChanged(tr("Partial list of 'followers' "
                                                  "received.")
                                               + batchInfoString);
                        qDebug() << "Partial following received:"
                                 << batchInfoString;
                    }
                }
            }
            else
            {
                qDebug() << "Expected a list of contacts, received something else:";
                qDebug() << jsonData;
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;



    case ListsListRequest:
        qDebug() << "The list of person lists was requested";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QVariant listsVariant = jsonData.value("items");
            if (listsVariant.type() == QVariant::List)
            {
                qDebug() << "Parsed a List, listing lists...";

                m_havePersonLists = true;
                emit currentJobChanged(tr("List of 'lists' received."));
                emit listsListReceived(listsVariant.toList());
            }
            else
            {
                qDebug() << "Expected a list of lists, received something else:";
                qDebug() << jsonData;
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;



    case SiteUserListRequest:
        this->showStatusMessageAndLogIt(tr("List of %1 users received.",
                                           "%1 is a server name")
                                        .arg(m_serverDomain)
                                        + "  ("
                                        + QLocale::system()
                                          .toString(jsonData.value("totalItems")
                                                            .toInt())
                                        + ")");

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";
            emit siteUserListReceived(jsonData.value("items").toList(),
                                      jsonData.value("totalItems").toInt());
            //qDebug() << "\n\n*** Site User List:\n\n" << replyData;
        }

        break;


    ///////////////////////////////////////////////////////////////////// Lists

    // Person list created OK
    case CreatePersonListRequest:
        this->showStatusMessageAndLogIt(tr("Person list '%1' created successfully.")
                                        .arg(jsonData.value("object").toMap()
                                                     .value("displayName").toString()));

        this->getListsList(); // And reload the person lists (FIXME!)
        emit userDidSomething();
        break;


    // Person list deleted OK
    case DeletePersonListRequest:
        this->showStatusMessageAndLogIt(tr("Person list deleted successfully."));

        this->getListsList(); // And reload the person lists (FIXME!)
        emit userDidSomething();
        break;


    case PersonListRequest:
        qDebug() << "A person list was requested";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QVariant personListVariant = jsonData.value("items");
            if (personListVariant.type() == QVariant::List)
            {
                qDebug() << "Parsed a List, listing people in list..."
                         << jsonData.value("displayName").toString();

                emit currentJobChanged(tr("Person list received."));

                // Using list URL as ID, since the ID isn't provided here...
                emit personListReceived(personListVariant.toList(),
                                        jsonData.value("url").toString());
            }
            else
            {
                qDebug() << "Expected a list of people, received something else:";
                qDebug() << jsonData;
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;


    // Person added to list OK
    case AddMemberToListRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            QString personId = ASPerson::cleanupId(jsonData.value("object").toMap()
                                                           .value("id").toString());
            QString personName = jsonData.value("object").toMap()
                                         .value("displayName").toString();
            QString personAvatar = jsonData.value("object").toMap()
                                           .value("image").toMap()
                                           .value("url").toString();

            emit personAddedToList(personId, personName, personAvatar);

            this->showStatusMessageAndLogIt(tr("%1 (%2) added to list successfully.",
                                               "1=contact name, 2=contact ID")
                                            .arg(personName)
                                            .arg(personId));
            emit userDidSomething();
        }
        break;


    // Person removed from list OK
    case RemoveMemberFromListRequest:
        if (jsonParsedOK && jsonData.size() > 0)
        {
            QString personId = ASPerson::cleanupId(jsonData.value("object").toMap()
                                                            .value("id").toString());
            QString personName = jsonData.value("object").toMap()
                                         .value("displayName").toString();

            emit personRemovedFromList(personId);

            this->showStatusMessageAndLogIt(tr("%1 (%2) removed from list successfully.",
                                               "1=contact name, 2=contact ID")
                                            .arg(personName)
                                            .arg(personId));
            emit userDidSomething();
        }
        break;


    //////////////////////////////////////////////////////////////////// Groups
    case CreateGroupRequest:
        this->showStatusMessageAndLogIt(tr("Group %1 created successfully.")
                                        .arg(jsonData.value("object").toMap()
                                                     .value("displayName").toString()));
        emit userDidSomething();

        qDebug() << "Group created; ID:" << jsonData.value("object").toMap()
                                                    .value("id").toString();
        break;


    case JoinGroupRequest:
        this->showStatusMessageAndLogIt(tr("Group %1 joined successfully.")
                                        .arg(jsonData.value("object").toMap()
                                                     .value("displayName").toString()));
        emit userDidSomething();

        qDebug() << "Group joined; ==========================";
        qDebug() << "Keys: " << jsonData.keys();
        qDebug() << jsonData.value("object").toString();

        break;


    case LeaveGroupRequest:
        this->showStatusMessageAndLogIt(tr("Left the %1 group successfully.")
                                        .arg(jsonData.value("object").toMap()
                                                     .value("displayName").toString()));
        emit userDidSomething();

        break;



    case AvatarRequest:
        qDebug() << "Received AVATAR data, from " << replyUrl;
        emit avatarPictureReceived(replyData, replyUrl);

        break;


    case ImageRequest:
        qDebug() << "Received IMAGE data, from " << replyUrl;
        if (replyUrl == requestExtraString || requestExtraString.isEmpty())
        {
            qDebug() << "-- Directly";
            emit imageReceived(replyData, replyUrl);
        }
        else
        {
            qDebug() << "-- Via redirect!\n"
                     << "(Originally from: " << requestExtraString << ")";
            emit imageReceived(replyData, requestExtraString);
        }

        break;


    case MediaRequest:
        qDebug() << "Received MEDIA data, from " << replyUrl;
        emit downloadCompleted(replyUrl);

        break;


    //////////////////////////////////////// If uploading a file was requested
    case UploadAvatarRequest:
        // just jump to next
    case UploadMediaForPostRequest:
        // just jump
    case UploadFileRequest:
        qDebug() << "Uploading a file was requested";

        if (jsonParsedOK && jsonData.size() > 0)
        {
            qDebug() << "JSON parsed OK";

            QString uploadedFileId = jsonData["id"].toString();
            qDebug() << "Uploaded file ID:" << uploadedFileId;


            QString objectType = jsonData["objectType"].toString();
            if (objectType == "image"
             || objectType == "audio"
             || objectType == "video"
             || objectType == "file")
            {
                if (requestType == UploadMediaForPostRequest)
                {
                    prettyLogMessage = tr("File uploaded successfully. "
                                          "Posting message...");
                    emit currentJobChanged(prettyLogMessage);
                    emit logMessage(prettyLogMessage, replyUrl);

                    this->postMediaStepTwo(uploadedFileId);
                }
                else if (requestType == UploadAvatarRequest)
                {
                    this->showStatusMessageAndLogIt(tr("Avatar uploaded."));
                    this->postAvatarStepTwo(uploadedFileId);
                }
            }
        }
        else
        {
            qDebug() << "Error parsing received JSON data!";
            qDebug() << "Raw data:" << replyData; // JSON directly
        }

        break;


    }
    // end switch (requestType)

    qDebug() << "requestFinished() ended; " << replyUrl;
}



/*
 * Handle SSL errors
 *
 * Default is blocking connections with SSL problems, unless the request
 * if for an image on a remote host and the corresponding option was set
 * in the Post settings, or --ignoresslerrors was used
 *
 */
void PumpController::sslErrorsHandler(QNetworkReply *reply,
                                      QList<QSslError> errorList)
{
    qDebug() << "\n==== SSL errors!! ====";
    qDebug() << "At:" << reply->url().toString();
    qDebug() << "Error list:" << errorList << "\n\n";

    QString errorsString;
    foreach (const QSslError sslError, errorList)
    {
        errorsString.append(sslError.errorString() + "; ");
    }
    errorsString.remove(-2, 2); // remove "; " at the end

    this->showStatusMessageAndLogIt(tr("SSL errors in connection to %1!")
                                    .arg(reply->url().host())
                                    + QString(" (%1)").arg(errorsString));

    bool allowLoadingImage = false;
    if (m_ignoreSslInImages)
    {
        int requestType = reply->request().attribute(QNetworkRequest::User).toInt();
        if (requestType == ImageRequest
         && reply->url().host() != m_serverDomain)
        {
            allowLoadingImage = true;
            this->showStatusMessageAndLogIt(tr("Loading external image from "
                                               "%1 regardless of SSL errors, "
                                               "as configured...",
                                               "%1 is a hostname")
                                            .arg(reply->url().host()),
                                            reply->url().toString());
        }
    }

    // Ignore SSL errors and continue, if configured to do so
    if (m_ignoreSslErrors || allowLoadingImage)
    {
        qDebug() << "Ignoring these errors...";
        reply->ignoreSslErrors();
    }
}




void PumpController::getToken()
{
    // If we do not have client_id or client_secret, do dynamic client registration
    if (m_clientId.isEmpty() || m_clientSecret.isEmpty())
    {
        qDebug() << "PumpController::getToken()";
        qDebug() << "We do not have client_id/client_secret yet; "
                    "doing Dynamic Client Registration";

        this->showStatusMessageAndLogIt(tr("The application is not registered with "
                                           "your server yet. Registering..."));

        // POST to https://yourserver.example/api/client/register
        QNetworkRequest postRequest(QUrl(m_serverScheme + m_serverDomain
                                         + "/api/client/register"));
        postRequest.setHeader(QNetworkRequest::ContentTypeHeader,
                              "application/json");
        postRequest.setRawHeader(QByteArrayLiteral("User-Agent"),
                                 m_userAgentString);
        postRequest.setAttribute(QNetworkRequest::User,
                                 QVariant(ClientRegistrationRequest));

        QByteArray data("{"
                        " \"type\": \"client_associate\",  "
                        " \"application_type\": \"native\", "
                        " \"application_name\": \"Dianara\", "
                        " \"logo_uri\": \"http://dianara.nongnu.org/dianara-logo.png\", "
                        " \"client_uri\": \"https://jancoding.wordpress.com/dianara\" "
                        "}");

        qDebug() << "About to POST:" << data;


        // upon receiving data (id+secret), will execute getToken() again
        m_nam.post(postRequest, data);
    }
    else
    {
        qDebug() << "Using saved client_id and client_secret:"
                 << m_clientId << m_clientSecret;

        // OAuth stuff.....
        // 1. obtaining an unauthorized Request Token from the Service Provider,
        // 2. asking the User to authorize the Request Token,
        // 3. exchanging the Request Token for the Access Token


        this->showStatusMessageAndLogIt(tr("Getting OAuth token..."));

        qDebug() << "Doing OAuth token stuff...";
        qDebug() << "NOTE: if you see a crash here, you need QCA and its "
                    "openSSL plugin:";
        qDebug() << ">>> qca2-plugin-openssl, libqca2-plugin-ossl, or similar";
        qDebug() << "If you compiled Dianara from source, check the INSTALL "
                    "file carefully";

        QStringList QCAsupportedFeatures = QCA::supportedFeatures();
        qDebug() << "QCA Supported Features:" << QCAsupportedFeatures;

        if (QCAsupportedFeatures.contains("hmac(sha1)"))
        {
            qDebug() << "HMAC-SHA1 support is OK";
        }
        else
        {
            qDebug() << "Warning, HMAC-SHA1 doesn't seem to be supported!";

            // Notify the user about missing plugin
            emit authorizationFailed(tr("OAuth support error"),
                                     tr("Your installation of QOAuth, a library "
                                        "used by Dianara, doesn't seem to have "
                                        "HMAC-SHA1 support.")
                                     + "\n"
                                     + tr("You probably need to install the OpenSSL "
                                          "plugin for QCA: %1, %2 or similar.")
                                       .arg("qca2-plugin-openssl")
                                       .arg("libqca2-plugin-ossl"));
        }

        m_qoauth->setConsumerKey(m_clientId.toLocal8Bit());
        m_qoauth->setConsumerSecret(m_clientSecret.toLocal8Bit());


        QString requestTokenUrl = m_serverScheme + m_serverDomain
                                  + "/oauth/request_token";
        qDebug() << "GET: " << requestTokenUrl
                 << "with" << m_qoauth->consumerKey()
                           << m_qoauth->consumerSecret();

        QOAuth::ParamMap oAuthParams;
        oAuthParams.insert("oauth_callback", "oob");

        QOAuth::ParamMap reply = m_qoauth->requestToken(requestTokenUrl,
                                                        QOAuth::GET,
                                                        QOAuth::HMAC_SHA1,
                                                        oAuthParams);

        if (m_qoauth->error() == QOAuth::NoError)
        {
            qDebug() << "requestToken OK:" << reply.keys();

            m_token = reply.value(QOAuth::tokenParameterName());
            m_tokenSecret = reply.value(QOAuth::tokenSecretParameterName());

            qDebug() << "Token:" << m_token;
            qDebug() << "Token Secret:" << m_tokenSecret.left(5)
                     << "********** (hidden)";

            QUrl oAuthAuthorizeUrl(m_serverScheme + m_serverDomain
                                   + "/oauth/authorize");
            QUrlQuery query;
            query.addQueryItem("oauth_token", m_token);
            oAuthAuthorizeUrl.setQuery(query);

            bool oauthUrlOpenedOk = QDesktopServices::openUrl(oAuthAuthorizeUrl);

            // Send also a signal, so AccountDialog can show the URL in
            // a label, in case the browser didn't launch
            emit openingAuthorizeUrl(oAuthAuthorizeUrl, oauthUrlOpenedOk);

            // Now, user should enter VERIFIER in AccountDialog to authorize the program
        }
        else
        {
            qDebug() << "QOAuth error" << m_qoauth->error() << "!";
            qDebug() << reply.keys();

            emit authorizationFailed(tr("Authorization error"),
                                     tr("There was an OAuth error while trying "
                                        "to get the authorization token.")
                                     + "\n\n"
                                     + tr("QOAuth error %1").arg(m_qoauth->error()));
        }
    }
}



void PumpController::authorizeApplication(QString verifierCode)
{
    qDebug() << "Verifier code entered by user:" << verifierCode;

    QOAuth::ParamMap moreParams;
    moreParams.insert("oauth_verifier", verifierCode.toUtf8()); // verifier as QByteArray

    QString requestAuthorizationUrl = m_serverScheme + m_serverDomain
                                      + "/oauth/access_token";

    QOAuth::ParamMap reply = m_qoauth->accessToken(requestAuthorizationUrl,
                                                   QOAuth::GET,
                                                   m_token,
                                                   m_tokenSecret,
                                                   QOAuth::HMAC_SHA1,
                                                   moreParams);

    if (m_qoauth->error() == QOAuth::NoError) // Woooohooo!!
    {
        qDebug() << "Got authorized token; Dianara is authorized to access the account";
        m_token = reply.value(QOAuth::tokenParameterName());
        m_tokenSecret = reply.value(QOAuth::tokenSecretParameterName());

        m_applicationAuthorized = true;

        this->showStatusMessageAndLogIt(tr("Application authorized successfully."));

        QSettings settings;
        settings.setValue("isApplicationAuthorized", m_applicationAuthorized);
        settings.setValue("token",       m_token);
        settings.setValue("tokenSecret", m_tokenSecret);
        settings.sync();

        qDebug() << "Token:" << m_token;
        qDebug() << "TokenSecret:" << m_tokenSecret.left(5) << "***********";

        emit authorizationStatusChanged(m_applicationAuthorized);
        emit authorizationSucceeded();
    }
    else
    {
        QString errorMessage = tr("OAuth error while authorizing application.")
                               + QString(" (%1)").arg(m_qoauth->error());
        this->showStatusMessageAndLogIt(errorMessage);

        emit authorizationFailed(tr("Authorization error"), errorMessage);

        qDebug() << "OAuth error while authorizing application" << m_qoauth->error();
    }
}




/*
 * Called by a QTimer;
 * Get initial data (profile, contacts, timelines), one step at a time
 *
 */
void PumpController::getInitialData()
{
    qDebug() << "PumpController::getInitialData() step" << m_initialDataStep;

    m_initialDataTimer->setInterval(3000);  // Every 3 sec

    // If we're still waiting for a proxy password, don't do anything
    if (this->needsProxyPassword())
    {
        emit currentJobChanged(tr("Waiting for proxy password..."));
        return;
    }

    /*
     * FIXME: this needs to be way more elaborate.
     *
     * Ensure certain stuff has been received before continuing.
     *
     * For instance, getting the "following" list is needed before
     * being able to post to specific contacts, and to know the state
     * of following/not Following the author of a post in a timeline
     *
     */

    switch (m_initialDataStep)
    {
    case 0:
        if (!m_haveProfile)
        {
            this->getUserProfile(m_userId);
        }
        break;

    case 1:
        // Ensure we have the profile already, before getting contact lists...
        if (!m_haveProfile)
        {
            m_initialDataStep = -1; // Restart initialization!
            this->showStatusMessageAndLogIt(tr("Still waiting for profile. "
                                               "Trying again..."));
        }
        break;

    case 2:
        if (!m_haveFollowing)
        {
            if (m_haveProfile) // Require profile first, since it provides followingCount
            {
                this->getContactList("following");
            }
            else
            {
                --m_initialDataStep; // go back 1 to retry
            }
        }
        break;

    case 3:
        if (!m_haveFollowers)
        {
            if (m_haveProfile) // Required for followersCount
            {
                this->getContactList("followers");
            }
            else
            {
                --m_initialDataStep; // retry
            }
        }
        break;

    case 4:
        if (!m_haveMainTL)
        {
            this->getFeed(PumpController::MainTimelineRequest,
                          m_postsPerPageMain);
        }
        break;

    case 5:
        if (!m_haveMainMF)
        {
            this->getFeed(PumpController::MinorFeedMainRequest, 50);
        }
        break;

    case 6:
        if (!m_havePersonLists)
        {
            this->getListsList();
        }
        break;

    case 7:
        if (!m_haveDirectTL)
        {
            this->getFeed(PumpController::DirectTimelineRequest,
                          m_postsPerPageOther);
        }
        break;

    case 8:
        if (!m_haveDirectMF)
        {
            this->getFeed(PumpController::MinorFeedDirectRequest, 20);
        }
        break;

    case 9:
        if (!m_haveActivityTL)
        {
            this->getFeed(PumpController::ActivityTimelineRequest,
                          m_postsPerPageOther);
        }
        break;

    case 10:
        if (!m_haveActivityMF)
        {
            this->getFeed(PumpController::MinorFeedActivityRequest, 20);
        }
        break;

    case 11:
        if (!m_haveFavoritesTL)
        {
            this->getFeed(PumpController::FavoritesTimelineRequest,
                          m_postsPerPageOther);
        }
        break;

    case 12:
        // If some data is still missing, go back to the beginning of the cycle
        if (!m_haveProfile
         || !m_haveFollowing  || !m_haveFollowers || !m_havePersonLists
         || !m_haveMainTL     || !m_haveMainMF
         || !m_haveDirectTL   || !m_haveDirectMF
         || !m_haveActivityTL || !m_haveActivityMF
         || !m_haveFavoritesTL)
        {
            // Unless we've already tried several times, like people with broken
            // contact lists, for instance, which will never be received
            if (m_initialDataAttempts < 5)
            {
                m_initialDataStep = -1;
                ++m_initialDataAttempts;

                QString attempts;
                if (m_initialDataAttempts > 1)
                {
                    attempts = tr("%1 attempts").arg(m_initialDataAttempts);
                }
                else
                {
                    attempts = tr("1 attempt");
                }

                this->showStatusMessageAndLogIt(tr("Some initial data was not "
                                                   "received. Restarting "
                                                   "initialization...")
                                                + " (" + attempts + ")");
            }
            else
            {
                this->showStatusMessageAndLogIt(tr("Some initial data was not "
                                                   "received after several "
                                                   "attempts. Something might "
                                                   "be wrong with your server. "
                                                   "You might still be able to "
                                                   "use the service normally."));
            }
        }
        else
        {
            this->showStatusMessageAndLogIt(tr("All initial data received. "
                                               "Initialization complete."));
            // If there are no problems, this will just wait one interval,
            // so it takes longer for the final (default) step to arrive
        }
        break;


    default:
        m_initialDataTimer->stop();

        if (m_initialDataAttempts < 5) // with 5+ attempts, leave previous message
        {
            this->showStatusMessageAndLogIt(tr("Ready."));
        }

        emit initializationCompleted();

        qDebug() << "--------------------------------------";
        qDebug() << "-- All initial data loaded -----------";
        qDebug() << "--------------------------------------";
    }

    emit initializationStepChanged(m_initialDataStep);

    ++m_initialDataStep;
}





/*
 * Send a NOTE to the server
 *
 */
void PumpController::postNote(QVariantMap audienceMap,
                              QString postText, QString postTitle)
{
    qDebug() << "PumpController::postNote()";

    QNetworkRequest postRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       PublishPostRequest);

    qDebug() << "Should be posting to:" << audienceMap;



    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "note");
    if (!postTitle.isEmpty())
    {
        jsonVariantObject.insert("displayName", postTitle);
    }
    //jsonVariantObject.insert("summary", "summary test");
    jsonVariantObject.insert("content", postText);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "post");
    jsonVariant.insert("object", jsonVariantObject);

    jsonVariant.insert("to", audienceMap.value("to").toList());
    jsonVariant.insert("cc", audienceMap.value("cc").toList());


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;

    m_nam.post(postRequest, data);
}


/*
 * Post media-type of object (image, audio, video, file)
 *
 * First, upload the file.
 * Then we get its ID in a signal, and create the post itself
 *
 */
QNetworkReply *PumpController::postMedia(QVariantMap audienceMap,
                                         QString postText, QString postTitle,
                                         QString mediaFilename, QString mediaType,
                                         QString mimeContentType)
{
    qDebug() << "PumpController::postMedia()" << mediaType;
    qDebug() << "Uploading" << mediaFilename << "with title:" << postTitle;

    // Store postTitle, postText, audienceMap, and postType, then upload
    m_currentPostTitle = postTitle;
    m_currentPostDescription = postText;
    m_currentPostAudience = audienceMap;
    m_currentPostType = mediaType;

    QNetworkReply *networkReply = this->uploadFile(mediaFilename,
                                                   mimeContentType,
                                                   UploadMediaForPostRequest);
    return networkReply;
}


/*
 * Post Media, step 2: after getting the ID in the file upload request,
 * create the post itself
 *
 */
void PumpController::postMediaStepTwo(QString id)
{
    qDebug() << "PumpController::postMediaStepTwo()"
             <<  m_currentPostType << "ID:" << id;

    QNetworkRequest postRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       PublishPostRequest);


    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", m_currentPostType);
    jsonVariantObject.insert("id", id);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "post");
    jsonVariant.insert("object", jsonVariantObject);
    jsonVariant.insert("to", m_currentPostAudience.value("to").toList());
    jsonVariant.insert("cc", m_currentPostAudience.value("cc").toList());


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;
    m_nam.post(postRequest, data);
}



/*
 * Second step for avatar upload.
 *
 * Post the image to Public
 *
 */
void PumpController::postAvatarStepTwo(QString id)
{
    qDebug() << "PumpController::postAvatarStepTwo() image ID:" << id;

    QNetworkRequest postRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       PublishAvatarRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "image");
    jsonVariantObject.insert("id", id);

    // audience, Cc: Public
    QVariantMap jsonVariantPublic;
    jsonVariantPublic.insert("objectType", "collection");
    jsonVariantPublic.insert("id", "http://activityschema.org/collection/public");

    QVariantList jsonVariantAudience;
    jsonVariantAudience.append(jsonVariantPublic);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "post");
    jsonVariant.insert("object", jsonVariantObject);
    jsonVariant.insert("cc", jsonVariantAudience); // Cc: Public


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;
    m_nam.post(postRequest, data);
}



/*
 * Update post contents (the object)
 *
 */
void PumpController::updatePost(QString id,
                                QString type,
                                QString content,
                                QString title)
{
    qDebug() << "PumpController::updatePost(), post ID:" << id;

    QNetworkRequest postRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       UpdatePostRequest);
    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("id", id);
    jsonVariantObject.insert("objectType", type);

    if (!title.isEmpty()) // FIXME: needs a way to remove titles...
    {
        jsonVariantObject.insert("displayName", title);
    }

    jsonVariantObject.insert("content", content);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb",   "update");
    jsonVariant.insert("object", jsonVariantObject);

    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;
    m_nam.post(postRequest, data);
}



/*
 * Like (favorite) a post, by its ID (URL)
 *
 */
void PumpController::likePost(QString postId, QString postType,
                              QString authorId, bool like)
{
    qDebug() << "PumpController::likePost() liking post" << postId;

    QNetworkRequest likeRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       LikePostRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", postType);
    jsonVariantObject.insert("id", postId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", like ? "favorite":"unfavorite"); // like or unlike
    jsonVariant.insert("object", jsonVariantObject);


    if (m_silentLikes)
    {
        QVariantMap authorVariant;
        authorVariant.insert("objectType", "person");
        authorVariant.insert("id", "acct:" + authorId);

        QVariantList jsonAudienceTo;
        jsonAudienceTo.append(authorVariant);   // To: only the author
        jsonVariant.insert("to", jsonAudienceTo);
    }



    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(likeRequest, data);
}



void PumpController::addComment(QString comment,
                                QString postId,
                                QString postType)
{
    qDebug() << "PumpController::addComment() sending comment to this post:"
             << postId;

    QNetworkRequest commentRequest = this->prepareRequest(m_apiFeedUrl,
                                                          QOAuth::POST,
                                                          CommentPostRequest);

    commentRequest.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User + 1),
                                QVariant(postId));

    QVariantMap jsonVariantInReplyTo;
    jsonVariantInReplyTo.insert("id", postId);
    jsonVariantInReplyTo.insert("objectType", postType);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "comment");
    jsonVariantObject.insert("content", comment);
    jsonVariantObject.insert("inReplyTo", jsonVariantInReplyTo);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "post");
    jsonVariant.insert("object", jsonVariantObject);

    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(commentRequest, data);
}


/*
 * Update comment contents (object)
 *
 * FIXME: This should be merged with :updatePost()
 *
 */
void PumpController::updateComment(QString id, QString content,
                                   QString inReplyToId)
{
    qDebug() << "PumpController::updateComment(), comment ID:" << id;

    QNetworkRequest postRequest = this->prepareRequest(m_apiFeedUrl,
                                                       QOAuth::POST,
                                                       UpdateCommentRequest);
    postRequest.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User + 1),
                             QVariant(inReplyToId));

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("id", id);
    jsonVariantObject.insert("objectType", "comment");
    jsonVariantObject.insert("content", content);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb",   "update");
    jsonVariant.insert("object", jsonVariantObject);

    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "About to POST:" << data;
    m_nam.post(postRequest, data);
}




void PumpController::sharePost(QString postId, QString postType)
{
    qDebug() << "PumpController::sharePost() sharing post" << postId;

    QNetworkRequest shareRequest = this->prepareRequest(m_apiFeedUrl,
                                                        QOAuth::POST,
                                                        SharePostRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", postType);
    jsonVariantObject.insert("id", postId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "share");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(shareRequest, data);
}


void PumpController::unsharePost(QString postId, QString postType)
{
    qDebug() << "PumpController::unsharePost() unsharing post" << postId;

    QNetworkRequest unshareRequest = this->prepareRequest(m_apiFeedUrl,
                                                          QOAuth::POST,
                                                          UnsharePostRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", postType);
    jsonVariantObject.insert("id", postId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "unshare");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(unshareRequest, data);
}



void PumpController::deletePost(QString postId, QString postType)
{
    qDebug() << "PumpController::deletePost() deleting post" << postId;

    QNetworkRequest deleteRequest = this->prepareRequest(m_apiFeedUrl,
                                                         QOAuth::POST,
                                                         DeletePostRequest);
    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", postType);
    jsonVariantObject.insert("id", postId);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "delete");
    jsonVariant.insert("object", jsonVariantObject);


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(deleteRequest, data);
}



/*
 * Check if a user ID exists in the corresponding server,
 * and if it is a Pump server.
 *
 * Used before actually following a contact.
 *
 */
void PumpController::followContact(QString address)
{
    // Don't do anything if another check+follow is already in progress
    if (!m_addressPendingToFollow.isEmpty())
    {
        // FIXME -- Show more clearly, a dialog box or something
        this->showStatusMessageAndLogIt(tr("Can't follow %1 at this time.",
                                           "%1 is a user ID")
                                        .arg("'" + address + "'")
                                        + " "
                                        + tr("Trying to follow %1.",
                                             "%1 is a user ID")
                                          .arg("'" + m_addressPendingToFollow) + "'");

        emit cannotFollowNow(address);

        return;
    }

    const QString host = address.split('@').last();

    QString url = "https://" // TMP/FIXME: should retry with HTTP if it fails
                  + host
                  + "/.well-known/webfinger?resource="
                  + address;

    QNetworkRequest checkRequest = this->prepareRequest(url,
                                                        QOAuth::GET,
                                                        CheckContactRequest);

    emit currentJobChanged(tr("Checking address %1 before "
                              "following...").arg(address));

    m_addressPendingToFollow = address;

    m_webfingerCheckTimer->start(60000); // Take care of possible timeouts (1 min)
    m_webfingerCheckTimedOut = false;

    qDebug() << "About to verify webfinger ID:" << url;    
    m_webfingerCheckReply = m_nam.get(checkRequest);
}


/*
 * Add a contact to the /following list with their webfinger address
 *
 * Info for the newly added contact will come in the reply
 *
 */
void PumpController::followVerifiedContact(QString address)
{
    qDebug() << "PumpController::followVerifiedContact()" << address;

    QNetworkRequest followRequest = this->prepareRequest(m_apiFeedUrl,
                                                         QOAuth::POST,
                                                         FollowContactRequest);

    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "person");
    jsonVariantObject.insert("id", "acct:" + address);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "follow");
    jsonVariant.insert("object", jsonVariantObject);
    if (m_silentFollows) // In 'private' mode, address to the same as the object
    {
        QVariantList jsonAudienceTo;
        jsonAudienceTo.append(jsonVariantObject); // To: only the specific user
        jsonVariant.insert("to", jsonAudienceTo);
    }

    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(followRequest, data);
}



/*
 * Remove a contact from the /following list with their webfinger address
 *
 */
void PumpController::unfollowContact(QString address)
{
    qDebug() << "PumpController::unfollowContact()" << address;

    QNetworkRequest unfollowRequest = this->prepareRequest(m_apiFeedUrl,
                                                           QOAuth::POST,
                                                           UnfollowContactRequest);
    QVariantMap jsonVariantObject;
    jsonVariantObject.insert("objectType", "person");
    jsonVariantObject.insert("id", "acct:" + address);

    QVariantMap jsonVariant;
    jsonVariant.insert("verb", "stop-following");
    jsonVariant.insert("object", jsonVariantObject);
    if (m_silentFollows)
    {
        QVariantList jsonAudienceTo;
        jsonAudienceTo.append(jsonVariantObject);
        jsonVariant.insert("to", jsonAudienceTo);
    }


    QByteArray data = this->prepareJSON(jsonVariant);
    qDebug() << "about to POST:" << data;
    m_nam.post(unfollowRequest, data);
}


/*
 * If the QTimer times out, abort the connection manually,
 * since Qt doesn't handle this
 *
 */
void PumpController::onValidationTimeout()
{
    qDebug() << "Aborting user ID check due to timeout!";
    m_webfingerCheckReply->abort();
    m_webfingerCheckReply->deleteLater();

    m_webfingerCheckTimedOut = true;
}
