Make the download use a temp file to save ram and make it threaded.

pull/520/head
Adam Treat 1 year ago
parent c086a45173
commit fe97a6e04f

@ -19,7 +19,12 @@ Download *Download::globalInstance()
Download::Download() Download::Download()
: QObject(nullptr) : QObject(nullptr)
, m_hashAndSave(new HashAndSaveFile)
{ {
connect(this, &Download::requestHashAndSave, m_hashAndSave,
&HashAndSaveFile::hashAndSave, Qt::QueuedConnection);
connect(m_hashAndSave, &HashAndSaveFile::hashAndSaveFinished, this,
&Download::handleHashAndSaveFinished, Qt::QueuedConnection);
updateModelList(); updateModelList();
} }
@ -69,17 +74,27 @@ void Download::updateModelList()
void Download::downloadModel(const QString &modelFile) void Download::downloadModel(const QString &modelFile)
{ {
QTemporaryFile *tempFile = new QTemporaryFile;
bool success = tempFile->open();
qWarning() << "Opening temp file for writing:" << tempFile->fileName();
if (!success) {
qWarning() << "ERROR: Could not open temp file:"
<< tempFile->fileName() << modelFile;
return;
}
QNetworkRequest request("http://gpt4all.io/models/" + modelFile); QNetworkRequest request("http://gpt4all.io/models/" + modelFile);
QNetworkReply *modelReply = m_networkManager.get(request); QNetworkReply *modelReply = m_networkManager.get(request);
connect(modelReply, &QNetworkReply::downloadProgress, this, &Download::handleDownloadProgress); connect(modelReply, &QNetworkReply::downloadProgress, this, &Download::handleDownloadProgress);
connect(modelReply, &QNetworkReply::finished, this, &Download::handleModelDownloadFinished); connect(modelReply, &QNetworkReply::finished, this, &Download::handleModelDownloadFinished);
m_activeDownloads.append(modelReply); connect(modelReply, &QNetworkReply::readyRead, this, &Download::handleReadyRead);
m_activeDownloads.insert(modelReply, tempFile);
} }
void Download::cancelDownload(const QString &modelFile) void Download::cancelDownload(const QString &modelFile)
{ {
for (int i = 0; i < m_activeDownloads.size(); ++i) { for (int i = 0; i < m_activeDownloads.size(); ++i) {
QNetworkReply *modelReply = m_activeDownloads.at(i); QNetworkReply *modelReply = m_activeDownloads.keys().at(i);
QUrl url = modelReply->request().url(); QUrl url = modelReply->request().url();
if (url.toString().endsWith(modelFile)) { if (url.toString().endsWith(modelFile)) {
// Disconnect the signals // Disconnect the signals
@ -88,7 +103,10 @@ void Download::cancelDownload(const QString &modelFile)
modelReply->abort(); // Abort the download modelReply->abort(); // Abort the download
modelReply->deleteLater(); // Schedule the reply for deletion modelReply->deleteLater(); // Schedule the reply for deletion
m_activeDownloads.removeAll(modelReply);
QTemporaryFile *tempFile = m_activeDownloads.value(modelReply);
tempFile->deleteLater();
m_activeDownloads.remove(modelReply);
// Emit downloadFinished signal for cleanup // Emit downloadFinished signal for cleanup
emit downloadFinished(modelFile); emit downloadFinished(modelFile);
@ -192,61 +210,134 @@ bool operator==(const ModelInfo& lhs, const ModelInfo& rhs) {
return lhs.filename == rhs.filename && lhs.md5sum == rhs.md5sum; return lhs.filename == rhs.filename && lhs.md5sum == rhs.md5sum;
} }
void Download::handleModelDownloadFinished() HashAndSaveFile::HashAndSaveFile()
: QObject(nullptr)
{ {
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender()); moveToThread(&m_hashAndSaveThread);
if (!modelReply) m_hashAndSaveThread.setObjectName("hashandsave thread");
return; m_hashAndSaveThread.start();
}
void HashAndSaveFile::hashAndSave(const QString &expectedHash, const QString &saveFilePath,
QTemporaryFile *tempFile, QNetworkReply *modelReply)
{
Q_ASSERT(!tempFile->isOpen());
QString modelFilename = modelReply->url().fileName(); QString modelFilename = modelReply->url().fileName();
m_activeDownloads.removeAll(modelReply);
if (modelReply->error()) { // Reopen the tempFile for hashing
qWarning() << "ERROR: downloading:" << modelReply->errorString(); if (!tempFile->open()) {
modelReply->deleteLater(); qWarning() << "ERROR: Could not open temp file for hashing:"
emit downloadFinished(modelFilename); << tempFile->fileName() << modelFilename;
emit hashAndSaveFinished(false, tempFile, modelReply);
return; return;
} }
QByteArray modelData = modelReply->readAll();
if (!m_modelMap.contains(modelFilename)) {
qWarning() << "ERROR: Cannot find in download map:" << modelFilename;
modelReply->deleteLater();
emit downloadFinished(modelFilename);
return;
}
ModelInfo info = m_modelMap.value(modelFilename);
QCryptographicHash hash(QCryptographicHash::Md5); QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(modelData); hash.addData(tempFile->readAll());
if (hash.result().toHex() != info.md5sum) { while(!tempFile->atEnd())
hash.addData(tempFile->read(16384));
if (hash.result().toHex() != expectedHash) {
tempFile->close();
qWarning() << "ERROR: Download error MD5SUM did not match:" qWarning() << "ERROR: Download error MD5SUM did not match:"
<< hash.result().toHex() << hash.result().toHex()
<< "!=" << info.md5sum << "for" << modelFilename; << "!=" << expectedHash << "for" << modelFilename;
modelReply->deleteLater(); emit hashAndSaveFinished(false, tempFile, modelReply);
emit downloadFinished(modelFilename); return;
}
// The file save needs the tempFile closed
tempFile->close();
// Reopen the tempFile for copying
if (!tempFile->open()) {
qWarning() << "ERROR: Could not open temp file at finish:"
<< tempFile->fileName() << modelFilename;
emit hashAndSaveFinished(false, tempFile, modelReply);
return; return;
} }
// Save the model file to disk // Save the model file to disk
QFile file(downloadLocalModelsPath() + modelFilename); QFile file(saveFilePath);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
file.write(modelData); QByteArray buffer;
while (!tempFile->atEnd()) {
buffer = tempFile->read(16384);
file.write(buffer);
}
file.close(); file.close();
tempFile->close();
emit hashAndSaveFinished(true, tempFile, modelReply);
} else { } else {
QFile::FileError error = file.error(); QFile::FileError error = file.error();
qWarning() << "ERROR: Could not save model to location:" qWarning() << "ERROR: Could not save model to location:"
<< downloadLocalModelsPath() + modelFilename << saveFilePath
<< "failed with code" << error; << "failed with code" << error;
tempFile->close();
emit hashAndSaveFinished(false, tempFile, modelReply);
return;
}
}
void Download::handleModelDownloadFinished()
{
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender());
if (!modelReply)
return;
QString modelFilename = modelReply->url().fileName();
QTemporaryFile *tempFile = m_activeDownloads.value(modelReply);
m_activeDownloads.remove(modelReply);
if (modelReply->error()) {
qWarning() << "ERROR: downloading:" << modelReply->errorString();
modelReply->deleteLater(); modelReply->deleteLater();
tempFile->deleteLater();
emit downloadFinished(modelFilename); emit downloadFinished(modelFilename);
return; return;
} }
modelReply->deleteLater(); // The hash and save needs the tempFile closed
emit downloadFinished(modelFilename); tempFile->close();
info.installed = true; // Notify that we are calculating hash
ModelInfo info = m_modelMap.value(modelFilename);
info.calcHash = true;
m_modelMap.insert(modelFilename, info); m_modelMap.insert(modelFilename, info);
emit modelListChanged(); emit modelListChanged();
const QString saveFilePath = downloadLocalModelsPath() + modelFilename;
emit requestHashAndSave(info.md5sum, saveFilePath, tempFile, modelReply);
}
void Download::handleHashAndSaveFinished(bool success,
QTemporaryFile *tempFile, QNetworkReply *modelReply)
{
// The hash and save should send back with tempfile closed
Q_ASSERT(!tempFile->isOpen());
QString modelFilename = modelReply->url().fileName();
ModelInfo info = m_modelMap.value(modelFilename);
info.calcHash = false;
info.installed = success;
m_modelMap.insert(modelFilename, info);
emit modelListChanged();
modelReply->deleteLater();
tempFile->deleteLater();
emit downloadFinished(modelFilename);
}
void Download::handleReadyRead()
{
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender());
if (!modelReply)
return;
QString modelFilename = modelReply->url().fileName();
QTemporaryFile *tempFile = m_activeDownloads.value(modelReply);
QByteArray buffer;
while (!modelReply->atEnd()) {
buffer = modelReply->read(16384);
tempFile->write(buffer);
}
} }

@ -6,12 +6,15 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QFile> #include <QFile>
#include <QVariant> #include <QVariant>
#include <QTemporaryFile>
#include <QThread>
struct ModelInfo { struct ModelInfo {
Q_GADGET Q_GADGET
Q_PROPERTY(QString filename MEMBER filename) Q_PROPERTY(QString filename MEMBER filename)
Q_PROPERTY(QString filesize MEMBER filesize) Q_PROPERTY(QString filesize MEMBER filesize)
Q_PROPERTY(QByteArray md5sum MEMBER md5sum) Q_PROPERTY(QByteArray md5sum MEMBER md5sum)
Q_PROPERTY(bool calcHash MEMBER calcHash)
Q_PROPERTY(bool installed MEMBER installed) Q_PROPERTY(bool installed MEMBER installed)
Q_PROPERTY(bool isDefault MEMBER isDefault) Q_PROPERTY(bool isDefault MEMBER isDefault)
@ -19,11 +22,30 @@ public:
QString filename; QString filename;
QString filesize; QString filesize;
QByteArray md5sum; QByteArray md5sum;
bool calcHash = false;
bool installed = false; bool installed = false;
bool isDefault = false; bool isDefault = false;
}; };
Q_DECLARE_METATYPE(ModelInfo) Q_DECLARE_METATYPE(ModelInfo)
class HashAndSaveFile : public QObject
{
Q_OBJECT
public:
HashAndSaveFile();
public Q_SLOTS:
void hashAndSave(const QString &hash, const QString &saveFilePath,
QTemporaryFile *tempFile, QNetworkReply *modelReply);
Q_SIGNALS:
void hashAndSaveFinished(bool success,
QTemporaryFile *tempFile, QNetworkReply *modelReply);
private:
QThread m_hashAndSaveThread;
};
class Download : public QObject class Download : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -42,18 +64,24 @@ private Q_SLOTS:
void handleJsonDownloadFinished(); void handleJsonDownloadFinished();
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleModelDownloadFinished(); void handleModelDownloadFinished();
void handleHashAndSaveFinished(bool success,
QTemporaryFile *tempFile, QNetworkReply *modelReply);
void handleReadyRead();
Q_SIGNALS: Q_SIGNALS:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile); void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile);
void downloadFinished(const QString &modelFile); void downloadFinished(const QString &modelFile);
void modelListChanged(); void modelListChanged();
void requestHashAndSave(const QString &hash, const QString &saveFilePath,
QTemporaryFile *tempFile, QNetworkReply *modelReply);
private: private:
void parseJsonFile(const QByteArray &jsonData); void parseJsonFile(const QByteArray &jsonData);
HashAndSaveFile *m_hashAndSave;
QMap<QString, ModelInfo> m_modelMap; QMap<QString, ModelInfo> m_modelMap;
QNetworkAccessManager m_networkManager; QNetworkAccessManager m_networkManager;
QVector<QNetworkReply*> m_activeDownloads; QMap<QNetworkReply*, QTemporaryFile*> m_activeDownloads;
private: private:
explicit Download(); explicit Download();

@ -126,6 +126,36 @@ Dialog {
Accessible.description: qsTr("Shows the progress made in the download") Accessible.description: qsTr("Shows the progress made in the download")
} }
Item {
visible: modelData.calcHash
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 10
Label {
id: calcHashLabel
anchors.right: busyCalcHash.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
objectName: "calcHashLabel"
color: theme.textColor
text: qsTr("Calculating MD5...")
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file hash is being calculated")
}
BusyIndicator {
id: busyCalcHash
anchors.right: parent.right
anchors.verticalCenter: calcHashLabel.verticalCenter
running: modelData.calcHash
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the file hash is being calculated")
}
}
Label { Label {
id: installedLabel id: installedLabel
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -146,7 +176,7 @@ Dialog {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 10 anchors.rightMargin: 10
visible: !modelData.installed visible: !modelData.installed && !modelData.calcHash
padding: 10 padding: 10
onClicked: { onClicked: {
if (!downloading) { if (!downloading) {

Loading…
Cancel
Save