File indexing completed on 2024-11-24 04:31:15
0001 /* 0002 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "multifilecache.h" 0007 #include <QSet> 0008 #include <QTextStream> 0009 #include <errno.h> 0010 #include <klocalizedstring.h> 0011 #include <qdir.h> 0012 #include <qfileinfo.h> 0013 #include <qstringlist.h> 0014 #include <util/error.h> 0015 #include <util/file.h> 0016 #include <util/fileops.h> 0017 #include <util/functions.h> 0018 #include <util/log.h> 0019 #ifdef Q_WS_WIN 0020 #include <util/win32.h> 0021 #endif 0022 #include "cache.h" 0023 #include "cachefile.h" 0024 #include "chunk.h" 0025 #include "deletedatafilesjob.h" 0026 #include "dndfile.h" 0027 #include "movedatafilesjob.h" 0028 #include "piecedata.h" 0029 #include "preallocationthread.h" 0030 #include <torrent/torrent.h> 0031 0032 namespace bt 0033 { 0034 static Uint64 FileOffset(Chunk *c, const TorrentFile &f, Uint64 chunk_size); 0035 static Uint64 FileOffset(Uint32 cindex, const TorrentFile &f, Uint64 chunk_size); 0036 static void DeleteEmptyDirs(const QString &output_dir, const QString &fpath); 0037 0038 MultiFileCache::MultiFileCache(Torrent &tor, const QString &tmpdir, const QString &datadir, bool custom_output_name) 0039 : Cache(tor, tmpdir, datadir) 0040 , opened(false) 0041 { 0042 cache_dir = tmpdir + "cache" + bt::DirSeparator(); 0043 0044 if (!custom_output_name) 0045 output_dir = this->datadir + tor.getNameSuggestion() + bt::DirSeparator(); 0046 else 0047 output_dir = this->datadir; 0048 } 0049 0050 MultiFileCache::~MultiFileCache() 0051 { 0052 cleanupPieceCache(); 0053 } 0054 0055 void MultiFileCache::loadFileMap() 0056 { 0057 QString file_map = tmpdir + "file_map"; 0058 if (!bt::Exists(file_map)) { 0059 // file map doesn't exist, so just set the path on disk if it has not happened yet 0060 Uint32 num = tor.getNumFiles(); 0061 for (Uint32 i = 0; i < num; i++) { 0062 TorrentFile &tf = tor.getFile(i); 0063 if (tf.getPathOnDisk().isEmpty()) 0064 tf.setPathOnDisk(output_dir + tf.getUserModifiedPath()); 0065 } 0066 saveFileMap(); 0067 } else { 0068 QFile fptr(tmpdir + "file_map"); 0069 if (!fptr.open(QIODevice::ReadOnly)) 0070 throw Error(i18n("Failed to open %1: %2", file_map, fptr.errorString())); 0071 0072 Uint32 idx = 0; 0073 while (!fptr.atEnd() && idx < tor.getNumFiles()) { 0074 QString path = QString::fromLocal8Bit(fptr.readLine().trimmed()); 0075 tor.getFile(idx).setPathOnDisk(path); 0076 idx++; 0077 } 0078 0079 // now the user modified paths must come 0080 idx = 0; 0081 while (!fptr.atEnd() && idx < tor.getNumFiles()) { 0082 QString path = QString::fromLocal8Bit(fptr.readLine().trimmed()); 0083 if (!path.isEmpty()) 0084 tor.getFile(idx).setUserModifiedPath(path); 0085 idx++; 0086 } 0087 } 0088 } 0089 0090 void MultiFileCache::saveFileMap() 0091 { 0092 QString file_map = tmpdir + "file_map"; 0093 QFile fptr(file_map); 0094 if (!fptr.open(QIODevice::WriteOnly)) 0095 throw Error(i18n("Failed to create %1: %2", file_map, fptr.errorString())); 0096 0097 QTextStream out(&fptr); 0098 // file map doesn't exist, so create it based upon the output_dir 0099 Uint32 num = tor.getNumFiles(); 0100 for (Uint32 i = 0; i < num; i++) { 0101 TorrentFile &tf = tor.getFile(i); 0102 out << tf.getPathOnDisk() << Qt::endl; 0103 } 0104 0105 // After the actual paths on disk, save the user modified path names 0106 for (Uint32 i = 0; i < num; i++) { 0107 TorrentFile &tf = tor.getFile(i); 0108 out << tf.getUserModifiedPath() << Qt::endl; 0109 } 0110 } 0111 0112 QString MultiFileCache::getOutputPath() const 0113 { 0114 return output_dir; 0115 } 0116 0117 void MultiFileCache::close() 0118 { 0119 clearPieceCache(); 0120 if (piece_cache.isEmpty()) 0121 files.clear(); 0122 opened = false; 0123 } 0124 0125 void MultiFileCache::open() 0126 { 0127 // Check if the cache is not yet open 0128 if (opened) 0129 return; 0130 0131 QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); 0132 // open all files 0133 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0134 TorrentFile &tf = tor.getFile(i); 0135 if (!tf.doNotDownload()) { 0136 if (files.contains(i)) 0137 files.remove(i); 0138 0139 CacheFile::Ptr fd(new CacheFile()); 0140 fd->open(tf.getPathOnDisk(), tf.getSize()); 0141 files.insert(i, fd); 0142 } else { 0143 if (dnd_files.contains(i)) 0144 dnd_files.remove(i); 0145 0146 QString dnd_path = QString("file%1.dnd").arg(tf.getIndex()); 0147 QString dnd_file = dnd_dir + dnd_path; 0148 if (bt::Exists(dnd_dir + tf.getUserModifiedPath() + ".dnd")) { 0149 // old style dnd dir, move the file so that we can keep working 0150 // with the old file 0151 bt::Move(dnd_dir + tf.getUserModifiedPath() + ".dnd", dnd_file, true, true); 0152 } 0153 DNDFile::Ptr dfd(new DNDFile(dnd_file, &tf, tor.getChunkSize())); 0154 dfd->checkIntegrity(); 0155 dnd_files.insert(i, dfd); 0156 } 0157 } 0158 0159 opened = true; 0160 } 0161 0162 void MultiFileCache::changeTmpDir(const QString &ndir) 0163 { 0164 Cache::changeTmpDir(ndir); 0165 QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); 0166 0167 // change paths for individual files, it should not 0168 // be a problem to move these files when they are open 0169 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0170 TorrentFile &tf = tor.getFile(i); 0171 if (tf.doNotDownload()) { 0172 DNDFile::Ptr dfd = dnd_files[i]; 0173 if (dfd) { 0174 QString dnd_path = QString("file%1.dnd").arg(tf.getIndex()); 0175 dfd->changePath(dnd_dir + dnd_path); 0176 } 0177 } 0178 } 0179 } 0180 0181 void MultiFileCache::changeOutputPath(const QString &outputpath) 0182 { 0183 output_dir = outputpath; 0184 if (!output_dir.endsWith(bt::DirSeparator())) 0185 output_dir += bt::DirSeparator(); 0186 0187 datadir = output_dir; 0188 0189 Uint32 num = tor.getNumFiles(); 0190 for (Uint32 i = 0; i < num; i++) { 0191 TorrentFile &tf = tor.getFile(i); 0192 tf.setPathOnDisk(output_dir + tf.getUserModifiedPath()); 0193 CacheFile::Ptr cf = files[tf.getIndex()]; 0194 if (cf) 0195 cf->changePath(tf.getPathOnDisk()); 0196 } 0197 saveFileMap(); 0198 } 0199 0200 Job *MultiFileCache::moveDataFiles(const QString &ndir) 0201 { 0202 if (!bt::Exists(ndir)) 0203 bt::MakeDir(ndir); 0204 0205 QString nd = ndir; 0206 if (!nd.endsWith(bt::DirSeparator())) 0207 nd += bt::DirSeparator(); 0208 0209 new_output_dir = nd; 0210 0211 MoveDataFilesJob *job = new MoveDataFilesJob(); 0212 int nmoves = 0; 0213 0214 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0215 TorrentFile &tf = tor.getFile(i); 0216 if (tf.doNotDownload()) 0217 continue; 0218 0219 // check if every directory along the path exists, and if it doesn't 0220 // create it 0221 MakeFilePath(nd + tf.getUserModifiedPath()); 0222 0223 if (QFileInfo(tf.getPathOnDisk()).canonicalPath() != QFileInfo(nd + tf.getUserModifiedPath()).canonicalPath()) { 0224 job->addMove(tf.getPathOnDisk(), nd + tf.getUserModifiedPath()); 0225 nmoves++; 0226 } 0227 } 0228 0229 if (nmoves == 0) { 0230 delete job; 0231 return nullptr; 0232 } else { 0233 return job; 0234 } 0235 } 0236 0237 void MultiFileCache::moveDataFilesFinished(Job *job) 0238 { 0239 if (job->error()) 0240 return; 0241 0242 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0243 TorrentFile &tf = tor.getFile(i); 0244 tf.setPathOnDisk(new_output_dir + tf.getUserModifiedPath()); 0245 CacheFile::Ptr cf = files[tf.getIndex()]; 0246 if (cf) 0247 cf->changePath(tf.getPathOnDisk()); 0248 // check for empty directories and delete them 0249 DeleteEmptyDirs(output_dir, tf.getUserModifiedPath()); 0250 } 0251 } 0252 0253 Job *MultiFileCache::moveDataFiles(const QMap<TorrentFileInterface *, QString> &files) 0254 { 0255 if (files.count() == 0) 0256 return nullptr; 0257 0258 MoveDataFilesJob *job = new MoveDataFilesJob(files); 0259 return job; 0260 } 0261 0262 void MultiFileCache::moveDataFilesFinished(const QMap<TorrentFileInterface *, QString> &fmap, Job *job) 0263 { 0264 if (job->error()) 0265 return; 0266 0267 QMap<TorrentFileInterface *, QString>::const_iterator i = fmap.begin(); 0268 while (i != fmap.end()) { 0269 TorrentFileInterface *tf = i.key(); 0270 QString path = tf->getPathOnDisk(); 0271 QString dest = i.value(); 0272 if (QFileInfo(dest).isDir()) { 0273 QString path = tf->getUserModifiedPath(); 0274 if (!dest.endsWith(bt::DirSeparator())) 0275 dest += bt::DirSeparator(); 0276 0277 int last = path.lastIndexOf(bt::DirSeparator()); 0278 tf->setPathOnDisk(dest + path.mid(last + 1)); 0279 } else 0280 tf->setPathOnDisk(i.value()); 0281 0282 CacheFile::Ptr cf = files[tf->getIndex()]; 0283 if (cf) 0284 cf->changePath(tf->getPathOnDisk()); 0285 ++i; 0286 } 0287 0288 Cache::moveDataFilesFinished(fmap, job); 0289 saveFileMap(); 0290 } 0291 0292 void MultiFileCache::create() 0293 { 0294 if (!bt::Exists(output_dir)) 0295 MakeDir(output_dir); 0296 if (!bt::Exists(tmpdir + "dnd")) 0297 bt::MakeDir(tmpdir + "dnd"); 0298 0299 QSet<QString> mount_points; 0300 QSet<QString> shortened_names; 0301 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0302 TorrentFile &tf = tor.getFile(i); 0303 #ifndef Q_WS_WIN 0304 // check if the filename is to long 0305 if (FileNameToLong(tf.getPathOnDisk())) { 0306 QString s = ShortenFileName(tf.getPathOnDisk()); 0307 Out(SYS_DIO | LOG_DEBUG) << "Path to long " << tf.getPathOnDisk() << endl; 0308 // make sure there are no dupes 0309 int cnt = 1; 0310 while (shortened_names.contains(s)) { 0311 s = ShortenFileName(tf.getPathOnDisk(), cnt++); 0312 } 0313 Out(SYS_DIO | LOG_DEBUG) << "Shortened to " << s << endl; 0314 0315 tf.setPathOnDisk(s); 0316 shortened_names.insert(s); 0317 } 0318 #endif 0319 touch(tf); 0320 if (!tf.doNotDownload()) 0321 mount_points.insert(tf.getMountPoint()); 0322 } 0323 0324 saveMountPoints(mount_points); 0325 saveFileMap(); 0326 } 0327 0328 void MultiFileCache::touch(TorrentFile &tf) 0329 { 0330 QString fpath = tf.getUserModifiedPath(); 0331 bool dnd = tf.doNotDownload(); 0332 // first split fpath by / separator 0333 QStringList sl = fpath.split(bt::DirSeparator()); 0334 0335 // then make the file 0336 if (!dnd) { 0337 MakeFilePath(tf.getPathOnDisk()); 0338 if (!bt::Exists(tf.getPathOnDisk())) { 0339 bt::Touch(tf.getPathOnDisk()); 0340 } else { 0341 preexisting_files = true; 0342 tf.setPreExisting(true); // mark the file as preexisting 0343 } 0344 } 0345 } 0346 0347 PieceData::Ptr MultiFileCache::createPiece(Chunk *c, Uint32 off, Uint32 length, bool read_only) 0348 { 0349 open(); 0350 0351 QList<Uint32> tflist; 0352 tor.calcChunkPos(c->getIndex(), tflist); 0353 0354 // one file so try to map it 0355 if (tflist.count() == 1) { 0356 const TorrentFile &f = tor.getFile(tflist.first()); 0357 CacheFile::Ptr fd = files[tflist.first()]; 0358 if (!fd) 0359 return PieceData::Ptr(); 0360 0361 if (Cache::mappedModeAllowed() && mmap_failures < 3) { 0362 Uint64 offset = FileOffset(c, f, tor.getChunkSize()) + off; 0363 PieceData::Ptr piece(new PieceData(c, off, length, nullptr, fd, read_only)); 0364 Uint8 *buf = (Uint8 *)fd->map(piece.data(), offset, length, read_only ? CacheFile::READ : CacheFile::RW); 0365 if (buf) { 0366 piece->setData(buf); 0367 insertPiece(c, piece); 0368 return piece; 0369 } else { 0370 mmap_failures++; 0371 } 0372 } 0373 } 0374 0375 // mmap failed or there are multiple files, so just do buffered 0376 Uint8 *buf = new Uint8[length]; 0377 PieceData::Ptr piece(new PieceData(c, off, length, buf, CacheFile::Ptr(), read_only)); 0378 insertPiece(c, piece); 0379 return piece; 0380 } 0381 0382 PieceData::Ptr MultiFileCache::preparePiece(Chunk *c, Uint32 off, Uint32 length) 0383 { 0384 PieceData::Ptr piece = findPiece(c, off, length, false); 0385 if (piece) 0386 return piece; 0387 0388 return createPiece(c, off, length, false); 0389 } 0390 0391 void MultiFileCache::calculateOffsetAndLength(Uint32 piece_off, Uint32 piece_len, Uint64 file_off, Uint32 chunk_off, Uint32 chunk_len, Uint64 &off, Uint32 &len) 0392 { 0393 // if the piece offset lies in the range of the current chunk, we need to read data 0394 if (piece_off >= chunk_off && piece_off + piece_len <= chunk_off + chunk_len) { 0395 // The piece lies entirely in the current file 0396 off = file_off + (piece_off - chunk_off); 0397 len = piece_len; 0398 } else if (piece_off >= chunk_off && piece_off < chunk_off + chunk_len) { 0399 // The start of the piece lies partially in the current file 0400 off = file_off + (piece_off - chunk_off); 0401 len = chunk_len - (piece_off - chunk_off); 0402 } else if (piece_off < chunk_off && piece_off + piece_len > chunk_off && piece_off + piece_len <= chunk_off + chunk_len) { 0403 // The end of the piece lies in the file 0404 off = file_off; 0405 len = piece_len - (chunk_off - piece_off); 0406 } else if (chunk_off >= piece_off && chunk_off + chunk_len < piece_off + piece_len) { 0407 // the current file lies entirely in the piece 0408 off = file_off; 0409 len = chunk_len; 0410 } 0411 } 0412 0413 PieceData::Ptr MultiFileCache::loadPiece(Chunk *c, Uint32 off, Uint32 length) 0414 { 0415 open(); 0416 0417 PieceData::Ptr piece = findPiece(c, off, length, true); 0418 if (piece) 0419 return piece; 0420 0421 // create piece and return it if it is mapped or the create failed 0422 piece = createPiece(c, off, length, true); 0423 if (!piece || piece->mapped()) 0424 return piece; 0425 0426 // Now we need to load it 0427 QList<Uint32> tflist; 0428 tor.calcChunkPos(c->getIndex(), tflist); 0429 0430 // The chunk lies in one file, so it is easy 0431 if (tflist.count() == 1) { 0432 const TorrentFile &f = tor.getFile(tflist[0]); 0433 CacheFile::Ptr fd = files[tflist[0]]; 0434 Uint64 piece_off = FileOffset(c, f, tor.getChunkSize()) + off; 0435 0436 fd->read(piece->data(), length, piece_off); 0437 return piece; 0438 } 0439 0440 // multiple files 0441 Uint8 *data = piece->data(); 0442 Uint32 chunk_off = 0; // number of bytes passed of the chunk 0443 Uint32 piece_off = 0; // how many bytes read to the piece 0444 for (int i = 0; i < tflist.count(); i++) { 0445 const TorrentFile &f = tor.getFile(tflist[i]); 0446 CacheFile::Ptr fd = files[tflist[i]]; 0447 DNDFile::Ptr dfd = dnd_files[tflist[i]]; 0448 0449 // first calculate offset into file 0450 // only the first file can have an offset 0451 // the following files will start at the beginning 0452 Uint64 file_off = 0; 0453 if (i == 0) 0454 file_off = FileOffset(c, f, tor.getChunkSize()); 0455 0456 Uint32 cdata = 0; 0457 // then the amount of data of the chunk which is located in this file 0458 if (tflist.count() == 1) 0459 cdata = c->getSize(); 0460 else if (i == 0) 0461 cdata = f.getLastChunkSize(); 0462 else if (i == tflist.count() - 1) 0463 cdata = c->getSize() - chunk_off; 0464 else 0465 cdata = f.getSize(); 0466 0467 // if the piece does not lie in this part of the chunk, move on 0468 if (off + length <= chunk_off || off >= chunk_off + cdata) { 0469 chunk_off += cdata; 0470 continue; 0471 } 0472 0473 Uint64 read_offset = 0; // The read offset in the file 0474 Uint32 read_length = 0; // how many bytes to read 0475 calculateOffsetAndLength(off, length, file_off, chunk_off, cdata, read_offset, read_length); 0476 0477 Uint8 *ptr = data + piece_off; // location to write to 0478 piece_off += read_length; 0479 0480 if (fd) { 0481 fd->read(ptr, read_length, read_offset); 0482 } else if (dfd) { 0483 Uint32 ret = 0; 0484 if (i == 0) 0485 ret = dfd->readLastChunk(ptr, read_offset - file_off, read_length); 0486 else 0487 ret = dfd->readFirstChunk(ptr, read_offset, read_length); 0488 0489 if (ret > 0 && ret != read_length) 0490 Out(SYS_DIO | LOG_DEBUG) << "Warning : MultiFileCache::loadPiece ret != to_read" << endl; 0491 } 0492 0493 chunk_off += cdata; 0494 } 0495 0496 return piece; 0497 } 0498 0499 void MultiFileCache::savePiece(PieceData::Ptr piece) 0500 { 0501 open(); 0502 0503 // in mapped mode unload the piece if not in use 0504 if (piece->mapped()) 0505 return; 0506 0507 Uint8 *data = piece->data(); 0508 if (!data) // this should not happen but just in case 0509 return; 0510 0511 Chunk *c = piece->parentChunk(); 0512 QList<Uint32> tflist; 0513 tor.calcChunkPos(c->getIndex(), tflist); 0514 Uint32 chunk_off = 0; // number of bytes passed of the chunk 0515 Uint32 piece_off = 0; // how many bytes written from the piece 0516 Uint32 off = piece->offset(); 0517 Uint32 length = piece->length(); 0518 0519 for (int i = 0; i < tflist.count(); i++) { 0520 const TorrentFile &f = tor.getFile(tflist[i]); 0521 CacheFile::Ptr fd = files[tflist[i]]; 0522 DNDFile::Ptr dfd = dnd_files[tflist[i]]; 0523 0524 // first calculate offset into file 0525 // only the first file can have an offset 0526 // the following files will start at the beginning 0527 Uint64 file_off = 0; 0528 if (i == 0) 0529 file_off = FileOffset(c, f, tor.getChunkSize()); 0530 0531 Uint32 cdata = 0; 0532 // then the amount of data of the chunk which is located in this file 0533 if (tflist.count() == 1) 0534 cdata = c->getSize(); 0535 else if (i == 0) 0536 cdata = f.getLastChunkSize(); 0537 else if (i == tflist.count() - 1) 0538 cdata = c->getSize() - chunk_off; 0539 else 0540 cdata = f.getSize(); 0541 0542 // if the piece does not lie in this part of the chunk, move on 0543 if (off + length <= chunk_off || off >= chunk_off + cdata) { 0544 chunk_off += cdata; 0545 continue; 0546 } 0547 0548 Uint64 write_offset = 0; // The write offset in the file 0549 Uint32 write_length = 0; // how many bytes to write 0550 calculateOffsetAndLength(off, length, file_off, chunk_off, cdata, write_offset, write_length); 0551 0552 Uint8 *ptr = data + piece_off; // location to read from 0553 piece_off += write_length; 0554 0555 if (fd) { 0556 fd->write(ptr, write_length, write_offset); 0557 } else if (dfd) { 0558 if (i == 0) 0559 dfd->writeLastChunk(ptr, write_offset - file_off, write_length); 0560 else 0561 dfd->writeFirstChunk(ptr, write_offset, write_length); 0562 } 0563 0564 chunk_off += cdata; 0565 } 0566 } 0567 0568 void MultiFileCache::downloadStatusChanged(TorrentFile *tf, bool download) 0569 { 0570 bool dnd = !download; 0571 QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); 0572 QString dnd_path = QString("file%1.dnd").arg(tf->getIndex()); 0573 QString dnd_file = dnd_dir + dnd_path; 0574 // if it is dnd and it is already in the dnd tree do nothing 0575 if (dnd && bt::Exists(dnd_dir + dnd_path)) 0576 return; 0577 else if (dnd && bt::Exists(dnd_dir + tf->getUserModifiedPath() + ".dnd")) { 0578 // old style dnd dir, move the file so that we can keep working 0579 // with the old file 0580 bt::Move(dnd_dir + tf->getUserModifiedPath() + ".dnd", dnd_file, true, true); 0581 return; 0582 } 0583 0584 // if it is !dnd and it is already in the output_dir tree do nothing 0585 if (!dnd && bt::Exists(tf->getPathOnDisk())) 0586 return; 0587 0588 try { 0589 if (dnd) { 0590 // save first and last chunk of the file 0591 if (bt::Exists(tf->getPathOnDisk())) 0592 saveFirstAndLastChunk(tf, tf->getPathOnDisk(), dnd_file); 0593 0594 // delete data file 0595 if (bt::Exists(tf->getPathOnDisk())) 0596 bt::Delete(tf->getPathOnDisk(), true); 0597 0598 files.remove(tf->getIndex()); 0599 DNDFile::Ptr dfd(new DNDFile(dnd_file, tf, tor.getChunkSize())); 0600 dfd->checkIntegrity(); 0601 dnd_files.insert(tf->getIndex(), dfd); 0602 } else { 0603 // recreate the file 0604 recreateFile(tf, dnd_dir + dnd_path, tf->getPathOnDisk()); 0605 bt::Delete(dnd_dir + dnd_path); 0606 dnd_files.remove(tf->getIndex()); 0607 CacheFile::Ptr fd(new CacheFile()); 0608 fd->open(tf->getPathOnDisk(), tf->getSize()); 0609 files.insert(tf->getIndex(), fd); 0610 } 0611 } catch (bt::Error &err) { 0612 Out(SYS_DIO | LOG_DEBUG) << err.toString() << endl; 0613 } 0614 } 0615 0616 void MultiFileCache::saveFirstAndLastChunk(TorrentFile *tf, const QString &src_file, const QString &dst_file) 0617 { 0618 DNDFile out(dst_file, tf, tor.getChunkSize()); 0619 File fptr; 0620 if (!fptr.open(src_file, "rb")) 0621 throw Error(i18n("Cannot open file %1: %2", src_file, fptr.errorString())); 0622 0623 Uint32 cs = (tf->getFirstChunk() == tor.getNumChunks() - 1) ? tor.getLastChunkSize() : tor.getChunkSize(); 0624 0625 Uint8 *tmp = new Uint8[tor.getChunkSize()]; 0626 try { 0627 fptr.read(tmp, cs - tf->getFirstChunkOffset()); 0628 out.writeFirstChunk(tmp, 0, cs - tf->getFirstChunkOffset()); 0629 0630 if (tf->getFirstChunk() != tf->getLastChunk()) { 0631 Uint64 off = FileOffset(tf->getLastChunk(), *tf, tor.getChunkSize()); 0632 fptr.seek(File::BEGIN, off); 0633 fptr.read(tmp, tf->getLastChunkSize()); 0634 out.writeLastChunk(tmp, 0, tf->getLastChunkSize()); 0635 } 0636 delete[] tmp; 0637 } catch (...) { 0638 delete[] tmp; 0639 throw; 0640 } 0641 } 0642 0643 void MultiFileCache::recreateFile(TorrentFile *tf, const QString &dnd_file, const QString &output_file) 0644 { 0645 DNDFile dnd(dnd_file, tf, tor.getChunkSize()); 0646 0647 // make sure path exists 0648 MakeFilePath(output_file); 0649 // create the output file 0650 bt::Touch(output_file); 0651 0652 Uint32 cs = (tf->getFirstChunk() == tor.getNumChunks() - 1) ? tor.getLastChunkSize() : tor.getChunkSize(); 0653 0654 File fptr; 0655 if (!fptr.open(output_file, "r+b")) 0656 throw Error(i18n("Cannot open file %1: %2", output_file, fptr.errorString())); 0657 0658 Uint32 ts = cs - tf->getFirstChunkOffset() > tf->getLastChunkSize() ? cs - tf->getFirstChunkOffset() : tf->getLastChunkSize(); 0659 Uint8 *tmp = new Uint8[ts]; 0660 0661 try { 0662 Uint32 to_read = cs - tf->getFirstChunkOffset(); 0663 if (to_read > tf->getSize()) // check for files which are smaller then a chunk 0664 to_read = tf->getSize(); 0665 0666 to_read = dnd.readFirstChunk(tmp, 0, to_read); 0667 if (to_read > 0) 0668 fptr.write(tmp, to_read); 0669 0670 if (tf->getFirstChunk() != tf->getLastChunk()) { 0671 Uint64 off = FileOffset(tf->getLastChunk(), *tf, tor.getChunkSize()); 0672 fptr.seek(File::BEGIN, off); 0673 to_read = dnd.readLastChunk(tmp, 0, tf->getLastChunkSize()); 0674 if (to_read > 0) 0675 fptr.write(tmp, to_read); 0676 } 0677 delete[] tmp; 0678 0679 } catch (...) { 0680 delete[] tmp; 0681 throw; 0682 } 0683 } 0684 0685 void MultiFileCache::preparePreallocation(PreallocationThread *prealloc) 0686 { 0687 QMap<Uint32, CacheFile::Ptr>::iterator i = files.begin(); 0688 while (i != files.end()) { 0689 CacheFile::Ptr cf = i.value(); 0690 if (cf) 0691 prealloc->add(cf); 0692 ++i; 0693 } 0694 } 0695 0696 bool MultiFileCache::hasMissingFiles(QStringList &sl) 0697 { 0698 bool ret = false; 0699 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0700 TorrentFile &tf = tor.getFile(i); 0701 if (tf.doNotDownload()) 0702 continue; 0703 0704 QString p = tf.getPathOnDisk(); 0705 if (!bt::Exists(p)) { 0706 ret = true; 0707 tf.setMissing(true); 0708 sl.append(p); 0709 } else 0710 tf.setMissing(false); 0711 } 0712 return ret; 0713 } 0714 0715 static void DeleteEmptyDirs(const QString &output_dir, const QString &fpath) 0716 { 0717 QStringList sl = fpath.split(bt::DirSeparator()); 0718 // remove the last, which is just the filename 0719 sl.pop_back(); 0720 0721 while (sl.count() > 0) { 0722 QString path = output_dir; 0723 // reassemble the full directory path 0724 for (const QString &s : sl) 0725 path += s + bt::DirSeparator(); 0726 0727 QDir dir(path); 0728 QStringList el = dir.entryList(QDir::AllEntries | QDir::System | QDir::Hidden); 0729 el.removeAll("."); 0730 el.removeAll(".."); 0731 if (el.count() == 0 && dir.exists()) { 0732 // no childern so delete the directory 0733 Out(SYS_GEN | LOG_DEBUG) << "Deleting empty directory : " << path << endl; 0734 bt::Delete(path, true); 0735 sl.pop_back(); // remove the last so we can go one higher 0736 } else { 0737 // children, so we cannot delete any more directories higher up 0738 return; 0739 } 0740 } 0741 0742 // now the output_dir itself 0743 QDir dir(output_dir); 0744 QStringList el = dir.entryList(QDir::AllEntries | QDir::System | QDir::Hidden); 0745 el.removeAll("."); 0746 el.removeAll(".."); 0747 if (el.count() == 0 && dir.exists()) { 0748 Out(SYS_GEN | LOG_DEBUG) << "Deleting empty directory : " << output_dir << endl; 0749 bt::Delete(output_dir, true); 0750 } 0751 } 0752 0753 Job *MultiFileCache::deleteDataFiles() 0754 { 0755 DeleteDataFilesJob *job = new DeleteDataFilesJob(output_dir); 0756 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0757 TorrentFile &tf = tor.getFile(i); 0758 QString fpath = tf.getPathOnDisk(); 0759 if (!tf.doNotDownload()) { 0760 // first delete the file 0761 job->addFile(fpath); 0762 } 0763 0764 // check for subdirectories 0765 job->addEmptyDirectoryCheck(tf.getUserModifiedPath()); 0766 } 0767 0768 return job; 0769 } 0770 0771 Uint64 MultiFileCache::diskUsage() 0772 { 0773 Uint64 sum = 0; 0774 0775 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0776 TorrentFile &tf = tor.getFile(i); 0777 if (tf.doNotDownload()) 0778 continue; 0779 0780 try { 0781 CacheFile::Ptr cf = files[i]; 0782 if (cf) { 0783 sum += cf->diskUsage(); 0784 } else if (bt::Exists(tf.getPathOnDisk())) { 0785 // doesn't exist yet, must be before open is called 0786 // so create one and delete it right after 0787 CacheFile::Ptr cf(new CacheFile()); 0788 cf->open(tf.getPathOnDisk(), tf.getSize()); 0789 sum += cf->diskUsage(); 0790 } 0791 } catch (bt::Error &err) { // make sure we catch any exceptions 0792 Out(SYS_DIO | LOG_DEBUG) << "Error: " << err.toString() << endl; 0793 } 0794 } 0795 0796 return sum; 0797 } 0798 0799 bool MultiFileCache::getMountPoints(QSet<QString> &mps) 0800 { 0801 for (Uint32 i = 0; i < tor.getNumFiles(); i++) { 0802 TorrentFile &tf = tor.getFile(i); 0803 if (tf.doNotDownload()) 0804 continue; 0805 0806 QString mp = tf.getMountPoint(); 0807 if (mp.isEmpty()) 0808 return false; 0809 0810 mps.insert(mp); 0811 } 0812 0813 return true; 0814 } 0815 0816 /////////////////////////////// 0817 0818 Uint64 FileOffset(Chunk *c, const TorrentFile &f, Uint64 chunk_size) 0819 { 0820 return FileOffset(c->getIndex(), f, chunk_size); 0821 } 0822 0823 Uint64 FileOffset(Uint32 cindex, const TorrentFile &f, Uint64 chunk_size) 0824 { 0825 return f.fileOffset(cindex, chunk_size); 0826 } 0827 0828 }