1 /* This file is part of the KDE project
2 
3    Copyright (C) 2006 Manolo Valdes <nolis71cu@gmail.com>
4    Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 */
11 
12 #include "segment.h"
13 #include "multisegkiosettings.h"
14 
15 #include <cmath>
16 
17 #include "kget_debug.h"
18 #include <QDebug>
19 #include <KLocalizedString>
20 
21 #include <QTimer>
22 
Segment(const QUrl & src,const QPair<KIO::fileoffset_t,KIO::fileoffset_t> & segmentSize,const QPair<int,int> & segmentRange,QObject * parent)23 Segment::Segment(const QUrl &src, const QPair<KIO::fileoffset_t, KIO::fileoffset_t> &segmentSize, const QPair<int, int> &segmentRange, QObject *parent)
24   : QObject(parent),
25     m_findFilesize((segmentRange.first == -1) && (segmentRange.second == -1)),
26     m_canResume(true),
27     m_status(Stopped),
28     m_currentSegment(segmentRange.first),
29     m_endSegment(segmentRange.second),
30     m_errorCount(0),
31     m_offset(segmentSize.first * segmentRange.first),
32     m_currentSegSize(segmentSize.first),
33     m_bytesWritten(0),
34     m_getJob(nullptr),
35     m_url(src),
36     m_segSize(segmentSize)
37 {
38     //last segment
39     if (m_endSegment - m_currentSegment == 0) {
40         m_currentSegSize = m_segSize.second;
41     }
42 
43     if (m_findFilesize) {
44         m_offset = 0;
45         m_currentSegSize = 0;
46         m_currentSegment = 0;
47         m_endSegment = 0;
48         m_totalBytesLeft = 0;
49     } else {
50         m_totalBytesLeft = m_segSize.first * (m_endSegment - m_currentSegment) + m_segSize.second;
51     }
52 }
53 
~Segment()54 Segment::~Segment()
55 {
56     if (m_getJob)
57     {
58         qCDebug(KGET_DEBUG) << "Closing transfer ...";
59         m_getJob->kill(KJob::Quietly);
60     }
61 }
62 
findingFileSize() const63 bool Segment::findingFileSize() const
64 {
65     return m_findFilesize;
66 }
67 
createTransfer()68 bool Segment::createTransfer()
69 {
70     qCDebug(KGET_DEBUG) << " -- " << m_url;
71     if ( m_getJob )
72         return false;
73 
74     m_getJob = KIO::get(m_url, KIO::Reload, KIO::HideProgressInfo);
75     m_getJob->suspend();
76     m_getJob->addMetaData( "errorPage", "false" );
77     m_getJob->addMetaData( "AllowCompressedPage", "false" );
78     if (m_offset)
79     {
80         m_canResume = false;//FIXME set m_canResume to false by default!!
81         m_getJob->addMetaData( "resume", KIO::number(m_offset) );
82         connect(m_getJob, &KIO::TransferJob::canResume,
83                  this, &Segment::slotCanResume);
84     }
85     #if 0 //TODO: we disable that code till it's implemented in kdelibs, also we need to think, which settings we should use
86     if (Settings::speedLimit())
87     {
88         m_getJob->addMetaData( "speed-limit", KIO::number(Settings::transferSpeedLimit() * 1024) );
89     }
90     #endif
91     connect(m_getJob, &KJob::totalSize, this, &Segment::slotTotalSize);
92     connect(m_getJob, &KIO::TransferJob::data,
93                  this, &Segment::slotData);
94     connect(m_getJob, &KJob::result, this, &Segment::slotResult);
95     connect(m_getJob, &KIO::TransferJob::redirection, this, &Segment::slotRedirection);
96     return true;
97 }
98 
slotRedirection(KIO::Job *,const QUrl & url)99 void Segment::slotRedirection(KIO::Job* , const QUrl &url)
100 {
101     m_url = url;
102     Q_EMIT urlChanged(url);
103 }
104 
slotCanResume(KIO::Job * job,KIO::filesize_t offset)105 void Segment::slotCanResume( KIO::Job* job, KIO::filesize_t offset )
106 {
107     Q_UNUSED(job)
108     Q_UNUSED(offset)
109     qCDebug(KGET_DEBUG);
110     m_canResume = true;
111     Q_EMIT canResume();
112 }
113 
slotTotalSize(KJob * job,qulonglong size)114 void Segment::slotTotalSize(KJob *job, qulonglong size)
115 {
116     Q_UNUSED(job)
117     qCDebug(KGET_DEBUG) << "Size found for" << m_url;
118 
119     if (m_findFilesize) {
120         int numSegments = size / m_segSize.first;
121         KIO::fileoffset_t rest = size % m_segSize.first;
122         if (rest) {
123             ++numSegments;
124             m_segSize.second = rest;
125         }
126 
127         m_endSegment = numSegments - 1;
128 
129         m_currentSegment = 0;
130         m_currentSegSize = (numSegments == 1 ? m_segSize.second : m_segSize.first);
131         m_totalBytesLeft = size;
132 
133         Q_EMIT totalSize(size, qMakePair(m_currentSegment, m_endSegment));
134         m_findFilesize = false;
135     } else {
136         Q_EMIT totalSize(size, qMakePair(-1, -1));
137     }
138 }
139 
startTransfer()140 bool Segment::startTransfer ()
141 {
142     qCDebug(KGET_DEBUG) << m_url;
143     if (!m_getJob) {
144         createTransfer();
145     }
146     if (m_getJob && (m_status != Running)) {
147         setStatus(Running, false);
148         m_getJob->resume();
149         return true;
150     }
151     return false;
152 }
153 
stopTransfer()154 bool Segment::stopTransfer()
155 {
156     qCDebug(KGET_DEBUG);
157 
158     setStatus(Stopped, false);
159     if (m_getJob) {
160         if (m_getJob) {
161             m_getJob->kill(KJob::EmitResult);
162         }
163         return true;
164     }
165     return false;
166 }
167 
slotResult(KJob * job)168 void Segment::slotResult( KJob *job )
169 {
170     qCDebug(KGET_DEBUG) << "Job:" << job << m_url << "error:" << job->error();
171 
172     m_getJob = nullptr;
173 
174     //clear the buffer as the download might be moved around
175     if (m_status == Stopped)
176     {
177         m_buffer.clear();
178     }
179     if ( !m_buffer.isEmpty() )
180     {
181         if (m_findFilesize && !job->error()) {
182             qCDebug(KGET_DEBUG) << "Looping until write the buffer ..." << m_url;
183             slotWriteRest();
184             return;
185         }
186     }
187     if (!m_totalBytesLeft  && !m_findFilesize)
188     {
189         setStatus(Finished);
190         return;
191     }
192     if( m_status == Killed )
193     {
194         return;
195     }
196     if (job->error() && (m_status == Running)) {
197         Q_EMIT error(this, job->errorString(), Transfer::Log_Error);
198     }
199 }
200 
slotData(KIO::Job *,const QByteArray & _data)201 void Segment::slotData(KIO::Job *, const QByteArray& _data)
202 {
203     // Check if the transfer allows resuming...
204     if (m_offset && !m_canResume)
205     {
206         qCDebug(KGET_DEBUG) << m_url << "does not allow resuming.";
207         stopTransfer();
208         setStatus(Killed, false );
209         const QString errorText = KIO::buildErrorString(KIO::ERR_CANNOT_RESUME, m_url.toString());
210         Q_EMIT error(this, errorText, Transfer::Log_Warning);
211         return;
212     }
213 
214     m_buffer.append(_data);
215     if (!m_findFilesize && m_totalBytesLeft && static_cast<uint>(m_buffer.size()) >= m_totalBytesLeft)
216     {
217         qCDebug(KGET_DEBUG) << "Segment::slotData() buffer full. Stopping transfer...";//TODO really stop it? is this even needed?
218         if (m_getJob) {
219             m_getJob->kill(KJob::Quietly);
220             m_getJob = nullptr;
221         }
222         m_buffer.truncate(m_totalBytesLeft);
223         slotWriteRest();
224     }
225     else
226     {
227     /*
228      write to the local file only if the buffer has more than 100kbytes
229      this hack try to avoid too much cpu usage. it seems to be due KIO::Filejob
230      so remove it when it works property
231     */
232     if (m_buffer.size() > MultiSegKioSettings::saveSegSize() * 1024)
233         writeBuffer();
234     }
235 }
236 
writeBuffer()237 bool Segment::writeBuffer()
238 {
239     qCDebug(KGET_DEBUG) << "Segment::writeBuffer() sending:" << m_buffer.size() << "from job:" << m_getJob;
240     if (m_buffer.isEmpty()) {
241         return false;
242     }
243 
244     bool worked = false;
245     Q_EMIT data(m_offset, m_buffer, worked);
246 
247     if (worked) {
248         m_currentSegSize -= m_buffer.size();
249         if (!m_findFilesize) {
250             m_totalBytesLeft -= m_buffer.size();
251         }
252         m_offset += m_buffer.size();
253         m_bytesWritten += m_buffer.size();
254         m_buffer.clear();
255         qCDebug(KGET_DEBUG) << "Segment::writeBuffer() updating segment record of job:" << m_getJob << "--" << m_totalBytesLeft << "bytes left";
256     }
257 
258     //finding filesize, so no segments defined yet
259     if (m_findFilesize) {
260         return worked;
261     }
262 
263     //check which segments have been finished
264     bool finished = false;
265     //m_currentSegSize being smaller than 1 means that at least one segment has been finished
266     while (m_currentSegSize <= 0 && !finished) {
267         finished = (m_currentSegment == m_endSegment);
268         Q_EMIT finishedSegment(this, m_currentSegment, finished);
269 
270         if (!finished) {
271             ++m_currentSegment;
272             m_currentSegSize += (m_currentSegment == m_endSegment ? m_segSize.second : m_segSize.first);
273         }
274     }
275 
276     return worked;
277 }
278 
slotWriteRest()279 void Segment::slotWriteRest()
280 {
281     if (m_buffer.isEmpty()) {
282         return;
283     }
284     qCDebug(KGET_DEBUG) << this;
285 
286     if (writeBuffer()) {
287         m_errorCount = 0;
288         if (m_findFilesize) {
289             Q_EMIT finishedDownload(m_bytesWritten);
290         }
291         return;
292     }
293 
294     if (++m_errorCount >= 100) {
295         qWarning() << "Failed to write to the file:" << m_url << this;
296         Q_EMIT error(this, i18n("Failed to write to the file."), Transfer::Log_Error);
297     } else {
298         qCDebug(KGET_DEBUG) << "Wait 50 msec:" << this;
299         QTimer::singleShot(50, this, &Segment::slotWriteRest);
300     }
301 }
302 
setStatus(Status stat,bool doEmit)303 void Segment::setStatus(Status stat, bool doEmit)
304 {
305     m_status = stat;
306     if (doEmit)
307         Q_EMIT statusChanged(this);
308 }
309 
assignedSegments() const310 QPair<int, int> Segment::assignedSegments() const
311 {
312     return QPair<int, int>(m_currentSegment, m_endSegment);
313 }
314 
segmentSize() const315 QPair<KIO::fileoffset_t, KIO::fileoffset_t> Segment::segmentSize() const
316 {
317     return m_segSize;
318 }
319 
countUnfinishedSegments() const320 int Segment::countUnfinishedSegments() const
321 {
322     return m_endSegment - m_currentSegment;
323 }
324 
split()325 QPair<int, int> Segment::split()
326 {
327     if (m_getJob)
328     {
329         m_getJob->suspend();
330     }
331 
332     QPair<int, int> freed = QPair<int, int>(-1, -1);
333     const int free = std::ceil((countUnfinishedSegments() + 1) / static_cast<double>(2));
334 
335     if (!free)
336     {
337         qCDebug(KGET_DEBUG) << "None freed, start:" << m_currentSegment << "end:" << m_endSegment;
338 
339         if (m_getJob)
340         {
341             m_getJob->resume();
342         }
343         return freed;
344     }
345 
346     const int newEnd = m_endSegment - free;
347     freed = QPair<int, int>(newEnd + 1, m_endSegment);
348     qCDebug(KGET_DEBUG) << "Start:" << m_currentSegment << "old end:" << m_endSegment << "new end:" << newEnd << "freed:" << freed;
349     m_endSegment = newEnd;
350     m_totalBytesLeft -= m_segSize.first * (free - 1) + m_segSize.second;
351 
352     //end changed, so in any case the lastSegSize should be the normal segSize
353     if (free)
354     {
355         m_segSize.second = m_segSize.first;
356     }
357 
358     if (m_getJob)
359     {
360         m_getJob->resume();
361     }
362     return freed;
363 }
364 
merge(const QPair<KIO::fileoffset_t,KIO::fileoffset_t> & segmentSize,const QPair<int,int> & segmentRange)365 bool Segment::merge(const QPair<KIO::fileoffset_t, KIO::fileoffset_t> &segmentSize, const QPair<int, int> &segmentRange)
366 {
367     if (m_endSegment + 1 == segmentRange.first) {
368         m_endSegment = segmentRange.second;
369         m_segSize.second = segmentSize.second;
370         m_totalBytesLeft += segmentSize.first * (m_endSegment - segmentRange.first) + m_segSize.second;
371         return true;
372     }
373 
374     return false;
375 }
376 
377 
378