File indexing completed on 2025-01-05 04:37:26

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "torrentcreator.h"
0007 #include "globals.h"
0008 #include "statsfile.h"
0009 #include "torrentcontrol.h"
0010 #include <bcodec/bencoder.h>
0011 #include <diskio/chunkmanager.h>
0012 #include <klocalizedstring.h>
0013 #include <qdir.h>
0014 #include <qfileinfo.h>
0015 #include <time.h>
0016 #include <util/array.h>
0017 #include <util/error.h>
0018 #include <util/file.h>
0019 #include <util/fileops.h>
0020 #include <util/functions.h>
0021 #include <util/log.h>
0022 #include <util/sha1hash.h>
0023 #include <version.h>
0024 
0025 namespace bt
0026 {
0027 TorrentCreator::TorrentCreator(const QString &tar,
0028                                const QStringList &track,
0029                                const QList<QUrl> &webseeds,
0030                                Uint32 cs,
0031                                const QString &name,
0032                                const QString &comments,
0033                                bool priv,
0034                                bool decentralized)
0035     : target(tar)
0036     , trackers(track)
0037     , webseeds(webseeds)
0038     , chunk_size(cs)
0039     , name(name)
0040     , comments(comments)
0041     , cur_chunk(0)
0042     , priv(priv)
0043     , tot_size(0)
0044     , decentralized(decentralized)
0045     , stopped(false)
0046 {
0047     this->chunk_size *= 1024;
0048     QFileInfo fi(target);
0049     if (fi.isDir()) {
0050         if (!this->target.endsWith(bt::DirSeparator()))
0051             this->target += bt::DirSeparator();
0052 
0053         tot_size = 0;
0054         buildFileList("");
0055         num_chunks = tot_size / chunk_size;
0056         if (tot_size % chunk_size > 0)
0057             num_chunks++;
0058         last_size = tot_size % chunk_size;
0059         Out(SYS_GEN | LOG_DEBUG) << "Tot Size : " << tot_size << endl;
0060     } else {
0061         tot_size = bt::FileSize(target);
0062         num_chunks = tot_size / chunk_size;
0063         if (tot_size % chunk_size > 0)
0064             num_chunks++;
0065         last_size = tot_size % chunk_size;
0066         Out(SYS_GEN | LOG_DEBUG) << "Tot Size : " << tot_size << endl;
0067     }
0068 
0069     if (last_size == 0)
0070         last_size = chunk_size;
0071 
0072     Out(SYS_GEN | LOG_DEBUG) << "Num Chunks : " << num_chunks << endl;
0073     Out(SYS_GEN | LOG_DEBUG) << "Chunk Size : " << chunk_size << endl;
0074     Out(SYS_GEN | LOG_DEBUG) << "Last Size : " << last_size << endl;
0075 }
0076 
0077 TorrentCreator::~TorrentCreator()
0078 {
0079 }
0080 
0081 void TorrentCreator::buildFileList(const QString &dir)
0082 {
0083     QDir d(target + dir);
0084     // first get all files (we ignore symlinks)
0085     const QStringList dfiles = d.entryList(QDir::Files);
0086     Uint32 cnt = 0; // counter to keep track of file index
0087     for (const QString &s : dfiles) {
0088         // add a TorrentFile to the list
0089         Uint64 fs = bt::FileSize(target + dir + s);
0090         TorrentFile f(nullptr, cnt, dir + s, tot_size, fs, chunk_size);
0091         files.append(f);
0092         // update total size
0093         tot_size += fs;
0094         cnt++;
0095     }
0096 
0097     // now for each subdir do a buildFileList
0098     const QStringList subdirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0099     for (const QString &s : subdirs) {
0100         QString sd = dir + s;
0101         if (!sd.endsWith(bt::DirSeparator()))
0102             sd += bt::DirSeparator();
0103         buildFileList(sd);
0104     }
0105 }
0106 
0107 void TorrentCreator::saveTorrent(const QString &url)
0108 {
0109     File fptr;
0110     if (!fptr.open(url, "wb"))
0111         throw Error(i18n("Cannot open file %1: %2", url, fptr.errorString()));
0112 
0113     BEncoder enc(&fptr);
0114     enc.beginDict(); // top dict
0115 
0116     if (!decentralized) {
0117         enc.write(QByteArrayLiteral("announce"));
0118         if (trackers.count() > 0)
0119             enc.write(trackers[0].toUtf8());
0120         else
0121             enc.write(QByteArray());
0122 
0123         if (trackers.count() > 1) {
0124             enc.write(QByteArrayLiteral("announce-list"));
0125             enc.beginList();
0126             for (const QString &t : std::as_const(trackers)) {
0127                 enc.beginList();
0128                 enc.write(t.toUtf8());
0129                 enc.end();
0130             }
0131             enc.end();
0132         }
0133     }
0134 
0135     if (comments.length() > 0) {
0136         enc.write(QByteArrayLiteral("comment"));
0137         enc.write(comments.toUtf8());
0138     }
0139     enc.write(QByteArrayLiteral("created by"));
0140     enc.write(bt::GetVersionString().toLatin1());
0141     enc.write(QByteArrayLiteral("creation date"));
0142     enc.write((Uint64)time(nullptr));
0143     enc.write(QByteArrayLiteral("info"));
0144     saveInfo(enc);
0145     // save the nodes list after the info hash, keys must be sorted !
0146     if (decentralized) {
0147         // DHT torrent
0148         enc.write(QByteArrayLiteral("nodes"));
0149         enc.beginList();
0150 
0151         for (const QString &t : std::as_const(trackers)) {
0152             enc.beginList();
0153             enc.write(t.section(',', 0, 0).toUtf8());
0154             enc.write((Uint32)t.section(',', 1, 1).toInt());
0155             enc.end();
0156         }
0157         enc.end();
0158     }
0159 
0160     if (webseeds.count() == 1) {
0161         enc.write(QByteArrayLiteral("url-list"));
0162         enc.write(webseeds[0].toDisplayString().toUtf8());
0163     } else if (webseeds.count() > 0) {
0164         enc.write(QByteArrayLiteral("url-list"));
0165         enc.beginList();
0166         for (const QUrl &u : std::as_const(webseeds)) {
0167             enc.write(u.toDisplayString().toUtf8());
0168         }
0169         enc.end();
0170     }
0171 
0172     enc.end();
0173 }
0174 
0175 void TorrentCreator::saveInfo(BEncoder &enc)
0176 {
0177     enc.beginDict();
0178 
0179     QFileInfo fi(target);
0180     if (fi.isDir()) {
0181         enc.write(QByteArrayLiteral("files"));
0182         enc.beginList();
0183         for (const TorrentFile &file : std::as_const(files))
0184             saveFile(enc, file);
0185 
0186         enc.end();
0187     } else {
0188         enc.write(QByteArrayLiteral("length"));
0189         enc.write(bt::FileSize(target));
0190     }
0191     enc.write(QByteArrayLiteral("name"));
0192     enc.write(name.toUtf8());
0193     enc.write(QByteArrayLiteral("piece length"));
0194     enc.write((Uint64)chunk_size);
0195     enc.write(QByteArrayLiteral("pieces"));
0196     savePieces(enc);
0197     if (priv) {
0198         enc.write(QByteArrayLiteral("private"));
0199         enc.write((Uint64)1);
0200     }
0201     enc.end();
0202 }
0203 
0204 void TorrentCreator::saveFile(BEncoder &enc, const TorrentFile &file)
0205 {
0206     enc.beginDict();
0207     enc.write(QByteArrayLiteral("length"));
0208     enc.write(file.getSize());
0209     enc.write(QByteArrayLiteral("path"));
0210     enc.beginList();
0211     const QStringList sl = file.getPath().split(bt::DirSeparator());
0212     for (const QString &s : sl)
0213         enc.write(s.toUtf8());
0214     enc.end();
0215     enc.end();
0216 }
0217 
0218 void TorrentCreator::savePieces(BEncoder &enc)
0219 {
0220     Array<Uint8> big_hash(num_chunks * 20);
0221     for (Uint32 i = 0; i < num_chunks; ++i) {
0222         memcpy(big_hash + (20 * i), hashes[i].getData(), 20);
0223     }
0224     enc.write(big_hash, num_chunks * 20);
0225 }
0226 
0227 bool TorrentCreator::calcHashSingle()
0228 {
0229     Array<Uint8> buf(chunk_size);
0230     File fptr;
0231     if (!fptr.open(target, "rb"))
0232         throw Error(i18n("Cannot open file %1: %2", target, fptr.errorString()));
0233 
0234     Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size;
0235     fptr.seek(File::BEGIN, (Int64)cur_chunk * chunk_size);
0236     fptr.read(buf, s);
0237     SHA1Hash h = SHA1Hash::generate(buf, s);
0238     hashes.append(h);
0239     cur_chunk++;
0240     return cur_chunk >= num_chunks;
0241 }
0242 
0243 bool TorrentCreator::calcHashMulti()
0244 {
0245     Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size;
0246     // first find the file(s) the chunk lies in
0247     Array<Uint8> buf(s);
0248     QList<TorrentFile> file_list;
0249     int i = 0;
0250     while (i < files.size()) {
0251         const TorrentFile &tf = files[i];
0252         if (cur_chunk >= tf.getFirstChunk() && cur_chunk <= tf.getLastChunk()) {
0253             file_list.append(tf);
0254         }
0255 
0256         i++;
0257     }
0258 
0259     Uint32 read = 0;
0260     for (i = 0; i < file_list.count(); i++) {
0261         const TorrentFile &f = file_list[i];
0262         File fptr;
0263         if (!fptr.open(target + f.getPath(), "rb")) {
0264             throw Error(i18n("Cannot open file %1: %2", f.getPath(), fptr.errorString()));
0265         }
0266 
0267         // first calculate offset into file
0268         // only the first file can have an offset
0269         // the following files will start at the beginning
0270         Uint64 off = 0;
0271         if (i == 0)
0272             off = f.fileOffset(cur_chunk, chunk_size);
0273 
0274         Uint32 to_read = 0;
0275         // then the amount of data we can read from this file
0276         if (file_list.count() == 1)
0277             to_read = s;
0278         else if (i == 0)
0279             to_read = f.getLastChunkSize();
0280         else if (i == file_list.count() - 1)
0281             to_read = s - read;
0282         else
0283             to_read = f.getSize();
0284 
0285         // read part of data
0286         fptr.seek(File::BEGIN, (Int64)off);
0287         fptr.read(buf + read, to_read);
0288         read += to_read;
0289     }
0290 
0291     // generate hash
0292     SHA1Hash h = SHA1Hash::generate(buf, s);
0293     hashes.append(h);
0294 
0295     cur_chunk++;
0296     //  Out(SYS_GEN|LOG_DEBUG) << "=============================================" << endl;
0297     return cur_chunk >= num_chunks;
0298 }
0299 
0300 bool TorrentCreator::calculateHash()
0301 {
0302     if (cur_chunk >= num_chunks)
0303         return true;
0304     if (files.empty())
0305         return calcHashSingle();
0306     else
0307         return calcHashMulti();
0308 }
0309 
0310 void TorrentCreator::run()
0311 {
0312     if (hashes.empty())
0313         while (!stopped && !calculateHash())
0314             ;
0315 }
0316 
0317 TorrentControl *TorrentCreator::makeTC(const QString &data_dir)
0318 {
0319     QString dd = data_dir;
0320     if (!dd.endsWith(bt::DirSeparator()))
0321         dd += bt::DirSeparator();
0322 
0323     // make data dir if necessary
0324     if (!bt::Exists(dd))
0325         bt::MakeDir(dd);
0326 
0327     // save the torrent
0328     saveTorrent(dd + QLatin1String("torrent"));
0329     // write full index file
0330     File fptr;
0331     if (!fptr.open(dd + QLatin1String("index"), "wb"))
0332         throw Error(i18n("Cannot create index file: %1", fptr.errorString()));
0333 
0334     for (Uint32 i = 0; i < num_chunks; i++) {
0335         NewChunkHeader hdr;
0336         hdr.index = i;
0337         fptr.write(&hdr, sizeof(NewChunkHeader));
0338     }
0339     fptr.close();
0340 
0341     // now create the torrentcontrol object
0342     TorrentControl *tc = new TorrentControl();
0343     try {
0344         // get the parent dir of target
0345         QFileInfo fi = QFileInfo(target);
0346 
0347         QString odir;
0348         StatsFile st(dd + QLatin1String("stats"));
0349         if (fi.fileName() == name) {
0350             st.write("OUTPUTDIR", fi.path());
0351             odir = fi.path();
0352         } else {
0353             st.write("CUSTOM_OUTPUT_NAME", "1");
0354             st.write("OUTPUTDIR", target);
0355             odir = target;
0356         }
0357         st.write("UPLOADED", "0");
0358         st.write("RUNNING_TIME_DL", "0");
0359         st.write("RUNNING_TIME_UL", "0");
0360         st.write("PRIORITY", "0");
0361         st.write("AUTOSTART", "1");
0362         st.write("IMPORTED", QString::number(tot_size));
0363         st.sync();
0364 
0365         tc->init(nullptr, bt::LoadFile(dd + "torrent"), dd, odir);
0366         tc->createFiles();
0367     } catch (...) {
0368         bt::Delete(dd, true);
0369         delete tc;
0370         throw;
0371     }
0372 
0373     return tc;
0374 }
0375 }
0376 
0377 #include "moc_torrentcreator.cpp"