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"