mirror of
https://github.com/cmclark00/retro-imager.git
synced 2025-05-18 07:55:21 +01:00
Qt/QML edition
This commit is contained in:
commit
d7b361ba44
2168 changed files with 721948 additions and 0 deletions
438
imagewriter.cpp
Normal file
438
imagewriter.cpp
Normal file
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
|
||||
*/
|
||||
|
||||
#include "imagewriter.h"
|
||||
#include "drivelistitem.h"
|
||||
#include "downloadextractthread.h"
|
||||
#include "dependencies/drivelist/src/drivelist.hpp"
|
||||
#include "driveformatthread.h"
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <QFileInfo>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QProcess>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QStorageInfo>
|
||||
#include <QWindow>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <winioctl.h>
|
||||
#endif
|
||||
|
||||
ImageWriter::ImageWriter(QObject *parent)
|
||||
: QObject(parent), _repo(QUrl(QString(OSLIST_URL))), _dlnow(0), _verifynow(0),
|
||||
_engine(nullptr), _thread(nullptr), _verifyEnabled(false), _cachingEnabled(false)
|
||||
{
|
||||
connect(&_polltimer, SIGNAL(timeout()), SLOT(pollProgress()));
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess *p = new QProcess(this);
|
||||
p->start("net stop ShellHWDetection");
|
||||
#endif
|
||||
|
||||
if (!_settings.isWritable() && !_settings.fileName().isEmpty())
|
||||
{
|
||||
/* Settings file is not writable, probably run by root previously */
|
||||
QString settingsFile = _settings.fileName();
|
||||
qDebug() << "Settings file" << settingsFile << "not writable. Recreating it";
|
||||
QFile f(_settings.fileName());
|
||||
QByteArray oldsettings;
|
||||
|
||||
if (f.open(f.ReadOnly))
|
||||
{
|
||||
oldsettings = f.readAll();
|
||||
f.close();
|
||||
}
|
||||
f.remove();
|
||||
if (f.open(f.WriteOnly))
|
||||
{
|
||||
f.write(oldsettings);
|
||||
f.close();
|
||||
_settings.sync();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Error deleting and recreating settings file. Please remove manually.";
|
||||
}
|
||||
}
|
||||
|
||||
_settings.beginGroup("caching");
|
||||
_cachingEnabled = _settings.value("enabled", IMAGEWRITER_ENABLE_CACHE_DEFAULT).toBool();
|
||||
_cachedFileHash = _settings.value("lastDownloadSHA256").toByteArray();
|
||||
_cacheFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+QDir::separator()+"lastdownload.cache";
|
||||
if (!_cachedFileHash.isEmpty())
|
||||
{
|
||||
QFileInfo f(_cacheFileName);
|
||||
if (!f.exists() || !f.isReadable() || !f.size())
|
||||
{
|
||||
_cachedFileHash.clear();
|
||||
_settings.remove("lastDownloadSHA256");
|
||||
_settings.sync();
|
||||
}
|
||||
}
|
||||
_settings.endGroup();
|
||||
}
|
||||
|
||||
ImageWriter::~ImageWriter()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QProcess *p = new QProcess(this);
|
||||
p->startDetached("net start ShellHWDetection");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ImageWriter::setEngine(QQmlApplicationEngine *engine)
|
||||
{
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
/* Set URL to download from */
|
||||
void ImageWriter::setSrc(const QUrl &url, quint64 downloadLen, quint64 extrLen, QByteArray expectedHash, bool multifilesinzip)
|
||||
{
|
||||
_src = url;
|
||||
_downloadLen = downloadLen;
|
||||
_extrLen = extrLen;
|
||||
_expectedHash = expectedHash;
|
||||
_multipleFilesInZip = multifilesinzip;
|
||||
|
||||
if (!_downloadLen && url.isLocalFile())
|
||||
{
|
||||
QFileInfo fi(url.toLocalFile());
|
||||
_downloadLen = fi.size();
|
||||
}
|
||||
}
|
||||
|
||||
/* Set device to write to */
|
||||
void ImageWriter::setDst(const QString &device, quint64 deviceSize)
|
||||
{
|
||||
_dst = device;
|
||||
_devLen = deviceSize;
|
||||
}
|
||||
|
||||
/* Returns true if src and dst are set */
|
||||
bool ImageWriter::readyToWrite()
|
||||
{
|
||||
return !_src.isEmpty() && !_dst.isEmpty();
|
||||
}
|
||||
|
||||
/* Start writing */
|
||||
void ImageWriter::startWrite()
|
||||
{
|
||||
if (!readyToWrite())
|
||||
return;
|
||||
|
||||
if (_src.toString() == "internal://format")
|
||||
{
|
||||
DriveFormatThread *dft = new DriveFormatThread(_dst.toLatin1(), this);
|
||||
connect(dft, SIGNAL(success()), SLOT(onSuccess()));
|
||||
connect(dft, SIGNAL(error(QString)), SLOT(onError(QString)));
|
||||
dft->start();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray urlstr = _src.toString(_src.FullyEncoded).toLatin1();
|
||||
QString lowercaseurl = urlstr.toLower();
|
||||
bool compressed = lowercaseurl.endsWith(".zip") || lowercaseurl.endsWith(".xz") || lowercaseurl.endsWith(".bz2") || lowercaseurl.endsWith(".gz") || lowercaseurl.endsWith(".7z") || lowercaseurl.endsWith(".cache");
|
||||
if (!_extrLen && _src.isLocalFile())
|
||||
{
|
||||
if (!compressed)
|
||||
_extrLen = _downloadLen;
|
||||
else if (lowercaseurl.endsWith(".zip"))
|
||||
_parseCompressedFile();
|
||||
}
|
||||
|
||||
if (_devLen && _extrLen > _devLen)
|
||||
{
|
||||
emit error(tr("Storage capacity is not large enough.\nNeeds to be at least %1 GB").arg(QString::number(_extrLen/1000000000.0, 'f', 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_extrLen && !_multipleFilesInZip && _extrLen % 512 != 0)
|
||||
{
|
||||
emit error(tr("Input file is not a valid disk image.\nFile size %1 bytes is not a multiple of 512 bytes.").arg(_extrLen));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_expectedHash.isEmpty() && _cachedFileHash == _expectedHash)
|
||||
{
|
||||
// Use cached file
|
||||
urlstr = QUrl::fromLocalFile(_cacheFileName).toString(_src.FullyEncoded).toLatin1();
|
||||
}
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
_thread = new DownloadExtractThread(urlstr, _dst.toLatin1(), _expectedHash, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_thread = new DownloadThread(urlstr, _dst.toLatin1(), _expectedHash, this);
|
||||
}
|
||||
|
||||
_powersave.applyBlock(tr("Downloading and writing image"));
|
||||
|
||||
connect(_thread, SIGNAL(success()), SLOT(onSuccess()));
|
||||
connect(_thread, SIGNAL(error(QString)), SLOT(onError(QString)));
|
||||
_thread->setVerifyEnabled(_verifyEnabled);
|
||||
|
||||
if (!_expectedHash.isEmpty() && _cachedFileHash != _expectedHash && _cachingEnabled)
|
||||
{
|
||||
if (!_cachedFileHash.isEmpty())
|
||||
{
|
||||
if (_settings.isWritable() && QFile::remove(_cacheFileName))
|
||||
{
|
||||
_settings.remove("caching/lastDownloadSHA256");
|
||||
_settings.sync();
|
||||
_cachedFileHash.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Error removing old cache file. Disabling caching";
|
||||
_cachingEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cachingEnabled)
|
||||
{
|
||||
QStorageInfo si(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
qint64 avail = si.bytesAvailable();
|
||||
qDebug() << "Available disk space for caching:" << avail/1024/1024/1024 << "GB";
|
||||
|
||||
if (avail-_downloadLen < IMAGEWRITER_MINIMAL_SPACE_FOR_CACHING)
|
||||
{
|
||||
qDebug() << "Low disk space. Not caching files to disk.";
|
||||
}
|
||||
else
|
||||
{
|
||||
_thread->setCacheFile(_cacheFileName, _downloadLen);
|
||||
connect(_thread, SIGNAL(cacheFileUpdated(QByteArray)), SLOT(onCacheFileUpdated(QByteArray)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_multipleFilesInZip)
|
||||
{
|
||||
static_cast<DownloadExtractThread *>(_thread)->enableMultipleFileExtraction();
|
||||
DriveFormatThread *dft = new DriveFormatThread(_dst.toLatin1(), this);
|
||||
connect(dft, SIGNAL(success()), _thread, SLOT(start()));
|
||||
connect(dft, SIGNAL(error(QString)), SLOT(onError(QString)));
|
||||
dft->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_thread->start();
|
||||
}
|
||||
|
||||
_polltimer.start(PROGRESS_UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
void ImageWriter::onCacheFileUpdated(QByteArray sha256)
|
||||
{
|
||||
_settings.setValue("caching/lastDownloadSHA256", sha256);
|
||||
_settings.sync();
|
||||
_cachedFileHash = sha256;
|
||||
qDebug() << "Done writing cache file";
|
||||
}
|
||||
|
||||
/* Cancel write */
|
||||
void ImageWriter::cancelWrite()
|
||||
{
|
||||
if (_thread)
|
||||
{
|
||||
connect(_thread, SIGNAL(finished()), SLOT(onCancelled()));
|
||||
_thread->cancelDownload();
|
||||
}
|
||||
|
||||
if (!_thread || !_thread->isRunning())
|
||||
{
|
||||
emit cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWriter::onCancelled()
|
||||
{
|
||||
sender()->deleteLater();
|
||||
if (sender() == _thread)
|
||||
{
|
||||
_thread = nullptr;
|
||||
}
|
||||
emit cancelled();
|
||||
}
|
||||
|
||||
/* Return true if url is in our local disk cache */
|
||||
bool ImageWriter::isCached(const QUrl &, const QByteArray &sha256)
|
||||
{
|
||||
return !sha256.isEmpty() && _cachedFileHash == sha256;
|
||||
}
|
||||
|
||||
/* Utility function to return filename part from URL */
|
||||
QString ImageWriter::fileNameFromUrl(const QUrl &url)
|
||||
{
|
||||
//return QFileInfo(url.toLocalFile()).fileName();
|
||||
return url.fileName();
|
||||
}
|
||||
|
||||
QString ImageWriter::srcFileName()
|
||||
{
|
||||
return _src.isEmpty() ? "" : _src.fileName();
|
||||
}
|
||||
|
||||
/* Function to return OS list URL */
|
||||
QUrl ImageWriter::constantOsListUrl() const
|
||||
{
|
||||
return _repo;
|
||||
}
|
||||
|
||||
void ImageWriter::setCustomOsListUrl(const QUrl &url)
|
||||
{
|
||||
_repo = url;
|
||||
}
|
||||
|
||||
/* Refresh the list of available drives */
|
||||
void ImageWriter::refreshDriveList()
|
||||
{
|
||||
_drivelist.refreshDriveList();
|
||||
}
|
||||
|
||||
DriveListModel *ImageWriter::getDriveList()
|
||||
{
|
||||
return &_drivelist;
|
||||
}
|
||||
|
||||
void ImageWriter::pollProgress()
|
||||
{
|
||||
if (!_thread)
|
||||
return;
|
||||
|
||||
quint64 newDlNow, dlTotal;
|
||||
if (_extrLen)
|
||||
{
|
||||
newDlNow = _thread->bytesWritten();
|
||||
dlTotal = _extrLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
newDlNow = _thread->dlNow();
|
||||
dlTotal = _thread->dlTotal();
|
||||
}
|
||||
|
||||
if (newDlNow != _dlnow)
|
||||
{
|
||||
_dlnow = newDlNow;
|
||||
emit downloadProgress(newDlNow, dlTotal);
|
||||
}
|
||||
|
||||
quint64 newVerifyNow = _thread->verifyNow();
|
||||
|
||||
if (newVerifyNow != _verifynow)
|
||||
{
|
||||
_verifynow = newVerifyNow;
|
||||
quint64 verifyTotal = _thread->verifyTotal();
|
||||
emit verifyProgress(newVerifyNow, verifyTotal);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWriter::setVerifyEnabled(bool verify)
|
||||
{
|
||||
_verifyEnabled = verify;
|
||||
if (_thread)
|
||||
_thread->setVerifyEnabled(verify);
|
||||
}
|
||||
|
||||
/* Relay events from download thread to QML */
|
||||
void ImageWriter::onSuccess()
|
||||
{
|
||||
_polltimer.stop();
|
||||
pollProgress();
|
||||
_powersave.removeBlock();
|
||||
emit success();
|
||||
}
|
||||
|
||||
void ImageWriter::onError(QString msg)
|
||||
{
|
||||
_polltimer.stop();
|
||||
pollProgress();
|
||||
_powersave.removeBlock();
|
||||
emit error(msg);
|
||||
}
|
||||
|
||||
void ImageWriter::onFinalizing()
|
||||
{
|
||||
_polltimer.stop();
|
||||
emit finalizing();
|
||||
}
|
||||
|
||||
void ImageWriter::openFileDialog()
|
||||
{
|
||||
QFileDialog *fd = new QFileDialog(nullptr, tr("Select image"),
|
||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation),
|
||||
"Image files (*.img *.zip *.gz *.xz);;All files (*.*)");
|
||||
connect(fd, SIGNAL(fileSelected(QString)), SLOT(onFileSelected(QString)));
|
||||
|
||||
if (_engine)
|
||||
{
|
||||
fd->createWinId();
|
||||
QWindow *handle = fd->windowHandle();
|
||||
QWindow *qmlwindow = qobject_cast<QWindow *>(_engine->rootObjects().value(0));
|
||||
if (qmlwindow)
|
||||
{
|
||||
handle->setTransientParent(qmlwindow);
|
||||
}
|
||||
}
|
||||
|
||||
fd->show();
|
||||
}
|
||||
|
||||
void ImageWriter::onFileSelected(QString filename)
|
||||
{
|
||||
QFileInfo fi(filename);
|
||||
|
||||
if (fi.isFile())
|
||||
{
|
||||
emit fileSelected(QUrl::fromLocalFile(filename));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Item selected is not a regular file";
|
||||
}
|
||||
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void ImageWriter::_parseCompressedFile()
|
||||
{
|
||||
struct archive *a = archive_read_new();
|
||||
struct archive_entry *entry;
|
||||
QByteArray fn = _src.toLocalFile().toLatin1();
|
||||
int numFiles = 0;
|
||||
_extrLen = 0;
|
||||
|
||||
archive_read_support_filter_all(a);
|
||||
archive_read_support_format_all(a);
|
||||
|
||||
if (archive_read_open_filename(a, fn.data(), 10240) == ARCHIVE_OK)
|
||||
{
|
||||
while ( (archive_read_next_header(a, &entry)) == ARCHIVE_OK)
|
||||
{
|
||||
if (archive_entry_size(entry) > 0)
|
||||
{
|
||||
_extrLen += archive_entry_size(entry);
|
||||
numFiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numFiles > 1)
|
||||
_multipleFilesInZip = true;
|
||||
|
||||
qDebug() << "Parsed .zip file containing" << numFiles << "files, uncompressed size:" << _extrLen;
|
||||
}
|
||||
|
||||
void MountUtilsLog(std::string msg) {
|
||||
qDebug() << "mountutils:" << msg.c_str();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue