/*
# 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();
}