From 6971b49f85d9ddeb2da968a594d65affec3d2b44 Mon Sep 17 00:00:00 2001 From: Benjamin Dobell Date: Tue, 10 Mar 2015 02:49:32 +1100 Subject: Lots of Frontend refactoring with a few bug fixes There is still far too much overly convoluted code. However, the introduction of some RAII when dealing with files has made the code-base slightly less error prone. --- .gitignore | 3 +- CMakeLists.txt | 2 - heimdall-frontend/CMakeLists.txt | 8 +- heimdall-frontend/source/FirmwareInfo.cpp | 10 +- heimdall-frontend/source/GZipFile.cpp | 51 +++++ heimdall-frontend/source/GZipFile.h | 81 ++++++++ heimdall-frontend/source/PackageData.cpp | 33 +-- heimdall-frontend/source/PackageData.h | 27 ++- heimdall-frontend/source/Packaging.cpp | 318 +++++++++++++---------------- heimdall-frontend/source/Packaging.h | 16 +- heimdall-frontend/source/aboutform.cpp | 6 +- heimdall-frontend/source/mainwindow.cpp | 104 +++++----- heimdall-frontend/source/mainwindow.h | 2 +- heimdall-frontend/source/qml/DropFiles.qml | 27 +-- heimdall-frontend/source/qml/FileUtils.js | 44 ++++ heimdall-frontend/source/qml/qml.qrc | 5 +- 16 files changed, 445 insertions(+), 292 deletions(-) create mode 100644 heimdall-frontend/source/GZipFile.cpp create mode 100644 heimdall-frontend/source/GZipFile.h create mode 100644 heimdall-frontend/source/qml/FileUtils.js diff --git a/.gitignore b/.gitignore index de1c512..cc506df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store ._* .idea/ -/build/ +/*build/ +CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt index 258fcb2..4fb8acf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 2.8.11) -set(Qt5Quick_DIR "/usr/local/Cellar/qt5/5.4.0/lib/cmake/Qt5Quick" CACHE FILEPATH "Qt5Quick path" FORCE) - set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) diff --git a/heimdall-frontend/CMakeLists.txt b/heimdall-frontend/CMakeLists.txt index ceaffd0..da572cb 100644 --- a/heimdall-frontend/CMakeLists.txt +++ b/heimdall-frontend/CMakeLists.txt @@ -29,17 +29,19 @@ set(HEIMDALL_FRONTEND_SOURCE_FILES source/aboutform.cpp source/Alerts.cpp source/FirmwareInfo.cpp + source/GZipFile.cpp source/main.cpp source/mainwindow.cpp source/PackageData.cpp source/Packaging.cpp) set(HEIMDALL_FRONTEND_QML_FILES + source/qml/DropFiles.qml + source/qml/DropFilesForm.qml + source/qml/FileUtils.js source/qml/main.qml source/qml/Root.qml - source/qml/RootForm.qml - source/qml/DropFiles.qml - source/qml/DropFilesForm.qml) + source/qml/RootForm.qml) qt5_wrap_ui(HEIMDALL_FRONTEND_FORMS mainwindow.ui diff --git a/heimdall-frontend/source/FirmwareInfo.cpp b/heimdall-frontend/source/FirmwareInfo.cpp index c01d636..7a1043c 100644 --- a/heimdall-frontend/source/FirmwareInfo.cpp +++ b/heimdall-frontend/source/FirmwareInfo.cpp @@ -720,10 +720,10 @@ void FirmwareInfo::WriteXml(QXmlStreamWriter& xml) const xml.writeStartElement("developers"); - for (int i = 0; i < developers.length(); i++) + for (const QString& developer : developers) { xml.writeStartElement("name"); - xml.writeCharacters(developers[i]); + xml.writeCharacters(developer); xml.writeEndElement(); } @@ -745,8 +745,10 @@ void FirmwareInfo::WriteXml(QXmlStreamWriter& xml) const xml.writeStartElement("devices"); - for (int i = 0; i < deviceInfos.length(); i++) - deviceInfos[i].WriteXml(xml); + for (const DeviceInfo& deviceInfo : deviceInfos) + { + deviceInfo.WriteXml(xml); + } xml.writeEndElement(); diff --git a/heimdall-frontend/source/GZipFile.cpp b/heimdall-frontend/source/GZipFile.cpp new file mode 100644 index 0000000..af75f39 --- /dev/null +++ b/heimdall-frontend/source/GZipFile.cpp @@ -0,0 +1,51 @@ +#import "GZipFile.h" + +using namespace HeimdallFrontend; + +GZipFile::GZipFile(const QString& path) : + file(path), + gzFile(nullptr) +{ +} + +GZipFile::~GZipFile() +{ + Close(); + + if (temporary) + { + file.remove(); + } +} + +bool GZipFile::Open(Mode mode) +{ + if (!file.isOpen() && !file.open(mode == GZipFile::ReadOnly ? QFile::ReadOnly : QFile::WriteOnly)) + { + return (false); + } + + gzFile = gzdopen(file.handle(), mode == GZipFile::ReadOnly ? "rb" : "wb"); + return (gzFile != nullptr); +} + +void GZipFile::Close() +{ + file.close(); + gzclose(gzFile); +} + +int GZipFile::Read(void *buffer, int length) +{ + return (length >= 0 && !file.isWritable() ? gzread(gzFile, buffer, length) : -1); +} + +bool GZipFile::Write(void *buffer, int length) +{ + return (length >= 0 && gzwrite(gzFile, buffer, length) == length); +} + +qint64 GZipFile::Offset() const +{ + return gzoffset(gzFile); +} diff --git a/heimdall-frontend/source/GZipFile.h b/heimdall-frontend/source/GZipFile.h new file mode 100644 index 0000000..c4e225c --- /dev/null +++ b/heimdall-frontend/source/GZipFile.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE.*/ + +#ifndef GZIPFILE_H +#define GZIPFILE_H + +// Qt +#include +#include + +// zlib +#include "zlib.h" + +namespace HeimdallFrontend +{ + class GZipFile + { + public: + + enum Mode + { + ReadOnly = 0, + WriteOnly + }; + + private: + + QFile file; + gzFile gzFile; + + bool temporary; + + public: + + GZipFile(const QString& path); + ~GZipFile(); + + bool Open(Mode mode); + void Close(); + + int Read(void *buffer, int length); + bool Write(void *buffer, int length); + + qint64 Offset() const; + + bool IsTemporary() const + { + return temporary; + } + + // Delete the file when we go out of scope? + void SetTemporary(bool temporary) + { + this->temporary = temporary; + } + + qint64 Size() const + { + return file.size(); + } + }; +} + +#endif diff --git a/heimdall-frontend/source/PackageData.cpp b/heimdall-frontend/source/PackageData.cpp index c3689b3..23ef720 100644 --- a/heimdall-frontend/source/PackageData.cpp +++ b/heimdall-frontend/source/PackageData.cpp @@ -30,37 +30,40 @@ PackageData::PackageData() PackageData::~PackageData() { - for (int i = 0; i < files.length(); i++) - delete files[i]; + Clear(true); } -void PackageData::Clear(void) +void PackageData::Clear(bool deletePackageDirectory) { - firmwareInfo.Clear(); - - for (int i = 0; i < files.length(); i++) - delete files[i]; + if (deletePackageDirectory) + { + packageDirectory.removeRecursively(); + } - files.clear(); + packageDirectory.setPath(QString()); + firmwareInfo.Clear(); + filePaths.clear(); } -bool PackageData::ReadFirmwareInfo(QFile *file) +bool PackageData::ReadFirmwareInfo(const QString& path) { - if (!file->open(QFile::ReadOnly)) + QFile file(path); + + if (!file.open(QFile::ReadOnly)) { - Alerts::DisplayError(QString("Failed to open file: \1%s").arg(file->fileName())); + Alerts::DisplayError(QString("Failed to open file: %1").arg(path)); return (false); } - QXmlStreamReader xml(file); + QXmlStreamReader xml(&file); bool success = firmwareInfo.ParseXml(xml); - file->close(); - return (success); } bool PackageData::IsCleared(void) const { - return (firmwareInfo.IsCleared() && files.isEmpty()); + return (packageDirectory.path().length() == 0 + && firmwareInfo.IsCleared() + && filePaths.isEmpty()); } diff --git a/heimdall-frontend/source/PackageData.h b/heimdall-frontend/source/PackageData.h index 84f081c..28be679 100644 --- a/heimdall-frontend/source/PackageData.h +++ b/heimdall-frontend/source/PackageData.h @@ -22,7 +22,7 @@ #define PACKAGEDATA_H // Qt -#include +#include // Heimdall Frontend #include "FirmwareInfo.h" @@ -34,15 +34,16 @@ namespace HeimdallFrontend private: FirmwareInfo firmwareInfo; - QList files; + QList filePaths; + QDir packageDirectory; public: PackageData(); ~PackageData(); - void Clear(void); - bool ReadFirmwareInfo(QFile *file); + void Clear(bool deletePackageDirectory = true); + bool ReadFirmwareInfo(const QString& path); bool IsCleared(void) const; @@ -56,20 +57,24 @@ namespace HeimdallFrontend return (firmwareInfo); } - const QList& GetFiles(void) const + const QList& GetFilePaths(void) const { - return (files); + return (filePaths); } - QList& GetFiles(void) + QList& GetFilePaths(void) { - return (files); + return (filePaths); } - // Simply clears the files list, it does delete/close any files. - void RemoveAllFiles(void) + void SetPackagePath(const QString& path) { - files.clear(); + packageDirectory.setPath(path); + } + + QString GetPackagePath() const + { + return packageDirectory.path(); } }; } diff --git a/heimdall-frontend/source/Packaging.cpp b/heimdall-frontend/source/Packaging.cpp index 7a27b3c..7e0e1c0 100644 --- a/heimdall-frontend/source/Packaging.cpp +++ b/heimdall-frontend/source/Packaging.cpp @@ -30,31 +30,85 @@ // Qt #include -#include #include +#include +#include // Heimdall Frontend #include "Alerts.h" #include "Packaging.h" +#include "GZipFile.h" using namespace HeimdallFrontend; -const qint64 Packaging::kMaxFileSize = 8589934592ll; +const qint64 Packaging::kMaxFileSize = 4294967295ll; const char *Packaging::ustarMagic = "ustar"; -bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) +bool Packaging::DecompressGZippedFile(const QString &path, const QString &outputPath) +{ + GZipFile gzipFile(path); + + if (!gzipFile.Open(GZipFile::ReadOnly)) + { + Alerts::DisplayError(QString("Failed to open file:\n%1").arg(path)); + return (false); + } + + qint64 compressedFileSize = gzipFile.Size(); + + QFile outputFile(outputPath); + + if (!outputFile.open(QIODevice::WriteOnly)) + { + Alerts::DisplayError(QString("Failed to open decompression output file:\n%1").arg(outputFile.fileName())); + return (false); + } + + QProgressDialog progressDialog("Decompressing...", "Cancel", 0, 1000); + progressDialog.setWindowModality(Qt::ApplicationModal); + progressDialog.setWindowTitle("Heimdall Frontend"); + + char buffer[kExtractBufferLength]; + int bytesRead; + + do + { + bytesRead = gzipFile.Read(buffer, kExtractBufferLength); + + if (bytesRead == -1) + { + progressDialog.close(); + Alerts::DisplayError("Error decompressing archive."); + return (false); + } + + outputFile.write(buffer, bytesRead); + progressDialog.setValue((1000ll * gzipFile.Offset()) / compressedFileSize); + + if (progressDialog.wasCanceled()) + { + return (false); + } + } while (bytesRead > 0); + + progressDialog.close(); + return (true); +} + +bool Packaging::ExtractTar(QFile& tarFile, const QDir& outputDirectory, QList& outputFilePaths) { TarHeader tarHeader; - if (!tarFile.open()) + if (!tarFile.open(QFile::ReadOnly)) { - Alerts::DisplayError(QString("Error opening temporary TAR archive:\n%1").arg(tarFile.fileName())); + Alerts::DisplayError(QString("Error opening archive:\n%1").arg(tarFile.fileName())); return (false); } bool previousEmpty = false; - QProgressDialog progressDialog("Extracting files...", "Cancel", 0, tarFile.size()); + qint64 tarFileSize = tarFile.size(); + QProgressDialog progressDialog("Extracting archive...", "Cancel", 0, 1000); progressDialog.setWindowModality(Qt::ApplicationModal); progressDialog.setWindowTitle("Heimdall Frontend"); @@ -82,7 +136,6 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) return (false); } - //bool ustarFormat = strcmp(tarHeader.fields.magic, ustarMagic) == 0; bool empty = true; for (int i = 0; i < TarHeader::kBlockLength; i++) @@ -104,21 +157,6 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) } else { - int checksum = 0; - - for (char *bufferIndex = tarHeader.buffer; bufferIndex < tarHeader.fields.checksum; bufferIndex++) - checksum += static_cast(*bufferIndex); - - checksum += 8 * ' '; - checksum += static_cast(tarHeader.fields.typeFlag); - - // Both the TAR and USTAR formats have terrible documentation, it's not clear if the following code is required. - /*if (ustarFormat) - { - for (char *bufferIndex = tarHeader.fields.linkName; bufferIndex < tarHeader.fields.prefix + 155; bufferIndex++) - checksum += static_cast(*bufferIndex); - }*/ - bool parsed = false; // The size field is not always null terminated, so we must create a copy and null terminate it for parsing. @@ -143,19 +181,21 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) // We're working with a file. QString filename = QString::fromUtf8(tarHeader.fields.name); - QTemporaryFile *outputFile = new QTemporaryFile("XXXXXX-" + filename); - packageData->GetFiles().append(outputFile); + QString filePath = outputDirectory.path() + "/" + filename; + QFile outputFile(filePath); - if (!outputFile->open()) + if (!outputFile.open(QFile::WriteOnly)) { progressDialog.close(); - Alerts::DisplayError(QString("Failed to open output file: \n%1").arg(outputFile->fileName())); + Alerts::DisplayError(QString("Failed to open output file: \n%1").arg(outputFile.fileName())); tarFile.close(); return (false); } + outputFilePaths.append(filePath); + qulonglong dataRemaining = fileSize; char readBuffer[TarHeader::kBlockReadCount * TarHeader::kBlockLength]; @@ -173,25 +213,23 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) Alerts::DisplayError("Unexpected read error whilst extracting package files."); tarFile.close(); - outputFile->close(); - - remove(outputFile->fileName().toStdString().c_str()); + outputFile.close(); + outputFile.remove(); return (false); } - outputFile->write(readBuffer, fileDataToRead); + outputFile.write(readBuffer, fileDataToRead); dataRemaining -= fileDataToRead; - progressDialog.setValue(tarFile.pos()); + progressDialog.setValue((1000ll * tarFile.pos()) / tarFileSize); if (progressDialog.wasCanceled()) { tarFile.close(); - outputFile->close(); - - remove(outputFile->fileName().toStdString().c_str()); + outputFile.close(); + outputFile.remove(); progressDialog.close(); @@ -199,7 +237,7 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) } } - outputFile->close(); + outputFile.close(); } else { @@ -221,7 +259,7 @@ bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData) return (true); } -bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, const QString& entryFilename) +bool Packaging::WriteTarEntry(const QString& entryFilename, const QString& filePath, QFile& outputTarFile) { TarHeader tarHeader; memset(tarHeader.buffer, 0, TarHeader::kBlockLength); @@ -316,7 +354,7 @@ bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, sprintf(tarHeader.fields.checksum, "%07o", checksum); // Write the header to the TAR file. - tarFile->write(tarHeader.buffer, TarHeader::kBlockLength); + outputTarFile.write(tarHeader.buffer, TarHeader::kBlockLength); char buffer[TarHeader::kBlockWriteCount * TarHeader::kBlockLength]; qint64 offset = 0; @@ -325,7 +363,7 @@ bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, { qint64 dataRead = file.read(buffer, TarHeader::kBlockWriteCount * TarHeader::kBlockLength); - if (tarFile->write(buffer, dataRead) != dataRead) + if (outputTarFile.write(buffer, dataRead) != dataRead) { Alerts::DisplayError("Failed to write data to the temporary TAR file."); return (false); @@ -336,7 +374,7 @@ bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, int remainingBlockLength = TarHeader::kBlockLength - dataRead % TarHeader::kBlockLength; memset(buffer, 0, remainingBlockLength); - if (tarFile->write(buffer, remainingBlockLength) != remainingBlockLength) + if (outputTarFile.write(buffer, remainingBlockLength) != remainingBlockLength) { Alerts::DisplayError("Failed to write data to the temporary TAR file."); return (false); @@ -349,7 +387,7 @@ bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, return (true); } -bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarFile) +bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QFile& outputTarFile) { const QList& fileInfos = firmwareInfo.GetFileInfos(); @@ -371,10 +409,10 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF firmwareInfo.WriteXml(xml); firmwareXmlFile.close(); - if (!tarFile->open()) + if (!outputTarFile.open(QFile::WriteOnly)) { progressDialog.close(); - Alerts::DisplayError(QString("Failed to open file: \n%1").arg(tarFile->fileName())); + Alerts::DisplayError(QString("Failed to open file: \n%1").arg(outputTarFile.fileName())); return (false); } @@ -407,10 +445,10 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF return (false); } - if (!WriteTarEntry(fileInfos[i].GetFilename(), tarFile, filename)) + if (!WriteTarEntry(filename, fileInfos[i].GetFilename(), outputTarFile)) { - tarFile->resize(0); - tarFile->close(); + outputTarFile.resize(0); + outputTarFile.close(); progressDialog.close(); @@ -421,8 +459,8 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF if (progressDialog.wasCanceled()) { - tarFile->resize(0); - tarFile->close(); + outputTarFile.resize(0); + outputTarFile.close(); progressDialog.close(); @@ -443,10 +481,10 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF return (false); } - if (!WriteTarEntry(firmwareInfo.GetPitFilename(), tarFile, pitFilename)) + if (!WriteTarEntry(pitFilename, firmwareInfo.GetPitFilename(), outputTarFile)) { - tarFile->resize(0); - tarFile->close(); + outputTarFile.resize(0); + outputTarFile.close(); return (false); } @@ -455,18 +493,18 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF if (progressDialog.wasCanceled()) { - tarFile->resize(0); - tarFile->close(); + outputTarFile.resize(0); + outputTarFile.close(); progressDialog.close(); return (false); } - if (!WriteTarEntry(firmwareXmlFile.fileName(), tarFile, "firmware.xml")) + if (!WriteTarEntry("firmware.xml", firmwareXmlFile.fileName(), outputTarFile)) { - tarFile->resize(0); - tarFile->close(); + outputTarFile.resize(0); + outputTarFile.close(); return (false); } @@ -478,97 +516,51 @@ bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarF char emptyEntry[TarHeader::kBlockLength]; memset(emptyEntry, 0, TarHeader::kBlockLength); - tarFile->write(emptyEntry, TarHeader::kBlockLength); - tarFile->write(emptyEntry, TarHeader::kBlockLength); + outputTarFile.write(emptyEntry, TarHeader::kBlockLength); + outputTarFile.write(emptyEntry, TarHeader::kBlockLength); - tarFile->close(); + outputTarFile.close(); return (true); } -bool Packaging::ExtractPackage(const QString& packagePath, PackageData *packageData) +bool Packaging::ExtractPackage(const QString& packagePath, PackageData& packageData) { - FILE *compressedPackageFile = fopen(packagePath.toStdString().c_str(), "rb"); + QTemporaryDir outputDirectory; - if (!compressedPackageFile) + if (!outputDirectory.isValid()) { - Alerts::DisplayError(QString("Failed to open package:\n%1").arg(packagePath)); + Alerts::DisplayError("Failed to create package output directory."); return (false); } - fseek(compressedPackageFile, 0, SEEK_END); - quint64 compressedFileSize = ftell(compressedPackageFile); - rewind(compressedPackageFile); - - gzFile packageFile = gzdopen(fileno(compressedPackageFile), "rb"); + QTemporaryFile tarFile; + tarFile.open(); + tarFile.close(); - QTemporaryFile outputTar("XXXXXX.tar"); + QList decompressedFilePaths; - if (!outputTar.open()) + if (!DecompressGZippedFile(packagePath, tarFile.fileName()) + || !ExtractTar(tarFile, QDir(outputDirectory.path()), decompressedFilePaths)) { - Alerts::DisplayError("Failed to open temporary TAR archive."); - gzclose(packageFile); - return (false); } - char buffer[kExtractBufferLength]; - int bytesRead; - quint64 totalBytesRead = 0; - - QProgressDialog progressDialog("Decompressing package...", "Cancel", 0, compressedFileSize); - progressDialog.setWindowModality(Qt::ApplicationModal); - progressDialog.setWindowTitle("Heimdall Frontend"); - - do - { - bytesRead = gzread(packageFile, buffer, kExtractBufferLength); - - if (bytesRead == -1) - { - progressDialog.close(); - Alerts::DisplayError("Error decompressing archive."); - - gzclose(packageFile); - - return (false); - } - - outputTar.write(buffer, bytesRead); - - totalBytesRead += bytesRead; - progressDialog.setValue(totalBytesRead); - - if (progressDialog.wasCanceled()) - { - gzclose(packageFile); - progressDialog.close(); - - return (false); - } - } while (bytesRead > 0); - - progressDialog.close(); - - outputTar.close(); - gzclose(packageFile); // Closes packageFile and compressedPackageFile - - if (!ExtractTar(outputTar, packageData)) - return (false); - // Find and read firmware.xml - for (int i = 0; i < packageData->GetFiles().length(); i++) + for (const QString& path : decompressedFilePaths) { - QTemporaryFile *file = packageData->GetFiles()[i]; - - if (file->fileTemplate() == "XXXXXX-firmware.xml") + if (path.endsWith("firmware.xml")) { - if (!packageData->ReadFirmwareInfo(file)) + if (!packageData.ReadFirmwareInfo(path)) { - packageData->Clear(); + packageData.Clear(); return (false); } + outputDirectory.setAutoRemove(false); + + packageData.GetFilePaths().append(decompressedFilePaths); + packageData.SetPackagePath(outputDirectory.path()); return (true); } } @@ -579,44 +571,39 @@ bool Packaging::ExtractPackage(const QString& packagePath, PackageData *packageD bool Packaging::BuildPackage(const QString& packagePath, const FirmwareInfo& firmwareInfo) { - FILE *compressedPackageFile = fopen(packagePath.toStdString().c_str(), "wb"); + GZipFile packageFile(packagePath); - if (!compressedPackageFile) + if (!packageFile.Open(GZipFile::WriteOnly)) { Alerts::DisplayError(QString("Failed to create package:\n%1").arg(packagePath)); return (false); } + packageFile.SetTemporary(true); + QTemporaryFile tar("XXXXXX.tar"); - if (!CreateTar(firmwareInfo, &tar)) + if (!CreateTar(firmwareInfo, tar)) { - fclose(compressedPackageFile); - remove(packagePath.toStdString().c_str()); - return (false); } if (!tar.open()) { Alerts::DisplayError(QString("Failed to open temporary file: \n%1").arg(tar.fileName())); - - fclose(compressedPackageFile); - remove(packagePath.toStdString().c_str()); - return (false); } - - gzFile packageFile = gzdopen(fileno(compressedPackageFile), "wb"); char buffer[kCompressBufferLength]; qint64 totalBytesRead = 0; - int bytesRead; - QProgressDialog progressDialog("Compressing package...", "Cancel", 0, tar.size()); + quint64 tarSize = tar.size(); + QProgressDialog progressDialog("Compressing package...", "Cancel", 0, 1000); progressDialog.setWindowModality(Qt::ApplicationModal); progressDialog.setWindowTitle("Heimdall Frontend"); + int bytesRead; + do { bytesRead = tar.read(buffer, kCompressBufferLength); @@ -625,42 +612,29 @@ bool Packaging::BuildPackage(const QString& packagePath, const FirmwareInfo& fir { progressDialog.close(); Alerts::DisplayError("Error reading temporary TAR file."); - - gzclose(packageFile); - remove(packagePath.toStdString().c_str()); - return (false); } - if (gzwrite(packageFile, buffer, bytesRead) != bytesRead) + if (!packageFile.Write(buffer, bytesRead)) { progressDialog.close(); Alerts::DisplayError("Error compressing package."); - - gzclose(packageFile); - remove(packagePath.toStdString().c_str()); - return (false); } totalBytesRead += bytesRead; - progressDialog.setValue(totalBytesRead); + progressDialog.setValue((1000ll * totalBytesRead) / tarSize); if (progressDialog.wasCanceled()) { - gzclose(packageFile); - remove(packagePath.toStdString().c_str()); - progressDialog.close(); - return (false); } } while (bytesRead > 0); progressDialog.close(); - gzclose(packageFile); // Closes packageFile and compressedPackageFile - + packageFile.SetTemporary(false); return (true); } @@ -709,14 +683,14 @@ QString Packaging::ClashlessFilename(const QList& fileInfos, int fileI bool validIndexOffset = true; // Before we append a rename index we must ensure it doesn't produce further collisions. - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { - int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/'); + int lastSlash = fileInfo.GetFilename().lastIndexOf('/'); if (lastSlash < 0) - lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\'); + lastSlash = fileInfo.GetFilename().lastIndexOf('\\'); - QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1); + QString otherFilename = fileInfo.GetFilename().mid(lastSlash + 1); if (otherFilename.length() > filename.length() + 1) { @@ -778,14 +752,14 @@ QString Packaging::ClashlessFilename(const QList& fileInfos, int fileI bool valid = true; - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { - int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/'); + int lastSlash = fileInfo.GetFilename().lastIndexOf('/'); if (lastSlash < 0) - lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\'); + lastSlash = fileInfo.GetFilename().lastIndexOf('\\'); - if (filename == fileInfos[i].GetFilename().mid(lastSlash + 1)) + if (filename == fileInfo.GetFilename().mid(lastSlash + 1)) { valid = false; break; @@ -809,14 +783,14 @@ QString Packaging::ClashlessFilename(const QList& fileInfos, const QSt unsigned int renameIndex = 0; // Check for name clashes - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { - int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/'); + int lastSlash = fileInfo.GetFilename().lastIndexOf('/'); if (lastSlash < 0) - lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\'); + lastSlash = fileInfo.GetFilename().lastIndexOf('\\'); - QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1); + QString otherFilename = fileInfo.GetFilename().mid(lastSlash + 1); if (filename == otherFilename) renameIndex++; @@ -842,14 +816,14 @@ QString Packaging::ClashlessFilename(const QList& fileInfos, const QSt bool validIndexOffset = true; // Before we append a rename index we must ensure it doesn't produce further collisions. - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { - int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/'); + int lastSlash = fileInfo.GetFilename().lastIndexOf('/'); if (lastSlash < 0) - lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\'); + lastSlash = fileInfo.GetFilename().lastIndexOf('\\'); - QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1); + QString otherFilename = fileInfo.GetFilename().mid(lastSlash + 1); if (otherFilename.length() > filename.length() + 1) { @@ -909,14 +883,14 @@ QString Packaging::ClashlessFilename(const QList& fileInfos, const QSt for (int i = 0; i < 8; i++) filename.append(QChar(qrand() % ('Z' - 'A' + 1) + 'A')); - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { - int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/'); + int lastSlash = fileInfo.GetFilename().lastIndexOf('/'); if (lastSlash < 0) - lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\'); + lastSlash = fileInfo.GetFilename().lastIndexOf('\\'); - if (filename == fileInfos[i].GetFilename().mid(lastSlash + 1)) + if (filename == fileInfo.GetFilename().mid(lastSlash + 1)) { valid = false; break; diff --git a/heimdall-frontend/source/Packaging.h b/heimdall-frontend/source/Packaging.h index 8ba83f7..80b6d81 100644 --- a/heimdall-frontend/source/Packaging.h +++ b/heimdall-frontend/source/Packaging.h @@ -22,9 +22,7 @@ #define PACKAGING_H // Qt -#include -#include -#include +#include // Heimdall Frontend #include "PackageData.h" @@ -99,18 +97,20 @@ namespace HeimdallFrontend kExtractBufferLength = 262144, kCompressBufferLength = 262144 }; - + + static bool DecompressGZippedFile(const QString &path, const QString &outputPath); + // TODO: Add support for sparse files to both methods? - static bool ExtractTar(QTemporaryFile& tarFile, PackageData *packageData); + static bool ExtractTar(QFile& tarFile, const QDir& outputDirectory, QList& outputFilePaths); - static bool WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, const QString& entryFilename); - static bool CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarFile); // Uses original TAR format. + static bool WriteTarEntry(const QString& entryFilename, const QString& filePath, QFile& outputTarFile); + static bool CreateTar(const FirmwareInfo& firmwareInfo, QFile& outputTarFile); // Uses original TAR format. public: static const char *ustarMagic; - static bool ExtractPackage(const QString& packagePath, PackageData *packageData); + static bool ExtractPackage(const QString& packagePath, PackageData& packageData); static bool BuildPackage(const QString& packagePath, const FirmwareInfo& firmwareInfo); static QString ClashlessFilename(const QList& fileInfos, int fileInfoIndex); diff --git a/heimdall-frontend/source/aboutform.cpp b/heimdall-frontend/source/aboutform.cpp index 93e56fb..4be8a1b 100644 --- a/heimdall-frontend/source/aboutform.cpp +++ b/heimdall-frontend/source/aboutform.cpp @@ -60,11 +60,11 @@ void AboutForm::RetrieveHeimdallVersion(void) QStringList paths; // Ensure /usr/bin is in PATH - for (int i = 0; i < environment.length(); i++) + for (const QString& var : environment) { - if (environment[i].left(5) == "PATH=") + if (var.left(5) == "PATH=") { - paths = environment[i].mid(5).split(':'); + paths = var.mid(5).split(':'); paths.prepend("/usr/bin"); break; } diff --git a/heimdall-frontend/source/mainwindow.cpp b/heimdall-frontend/source/mainwindow.cpp index e110d69..ab83105 100644 --- a/heimdall-frontend/source/mainwindow.cpp +++ b/heimdall-frontend/source/mainwindow.cpp @@ -53,11 +53,11 @@ void MainWindow::StartHeimdall(const QStringList& arguments) QStringList paths; // Ensure /usr/local/bin and /usr/bin are in PATH. - for (int i = 0; i < environment.length(); i++) + for (const QString& var : environment) { - if (environment[i].left(5) == "PATH=") + if (var.left(5) == "PATH=") { - paths = environment[i].mid(5).split(':'); + paths = var.mid(5).split(':'); if (!paths.contains("/usr/local/bin")) paths.prepend("/usr/local/bin"); @@ -114,21 +114,20 @@ void MainWindow::UpdateUnusedPartitionIds(void) } // Remove any used partition IDs from unusedPartitionIds - QList& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos(); - - for (int i = 0; i < fileList.length(); i++) - unusedPartitionIds.removeOne(fileList[i].GetPartitionId()); + for (const FileInfo& fileInfo : workingPackageData.GetFirmwareInfo().GetFileInfos()) + unusedPartitionIds.removeOne(fileInfo.GetPartitionId()); } -bool MainWindow::ReadPit(QFile *file) +bool MainWindow::ReadPit(const QString& path) { - if(!file->open(QIODevice::ReadOnly)) + QFile pitFile(path); + + if (!pitFile.open(QIODevice::ReadOnly)) return (false); - unsigned char *buffer = new unsigned char[file->size()]; + unsigned char *buffer = new unsigned char[pitFile.size()]; - file->read(reinterpret_cast(buffer), file->size()); - file->close(); + pitFile.read(reinterpret_cast(buffer), pitFile.size()); bool success = currentPitData.Unpack(buffer); delete buffer; @@ -176,15 +175,13 @@ void MainWindow::UpdatePackageUserInterface(void) platformLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetName() + " (" + loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetVersion() + ")"); - for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetDeviceInfos().length(); i++) + for (const DeviceInfo& deviceInfo : loadedPackageData.GetFirmwareInfo().GetDeviceInfos()) { - const DeviceInfo& deviceInfo = loadedPackageData.GetFirmwareInfo().GetDeviceInfos()[i]; supportedDevicesListWidget->addItem(deviceInfo.GetManufacturer() + " " + deviceInfo.GetName() + ": " + deviceInfo.GetProduct()); } - for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetFileInfos().length(); i++) + for (const FileInfo& fileInfo : loadedPackageData.GetFirmwareInfo().GetFileInfos()) { - const FileInfo& fileInfo = loadedPackageData.GetFirmwareInfo().GetFileInfos()[i]; includedFilesListWidget->addItem(fileInfo.GetFilename()); } @@ -247,9 +244,9 @@ void MainWindow::UpdateFlashInterfaceAvailability(void) bool allPartitionsValid = true; QList& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos(); - for (int i = 0; i < fileList.length(); i++) + for (const FileInfo& fileInfo : fileList) { - if (fileList[i].GetFilename().isEmpty()) + if (fileInfo.GetFilename().isEmpty()) { allPartitionsValid = false; break; @@ -374,8 +371,8 @@ void MainWindow::UpdatePartitionNamesInterface(void) { const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()]; - for (int i = 0; i < unusedPartitionIds.length(); i++) - partitionNameComboBox->addItem(currentPitData.FindEntry(unusedPartitionIds[i])->GetPartitionName()); + for (unsigned int id : unusedPartitionIds) + partitionNameComboBox->addItem(currentPitData.FindEntry(id)->GetPartitionName()); partitionNameComboBox->addItem(currentPitData.FindEntry(partitionInfo.GetPartitionId())->GetPartitionName()); partitionNameComboBox->setCurrentIndex(unusedPartitionIds.length()); @@ -510,7 +507,7 @@ void MainWindow::SelectFirmwarePackage(void) if (firmwarePackageLineEdit->text() != "") { - if (Packaging::ExtractPackage(firmwarePackageLineEdit->text(), &loadedPackageData)) + if (Packaging::ExtractPackage(firmwarePackageLineEdit->text(), loadedPackageData)) UpdatePackageUserInterface(); else loadedPackageData.Clear(); @@ -533,21 +530,17 @@ void MainWindow::LoadFirmwarePackage(void) { workingPackageData.Clear(); currentPitData.Clear(); - - workingPackageData.GetFiles().append(loadedPackageData.GetFiles()); - loadedPackageData.RemoveAllFiles(); - const QList packageFileInfos = loadedPackageData.GetFirmwareInfo().GetFileInfos(); - - for (int i = 0; i < packageFileInfos.length(); i++) + // Loaded packages FileInfo store filenames, but the working package FileInfo need absolute paths + for (const FileInfo& packageFileInfo : loadedPackageData.GetFirmwareInfo().GetFileInfos()) { bool fileFound = false; - for (int j = 0; j < workingPackageData.GetFiles().length(); j++) + for (const QString& packageFilePath : loadedPackageData.GetFilePaths()) { - if (workingPackageData.GetFiles()[j]->fileTemplate() == ("XXXXXX-" + packageFileInfos[i].GetFilename())) + if (packageFilePath.endsWith(packageFileInfo.GetFilename())) { - FileInfo partitionInfo(packageFileInfos[i].GetPartitionId(), QDir::current().absoluteFilePath(workingPackageData.GetFiles()[j]->fileName())); + FileInfo partitionInfo(packageFileInfo.GetPartitionId(), packageFilePath); workingPackageData.GetFirmwareInfo().GetFileInfos().append(partitionInfo); fileFound = true; @@ -556,27 +549,28 @@ void MainWindow::LoadFirmwarePackage(void) } if (!fileFound) - Alerts::DisplayWarning(QString("%1 is missing from the package.").arg(packageFileInfos[i].GetFilename())); + Alerts::DisplayWarning(QString("%1 is missing from the package.").arg(packageFileInfo.GetFilename())); } + workingPackageData.GetFilePaths().append(loadedPackageData.GetFilePaths()); + workingPackageData.SetPackagePath(loadedPackageData.GetPackagePath()); + + QString pitFilename = loadedPackageData.GetFirmwareInfo().GetPitFilename(); + // Find the PIT file and read it - for (int i = 0; i < workingPackageData.GetFiles().length(); i++) + for (const QString& filePath : workingPackageData.GetFilePaths()) { - QTemporaryFile *file = workingPackageData.GetFiles()[i]; - - if (file->fileTemplate() == ("XXXXXX-" + loadedPackageData.GetFirmwareInfo().GetPitFilename())) + if (filePath.endsWith(pitFilename)) { - workingPackageData.GetFirmwareInfo().SetPitFilename(QDir::current().absoluteFilePath(file->fileName())); + workingPackageData.GetFirmwareInfo().SetPitFilename(filePath); - if (!ReadPit(file)) + if (!ReadPit(filePath)) { Alerts::DisplayError("Failed to read PIT file."); - - loadedPackageData.Clear(); UpdatePackageUserInterface(); + UpdateUnusedPartitionIds(); workingPackageData.Clear(); - UpdateUnusedPartitionIds(); return; } @@ -584,21 +578,20 @@ void MainWindow::LoadFirmwarePackage(void) } } - UpdateUnusedPartitionIds(); workingPackageData.GetFirmwareInfo().SetRepartition(loadedPackageData.GetFirmwareInfo().GetRepartition()); workingPackageData.GetFirmwareInfo().SetNoReboot(loadedPackageData.GetFirmwareInfo().GetNoReboot()); - loadedPackageData.Clear(); + loadedPackageData.Clear(false); + + UpdateUnusedPartitionIds(); UpdatePackageUserInterface(); - firmwarePackageLineEdit->clear(); + firmwarePackageLineEdit->clear(); partitionsListWidget->clear(); // Populate partitionsListWidget with partition names (from the PIT file) - for (int i = 0; i < workingPackageData.GetFirmwareInfo().GetFileInfos().length(); i++) + for (const FileInfo& partitionInfo : workingPackageData.GetFirmwareInfo().GetFileInfos()) { - const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[i]; - const PitEntry *pitEntry = currentPitData.FindEntry(partitionInfo.GetPartitionId()); if (pitEntry) @@ -609,11 +602,12 @@ void MainWindow::LoadFirmwarePackage(void) { Alerts::DisplayError("Firmware package includes invalid partition IDs."); - loadedPackageData.GetFirmwareInfo().Clear(); + workingPackageData.Clear(); currentPitData.Clear(); - UpdateUnusedPartitionIds(); + UpdateUnusedPartitionIds(); partitionsListWidget->clear(); + return; } } @@ -803,9 +797,7 @@ void MainWindow::SelectPit(void) currentPitData.Clear(); - QFile pitFile(path); - - if (ReadPit(&pitFile)) + if (ReadPit(path)) { workingPackageData.GetFirmwareInfo().SetPitFilename(path); @@ -839,9 +831,7 @@ void MainWindow::SelectPit(void) if (!workingPackageData.GetFirmwareInfo().GetPitFilename().isEmpty()) { - QFile originalPitFile(workingPackageData.GetFirmwareInfo().GetPitFilename()); - - if (ReadPit(&originalPitFile)) + if (ReadPit(workingPackageData.GetFirmwareInfo().GetPitFilename())) { validPit = true; } @@ -919,13 +909,13 @@ void MainWindow::StartFlash(void) arguments.append("--pit"); arguments.append(firmwareInfo.GetPitFilename()); - for (int i = 0; i < fileInfos.length(); i++) + for (const FileInfo& fileInfo : fileInfos) { QString flag; - flag.sprintf("--%u", fileInfos[i].GetPartitionId()); + flag.sprintf("--%u", fileInfo.GetPartitionId()); arguments.append(flag); - arguments.append(fileInfos[i].GetFilename()); + arguments.append(fileInfo.GetFilename()); } if (firmwareInfo.GetNoReboot()) diff --git a/heimdall-frontend/source/mainwindow.h b/heimdall-frontend/source/mainwindow.h index d0b8703..685d342 100644 --- a/heimdall-frontend/source/mainwindow.h +++ b/heimdall-frontend/source/mainwindow.h @@ -109,7 +109,7 @@ namespace HeimdallFrontend void StartHeimdall(const QStringList& arguments); void UpdateUnusedPartitionIds(void); - bool ReadPit(QFile *file); + bool ReadPit(const QString& path); void UpdatePackageUserInterface(void); diff --git a/heimdall-frontend/source/qml/DropFiles.qml b/heimdall-frontend/source/qml/DropFiles.qml index ddcc90b..9187b4f 100644 --- a/heimdall-frontend/source/qml/DropFiles.qml +++ b/heimdall-frontend/source/qml/DropFiles.qml @@ -2,12 +2,11 @@ import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.2 +import "FileUtils.js" as FileUtils DropFilesForm { id: background - property var fileUrls: [] - signal nextPressed(var files) ListModel { @@ -30,15 +29,17 @@ DropFilesForm { var count = urls.length; if (count > 0) { for (var i = 0; i < count; i++) { - var url = urls[i].toString(); - var filename = url.slice(url.lastIndexOf('/') + 1, url.length); + if (FileUtils.isFile(urls[i])) { + var filename = FileUtils.filenameFromUrl(urls[i]); - fileModel.append({ - icon: "drop_zone.svg", - text: filename - }); + fileModel.append({ icon: "drop_zone.svg", text: filename }); - fileUrls.push(urls[i]); + if (FileUtils.isArchive(filename)) { + fileUrls.push(FileUtils.extractArchive(urls[i])); + } else { + fileUrls.push(urls[i]); + } + } } setFileGridVisible(true); @@ -90,12 +91,12 @@ DropFilesForm { } FileDialog { - id: browseDialog + id: browseDialog title: "Select firmware file(s)" selectMultiple: true selectFolder: false - onAccepted: { + onAccepted: { addFiles(browseDialog.fileUrls); - } - } + } + } } diff --git a/heimdall-frontend/source/qml/FileUtils.js b/heimdall-frontend/source/qml/FileUtils.js new file mode 100644 index 0000000..3735a37 --- /dev/null +++ b/heimdall-frontend/source/qml/FileUtils.js @@ -0,0 +1,44 @@ +function clipFileExtension(filename) { + var periodIndex = filename.lastIndexOf('.'); + + if (periodIndex > 0) { + return filename.slice(0, periodIndex - 1); + } else if (periodIndex === 0) { + return ""; + } + + return filename; +} + +function filenameFromUrl(url) { + var urlString = url.toString(); + return urlString.slice(urlString.lastIndexOf('/') + 1); +} + +function fileExtension(url) { + var filename = filenameFromUrl(url); + var periodIndex = filename.lastIndexOf('.'); + + if (periodIndex >= 0) { + return filename.slice(periodIndex + 1); + } + + return ""; +} + +// TODO: Real implemention - call out to C++ and validate with QFileInfo etc. +function isFile(url) { + var filename = filenameFromUrl(url); + return filename.length > 0; +} + +function isArchive(url) { + var filename = filenameFromUrl(url); + var extension = fileExtension(filename); + return (extension === 'tar' || extension === 'zip') + || (extension === 'gz' && fileExtension(clipFileExtension(filename)) === 'tar'); +} + +function extractArchive(url) { + +} diff --git a/heimdall-frontend/source/qml/qml.qrc b/heimdall-frontend/source/qml/qml.qrc index 25799fc..641c200 100644 --- a/heimdall-frontend/source/qml/qml.qrc +++ b/heimdall-frontend/source/qml/qml.qrc @@ -1,9 +1,10 @@ + DropFiles.qml + DropFilesForm.qml + FileUtils.js main.qml Root.qml RootForm.qml - DropFiles.qml - DropFilesForm.qml \ No newline at end of file -- cgit v1.2.3