mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
FS#13993 - rbutl push decompression onto a separate thread (Alessio Lenzi)
During Rockbox installation or update, the progress window currently becomes unresponsive to screen-reader navigation while archive inspection, free-space calculation and extraction are in progress. A blind user cannot reliably move through or read the status messages and therefore cannot determine what the utility is doing, how far the installation has progressed, or whether an error has occurred. The cause is that these operations are performed synchronously in the GUI thread after a package has been downloaded. Although the installation itself continues, the user interface cannot process keyboard input and accessibility events reliably until the operation finishes. The attached patch moves the package installation work to a low-priority QThread. Downloading remains asynchronous as before, while progress and log signals from archive extraction are delivered back to the GUI thread through Qt connections. This keeps the progress window fully navigable with a screen reader throughout the installation. Change-Id: I9a36a736e5b4bf98de8c3a71151679a4c1333dc7
This commit is contained in:
parent
3664373ce7
commit
3e996ec73e
2 changed files with 147 additions and 45 deletions
|
|
@ -23,13 +23,118 @@
|
|||
#include "ziputil.h"
|
||||
#include "Logger.h"
|
||||
|
||||
class ZipInstallThread : public QThread
|
||||
{
|
||||
public:
|
||||
enum Result {
|
||||
Success,
|
||||
OpenFailed,
|
||||
NotEnoughSpace,
|
||||
ExtractionFailed,
|
||||
CopyFailed
|
||||
};
|
||||
|
||||
explicit ZipInstallThread(ZipInstaller *owner)
|
||||
: QThread(owner), m_owner(owner), m_unzip(true),
|
||||
m_result(OpenFailed), m_requiredSpace(0)
|
||||
{
|
||||
}
|
||||
|
||||
void setArchive(const QString& file, const QString& mountpoint)
|
||||
{
|
||||
m_file = file;
|
||||
m_mountpoint = mountpoint;
|
||||
m_unzip = true;
|
||||
}
|
||||
|
||||
void setCopy(const QString& file, const QString& mountpoint,
|
||||
const QString& target)
|
||||
{
|
||||
m_file = file;
|
||||
m_mountpoint = mountpoint;
|
||||
m_target = target;
|
||||
m_unzip = false;
|
||||
}
|
||||
|
||||
Result result(void) const { return m_result; }
|
||||
QStringList installedFiles(void) const { return m_installedFiles; }
|
||||
protected:
|
||||
void run(void) override
|
||||
{
|
||||
if(m_unzip) {
|
||||
ZipUtil zip(nullptr);
|
||||
connect(&zip, &ZipUtil::logProgress,
|
||||
m_owner, &ZipInstaller::logProgress);
|
||||
connect(&zip, &ZipUtil::logItem,
|
||||
m_owner, &ZipInstaller::logItem);
|
||||
|
||||
if(!zip.open(m_file, QuaZip::mdUnzip)) {
|
||||
m_result = OpenFailed;
|
||||
return;
|
||||
}
|
||||
|
||||
m_requiredSpace = zip.totalUncompressedSize(
|
||||
Utils::filesystemClusterSize(m_mountpoint));
|
||||
if((qint64)Utils::filesystemFree(m_mountpoint)
|
||||
< m_requiredSpace + 1000000) {
|
||||
zip.close();
|
||||
m_result = NotEnoughSpace;
|
||||
return;
|
||||
}
|
||||
|
||||
m_installedFiles = zip.files();
|
||||
if(!zip.extractArchive(m_mountpoint)) {
|
||||
m_result = ExtractionFailed;
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
zip.close();
|
||||
}
|
||||
else {
|
||||
QString destfile = m_mountpoint + "/" + m_target;
|
||||
QString path = QFileInfo(destfile).absolutePath();
|
||||
if(!QDir().mkpath(path)) {
|
||||
m_result = CopyFailed;
|
||||
return;
|
||||
}
|
||||
QFile(destfile).remove();
|
||||
if(!QFile::copy(m_file, destfile)) {
|
||||
m_result = CopyFailed;
|
||||
return;
|
||||
}
|
||||
m_installedFiles.append(m_target);
|
||||
}
|
||||
|
||||
m_result = Success;
|
||||
}
|
||||
|
||||
private:
|
||||
ZipInstaller *m_owner;
|
||||
QString m_file;
|
||||
QString m_mountpoint;
|
||||
QString m_target;
|
||||
QStringList m_installedFiles;
|
||||
bool m_unzip;
|
||||
Result m_result;
|
||||
qint64 m_requiredSpace;
|
||||
};
|
||||
|
||||
ZipInstaller::ZipInstaller(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_unzip(true), m_usecache(false), m_getter(nullptr)
|
||||
m_unzip(true), m_usecache(false), m_getter(nullptr),
|
||||
m_installThread(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ZipInstaller::~ZipInstaller()
|
||||
{
|
||||
if(m_installThread && m_installThread->isRunning()) {
|
||||
m_installThread->wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ZipInstaller::install()
|
||||
{
|
||||
LOG_INFO() << "initializing installation";
|
||||
|
|
@ -106,7 +211,6 @@ void ZipInstaller::installStart()
|
|||
void ZipInstaller::downloadDone(QNetworkReply::NetworkError error)
|
||||
{
|
||||
LOG_INFO() << "download done, error:" << error;
|
||||
QStringList zipContents; // needed later
|
||||
// update progress bar
|
||||
|
||||
emit logProgress(1, 1);
|
||||
|
|
@ -128,64 +232,57 @@ void ZipInstaller::downloadDone(QNetworkReply::NetworkError error)
|
|||
else {
|
||||
emit logItem(tr("Download finished."),LOGOK);
|
||||
}
|
||||
QCoreApplication::processEvents();
|
||||
if(m_unzip) {
|
||||
// unzip downloaded file
|
||||
LOG_INFO() << "about to unzip" << m_file << "to" << m_mountpoint;
|
||||
|
||||
emit logItem(tr("Extracting file."), LOGINFO);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
ZipUtil zip(this);
|
||||
connect(&zip, &ZipUtil::logProgress, this, &ZipInstaller::logProgress);
|
||||
connect(&zip, &ZipUtil::logItem, this, &ZipInstaller::logItem);
|
||||
zip.open(m_file, QuaZip::mdUnzip);
|
||||
// check for free space. Make sure after installation will still be
|
||||
// some room for operating (also includes calculation mistakes due to
|
||||
// cluster sizes on the player).
|
||||
if((qint64)Utils::filesystemFree(m_mountpoint)
|
||||
< (zip.totalUncompressedSize(
|
||||
Utils::filesystemClusterSize(m_mountpoint))
|
||||
+ 1000000)) {
|
||||
emit logItem(tr("Not enough disk space! Aborting."), LOGERROR);
|
||||
emit logProgress(1, 1);
|
||||
emit done(true);
|
||||
return;
|
||||
}
|
||||
zipContents = zip.files();
|
||||
if(!zip.extractArchive(m_mountpoint)) {
|
||||
emit logItem(tr("Extraction failed!"), LOGERROR);
|
||||
emit logProgress(1, 1);
|
||||
emit done(true);
|
||||
return;
|
||||
}
|
||||
zip.close();
|
||||
m_installThread = new ZipInstallThread(this);
|
||||
m_installThread->setArchive(m_file, m_mountpoint);
|
||||
}
|
||||
else {
|
||||
if (m_target.isEmpty())
|
||||
m_target = QUrl(m_url).fileName();
|
||||
QString destfile = m_mountpoint + "/" + m_target;
|
||||
// only copy the downloaded file to the output location / name
|
||||
emit logItem(tr("Installing file."), LOGINFO);
|
||||
LOG_INFO() << "saving downloaded file (no extraction) to" << destfile;
|
||||
// Keep the temporary file materialized while the worker copies it.
|
||||
m_downloadFile->open();
|
||||
m_installThread = new ZipInstallThread(this);
|
||||
m_installThread->setCopy(m_file, m_mountpoint, m_target);
|
||||
}
|
||||
|
||||
m_downloadFile->open(); // copy fails if file is not opened (filename issue?)
|
||||
// make sure the required path is existing
|
||||
QString path = QFileInfo(destfile).absolutePath();
|
||||
QDir p;
|
||||
p.mkpath(path);
|
||||
// QFile::copy() doesn't overwrite files, so remove old one first
|
||||
// TODO: compare old and new file and fail if those are different.
|
||||
QFile(destfile).remove();
|
||||
if(!m_downloadFile->copy(destfile)) {
|
||||
connect(m_installThread, &QThread::finished,
|
||||
this, &ZipInstaller::installFinished);
|
||||
m_installThread->start(QThread::LowPriority);
|
||||
}
|
||||
|
||||
|
||||
void ZipInstaller::installFinished()
|
||||
{
|
||||
ZipInstallThread *thread = m_installThread;
|
||||
m_installThread = nullptr;
|
||||
ZipInstallThread::Result result = thread->result();
|
||||
QStringList zipContents = thread->installedFiles();
|
||||
thread->deleteLater();
|
||||
|
||||
emit logProgress(1, 1);
|
||||
switch(result) {
|
||||
case ZipInstallThread::Success:
|
||||
break;
|
||||
case ZipInstallThread::NotEnoughSpace:
|
||||
emit logItem(tr("Not enough disk space! Aborting."), LOGERROR);
|
||||
emit done(true);
|
||||
return;
|
||||
case ZipInstallThread::CopyFailed:
|
||||
emit logItem(tr("Installing file failed."), LOGERROR);
|
||||
emit done(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// add file to log
|
||||
zipContents.append(m_target);
|
||||
case ZipInstallThread::OpenFailed:
|
||||
case ZipInstallThread::ExtractionFailed:
|
||||
emit logItem(tr("Extraction failed!"), LOGERROR);
|
||||
emit done(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_logver.isEmpty()) {
|
||||
// if no version info is set use the timestamp of the server file.
|
||||
m_logver = m_getter->timestamp().toString(Qt::ISODate);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
#include "httpget.h"
|
||||
#include "Logger.h"
|
||||
|
||||
class ZipInstallThread;
|
||||
|
||||
/** Install a file or zip.
|
||||
* Downloads file(s) from a given URL, and installs by either extracting or
|
||||
* copying it to the target path set by setMountpoint().
|
||||
|
|
@ -37,6 +39,7 @@ class ZipInstaller : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
ZipInstaller(QObject* parent);
|
||||
~ZipInstaller();
|
||||
void install(void);
|
||||
void setMountPoint(QString& mountpoint) {m_mountpoint = mountpoint;}
|
||||
void setUrl(QString& url){m_urllist = QStringList(url);}
|
||||
|
|
@ -59,6 +62,7 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void downloadDone(QNetworkReply::NetworkError error);
|
||||
void installFinished(void);
|
||||
void installStart(void);
|
||||
void installContinue(void);
|
||||
|
||||
|
|
@ -79,6 +83,7 @@ private:
|
|||
|
||||
HttpGet *m_getter;
|
||||
QTemporaryFile *m_downloadFile;
|
||||
ZipInstallThread *m_installThread;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue