Work in Progress. This isn’t official and finished tutorial yet

Writing upload/update plugins with Webupload Framework

Accounts & SSO

Upload/update plugins work best if you add service extension to Accounts & SSO framework. This framework offers the user a way to manage accounts (create/remove) and it offers single sign-on service to applications. Accounts & SSO documentation is here.

Most important part of things implemented to Accounts & SSO side is the service definition file. We will not cover Accounts related parts of it, but simply check the extension part our framework uses (content inside webService tag).

Here are the locations where the different files should be installed on device:
.service file: /usr/share/accounts/services/
.provider file: /usr/share/accounts/providers/
SSO plugin: /usr/lib/signon/
webupload-engine plugin: /usr/lib/webupload/plugins/

Note that the value of uploadPlugin attribute in the .service file must match the name of the webupload-engine plugin binary.

Example of dropbox-share-example.service file

<?xml version="1.0" encoding="UTF-8" ?>
<service id="dropbox-share-example">  
  <type>sharing</type>  
  <provider>dropbox</provider>  
  <name>qtn_acc_sel_pro_ser_sharing</name>  
  <icon>my-id-for-dropbox-icon</icon>

  <template>  
    <setting name="enabled" type="b">true</setting>  
  </template>

  <!-- Extension part needed for upload services  
       mainly defines the upload/update plugin name. -->  
  <webService version="0.1" uploadPlugin="dropbox">  
    <presentation>  
      <name>Dropbox</name> <!-- name used in Share UI -->  
      <!-- allows title to have multiple lines -->  
      <title multiline="1" order="1">  
      </title>  
      <!-- asking framework to not to show the description field -->  
      <description hide="1" />  
    </presentation>  
    <postOptions>  
      <!-- In Dropbox you can select the destination folder for the file.  
             Asking framework to show it in the Share UI -->  
      <option id="folder" type="changeable" default="root_" order="3">  
        <caption>Folder</caption>  
        <value id="root_">Root</value>  
        <update>  
          <command>Update</command>  
        </update>  
        <add>  
          <command>Create new folder</command>  
          <caption>Title</caption>  
          <tooltip>Add title</tooltip>  
          <!-- limit the length of folder name to 100 chars -->  
          <input maxLength="100"/  
          <button>Create</button>  
        </add>  
      </option>  
    </postOptions>  
    <media>  
      <formats>  
        <!-- As an example, this will ask framwork to present dropbox  
               if shared content in a jpeg image or any video. Or if each  
               shared content is in one of these groups. -->  
        <mime>image/jpeg</mime>  
        <mime>video/*</mime>  
      </formats>  
    </media>  
  </webService>

</service>

Example of a provider file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE provider>  
<provider version="1.0" id="dropbox">  
    <name>qtn_drop_title</name>  
    <translations>dropbox</translations>  
    <description>qtn_drop_setup_desc</description>  
    <short-description>qtn_drop_desc</short-description>  
    <icon>icon-m-service-dropbox</icon>  
    <sign-up-link>http://m.dropbox.com/register/</sign-up-link>  
    <account-setup>  
        <register type="formular"/>  
        <setting name="remember" type="boolean"/>  
        <authsession>  
          <validate method="dropbox" mechanism="Dropbox"/>  
          <method name="dropbox">  
              <mechanism name="Dropbox"/>  
          </method>  
        </authsession>  
    </account-setup>  
</provider>

Example header of SSO plugin class

#ifndef _DROPBOX_SSO_PLUGIN_
#define _DROPBOX_SSO_PLUGIN_  

#include <SignOn/authpluginif.h>  
#include <QString>  
#include <QStringList>  
#include <QVariant>  
#include "dropboxrequest.h"  
#include "logger.h"  

// Path to make sure we compile with new data class  
#include "../inc/dropboxsessiondata.h"  

// SSO plugin functionality to Dropbox  
class DropboxPlugin : public AuthPluginInterface {  

    Q_OBJECT  
    Q_INTERFACES(AuthPluginInterface);      

    public:  
        // Create new instance of plugin  
        DropboxPlugin (QObject * parent = 0);  

        virtual ~DropboxPlugin();  

    public Q_SLOTS:  

        QString type() const;  
        QStringList mechanisms() const;  

        void cancel ();  

        void process (const SignOn::SessionData & inData,  
        const QString & mechanism = 0);  

        void tokenRequestDone (int error);  

    private:  

        // Internal function for setting mechanism  
        bool setMechanism (const QString &mechanism);  

        // Internal function for mechanism to mode conversion  
        static DropboxRequest::Mode mechanismToMode (  
             const QString & mechanism);  

        // Internal function for mode to mechanism conversion  
        QString modeToMechanism (DropboxRequest::Mode mode) const;  

        // Internal function for marking request as current  
        void setCurrentRequest (DropboxRequest * request);  

        // Internal function for reading values from cache, will emit singal  
        bool readCache (const QString & developerKey,  
                        const QString & secretKey,  
                        const QString & eMail);  

        // Internal function for writing return values to cache  
        void writeCache (DropboxSessionData & data);  

        // Internal function for generating unique key for storing token  
        static QString generateTokenKey (const QString & developerKey,  
                                         const QString & secretKey,  
                                         const QString & eMail);  

        // Internal function for reading token from credentials storage  
        // Will check if caller is trusted before allowing access  
        bool readFromCredentials (DropboxSessionData & input);  

        // Internal function for writing token to credentials storage  
        // Will check if caller is trusted before allowing access  
        bool writeToCredentials (DropboxSessionData & input);  

        DropboxRequest::Mode m_curMode;  
        DropboxRequest * m_currentRequest;  
        DropboxSessionData m_inputData;  

        QString m_developerKey;  
        QString m_secretKey;  
        bool m_trustedToken;  

        Logger *m_logger;  
};  

#endif //#ifndef _DROPBOX_SSO_PLUGIN_

Example source file of SSO plugin

#include "dropboxssoplugin.h"
#include "dropboxcommon.h"  
#include "dropboxrequest.h"  
#include "dropboxtokenrequest.h"  
#include <QSettings>  
#include <QCryptographicHash>  

DropboxPlugin::DropboxPlugin (QObject * parent) :  
    AuthPluginInterface (parent),  
    m_curMode (DropboxRequest::DROPBOX_MODE), m_currentRequest (0),  
    // API Developer and secret keys.  
    // Remember to update the checksum below as well.  
    m_developerKey ("INSERT_DEVELOPER_KEY"),  
    m_secretKey ("INSERT_SECRET_KEY"),  
    m_trustedToken (false), m_logger (0) {  
}  

DropboxPlugin::~DropboxPlugin () {  

    if (m_currentRequest != 0) {  
        delete m_currentRequest;  
    }  

    if (m_logger != 0) {  
        delete m_logger;  
        m_logger = 0;  
    }  
}  

inline bool isTrustedCredentials (const QString & a, const QString b) {  

    QCryptographicHash hasher (QCryptographicHash::Sha1);  
    hasher.addData (a.toAscii());  
    hasher.addData (b.toAscii());  

    //sha1 sum of keys written togetter (developer key first)  
    //remember to change this when keys change (or read it from key provider)  
    QByteArray result ("INSERT_DEVELOPER+SECRET_KEY_SHA1_SUM");  

    bool isTrusted = (hasher.result().toHex() == result);  

    if (isTrusted == false) {      
        qDebug() << hasher.result ().toHex()  
             << "(developer+secret key:" << a+b << ")"  
             << "not matching with" << result;  
    }  

    return isTrusted;  
}  

QString DropboxPlugin::type() const {  

    return "dropbox";  
}  

QStringList DropboxPlugin::mechanisms() const {  

    QStringList list;  

    for (int i = DropboxRequest::INVALID_MODE + 1;  
        i < DropboxRequest::LAST_MODE; ++i) {  

        list << modeToMechanism ((DropboxRequest::Mode)i);  
    }  

    return list;  
}  

bool DropboxPlugin::setMechanism (const QString &mechanism) {  

    DropboxRequest::Mode newMode = mechanismToMode (mechanism);  
    if (newMode > DropboxRequest::INVALID_MODE &&  
        newMode < DropboxRequest::LAST_MODE) {  

        DBG_STREAM << "Set mechanism to" << mechanism;  
        m_curMode = newMode;  
        return true;  
    } else {  
        WARN_STREAM << "Can't set mechanism to" << mechanism;  
        return false;  
    }  
}  

void DropboxPlugin::process (const SignOn::SessionData & inData,  
    const QString & mechanism) {   

    if (m_logger == 0) {  
        m_logger = new Logger ("dropbox-sso");  
    }  

    // Remember input values  
    m_inputData = inData;  
    m_inputData.setEMail (inData.UserName());  
    m_inputData.setPassword (inData.Secret());  

    if (!setMechanism (mechanism)) {  
        DBG_STREAM << "Mechanism" << mechanism << "is not valid";  
        Q_EMIT (  
            error (SignOn::Error (SignOn::Error::MechanismNotAvailable)));  
        return;  
    }  

    bool refresh = m_inputData.RefreshToken();  
    QString eMail = m_inputData.EMail();  
    QString password = m_inputData.Password();  
    m_trustedToken = isTrustedCredentials (m_developerKey, m_secretKey);  

    DBG_STREAM << "Checking credentials";  

    if(refresh == false) {  
        if (readFromCredentials (m_inputData)) {  
            return;  
        }  
    }  

    // Try to get token from cache if allowed  
    if (refresh == false) {  
        if (readCache (m_developerKey, m_secretKey, eMail)) {  
            DBG_STREAM << "readCache == true";  
            return;  
        }  
    } else {  
        DBG_STREAM << "Token refresh request received. Cache ignored";  
    }  

    // Check that we have all data needed  
    if (m_developerKey.isEmpty() || m_secretKey.isEmpty()) {  

        WARN_STREAM << "Data missing:" << m_developerKey.isEmpty()  
            << m_secretKey.isEmpty();  
        Q_EMIT (error(SignOn::Error (SignOn::Error::MissingData,  
            QString("Missing developer and/or secret key"))));  
        return;  
    }  

    DropboxTokenRequest * request = new DropboxTokenRequest (this);  
    request->setDeveloperKey (m_developerKey);  
    request->setSecretKey (m_secretKey);  
    request->setEMail (eMail);  
    request->setPassword (password);  
    request->setMode (m_curMode);  
    m_inputData.setToken ("");  
    m_inputData.setTokenSecret ("");  

    setCurrentRequest (request);  

    QObject::connect (request, SIGNAL (done(int)), this,  
        SLOT (tokenRequestDone (int)));  

    DBG_STREAM << "Start token request (with developer/secret key)...";  
    request->start ();  
}  

DropboxRequest::Mode DropboxPlugin::mechanismToMode (  
    const QString & mechanism) {  

    if (mechanism == "Dropbox") {  
        return DropboxRequest::DROPBOX_MODE;  
    } else {  
        WARN_STREAM << "Unknown mechanism" << mechanism;  
        return DropboxRequest::INVALID_MODE;  
    }  
}  

QString DropboxPlugin::modeToMechanism (DropboxRequest::Mode mode) const {  

    switch (mode) {  
        case DropboxRequest::DROPBOX_MODE:  
            return "Dropbox";  
        default:  
            WARN_STREAM << "Unknown mode" << mode;          
            return "";  
    }   
}  

void DropboxPlugin::cancel () {  

    if (m_currentRequest != 0) {  
        m_currentRequest->cancel();  
    } else {  
        WARN_STREAM << "Nothing to cancel";  
    }  
}  

void DropboxPlugin::tokenRequestDone (int request_error) {  

    DropboxSessionData myData = m_inputData;  
    DropboxTokenRequest * tokenRequest =  
        qobject_cast <DropboxTokenRequest*> (QObject::sender());  
    QString token;  
    QString tokenSecret;  
    QString errorMessage;  

    if (tokenRequest == 0) {  
        errorMessage = "Signal received from wrong class or slot called" \  
            " as function";  
        CRIT_STREAM << errorMessage;  
        Q_EMIT (error(SignOn::Error(SignOn::Error::Unknown, errorMessage)));  
        return;  
    }   

    tokenRequest->deleteLater();  
    if (m_currentRequest == tokenRequest) {  
        m_currentRequest = 0;  
    }   

    if (request_error == 0) {  
        token = tokenRequest->token();  
        tokenSecret = tokenRequest->tokenSecret();  
        if (!token.isEmpty() && !tokenSecret.isEmpty()) {  
            myData.setDeveloperKey (m_developerKey);  
            myData.setSecretKey (m_secretKey);  
            myData.setToken (token);  
            myData.setTokenSecret (tokenSecret);  
            myData.setResponse (tokenRequest->response());  
            DBG_STREAM << "Writing credentials";  
            if (writeToCredentials (myData)== false) {  
                writeCache (myData);  
            }  
        }  

        // Only send result if process wasn't cancelled  
        Q_EMIT (result(myData));  
    } else {  
        Q_EMIT (error(SignOn::Error(request_error, errorMessage)));  
    }  
}  

void DropboxPlugin::setCurrentRequest (DropboxRequest * request) {  

    m_currentRequest = request;  
}  

QString DropboxPlugin::generateTokenKey (const QString & developerKey,  
    const QString & secretKey, const QString & eMail) {  

    QCryptographicHash hasher (QCryptographicHash::Sha1);  

    QByteArray bArray = developerKey.toAscii();  
    hasher.addData (bArray.constData(), bArray.size());  

    bArray = secretKey.toAscii();  
    hasher.addData (bArray.constData(), bArray.size());  

    bArray = eMail.toAscii();  
    hasher.addData (bArray.constData(), bArray.size());  

    return hasher.result().toHex();  
}  

bool DropboxPlugin::readCache (const QString & developerKey,  
    const QString & secretKey, const QString & eMail) {  

    QString key = generateTokenKey (developerKey, secretKey, eMail);  

    QSettings settings ("nokia", "dropbox-sso");  
    settings.beginReadArray (key);  

    QString token = settings.value ("token", QString("")).toString();  
    QString tokenSecret =   
        settings.value ("tokensecret", QString("")).toString();  

    if (token.isEmpty()) {  
        DBG_STREAM << "Old token not found in cache";  
        return false;  
    }  

    DBG_STREAM << "Read from cache" << key;  

    DropboxSessionData myData = m_inputData;  
    myData.setToken (token);  
    myData.setTokenSecret (tokenSecret);  
    myData.setEMail (eMail);  

    Q_EMIT (result(myData));  

    return true;      
}  

void DropboxPlugin::writeCache (DropboxSessionData & data) {  

    QString key = generateTokenKey (data.DeveloperKey(),  
        data.SecretKey(), data.EMail());  
    QSettings settings ("nokia", "dropbox-sso");  
    settings.beginReadArray (key);  

    settings.setValue ("token", data.Token ());  
    settings.setValue ("tokensecret", data.TokenSecret ());  
    settings.setValue ("email", data.EMail ());  
}  

bool DropboxPlugin::readFromCredentials (DropboxSessionData & input) {  

    if (m_trustedToken == false) {  
        return false;  
    }  

    //Check that we have password  
    if (input.SecretKey().isEmpty() == true) {  
        DBG_STREAM << "Can not find token from credentials";  
        return false;  
    }  

    //Copy secret to token  
    DropboxSessionData myData = m_inputData;  
    myData.setToken (m_inputData.Token());  
    myData.setTokenSecret (m_inputData.TokenSecret());  
    myData.setEMail (m_inputData.EMail ());  

    //Emit signal and return true  
    Q_EMIT (result(myData));  
    return true;  
}  

bool DropboxPlugin::writeToCredentials (DropboxSessionData & input) {  

    QString developerKey = m_developerKey;  
    QString secretKey = m_secretKey;  

    //Check that keys are trusted  
    if (isTrustedCredentials(developerKey, secretKey) == false) {  
        DBG_STREAM << "Untrusted client (will not write)";          
        return false;  
    }  

    DBG_STREAM << "Emitting store";  
    Q_EMIT (store (input));  
    return true;  
}  

SIGNON_DECL_AUTH_PLUGIN(DropboxPlugin)

Upload/update plugin

An upload/update plugin for a service implements the interfaces defined by the libwebupload library. The plugin can then be used by the webupload engine or other applications to upload to the relevant service. The plugin can also be used to provide additional functionality like retrieving list of albums, or creating a new album.

Upload/update plugins run as independent processes started and controlled by the webupload engine (during an upload) or the share-ui application (during an update).

Implementation

All upload/update plugins have to implement the PluginInterface class. This class has two functions – one provides a handle to the re-implemented PostInterface class which supports upload to the service, and the other provides a handle to the re-implemented UpdateInterface class which supports updates on the service account.

Example header of plugin class

#ifndef _DROPBOX_PLUGIN_H_
#define _DROPBOX_PLUGIN_H_

#include <QObject>  
#include <WebUpload/PluginInterface>  
#include <WebUpload/PluginBase>

//uses pluginbase as parent (it's just actual qobject where interface is pure c++ interface class)  
class DropboxPlugin : public WebUpload::PluginBase {  
Q_OBJECT  

public:  

    DropboxPlugin(QObject * parent = 0);  
    virtual ~DropboxPlugin();  

    //Function always called first. If false is returned, plugin will not be used after calling this  
    virtual bool init();  

    //Function returning instance of post class  
    virtual WebUpload::PostInterface * getPost();  

    //Function returning instanse of update class (null if you do not need one)  
    virtual WebUpload::UpdateInterface * getUpdate();

};

#endif

Example source file of plugin

#include "dropboxplugin.h"
#include "dropboxpost.h"  
#include "dropboxupdate.h"  
#include <WebUpload/PluginApplication>

//Important macro. This will add the main function that is needed to run plugin binary  
//The name of the plugin class is passed as argument  
PLUGIN_MAIN_FUNCTION (DropboxPlugin)

// just dummy constructor and destroyer are enough for this example  
DropboxPlugin::DropboxPlugin (QObject * parent) : WebUpload::PluginBase (parent) {}  
DropboxPlugin::~DropboxPlugin() {}

// do your inits here and return false if something went wrong (actually you do not have to  
// reimplement this if you do not do anything here, base class has implementation like this  
// already)  
bool DropboxPlugin::init() {  
      return true;  
 }

 // this function allows your code to tell what post class to use. Usually it's our own  
 WebUpload::PostInterface * DropboxPlugin::getPost() {  
      return new DropboxPost (this);  
 }

 // Tell what update class to use by creating instance you want  
 // (if you do not reimplement this function null will be returned, so only reimplement  
 // if you need update features)  
 WebUpload::UpdateInterface * DropboxPlugin::getUpdate() {  
      return new DropboxUpdate(this);  
 }

Posting/Uploading

Post classes are used to upload content to web services. For each transfer, a new instance is made. One transfer can anyway contain multiple items. This framework uses names Entry for transfer and Media for item in transfer. So each Entry will contain one or more media items (depending what user has selected to be shared and what service is accepting).

Example header of post class

#ifndef _DROPBOX_POST_H_
#define _DROPBOX_POST_H_  

#include <WebUpload/PostSimpleHttp>  
#include "dropboxuploadrequest.h"  
#include "datatypes.h"  

namespace WebUpload {  
    class Entry;  
    class ServiceOption;  
}  

class QNetworkReply;  

class DropboxPost : public WebUpload::PostSimpleHttp {  

    Q_OBJECT  

public:  

    DropboxPost (QObject *parent = 0);  

    ~DropboxPost ();

protected:  

    // Implementation for WebUpload::PostBase::getAuthPtr  
    WebUpload::AuthBase * getAuthPtr ();   

    // Implementation for WebUpload::PostBase::fixError  
    virtual void fixError (WebUpload::Entry * entry, WebUpload::Error error); 

    // Implementation for WebUpload::PostSimpleHttp::generateRequest  
    virtual QNetworkReply * generateRequest (WebUpload::Media *media);  

    // Implementation for WebUpload::PostSimpleHttp::handleResponse  
    virtual void handleResponse (QNetworkReply *media);  

private:  

    // Internal function for handling successful upload  
    void handleUploadSuccess();  

    // Internal function for handling upload HTTP errors  
    // (Dropbox uses lots of HTTP error codes)  
    void handleHttpError (int httpError);  

    // Internal function for handling upload errors  
    void handleUploadError(int errorCode, const QString& errorName);  

private:  

    enum RequestType {  
        REQUEST_UPLOAD,  
        REQUEST_OTHER  
    };  

    DropboxUploadRequest m_request;  
    RequestType m_requestType;  
    QString m_uploadedMediaURL;  
    WebUpload::Media* m_media;  
    WebUpload::Entry* m_entry;  
};  

#endif // _DROPBOX_POST_H_

Example source file of post

#include "dropboxpost.h"
#include "dropboxresponseparser.h"  
#include "dropboxupdate.h"  
#include <WebUpload/Account>  
#include <WebUpload/ServiceOptionValue>  
#include <QDebug>  

DropboxPost::DropboxPost (QObject *parent) : WebUpload::PostSimpleHttp (parent),  
    m_requestType (REQUEST_UPLOAD), m_media (0), m_entry (0) {  

    m_request.setNetAM(netAM);  
}  

DropboxPost::~DropboxPost () {  
}  

WebUpload::AuthBase * DropboxPost::getAuthPtr () {  
    return m_request.getAuthPtr ();  
}  

void DropboxPost::fixError (WebUpload::Entry * entry, WebUpload::Error error) {  
    if (canRetry (error)) {  
        Q_EMIT (errorFixed ());  
        return;  
    } else if (error.code() == WebUpload::Error::CODE_CUSTOM) {  
        QVariant data = error.data ();  
        if (data.isValid () && data.canConvert<int> ()) {  
            int val = data.toInt ();  
            if (val == 0xff) {  
                m_request.setTokenExpired ();  
                Q_EMIT (errorFixed ());  
                return;  
            }  
        }  
    }   

    Q_EMIT (errorFixFailed (error));  
}  

QNetworkReply* DropboxPost::generateRequest (WebUpload::Media* media) {  

    m_media = media;  
        if (media != 0 && m_entry == 0) {  
        const WebUpload::Entry* constEntry = media->entry();  
        m_entry = (WebUpload::Entry*)constEntry;  
        }  

    QNetworkReply* networkReply = 0;  
    m_requestType = REQUEST_UPLOAD;  
    networkReply = m_request.generateUploadRequest(media);  

    return networkReply;  
}  

void DropboxPost::handleResponse (QNetworkReply* response) {  

    // This is the complete response message sent by Dropbox  
    QByteArray responseData;  

    if (response != 0) {  
        responseData = response->readAll();  
    } else {  
        qCritical() << "got null network reply";  
    }  

    if(response->attribute(  
        QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {  
        // Parse response and try to get the information about the upload  
        DropboxResponseParser parser;  
        bool parsingOk = parser.parseResponse(responseData);  

        if (!parsingOk) {  
            handleUploadError(-1, "");  
            return;  
        }  

        bool successCheck = parser.checkIfSuccess ();  
        switch (m_requestType) {  
            case REQUEST_UPLOAD:  
            {  
                if (successCheck) {  
                    handleUploadSuccess();  
                } else {  
                    int errorCode = parser.errorCode();  
                    QString errorName = parser.errorName();  
                    handleUploadError(errorCode, errorName);  
                }  
                break;  
            }  


            default:  
            {  
                // Unspecified request type, assume error  
                handleUploadError(-1, "Unspecified request type");  
            }  
        }  
    } else {  
        handleHttpError(response->attribute(  
            QNetworkRequest::HttpStatusCodeAttribute).toInt());  
    }  
}  

void DropboxPost::handleHttpError (int httpError) {  

    WebUpload::Error error;  
    switch (httpError) {  
        case 401:  
        {  
            WebUpload::SharedAccount account;  
            if (m_entry != 0) {  
                account = m_entry->account();  
            }  

            QString accountName;  
            if (!account.isNull ()) {  
                accountName = account->name ();  
            }  
            account.clear ();  

            QString message = "Account not authorized";  
            QString desc = "Account is currently not authorized. \  
                Do you want to reauthorize it?";  
            QString retryMsg = "Reauthorize";  
            error = WebUpload::Error::custom (message, desc, retryMsg);  
            error.setData (0xff);  
            break;  
        }  

        default:  
        {  
            error = WebUpload::Error::serviceError("");  
            break;  
        }  
    }  

    Q_EMIT (mediaError(error));  
}  

void DropboxPost::handleUploadSuccess() {  

    Q_EMIT (mediaDone(m_uploadedMediaURL));  
}  

void DropboxPost::handleUploadError(int errorCode, const QString& errorName) {  

    WebUpload::Error error;  
    switch (errorCode) {  

        default:  
            error = WebUpload::Error::serviceError(errorName);  
            break;  
    }  

    Q_EMIT (mediaError(error));  
}

Updating

Update classes are used by UI to update options offered to user, for example, refresh folders user currently has on Dropbox service. It can also offer method to add new folder.

Example header of update class

#ifndef DROPBOX_UPDATE_H
#define DROPBOX_UPDATE_H  

#include <WebUpload/UpdateSimpleHttp>  
#include "dropboxrequest.h"  
#include "datatypes.h"  

class DropboxFolder;  

namespace WebUpload {  
    class Account;  
}  

class DropboxUpdate : public WebUpload::UpdateSimpleHttp {  
    Q_OBJECT  

public:  

    DropboxUpdate (QObject *parent = 0);  
    virtual ~DropboxUpdate();  

    // Implements WebUpload::UpdateBase::getAuthPtr  
    WebUpload::AuthBase * getAuthPtr ();  

protected:  

    // Implements WebUploadUpdate::SimpleHttp::generateUpdateRequest  
    QNetworkReply* generateUpdateRequest();  

    // Implements WebUploadUpdate::SimpleHttp::generateAddRequest  
    QNetworkReply* generateAddRequest (const QString &valueName);  

    // Implements WebUpload::UpdateSimpleHttp::handleResponse  
    void handleResponse(QNetworkReply* response);  

private:  

    void updateFolders(QList<DropboxFolder>& folders);  

    void handleRequestError(int errorCode, const QString& errorName);  

    void addDefaultFolderToFolderList (QList<DropboxFolder>& folderList);  

    void addFolderToFolderList (const QString & path,  
                                const QString & name,  
                                WebUpload::ServiceOption * option);  


private:  

    RequestType requestType;  
    DropboxRequest request;  
    QString folderOptionName;  
};  

#endif

Example source file of update

#include "dropboxupdate.h"
#include "dropboxresponseparser.h"  
#include "dropboxfolder.h"  
#include <WebUpload/ServiceOption>  
#include <WebUpload/ServiceOptionValue>  
#include <WebUpload/Account>  

DropboxUpdate::DropboxUpdate (QObject* parent) :  
    WebUpload::UpdateSimpleHttp (parent), folderOptionName("folder")  
{  
    request.setNetAM(netAM);  
}  

DropboxUpdate::~DropboxUpdate()  
{  
}  

WebUpload::AuthBase * DropboxUpdate::getAuthPtr()  
{  
    return request.getAuthPtr ();  
}  

QNetworkReply* DropboxUpdate::generateUpdateRequest()  
{  
    requestType = REQUEST_UPDATE;  
    return request.generateUpdateRequest();  
}  

QNetworkReply* DropboxUpdate::generateAddRequest (const QString &valueName)  
{  
    requestType = REQUEST_CREATE_FOLDER;  
    return request.generateAddFolderRequest(valueName, "/");  
}  

void DropboxUpdate::handleResponse(QNetworkReply* response)  
{  
    // This is the complete response message sent by Dropbox  
    QByteArray responseData;  

    if (response != 0) {  
        responseData = response->readAll();  
    }  

    // Parse response and try to get the information about the uploaded image  
    DropboxResponseParser parser;  
    bool parsingOk = parser.parseResponse(responseData);  

    if (parsingOk) {  
        // If the response is valid (not broken message etc.), check the  
        // actual content. It may either report the request was successful or  
        // describe an error, if one occurred.  

        bool requestWasSuccessful = parser.checkIfSuccess();  

        if (requestWasSuccessful) {  
            if (requestType == REQUEST_UPDATE) {  
                QList<DropboxFolder> folderList = parser.folders();  
                addDefaultFolderToFolderList(folderList);  
                updateFolders(folderList);  
            } else if (requestType == REQUEST_CREATE_FOLDER) {  
                Q_EMIT (optionAdded(true));  
            } else {  
                Q_EMIT (optionFailed(WebUpload::Error::CODE_CUSTOM));  
            }  
        } else {  
            int errorCode = parser.errorCode();  
            QString errorName = parser.errorName();  
            handleRequestError(errorCode, errorName);  
        }  
    } else {  
        qCritical() << "Error parsing the response: " << responseData;  
        handleRequestError(-1, "");  
    }  
}  


void DropboxUpdate::updateFolders(QList<DropboxFolder>& folders)  
{  
    WebUpload::ServiceOption* curOption = currentOption();  

    if (curOption != 0) {  
        QList<WebUpload::ServiceOptionValue> folderList =  
          *reinterpret_cast<QList<WebUpload::ServiceOptionValue>*>(&folders);  

        curOption->updateValues(folderList);  
        Q_EMIT (optionDone());  
    } else {  
        qWarning() << "Unable to update folder list";  
        Q_EMIT (optionFailed(WebUpload::Error::CODE_CUSTOM));  
    }  
}  

void DropboxUpdate::handleRequestError(int errorCode,  
                                      const QString& errorName)  
{  
    Q_EMIT (optionFailed (WebUpload::Error::CODE_CUSTOM));  
}  

void DropboxUpdate::addFolderToFolderList (const QString & path,  
                                           const QString & name,  
                                           WebUpload::ServiceOption * option)  
{  
    WebUpload::ServiceOptionValue folder(path, name);  

    QList<WebUpload::ServiceOptionValue> folderList = option->values();  
    folderList.prepend(folder);  
    option->updateValues(folderList, true);  
    option->setActiveValueId(name);  
}  


void DropboxUpdate::addDefaultFolderToFolderList (QList<DropboxFolder>& folderList)  
{  
    const QString defaultFolderId = "none_";  
    const QString defaultFolderName = "None";  
    DropboxFolder defaultFolder(defaultFolderId, defaultFolderName);  
    // This should be always the first list item  
    folderList.prepend(defaultFolder);  
}