-
-
Save CypherpunkSamurai/1be2ba386098a4f2dbdf05b297f87221 to your computer and use it in GitHub Desktop.
Qt Bencode and Decoder and Torrent Creator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "bencode.h" | |
BEncode::BEncode() | |
: m_decodeError{ false } | |
{ | |
} | |
QByteArray BEncode::encode(QVariant value) | |
{ | |
return encodeVariant(value); | |
} | |
QVariant BEncode::decode(QByteArray value) | |
{ | |
Decoder decoder(value); | |
auto retval = decoder.decodeOne(); | |
if (decoder.hasErrors()) | |
qWarning() << "Error Decoding BEncode data: the data was malformed."; | |
return retval; | |
} | |
QByteArray BEncode::encodeVariant(QVariant value) | |
{ | |
QByteArray out; | |
if (value.type() == QVariant::LongLong | |
|| value.type() == QVariant::Int) { | |
out.append(encodeInteger(value.toLongLong())); | |
} | |
else if (value.canConvert(QMetaType::QByteArray)) { | |
out.append(encodeByteArray(value.toByteArray())); | |
} | |
else if (value.canConvert(QMetaType::QString)) { | |
out.append(encodeByteArray(value.toString().toUtf8())); | |
} | |
else if (value.canConvert(QMetaType::QVariantList)) { | |
out.append(encodeList(value.toList())); | |
} | |
else if (value.canConvert(QMetaType::QVariantMap)) { | |
out.append(encodeDict(value.toMap())); | |
} | |
return out; | |
} | |
QVariant BEncode::Decoder::decodeOne() | |
{ | |
char value = m_value.at(m_p); | |
if (value == 'i') { | |
return decodeInt(); | |
} | |
else if (value >= '0' && value <= '9') { | |
return decodeString(); | |
} | |
else if (value == 'l') { | |
return decodeList(); | |
} | |
else if (value == 'd') { | |
return decodeDict(); | |
} | |
else { | |
m_decodeError = true; | |
return QVariant(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <Qt> | |
#include <QByteArray> | |
#include <QString> | |
#include <QVariantList> | |
#include <QVariantMap> | |
#include <QStringList> | |
#include <QMetaType> | |
#include <QDebug> | |
#include <QPair> | |
#include <QPointer> | |
#include <QtDebug> | |
#include <new> | |
class BEncode { | |
public: | |
BEncode(); | |
QByteArray encode(QVariant value); | |
QVariant decode(QByteArray value); | |
private: | |
bool m_decodeError; | |
class Decoder { | |
bool m_decodeError; | |
QByteArray m_value; | |
int m_p; | |
public: | |
inline Decoder(QByteArray& data) | |
: m_decodeError{ false } | |
, m_value{ data } | |
, m_p{ 0 } | |
{ | |
} | |
QVariant decodeOne(); | |
inline bool hasErrors() | |
{ | |
return m_decodeError; | |
} | |
private: | |
inline qint64 decodeRawInt() | |
{ | |
int start = m_p; | |
int sign = 1; | |
QString parsedData; | |
if (m_value.at(m_p) == '-') { | |
sign = -1; | |
start = ++m_p; | |
} | |
while (m_value.at(m_p) >= '0' && m_value.at(m_p) <= '9') { | |
parsedData.append(m_value.at(m_p)); | |
m_p++; | |
} | |
if (m_p == start) { | |
m_decodeError = true; | |
return 0; | |
} | |
return parsedData.toLongLong() * sign; | |
} | |
inline QVariant decodeInt() | |
{ | |
m_p++; | |
qint64 value = decodeRawInt(); | |
if (m_value.at(m_p) != 'e') { | |
return QVariant(); | |
} | |
m_p++; | |
return value; | |
} | |
inline QVariant decodeString() | |
{ | |
int len{ static_cast<int>(decodeRawInt()) }; | |
if (m_value.at(m_p) != ':') { | |
m_decodeError = true; | |
return QVariant(); | |
} | |
m_p++; | |
char* charData = new char[len]; | |
for (qint64 i = 0; i < len && m_value.length() > m_p; i++, m_p++) { | |
charData[i] = m_value.at(m_p); | |
} | |
QByteArray ret_value(charData, len); | |
delete[] charData; | |
if (m_value.length() < m_p) { | |
m_decodeError = true; | |
return QVariant(); | |
} | |
return ret_value; | |
} | |
inline QVariantList decodeList() | |
{ | |
QVariantList out; | |
m_p++; | |
while (m_value.length() > m_p | |
&& m_value.at(m_p) != 'e' | |
&& !m_decodeError) { | |
out << decodeOne(); | |
} | |
if (m_value.length() <= m_p | |
|| m_decodeError | |
|| m_value.at(m_p) != 'e') { | |
m_decodeError = true; | |
return QVariantList(); | |
} | |
m_p++; | |
return out; | |
} | |
inline QVariantMap decodeDict() | |
{ | |
QVariantMap out; | |
m_p++; | |
while (m_value.length() > m_p | |
&& m_value.at(m_p) != 'e' | |
&& !m_decodeError) { | |
QString key{ decodeString().toString() }; | |
auto value = decodeOne(); | |
out[key] = value; | |
} | |
if (m_value.length() <= m_p | |
|| m_decodeError | |
|| m_value.at(m_p) != 'e') { | |
m_decodeError = true; | |
return QVariantMap(); | |
} | |
m_p++; | |
return out; | |
} | |
}; | |
inline QByteArray encodeInteger(qint64 value) | |
{ | |
return QString("i%1e").arg(value).toLocal8Bit(); | |
} | |
inline QByteArray encodeByteArray(QByteArray value) | |
{ | |
return QString("%1:").arg(value.length()).toLocal8Bit() + value; | |
} | |
QByteArray encodeVariant(QVariant value); | |
inline QByteArray encodeList(QVariantList list) | |
{ | |
QByteArray out{ "l" }; | |
foreach (QVariant value, list) { | |
out.append(encodeVariant(value)); | |
} | |
out.append("e"); | |
return out; | |
} | |
inline QByteArray encodeDict(QVariantMap map) | |
{ | |
QByteArray out{ "d" }; | |
foreach (QVariant key, map.keys()) { | |
auto v(encodeVariant(key)); | |
auto k(encodeVariant(map[key.toString()])); | |
out.append(v); | |
out.append(k); | |
} | |
out.append("e"); | |
return out; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "torrentcreator.h" | |
TorrentCreator::TorrentCreator(QObject* parent) | |
: QObject(parent) | |
{ | |
m_outputDir = QDir::homePath() + "/bitsync"; | |
} | |
QString TorrentCreator::tracker() | |
{ | |
return m_tracker; | |
} | |
void TorrentCreator::setTracker(QString tracker) | |
{ | |
m_tracker = tracker; | |
emit trackerChanged(tracker); | |
} | |
TorrentCreator::~TorrentCreator() | |
{ | |
} | |
QByteArray TorrentCreator::torrentMetadata() | |
{ | |
return m_torrentMetadata; | |
} | |
QString TorrentCreator::getHash() | |
{ | |
return m_fileHash; | |
} | |
QString TorrentCreator::fileName() | |
{ | |
return m_filename; | |
} | |
QString TorrentCreator::outputDir() | |
{ | |
return m_outputDir; | |
} | |
void TorrentCreator::createTorrentDialog() | |
{ | |
createTorrent(QFileDialog::getOpenFileName(0, tr("Selecione o arquivo a ser compartilhado."))); | |
} | |
void TorrentCreator::setOutputDir(QString dirname) | |
{ | |
if (QFileInfo(dirname).isDir()) { | |
m_outputDir = QDir().absoluteFilePath(dirname); | |
emit outputDirChanged(m_outputDir); | |
} | |
} | |
void TorrentCreator::createTorrent(QString filename) | |
{ | |
QFile file(filename); | |
if (file.exists()) { | |
m_filename = file.fileName(); | |
connect(this, &TorrentCreator::_done, [=](QVariantMap data) { | |
m_fileHash = data["hash"].toByteArray(); | |
m_isReady = true; | |
m_torrentMetadata = BEncode().encode(data["metadata"]); | |
emit created(m_torrentMetadata); | |
}); /* FIXME: here it should run as a queued connection type | |
/ however Qt functor connector can't set it, | |
/ should we use mine QtLambdaConnect intead? | |
/ it seems that QtAutoConnection is clever enough to check the ThreadId | |
/ to decides if it should use DirectConnection or QueuedConnection, let's see | |
*/ | |
QThreadPool::globalInstance()->start(new TorrentCreatorWorker([=]() { | |
auto outputFileName = this->outputDir() + QDir::separator() + QFileInfo(filename).fileName(); | |
QFile file(filename); | |
QFile outputFile(outputFileName); | |
auto filePath = QFileInfo(file).absoluteFilePath(); | |
auto outputFilePath = QFileInfo(outputFileName).absoluteFilePath(); | |
qDebug() << filePath << outputFilePath << (filePath == outputFilePath); | |
bool needCopy = (filePath != outputFilePath); | |
if (needCopy) | |
qDebug() << "needcopy??" << needCopy; | |
if (needCopy) | |
outputFile.open(QFile::WriteOnly); | |
file.open(QFile::ReadOnly); | |
int pieceSize = PIECE_SIZE; | |
//adaptative piecesize to avoid an huge big torrent file | |
while ((file.size()/pieceSize) > 3000 | |
&& pieceSize < 0x40000 /* 512k */ ) { | |
pieceSize <<= 1; | |
} | |
QCryptographicHash fileHash(QCryptographicHash::Sha1); | |
QByteArray data; | |
QList<QByteArray> hashes; | |
int read = 0; | |
int leftBytes = file.size(); | |
while((leftBytes = file.bytesAvailable())) { | |
int readSize = pieceSize < leftBytes ? pieceSize : leftBytes; | |
data = file.read(readSize); | |
if(data.isEmpty()){ | |
qDebug() << "WTF!!! no bytes? how it could be??" << readSize; | |
break; | |
} | |
read += data.length(); | |
qreal progress = qreal(read) / file.size(); | |
fileHash.addData(data); | |
hashes.append(QCryptographicHash::hash(data, QCryptographicHash::Sha1)); | |
if (needCopy) | |
outputFile.write(data); | |
emit this->createProgress(progress); | |
} | |
QVariantMap output; | |
QVariantMap torrent; | |
QVariantMap info; | |
torrent["encoding"] = "UTF-8"; | |
torrent["creation date"] = int(QDateTime::currentMSecsSinceEpoch() / 1000); | |
torrent["created by"] = "RogerSoftware BitSync/1.0"; | |
torrent["announce"] = tracker(); | |
info["name"] = QFileInfo(filename).fileName(); | |
info["length"] = QFileInfo(filename).size(); | |
info["piece length"] = pieceSize; | |
info["private"] = static_cast<int>(isPrivate()); | |
info["pieces"] = hashes.join(""); | |
torrent["info"] = info; | |
output["hash"] = fileHash.result().toHex(); | |
output["metadata"] = torrent; | |
emit this->_done(output); | |
})); | |
} | |
} | |
bool TorrentCreator::isPrivate() | |
{ | |
return m_private; | |
} | |
void TorrentCreator::setPrivate(bool pvt) | |
{ | |
m_private = pvt; | |
emit privacyChanged(pvt); | |
} | |
TorrentCreatorWorker::TorrentCreatorWorker(std::function<void()> worker) | |
: m_worker{ worker } | |
{ | |
setAutoDelete(true); | |
} | |
void TorrentCreatorWorker::run() | |
{ | |
m_worker(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <QObject> | |
#include <QFile> | |
#include <QDir> | |
#include <QThreadPool> | |
#include <QThread> | |
#include <QRunnable> | |
#include <functional> | |
#include <QCryptographicHash> | |
#include "bencode.h" | |
#include <QVariantMap> | |
#include <QMutex> | |
#include <QTime> | |
#include <QFileDialog> | |
const int PIECE_SIZE = 0x8000; | |
class TorrentCreatorWorker : public QRunnable { | |
std::function<void(void)> m_worker; | |
public: | |
TorrentCreatorWorker(std::function<void(void)> worker); | |
void run(); | |
}; | |
class TorrentCreator : public QObject { | |
Q_OBJECT | |
Q_PROPERTY(QString outputDir READ outputDir WRITE setOutputDir NOTIFY outputDirChanged) | |
Q_PROPERTY(QString tracker READ tracker WRITE setTracker NOTIFY trackerChanged) | |
Q_PROPERTY(bool isPrivate READ isPrivate WRITE setPrivate NOTIFY privacyChanged) | |
Q_PROPERTY(QString hash READ getHash) | |
Q_PROPERTY(QByteArray metadata READ torrentMetadata) | |
QString m_outputDir; | |
QString m_tracker; | |
QByteArray m_torrentMetadata; | |
QByteArray m_fileHash; | |
QString m_filename; | |
bool m_isReady; | |
bool m_private; | |
public: | |
explicit TorrentCreator(QObject* parent = 0); | |
~TorrentCreator(); | |
signals: | |
void privacyChanged(bool); | |
void trackerChanged(QString); | |
void outputDirChanged(QString); | |
void createProgress(qreal); | |
void created(QByteArray); | |
void _done(QVariantMap); | |
public slots: | |
QByteArray torrentMetadata(); | |
QString getHash(); | |
QString fileName(); | |
QString tracker(); | |
void setTracker(QString tracker); | |
QString outputDir(); | |
void createTorrentDialog(); | |
void setOutputDir(QString dirname); | |
void createTorrent(QString filename); | |
bool isPrivate(); | |
void setPrivate(bool pvt); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment