Skip to content

Instantly share code, notes, and snippets.

@caetanus
Last active August 10, 2021 19:39
Show Gist options
  • Save caetanus/cb05f3a50f3241041d3a to your computer and use it in GitHub Desktop.
Save caetanus/cb05f3a50f3241041d3a to your computer and use it in GitHub Desktop.
Qt Bencode and Decoder and Torrent Creator
#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();
}
}
#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;
}
};
#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();
}
#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