File indexing completed on 2024-11-24 04:31:16

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "singlefilecache.h"
0007 #include <QTextStream>
0008 #include <kio/copyjob.h>
0009 #include <kio/jobuidelegate.h>
0010 #include <klocalizedstring.h>
0011 #include <qfileinfo.h>
0012 #include <qstringlist.h>
0013 #include <util/error.h>
0014 #include <util/fileops.h>
0015 #include <util/functions.h>
0016 #include <util/log.h>
0017 #ifdef Q_WS_WIN
0018 #include <util/win32.h>
0019 #endif
0020 #include "cachefile.h"
0021 #include "chunk.h"
0022 #include "deletedatafilesjob.h"
0023 #include "movedatafilesjob.h"
0024 #include "piecedata.h"
0025 #include "preallocationthread.h"
0026 #include <torrent/torrent.h>
0027 
0028 namespace bt
0029 {
0030 SingleFileCache::SingleFileCache(Torrent &tor, const QString &tmpdir, const QString &datadir)
0031     : Cache(tor, tmpdir, datadir)
0032     , fd(nullptr)
0033 {
0034     cache_file = tmpdir + "cache";
0035     QFileInfo fi(cache_file);
0036     if (fi.isSymLink()) // old style symlink
0037         output_file = fi.symLinkTarget();
0038     else
0039         output_file = datadir + tor.getNameSuggestion();
0040 }
0041 
0042 SingleFileCache::~SingleFileCache()
0043 {
0044     cleanupPieceCache();
0045 }
0046 
0047 void SingleFileCache::loadFileMap()
0048 {
0049     QString file_map = tmpdir + "file_map";
0050     if (!bt::Exists(file_map)) {
0051         saveFileMap();
0052         return;
0053     }
0054 
0055     QFile fptr(file_map);
0056     if (!fptr.open(QIODevice::ReadOnly))
0057         throw Error(i18n("Failed to open %1: %2", file_map, fptr.errorString()));
0058 
0059     output_file = QString::fromLocal8Bit(fptr.readLine().trimmed());
0060 }
0061 
0062 void SingleFileCache::saveFileMap()
0063 {
0064     QString file_map = tmpdir + "file_map";
0065     QFile fptr(file_map);
0066     if (!fptr.open(QIODevice::WriteOnly))
0067         throw Error(i18n("Failed to create %1: %2", file_map, fptr.errorString()));
0068 
0069     QTextStream out(&fptr);
0070     out << output_file << Qt::endl;
0071 }
0072 
0073 void SingleFileCache::changeTmpDir(const QString &ndir)
0074 {
0075     Cache::changeTmpDir(ndir);
0076     cache_file = tmpdir + "cache";
0077 }
0078 
0079 void SingleFileCache::changeOutputPath(const QString &outputpath)
0080 {
0081     close();
0082     output_file = outputpath;
0083     datadir = output_file.left(output_file.lastIndexOf(bt::DirSeparator()));
0084     saveFileMap();
0085 }
0086 
0087 Job *SingleFileCache::moveDataFiles(const QString &ndir)
0088 {
0089     QString dst = ndir;
0090     if (!dst.endsWith(bt::DirSeparator()))
0091         dst += bt::DirSeparator();
0092 
0093     dst += output_file.mid(output_file.lastIndexOf(bt::DirSeparator()) + 1);
0094     if (output_file == dst)
0095         return nullptr;
0096 
0097     move_data_files_dst = dst;
0098     MoveDataFilesJob *job = new MoveDataFilesJob();
0099     job->addMove(output_file, dst);
0100     return job;
0101 }
0102 
0103 void SingleFileCache::moveDataFilesFinished(Job *job)
0104 {
0105     if (job->error() == KIO::ERR_USER_CANCELED) {
0106         if (bt::Exists(move_data_files_dst))
0107             bt::Delete(move_data_files_dst, true);
0108     }
0109     move_data_files_dst = QString();
0110 }
0111 
0112 PieceData::Ptr SingleFileCache::createPiece(Chunk *c, Uint64 off, Uint32 length, bool read_only)
0113 {
0114     if (!fd)
0115         open();
0116 
0117     Uint64 piece_off = c->getIndex() * tor.getChunkSize() + off;
0118     Uint8 *buf = nullptr;
0119     if (mmap_failures >= 3) {
0120         buf = new Uint8[length];
0121         PieceData::Ptr cp(new PieceData(c, off, length, buf, CacheFile::Ptr(), read_only));
0122         insertPiece(c, cp);
0123         return cp;
0124     } else {
0125         PieceData::Ptr cp(new PieceData(c, off, length, nullptr, fd, read_only));
0126         buf = (Uint8 *)fd->map(cp.data(), piece_off, length, read_only ? CacheFile::READ : CacheFile::RW);
0127         if (buf) {
0128             cp->setData(buf);
0129         } else {
0130             if (mmap_failures < 3)
0131                 mmap_failures++;
0132 
0133             buf = new Uint8[length];
0134             cp = PieceData::Ptr(new PieceData(c, off, length, buf, CacheFile::Ptr(), read_only));
0135         }
0136         insertPiece(c, cp);
0137         return cp;
0138     }
0139 }
0140 
0141 PieceData::Ptr SingleFileCache::loadPiece(Chunk *c, Uint32 off, Uint32 length)
0142 {
0143     PieceData::Ptr cp = findPiece(c, off, length, true);
0144     if (cp)
0145         return cp;
0146 
0147     cp = createPiece(c, off, length, true);
0148     if (cp && !cp->mapped()) {
0149         // read data from file if piece isn't mapped
0150         Uint64 piece_off = c->getIndex() * tor.getChunkSize() + off;
0151         fd->read(cp->data(), length, piece_off);
0152     }
0153 
0154     return cp;
0155 }
0156 
0157 PieceData::Ptr SingleFileCache::preparePiece(Chunk *c, Uint32 off, Uint32 length)
0158 {
0159     PieceData::Ptr cp = findPiece(c, off, length, false);
0160     if (cp)
0161         return cp;
0162 
0163     return createPiece(c, off, length, false);
0164 }
0165 
0166 void SingleFileCache::savePiece(PieceData::Ptr piece)
0167 {
0168     if (!fd)
0169         open();
0170 
0171     // mapped pieces will be unmapped when they are destroyed, buffered ones need to be written
0172     if (!piece->mapped()) {
0173         Uint64 off = piece->parentChunk()->getIndex() * tor.getChunkSize() + piece->offset();
0174         if (piece->ok())
0175             fd->write(piece->data(), piece->length(), off);
0176     }
0177 }
0178 
0179 void SingleFileCache::create()
0180 {
0181     // check for a to long path name
0182     if (FileNameToLong(output_file))
0183         output_file = ShortenFileName(output_file);
0184 
0185     if (!bt::Exists(output_file)) {
0186         MakeFilePath(output_file);
0187         bt::Touch(output_file);
0188     } else
0189         preexisting_files = true;
0190 
0191     QSet<QString> mps;
0192     mps.insert(MountPoint(output_file));
0193     saveMountPoints(mps);
0194     saveFileMap();
0195 }
0196 
0197 bool SingleFileCache::getMountPoints(QSet<QString> &mps)
0198 {
0199     QString mp = MountPoint(output_file);
0200     if (mp.isEmpty())
0201         return false;
0202 
0203     mps.insert(mp);
0204     return true;
0205 }
0206 
0207 void SingleFileCache::close()
0208 {
0209     clearPieceCache();
0210     if (fd && piece_cache.isEmpty()) {
0211         fd.clear();
0212     }
0213 }
0214 
0215 void SingleFileCache::open()
0216 {
0217     if (fd)
0218         return;
0219 
0220     CacheFile::Ptr tmp(new CacheFile());
0221     tmp->open(output_file, tor.getTotalSize());
0222     fd = tmp;
0223 }
0224 
0225 void SingleFileCache::preparePreallocation(PreallocationThread *prealloc)
0226 {
0227     if (!fd)
0228         open();
0229 
0230     prealloc->add(fd);
0231 }
0232 
0233 bool SingleFileCache::hasMissingFiles(QStringList &sl)
0234 {
0235     if (!bt::Exists(output_file)) {
0236         sl.append(output_file);
0237         return true;
0238     }
0239     return false;
0240 }
0241 
0242 Job *SingleFileCache::deleteDataFiles()
0243 {
0244     DeleteDataFilesJob *job = new DeleteDataFilesJob("");
0245     job->addFile(output_file);
0246     return job;
0247 }
0248 
0249 Uint64 SingleFileCache::diskUsage()
0250 {
0251     if (!fd)
0252         open();
0253 
0254     return fd->diskUsage();
0255 }
0256 }