aboutsummaryrefslogtreecommitdiff
path: root/source/QXmppTransferManager.cpp
diff options
context:
space:
mode:
authorJeremy Lainé <jeremy.laine@m4x.org>2010-02-24 10:33:02 +0000
committerJeremy Lainé <jeremy.laine@m4x.org>2010-02-24 10:33:02 +0000
commit95d3dfef904b3a90b64c8b28f93b3f4c04701048 (patch)
tree99fa0ba9138ad6de46464f921c121f955f690b99 /source/QXmppTransferManager.cpp
parenteaa17977f41d1fa3568540824bf33fd725c40afa (diff)
downloadqxmpp-95d3dfef904b3a90b64c8b28f93b3f4c04701048.tar.gz
replace QXmppIbbTransfer* by QXmppTransferManager
Diffstat (limited to 'source/QXmppTransferManager.cpp')
-rw-r--r--source/QXmppTransferManager.cpp496
1 files changed, 496 insertions, 0 deletions
diff --git a/source/QXmppTransferManager.cpp b/source/QXmppTransferManager.cpp
new file mode 100644
index 00000000..511933b8
--- /dev/null
+++ b/source/QXmppTransferManager.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2010 Bolloré telecom
+ *
+ * Author:
+ * Jeremy Lainé
+ *
+ * Source:
+ * http://code.google.com/p/qxmpp
+ *
+ * This file is a part of QXmpp library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ */
+
+#include <QDomElement>
+#include <QFile>
+#include <QFileInfo>
+
+#include "QXmppClient.h"
+#include "QXmppConstants.h"
+#include "QXmppIbbIqs.h"
+#include "QXmppStreamInitiationIq.h"
+#include "QXmppTransferManager.h"
+#include "QXmppUtils.h"
+
+QXmppTransferJob::QXmppTransferJob(const QString &jid, QXmppTransferManager *manager)
+ : QObject(manager),
+ m_done(0),
+ m_error(NoError),
+ m_iodevice(0),
+ m_jid(jid),
+ m_fileSize(0),
+ m_ibbSequence(0)
+{
+}
+
+void QXmppTransferJob::accept(QIODevice *iodevice)
+{
+ if (!m_iodevice)
+ m_iodevice = iodevice;
+}
+
+QXmppTransferJob::Error QXmppTransferJob::error() const
+{
+ return m_error;
+}
+
+QString QXmppTransferJob::jid() const
+{
+ return m_jid;
+}
+
+QDateTime QXmppTransferJob::fileDate() const
+{
+ return m_fileDate;
+}
+
+QString QXmppTransferJob::fileHash() const
+{
+ return m_fileHash;
+}
+
+QString QXmppTransferJob::fileName() const
+{
+ return m_fileName;
+}
+
+int QXmppTransferJob::fileSize() const
+{
+ return m_fileSize;
+}
+
+void QXmppTransferJob::terminate(QXmppTransferJob::Error cause)
+{
+ // close IO device
+ m_iodevice->close();
+
+ // emit signal
+ m_error = cause;
+ if (cause == NoError)
+ emit finished();
+ else
+ emit error(m_error);
+}
+
+QXmppTransferManager::QXmppTransferManager(QXmppClient *client)
+ : m_client(client), m_ibbBlockSize(4096)
+{
+}
+
+QXmppTransferJob* QXmppTransferManager::getJobByRequestId(const QString &jid, const QString &id)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->m_jid == jid && job->m_requestId == id)
+ return job;
+ return 0;
+}
+
+QXmppTransferJob* QXmppTransferManager::getJobBySid(const QString &jid, const QString &sid)
+{
+ foreach (QXmppTransferJob *job, m_jobs)
+ if (job->m_jid == jid && job->m_sid == sid)
+ return job;
+ return 0;
+}
+
+void QXmppTransferManager::ibbCloseIqReceived(const QXmppIbbCloseIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.getFrom());
+ response.setId(iq.getId());
+
+ QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
+ if (!job)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ // acknowledge the packet
+ response.setType(QXmppIq::Result);
+ m_client->sendPacket(response);
+
+ // terminate the transfer
+ if (job->fileSize() && job->m_done != job->fileSize())
+ job->terminate(QXmppTransferJob::FileCorruptError);
+ else
+ job->terminate(QXmppTransferJob::NoError);
+}
+
+void QXmppTransferManager::ibbDataIqReceived(const QXmppIbbDataIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.getFrom());
+ response.setId(iq.getId());
+
+ QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
+ if (!job)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ if (iq.getSequence() != job->m_ibbSequence)
+ {
+ // the packet is out of sequence
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::UnexpectedRequest);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ // write data
+ const QByteArray data = iq.getPayload();
+ job->m_iodevice->write(data);
+ job->m_done += data.size();
+ job->m_ibbSequence++;
+ job->progress(job->m_done, job->fileSize());
+
+ // acknowledge the packet
+ response.setType(QXmppIq::Result);
+ m_client->sendPacket(response);
+}
+
+void QXmppTransferManager::ibbOpenIqReceived(const QXmppIbbOpenIq &iq)
+{
+ QXmppIq response;
+ response.setTo(iq.getFrom());
+ response.setId(iq.getId());
+
+ QXmppTransferJob *job = getJobBySid(iq.getFrom(), iq.getSid());
+ if (!job)
+ {
+ // the job is unknown, cancel it
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::ItemNotFound);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ if (iq.getBlockSize() > m_ibbBlockSize)
+ {
+ // we prefer a smaller block size
+ QXmppStanza::Error error(QXmppStanza::Error::Modify, QXmppStanza::Error::ResourceConstraint);
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ // accept transfer
+ response.setType(QXmppIq::Result);
+ m_client->sendPacket(response);
+}
+
+void QXmppTransferManager::iqReceived(const QXmppIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
+ if (!job)
+ return;
+
+ // if the IO device is closed, do nothing
+ if (!job->m_iodevice->isOpen())
+ return;
+
+ if (iq.getType() == QXmppIq::Result)
+ {
+ const QByteArray buffer = job->m_iodevice->read(m_ibbBlockSize);
+
+ if (buffer.size())
+ {
+ // send next data block
+ QXmppIbbDataIq dataIq;
+ dataIq.setTo(job->m_jid);
+ dataIq.setSid(job->m_sid);
+ dataIq.setSequence(job->m_ibbSequence);
+ dataIq.setPayload(buffer);
+ m_client->sendPacket(dataIq);
+
+ job->m_done += buffer.size();
+ job->m_requestId = dataIq.getId();
+ job->m_ibbSequence++;
+ job->progress(job->m_done, job->fileSize());
+ } else {
+ // close the bytestream
+ QXmppIbbCloseIq closeIq;
+ closeIq.setTo(job->m_jid);
+ closeIq.setSid(job->m_sid);
+ m_client->sendPacket(closeIq);
+
+ job->m_requestId = closeIq.getId();
+ job->terminate(QXmppTransferJob::NoError);
+ }
+ }
+ else if (iq.getType() == QXmppIq::Error)
+ {
+ // close the bytestream
+ QXmppIbbCloseIq closeIq;
+ closeIq.setTo(job->m_jid);
+ closeIq.setSid(job->m_sid);
+ m_client->sendPacket(closeIq);
+
+ job->m_requestId = closeIq.getId();
+ job->terminate(QXmppTransferJob::ProtocolError);
+
+ }
+}
+
+QXmppTransferJob *QXmppTransferManager::sendFile(const QString &jid, const QString &fileName)
+{
+ // open file
+ QFile *fileIo = new QFile(fileName, this);
+ fileIo->open(QIODevice::ReadOnly);
+ QFileInfo info(*fileIo);
+
+ // create job
+ QXmppTransferJob *job = new QXmppTransferJob(jid, this);
+ job->m_iodevice = fileIo;
+ job->m_sid = generateStanzaHash();
+ job->m_fileDate = info.lastModified();
+ job->m_fileName = info.fileName();
+ job->m_fileSize = info.size();
+ m_jobs.append(job);
+
+ // prepare negotiation
+ QXmppElementList items;
+
+ QXmppElement file;
+ file.setTagName("file");
+ file.setAttribute("xmlns", ns_stream_initiation_file_transfer);
+ file.setAttribute("date", datetimeToString(job->fileDate()));
+ file.setAttribute("name", job->fileName());
+ file.setAttribute("size", QString::number(job->fileSize()));
+ items.append(file);
+
+ QXmppElement feature;
+ feature.setTagName("feature");
+ feature.setAttribute("xmlns", ns_feature_negotiation);
+
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", "jabber:x:data");
+ x.setAttribute("type", "form");
+ feature.appendChild(x);
+
+ QXmppElement field;
+ field.setTagName("field");
+ field.setAttribute("var", "stream-method");
+ field.setAttribute("type", "list-single");
+ x.appendChild(field);
+
+ QXmppElement option;
+ option.setTagName("option");
+ field.appendChild(option);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_ibb);
+ option.appendChild(value);
+ items.append(feature);
+
+ QXmppStreamInitiationIq request;
+ request.setType(QXmppIq::Set);
+ request.setTo(jid);
+ request.setProfile(QXmppStreamInitiationIq::FileTransfer);
+ request.setSiItems(items);
+ request.setSiId(job->m_sid);
+ job->m_requestId = request.getId();
+ m_client->sendPacket(request);
+
+ return job;
+}
+
+void QXmppTransferManager::streamInitiationIqReceived(const QXmppStreamInitiationIq &iq)
+{
+ if (iq.getType() == QXmppIq::Result)
+ streamInitiationResultReceived(iq);
+ else if (iq.getType() == QXmppIq::Set)
+ streamInitiationSetReceived(iq);
+}
+
+void QXmppTransferManager::streamInitiationResultReceived(const QXmppStreamInitiationIq &iq)
+{
+ QXmppTransferJob *job = getJobByRequestId(iq.getFrom(), iq.getId());
+ if (!job)
+ return;
+
+ int method = 0;
+ foreach (const QXmppElement &item, iq.getSiItems())
+ {
+ if (item.tagName() == "feature" && item.attribute("xmlns") == ns_feature_negotiation)
+ {
+ QXmppElement field = item.firstChildElement("x").firstChildElement("field");
+ while (!field.isNull())
+ {
+ if (field.attribute("var") == "stream-method")
+ {
+ if (field.firstChildElement("value").value() == ns_ibb)
+ method = QXmppTransferJob::InBandByteStream;
+/*
+ else if (field.firstChildElement("value").value() == ns_bytestreams)
+ method = QXmppTransferJob::SocksByteStream;
+*/
+ }
+ field = field.nextSiblingElement("field");
+ }
+ }
+ }
+
+ if (method == QXmppTransferJob::InBandByteStream)
+ {
+ QXmppIbbOpenIq openIq;
+ openIq.setTo(job->m_jid);
+ openIq.setSid(job->m_sid);
+ openIq.setBlockSize(m_ibbBlockSize);
+ m_client->sendPacket(openIq);
+ }
+}
+
+void QXmppTransferManager::streamInitiationSetReceived(const QXmppStreamInitiationIq &iq)
+{
+ QXmppStreamInitiationIq response;
+ response.setTo(iq.getFrom());
+ response.setId(iq.getId());
+
+ // check we support the profile
+ if (iq.getProfile() != QXmppStreamInitiationIq::FileTransfer)
+ {
+ // FIXME : we should add:
+ // <bad-profile xmlns='http://jabber.org/protocol/si'/>
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest);
+ error.setCode(400);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+ return;
+ }
+
+ // check the stream type
+ QXmppTransferJob *job = new QXmppTransferJob(iq.getFrom(), this);
+ job->m_methods = 0;
+ job->m_sid = iq.getSiId();
+ job->m_mimeType = iq.getMimeType();
+ foreach (const QXmppElement &item, iq.getSiItems())
+ {
+ if (item.tagName() == "feature" && item.attribute("xmlns") == ns_feature_negotiation)
+ {
+ QXmppElement field = item.firstChildElement("x").firstChildElement("field");
+ while (!field.isNull())
+ {
+ if (field.attribute("var") == "stream-method" && field.attribute("type") == "list-single")
+ {
+ QXmppElement option = field.firstChildElement("option");
+ while (!option.isNull())
+ {
+ if (option.firstChildElement("value").value() == ns_ibb)
+ job->m_methods = job->m_methods | QXmppTransferJob::InBandByteStream;
+/*
+ else if (option.firstChildElement("value").value() == ns_bytestreams)
+ job->m_methods = job->m_methods | QXmppTransferJob::SocksByteStream;
+*/
+ option = option.nextSiblingElement("option");
+ }
+ }
+ field = field.nextSiblingElement("field");
+ }
+ }
+ else if (item.tagName() == "file" && item.attribute("xmlns") == ns_stream_initiation_file_transfer)
+ {
+ job->m_fileDate = datetimeFromString(item.attribute("date"));
+ job->m_fileHash = item.attribute("hash");
+ job->m_fileName = item.attribute("name");
+ job->m_fileSize = item.attribute("size").toInt();
+ }
+ }
+
+ if (!job->m_methods)
+ {
+ // FIXME : we should add:
+ // <no-valid-streams xmlns='http://jabber.org/protocol/si'/>
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::BadRequest);
+ error.setCode(400);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+
+ delete job;
+ return;
+ }
+
+ // allow user to accept or decline the job
+ emit fileReceived(job);
+ if (!job->m_iodevice)
+ {
+ QXmppStanza::Error error(QXmppStanza::Error::Cancel, QXmppStanza::Error::Forbidden);
+ error.setCode(403);
+
+ response.setType(QXmppIq::Error);
+ response.setError(error);
+ m_client->sendPacket(response);
+
+ delete job;
+ return;
+ }
+
+ // the job was accepted
+ m_jobs.append(job);
+
+ QXmppElement value;
+ value.setTagName("value");
+ value.setValue(ns_ibb);
+
+ QXmppElement field;
+ field.setTagName("field");
+ field.setAttribute("var", "stream-method");
+ field.appendChild(value);
+
+ QXmppElement x;
+ x.setTagName("x");
+ x.setAttribute("xmlns", "jabber:x:data");
+ x.setAttribute("type", "submit");
+ x.appendChild(field);
+
+ QXmppElement feature;
+ feature.setTagName("feature");
+ feature.setAttribute("xmlns", ns_feature_negotiation);
+ feature.appendChild(x);
+
+ response.setType(QXmppIq::Result);
+ response.setProfile(iq.getProfile());
+ response.setSiItems(feature);
+
+ m_client->sendPacket(response);
+}
+