1 /*
2     This file is part of the KDE project
3     Copyright (C) 2011 Ernesto Rodriguez Ortiz <eortiz@uci.cu>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 
20 #include "mmsdownload.h"
21 
22 const int SPEEDTIMER = 1000;//1 second...
23 
MmsDownload(const QString & url,const QString & name,const QString & temp,int amountsThread)24 MmsDownload::MmsDownload(const QString &url, const QString &name, const QString &temp,
25                          int amountsThread)
26 : QThread(),
27   m_sourceUrl(url),
28   m_fileName(name),
29   m_fileTemp(temp),
30   m_amountThreads(amountsThread),
31   m_connectionsFails(0),
32   m_connectionsSuccessfully(0),
33   m_downloadedSize(0),
34   m_mms(NULL)
35 {
36     m_speedTimer = new QTimer(this);
37     m_speedTimer->setInterval(SPEEDTIMER);
38     connect(m_speedTimer, SIGNAL(timeout()), this, SLOT(slotSpeedChanged()));
39 }
40 
~MmsDownload()41 MmsDownload::~MmsDownload()
42 {
43     if (m_mms) {
44         mmsx_close(m_mms);
45     }
46     m_speedTimer->stop();
47     m_speedTimer->deleteLater();
48 }
49 
run()50 void MmsDownload::run()
51 {
52     if (isWorkingUrl()) {
53         splitTransfer();
54         startTransfer();
55     } else {
56         Q_EMIT signBrokenUrl();
57         quit();
58     }
59     exec();
60 }
61 
62 
isWorkingUrl()63 bool MmsDownload::isWorkingUrl()
64 {
65     /** Check if the URL is working, if it can't connect then not start the download.*/
66     m_mms = mmsx_connect(NULL, NULL, qstrdup(m_sourceUrl.toLatin1()), 1e9);
67     return m_mms;
68 }
69 
splitTransfer()70 void MmsDownload::splitTransfer()
71 {
72     /** We split the download in similar and each part is asigned to a thread and thi is saved in
73      * a map named m_mapEndIni. If we resume the download, then the temporal file will exist
74      * and we don't have to split the download only use it.
75      */
76     m_amountThreads = mmsx_get_seekable(m_mms) ? m_amountThreads : 0;
77     if (m_amountThreads == 0) {
78         m_amountThreads = 1;
79         Q_EMIT signNotAllowMultiDownload();
80         QFile::remove(m_fileTemp);
81     }
82 
83     const qulonglong total = mmsx_get_length(m_mms);
84     Q_EMIT signTotalSize(total);
85 
86     if (QFile::exists(m_fileTemp)) {
87         unSerialization();
88     } else {
89         int part = mmsx_get_length(m_mms) / m_amountThreads;
90         int ini = 0;
91         int end = 0;
92         for (int i = 0; i < m_amountThreads; i++) {
93             if (i + 1 == m_amountThreads) {
94                 part = total - ini;
95             }
96             end = ini + part;
97             m_mapEndIni.insert(end, ini);
98             ini += part;
99         }
100     }
101 }
102 
startTransfer()103 void MmsDownload::startTransfer()
104 {
105     m_speedTimer->start();
106     QMap<int, int>::const_iterator iterator = m_mapEndIni.constBegin();
107     while (iterator != m_mapEndIni.constEnd()) {
108         auto* thread = new MmsThread(m_sourceUrl, m_fileName,
109                                           iterator.value(), iterator.key());
110         m_threadList.append(thread);
111         connect(thread, SIGNAL(finished()), this, SLOT(slotThreadFinish()));
112         connect(thread, SIGNAL(signIsConnected(bool)), this, SLOT(slotIsThreadConnected(bool)));
113         connect(thread, SIGNAL(signReading(int,int,int)), this, SLOT(slotRead(int,int,int)));
114         thread->start();
115         ++iterator;
116     }
117 }
118 
slotSpeedChanged()119 void MmsDownload::slotSpeedChanged()
120 {
121     /** Using the same speed calculating datasourcefactory uses (use all downloaded data
122      * of the last 10 secs)
123      */
124     qulonglong speed;
125     if (m_prevDownloadedSizes.size()) {
126         speed = (m_downloadedSize - m_prevDownloadedSizes.first()) / (SPEEDTIMER *
127             m_prevDownloadedSizes.size() / 1000);//downloaded in 1 second
128     } else {
129         speed = 0;
130     }
131 
132     m_prevDownloadedSizes.append(m_downloadedSize);
133     if(m_prevDownloadedSizes.size() > 10)
134         m_prevDownloadedSizes.removeFirst();
135 
136     Q_EMIT signSpeed(speed);
137     serialization();
138 }
139 
140 
stopTransfer()141 void MmsDownload::stopTransfer()
142 {
143     /** Here only is called thread->stop() because when the thread finish it Q_EMIT a signal
144      * and slotThreadFinish(); is called where the thread is delete calling deleteLater(); and
145      * m_threadList is cleaning using removeAll().
146      */
147     foreach (MmsThread* thread, m_threadList) {
148         thread->stop();
149         thread->quit();
150     }
151 }
152 
threadsAlive()153 int MmsDownload::threadsAlive()
154 {
155     return m_threadList.size();
156 }
157 
158 
slotThreadFinish()159 void MmsDownload::slotThreadFinish()
160 {
161     auto* thread = qobject_cast<MmsThread*>(QObject::sender());
162     m_threadList.removeAll(thread);
163     thread->deleteLater();
164 
165     if (m_threadList.isEmpty()) {
166         serialization();
167         quit();
168     }
169 }
170 
slotRead(int reading,int thread_end,int thread_in)171 void MmsDownload::slotRead(int reading, int thread_end, int thread_in)
172 {
173     /** We update the status of the thread in the map and Q_EMIT a signal for update the download
174      * speed.
175      */
176     if (thread_in == thread_end) {
177         m_mapEndIni.remove(thread_end);
178     } else {
179         m_mapEndIni[thread_end] = thread_in;
180     }
181     m_downloadedSize += reading;
182     Q_EMIT signDownloaded(m_downloadedSize);
183 }
184 
slotIsThreadConnected(bool connected)185 void MmsDownload::slotIsThreadConnected(bool connected)
186 {
187     /** All threads Q_EMIT a signal connected with this slot, if they get connected successfully
188      * the value of "connected" will be true, and will be false if they can't connected. When all
189      * the threads emitted the signal the amount of m_connectionsSuccefusslly and m_connectionsFails
190      * will be equal to m_amountThreads and we Q_EMIT a signal to restart the download in
191      * mmstransfer using the amount of connections succefusslly connected.
192      */
193     if (connected) {
194         m_connectionsSuccessfully++;
195     } else {
196         m_connectionsFails++;
197     }
198     if ((m_connectionsFails != 0) &&
199         (m_connectionsFails + m_connectionsSuccessfully == m_amountThreads)) {
200         Q_EMIT signRestartDownload(m_connectionsSuccessfully);
201     }
202 }
203 
serialization()204 void MmsDownload::serialization()
205 {
206     /** Here we save the status of the download to the temporal file for resume the download
207      * if we stop it.
208      */
209     QFile file(m_fileTemp);
210     file.open(QIODevice::WriteOnly);
211     QDataStream out(&file);
212     out << m_mapEndIni << m_downloadedSize << m_prevDownloadedSizes;
213     file.close();
214 }
215 
unSerialization()216 void MmsDownload::unSerialization()
217 {
218     /** Here we read the status of the download to the temporal file for resume the download
219      */
220     QFile file(m_fileTemp);
221     file.open(QIODevice::ReadOnly);
222     QDataStream in(&file);
223     in >> m_mapEndIni >> m_downloadedSize >> m_prevDownloadedSizes;
224     file.close();
225 }
226