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 }