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"