Logo Search packages:      
Sourcecode: webissues version File versions  Download package

commandmanager.cpp

/**************************************************************************
* This file is part of the WebIssues program
* Copyright (C) 2006 Michał Męciński
* Copyright (C) 2007 WebIssues Team
*
* 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.
**************************************************************************/

#include "commandmanager.h"

#include <QHttp>
#include <QRegExp>

#include "command.h"
#include "abstractbatch.h"
#include "formdatamessage.h"

using namespace WebIssues;

00023 CommandManager* WebIssues::commandManager = NULL;

00025 CommandManager::CommandManager() :
    m_currentRequest( 0 ),
    m_currentBatch( NULL ),
    m_currentCommand( NULL ),
    m_error( NoError )
{
    m_http = new QHttp();

    connect( m_http, SIGNAL( dataSendProgress( int , int ) ),
        this, SLOT( dataSendProgress( int , int ) ) );
    connect( m_http, SIGNAL( dataReadProgress( int , int ) ),
        this, SLOT( dataReadProgress( int , int ) ) );

    connect( m_http, SIGNAL( responseHeaderReceived( const QHttpResponseHeader& ) ),
        this, SLOT( responseHeaderReceived( const QHttpResponseHeader& ) ) );

    connect( m_http, SIGNAL( readyRead( const QHttpResponseHeader& ) ),
        this, SLOT( readyRead( const QHttpResponseHeader& ) ) );

    connect( m_http, SIGNAL( requestFinished( int, bool ) ),
        this, SLOT( requestFinished( int, bool ) ) );
}

00048 CommandManager::~CommandManager()
{
    delete m_http;
    m_http = NULL;

    setError( Aborted, 0, QString() );

    while ( !m_batches.isEmpty() ) {
        AbstractBatch* batch = m_batches.takeFirst();
        batch->setCompleted( false );
        delete batch;
    }
}

00062 void CommandManager::setServerUrl( const QUrl& url )
{
    m_url = url;
    m_cookie = QString();

    sendSetHostRequest();
}

00070 void CommandManager::execute( AbstractBatch* batch )
{
    int pos = 0;
    for ( int i = 0; i < m_batches.count(); i++ ) {
        if ( m_batches.at( i )->priority() < batch->priority() )
            break;
        pos++;
    }
    m_batches.insert( pos, batch );

    checkPendingCommand();
}

00083 void CommandManager::abort( AbstractBatch* batch )
{
    if ( batch == m_currentBatch ) {
        m_http->abort();
    } else {
        setError( Aborted, 0, QString() );
        m_batches.removeAt( m_batches.indexOf( batch ) );
        batch->setCompleted( false );
        delete batch;
    }
}

00095 QString CommandManager::errorMessage( const QString& whatFailed )
{
    QString message;

    switch ( m_error ) {
        case Aborted:
            message = tr( "Operation aborted" );
            break;
        case ConnectionError:
            message = tr( "Connection failed" );
            break;
        case InvalidServer:
            message = tr( "Not a WebIssues server" );
            break;
        case InvalidVersion:
            message = tr( "Unsupported server version" );
            break;
        case WebIssuesError:
            message = whatFailed;
            break;
        case InvalidResponse:
            message = tr( "Invalid server response" );
            break;
        case HttpError:
            message = tr( "Server error" );
            break;
    }

    if ( m_error == CommandManager::WebIssuesError || m_error == CommandManager::HttpError )
        message += QString( " (%1 %2)" ).arg( m_errorCode ).arg( m_errorString );

    return message;
}

void CommandManager::checkPendingCommand()
{
    if ( m_currentBatch )
        return;

    while ( !m_batches.isEmpty() ) {
        AbstractBatch* batch = m_batches.first();

        Command* command = batch->fetchNext();
        if ( command ) {
            m_currentBatch = batch;
            m_currentCommand = command;
            sendCommandRequest( command );
            break;
        }

        m_batches.removeFirst();
        batch->setCompleted( true );
        delete batch;

        if ( m_currentBatch )
            break;
    }
}

void CommandManager::sendSetHostRequest()
{
    QString host = m_url.host();

    int port = m_url.port();
    if ( port < 0 )
        port = 80;

    m_http->setHost( host, port );
}

void CommandManager::sendCommandRequest( Command* command )
{
    QHttpRequestHeader header;

    QString path = m_url.path();
    if ( path.isEmpty() )
        path = "/";

    header.setRequest( "POST", path );
    header.setValue( "Host", m_url.host() );

    if ( !m_cookie.isEmpty() )
        header.setValue( "Cookie", m_cookie );

    QString commandLine = command->keyword();

    for ( int i = 0; i < command->args().count(); i++ ) {
        commandLine += ' ';
        QVariant arg = command->args().at( i );
        if ( arg.type() == QVariant::String )
            commandLine += quoteString( arg.toString() );
        else
            commandLine += arg.toString();
    }

    FormDataMessage message;

    message.addField( "command", commandLine.toUtf8() );

    if ( !command->attachment().isEmpty() )
        message.addAttachment( "file", "file", command->attachment() );

    message.finish();

    header.setContentType( message.contentType() );

    m_currentRequest = m_http->request( header, message.body() );
}

void CommandManager::dataSendProgress( int done, int total )
{
    m_currentCommand->setSendProgress( done, total );
}

void CommandManager::dataReadProgress( int done, int total )
{
    m_currentCommand->setReadProgress( done, total );
}

void CommandManager::responseHeaderReceived( const QHttpResponseHeader& response )
{
    m_responseStatus = response.statusCode();
    m_responseReason = response.reasonPhrase();

    m_protocolVersion = response.value( "X-WebIssues-Version" );
    m_serverVersion = response.value( "X-WebIssues-Server" );

    if ( m_responseStatus == 301 || m_responseStatus == 302 )
        m_url = m_url.resolved( response.value( "Location" ) );

    m_contentType = response.contentType();

    QString cookie = response.value( "Set-Cookie" );
    if ( !cookie.isEmpty() )
        m_cookie = cookie;
}

void CommandManager::readyRead( const QHttpResponseHeader& response )
{
    if ( m_currentCommand->acceptBinaryResponse() && m_contentType == "application/octet-stream" ) {
        int length;
        char buffer[ 8192 ];
        while ( ( length = m_http->read( buffer, 8192 ) ) > 0 )
            m_currentCommand->setBinaryBlock( buffer, length );
    }
}

void CommandManager::requestFinished( int id, bool error )
{
    if ( !m_currentBatch || id != m_currentRequest )
        return;

    if ( !error && ( m_responseStatus == 301 || m_responseStatus == 302 ) ) {
        sendSetHostRequest();
        sendCommandRequest( m_currentCommand );
        return;
    }

    bool successful = handleCommandResponse();

    if ( !successful ) {
        m_batches.removeAt( m_batches.indexOf( m_currentBatch ) );
        m_currentBatch->setCompleted( false );
        delete m_currentBatch;
    }

    m_currentBatch = NULL;
    m_currentCommand = NULL;

    checkPendingCommand();
}

bool CommandManager::handleCommandResponse()
{
    if ( m_http->error() != QHttp::NoError ) {
        if ( m_http->error() == QHttp::Aborted )
            setError( Aborted, 0, QString() );
        else
            setError( ConnectionError, m_http->error(), m_http->errorString() );
        return false;
    }

    if ( m_responseStatus != 200 ) {
        setError( HttpError, m_responseStatus, m_responseReason );
        return false;
    }

    if ( m_protocolVersion.isEmpty() ) {
        setError( InvalidServer, 0, QString() );
        return false;
    }

    if ( m_protocolVersion != "0.8" ) {
        setError( InvalidVersion, 0, m_protocolVersion );
        return false;
    }

    if ( m_contentType == "text/plain" ) {
        QByteArray body = m_http->readAll();

        QString string = QString::fromUtf8( body.data(), body.size() );

        Reply reply;
        if ( !parseReply( string, reply ) ) {
            setError( InvalidResponse, 0, QString() );
            return false;
        }

        return handleCommandReply( reply );
    }

    if ( m_contentType == "application/octet-stream" ) {
        if ( !m_currentCommand->acceptBinaryResponse() ) {
            setError( InvalidResponse, 0, QString() );
            return false;
        }

        setError( NoError, 0, QString() );
        return true;
    }

    setError( InvalidResponse, 0, QString() );
    return false;
}

bool CommandManager::handleCommandReply( const Reply& reply )
{
    bool isNull = false;

    if ( reply.lines().count() == 1 ) {
        ReplyLine line = reply.lines().at( 0 );
        QString signature = makeSignature( line );

        if ( signature == "ERROR is" ) {
            setError( WebIssuesError, line.argInt( 0 ), line.argString( 1 ) );
            return false;
        }

        if ( signature == "NULL" )
            isNull = true;
    }

    bool isValid = isNull ? m_currentCommand->acceptNullReply() : validateReply( reply );

    if ( !isValid ) {
        setError( InvalidResponse, 0, QString() );
        return false;
    }

    if ( !isNull )
        m_currentCommand->setCommandReply( reply );

    setError( NoError, 0, QString() );
    return true;
}

bool CommandManager::parseReply( const QString& string, Reply& reply )
{
    QStringList lines = string.split( "\r\n", QString::SkipEmptyParts );

    QRegExp lineRegExp( "([A-Z]+)(?: ('(?:\\\\['\\\\n]|[^'\\\\])*'|-?[0-9]+))*" );
    QRegExp argumentRegExp( "('(?:\\\\['\\\\n]|[^'\\\\])*'|-?[0-9]+)" );

    for ( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) {
        if ( !lineRegExp.exactMatch( *it ) )
            return false;

        ReplyLine line;
        line.setKeyword( lineRegExp.cap( 1 ) );

        int pos = 0;
        while ( ( pos = argumentRegExp.indexIn( *it, pos ) ) >= 0 ) {
            QString argument = argumentRegExp.cap( 0 );
            if ( argument[ 0 ] == '\'' )
                line.addArg( unquoteString( argument ) );
            else
                line.addArg( argument.toInt() );
            pos += argumentRegExp.matchedLength();
        }

        reply.addLine( line );
    }

    return true;
}

bool CommandManager::validateReply( const Reply& reply )
{
    int line = 0;
    int rule = 0;

    while ( line < reply.lines().count() && rule < m_currentCommand->rules().count() ) {
        ReplyRule replyRule = m_currentCommand->rules().at( rule );
        if ( makeSignature( reply.lines().at( line ) ) == replyRule.signature() ) {
            line++;
            if ( replyRule.multiplicity() == ReplyRule::One )
                rule++;
        } else {
            if ( replyRule.multiplicity() == ReplyRule::One )
                return false;
            rule++;
        }
    }

    while ( rule < m_currentCommand->rules().count() ) {
        if ( m_currentCommand->rules().at( rule ).multiplicity() == ReplyRule::One )
            return false;
        rule++;
    }

    if ( line < reply.lines().count() )
        return false;

    return true;
}

QString CommandManager::makeSignature( const ReplyLine& line )
{
    if ( line.args().isEmpty() )
        return line.keyword();

    QString signature = line.keyword() + ' ';

    for ( int i = 0; i < line.args().count(); i++ ) {
        switch ( line.args().at( i ).type() ) {
            case QVariant::Int:
                signature += 'i';
                break;
            case QVariant::String:
                signature += 's';
                break;
        }
    }

    return signature;
}

QString CommandManager::quoteString( const QString& string )
{
    QString result = "\'";
    int length = string.length();
    for ( int i = 0; i < length; i++ ) {
        QChar ch = string[ i ];
        if  ( ch == '\\' || ch == '\'' || ch == '\n' ) {
            result += '\\';
            if ( ch == '\n' )
                ch = 'n';
        }
        result += ch;
    }
    result += '\'';
    return result;
}

QString CommandManager::unquoteString( const QString& string )
{
    QString result;
    int length = string.length();
    for ( int i = 1; i < length - 1; i++ ) {
        QChar ch = string[ i ];
        if ( ch == '\\' ) {
            ch = string[ ++i ];
            if ( ch == 'n' )
                ch = '\n';
        }
        result += ch;
    }
    return result;
}

void CommandManager::setError( Error error, int code, const QString& string )
{
    m_error = error;
    m_errorCode = code;
    m_errorString = string;
}

#include "commandmanager.moc"

Generated by  Doxygen 1.6.0   Back to index