summaryrefslogblamecommitdiffstats
path: root/src/gui/clientFileSendDialog.cpp
blob: 93da7256654644bb94dc965db4225c97df637d85 (plain) (tree)























                                                                                



                         





                                                           





























                                                                                








                                               













                                                                          































































                                                                                                                                                                          





















































                                                                                      

                                          






























                                                                             
                      
     
















                                                                                      

     
                                                                        
 
                








                                                              
                                                               


     











                                                                 

                                                                    

                                 















                                                                       
















































                                                                                                            






















                                                                                       


















                                                                                            










                                             
/*
 # Copyright (c) 2009, 2010 - OpenSLX Project, Computer Center University of
 # Freiburg
 #
 # This program is free software distributed under the GPL version 2.
 # See http://openslx.org/COPYING
 #
 # If you have any feedback please consult http://openslx.org/feedback and
 # send your suggestions, praise, or complaints to feedback@openslx.org
 #
 # General information about OpenSLX can be found at http://openslx.org/
 # -----------------------------------------------------------------------------
 # clientFileSendDialog.cpp
 #  - filechooser and progress dialog
 # -----------------------------------------------------------------------------
 */

#include "clientFileSendDialog.h"

ClientFileSendDialog::ClientFileSendDialog(QWidget *parent) :
        QDialog(parent)
{
    setupUi(this);

    _transferID = 0;
    _error = false;
    _isMulticast = false;

    _file = NULL;
    _socket = NULL;
    _clientNicklistDialog = new ClientNicklistDialog(this);

    // connect to D-Bus and get interface
    QDBusConnection dbus = QDBusConnection::sessionBus();
    _ifaceDBus = new OrgOpenslxPvsInterface("org.openslx.pvs", "/", dbus, this);

    // get current users name from backend
    QDBusPendingReply<QString> reply = _ifaceDBus->chat_getNickname();
    reply.waitForFinished();
    if (reply.isValid())
        _nickname = reply.value();
    else
        _nickname = "unknown";

    connect(this, SIGNAL(finished(int)), this, SLOT(deleteLater()));
}

ClientFileSendDialog::~ClientFileSendDialog()
{
    qDebug("[%s] Deleted!", metaObject()->className());
}

////////////////////////////////////////////////////////////////////////////////
// Public

void ClientFileSendDialog::open()
{
    // get nick of remote user
    int result = _clientNicklistDialog->exec();
    if (result == 0)    // User canceled
    {
        reject();
        return;
    }

    if (_clientNicklistDialog->isSendToAll())
    {
    	sendToAll();
    }
    else
    {
    	open(_clientNicklistDialog->getNick());
    }
}

void ClientFileSendDialog::open(QString nick)
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Open File"),
            QDir::homePath(), "");
    if (filename == "")
    {
        reject();
        return;
    }
    open(nick, filename);
}

void ClientFileSendDialog::sendToAll()
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Send File"), QDir::homePath(), "");
    if (filename == "")
    {
        reject();
        return;
    }
    sendToAll(filename);
}

void ClientFileSendDialog::sendToAll(QString filename)
{
    _isMulticast = true;

    connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferStarted(qulonglong)), SLOT(multicastTransferStarted(qulonglong)));
    connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferProgress(qulonglong,qulonglong,qulonglong)), SLOT(multicastTransferProgress(qulonglong, qulonglong, qulonglong)));
    connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferFinished(qulonglong)), SLOT(multicastTransferFinished(qulonglong)));
    connect(_ifaceDBus, SIGNAL(outgoingMulticastTransferFailed(qulonglong, QString const&)), SLOT(multicastTransferFailed(qulonglong, QString const&)));

    filenameLabel->setText(filename);
    progressBar->setRange(0, 0);
    labelNick->setText(tr("all"));
    labelStatus->setText(tr("Waiting to start"));

    QString errorMessage("Backend error");

    // We need to jump through a lot of hoops because this call is prone to time out, and
    // QtDBus does not support specifying timeouts in generated interfaces.
    QDBusMessage call = QDBusMessage::createMethodCall("org.openslx.pvs", "/", "org.openslx.pvs", "createMulticastTransfer");
    call << filename;

    QDBusMessage reply = _ifaceDBus->connection().call(call, QDBus::Block, 5000);
    if (reply.type() == QDBusMessage::ErrorMessage)
    {
        QMessageBox::critical(this, tr("File Send error"), tr("Error communicating with backend: %1: %2").arg(reply.errorName()).arg(reply.errorMessage()));
        reject();
        return;
    }
    else if (reply.type() == QDBusMessage::InvalidMessage)
    {
        QMessageBox::critical(this, tr("File Send error"), tr("Something went wrong while communicating with backend, but I don't know what."));
        reject();
        return;
    }
    else if (reply.type() == QDBusMessage::ReplyMessage)
    {
        QList<QVariant> args = reply.arguments();
        bool created = args.at(0).toBool();
        _transferID = args.at(1).toULongLong();
        QString errorMessage = args.at(2).toString();
        if (!created)
        {
            QMessageBox::critical(this, tr("File Send error"), tr("Could not create a multicast transfer: %1").arg(errorMessage));
            reject();
            return;
        }
    }

    connect(cancelButton, SIGNAL(clicked()), SLOT(canceled()));

    show();
}

void ClientFileSendDialog::open(QString nick, QString filename)
{
    // open file
    _file = new QFile(filename);
    _file->open(QIODevice::ReadOnly);
    _bytesToWrite = _file->size();
    div = 1 + _bytesToWrite / 1000000000; // bc. progressBar supports only int

    // get host from backend
    QString host = "";
    QDBusPendingReply<QString> reply = _ifaceDBus->getIpByNick(nick);
    reply.waitForFinished();
    if (reply.isValid())
        host = reply.value();
    else // DBus Error, hostname
        qDebug("[%s] D-Bus ERROR, no hostname available!", metaObject()->className());

    // gui
    filenameLabel->setText(filename);
    progressBar->setValue(0);
    progressBar->setMaximum(_bytesToWrite/div);
    labelNick->setText(nick);
    labelB->setText(formatSize(_bytesToWrite));
    connect(cancelButton, SIGNAL(clicked()), this, SLOT(close()));

    // open socket
    _socket = new QTcpSocket();
    _socket->connectToHost(host, 29481);
    qDebug("[%s] Remote host: %s", metaObject()->className(), qPrintable(host));

    connect(_socket, SIGNAL(connected()), this, SLOT(sendHeader()));
    connect(_socket, SIGNAL(disconnected()), this, SLOT(close()));
    connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(error(QAbstractSocket::SocketError)));
}

////////////////////////////////////////////////////////////////////////////////
// Private

void ClientFileSendDialog::sendHeader()
{
    QFileInfo info(_file->fileName());
    QString size = QString::number(_bytesToWrite);
    QString header = _nickname + ";" + info.fileName() + ";" + size  + "\n";
    _socket->write(header.toLocal8Bit());
    connect(_socket, SIGNAL(readyRead()), this, SLOT(receiveAck()));
    qDebug("[%s] Sending header...", metaObject()->className());
}

void ClientFileSendDialog::receiveAck()
{
    QString ack = QString::fromUtf8(_socket->readLine());
    if (ack != "ok\n")
    {
    	_error = true;
    	_reason = tr("Receiver declined");
        qDebug("[%s] Received nack!", metaObject()->className());
        close();
        return;
    }
    qDebug("[%s] Received ack.", metaObject()->className());

    disconnect(_socket, SIGNAL(readyRead()), this, SLOT(receiveAck()));
    connect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(sendFile()));
    show();
    qDebug("[%s] Sending file...", metaObject()->className());
    sendFile();
}

void ClientFileSendDialog::sendFile()
{
    if (_bytesToWrite == 0)
    {
        qDebug("[%s] Transfer completed.", metaObject()->className());
        close(); // finished
    }
    else
    {
        qint64 bytesWritten = _socket->write(_file->read(1024)); // data left
        _bytesToWrite -= bytesWritten;
        progressBar->setValue(progressBar->value() + bytesWritten/div);
        labelA->setText(formatSize(progressBar->value()*div));
    }
}

void ClientFileSendDialog::close()
{
    if (!_isMulticast)
    {
        if (_file && _file->isOpen())
        {
            _file->close();
            qDebug("[%s] File closed.", metaObject()->className());
        }

        if (_socket && _socket->isOpen())
        {
            disconnect(_socket, SIGNAL(readyRead()), this, SLOT(receiveAck()));
            disconnect(_socket, SIGNAL(bytesWritten(qint64)), this, SLOT(sendFile()));
            disconnect(_socket, SIGNAL(disconnected()), this, SLOT(close()));
            disconnect(_socket, SIGNAL(connected()), this, SLOT(sendHeader()));
            disconnect(_socket, SIGNAL(error(QAbstractSocket::SocketError)),
                        this, SLOT(error(QAbstractSocket::SocketError)));
            _socket->disconnectFromHost();
            qDebug("[%s] Connection closed.", metaObject()->className());
        }
    }

    disconnect(cancelButton, SIGNAL(clicked()), this, SLOT(canceled()));

    if (!_error)
    {
        accept();
        QMessageBox::information(0, tr("PVS - File Transfer"),
                tr("File Transfer complete."));
    }
    else
    {
        reject();
        QMessageBox::warning(0, tr("PVS - File Transfer"),
                tr("File Transfer canceled: %1").arg(_reason));
    }
}

void ClientFileSendDialog::canceled()
{
    if(_isMulticast)
    {
        _ifaceDBus->cancelOutgoingMulticastTransfer(_transferID);
    }

    _error = true;
    _reason = tr("You clicked 'Cancel'");
    close();
}

void ClientFileSendDialog::error(QAbstractSocket::SocketError error)
{
    _error = true;
    _reason = tr("Socket Error");
    qDebug("[%s] Socket error: %i", metaObject()->className(), error);
    close();
}

QString ClientFileSendDialog::formatSize(qint64 size)
{
    if (size >= 1000000000) // GB
        return QString("%1GB").arg((qreal)size / 1000000000, 0, 'f',1);
    else if (size >= 1000000) // MB
        return QString("%1MB").arg((qreal)size / 1000000, 0, 'f',1);
    else if (size >= 1000) // KB
        return QString("%1KB").arg((qreal)size / 1000, 0, 'f',1);
    else // B
        return QString("%1B").arg((qreal)size, 0, 'f',1);
}

void ClientFileSendDialog::multicastTransferStarted(qulonglong transferID)
{
    qDebug() << "multicastTransferStarted(" << transferID << ")";
    if (transferID != _transferID)
    {
        return;
    }
    labelStatus->setText("Started");
}

void ClientFileSendDialog::multicastTransferProgress(qulonglong transferID, qulonglong bytes, qulonglong of)
{
    qDebug() << "multicastTransferProgress(" << transferID << bytes << of << ")";
    if (transferID != _transferID)
    {
        return;
    }

    if(bytes < of)
    {
        labelStatus->setText("Transferring");
        progressBar->setRange(0, of);
        progressBar->setValue(bytes);
    }
    else
    {
        labelStatus->setText("Waiting to finish");
        progressBar->setRange(0, 0);
    }

    labelA->setText(formatSize(bytes));
    labelB->setText(formatSize(of));
}

void ClientFileSendDialog::multicastTransferFinished(quint64 transferID)
{
    qDebug() << "multicastTransferFinished(" << transferID << ")";
    qDebug("[%s] MulticastTransfer finished", metaObject()->className());
    close();
}

void ClientFileSendDialog::multicastTransferFailed(quint64 transferID, QString const& reason)
{
    qDebug() << "multicastTransferFailed(" << transferID << reason << ")";
    _error = true;
    _reason = reason;
    close();
}

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

ClientNicklistDialog::ClientNicklistDialog(QWidget *parent) :
    QDialog(parent)
{
    setupUi(this);

    // connect to D-Bus and get interface
    QDBusConnection dbus = QDBusConnection::sessionBus();
    dbus.registerObject("/nicklist", this);
    dbus.registerService("org.openslx.pvsgui");
    _ifaceDBus = new OrgOpenslxPvsInterface("org.openslx.pvs", "/", dbus, this);

    // get available nicknames
    QDBusPendingReply<QStringList> reply = _ifaceDBus->chat_getNicknames();
    reply.waitForFinished();
    QStringList nicknames = reply.value();
    if (!reply.isValid() || nicknames.isEmpty()) // DBus Error, nicknames
        qDebug("[%s] D-Bus ERROR, no nicknames available!", metaObject()->className());

    listWidget->addItems(nicknames);
    listWidget->setCurrentRow(0);

    connect(sendToAllCheckBox, SIGNAL(stateChanged(int)), SLOT(sendToAllStateChanged(int)));

    sendToAllCheckBox->setCheckState(Qt::Unchecked);
    _isSendToAll = false;
}

void ClientNicklistDialog::sendToAllStateChanged(int state)
{
    if (state)
    {
        listWidget->setEnabled(false);
        _isSendToAll = true;
    }
    else
    {
        listWidget->setEnabled(true);
        _isSendToAll = false;
    }
}

ClientNicklistDialog::~ClientNicklistDialog()
{

}

QString ClientNicklistDialog::getNick()
{
    return listWidget->currentItem()->text();
}