Just Intonation  Version 1.3.1 (19)
Explore key-independent dynamically adapting tuning in just intonation
downloader.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * Copyright 2016-2017 Karolin Stange, Christoph Wick, and Haye Hinrichsen
3  *
4  * This file is part of JustIntonation.
5  *
6  * JustIntonation is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * JustIntonation is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with JustIntonation. If not, see http://www.gnu.org/licenses/.
18  *****************************************************************************/
19 
20 //=============================================================================
21 // Downloader
22 //=============================================================================
23 
24 #include "downloader.h"
25 
26 #include <QNetworkInterface>
27 #include <QNetworkProxy>
28 #include <QFileInfo>
29 #include <QDir>
30 #include <QStandardPaths>
31 #include <QStorageInfo>
32 #include <QTimer>
33 
34 #include "config.h"
36 
37 //-----------------------------------------------------------------------------
38 // Downloader Constructor
39 //-----------------------------------------------------------------------------
40 
47 
48 Downloader::Downloader(QString url, QObject *parent)
49  : QObject(parent)
50  , mLocalPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
51  , mRemotePath(url)
52  , mIndexFileName("index.txt")
53  , mNetworkManager()
54  , mIndexOfFiles()
55  , pNetworkReply(nullptr)
56  , mFile()
57  , mIsWaitingForConnection(false)
58  , mForcedDownload(false)
59  , mIsDownloading(false)
60 {
61  setModuleName("Downloader");
62 }
63 
64 
65 //-----------------------------------------------------------------------------
66 // Check internet connection
67 //-----------------------------------------------------------------------------
72 
74 {
75  QList<QNetworkInterface> networkInterfaces = QNetworkInterface::allInterfaces();
76  bool connectionFound = false;
77  LOGMESSAGE << "Checking network interfaces and internet connections:";
78  for (auto &networkInterface : networkInterfaces)
79  {
80  if ( networkInterface.flags().testFlag(QNetworkInterface::IsUp)
81  and not networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
82  {
83  LOGMESSAGE << "Testing" << networkInterface.name()
84  << "MAC:" << networkInterface.hardwareAddress() << ":";
85  if (networkInterface.addressEntries().empty())
86  { LOGMESSAGE << "This adapter is not connected."; }
87  else for (QNetworkAddressEntry &entry : networkInterface.addressEntries())
88  {
89  // ignore MAC utun-devices
90  if (networkInterface.name().startsWith("utun"))
91  {
92  LOGMESSAGE << "Ignoring connected MAC device" << networkInterface.name();
93  }
94  else
95  {
96  LOGMESSAGE << "This adapter IS CONNECTED.";
97  LOGMESSAGE << "IP:" << entry.ip().toString();
98  LOGMESSAGE << "/ Netmask:" << entry.netmask().toString();
99 
100  connectionFound = true;
101  }
102  }
103  }
104  }
105  return connectionFound;
106 }
107 
108 
109 //-----------------------------------------------------------------------------
110 // Start downloading
111 //-----------------------------------------------------------------------------
120 
121 void Downloader::start (bool forced)
122 {
123  LOGMESSAGE << "Start downloading, forced =" << forced;
124  if (mIsDownloading)
125  {
126  if (forced) stop();
127  else
128  {
129  LOGERROR << "Downloader is already downloading. Do not call start() twice.";
130  return;
131  }
132  }
133 
134  mForcedDownload = forced;
136  {
137  LOGMESSAGE << "Downloader has already been started and is waiting for internet connection. Ignore call to start().";
138  return;
139  }
140 
141  // Check for writeable app data storage location
142  QDir directory(mLocalPath);
143  if (not directory.exists()) directory.mkpath(".");
144  if (not directory.exists())
145  {
146  LOGERROR << "Cannot create local app data storage in" << mLocalPath;
147  return;
148  }
149 
152  {
153  LOGWARNING << "Not connected to the internet, retry in 5 secs.";
154  if (forced or getPathsOfDownloadedFiles().size()==0)
155  {
156  LOGMESSAGE << "Emit a warning to Qml that forced download failed because of missing connection";
157  emit signalNoInternetConnection(forced);
158  }
159  QTimer::singleShot(5000,this,&Downloader::downloadIndexFile);
160  return;
161  }
162 
163  // go to the next step
165 }
166 
167 
168 //-----------------------------------------------------------------------------
169 // Download index file
170 //-----------------------------------------------------------------------------
178 
180 {
181  LOGMESSAGE << "entering downloadIndexFile()";
182 
183  // Check internet connection
185  {
188  {
189  LOGWARNING << "Still not connected to the internet, retry in 60 secs.";
190  QTimer::singleShot(60000,this,&Downloader::downloadIndexFile);
191  return;
192  }
193  }
194 
195  // Detect proxy if necessary
196 #ifndef QT_NO_NETWORKPROXY
197  QNetworkProxyQuery query(QUrl(INT_SAMPLES_REPOSITORY));
198  QList<QNetworkProxy> listOfProxies = QNetworkProxyFactory::systemProxyForQuery(query);
199  LOGMESSAGE << "Number of available proxies" << listOfProxies.size();
200  LOGMESSAGE << "Proxy list:" << listOfProxies;
201  if (listOfProxies.size())
202  {
203  LOGMESSAGE << "Set the first proxy in the list";
204  QNetworkProxy::setApplicationProxy(listOfProxies[0]);
205  }
206 #endif
207 
208  // Start to download index file
209  connect(&mNetworkManager, &QNetworkAccessManager::finished,
210  this, &Downloader::indexFileDownloadFinished,Qt::QueuedConnection);
211  QUrl index = QUrl(fullPath(mRemotePath,mIndexFileName));
212  pNetworkReply = mNetworkManager.get(QNetworkRequest(index));
213  if (pNetworkReply->error())
214  {
215  LOGWARNING << "Error requesting file" << index.fileName() << "for download";
216  LOGWARNING << "Message:" << pNetworkReply->errorString();
217  stop();
218  }
219  else
220  {
221  LOGMESSAGE << "Downloading file, waiting for network reply on the URL:";
222  LOGMESSAGE << index;
223  mIsDownloading = true;
224  }
225  // Next step: indexFileDownloadFinished
226 }
227 
228 
229 //-----------------------------------------------------------------------------
230 // Stop downloading
231 //-----------------------------------------------------------------------------
236 
238 {
239  mIsWaitingForConnection = false;
240  if (mIsDownloading)
241  {
242  LOGMESSAGE << "Request to stop downloading";
243  if (pNetworkReply) if (pNetworkReply->isOpen()) pNetworkReply->abort();
244  if (mFile.isOpen()) mFile.close();
245  mIsDownloading = false;
246  }
247  return;
248 }
249 
250 
251 //-----------------------------------------------------------------------------
252 // Private slot: Index file downloaded
253 //-----------------------------------------------------------------------------
261 
262 void Downloader::indexFileDownloadFinished(QNetworkReply *pReply)
263 {
264  // Disconnect this slot from the network manager
265  disconnect(&mNetworkManager, &QNetworkAccessManager::finished,
267 
268  // Detect possible errors in the reply
269  if (pReply->error())
270  {
271  LOGWARNING << "A network error occured during download of the index file";
272  LOGWARNING << pReply->errorString();
273  stop();
274  LOGWARNING << "After network error: Retry downloading in 60 secs.";
275  QTimer::singleShot(60000,this,&Downloader::downloadIndexFile);
276  return;
277  }
278 
279  // Read the index file into the QStringList mIndexOfFiles and delete reply
280  QTextStream inputStream(pReply);
281  mIndexOfFiles.clear();
282  while (not inputStream.atEnd()) mIndexOfFiles << inputStream.readLine();
283  pReply->deleteLater();
284  if (mIndexOfFiles.isEmpty())
285  {
286  LOGWARNING << mIndexFileName << "is empty or could not be downloaded.";
288  mIsDownloading=false;
289  return;
290  }
291 
292  // Write the index file to the local app storage
293  QFile indexFile(fullPath(mLocalPath,mIndexFileName));
294  if (not indexFile.open(QIODevice::WriteOnly))
295  {
296  LOGWARNING << "Could not open index file for writing: " << mIndexFileName;
297  mIsDownloading=false;
298  return;
299  }
300  QTextStream outputStream(&indexFile);
301  for (auto &line : mIndexOfFiles) outputStream << line << "\n";
302  if (indexFile.isOpen()) indexFile.close();
303  LOGMESSAGE << "Successfully downloaded" << pReply->url().toString();
304 
305  // Start downloading the listed files
307 }
308 
309 
310 
311 //-----------------------------------------------------------------------------
312 // Get a list of paths of all downloaded files
313 //-----------------------------------------------------------------------------
318 
320 {
321  QStringList nameFilter("*.int");
322  QDir directory(mLocalPath);
323  QStringList list = directory.entryList(nameFilter);
324  QFile indexFile(fullPath(mLocalPath,mIndexFileName));
325  QStringList filteredList;
326  if (not indexFile.open(QIODevice::ReadOnly)) filteredList = list;
327  else
328  {
329  QStringList indexFileContent;
330  QTextStream inputStream(&indexFile);
331  while (not inputStream.atEnd()) indexFileContent << inputStream.readLine();
332  if (indexFile.isOpen()) indexFile.close();
333  for (QString &e : indexFileContent) if (list.contains(e))
334  filteredList.append(fullPath(directory.path(),e));
335  }
336 
337  for (QString &e : filteredList) if (mIndexOfFiles.contains(e))
338  filteredList.append(fullPath(directory.path(),e));
339  return filteredList;
340 }
341 
342 
343 //-----------------------------------------------------------------------------
344 // Private slot: Initiate download of next file
345 //-----------------------------------------------------------------------------
355 
357 {
358  if (mIndexOfFiles.isEmpty()) { mIsDownloading=false; return; }
359  QString versiontag = "." + QString::number(INT_FILEFORMAT_ROLLING_VERSION);
360  QString remotefile = fullPath(mRemotePath,mIndexOfFiles.first()+versiontag);
361  QNetworkRequest request;
362  request.setUrl(QUrl(remotefile));
363  connect(&mNetworkManager, &QNetworkAccessManager::finished,this,
364  &Downloader::checkWhetherFileExistsAndDownload,Qt::QueuedConnection);
365  pNetworkReply = mNetworkManager.head(request);
366 }
367 
368 
369 //-----------------------------------------------------------------------------
370 // Private slot: Start downloading if necessary
371 //-----------------------------------------------------------------------------
380 
382 {
383  // Disconnect this slot from the network manager
384  disconnect(&mNetworkManager, &QNetworkAccessManager::finished,this,
386 
387  // Determine remote file size and last-modified date
388  qint64 remotefilesize = pNetworkReply->header(QNetworkRequest::ContentLengthHeader).toUInt();
389  QDateTime remotefilelastmodified =
390  pNetworkReply->header(QNetworkRequest::LastModifiedHeader).value<QDateTime>();
391  pNetworkReply->deleteLater();
392  if (mIndexOfFiles.empty()) { mIsDownloading=false; return; }
393  QString filename = mIndexOfFiles.first();
394  QString versiontag = "." + QString::number(INT_FILEFORMAT_ROLLING_VERSION);
395  QString remotefile = fullPath(mRemotePath,filename+versiontag);
396  LOGMESSAGE << "Found file " << remotefile << ", size =" << remotefilesize;
397 
398  // Check whether there is already a local file of the same name
399  QString localfile = fullPath(mLocalPath,filename);
400  QFileInfo fileinfo(localfile);
401  if (fileinfo.exists() and fileinfo.isFile())
402  {
403  // If so, compare file size
404  qint64 localfilesize = fileinfo.size();
405  QDateTime localfilecreated = fileinfo.created();
406  LOGSTATUS << "Local file data:" << localfilecreated;
407  LOGSTATUS << "Remote file date:" << remotefilelastmodified;
408  if (localfilesize != remotefilesize or localfilecreated < remotefilelastmodified)
409  {
410  // If file are different delete local file
411  LOGWARNING << "Length of date is different, hence delete file" << localfile;
412  QFile::remove(localfile);
413  }
414  else if (mForcedDownload)
415  {
416  LOGMESSAGE << "Forced download: File" << localfile << "will be deleted";
417  QFile::remove(localfile);
418  }
419  else
420  {
421  // If files are identical
422  LOGMESSAGE << "File" << localfile << "already exists, skipping download.";
423  emit finalize();
424  return;
425  }
426  }
427 
428  // The download is started only if there is at least twice as much disk space
430  diskspace = 10000000000;
431  LOGSTATUS << "Determining free disk space at location" << mLocalPath;
432  LOGSTATUS << "Free disk space =" << diskspace;
433  if (2*remotefilesize > diskspace)
434  {
435  LOGWARNING << "Device does not provide enough disk space for downloading.";
436  mIsDownloading=false;
437  return;
438  }
439 
440  // Open temporary local file for writing
441  QString tmpfile = localfile + ".part";
442  mFile.setFileName(tmpfile);
443  if (not mFile.open(QIODevice::WriteOnly))
444  {
445  LOGWARNING << "Could not open file for writing: " << localfile;
446  mIsDownloading=false;
447  return;
448  }
449 
450  // Start downloading
451  LOGMESSAGE << "downloading" << remotefile << "to" << localfile;
452  connect(&mNetworkManager, &QNetworkAccessManager::finished,
453  this, &Downloader::downloadComplete,Qt::QueuedConnection);
454  pNetworkReply = mNetworkManager.get(QNetworkRequest(QUrl(remotefile)));
455  connect(pNetworkReply,&QNetworkReply::downloadProgress,
456  this,&Downloader::downloadProgress,Qt::QueuedConnection);
457  emit signalDownloading(true);
458 }
459 
460 
461 
462 
463 //-----------------------------------------------------------------------------
464 // Private slot: Download a data chunk and report progress
465 //-----------------------------------------------------------------------------
476 
477 void Downloader::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
478 {
479  if (pNetworkReply->isOpen())
480  {
481  LOGSTATUS << bytesReceived << "of" << bytesTotal << "bytes downloaded.";
482  emit signalProgress (mIndexOfFiles.size(),100.0*bytesReceived/bytesTotal);
483  QByteArray chunk = pNetworkReply->readAll();
484  mFile.write(chunk);
485  }
486 }
487 
488 
489 //-----------------------------------------------------------------------------
490 // Private slot: Download completed
491 //-----------------------------------------------------------------------------
500 
501 void Downloader::downloadComplete(QNetworkReply* pReply)
502 {
503  // Read the last chunk of data and cloe file
504  emit signalDownloading(false);
505  disconnect(&mNetworkManager, &QNetworkAccessManager::finished,
507  disconnect(pNetworkReply,&QNetworkReply::downloadProgress,
509  if (pReply->isOpen())
510  {
511  QByteArray lastchunk = pReply->readAll();
512  mFile.write(lastchunk);
513  if (mFile.isOpen()) mFile.close();
514 
515  // Rename partial temporary file to the final filename
516  if (mIndexOfFiles.isEmpty()) { mIsDownloading=false; return; }
517  QString filename = mIndexOfFiles.first();
518  QString localfile = fullPath(mLocalPath,filename);
519  QString tmpfile = localfile + ".part";
520  if (QFile::rename(tmpfile,localfile))
521  { LOGMESSAGE << "Completed download of file" << localfile; }
522  else LOGWARNING << "Could not rename temporary file" << tmpfile;
523  emit finalize();
524  emit signalNewFileDownloaded(filename);
525  }
526  else
527  {
528  LOGMESSAGE << "Download cancelled. Abort.";
529  QString filename = mIndexOfFiles.first();
530  QString localfile = fullPath(mLocalPath,filename);
531  QString tmpfile = localfile + ".part";
532  QFile::remove(tmpfile);
533  }
534  pReply->deleteLater();
535 }
536 
537 
538 
539 //-----------------------------------------------------------------------------
540 // Private slot: Finialize download
541 //-----------------------------------------------------------------------------
549 
551 {
552  // Finalize
553  mIndexOfFiles.removeFirst();
554  if (not mIndexOfFiles.isEmpty()) emit initiateDownloadOfNextFile();
555  else
556  {
557  LOGMESSAGE << "Downloader shutting down.";
559  mIsDownloading = false;
560  }
561 }
562 
563 
564 //-----------------------------------------------------------------------------
565 // Helper function: Construct a full path out of a path and a file name
566 //-----------------------------------------------------------------------------
573 
574 QString Downloader::fullPath(const QString &path, const QString &file) const
575 {
576  if (not path.endsWith('/')) return path+"/"+file;
577  else return path+file;
578 }
579 
580 
581 
void signalNewFileDownloaded(QString localpath)
void checkWhetherFileExistsAndDownload()
Private slot: Start downloading if necessary.
Definition: downloader.cpp:381
QString mLocalPath
Local path of the file to be downloaded.
Definition: downloader.h:83
void downloadIndexFile()
Download index file.
Definition: downloader.cpp:179
void setModuleName(const QString &name)
Specify the name of the class-specific module.
Definition: log.cpp:82
void signalDownloading(QVariant downloading)
void stop()
Stop downloading.
Definition: downloader.cpp:237
void finalize()
Finalize download.
Definition: downloader.cpp:550
bool isConnectedToInternet()
Check internet connection.
Definition: downloader.cpp:73
bool mForcedDownload
Flag for forced download.
Definition: downloader.h:91
static PlatformTools & getSingleton()
Definition: platformtools.h:56
#define LOGMESSAGE
Definition: log.h:43
#define INT_SAMPLES_REPOSITORY
Definition: config.h:45
Downloader(QString url, QObject *parent=0)
Constructor, resetting the member variables.
Definition: downloader.cpp:48
void start(bool forced)
Start the downloading process in the background.
Definition: downloader.cpp:121
void signalNoInternetConnection(QVariant forced)
QNetworkAccessManager mNetworkManager
Instance of the Qt network access manager.
Definition: downloader.h:86
QString mRemotePath
Remote path of the file to be downloaded.
Definition: downloader.h:84
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Private slot: Download a data chunk and report progress.
Definition: downloader.cpp:477
QNetworkReply * pNetworkReply
Pointer to the network reply structure.
Definition: downloader.h:88
void initiateDownloadOfNextFile()
Private slot: Initiate download of next file.
Definition: downloader.cpp:356
#define LOGERROR
Definition: log.h:45
QString fullPath(const QString &path, const QString &file) const
Helper function: Construct a full path out of a path and a file name.
Definition: downloader.cpp:574
virtual qint64 getFreeDiskSpace(const QString &path)
Determine free disk space.
Definition: platformtools.h:93
QString mIndexFileName
Name of the remote index file.
Definition: downloader.h:85
#define INT_FILEFORMAT_ROLLING_VERSION
Definition: config.h:41
QFile mFile
File to be written.
Definition: downloader.h:89
#define LOGSTATUS
Definition: log.h:42
bool mIsWaitingForConnection
Flag indicating waiting status.
Definition: downloader.h:90
void signalProgress(QVariant filesRemaining, QVariant percent)
void signalAllFilesDownloaded()
QStringList getPathsOfDownloadedFiles()
Get a list of paths of all downloaded files.
Definition: downloader.cpp:319
void indexFileDownloadFinished(QNetworkReply *pReply)
Private slot: Index file downloaded.
Definition: downloader.cpp:262
QStringList mIndexOfFiles
The content of the index file as a QStringList.
Definition: downloader.h:87
void downloadComplete(QNetworkReply *pReply)
Private slot: Download completed.
Definition: downloader.cpp:501
bool mIsDownloading
Flag indicating active download.
Definition: downloader.h:92
#define LOGWARNING
Definition: log.h:44