File indexing completed on 2024-05-19 05:00:08

0001 /* This file is part of the KDE project
0002 
0003    Copyright (C) 2006 Manolo Valdes <nolis71cu@gmail.com>
0004    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 */
0011 
0012 #include "segment.h"
0013 #include "multisegkiosettings.h"
0014 
0015 #include <cmath>
0016 
0017 #include "kget_debug.h"
0018 
0019 #include <KIO/Job>
0020 #include <KIO/TransferJob>
0021 #include <KLocalizedString>
0022 
0023 #include <QDebug>
0024 #include <QTimer>
0025 
0026 Segment::Segment(const QUrl &src, const QPair<KIO::fileoffset_t, KIO::fileoffset_t> &segmentSize, const QPair<int, int> &segmentRange, QObject *parent)
0027     : QObject(parent)
0028     , m_findFilesize((segmentRange.first == -1) && (segmentRange.second == -1))
0029     , m_canResume(true)
0030     , m_status(Stopped)
0031     , m_currentSegment(segmentRange.first)
0032     , m_endSegment(segmentRange.second)
0033     , m_errorCount(0)
0034     , m_offset(segmentSize.first * segmentRange.first)
0035     , m_currentSegSize(segmentSize.first)
0036     , m_bytesWritten(0)
0037     , m_getJob(nullptr)
0038     , m_url(src)
0039     , m_segSize(segmentSize)
0040 {
0041     // last segment
0042     if (m_endSegment - m_currentSegment == 0) {
0043         m_currentSegSize = m_segSize.second;
0044     }
0045 
0046     if (m_findFilesize) {
0047         m_offset = 0;
0048         m_currentSegSize = 0;
0049         m_currentSegment = 0;
0050         m_endSegment = 0;
0051         m_totalBytesLeft = 0;
0052     } else {
0053         m_totalBytesLeft = m_segSize.first * (m_endSegment - m_currentSegment) + m_segSize.second;
0054     }
0055 }
0056 
0057 Segment::~Segment()
0058 {
0059     if (m_getJob) {
0060         qCDebug(KGET_DEBUG) << "Closing transfer ...";
0061         m_getJob->kill(KJob::Quietly);
0062     }
0063 }
0064 
0065 bool Segment::findingFileSize() const
0066 {
0067     return m_findFilesize;
0068 }
0069 
0070 bool Segment::createTransfer()
0071 {
0072     qCDebug(KGET_DEBUG) << " -- " << m_url;
0073     if (m_getJob)
0074         return false;
0075 
0076     m_getJob = KIO::get(m_url, KIO::Reload, KIO::HideProgressInfo);
0077     m_getJob->suspend();
0078     m_getJob->addMetaData("errorPage", "false");
0079     m_getJob->addMetaData("AllowCompressedPage", "false");
0080     if (m_offset) {
0081         m_canResume = false; // FIXME set m_canResume to false by default!!
0082         m_getJob->addMetaData("resume", KIO::number(m_offset));
0083         connect(m_getJob, &KIO::TransferJob::canResume, this, &Segment::slotCanResume);
0084     }
0085 #if 0 // TODO: we disable that code till it's implemented in kdelibs, also we need to think, which settings we should use
0086     if (Settings::speedLimit())
0087     {
0088         m_getJob->addMetaData( "speed-limit", KIO::number(Settings::transferSpeedLimit() * 1024) );
0089     }
0090 #endif
0091     connect(m_getJob, &KJob::totalSize, this, &Segment::slotTotalSize);
0092     connect(m_getJob, &KIO::TransferJob::data, this, &Segment::slotData);
0093     connect(m_getJob, &KJob::result, this, &Segment::slotResult);
0094     connect(m_getJob, &KIO::TransferJob::redirection, this, &Segment::slotRedirection);
0095     return true;
0096 }
0097 
0098 void Segment::slotRedirection(KIO::Job *, const QUrl &url)
0099 {
0100     m_url = url;
0101     Q_EMIT urlChanged(url);
0102 }
0103 
0104 void Segment::slotCanResume(KIO::Job *job, KIO::filesize_t offset)
0105 {
0106     Q_UNUSED(job)
0107     Q_UNUSED(offset)
0108     qCDebug(KGET_DEBUG);
0109     m_canResume = true;
0110     Q_EMIT canResume();
0111 }
0112 
0113 void Segment::slotTotalSize(KJob *job, qulonglong size)
0114 {
0115     Q_UNUSED(job)
0116     qCDebug(KGET_DEBUG) << "Size found for" << m_url;
0117 
0118     if (m_findFilesize) {
0119         int numSegments = size / m_segSize.first;
0120         KIO::fileoffset_t rest = size % m_segSize.first;
0121         if (rest) {
0122             ++numSegments;
0123             m_segSize.second = rest;
0124         }
0125 
0126         m_endSegment = numSegments - 1;
0127 
0128         m_currentSegment = 0;
0129         m_currentSegSize = (numSegments == 1 ? m_segSize.second : m_segSize.first);
0130         m_totalBytesLeft = size;
0131 
0132         Q_EMIT totalSize(size, qMakePair(m_currentSegment, m_endSegment));
0133         m_findFilesize = false;
0134     } else {
0135         Q_EMIT totalSize(size, qMakePair(-1, -1));
0136     }
0137 }
0138 
0139 bool Segment::startTransfer()
0140 {
0141     qCDebug(KGET_DEBUG) << m_url;
0142     if (!m_getJob) {
0143         createTransfer();
0144     }
0145     if (m_getJob && (m_status != Running)) {
0146         setStatus(Running, false);
0147         m_getJob->resume();
0148         return true;
0149     }
0150     return false;
0151 }
0152 
0153 bool Segment::stopTransfer()
0154 {
0155     qCDebug(KGET_DEBUG);
0156 
0157     setStatus(Stopped, false);
0158     if (m_getJob) {
0159         if (m_getJob) {
0160             m_getJob->kill(KJob::EmitResult);
0161         }
0162         return true;
0163     }
0164     return false;
0165 }
0166 
0167 void Segment::slotResult(KJob *job)
0168 {
0169     qCDebug(KGET_DEBUG) << "Job:" << job << m_url << "error:" << job->error();
0170 
0171     m_getJob = nullptr;
0172 
0173     // clear the buffer as the download might be moved around
0174     if (m_status == Stopped) {
0175         m_buffer.clear();
0176     }
0177     if (!m_buffer.isEmpty()) {
0178         if (m_findFilesize && !job->error()) {
0179             qCDebug(KGET_DEBUG) << "Looping until write the buffer ..." << m_url;
0180             slotWriteRest();
0181             return;
0182         }
0183     }
0184     if (!m_totalBytesLeft && !m_findFilesize) {
0185         setStatus(Finished);
0186         return;
0187     }
0188     if (m_status == Killed) {
0189         return;
0190     }
0191     if (job->error() && (m_status == Running)) {
0192         Q_EMIT error(this, job->errorString(), Transfer::Log_Error);
0193     }
0194 }
0195 
0196 void Segment::slotData(KIO::Job *, const QByteArray &_data)
0197 {
0198     // Check if the transfer allows resuming...
0199     if (m_offset && !m_canResume) {
0200         qCDebug(KGET_DEBUG) << m_url << "does not allow resuming.";
0201         stopTransfer();
0202         setStatus(Killed, false);
0203         const QString errorText = KIO::buildErrorString(KIO::ERR_CANNOT_RESUME, m_url.toString());
0204         Q_EMIT error(this, errorText, Transfer::Log_Warning);
0205         return;
0206     }
0207 
0208     m_buffer.append(_data);
0209     if (!m_findFilesize && m_totalBytesLeft && static_cast<uint>(m_buffer.size()) >= m_totalBytesLeft) {
0210         qCDebug(KGET_DEBUG) << "Segment::slotData() buffer full. Stopping transfer..."; // TODO really stop it? is this even needed?
0211         if (m_getJob) {
0212             m_getJob->kill(KJob::Quietly);
0213             m_getJob = nullptr;
0214         }
0215         m_buffer.truncate(m_totalBytesLeft);
0216         slotWriteRest();
0217     } else {
0218         /*
0219          write to the local file only if the buffer has more than 100kbytes
0220          this hack try to avoid too much cpu usage. it seems to be due KIO::Filejob
0221          so remove it when it works property
0222         */
0223         if (m_buffer.size() > MultiSegKioSettings::saveSegSize() * 1024)
0224             writeBuffer();
0225     }
0226 }
0227 
0228 bool Segment::writeBuffer()
0229 {
0230     qCDebug(KGET_DEBUG) << "Segment::writeBuffer() sending:" << m_buffer.size() << "from job:" << m_getJob;
0231     if (m_buffer.isEmpty()) {
0232         return false;
0233     }
0234 
0235     bool worked = false;
0236     Q_EMIT data(m_offset, m_buffer, worked);
0237 
0238     if (worked) {
0239         m_currentSegSize -= m_buffer.size();
0240         if (!m_findFilesize) {
0241             m_totalBytesLeft -= m_buffer.size();
0242         }
0243         m_offset += m_buffer.size();
0244         m_bytesWritten += m_buffer.size();
0245         m_buffer.clear();
0246         qCDebug(KGET_DEBUG) << "Segment::writeBuffer() updating segment record of job:" << m_getJob << "--" << m_totalBytesLeft << "bytes left";
0247     }
0248 
0249     // finding filesize, so no segments defined yet
0250     if (m_findFilesize) {
0251         return worked;
0252     }
0253 
0254     // check which segments have been finished
0255     bool finished = false;
0256     // m_currentSegSize being smaller than 1 means that at least one segment has been finished
0257     while (m_currentSegSize <= 0 && !finished) {
0258         finished = (m_currentSegment == m_endSegment);
0259         Q_EMIT finishedSegment(this, m_currentSegment, finished);
0260 
0261         if (!finished) {
0262             ++m_currentSegment;
0263             m_currentSegSize += (m_currentSegment == m_endSegment ? m_segSize.second : m_segSize.first);
0264         }
0265     }
0266 
0267     return worked;
0268 }
0269 
0270 void Segment::slotWriteRest()
0271 {
0272     if (m_buffer.isEmpty()) {
0273         return;
0274     }
0275     qCDebug(KGET_DEBUG) << this;
0276 
0277     if (writeBuffer()) {
0278         m_errorCount = 0;
0279         if (m_findFilesize) {
0280             Q_EMIT finishedDownload(m_bytesWritten);
0281         }
0282         return;
0283     }
0284 
0285     if (++m_errorCount >= 100) {
0286         qWarning() << "Failed to write to the file:" << m_url << this;
0287         Q_EMIT error(this, i18n("Failed to write to the file."), Transfer::Log_Error);
0288     } else {
0289         qCDebug(KGET_DEBUG) << "Wait 50 msec:" << this;
0290         QTimer::singleShot(50, this, &Segment::slotWriteRest);
0291     }
0292 }
0293 
0294 void Segment::setStatus(Status stat, bool doEmit)
0295 {
0296     m_status = stat;
0297     if (doEmit)
0298         Q_EMIT statusChanged(this);
0299 }
0300 
0301 QPair<int, int> Segment::assignedSegments() const
0302 {
0303     return QPair<int, int>(m_currentSegment, m_endSegment);
0304 }
0305 
0306 QPair<KIO::fileoffset_t, KIO::fileoffset_t> Segment::segmentSize() const
0307 {
0308     return m_segSize;
0309 }
0310 
0311 int Segment::countUnfinishedSegments() const
0312 {
0313     return m_endSegment - m_currentSegment;
0314 }
0315 
0316 QPair<int, int> Segment::split()
0317 {
0318     if (m_getJob) {
0319         m_getJob->suspend();
0320     }
0321 
0322     QPair<int, int> freed = QPair<int, int>(-1, -1);
0323     const int free = std::ceil((countUnfinishedSegments() + 1) / static_cast<double>(2));
0324 
0325     if (!free) {
0326         qCDebug(KGET_DEBUG) << "None freed, start:" << m_currentSegment << "end:" << m_endSegment;
0327 
0328         if (m_getJob) {
0329             m_getJob->resume();
0330         }
0331         return freed;
0332     }
0333 
0334     const int newEnd = m_endSegment - free;
0335     freed = QPair<int, int>(newEnd + 1, m_endSegment);
0336     qCDebug(KGET_DEBUG) << "Start:" << m_currentSegment << "old end:" << m_endSegment << "new end:" << newEnd << "freed:" << freed;
0337     m_endSegment = newEnd;
0338     m_totalBytesLeft -= m_segSize.first * (free - 1) + m_segSize.second;
0339 
0340     // end changed, so in any case the lastSegSize should be the normal segSize
0341     if (free) {
0342         m_segSize.second = m_segSize.first;
0343     }
0344 
0345     if (m_getJob) {
0346         m_getJob->resume();
0347     }
0348     return freed;
0349 }
0350 
0351 bool Segment::merge(const QPair<KIO::fileoffset_t, KIO::fileoffset_t> &segmentSize, const QPair<int, int> &segmentRange)
0352 {
0353     if (m_endSegment + 1 == segmentRange.first) {
0354         m_endSegment = segmentRange.second;
0355         m_segSize.second = segmentSize.second;
0356         m_totalBytesLeft += segmentSize.first * (m_endSegment - segmentRange.first) + m_segSize.second;
0357         return true;
0358     }
0359 
0360     return false;
0361 }
0362 
0363 #include "moc_segment.cpp"