diff --git a/application/application.cpp b/application/application.cpp index 96e323d1..9dfbbeb7 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -7,8 +7,9 @@ #include #include "qflipperbackend.h" +#include "updateregistry.h" #include "screencanvas.h" -#include "remotefilefetcher.h" +#include "preferences.h" #include "macros.h" @@ -44,10 +45,10 @@ void Application::initStyles() void Application::initContextProperties() { m_engine.rootContext()->setContextProperty("app", this); + m_engine.rootContext()->setContextProperty("preferences", globalPrefs()); m_engine.rootContext()->setContextProperty("deviceRegistry", &m_backend.deviceRegistry); m_engine.rootContext()->setContextProperty("firmwareUpdates", &m_backend.firmwareUpdates); m_engine.rootContext()->setContextProperty("applicationUpdates", &m_backend.applicationUpdates); - m_engine.rootContext()->setContextProperty("downloader", &m_backend.downloader); } void Application::initInstanceProperties() diff --git a/application/appupdater.cpp b/application/appupdater.cpp index 98ed1fca..9ba640bf 100644 --- a/application/appupdater.cpp +++ b/application/appupdater.cpp @@ -52,8 +52,6 @@ void AppUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo) #endif auto *file = new QFile(filePath + QStringLiteral(".part")); - check_return_void(file->open(QIODevice::ReadWrite), QStringLiteral("Failed to create file: %1.").arg(file->fileName())); - auto *fetcher = new RemoteFileFetcher(this); const auto cleanup = [=]() { diff --git a/application/assets/symbol-gear.svg b/application/assets/symbol-gear.svg new file mode 100644 index 00000000..14f09814 --- /dev/null +++ b/application/assets/symbol-gear.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + diff --git a/application/components/FlipperListDelegate.qml b/application/components/FlipperListDelegate.qml index 10f2b496..332963fd 100644 --- a/application/components/FlipperListDelegate.qml +++ b/application/components/FlipperListDelegate.qml @@ -3,20 +3,16 @@ import QtQuick.Controls 2.12 Item { signal updateRequested(var device) + + signal versionListRequested(var device) + signal screenStreamRequested(var device) + signal localUpdateRequested(var device) signal localRadioUpdateRequested(var device) signal localFUSUpdateRequested(var device) - signal localAssetsUpdateRequested(var device) - signal fixOptionBytesRequested(var device) signal fixBootRequested(var device) - signal versionListRequested(var device) - signal screenStreamRequested(var device) - - signal backupRequested(var device) - signal restoreRequested(var device) - id: item width: parent.width height: 85 @@ -25,21 +21,21 @@ Item { id: progressBar anchors.fill: parent anchors.margins: frame.border.width - value: device.progress + value: device.state.progress } Rectangle { id: frame radius: 6 anchors.fill: parent - color: device.isError ? "#3a0000" : "transparent" - border.color: device.isError ? "#d32a34" : "white" + color: device.state.isError ? "#3a0000" : "transparent" + border.color: device.state.isError ? "#d32a34" : "white" border.width: 1 } Text { id: modelLabel - text: device.model + text: device.state.model color: "darkgray" font.pixelSize: 13 anchors.verticalCenter: parent.verticalCenter @@ -49,7 +45,7 @@ Item { Rectangle { id: nameLabel - color: device.isError ? "#d32a34" : (device.isDFU ? "#0345ff" : "darkorange") + color: device.state.isError ? "#d32a34" : (device.state.isRecoveryMode ? "#0345ff" : "darkorange") width: 100 height: 30 @@ -60,7 +56,7 @@ Item { anchors.leftMargin: 10 Text { - text: device.name + text: device.state.name color: "black" font.pixelSize: 16 font.bold: true @@ -79,7 +75,7 @@ Item { anchors.rightMargin: 25 anchors.verticalCenter: parent.verticalCenter - enabled: !device.isPersistent && !device.isError + enabled: !device.state.isPersistent && !device.state.isError onClicked: actionMenu.open() } @@ -87,36 +83,23 @@ Item { StyledButton { id: updateButton text: { - if(firmwareUpdates.channelNames.length === 0) { + if(!firmwareUpdates.isReady) { return qsTr("Error"); - } else if(device.isDFU || (device.version === "N/A")) { + } else if(device.state.isRecoveryMode) { return qsTr("Repair"); - } - - const channelName = "release"; - const latestVersion = firmwareUpdates.channel(channelName).latestVersion; - - if(latestVersion.number === device.version) { - return qsTr("Reinstall"); - } else if((latestVersion.number > device.version) || (device.version.includes(latestVersion.number))) { + } else if(device.updater.canChangeChannel(firmwareUpdates.latestVersion)) { + return qsTr("Change"); + } else if(device.updater.canUpdate(firmwareUpdates.latestVersion)) { return qsTr("Update"); - } else { + } else if(device.updater.canRollback(firmwareUpdates.latestVersion)) { return qsTr("Rollback"); + } else { + return qsTr("Reinstall"); } } - suggested: { - if(device.isDFU || (firmwareUpdates.channelNames.length === 0)) { - return false; - } - - const channelName = "release"; - const latestVersion = firmwareUpdates.channel(channelName).latestVersion; - - return (latestVersion.number > device.version) || ((latestVersion.number !== device.version) && (device.version.includes(latestVersion.number))); - } - - visible: (firmwareUpdates.channelNames.length > 0) && !(device.isPersistent || device.isError) + suggested: device.state.isRecoveryMode ? false : device.updater.canUpdate(firmwareUpdates.latestVersion) + visible: firmwareUpdates.isReady && !(device.state.isPersistent || device.state.isError) anchors.right: menuButton.left anchors.rightMargin: 10 @@ -127,8 +110,8 @@ Item { Text { id: versionLabel - visible: !(messageLabel.visible || device.isDFU) - text: qsTr("version ") + device.version + visible: !(messageLabel.visible || device.state.isRecoveryMode) + text: qsTr("version ") + device.state.version font.pixelSize: 13 anchors.left: nameLabel.right @@ -140,9 +123,9 @@ Item { Text { id: messageLabel - text: device.isError ? device.errorString : device.messageString - visible: device.isPersistent || device.isError - color: device.isError ? "#ddd" : "white" + text: device.state.isError ? device.state.errorString : device.state.statusString + visible: device.state.isPersistent || device.state.isError + color: device.state.isError ? "#ddd" : "white" font.pixelSize: 13 @@ -168,66 +151,36 @@ Item { MenuItem { text: qsTr("Other versions...") onTriggered: versionListRequested(device) - enabled: firmwareUpdates.channelNames.length > 0 + enabled: firmwareUpdates.isReady } - MenuItem { - text: qsTr("Update from local file...") - onTriggered: localUpdateRequested(device) - } MenuSeparator {} MenuItem { text: qsTr("Screen Streaming...") onTriggered: screenStreamRequested(device) - enabled: !device.isDFU + enabled: !device.state.isRecoveryMode } MenuSeparator {} Menu { - title: qsTr("Backup && Restore") - - MenuItem { - text: qsTr("Backup User Data...") - onTriggered: backupRequested(device) - } + title: qsTr("Install from file") - MenuItem { - text: qsTr("Restore User Data...") - onTriggered: restoreRequested(device) - } + MenuItem { + text: qsTr("Application firmware...") + onTriggered: localUpdateRequested(device) } - - Menu { - title: qsTr("Expert options") - MenuItem { - text: qsTr("Update Databases...") - onTriggered: localAssetsUpdateRequested(device) - } - - MenuItem { - text: qsTr("Update Wireless stack...") + text: qsTr("Wireless stack...") onTriggered: localRadioUpdateRequested(device) } MenuItem { - text: qsTr("Update FUS...") + text: qsTr("FUS (not recommended)...") onTriggered: localFUSUpdateRequested(device) } - - MenuItem { - text: qsTr("Fix Option Bytes...") - onTriggered: fixOptionBytesRequested(device) - } - - MenuItem { - text: qsTr("Fix boot issues") - onTriggered: fixBootRequested(device) - enabled: device.isDFU - } } } } diff --git a/application/components/StyledToolButton.qml b/application/components/StyledToolButton.qml index c62fc3a0..4be46ea7 100644 --- a/application/components/StyledToolButton.qml +++ b/application/components/StyledToolButton.qml @@ -5,6 +5,7 @@ import QtQuick.Controls 2.12 Item { id: control property alias icon: button.icon.source + property alias color: button.icon.color width: 50 height: width diff --git a/application/main.qml b/application/main.qml index 5791e76f..2a90cd04 100644 --- a/application/main.qml +++ b/application/main.qml @@ -32,5 +32,6 @@ Window { function onHomeRequested() { mainLoader.setSource("qrc:/screens/homescreen.qml"); } function onVersionsRequested(device) { mainLoader.setSource("qrc:/screens/versionscreen.qml", {device: device}); } function onStreamRequested(device) { mainLoader.setSource("qrc:/screens/streamscreen.qml", {device: device}); } + function onPrefsRequested() { mainLoader.setSource("qrc:/screens/prefscreen.qml") } } } diff --git a/application/qml.qrc b/application/qml.qrc index bb7c1bdf..e25c3cf0 100644 --- a/application/qml.qrc +++ b/application/qml.qrc @@ -19,5 +19,7 @@ assets/arrow-down.svg assets/symbol-back.svg components/StyledRoundButton.qml + screens/prefscreen.qml + assets/symbol-gear.svg diff --git a/application/screens/homescreen.qml b/application/screens/homescreen.qml index 95a44b5c..5ad866c7 100644 --- a/application/screens/homescreen.qml +++ b/application/screens/homescreen.qml @@ -12,6 +12,7 @@ Item { signal versionsRequested(var device) signal streamRequested(var device) + signal prefsRequested() readonly property string channelName: "development" //TODO move this property into application settings readonly property bool hasUpdates: { @@ -118,112 +119,73 @@ Item { delegate: FlipperListDelegate { onUpdateRequested: { - const channelName = "release"; - const latestVersion = firmwareUpdates.channel(channelName).latestVersion; + const channelName = preferences.updateChannel; + const latestVersion = firmwareUpdates.latestVersion; - const messageObj = { - title : qsTr("Install version %1?").arg(latestVersion.number), - subtitle : qsTr("This will install the latest available %1 version.").arg(channelName.toUpperCase()) - }; + let messageObj, actionFunc; - confirmationDialog.openWithMessage(function() { - downloader.downloadRemoteFile(device, latestVersion); - }, messageObj); + if(device.state.isRecoveryMode) { + messageObj = { + title : qsTr("Repair device with version %1?").arg(latestVersion.number), + subtitle : qsTr("WARNING! This will fully erase the contents of internal storage.") + }; + + actionFunc = function() { + device.updater.fullRepair(latestVersion); + } + + } else { + messageObj = { + title : qsTr("Install version %1?").arg(latestVersion.number), + subtitle : qsTr("This will install the latest available %1 version.").arg(channelName.toUpperCase()) + }; + + actionFunc = function() { + device.updater.fullUpdate(latestVersion); + } + } + + confirmationDialog.openWithMessage(actionFunc, messageObj); } onVersionListRequested: { - screen.versionsRequested(device) + screen.versionsRequested(device); } onScreenStreamRequested: { - screen.streamRequested(device) + screen.streamRequested(device); } onLocalUpdateRequested: { const messageObj = { - title : qsTr("Install update from the file?"), - subtitle : qsTr("Caution: this may brick your device.") + title : qsTr("Install update from a file?"), + subtitle : qsTr("Caution: this may brick your device.
User settings will NOT be saved.") }; fileDialog.openWithConfirmation(["Firmware files (*.dfu)", "All files (*)"], function() { - downloader.downloadLocalFile(device, fileDialog.fileUrl); - }, messageObj); - } - - onBackupRequested: { - const messageObj = { - title : qsTr("Backup user data?"), - subtitle : qsTr("This will backup the contents of internal storage.") - }; - - dirDialog.openWithConfirmation(function() { - downloader.backupUserData(device, dirDialog.fileUrl); - }, messageObj); - } - - onRestoreRequested: { - const messageObj = { - title : qsTr("Restore user data?"), - subtitle : qsTr("This will restore the contents of internal storage.") - }; - - dirDialog.openWithConfirmation(function() { - downloader.restoreUserData(device, dirDialog.fileUrl); - }, messageObj); - } - - onLocalAssetsUpdateRequested: { - const messageObj = { - title : qsTr("Update the databases?"), - subtitle : qsTr("This will install the databases from a file.") - }; - - fileDialog.openWithConfirmation(["Database files (*.tgz)", "All files (*)"], function() { - downloader.downloadAssets(device, fileDialog.fileUrl); + device.updater.localFirmwareUpdate(fileDialog.fileUrl); }, messageObj); } onLocalRadioUpdateRequested: { const messageObj = { title : qsTr("Update the wireless stack?"), - subtitle : qsTr("Warning: this operation is potetntially unstable
and may need several attempts.") + subtitle : qsTr("Warning: this operation may need several attempts.
User settings will NOT be saved.") }; - fileDialog.openWithConfirmation(["Radio firmware files (*.bin)", "All files (*)"], function() { - downloader.downloadLocalWirelessStack(device, fileDialog.fileUrl); + fileDialog.openWithConfirmation(["Wireless firmware files (*.bin)", "All files (*)"], function() { + device.updater.localWirelessStackUpdate(fileDialog.fileUrl); }, messageObj); } onLocalFUSUpdateRequested: { const messageObj = { title : qsTr("Update the FUS?"), - subtitle : qsTr("WARNING: this operation is potentially unstable
and may need several attempts.") + subtitle : qsTr("WARNING: this operation will ERASE your SECURITY KEYS.
You MUST know what you are doing.") }; fileDialog.openWithConfirmation(["FUS firmware files (*.bin)", "All files (*)"], function() { - downloader.downloadLocalFUS(device, fileDialog.fileUrl); - }, messageObj); - } - - onFixBootRequested: { - const messageObj = { - title : qsTr("Attempt to fix boot issues?"), - subtitle : qsTr("This will try to correct device option bytes.") - }; - - confirmationDialog.openWithMessage(function() { - downloader.fixBootIssues(device); - }, messageObj); - } - - onFixOptionBytesRequested: { - const messageObj = { - title : qsTr("Check and fix the Option Bytes?"), - subtitle : qsTr("Results will be displayed in the program log.") - }; - - fileDialog.openWithConfirmation(["Option bytes description files (*.data)", "All files (*)"], function() { - downloader.fixOptionBytes(device, fileDialog.fileUrl); + device.updater.localFUSUpdate(fileDialog.fileUrl); }, messageObj); } } @@ -307,4 +269,16 @@ Item { app.updater.installUpdate(applicationUpdates.channel(channelName).latestVersion); } } + + StyledToolButton { + id: prefsButton + anchors.right: screen.right + anchors.bottom: screen.bottom + anchors.margins: 6 + + color: "darkgray" + icon: "qrc:/assets/symbol-gear.svg" + + onPressed: screen.prefsRequested() + } } diff --git a/application/screens/prefscreen.qml b/application/screens/prefscreen.qml new file mode 100644 index 00000000..ad793b96 --- /dev/null +++ b/application/screens/prefscreen.qml @@ -0,0 +1,105 @@ +import QtQml 2.12 +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import "../components" + +Item { + id: screen + anchors.fill: parent + + signal homeRequested() + + Text { + id: titleLabel + anchors.horizontalCenter: screen.horizontalCenter + anchors.bottom: container.top + anchors.bottomMargin: 40 + + font.pixelSize: 30 + font.capitalization: Font.AllUppercase + + color: "white" + text: qsTr("Preferences") + } + + Text { + id: subtitleLabel + anchors.top: titleLabel.bottom + anchors.horizontalCenter: titleLabel.horizontalCenter + text: qsTr("Awesome stuff and whatnot") + + color: "darkgray" + font.capitalization: Font.AllUppercase + } + + GridLayout { + id: container + anchors.top: parent.top + anchors.topMargin: parent.height/4 + + anchors.bottom: parent.bottom + anchors.bottomMargin: 50 + + anchors.horizontalCenter: parent.horizontalCenter + + columns: 2 + columnSpacing: 16 + + Text { + color: "white" + font.pixelSize: 16 + text: qsTr("Flipper update channel") + Layout.alignment: Qt.AlignRight + } + + ComboBox { + id: channelSelector + implicitWidth: 200 + model: firmwareUpdates.channelNames + + onCountChanged: { + currentIndex = find(preferences.updateChannel); + } + + onActivated: { + preferences.updateChannel = textAt(index); + } + } + + Text { + color: "white" + font.pixelSize: 16 + text: qsTr("Check for application updates") + Layout.alignment: Qt.AlignRight + } + + Switch { + id: updateSwitch + checked: preferences.checkAppUpdates + Layout.leftMargin: -10 + + onCheckedChanged: { + preferences.checkAppUpdates = checked + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + } + + StyledButton { + id: closeButton + text: qsTr("Close") + + anchors.right: screen.right + anchors.bottom: screen.bottom + anchors.margins: 16 + + onClicked: screen.homeRequested() + } +} diff --git a/application/screens/streamscreen.qml b/application/screens/streamscreen.qml index 961dc2df..9ec4fac9 100644 --- a/application/screens/streamscreen.qml +++ b/application/screens/streamscreen.qml @@ -17,12 +17,12 @@ Item { function close() { screen.homeRequested(); - device.remote.enabled = false; + device.streamer.enabled = false; } function onDeviceIsOnlineChanged() { - if(!device.isOnline) { - device.isOnlineChanged.disconnect(screen.onDeviceIsOnlineChanged); + if(!device.state.isOnline) { + device.state.isOnlineChanged.disconnect(screen.onDeviceIsOnlineChanged); screen.close(); } } @@ -52,10 +52,10 @@ Item { Layout.fillHeight: true Layout.fillWidth: true - canvasWidth: device.remote.screenWidth - canvasHeight: device.remote.screenHeight + canvasWidth: device.streamer.screenWidth + canvasHeight: device.streamer.screenHeight - data: device.remote.screenData + data: device.streamer.screenData } StyledKeypad { @@ -63,7 +63,7 @@ Item { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter onInputEvent: { - device.remote.sendInputEvent(key, type); + device.streamer.sendInputEvent(key, type); } } @@ -147,7 +147,7 @@ Item { Component.onCompleted: { keypad.forceActiveFocus(); - device.remote.enabled = true; - device.isOnlineChanged.connect(screen.onDeviceIsOnlineChanged); + device.streamer.enabled = true; + device.state.isOnlineChanged.connect(screen.onDeviceIsOnlineChanged); } } diff --git a/application/screens/versionscreen.qml b/application/screens/versionscreen.qml index a6a4d7b1..0820b73a 100644 --- a/application/screens/versionscreen.qml +++ b/application/screens/versionscreen.qml @@ -33,8 +33,12 @@ Item { implicitWidth: 200 model: firmwareUpdates.channelNames - onActivated: { - const channel = firmwareUpdates.channel(textAt(index)); + onCountChanged: { + currentIndex = find(preferences.updateChannel); + } + + onCurrentIndexChanged: { + const channel = firmwareUpdates.channel(textAt(currentIndex)); versionList.model = channel.versions; descriptionLabel.text = channel.description; @@ -60,7 +64,12 @@ Item { delegate: VersionListDelegate { onInstallRequested: { screen.homeRequested(); - downloader.downloadRemoteFile(screen.device, versionInfo); + + if(device.state.isRecoveryMode) { + device.updater.fullRepair(versionInfo) + } else { + device.updater.fullUpdate(versionInfo); + } } onChangelogRequested: { @@ -81,7 +90,7 @@ Item { anchors.centerIn: parent color: "#444" font.pixelSize: 30 - visible: versionList.count === 0 + visible: !firmwareUpdates.isReady } } @@ -91,13 +100,4 @@ Item { onClicked: screen.homeRequested() } } - - - Component.onCompleted: { - const defaultChannelName = "release"; - const defaultChannelNameIdx = channelSelector.find(defaultChannelName, Qt.MatchFixedString); - - channelSelector.currentIndex = defaultChannelNameIdx; - channelSelector.activated(defaultChannelNameIdx); - } } diff --git a/backend/abstractoperation.cpp b/backend/abstractoperation.cpp index e8693e07..90d30d84 100644 --- a/backend/abstractoperation.cpp +++ b/backend/abstractoperation.cpp @@ -7,17 +7,23 @@ AbstractOperation::AbstractOperation(QObject *parent): QObject(parent), m_timeout(new QTimer(this)), - m_state(BasicState::Ready) + m_operationState(BasicOperationState::Ready) { - connect(this, &AbstractOperation::finished, m_timeout, &QTimer::stop); connect(m_timeout, &QTimer::timeout, this, &AbstractOperation::onOperationTimeout); - m_timeout->setSingleShot(true); } -int AbstractOperation::state() const +void AbstractOperation::finish() +{ + stopTimeout(); + setOperationState(AbstractOperation::Finished); + + emit finished(); +} + +int AbstractOperation::operationState() const { - return m_state; + return m_operationState; } void AbstractOperation::onOperationTimeout() @@ -25,9 +31,9 @@ void AbstractOperation::onOperationTimeout() finishWithError(QStringLiteral("Operation timeout (generic)")); } -void AbstractOperation::setState(int state) +void AbstractOperation::setOperationState(int state) { - m_state = state; + m_operationState = state; } void AbstractOperation::finishWithError(const QString &errorMsg) diff --git a/backend/abstractoperation.h b/backend/abstractoperation.h index a16a31f0..2ec4839d 100644 --- a/backend/abstractoperation.h +++ b/backend/abstractoperation.h @@ -11,7 +11,7 @@ class AbstractOperation: public QObject, public Failable Q_OBJECT public: - enum BasicState { + enum BasicOperationState { Ready = 0, Finished, User @@ -22,9 +22,10 @@ class AbstractOperation: public QObject, public Failable virtual const QString description() const = 0; virtual void start() = 0; - virtual void finish() = 0; + virtual void finish(); + + int operationState() const; - int state() const; signals: void started(); void finished(); @@ -33,13 +34,13 @@ protected slots: virtual void onOperationTimeout(); protected: - void setState(int state); + void setOperationState(int state); void finishWithError(const QString &errorMsg); - void startTimeout(int msec = 5000); + void startTimeout(int msec = 10000); void stopTimeout(); private: QTimer *m_timeout; - int m_state; + int m_operationState; }; diff --git a/backend/abstractoperationhelper.cpp b/backend/abstractoperationhelper.cpp new file mode 100644 index 00000000..5dd68f03 --- /dev/null +++ b/backend/abstractoperationhelper.cpp @@ -0,0 +1,37 @@ +#include "abstractoperationhelper.h" + +#include + +AbstractOperationHelper::AbstractOperationHelper(QObject *parent): + QObject(parent), + m_state(State::Ready) +{ + advanceState(); +} + +void AbstractOperationHelper::finish() +{ + setState(State::Finished); + emit finished(); +} + +void AbstractOperationHelper::finishWithError(const QString &errorMessage) +{ + setError(errorMessage); + finish(); +} + +void AbstractOperationHelper::advanceState() +{ + QTimer::singleShot(0, this, &AbstractOperationHelper::nextStateLogic); +} + +int AbstractOperationHelper::state() const +{ + return m_state; +} + +void AbstractOperationHelper::setState(int newState) +{ + m_state = newState; +} diff --git a/backend/abstractoperationhelper.h b/backend/abstractoperationhelper.h new file mode 100644 index 00000000..ec66bb66 --- /dev/null +++ b/backend/abstractoperationhelper.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "failable.h" + +class AbstractOperationHelper : public QObject, public Failable +{ + Q_OBJECT + +public: + enum State { + Ready = 0, + Finished, + User + }; + + AbstractOperationHelper(QObject *parent = nullptr); + virtual ~AbstractOperationHelper() {} + +signals: + void finished(); + +protected slots: + void finish(); + void finishWithError(const QString &errorMessage); + + void advanceState(); + +protected: + int state() const; + void setState(int newState); + +private: + virtual void nextStateLogic() = 0; + + int m_state; +}; diff --git a/backend/abstractoperationrunner.cpp b/backend/abstractoperationrunner.cpp new file mode 100644 index 00000000..55bf3e42 --- /dev/null +++ b/backend/abstractoperationrunner.cpp @@ -0,0 +1,95 @@ +#include "abstractoperationrunner.h" + +#include +#include + +#include "abstractoperation.h" + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +AbstractOperationRunner::AbstractOperationRunner(QObject *parent): + SignalingFailable(parent), + m_state(State::Idle) +{} + +bool AbstractOperationRunner::onQueueStarted() +{ + //Default empty implementation + return true; +} + +bool AbstractOperationRunner::onQueueFinished() +{ + //Default empty implementation + return true; +} + +void AbstractOperationRunner::onOperationStarted(AbstractOperation *operation) +{ + Q_UNUSED(operation); + //Default empty implementation +} + +void AbstractOperationRunner::onOperationFinished(AbstractOperation *operation) +{ + Q_UNUSED(operation); + //Default empty implementation +} + +void AbstractOperationRunner::enqueueOperation(AbstractOperation *operation) +{ + if(m_state == State::Idle) { + m_state = State::Running; + + if(!onQueueStarted()) { + setError(QStringLiteral("Failed to start the operation queue")); + return; + } + + CALL_LATER(this, &AbstractOperationRunner::processQueue); + } + + m_queue.enqueue(operation); +} + +void AbstractOperationRunner::processQueue() +{ + if(m_queue.isEmpty()) { + m_state = State::Idle; + + if(!onQueueFinished()) { + setError(QStringLiteral("Failed to finish the operation queue")); + } + + return; + } + + auto *operation = m_queue.dequeue(); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + setError(operation->errorString()); + + clearQueue(); + processQueue(); + + } else { + CALL_LATER(this, &AbstractOperationRunner::processQueue); + } + + qDebug() << "[Operation Runner]" << operation->description() << "finished with status:" << operation->errorString(); + + onOperationFinished(operation); + operation->deleteLater(); + }); + + onOperationFinished(operation); + operation->start(); +} + +void AbstractOperationRunner::clearQueue() +{ + while(!m_queue.isEmpty()) { + m_queue.dequeue()->deleteLater(); + } +} diff --git a/backend/abstractoperationrunner.h b/backend/abstractoperationrunner.h new file mode 100644 index 00000000..ec262e5c --- /dev/null +++ b/backend/abstractoperationrunner.h @@ -0,0 +1,40 @@ +#pragma once + +#include "signalingfailable.h" + +#include + +class AbstractOperation; + +class AbstractOperationRunner : public SignalingFailable +{ + Q_OBJECT + + enum class State { + Idle, + Running + }; + + using OperationQueue = QQueue; + +public: + AbstractOperationRunner(QObject *parent = nullptr); + +protected: + virtual bool onQueueStarted(); + virtual bool onQueueFinished(); + + virtual void onOperationStarted(AbstractOperation *operation); + virtual void onOperationFinished(AbstractOperation *operation); + + void enqueueOperation(AbstractOperation *operation); + +private slots: + void processQueue(); + void clearQueue(); + +private: + State m_state; + OperationQueue m_queue; +}; + diff --git a/backend/abstractserialoperation.cpp b/backend/abstractserialoperation.cpp index a261afaf..2becd041 100644 --- a/backend/abstractserialoperation.cpp +++ b/backend/abstractserialoperation.cpp @@ -5,31 +5,27 @@ #include "macros.h" +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + AbstractSerialOperation::AbstractSerialOperation(QSerialPort *serialPort, QObject *parent): AbstractOperation(parent), m_serialPort(serialPort) {} -AbstractSerialOperation::~AbstractSerialOperation() -{} - void AbstractSerialOperation::start() { connect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::onSerialPortReadyRead); connect(m_serialPort, &QSerialPort::errorOccurred, this, &AbstractSerialOperation::onSerialPortError); - QTimer::singleShot(0, this, &AbstractSerialOperation::begin); + CALL_LATER(this, &AbstractSerialOperation::begin); } void AbstractSerialOperation::finish() { - stopTimeout(); - setState(BasicState::Finished); - disconnect(m_serialPort, &QSerialPort::readyRead, this, &AbstractSerialOperation::onSerialPortReadyRead); disconnect(m_serialPort, &QSerialPort::errorOccurred, this, &AbstractSerialOperation::onSerialPortError); - emit finished(); + AbstractOperation::finish(); } QSerialPort *AbstractSerialOperation::serialPort() const diff --git a/backend/abstractserialoperation.h b/backend/abstractserialoperation.h index 5c45d21e..5d85c983 100644 --- a/backend/abstractserialoperation.h +++ b/backend/abstractserialoperation.h @@ -10,7 +10,7 @@ class AbstractSerialOperation : public AbstractOperation public: AbstractSerialOperation(QSerialPort *serialPort, QObject *parent = nullptr); - virtual ~AbstractSerialOperation(); + virtual ~AbstractSerialOperation() {} void start() override; void finish() override; diff --git a/backend/backend.pro b/backend/backend.pro index f3568651..97dc1eb1 100644 --- a/backend/backend.pro +++ b/backend/backend.pro @@ -10,82 +10,132 @@ include(../qflipper_common.pri) SOURCES += \ abstractoperation.cpp \ + abstractoperationhelper.cpp \ + abstractoperationrunner.cpp \ abstractserialoperation.cpp \ deviceregistry.cpp \ filenode.cpp \ - firmwaredownloader.cpp \ flipperupdates.cpp \ flipperzero/assetmanifest.cpp \ - flipperzero/common/skipmotdoperation.cpp \ + flipperzero/cli/dfuoperation.cpp \ + flipperzero/cli/rebootoperation.cpp \ + flipperzero/commandinterface.cpp \ + flipperzero/cli/skipmotdoperation.cpp \ flipperzero/deviceinfofetcher.cpp \ + flipperzero/devicestate.cpp \ flipperzero/factoryinfo.cpp \ + flipperzero/firmwareupdater.cpp \ flipperzero/flipperzero.cpp \ - flipperzero/operations/assetsdownloadoperation.cpp \ - flipperzero/operations/firmwaredownloadoperation.cpp \ - flipperzero/operations/fixbootissuesoperation.cpp \ - flipperzero/operations/fixoptionbytesoperation.cpp \ - flipperzero/operations/flipperzerooperation.cpp \ - flipperzero/operations/getfiletreeoperation.cpp \ - flipperzero/operations/userbackupoperation.cpp \ - flipperzero/operations/userrestoreoperation.cpp \ - flipperzero/operations/wirelessstackdownloadoperation.cpp \ - flipperzero/recoverycontroller.cpp \ - flipperzero/remotecontroller.cpp \ - flipperzero/storage/listoperation.cpp \ - flipperzero/storage/mkdiroperation.cpp \ - flipperzero/storage/readoperation.cpp \ - flipperzero/storage/removeoperation.cpp \ - flipperzero/storage/statoperation.cpp \ - flipperzero/storage/writeoperation.cpp \ - flipperzero/storagecontroller.cpp \ + flipperzero/helper/firmwarehelper.cpp \ + flipperzero/helper/radiomanifesthelper.cpp \ + flipperzero/helper/scriptshelper.cpp \ + flipperzero/radiomanifest.cpp \ + flipperzero/recovery.cpp \ + flipperzero/recovery/abstractrecoveryoperation.cpp \ + flipperzero/recovery/correctoptionbytesoperation.cpp \ + flipperzero/recovery/exitrecoveryoperation.cpp \ + flipperzero/recovery/firmwaredownloadoperation.cpp \ + flipperzero/recovery/fixbootissuesoperation.cpp \ + flipperzero/recovery/radioupdateoperation.cpp \ + flipperzero/recovery/setbootmodeoperation.cpp \ + flipperzero/recovery/wirelessstackdownloadoperation.cpp \ + flipperzero/recoveryinterface.cpp \ + flipperzero/cli/listoperation.cpp \ + flipperzero/cli/mkdiroperation.cpp \ + flipperzero/cli/readoperation.cpp \ + flipperzero/cli/removeoperation.cpp \ + flipperzero/cli/statoperation.cpp \ + flipperzero/cli/writeoperation.cpp \ + flipperzero/screenstreamer.cpp \ + flipperzero/toplevel/abstracttopleveloperation.cpp \ + flipperzero/toplevel/firmwareupdateoperation.cpp \ + flipperzero/toplevel/fullrepairoperation.cpp \ + flipperzero/toplevel/fullupdateoperation.cpp \ + flipperzero/toplevel/wirelessstackupdateoperation.cpp \ + flipperzero/utility/abstractutilityoperation.cpp \ + flipperzero/utility/assetsdownloadoperation.cpp \ + flipperzero/utility/getfiletreeoperation.cpp \ + flipperzero/utility/restartoperation.cpp \ + flipperzero/utility/startrecoveryoperation.cpp \ + flipperzero/utility/userbackupoperation.cpp \ + flipperzero/utility/userrestoreoperation.cpp \ + flipperzero/utilityinterface.cpp \ gzipuncompressor.cpp \ + preferences.cpp \ qflipperbackend.cpp \ remotefilefetcher.cpp \ serialfinder.cpp \ simpleserialoperation.cpp \ tararchive.cpp \ + tarziparchive.cpp \ + tempdirectories.cpp \ updateregistry.cpp HEADERS += \ abstractoperation.h \ + abstractoperationhelper.h \ + abstractoperationrunner.h \ abstractserialoperation.h \ deviceregistry.h \ failable.h \ fileinfo.h \ filenode.h \ - firmwaredownloader.h \ flipperupdates.h \ flipperzero/assetmanifest.h \ - flipperzero/common/skipmotdoperation.h \ + flipperzero/cli/dfuoperation.h \ + flipperzero/cli/rebootoperation.h \ + flipperzero/commandinterface.h \ + flipperzero/cli/skipmotdoperation.h \ flipperzero/deviceinfo.h \ flipperzero/deviceinfofetcher.h \ + flipperzero/devicestate.h \ flipperzero/factoryinfo.h \ + flipperzero/firmwareupdater.h \ flipperzero/flipperzero.h \ - flipperzero/operations/assetsdownloadoperation.h \ - flipperzero/operations/firmwaredownloadoperation.h \ - flipperzero/operations/fixbootissuesoperation.h \ - flipperzero/operations/fixoptionbytesoperation.h \ - flipperzero/operations/flipperzerooperation.h \ - flipperzero/operations/getfiletreeoperation.h \ - flipperzero/operations/userbackupoperation.h \ - flipperzero/operations/userrestoreoperation.h \ - flipperzero/operations/wirelessstackdownloadoperation.h \ - flipperzero/recoverycontroller.h \ - flipperzero/remotecontroller.h \ - flipperzero/storage/listoperation.h \ - flipperzero/storage/mkdiroperation.h \ - flipperzero/storage/readoperation.h \ - flipperzero/storage/removeoperation.h \ - flipperzero/storage/statoperation.h \ - flipperzero/storage/writeoperation.h \ - flipperzero/storagecontroller.h \ + flipperzero/helper/firmwarehelper.h \ + flipperzero/helper/radiomanifesthelper.h \ + flipperzero/helper/scriptshelper.h \ + flipperzero/radiomanifest.h \ + flipperzero/recovery.h \ + flipperzero/recovery/abstractrecoveryoperation.h \ + flipperzero/recovery/correctoptionbytesoperation.h \ + flipperzero/recovery/exitrecoveryoperation.h \ + flipperzero/recovery/firmwaredownloadoperation.h \ + flipperzero/recovery/fixbootissuesoperation.h \ + flipperzero/recovery/radioupdateoperation.h \ + flipperzero/recovery/setbootmodeoperation.h \ + flipperzero/recovery/wirelessstackdownloadoperation.h \ + flipperzero/recoveryinterface.h \ + flipperzero/cli/listoperation.h \ + flipperzero/cli/mkdiroperation.h \ + flipperzero/cli/readoperation.h \ + flipperzero/cli/removeoperation.h \ + flipperzero/cli/statoperation.h \ + flipperzero/cli/writeoperation.h \ + flipperzero/screenstreamer.h \ + flipperzero/toplevel/abstracttopleveloperation.h \ + flipperzero/toplevel/firmwareupdateoperation.h \ + flipperzero/toplevel/fullrepairoperation.h \ + flipperzero/toplevel/fullupdateoperation.h \ + flipperzero/toplevel/wirelessstackupdateoperation.h \ + flipperzero/utility/abstractutilityoperation.h \ + flipperzero/utility/assetsdownloadoperation.h \ + flipperzero/utility/getfiletreeoperation.h \ + flipperzero/utility/restartoperation.h \ + flipperzero/utility/startrecoveryoperation.h \ + flipperzero/utility/userbackupoperation.h \ + flipperzero/utility/userrestoreoperation.h \ + flipperzero/utilityinterface.h \ gzipuncompressor.h \ + preferences.h \ qflipperbackend.h \ remotefilefetcher.h \ serialfinder.h \ signalingfailable.h \ simpleserialoperation.h \ tararchive.h \ + tarziparchive.h \ + tempdirectories.h \ updateregistry.h unix|win32 { diff --git a/backend/deviceregistry.cpp b/backend/deviceregistry.cpp index 06ddbcf5..2933620e 100644 --- a/backend/deviceregistry.cpp +++ b/backend/deviceregistry.cpp @@ -4,6 +4,8 @@ #include "flipperzero/deviceinfofetcher.h" #include "flipperzero/flipperzero.h" +#include "flipperzero/devicestate.h" + #include "usbdevice.h" #include "macros.h" @@ -59,20 +61,21 @@ void DeviceRegistry::insertDevice(const USBDeviceInfo &info) void DeviceRegistry::removeDevice(const USBDeviceInfo &info) { const auto it = std::find_if(m_data.begin(), m_data.end(), [&](Flipper::FlipperZero *dev) { - return dev->deviceInfo().usbInfo.backendData() == info.backendData(); + const auto &deviceInfo = dev->deviceState()->deviceInfo().usbInfo; + return deviceInfo.backendData() == info.backendData(); }); if(it != m_data.end()) { const auto idx = std::distance(m_data.begin(), it); auto *device = m_data.at(idx); - if(!device->isPersistent()) { + if(!device->deviceState()->isPersistent()) { beginRemoveRows(QModelIndex(), idx, idx); m_data.takeAt(idx)->deleteLater(); endRemoveRows(); } else { - device->setOnline(false); + device->deviceState()->setOnline(false); } } } @@ -81,6 +84,8 @@ void DeviceRegistry::processDevice() { auto *fetcher = qobject_cast(sender()); + fetcher->deleteLater(); + if(fetcher->isError()) { error_msg(QStringLiteral("An error has occured: %1").arg(fetcher->errorString())); return; @@ -89,12 +94,12 @@ void DeviceRegistry::processDevice() const auto &info = fetcher->result(); const auto it = std::find_if(m_data.begin(), m_data.end(), [&info](Flipper::FlipperZero *arg) { - return info.name == arg->name(); + return info.name == arg->deviceState()->name(); }); if(it != m_data.end()) { // Preserving the old instance - (*it)->reset(info); + (*it)->deviceState()->reset(info); } else { auto *device = new FlipperZero(info, this); @@ -106,5 +111,4 @@ void DeviceRegistry::processDevice() emit deviceConnected(device); } - fetcher->deleteLater(); } diff --git a/backend/firmwaredownloader.cpp b/backend/firmwaredownloader.cpp deleted file mode 100644 index 48c616d3..00000000 --- a/backend/firmwaredownloader.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include "firmwaredownloader.h" - -#include -#include -#include -#include - -#include "flipperzero/flipperzero.h" -#include "flipperzero/operations/userbackupoperation.h" -#include "flipperzero/operations/userrestoreoperation.h" -#include "flipperzero/operations/fixbootissuesoperation.h" -#include "flipperzero/operations/assetsdownloadoperation.h" -#include "flipperzero/operations/fixoptionbytesoperation.h" -#include "flipperzero/operations/firmwaredownloadoperation.h" -#include "flipperzero/operations/wirelessstackdownloadoperation.h" - -#include "remotefilefetcher.h" -#include "macros.h" - -using namespace Flipper; - -FirmwareDownloader::FirmwareDownloader(QObject *parent): - QObject(parent), - m_state(State::Ready) -{} - -void FirmwareDownloader::downloadLocalFile(FlipperZero *device, const QString &filePath) -{ - const auto localUrl = QUrl(filePath).toLocalFile(); - auto *file = new QFile(localUrl, this); - - enqueueOperation(new Flipper::Zero::FirmwareDownloadOperation(device, file)); -} - -void FirmwareDownloader::downloadRemoteFile(FlipperZero *device, const Flipper::Updates::VersionInfo &versionInfo) -{ - // TODO: Local cache on hard disk? - const auto fileInfo = versionInfo.fileInfo(QStringLiteral("full_dfu"), device->target()); - - auto *fetcher = new RemoteFileFetcher(this); - auto *buf = new QBuffer(this); - - check_return_void(buf->open(QIODevice::ReadWrite), "Failed to create intermediate buffer."); - - connect(fetcher, &RemoteFileFetcher::finished, this, [=]() { - buf->seek(0); - buf->close(); - - enqueueOperation(new Flipper::Zero::FirmwareDownloadOperation(device, buf)); - - fetcher->deleteLater(); - }); - - device->setMessage(tr("Fetching the update file...")); - fetcher->fetch(fileInfo, buf); -} - -void FirmwareDownloader::downloadLocalFUS(FlipperZero *device, const QString &filePath) -{ - const auto localUrl = QUrl(filePath).toLocalFile(); - auto *file = new QFile(localUrl, this); - - enqueueOperation(new Flipper::Zero::WirelessStackDownloadOperation(device, file, 0x080EC000)); -} - -void FirmwareDownloader::downloadLocalWirelessStack(FlipperZero *device, const QString &filePath) -{ - const auto localUrl = QUrl(filePath).toLocalFile(); - auto *file = new QFile(localUrl, this); - - enqueueOperation(new Flipper::Zero::WirelessStackDownloadOperation(device, file)); -} - -void FirmwareDownloader::fixBootIssues(FlipperZero *device) -{ - enqueueOperation(new Flipper::Zero::FixBootIssuesOperation(device)); -} - -void FirmwareDownloader::fixOptionBytes(FlipperZero *device, const QString &filePath) -{ - const auto localUrl = QUrl(filePath).toLocalFile(); - auto *file = new QFile(localUrl, this); - - enqueueOperation(new Flipper::Zero::FixOptionBytesOperation(device, file)); -} - -void FirmwareDownloader::downloadAssets(FlipperZero *device, const QString &filePath) -{ - const auto localUrl = QUrl(filePath).toLocalFile(); - auto *file = new QFile(localUrl, this); - - enqueueOperation(new Flipper::Zero::AssetsDownloadOperation(device, file, this)); -} - -void FirmwareDownloader::backupUserData(FlipperZero *device, const QString &backupPath) -{ - enqueueOperation(new Flipper::Zero::UserBackupOperation(device, backupPath, this)); -} - -void FirmwareDownloader::restoreUserData(FlipperZero *device, const QString &backupPath) -{ - enqueueOperation(new Flipper::Zero::UserRestoreOperation(device, backupPath, this)); -} - -void FirmwareDownloader::processQueue() -{ - if(m_operationQueue.isEmpty()) { - m_state = State::Ready; - return; - } - - auto *currentOperation = m_operationQueue.dequeue(); - - connect(currentOperation, &AbstractOperation::finished, this, [=]() { - info_msg(QStringLiteral("Operation '%1' finished with status: %2.").arg(currentOperation->description(), currentOperation->errorString())); - currentOperation->deleteLater(); - - QTimer::singleShot(0, this, &FirmwareDownloader::processQueue); - }); - - currentOperation->start(); -} - -void FirmwareDownloader::enqueueOperation(AbstractOperation *op) -{ - m_operationQueue.enqueue(op); - - if(m_state == State::Ready) { - // Leave the context before calling processQueue() - m_state = State::Running; - QTimer::singleShot(20, this, &FirmwareDownloader::processQueue); - } -} diff --git a/backend/firmwaredownloader.h b/backend/firmwaredownloader.h deleted file mode 100644 index e20e4856..00000000 --- a/backend/firmwaredownloader.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef FIRMWAREUPDATER_H -#define FIRMWAREUPDATER_H - -#include -#include - -#include "flipperupdates.h" - -class QIODevice; -class AbstractOperation; - -namespace Flipper { - -class FlipperZero; -class FirmwareDownloader : public QObject -{ - Q_OBJECT - - enum class State { - Ready, - Running - }; - -public: - FirmwareDownloader(QObject *parent = nullptr); - -public slots: - void downloadLocalFile(Flipper::FlipperZero *device, const QString &filePath); - void downloadRemoteFile(Flipper::FlipperZero *device, const Flipper::Updates::VersionInfo &versionInfo); - - void downloadLocalFUS(Flipper::FlipperZero *device, const QString &filePath); - void downloadLocalWirelessStack(Flipper::FlipperZero *device, const QString &filePath); - - void fixBootIssues(Flipper::FlipperZero *device); - void fixOptionBytes(Flipper::FlipperZero *device, const QString &filePath); - - void downloadAssets(Flipper::FlipperZero *device, const QString &filePath); - - void backupUserData(Flipper::FlipperZero *device, const QString &backupPath); - void restoreUserData(Flipper::FlipperZero *device, const QString &backupPath); - -private slots: - void processQueue(); - -private: - void enqueueOperation(AbstractOperation *op); - - State m_state; - QQueue m_operationQueue; -}; - -} - -#endif // FIRMWAREUPDATER_H diff --git a/backend/flipperzero/cli/dfuoperation.cpp b/backend/flipperzero/cli/dfuoperation.cpp new file mode 100644 index 00000000..719dbb7c --- /dev/null +++ b/backend/flipperzero/cli/dfuoperation.cpp @@ -0,0 +1,35 @@ +#include "dfuoperation.h" + +#include +#include + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +DFUOperation::DFUOperation(QSerialPort *serialPort, QObject *parent): + AbstractSerialOperation(serialPort, parent) +{} + +const QString DFUOperation::description() const +{ + return QStringLiteral("Boot to recovery @%1").arg(QString(serialPort()->portName())); +} + +void DFUOperation::onSerialPortReadyRead() +{ + // This operation does not need serial output, discarding it + serialPort()->clear(); +} + +bool DFUOperation::begin() +{ + const auto success = (serialPort()->write("\rdfu\r\n") > 0) && serialPort()->flush(); + + if(success) { + CALL_LATER(this, &AbstractOperation::finish); + } + + return success; +} diff --git a/backend/flipperzero/cli/dfuoperation.h b/backend/flipperzero/cli/dfuoperation.h new file mode 100644 index 00000000..2236c386 --- /dev/null +++ b/backend/flipperzero/cli/dfuoperation.h @@ -0,0 +1,25 @@ +#pragma once + +#include "abstractserialoperation.h" + +namespace Flipper { +namespace Zero { + +class DFUOperation : public AbstractSerialOperation +{ + Q_OBJECT + +public: + DFUOperation(QSerialPort *serialPort, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void onSerialPortReadyRead() override; + +private: + bool begin() override; +}; + +} +} + diff --git a/backend/flipperzero/storage/listoperation.cpp b/backend/flipperzero/cli/listoperation.cpp similarity index 100% rename from backend/flipperzero/storage/listoperation.cpp rename to backend/flipperzero/cli/listoperation.cpp diff --git a/backend/flipperzero/storage/listoperation.h b/backend/flipperzero/cli/listoperation.h similarity index 100% rename from backend/flipperzero/storage/listoperation.h rename to backend/flipperzero/cli/listoperation.h diff --git a/backend/flipperzero/storage/mkdiroperation.cpp b/backend/flipperzero/cli/mkdiroperation.cpp similarity index 100% rename from backend/flipperzero/storage/mkdiroperation.cpp rename to backend/flipperzero/cli/mkdiroperation.cpp diff --git a/backend/flipperzero/storage/mkdiroperation.h b/backend/flipperzero/cli/mkdiroperation.h similarity index 100% rename from backend/flipperzero/storage/mkdiroperation.h rename to backend/flipperzero/cli/mkdiroperation.h diff --git a/backend/flipperzero/storage/readoperation.cpp b/backend/flipperzero/cli/readoperation.cpp similarity index 93% rename from backend/flipperzero/storage/readoperation.cpp rename to backend/flipperzero/cli/readoperation.cpp index 15355dad..082c1bbe 100644 --- a/backend/flipperzero/storage/readoperation.cpp +++ b/backend/flipperzero/cli/readoperation.cpp @@ -31,12 +31,12 @@ void ReadOperation::onSerialPortReadyRead() m_receivedData += serialPort()->readAll(); - if(state() == State::SettingUp) { + if(operationState() == State::SettingUp) { if(m_receivedData.endsWith(READY_PROMPT)) { if(!parseSetupReply()) { finish(); } else { - setState(State::ReceivingData); + setOperationState(State::ReceivingData); serialPort()->write("\n"); } @@ -46,7 +46,7 @@ void ReadOperation::onSerialPortReadyRead() finish(); } - } else if(state() == State::ReceivingData) { + } else if(operationState() == State::ReceivingData) { if(m_receivedData.endsWith(READY_PROMPT)) { m_file->write(m_receivedData.chopped(READY_PROMPT.size())); m_receivedData.clear(); @@ -67,7 +67,7 @@ bool ReadOperation::begin() const auto success = (serialPort()->write(cmdLine) == cmdLine.size()) && serialPort()->flush(); if(success) { - setState(State::SettingUp); + setOperationState(State::SettingUp); startTimeout(); } diff --git a/backend/flipperzero/storage/readoperation.h b/backend/flipperzero/cli/readoperation.h similarity index 93% rename from backend/flipperzero/storage/readoperation.h rename to backend/flipperzero/cli/readoperation.h index eac2b449..4431b04d 100644 --- a/backend/flipperzero/storage/readoperation.h +++ b/backend/flipperzero/cli/readoperation.h @@ -14,7 +14,7 @@ class ReadOperation : public AbstractSerialOperation Q_OBJECT enum State { - SettingUp = BasicState::Ready, + SettingUp = BasicOperationState::Ready, ReceivingData }; diff --git a/backend/flipperzero/cli/rebootoperation.cpp b/backend/flipperzero/cli/rebootoperation.cpp new file mode 100644 index 00000000..dd629821 --- /dev/null +++ b/backend/flipperzero/cli/rebootoperation.cpp @@ -0,0 +1,35 @@ +#include "rebootoperation.h" + +#include +#include + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +RebootOperation::RebootOperation(QSerialPort *serialPort, QObject *parent): + AbstractSerialOperation(serialPort, parent) +{} + +const QString RebootOperation::description() const +{ + return QStringLiteral("Reboot @%1").arg(QString(serialPort()->portName())); +} + +void RebootOperation::onSerialPortReadyRead() +{ + // This operation does not need serial output, discarding it + serialPort()->clear(); +} + +bool RebootOperation::begin() +{ + const auto success = (serialPort()->write("\rreboot\r\n") > 0) && serialPort()->flush(); + + if(success) { + CALL_LATER(this, &AbstractOperation::finish); + } + + return success; +} diff --git a/backend/flipperzero/cli/rebootoperation.h b/backend/flipperzero/cli/rebootoperation.h new file mode 100644 index 00000000..980ced6f --- /dev/null +++ b/backend/flipperzero/cli/rebootoperation.h @@ -0,0 +1,25 @@ +#pragma once + +#include "abstractserialoperation.h" + +namespace Flipper { +namespace Zero { + +class RebootOperation : public AbstractSerialOperation +{ + Q_OBJECT + +public: + RebootOperation(QSerialPort *serialPort, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void onSerialPortReadyRead() override; + +private: + bool begin() override; +}; + +} +} + diff --git a/backend/flipperzero/storage/removeoperation.cpp b/backend/flipperzero/cli/removeoperation.cpp similarity index 100% rename from backend/flipperzero/storage/removeoperation.cpp rename to backend/flipperzero/cli/removeoperation.cpp diff --git a/backend/flipperzero/storage/removeoperation.h b/backend/flipperzero/cli/removeoperation.h similarity index 100% rename from backend/flipperzero/storage/removeoperation.h rename to backend/flipperzero/cli/removeoperation.h diff --git a/backend/flipperzero/common/skipmotdoperation.cpp b/backend/flipperzero/cli/skipmotdoperation.cpp similarity index 91% rename from backend/flipperzero/common/skipmotdoperation.cpp rename to backend/flipperzero/cli/skipmotdoperation.cpp index 8041b1fb..fb04b086 100644 --- a/backend/flipperzero/common/skipmotdoperation.cpp +++ b/backend/flipperzero/cli/skipmotdoperation.cpp @@ -16,7 +16,7 @@ const QString SkipMOTDOperation::description() const QByteArray SkipMOTDOperation::endOfMessageToken() const { - return QByteArrayLiteral("\r\n>: "); + return QByteArrayLiteral("\r\n\r\n>: "); } uint32_t SkipMOTDOperation::flags() const diff --git a/backend/flipperzero/common/skipmotdoperation.h b/backend/flipperzero/cli/skipmotdoperation.h similarity index 100% rename from backend/flipperzero/common/skipmotdoperation.h rename to backend/flipperzero/cli/skipmotdoperation.h diff --git a/backend/flipperzero/storage/statoperation.cpp b/backend/flipperzero/cli/statoperation.cpp similarity index 100% rename from backend/flipperzero/storage/statoperation.cpp rename to backend/flipperzero/cli/statoperation.cpp diff --git a/backend/flipperzero/storage/statoperation.h b/backend/flipperzero/cli/statoperation.h similarity index 100% rename from backend/flipperzero/storage/statoperation.h rename to backend/flipperzero/cli/statoperation.h diff --git a/backend/flipperzero/storage/writeoperation.cpp b/backend/flipperzero/cli/writeoperation.cpp similarity index 92% rename from backend/flipperzero/storage/writeoperation.cpp rename to backend/flipperzero/cli/writeoperation.cpp index 8ec71ecf..093077e7 100644 --- a/backend/flipperzero/storage/writeoperation.cpp +++ b/backend/flipperzero/cli/writeoperation.cpp @@ -30,7 +30,7 @@ void WriteOperation::onSerialPortReadyRead() { m_receivedData.append(serialPort()->readAll()); - if(state() == State::SettingUp) { + if(operationState() == State::SettingUp) { if(m_receivedData.endsWith(FINISH_PROMPT)) { parseError(); finish(); @@ -39,18 +39,18 @@ void WriteOperation::onSerialPortReadyRead() return; } - setState(State::WritingData); + setOperationState(State::WritingData); if(!writeChunk()) { finishWithError(QStringLiteral("Failed to write chunk")); } - } else if(state() == State::WritingData) { + } else if(operationState() == State::WritingData) { if(!m_receivedData.endsWith(FINISH_PROMPT)) { return; } - setState(State::SettingUp); + setOperationState(State::SettingUp); if(!m_file->bytesAvailable()) { finish(); @@ -69,7 +69,7 @@ bool WriteOperation::begin() { check_return_bool(m_file->bytesAvailable(), "No data is available for reading from file"); - setState(State::SettingUp); + setOperationState(State::SettingUp); return writeSetupCommand(); } diff --git a/backend/flipperzero/storage/writeoperation.h b/backend/flipperzero/cli/writeoperation.h similarity index 93% rename from backend/flipperzero/storage/writeoperation.h rename to backend/flipperzero/cli/writeoperation.h index 1d8b68c1..d5ec0b50 100644 --- a/backend/flipperzero/storage/writeoperation.h +++ b/backend/flipperzero/cli/writeoperation.h @@ -14,7 +14,7 @@ class WriteOperation : public AbstractSerialOperation Q_OBJECT enum State { - SettingUp = BasicState::Ready, + SettingUp = BasicOperationState::Ready, WritingData }; diff --git a/backend/flipperzero/commandinterface.cpp b/backend/flipperzero/commandinterface.cpp new file mode 100644 index 00000000..2b772393 --- /dev/null +++ b/backend/flipperzero/commandinterface.cpp @@ -0,0 +1,118 @@ +#include "commandinterface.h" + +#include + +#include "flipperzero/devicestate.h" + +#include "cli/skipmotdoperation.h" +#include "cli/rebootoperation.h" +#include "cli/removeoperation.h" +#include "cli/mkdiroperation.h" +#include "cli/writeoperation.h" +#include "cli/readoperation.h" +#include "cli/statoperation.h" +#include "cli/listoperation.h" +#include "cli/dfuoperation.h" + +#include "macros.h" + +using namespace Flipper; +using namespace Zero; + +CommandInterface::CommandInterface(DeviceState *state, QObject *parent): + AbstractOperationRunner(parent), + m_serialPort(nullptr) +{ + // Automatically re-create serial port instance when a persistent device reconnects + const auto createSerialPort = [=]() { + if(m_serialPort) { + check_continue(!m_serialPort->isOpen(), "Deleting a Serial Port instance that is still open."); + m_serialPort->deleteLater(); + m_serialPort = nullptr; + } + + const auto &portInfo = state->deviceInfo().serialInfo; + + if(!portInfo.isNull()) { + m_serialPort = new QSerialPort(portInfo, this); + } + }; + + connect(state, &DeviceState::deviceInfoChanged, this, createSerialPort); + + createSerialPort(); +} + +CommandInterface::~CommandInterface() +{} + +RebootOperation *CommandInterface::reboot() +{ + auto *op = new RebootOperation(m_serialPort, this); + enqueueOperation(op); + return op; +} + +DFUOperation *CommandInterface::startRecoveryMode() +{ + auto *op = new DFUOperation(m_serialPort, this); + enqueueOperation(op); + return op; +} + +ListOperation *CommandInterface::list(const QByteArray &dirName) +{ + auto *op = new ListOperation(m_serialPort, dirName, this); + enqueueOperation(op); + return op; +} + +StatOperation *CommandInterface::stat(const QByteArray &fileName) +{ + auto *op = new StatOperation(m_serialPort, fileName, this); + enqueueOperation(op); + return op; +} + +ReadOperation *CommandInterface::read(const QByteArray &fileName, QIODevice *file) +{ + auto *op = new ReadOperation(m_serialPort, fileName, file, this); + enqueueOperation(op); + return op; +} + +MkDirOperation *CommandInterface::mkdir(const QByteArray &dirName) +{ + auto *op = new MkDirOperation(m_serialPort, dirName, this); + enqueueOperation(op); + return op; +} + +WriteOperation *CommandInterface::write(const QByteArray &fileName, QIODevice *file) +{ + auto *op = new WriteOperation(m_serialPort, fileName, file, this); + enqueueOperation(op); + return op; +} + +RemoveOperation *CommandInterface::remove(const QByteArray &fileName) +{ + auto *op = new RemoveOperation(m_serialPort, fileName, this); + enqueueOperation(op); + return op; +} + +bool CommandInterface::onQueueStarted() +{ + const auto success = m_serialPort->open(QIODevice::ReadWrite); + check_return_bool(success, QStringLiteral("Serial port error: %1").arg(m_serialPort->errorString())); + + enqueueOperation(new SkipMOTDOperation(m_serialPort, this)); + return true; +} + +bool CommandInterface::onQueueFinished() +{ + m_serialPort->close(); + return true; +} diff --git a/backend/flipperzero/storagecontroller.h b/backend/flipperzero/commandinterface.h similarity index 51% rename from backend/flipperzero/storagecontroller.h rename to backend/flipperzero/commandinterface.h index db90dd77..32ff3b74 100644 --- a/backend/flipperzero/storagecontroller.h +++ b/backend/flipperzero/commandinterface.h @@ -1,39 +1,35 @@ #pragma once -#include -#include - -#include "signalingfailable.h" +#include "abstractoperationrunner.h" class QIODevice; class QSerialPort; - -class AbstractSerialOperation; +class QSerialPortInfo; namespace Flipper { namespace Zero { +class DeviceState; + +class DFUOperation; class ListOperation; class StatOperation; class ReadOperation; class MkDirOperation; class WriteOperation; class RemoveOperation; +class RebootOperation; -class StorageController : public SignalingFailable +class CommandInterface : public AbstractOperationRunner { Q_OBJECT - using OperationQueue = QQueue; - - enum class State { - Idle, - Running - }; - public: - StorageController(const QSerialPortInfo &portInfo, QObject *parent = nullptr); - ~StorageController(); + CommandInterface(DeviceState *state, QObject *parent = nullptr); + ~CommandInterface(); + + RebootOperation *reboot(); + DFUOperation *startRecoveryMode(); ListOperation *list(const QByteArray &dirName); StatOperation *stat(const QByteArray &fileName); @@ -42,19 +38,11 @@ class StorageController : public SignalingFailable WriteOperation *write(const QByteArray &fileName, QIODevice *file); RemoveOperation *remove(const QByteArray &fileName); -private slots: - void processQueue(); - private: - bool openPort(); - void closePort(); - - void enqueueOperation(AbstractSerialOperation *op); - void clearQueue(); + bool onQueueStarted() override; + bool onQueueFinished() override; - OperationQueue m_operationQueue; QSerialPort *m_serialPort; - State m_state; }; } diff --git a/backend/flipperzero/deviceinfofetcher.cpp b/backend/flipperzero/deviceinfofetcher.cpp index f1fc2a7b..0c94334e 100644 --- a/backend/flipperzero/deviceinfofetcher.cpp +++ b/backend/flipperzero/deviceinfofetcher.cpp @@ -4,7 +4,7 @@ #include #include -#include "common/skipmotdoperation.h" +#include "cli/skipmotdoperation.h" #include "device/stm32wb55.h" #include "serialfinder.h" #include "factoryinfo.h" @@ -110,7 +110,12 @@ void VCPDeviceInfoFetcher::onSerialPortReadyRead() if(m_receivedData.endsWith(PROMPT_READY)) { parseReceivedData(); - finish(); + + if(m_deviceInfo.name.isEmpty()) { + finishWithError(QStringLiteral("Failed to read device factory information")); + } else { + finish(); + } } } @@ -139,6 +144,9 @@ void VCPDeviceInfoFetcher::parseReceivedData() return; } + m_deviceInfo.fusVersion = QStringLiteral("0.0.0"); + m_deviceInfo.radioVersion = QStringLiteral("0.0.0"); + while(buf.canReadLine()) { parseLine(buf.readLine()); } @@ -154,20 +162,51 @@ void VCPDeviceInfoFetcher::parseLine(const QByteArray &line) } const auto validx = line.indexOf(':'); + const auto key = line.left(validx).trimmed(); const auto value = line.mid(validx + 1).trimmed(); - if(line.startsWith(QByteArrayLiteral("hardware_name"))) { + if(key == QByteArrayLiteral("hardware_name")) { m_deviceInfo.name = value; - } else if(line.startsWith(QByteArrayLiteral("hardware_target"))) { + } else if(key == QByteArrayLiteral("hardware_target")) { m_deviceInfo.target = QStringLiteral("f") + value; - } else if(line.startsWith(QByteArrayLiteral("firmware_version"))) { + } else if(key == QByteArrayLiteral("firmware_version")) { m_deviceInfo.firmware.version = value; - } else if(line.startsWith(QByteArrayLiteral("firmware_commit"))) { + } else if(key == QByteArrayLiteral("firmware_commit")) { m_deviceInfo.firmware.commit = value; - } else if(line.startsWith(QByteArrayLiteral("firmware_branch"))) { + } else if(key == QByteArrayLiteral("firmware_branch")) { m_deviceInfo.firmware.branch = value; - } else if(line.startsWith(QByteArrayLiteral("firmware_build_date"))) { + } else if(key == QByteArrayLiteral("firmware_build_date")) { m_deviceInfo.firmware.date = QDateTime::fromString(value, QStringLiteral("dd-MM-yyyy")); + + } else if(key == QByteArrayLiteral("radio_stack_major")) { + auto fields = m_deviceInfo.radioVersion.split('.'); + fields.replace(0, value); + m_deviceInfo.radioVersion = fields.join('.'); + + } else if(key == QByteArrayLiteral("radio_stack_minor")) { + auto fields = m_deviceInfo.radioVersion.split('.'); + fields.replace(1, value); + m_deviceInfo.radioVersion = fields.join('.'); + + } else if(key == QByteArrayLiteral("radio_stack_sub")) { + auto fields = m_deviceInfo.radioVersion.split('.'); + fields.replace(2, value); + m_deviceInfo.radioVersion = fields.join('.'); + + } else if(key == QByteArrayLiteral("radio_fus_major")) { + auto fields = m_deviceInfo.fusVersion.split('.'); + fields.replace(0, value); + m_deviceInfo.fusVersion = fields.join('.'); + + } else if(key == QByteArrayLiteral("radio_fus_minor")) { + auto fields = m_deviceInfo.fusVersion.split('.'); + fields.replace(1, value); + m_deviceInfo.fusVersion = fields.join('.'); + + } else if(key == QByteArrayLiteral("radio_fus_sub")) { + auto fields = m_deviceInfo.fusVersion.split('.'); + fields.replace(2, value); + m_deviceInfo.fusVersion = fields.join('.'); } else {} } diff --git a/backend/flipperzero/devicestate.cpp b/backend/flipperzero/devicestate.cpp new file mode 100644 index 00000000..518a9c61 --- /dev/null +++ b/backend/flipperzero/devicestate.cpp @@ -0,0 +1,154 @@ +#include "devicestate.h" + +using namespace Flipper; +using namespace Zero; + +DeviceState::DeviceState(const DeviceInfo &deviceInfo, QObject *parent): + QObject(parent), + m_deviceInfo(deviceInfo), + m_isPersistent(false), + m_isOnline(true), + m_isError(false) +{} + +void DeviceState::reset(const DeviceInfo &newDeviceInfo) +{ + setDeviceInfo(newDeviceInfo); + setError(false); + setProgress(0.0); + setOnline(true); +} + +const DeviceInfo &DeviceState::deviceInfo() const +{ + return m_deviceInfo; +} + +void DeviceState::setDeviceInfo(const DeviceInfo &newDeviceInfo) +{ + m_deviceInfo = newDeviceInfo; + emit deviceInfoChanged(); +} + +bool DeviceState::isPersistent() const +{ + return m_isPersistent; +} + +void DeviceState::setPersistent(bool set) +{ + if(m_isPersistent == set) { + return; + } + + m_isPersistent = set; + emit isPersistentChanged(); +} + +bool DeviceState::isOnline() const +{ + return m_isOnline; +} + +void DeviceState::setOnline(bool set) +{ + if(m_isOnline == set) { + return; + } + + m_isOnline = set; + emit isOnlineChanged(); +} + +bool DeviceState::isError() const +{ + return m_isError; +} + +void DeviceState::setError(bool set) +{ + if(m_isError == set) { + return; + } + + m_isError = set; + emit errorChanged(); +} + +bool DeviceState::isRecoveryMode() const +{ + return m_deviceInfo.usbInfo.productID() == 0xdf11; +} + +double DeviceState::progress() const +{ + return m_progress; +} + +void DeviceState::setProgress(double newProgress) +{ + if(qFuzzyCompare(m_progress, newProgress)) { + return; + } + + m_progress = newProgress; + emit progressChanged(); +} + +const QString &DeviceState::statusString() const +{ + return m_statusString; +} + +void DeviceState::setStatusString(const QString &newStatusString) +{ + if(m_statusString == newStatusString) { + return; + } + + m_statusString = newStatusString; + emit statusChanged(); +} + +const QString &DeviceState::errorString() const +{ + return m_errorString; +} + +void DeviceState::setErrorString(const QString &newErrorString) +{ + if(m_errorString == newErrorString) { + return; + } + + m_errorString = newErrorString; + m_isError = true; + + emit errorChanged(); +} + +const QString &DeviceState::name() const +{ + return m_deviceInfo.name; +} + +const QString &DeviceState::model() const +{ + const static QString model("Flipper Zero"); + return model; +} + +const QString &DeviceState::target() const +{ + return m_deviceInfo.target; +} + +const QString &DeviceState::version() const +{ + if(m_deviceInfo.firmware.branch == QStringLiteral("dev")) { + return m_deviceInfo.firmware.commit; + } else { + return m_deviceInfo.firmware.version; + } +} + diff --git a/backend/flipperzero/devicestate.h b/backend/flipperzero/devicestate.h new file mode 100644 index 00000000..56f387e7 --- /dev/null +++ b/backend/flipperzero/devicestate.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "deviceinfo.h" + +namespace Flipper { +namespace Zero { + +class DeviceState : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool isPersistent READ isPersistent NOTIFY isPersistentChanged) + Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged) + Q_PROPERTY(bool isError READ isError NOTIFY errorChanged) + Q_PROPERTY(bool isRecoveryMode READ isRecoveryMode NOTIFY deviceInfoChanged) + + Q_PROPERTY(QString name READ name NOTIFY deviceInfoChanged) + Q_PROPERTY(QString model READ model CONSTANT) + Q_PROPERTY(QString target READ target NOTIFY deviceInfoChanged) + Q_PROPERTY(QString version READ version NOTIFY deviceInfoChanged) + + Q_PROPERTY(QString statusString READ statusString NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + + Q_PROPERTY(double progress READ progress NOTIFY progressChanged) + +public: + DeviceState(const DeviceInfo &deviceInfo, QObject *parent = nullptr); + + void reset(const DeviceInfo &newDeviceInfo); + + const DeviceInfo &deviceInfo() const; + void setDeviceInfo(const DeviceInfo &newDeviceInfo); + + bool isPersistent() const; + void setPersistent(bool set); + + bool isOnline() const; + void setOnline(bool set); + + bool isError() const; + void setError(bool set); + + bool isRecoveryMode() const; + + double progress() const; + void setProgress(double newProgress); + + const QString &statusString() const; + void setStatusString(const QString &newStatusString); + + const QString &errorString() const; + void setErrorString(const QString &newErrorString); + + const QString &name() const; + const QString &model() const; + const QString &target() const; + const QString &version() const; + +signals: + void deviceInfoChanged(); + void isPersistentChanged(); + void isOnlineChanged(); + + void updateInfoChanged(); + + void statusChanged(); + void errorChanged(); + + void progressChanged(); + +private: + DeviceInfo m_deviceInfo; + + bool m_isPersistent; + bool m_isOnline; + bool m_isError; + + QString m_statusString; + QString m_errorString; + + double m_progress; +}; + +} +} + diff --git a/backend/flipperzero/firmwareupdater.cpp b/backend/flipperzero/firmwareupdater.cpp new file mode 100644 index 00000000..b8714b4e --- /dev/null +++ b/backend/flipperzero/firmwareupdater.cpp @@ -0,0 +1,105 @@ +#include "firmwareupdater.h" + +#include "devicestate.h" +#include "recoveryinterface.h" +#include "utilityinterface.h" + +#include "toplevel/wirelessstackupdateoperation.h" +#include "toplevel/firmwareupdateoperation.h" +#include "toplevel/fullrepairoperation.h" +#include "toplevel/fullupdateoperation.h" + +#include "preferences.h" + +using namespace Flipper; +using namespace Zero; + +FirmwareUpdater::FirmwareUpdater(DeviceState *state, QObject *parent): + AbstractOperationRunner(parent), + m_state(state), + m_recovery(new RecoveryInterface(state, this)), + m_utility(new UtilityInterface(state, this)) +{} + +void FirmwareUpdater::fullUpdate(const Updates::VersionInfo &versionInfo) +{ + if(m_state->isRecoveryMode()) { + return; + } + + auto *operation = new FullUpdateOperation(m_recovery, m_utility, m_state, versionInfo, this); + enqueueOperation(operation); +} + +void FirmwareUpdater::fullRepair(const Updates::VersionInfo &versionInfo) +{ + if(!m_state->isRecoveryMode()) { + return; + } + + auto *operation = new FullRepairOperation(m_recovery, m_utility, m_state, versionInfo, this); + enqueueOperation(operation); +} + +void FirmwareUpdater::localFirmwareUpdate(const QUrl &fileUrl) +{ + enqueueOperation(new FirmwareUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this)); +} + +void FirmwareUpdater::localFUSUpdate(const QUrl &fileUrl) +{ + //TODO: User-settable address + enqueueOperation(new FUSUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), 0x080EC000, this)); +} + +void FirmwareUpdater::localWirelessStackUpdate(const QUrl &fileUrl) +{ + enqueueOperation(new WirelessStackUpdateOperation(m_recovery, m_utility, m_state, fileUrl.toLocalFile(), this)); +} + +bool FirmwareUpdater::canUpdate(const Updates::VersionInfo &versionInfo) const +{ + if(canChangeChannel()) { + return false; + } else if(branchToChannelName() == channelName(ChannelType::Development)) { + return m_state->deviceInfo().firmware.commit != versionInfo.number(); + } else { + return m_state->deviceInfo().firmware.version < versionInfo.number(); + } +} + +bool FirmwareUpdater::canRollback(const Updates::VersionInfo &versionInfo) const +{ + if(canChangeChannel() || (branchToChannelName() == channelName(ChannelType::Development))) { + return false; + } else { + return m_state->deviceInfo().firmware.version > versionInfo.number(); + } +} + +bool FirmwareUpdater::canChangeChannel() const +{ + return branchToChannelName() != globalPrefs()->firmwareUpdateChannel(); +} + +const QString &FirmwareUpdater::channelName(ChannelType channelType) +{ + static const QStringList channelNames = { + QStringLiteral("development"), QStringLiteral("release-candidate"), QStringLiteral("release") + }; + + return channelNames[(int)channelType]; +} + +const QString &FirmwareUpdater::branchToChannelName() const +{ + const auto &branchName = m_state->deviceInfo().firmware.branch; + + if(branchName == QStringLiteral("dev")) { + return channelName(ChannelType::Development); + } else if(branchName.contains(QStringLiteral("-rc"))) { + return channelName(ChannelType::ReleaseCandidate); + } else { + return channelName(ChannelType::Release); + } +} diff --git a/backend/flipperzero/firmwareupdater.h b/backend/flipperzero/firmwareupdater.h new file mode 100644 index 00000000..12849a27 --- /dev/null +++ b/backend/flipperzero/firmwareupdater.h @@ -0,0 +1,49 @@ +#pragma once + +#include "flipperupdates.h" +#include "abstractoperationrunner.h" + +namespace Flipper { +namespace Zero { + +class DeviceState; +class RecoveryInterface; +class UtilityInterface; + +class FirmwareUpdater : public AbstractOperationRunner +{ + Q_OBJECT + + enum class ChannelType { + Development = 0, + ReleaseCandidate, + Release + }; + +public: + FirmwareUpdater(DeviceState *state, QObject *parent = nullptr); + +public slots: + bool canUpdate(const Flipper::Updates::VersionInfo &versionInfo) const; + bool canRollback(const Flipper::Updates::VersionInfo &versionInfo) const; + bool canChangeChannel() const; + + void fullUpdate(const Flipper::Updates::VersionInfo &versionInfo); + void fullRepair(const Flipper::Updates::VersionInfo &versionInfo); + + void localFirmwareUpdate(const QUrl &fileUrl); + void localFUSUpdate(const QUrl &fileUrl); + void localWirelessStackUpdate(const QUrl &fileUrl); + +private: + static const QString &channelName(ChannelType channelType); + const QString &branchToChannelName() const; + + DeviceState *m_state; + RecoveryInterface *m_recovery; + UtilityInterface *m_utility; +}; + +} +} + diff --git a/backend/flipperzero/flipperzero.cpp b/backend/flipperzero/flipperzero.cpp index d7af1ff4..04c7f151 100644 --- a/backend/flipperzero/flipperzero.cpp +++ b/backend/flipperzero/flipperzero.cpp @@ -1,283 +1,45 @@ #include "flipperzero.h" -#include -#include - -#include "recoverycontroller.h" -#include "storagecontroller.h" -#include "remotecontroller.h" +#include "devicestate.h" +#include "firmwareupdater.h" +#include "screenstreamer.h" #include "macros.h" -namespace Flipper { - +using namespace Flipper; using namespace Zero; FlipperZero::FlipperZero(const Zero::DeviceInfo &info, QObject *parent): QObject(parent), - - m_isPersistent(false), - m_isOnline(true), - m_isError(false), - - m_deviceInfo(info), - - m_progress(0), - m_remote(nullptr), - m_recovery(nullptr), - m_storage(nullptr) + m_state(new DeviceState(info, this)), + m_updater(new FirmwareUpdater(m_state, this)), + m_streamer(new ScreenStreamer(m_state, this)) { - initControllers(); + connect(m_updater, &SignalingFailable::errorOccured, this, &FlipperZero::onErrorOccured); } FlipperZero::~FlipperZero() { - setOnline(false); -} - -void FlipperZero::reset(const Zero::DeviceInfo &info) -{ - setDeviceInfo(info); - initControllers(); - - setError(QStringLiteral("No error"), false); - setProgress(0); - setOnline(true); -} - -void FlipperZero::setDeviceInfo(const Zero::DeviceInfo &info) -{ - // Not checking the huge structure for equality - m_deviceInfo = info; - emit deviceInfoChanged(); -} - -void FlipperZero::setPersistent(bool set) -{ - if(set == m_isPersistent) { - return; - } - - m_isPersistent = set; - emit isPersistentChanged(); -} - -void FlipperZero::setOnline(bool set) -{ - if(set == m_isOnline) { - return; - } - - m_isOnline = set; - emit isOnlineChanged(); -} - -void FlipperZero::setError(const QString &msg, bool set) -{ - m_isError = set; - - if(!msg.isEmpty()) { - error_msg(msg); - m_errorString = msg; - } - - emit isErrorChanged(); -} - -bool FlipperZero::isPersistent() const -{ - return m_isPersistent; -} - -bool FlipperZero::isOnline() const -{ - return m_isOnline; -} - -bool FlipperZero::isError() const -{ - return m_isError; -} - -bool FlipperZero::bootToDFU() -{ - setMessage("Entering DFU bootloader mode..."); - - auto *serialPort = new QSerialPort(m_deviceInfo.serialInfo, this); - - const auto success = serialPort->open(QIODevice::WriteOnly) && serialPort->setDataTerminalReady(true) && - (serialPort->write(QByteArrayLiteral("\rdfu\r\n")) > 0) && serialPort->waitForBytesWritten(1000); - if(!success) { - setError("Can't detach the device: Failed to reset in DFU mode"); - error_msg(QString("Serial port status: %1").arg(serialPort->errorString())); - } - - serialPort->close(); - serialPort->deleteLater(); - - return success; -} - -const QString &FlipperZero::name() const -{ - return m_deviceInfo.name; -} - -const QString &FlipperZero::model() const -{ - static const QString m = "Flipper Zero"; - return m; -} - -const QString &FlipperZero::target() const -{ - return m_deviceInfo.target; -} - -const QString &FlipperZero::version() const -{ - if(m_deviceInfo.firmware.branch == QStringLiteral("dev")) { - return m_deviceInfo.firmware.commit; - } else { - return m_deviceInfo.firmware.version; - } -} - -const QString &FlipperZero::messageString() const -{ - return m_statusMessage; -} - -const QString &FlipperZero::errorString() const -{ - return m_errorString; -} - -double FlipperZero::progress() const -{ - return m_progress; -} - -const DeviceInfo &FlipperZero::deviceInfo() const -{ - return m_deviceInfo; -} - -bool FlipperZero::isDFU() const -{ - return m_deviceInfo.usbInfo.productID() == 0xdf11; + m_state->setOnline(false); } -Flipper::Zero::RemoteController *FlipperZero::remote() const +DeviceState *FlipperZero::deviceState() const { - return m_remote; + return m_state; } -RecoveryController *FlipperZero::recovery() const +Flipper::Zero::ScreenStreamer *FlipperZero::streamer() const { - return m_recovery; + return m_streamer; } -StorageController *FlipperZero::storage() const +FirmwareUpdater *FlipperZero::updater() const { - return m_storage; + return m_updater; } -void FlipperZero::setName(const QString &name) +void FlipperZero::onErrorOccured() { - if(m_deviceInfo.name == name) { - return; - } - - m_deviceInfo.name = name; - emit deviceInfoChanged(); -} - -void FlipperZero::setTarget(const QString &target) -{ - if(m_deviceInfo.target == target) { - return; - } - - m_deviceInfo.target = target; - emit deviceInfoChanged(); -} - -void FlipperZero::setVersion(const QString &version) -{ - if(m_deviceInfo.firmware.version == version) { - return; - } - - m_deviceInfo.firmware.version = version; - emit deviceInfoChanged(); -} - -void FlipperZero::setMessage(const QString &message) -{ - info_msg(message); - m_statusMessage = message; - emit messageChanged(); -} - -void FlipperZero::setProgress(double progress) -{ - if(qFuzzyCompare(m_progress, progress)) { - return; - } - - m_progress = progress; - emit progressChanged(); -} - -void FlipperZero::onControllerErrorOccured() -{ - auto *controller = qobject_cast(sender()); - - if(!controller) { - return; - } - - setError(controller->errorString()); -} - -void FlipperZero::initControllers() -{ - if(m_remote) { - m_remote->deleteLater(); - m_remote = nullptr; - } - - if(m_recovery) { - m_recovery->deleteLater(); - m_recovery = nullptr; - } - - if(m_storage) { - m_storage->deleteLater(); - m_storage = nullptr; - } - - // TODO: better message delivery system - if(isDFU()) { - m_recovery = new RecoveryController(m_deviceInfo.usbInfo, this); - - connect(m_recovery, &RecoveryController::messageChanged, this, [=]() { - setMessage(m_recovery->message()); - }); - - connect(m_recovery, &RecoveryController::progressChanged, this, [=]() { - setProgress(m_recovery->progress()); - }); - - connect(m_recovery, &SignalingFailable::errorOccured, this, &FlipperZero::onControllerErrorOccured); - - } else { - m_remote = new RemoteController(m_deviceInfo.serialInfo, this); - m_storage = new StorageController(m_deviceInfo.serialInfo, this); - - connect(m_storage, &SignalingFailable::errorOccured, this, &FlipperZero::onControllerErrorOccured); - } -} - + auto *instance = qobject_cast(sender()); + m_state->setErrorString(instance->errorString()); } diff --git a/backend/flipperzero/flipperzero.h b/backend/flipperzero/flipperzero.h index 2b76f59f..2de5621a 100644 --- a/backend/flipperzero/flipperzero.h +++ b/backend/flipperzero/flipperzero.h @@ -7,102 +7,33 @@ namespace Flipper { namespace Zero { - class RemoteController; - class RecoveryController; - class StorageController; + class DeviceState; + class ScreenStreamer; + class FirmwareUpdater; } class FlipperZero : public QObject { Q_OBJECT - - Q_PROPERTY(QString name READ name NOTIFY deviceInfoChanged) - Q_PROPERTY(QString model READ model CONSTANT) - Q_PROPERTY(QString target READ target NOTIFY deviceInfoChanged) - Q_PROPERTY(QString version READ version NOTIFY deviceInfoChanged) - Q_PROPERTY(QString messageString READ messageString NOTIFY messageChanged) - Q_PROPERTY(QString errorString READ errorString NOTIFY isErrorChanged) - - Q_PROPERTY(double progress READ progress NOTIFY progressChanged) - - Q_PROPERTY(bool isDFU READ isDFU NOTIFY deviceInfoChanged) - Q_PROPERTY(bool isPersistent READ isPersistent NOTIFY isPersistentChanged) - Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged) - Q_PROPERTY(bool isError READ isError NOTIFY isErrorChanged) - - Q_PROPERTY(Flipper::Zero::RemoteController* remote READ remote CONSTANT) - Q_PROPERTY(Flipper::Zero::RecoveryController* recovery READ recovery CONSTANT) + Q_PROPERTY(Flipper::Zero::DeviceState* state READ deviceState CONSTANT) + Q_PROPERTY(Flipper::Zero::FirmwareUpdater* updater READ updater CONSTANT) + Q_PROPERTY(Flipper::Zero::ScreenStreamer* streamer READ streamer CONSTANT) public: FlipperZero(const Zero::DeviceInfo &info, QObject *parent = nullptr); ~FlipperZero(); - void reset(const Zero::DeviceInfo &info); - void setDeviceInfo(const Zero::DeviceInfo &info); - - void setPersistent(bool set); - void setOnline(bool set); - void setError(const QString &msg = QString(), bool set = true); - - bool isPersistent() const; - bool isOnline() const; - bool isError() const; - bool isDFU() const; - - bool bootToDFU(); - - const QString &name() const; - const QString &model() const; - const QString &target() const; - const QString &version() const; - - const QString &messageString() const; - const QString &errorString() const; - - double progress() const; - - const Flipper::Zero::DeviceInfo &deviceInfo() const; - - Flipper::Zero::RemoteController *remote() const; - Flipper::Zero::RecoveryController *recovery() const; - Flipper::Zero::StorageController *storage() const; - - void setName(const QString &name); - void setTarget(const QString &target); - void setVersion(const QString &version); - void setMessage(const QString &message); - void setProgress(double progress); - -signals: - void deviceInfoChanged(); - - void messageChanged(); - void progressChanged(); - - void isPersistentChanged(); - void isOnlineChanged(); - void isErrorChanged(); + Flipper::Zero::DeviceState *deviceState() const; + Flipper::Zero::ScreenStreamer *streamer() const; + Flipper::Zero::FirmwareUpdater *updater() const; private slots: - void onControllerErrorOccured(); + void onErrorOccured(); private: - void initControllers(); - - bool m_isPersistent; - bool m_isOnline; - bool m_isError; - - Zero::DeviceInfo m_deviceInfo; - - QString m_statusMessage; - QString m_errorString; - - double m_progress; - - Zero::RemoteController *m_remote; - Zero::RecoveryController *m_recovery; - Zero::StorageController *m_storage; + Zero::DeviceState *m_state; + Zero::FirmwareUpdater *m_updater; + Zero::ScreenStreamer *m_streamer; }; } diff --git a/backend/flipperzero/helper/firmwarehelper.cpp b/backend/flipperzero/helper/firmwarehelper.cpp new file mode 100644 index 00000000..c65791f7 --- /dev/null +++ b/backend/flipperzero/helper/firmwarehelper.cpp @@ -0,0 +1,187 @@ +#include "firmwarehelper.h" + +#include +#include +#include + +#include "flipperzero/devicestate.h" + +#include "flipperzero/helper/scriptshelper.h" +#include "flipperzero/helper/radiomanifesthelper.h" + +#include "remotefilefetcher.h" +#include "tempdirectories.h" + +using namespace Flipper; +using namespace Zero; + +FirmwareHelper::FirmwareHelper(DeviceState *deviceState, const Updates::VersionInfo &versionInfo, QObject *parent): + AbstractOperationHelper(parent), + m_deviceState(deviceState), + m_versionInfo(versionInfo) +{} + +FirmwareHelper::~FirmwareHelper() +{ + for(const auto &file : qAsConst(m_files)) { + file->remove(); + } + + m_files.clear(); +} + +QFile *FirmwareHelper::file(FileIndex index) const +{ + return m_files.value(index, nullptr); +} + +bool FirmwareHelper::hasRadioUpdate() const +{ + return m_hasRadioUpdate; +} + +void FirmwareHelper::nextStateLogic() +{ + if(state() == AbstractOperationHelper::Ready) { + setState(FirmwareHelper::FetchingFirmware); + fetchFirmware(); + + } else if(state() == FirmwareHelper::FetchingFirmware) { + setState(FirmwareHelper::FetchingCore2Firmware); + fetchCore2Firmware(); + + } else if(state() == FirmwareHelper::FetchingCore2Firmware) { + setState(FirmwareHelper::PreparingRadioFirmware); + prepareRadioFirmware(); + + } else if(state() == FirmwareHelper::PreparingRadioFirmware) { + setState(FirmwareHelper::FetchingScripts); + fetchScripts(); + + } else if(state() == FirmwareHelper::FetchingScripts) { + setState(FirmwareHelper::PreparingOptionBytes); + prepareOptionBytes(); + + } else if(state() == FirmwareHelper::PreparingOptionBytes) { + setState(FirmwareHelper::FetchingAssets); + fetchAssets(); + + } else if(state() == FirmwareHelper::FetchingAssets) { + finish(); + } +} + +void FirmwareHelper::fetchFirmware() +{ + m_deviceState->setStatusString(QStringLiteral("Fetching application firmware...")); + const auto &fileInfo = m_versionInfo.fileInfo(QStringLiteral("full_dfu"), m_deviceState->deviceInfo().target); + fetchFile(FileIndex::Firmware, fileInfo); +} + +void FirmwareHelper::fetchCore2Firmware() +{ + m_deviceState->setStatusString(QStringLiteral("Fetching radio firmware...")); + const auto &fileInfo = m_versionInfo.fileInfo(QStringLiteral("core2_firmware_tgz"), QStringLiteral("any")); + fetchFile(FileIndex::Core2Tgz, fileInfo); +} + +void FirmwareHelper::prepareRadioFirmware() +{ + m_deviceState->setStatusString(QStringLiteral("Preparing radio firmware...")); + auto *helper = new RadioManifestHelper(m_files[FileIndex::Core2Tgz], this); + + connect(helper, &AbstractOperationHelper::finished, this, [=]() { + helper->deleteLater(); + + if(helper->isError()) { + finishWithError(helper->errorString()); + return; + } + + const auto &newRadioVersion = helper->radioVersion(); + const auto ¤tRadioVersion = m_deviceState->deviceInfo().radioVersion; + + m_hasRadioUpdate = currentRadioVersion.isEmpty() ? true : currentRadioVersion < newRadioVersion; + + if(m_hasRadioUpdate) { + auto *file = tempDirs()->createTempFile(); + m_files.insert(FileIndex::RadioFirmware, file); + + if(!file->open(QIODevice::WriteOnly)) { + finishWithError(QStringLiteral("Failed to open temporary file: %1").arg(file->errorString())); + return; + } else if(file->write(helper->radioFirmwareData()) <= 0) { + finishWithError(QStringLiteral("Failed to write to temporary file: %1").arg(file->errorString())); + return; + } else { + file->close(); + } + } + + advanceState(); + }); +} + +void FirmwareHelper::fetchScripts() +{ + m_deviceState->setStatusString(QStringLiteral("Fetching scripts...")); + const auto &fileInfo = m_versionInfo.fileInfo(QStringLiteral("scripts_tgz"), QStringLiteral("any")); + fetchFile(FileIndex::ScriptsTgz, fileInfo); +} + +void FirmwareHelper::prepareOptionBytes() +{ + m_deviceState->setStatusString(QStringLiteral("Preparing scripts...")); + auto *helper = new ScriptsHelper(m_files[FileIndex::ScriptsTgz], this); + + connect(helper, &AbstractOperationHelper::finished, this, [=]() { + helper->deleteLater(); + + if(helper->isError()) { + finishWithError(helper->errorString()); + return; + } + + auto *file = tempDirs()->createTempFile(); + m_files.insert(FileIndex::OptionBytes, file); + + if(!file->open(QIODevice::WriteOnly)) { + finishWithError(QStringLiteral("Failed to open temporary file: %1").arg(file->errorString())); + } else if(file->write(helper->optionBytesData()) <= 0) { + finishWithError(QStringLiteral("Failed to write to temporary file: %1").arg(file->errorString())); + } else { + file->close(); + advanceState(); + } + }); +} + +void FirmwareHelper::fetchAssets() +{ + m_deviceState->setStatusString(QStringLiteral("Fetching databases...")); + const auto &fileInfo = m_versionInfo.fileInfo(QStringLiteral("resources_tgz"), QStringLiteral("any")); + fetchFile(FileIndex::AssetsTgz, fileInfo); +} + +void FirmwareHelper::fetchFile(FileIndex index, const Updates::FileInfo &fileInfo) +{ + const auto fileName = QUrl(fileInfo.url()).fileName(); + + auto *file = tempDirs()->createFile(fileName, this); + auto *fetcher = new RemoteFileFetcher(fileInfo, file, this); + + if(fetcher->isError()) { + finishWithError(QStringLiteral("Failed to fetch file: %1").arg(fetcher->errorString())); + return; + } + + connect(fetcher, &RemoteFileFetcher::finished, this, [=]() { + m_files.insert(index, file); + + if(fetcher->isError()) { + finishWithError(QStringLiteral("Failed to fetch file: %1").arg(fetcher->errorString())); + } else { + advanceState(); + } + }); +} diff --git a/backend/flipperzero/helper/firmwarehelper.h b/backend/flipperzero/helper/firmwarehelper.h new file mode 100644 index 00000000..85983353 --- /dev/null +++ b/backend/flipperzero/helper/firmwarehelper.h @@ -0,0 +1,66 @@ +#pragma once + +#include "abstractoperationhelper.h" + +#include + +#include "flipperupdates.h" + +class QFile; + +namespace Flipper { +namespace Zero { + +class DeviceState; + +class FirmwareHelper : public AbstractOperationHelper +{ + Q_OBJECT + + enum State { + FetchingFirmware = AbstractOperationHelper::User, + FetchingCore2Firmware, + PreparingRadioFirmware, + FetchingScripts, + PreparingOptionBytes, + FetchingAssets + }; + +public: + + enum class FileIndex { + Firmware, + Core2Tgz, + ScriptsTgz, + AssetsTgz, + RadioFirmware, + OptionBytes + }; + + FirmwareHelper(DeviceState *deviceState, const Updates::VersionInfo &versionInfo, QObject *parent = nullptr); + ~FirmwareHelper(); + + QFile *file(FileIndex index) const; + bool hasRadioUpdate() const; + +private: + void nextStateLogic() override; + + void fetchFirmware(); + void fetchCore2Firmware(); + void prepareRadioFirmware(); + void fetchScripts(); + void prepareOptionBytes(); + void fetchAssets(); + + void fetchFile(FileIndex index, const Updates::FileInfo &fileInfo); + + DeviceState *m_deviceState; + Updates::VersionInfo m_versionInfo; + QMap m_files; + bool m_hasRadioUpdate; +}; + +} +} + diff --git a/backend/flipperzero/helper/radiomanifesthelper.cpp b/backend/flipperzero/helper/radiomanifesthelper.cpp new file mode 100644 index 00000000..716b4024 --- /dev/null +++ b/backend/flipperzero/helper/radiomanifesthelper.cpp @@ -0,0 +1,77 @@ +#include "radiomanifesthelper.h" + +#include + +#include "tararchive.h" +#include "tarziparchive.h" + +#include "flipperzero/radiomanifest.h" + +using namespace Flipper; +using namespace Zero; + +RadioManifestHelper::RadioManifestHelper(QFile *radioArchive, QObject *parent): + AbstractOperationHelper(parent), + m_compressedFile(radioArchive) +{} + +const QString &RadioManifestHelper::radioVersion() const +{ + return m_manifest.firmware().radio().version(); +} + +const QString &RadioManifestHelper::fusVersion() const +{ + return m_manifest.firmware().fus().version(); +} + +const QByteArray RadioManifestHelper::radioFirmwareData() const +{ + const auto &fileName = m_manifest.firmware().radio().files().first().name(); + return m_archive->archiveIndex()->fileData(QStringLiteral("core2_firmware/%1").arg(fileName)); +} + +void RadioManifestHelper::nextStateLogic() +{ + if(state() == AbstractOperationHelper::Ready) { + setState(RadioManifestHelper::UncompressingArchive); + uncompressArchive(); + + } else if(state() == RadioManifestHelper::UncompressingArchive) { + setState(RadioManifestHelper::ReadingManifest); + readManifest(); + + } else if(state() == RadioManifestHelper::ReadingManifest) { + finish(); + } +} + +void RadioManifestHelper::uncompressArchive() +{ + m_archive = new TarZipArchive(m_compressedFile, this); + + if(m_archive->isError()) { + finishWithError(QStringLiteral("Failed to uncompress archive file: %1").arg(m_archive->errorString())); + return; + } + + connect(m_archive, &TarZipArchive::ready, this, [=]() { + if(m_archive->isError()) { + finishWithError(QStringLiteral("Failed to uncompress archive file: %1").arg(m_archive->errorString())); + } else { + advanceState(); + } + }); +} + +void RadioManifestHelper::readManifest() +{ + const auto manifext = m_archive->archiveIndex()->fileData(QStringLiteral("core2_firmware/Manifest.json")); + m_manifest = RadioManifest(manifext); + + if(m_manifest.isError()) { + finishWithError(QStringLiteral("Failed to read radio manifest: %1").arg(m_manifest.errorString())); + } else { + advanceState(); + } +} diff --git a/backend/flipperzero/helper/radiomanifesthelper.h b/backend/flipperzero/helper/radiomanifesthelper.h new file mode 100644 index 00000000..fb151e28 --- /dev/null +++ b/backend/flipperzero/helper/radiomanifesthelper.h @@ -0,0 +1,43 @@ +#pragma once + +#include "abstractoperationhelper.h" +#include "flipperzero/radiomanifest.h" + +class QFile; +class TarZipArchive; + +namespace Flipper { +namespace Zero { + +class RadioManifestHelper : public AbstractOperationHelper +{ + Q_OBJECT + + enum State { + UncompressingArchive = AbstractOperationHelper::User, + ReadingManifest + }; + +public: + RadioManifestHelper(QFile *radioArchive, QObject *parent = nullptr); + + const QString &radioVersion() const; + const QString &fusVersion() const; + + const QByteArray radioFirmwareData() const; + const QByteArray fusFirmwareData(const QString &deviceFusVersion) const; + +private: + void nextStateLogic() override; + + void uncompressArchive(); + void readManifest(); + + QFile *m_compressedFile; + TarZipArchive *m_archive; + RadioManifest m_manifest; +}; + +} +} + diff --git a/backend/flipperzero/helper/scriptshelper.cpp b/backend/flipperzero/helper/scriptshelper.cpp new file mode 100644 index 00000000..75d70829 --- /dev/null +++ b/backend/flipperzero/helper/scriptshelper.cpp @@ -0,0 +1,48 @@ +#include "scriptshelper.h" + +#include + +#include "tararchive.h" +#include "tarziparchive.h" + +using namespace Flipper; +using namespace Zero; + +ScriptsHelper::ScriptsHelper(QFile *scriptsArchive, QObject *parent): + AbstractOperationHelper(parent), + m_compressedFile(scriptsArchive) +{} + +const QByteArray ScriptsHelper::optionBytesData() const +{ + return m_archive->archiveIndex()->fileData(QStringLiteral("scripts/ob.data")); +} + +void ScriptsHelper::nextStateLogic() +{ + if(state() == AbstractOperationHelper::Ready) { + setState(ScriptsHelper::UncompressingArchive); + uncompressArchive(); + + } else if(state() == ScriptsHelper::UncompressingArchive) { + finish(); + } +} + +void ScriptsHelper::uncompressArchive() +{ + m_archive = new TarZipArchive(m_compressedFile, this); + + if(m_archive->isError()) { + finishWithError(QStringLiteral("Failed to uncompress archive file: %1").arg(m_archive->errorString())); + return; + } + + connect(m_archive, &TarZipArchive::ready, this, [=]() { + if(m_archive->isError()) { + finishWithError(QStringLiteral("Failed to uncompress archive file: %1").arg(m_archive->errorString())); + } else { + advanceState(); + } + }); +} diff --git a/backend/flipperzero/helper/scriptshelper.h b/backend/flipperzero/helper/scriptshelper.h new file mode 100644 index 00000000..617c10a4 --- /dev/null +++ b/backend/flipperzero/helper/scriptshelper.h @@ -0,0 +1,36 @@ +#pragma once + +#include "abstractoperationhelper.h" + +#include + +class QFile; +class TarZipArchive; + +namespace Flipper { +namespace Zero { + +class ScriptsHelper : public AbstractOperationHelper +{ + Q_OBJECT + + enum State { + UncompressingArchive = AbstractOperationHelper::User + }; + +public: + ScriptsHelper(QFile *scriptsArchive, QObject *parent = nullptr); + + const QByteArray optionBytesData() const; + +private: + void nextStateLogic() override; + void uncompressArchive(); + + QFile *m_compressedFile; + TarZipArchive *m_archive; +}; + +} +} + diff --git a/backend/flipperzero/operations/assetsdownloadoperation.cpp b/backend/flipperzero/operations/assetsdownloadoperation.cpp deleted file mode 100644 index 3f4846bf..00000000 --- a/backend/flipperzero/operations/assetsdownloadoperation.cpp +++ /dev/null @@ -1,381 +0,0 @@ -#include "assetsdownloadoperation.h" - -#include -#include -#include -#include - -#include "flipperzero/storage/removeoperation.h" -#include "flipperzero/storage/mkdiroperation.h" -#include "flipperzero/storage/writeoperation.h" -#include "flipperzero/storage/readoperation.h" -#include "flipperzero/storage/statoperation.h" -#include "flipperzero/storagecontroller.h" -#include "flipperzero/assetmanifest.h" -#include "flipperzero/flipperzero.h" -#include "gzipuncompressor.h" -#include "tararchive.h" - -#include "macros.h" - -#define RESOURCES_PREFIX QByteArrayLiteral("resources") -#define DEVICE_MANIFEST QByteArrayLiteral("/ext/Manifest") - -using namespace Flipper; -using namespace Zero; - -static void print_file_list(const QString &header, const FileNode::FileInfoList &list) -{ - qDebug() << header; - - for(const auto &e : list) { - const auto icon = QStringLiteral("[%1]").arg(e.type == FileNode::Type::RegularFile ? 'F' : - e.type == FileNode::Type::Directory ? 'D' : '?'); - qDebug() << icon << e.absolutePath; - } -} - -AssetsDownloadOperation::AssetsDownloadOperation(FlipperZero *device, QIODevice *file, QObject *parent): - Operation(device, parent), - m_compressed(file), - m_uncompressed(nullptr), - m_isDeviceManifestPresent(false) -{ - device->setMessage(QStringLiteral("Databases download pending...")); -} - -AssetsDownloadOperation::~AssetsDownloadOperation() -{} - -const QString AssetsDownloadOperation::description() const -{ - return QStringLiteral("Assets Download @%1 %2").arg(device()->model(), device()->name()); -} - -void AssetsDownloadOperation::transitionToNextState() -{ - if(state() == BasicState::Ready) { - setState(State::CheckingExtStorage); - if(!checkForExtStorage()) { - finishWithError(QStringLiteral("Failed to access the external storage")); - } - - } else if(state() == State::CheckingExtStorage) { - setState(State::ExtractingArchive); - if(!extractArchive()) { - finishWithError(QStringLiteral("Failed to extract the databases archive")); - } - - } else if(state() == State::ExtractingArchive) { - setState(ReadingLocalManifest); - if(!readLocalManifest()) { - finishWithError(QStringLiteral("Failed to read local manifest")); - } - - } else if(state() == State::ReadingLocalManifest) { - setState(CheckingDeviceManifest); - if(!checkForDeviceManifest()) { - finishWithError(QStringLiteral("Failed to check for device manifest")); - } - - } else if(state() == State::CheckingDeviceManifest) { - setState(ReadingDeviceManifest); - if(!readDeviceManifest()) { - finishWithError(QStringLiteral("Failed to read device manifest")); - } - - } else if(state() == State::ReadingDeviceManifest) { - setState(State::BuildingFileLists); - if(!buildFileLists()) { - finishWithError(QStringLiteral("Failed to build file lists")); - } - - } else if(state() == State::BuildingFileLists) { - setState(State::DeletingFiles); - if(!deleteFiles()) { - finishWithError(QStringLiteral("Failed to delete files")); - } - - } else if(state() == State::DeletingFiles) { - setState(State::WritingFiles); - if(!writeFiles()) { - finishWithError(QStringLiteral("Failed to write files")); - } - - } else if(state() == State::WritingFiles) { - finish(); - } - - if(isError()) { - device()->setError(errorString()); - } -} - -void AssetsDownloadOperation::onOperationTimeout() -{ - qDebug() << "Operation timeout"; -} - -bool AssetsDownloadOperation::checkForExtStorage() -{ - auto *op = device()->storage()->stat(QByteArrayLiteral("/ext")); - - connect(op, &AbstractOperation::finished, this, [=]() { - if(op->isError()) { - finishWithError("Failed to perform stat operation"); - } else if(op->type() == StatOperation::Type::InternalError) { - info_msg("No external storage found, finishing early."); - finish(); - - } else if(op->type() != StatOperation::Type::Storage) { - finishWithError("/ext is not a storage"); - } else { - info_msg(QStringLiteral("External storage is present, %1 bytes free.").arg(op->sizeFree())); - transitionToNextState(); - } - - if(isError()) { - device()->setError(errorString()); - } - - op->deleteLater(); - }); - - return true; -} - -bool AssetsDownloadOperation::extractArchive() -{ - // TODO: put everything into a directory & generate random names for the files - const auto tempPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); - check_return_bool(!tempPath.isEmpty(), "Failed to find a suitable temporary location."); - - check_return_bool(m_compressed->open(QIODevice::ReadOnly), m_compressed->errorString()); - - // TODO: check if file exists, etc. - m_uncompressed = new QFile(tempPath + "/qflipper-databases.tar", this); - check_return_bool(m_uncompressed->open(QIODevice::ReadWrite), m_uncompressed->errorString()); - - auto *uncompressor = new GZipUncompressor(m_compressed, m_uncompressed, this); - - connect(uncompressor, &GZipUncompressor::finished, this, [=]() { - if(uncompressor->isError()) { - finishWithError(uncompressor->errorString()); - } else { - m_archive = TarArchive(m_uncompressed); - - if(m_archive.isError()) { - finishWithError(m_archive.errorString()); - } else { - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - } - } - - uncompressor->deleteLater(); - m_compressed->deleteLater(); - }); - - return true; -} - -bool AssetsDownloadOperation::readLocalManifest() -{ - const auto text = m_archive.fileData(QStringLiteral("resources/Manifest")); - check_return_bool(!text.isEmpty(), m_archive.errorString()); - - m_localManifest = AssetManifest(text); - check_return_bool(!m_localManifest.isError(), m_localManifest.errorString()); - - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - return true; -} - -bool AssetsDownloadOperation::checkForDeviceManifest() -{ - auto *op = device()->storage()->stat(DEVICE_MANIFEST); - connect(op, &AbstractOperation::finished, this, [=]() { - if(op->isError()) { - finishWithError(op->errorString()); - } else { - if(op->type() != StatOperation::Type::RegularFile) { - setState(State::ReadingDeviceManifest); - } else { - m_isDeviceManifestPresent = true; - } - - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - } - - op->deleteLater(); - }); - - return true; -} - -bool AssetsDownloadOperation::readDeviceManifest() -{ - auto *buf = new QBuffer(this); - - if(!buf->open(QIODevice::ReadWrite)) { - buf->deleteLater(); - return false; - } - - auto *op = device()->storage()->read(QByteArrayLiteral("/ext/Manifest"), buf); - - connect(op, &AbstractOperation::finished, this, [=]() { - if(!op->isError()) { - m_deviceManifest = AssetManifest(buf->readAll()); - } - - op->deleteLater(); - buf->deleteLater(); - - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - }); - - return true; -} - -bool AssetsDownloadOperation::buildFileLists() -{ - FileNode::FileInfoList deleted, added, changed; - - FileNode::FileInfo manifestInfo; - manifestInfo.name = QStringLiteral("Manifest"); - manifestInfo.absolutePath = manifestInfo.name; - manifestInfo.type = FileNode::Type::RegularFile; - - print_file_list("<<<<< Local manifest:", m_localManifest.tree()->toPreOrderList()); - - if(!m_isDeviceManifestPresent) { - info_msg("Device manifest not present, assumimg fresh install..."); - added.append(manifestInfo); - added.append(m_localManifest.tree()->toPreOrderList().mid(1)); - - } else if(m_deviceManifest.isError()) { - info_msg("Failed to build device manifest, assuming complete asset overwrite..."); - changed.append(manifestInfo); - changed.append(m_localManifest.tree()->toPreOrderList().mid(1)); - - } else { - print_file_list(">>>>> Device manifest:", m_deviceManifest.tree()->toPreOrderList()); - - deleted.append(m_localManifest.tree()->difference(m_deviceManifest.tree())); - added.append(m_deviceManifest.tree()->difference(m_localManifest.tree())); - changed.append(m_deviceManifest.tree()->changed(m_localManifest.tree())); - - if(!deleted.isEmpty() || !added.isEmpty() || !changed.isEmpty()) { - changed.prepend(manifestInfo); - } - } - - if(!deleted.isEmpty()) { - print_file_list("----- Files deleted:", deleted); - } - - if(!added.isEmpty()) { - print_file_list("+++++ Files added:", added); - } - - if(!changed.isEmpty()) { - print_file_list("***** Files changed:", changed); - } - - m_delete.append(deleted); - m_delete.append(changed); - - m_write.append(added); - m_write.append(changed); - - // Start deleting by the farthest nodes - std::reverse(m_delete.begin(), m_delete.end()); - - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - return true; -} - -bool AssetsDownloadOperation::deleteFiles() -{ - if(m_delete.isEmpty()) { - info_msg("No files to delete, skipping to write"); - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - return true; - } - - device()->setMessage(tr("Deleting unneeded files...")); - - int numFiles = m_delete.size(); - - for(const auto &fileInfo : qAsConst(m_delete)) { - const auto isLastFile = (--numFiles == 0); - const auto fileName = QByteArrayLiteral("/ext/") + fileInfo.absolutePath.toLocal8Bit(); - - auto *op = device()->storage()->remove(fileName); - - connect(op, &AbstractOperation::finished, this, [=]() { - if(op->isError()) { - finishWithError(op->errorString()); - } else if(isLastFile) { - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - } - }); - } - - return true; -} - -bool AssetsDownloadOperation::writeFiles() -{ - if(m_write.isEmpty()) { - info_msg("No files to write, skipping to the end"); - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - return true; - } - - - device()->setMessage(tr("Writing new files...")); - - int i = m_write.size(); - - for(const auto &fileInfo : qAsConst(m_write)) { - --i; - - AbstractOperation *op; - const auto filePath = QByteArrayLiteral("/ext/") + fileInfo.absolutePath.toLocal8Bit(); - - if(fileInfo.type == FileNode::Type::Directory) { - op = device()->storage()->mkdir(filePath); - - } else if(fileInfo.type == FileNode::Type::RegularFile) { - auto *buf = new QBuffer(this); - if(!buf->open(QIODevice::ReadWrite)) { - buf->deleteLater(); - return false; - } - - const auto resourcePath = QStringLiteral("resources/") + fileInfo.absolutePath; - if((buf->write(m_archive.fileData(resourcePath)) <= 0) || (!buf->seek(0))) { - buf->deleteLater(); - return false; - } - - op = device()->storage()->write(filePath, buf); - - } else { - return false; - } - - connect(op, &AbstractOperation::finished, this, [=]() { - if(op->isError()) { - finishWithError(op->errorString()); - } else if(i == 0) { - QTimer::singleShot(0, this, &AssetsDownloadOperation::transitionToNextState); - } - - op->deleteLater(); - }); - } - - return true; -} diff --git a/backend/flipperzero/operations/assetsdownloadoperation.h b/backend/flipperzero/operations/assetsdownloadoperation.h deleted file mode 100644 index c2ac5269..00000000 --- a/backend/flipperzero/operations/assetsdownloadoperation.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -#include "tararchive.h" -#include "flipperzerooperation.h" -#include "flipperzero/assetmanifest.h" - -class QIODevice; - -namespace Flipper { -namespace Zero { - -class AssetsDownloadOperation : public Operation -{ - Q_OBJECT - -public: - enum State { - CheckingExtStorage = BasicState::User, - ExtractingArchive, - ReadingLocalManifest, - CheckingDeviceManifest, - ReadingDeviceManifest, - BuildingFileLists, - DeletingFiles, - WritingFiles - }; - - AssetsDownloadOperation(FlipperZero *device, QIODevice *file, QObject *parent = nullptr); - ~AssetsDownloadOperation(); - - const QString description() const override; - -private slots: - void transitionToNextState() override; - void onOperationTimeout() override; - -private: - bool checkForExtStorage(); - bool extractArchive(); - bool readLocalManifest(); - bool checkForDeviceManifest(); - bool readDeviceManifest(); - - bool buildFileLists(); - - bool deleteFiles(); - bool writeFiles(); - - QIODevice *m_compressed; - QIODevice *m_uncompressed; - - TarArchive m_archive; - - AssetManifest m_localManifest; - AssetManifest m_deviceManifest; - bool m_isDeviceManifestPresent; - - FileNode::FileInfoList m_delete; - FileNode::FileInfoList m_write; -}; - -} -} - diff --git a/backend/flipperzero/operations/firmwaredownloadoperation.cpp b/backend/flipperzero/operations/firmwaredownloadoperation.cpp deleted file mode 100644 index 05f6d5c9..00000000 --- a/backend/flipperzero/operations/firmwaredownloadoperation.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "firmwaredownloadoperation.h" - -#include -#include - -#include "flipperzero/flipperzero.h" -#include "flipperzero/recoverycontroller.h" - -using namespace Flipper; -using namespace Zero; - -FirmwareDownloadOperation::FirmwareDownloadOperation(FlipperZero *device, QIODevice *file, QObject *parent): - Operation(device, parent), - m_file(file) -{ - device->setMessage(QStringLiteral("Firmware download pending...")); -} - -FirmwareDownloadOperation::~FirmwareDownloadOperation() -{ - m_file->deleteLater(); -} - -const QString FirmwareDownloadOperation::description() const -{ - return QStringLiteral("Firmware Download @%1 %2").arg(device()->model(), device()->name()); -} - -void FirmwareDownloadOperation::transitionToNextState() -{ - if(!device()->isOnline()) { - startTimeout(); - return; - } - - stopTimeout(); - - if(state() == AbstractOperation::Ready) { - setState(State::BootingToDFU); - booToDFU(); - - } else if(state() == State::BootingToDFU) { - setState(State::DownloadingFirmware); - downloadFirmware(); - - } else if(state() == State::DownloadingFirmware) { - setState(State::BootingToFirmware); - bootToFirmware(); - - } else if(state() == State::BootingToFirmware) { - setState(AbstractOperation::Finished); - finish(); - - } else { - finishWithError(QStringLiteral("Unexpected state.")); - device()->setError(errorString()); - } -} - -void FirmwareDownloadOperation::onOperationTimeout() -{ - QString msg; - - switch(state()) { - case FirmwareDownloadOperation::BootingToDFU: - msg = QStringLiteral("Failed to reach DFU mode: Operation timeout."); - break; - case FirmwareDownloadOperation::BootingToFirmware: - msg = QStringLiteral("Failed to reboot the device: Operation timeout."); - break; - default: - msg = QStringLiteral("Should not have timed out here, probably a bug."); - } - - finishWithError(msg); - device()->setError(msg); -} - -void FirmwareDownloadOperation::booToDFU() -{ - if(device()->isDFU()) { - transitionToNextState(); - } else if(!device()->bootToDFU()) { - finishWithError(device()->errorString()); - } -} - -void FirmwareDownloadOperation::downloadFirmware() -{ - auto *watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcherBase::finished, this, [=]() { - if(watcher->result()) { - transitionToNextState(); - } else { - finishWithError(device()->errorString()); - } - - watcher->deleteLater(); - }); - - watcher->setFuture(QtConcurrent::run(device()->recovery(), &RecoveryController::downloadFirmware, m_file)); -} - -void FirmwareDownloadOperation::bootToFirmware() -{ - if(!device()->recovery()->leaveDFU()) { - finishWithError(device()->errorString()); - } -} diff --git a/backend/flipperzero/operations/firmwaredownloadoperation.h b/backend/flipperzero/operations/firmwaredownloadoperation.h deleted file mode 100644 index bc7daf0a..00000000 --- a/backend/flipperzero/operations/firmwaredownloadoperation.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "flipperzerooperation.h" - -class QIODevice; - -namespace Flipper { - -class FlipperZero; - -namespace Zero { - -class FirmwareDownloadOperation : public Operation -{ - Q_OBJECT - - enum State { - BootingToDFU = AbstractOperation::User, - DownloadingFirmware, - BootingToFirmware, - }; - -public: - FirmwareDownloadOperation(FlipperZero *device, QIODevice *file, QObject *parent = nullptr); - ~FirmwareDownloadOperation(); - - const QString description() const override; - -private slots: - void transitionToNextState() override; - void onOperationTimeout() override; - -private: - void booToDFU(); - void downloadFirmware(); - void bootToFirmware(); - - QIODevice *m_file; -}; - -} -} - diff --git a/backend/flipperzero/operations/fixbootissuesoperation.cpp b/backend/flipperzero/operations/fixbootissuesoperation.cpp deleted file mode 100644 index 25020231..00000000 --- a/backend/flipperzero/operations/fixbootissuesoperation.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "fixbootissuesoperation.h" - -#include "flipperzero/flipperzero.h" -#include "flipperzero/recoverycontroller.h" - -using namespace Flipper; -using namespace Zero; - -FixBootIssuesOperation::FixBootIssuesOperation(FlipperZero *device, QObject *parent): - Operation(device, parent) -{ - device->setPersistent(true); - device->setMessage(QStringLiteral("Fix boot issues operation pending...")); -} - -FixBootIssuesOperation::~FixBootIssuesOperation() -{ - device()->setPersistent(false); -} - -const QString FixBootIssuesOperation::description() const -{ - return QStringLiteral("Fix boot issues @%1 %2").arg(device()->model(), device()->name()); -} - -void FixBootIssuesOperation::transitionToNextState() -{ - if(!device()->isOnline()) { - startTimeout(); - return; - } - - stopTimeout(); - - if(state() == AbstractOperation::Ready) { - setState(FixBootIssuesOperation::StartingWirelessStack); - startWirelessStack(); - - } else if(state() == FixBootIssuesOperation::StartingWirelessStack) { - setState(FixBootIssuesOperation::FixingBootMode); - fixBootMode(); - - } else if(state() == FixBootIssuesOperation::FixingBootMode) { - setState(AbstractOperation::Finished); - finish(); - - } else { - finishWithError(QStringLiteral("Unexpected state.")); - device()->setError(errorString()); - } -} - -void FixBootIssuesOperation::onOperationTimeout() -{ - QString msg; - - if(state() == FixBootIssuesOperation::StartingWirelessStack) { - msg = QStringLiteral("Failed to start the Wireless Stack: Operation timeout."); - } else if(state() == FixBootIssuesOperation::FixingBootMode) { - msg = QStringLiteral("Failed to set the Option Bytes: Operation timeout."); - } else { - msg = QStringLiteral("Should not have timed out here, probably a bug."); - } - - finishWithError(msg); - device()->setError(errorString()); -} - -void FixBootIssuesOperation::startWirelessStack() -{ - const auto wirelessStatus = device()->recovery()->wirelessStatus(); - - if(wirelessStatus == RecoveryController::WirelessStatus::FUSRunning) { - if(!device()->recovery()->startWirelessStack()) { - finishWithError(device()->errorString()); - } else if(device()->recovery()->wirelessStatus() == RecoveryController::WirelessStatus::UnhandledState) { - transitionToNextState(); - } else {} - - } else if(wirelessStatus == RecoveryController::WirelessStatus::WSRunning) { - transitionToNextState(); - } else if(wirelessStatus == RecoveryController::WirelessStatus::UnhandledState) { - finishWithError(QStringLiteral("Unhandled state. Probably a BUG.")); - device()->setError(errorString()); - } else { - finishWithError(QStringLiteral("Failed to get Wireless core status.")); - device()->setError(errorString()); - } -} - -void FixBootIssuesOperation::fixBootMode() -{ - if(!device()->isDFU()) { - transitionToNextState(); - } else if (!device()->recovery()->setBootMode(RecoveryController::BootMode::Normal)) { - finishWithError(device()->errorString()); - } else {} -} diff --git a/backend/flipperzero/operations/fixoptionbytesoperation.cpp b/backend/flipperzero/operations/fixoptionbytesoperation.cpp deleted file mode 100644 index 2026a4f9..00000000 --- a/backend/flipperzero/operations/fixoptionbytesoperation.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "fixoptionbytesoperation.h" - -#include - -#include "flipperzero/flipperzero.h" -#include "flipperzero/recoverycontroller.h" - -using namespace Flipper; -using namespace Zero; - -FixOptionBytesOperation::FixOptionBytesOperation(FlipperZero *device, QIODevice *file, QObject *parent): - Operation(device, parent), - m_file(file) -{ - device->setMessage(QStringLiteral("Fix Option Bytes operation pending...")); -} - -FixOptionBytesOperation::~FixOptionBytesOperation() -{ - m_file->deleteLater(); -} - -const QString FixOptionBytesOperation::description() const -{ - return QStringLiteral("Fix Option Bytes @%1 %2").arg(device()->model(), device()->name()); -} - -void FixOptionBytesOperation::transitionToNextState() -{ - if(!device()->isOnline()) { - startTimeout(); - return; - } - - stopTimeout(); - - if(state() == AbstractOperation::Ready) { - setState(State::BootingToDFU); - bootToDFU(); - - } else if(state() == State::BootingToDFU) { - setState(FixOptionBytesOperation::FixingOptionBytes); - fixOptionBytes(); - - } else if(state() == State::FixingOptionBytes) { - setState(AbstractOperation::Finished); - finish(); - - } else { - finishWithError(QStringLiteral("Unexpected state.")); - device()->setError(errorString()); - } -} - -void FixOptionBytesOperation::onOperationTimeout() -{ - QString msg; - - if(state() == FixOptionBytesOperation::BootingToDFU) { - msg = QStringLiteral("Failed to reach DFU mode: Operation timeout."); - } else if(state() == FixOptionBytesOperation::FixingOptionBytes) { - msg = QStringLiteral("Failed to write the corrected Option Bytes: Operation timeout."); - } else { - msg = QStringLiteral("Should not have timed out here, probably a bug."); - } - - finishWithError(msg); - device()->setError(errorString()); -} - -void FixOptionBytesOperation::bootToDFU() -{ - if(device()->isDFU()) { - transitionToNextState(); - } else if(!device()->bootToDFU()) { - finishWithError(device()->errorString()); - } else {} -} - -void FixOptionBytesOperation::fixOptionBytes() -{ - if(!device()->recovery()->downloadOptionBytes(m_file)) { - finishWithError(device()->errorString()); - } -} diff --git a/backend/flipperzero/operations/fixoptionbytesoperation.h b/backend/flipperzero/operations/fixoptionbytesoperation.h deleted file mode 100644 index 6094853b..00000000 --- a/backend/flipperzero/operations/fixoptionbytesoperation.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "flipperzerooperation.h" - -class QIODevice; - -namespace Flipper { - -class FlipperZero; - -namespace Zero { - -class FixOptionBytesOperation : public Operation -{ - Q_OBJECT - - enum State { - BootingToDFU = AbstractOperation::User, - FixingOptionBytes, - }; - -public: - FixOptionBytesOperation(FlipperZero *device, QIODevice *file, QObject *parent = nullptr); - ~FixOptionBytesOperation(); - - const QString description() const override; - -private slots: - void transitionToNextState() override; - void onOperationTimeout() override; - -private: - void bootToDFU(); - void fixOptionBytes(); - - QIODevice *m_file; -}; - -} -} diff --git a/backend/flipperzero/operations/flipperzerooperation.cpp b/backend/flipperzero/operations/flipperzerooperation.cpp deleted file mode 100644 index 46d67a7d..00000000 --- a/backend/flipperzero/operations/flipperzerooperation.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "flipperzerooperation.h" - -#include - -#include "flipperzero/flipperzero.h" - -using namespace Flipper; -using namespace Zero; - -Operation::Operation(FlipperZero *device, QObject *parent): - AbstractOperation(parent), - m_device(device) -{ - // TODO: set device persistent elsewhere - m_device->setPersistent(true); -} - -Operation::~Operation() -{ - // TODO: set device persistent elsewhere - m_device->setPersistent(false); -} - -FlipperZero *Operation::device() const -{ - return m_device; -} - -void Operation::start() -{ - if(state() != Ready) { - finishWithError(QStringLiteral("Trying to start an operation that is either already running or has finished.")); - return; - } - - connect(m_device, &FlipperZero::isOnlineChanged, this, &Operation::transitionToNextState); - transitionToNextState(); -} - -void Operation::finish() -{ - disconnect(m_device, &FlipperZero::isOnlineChanged, this, &Operation::transitionToNextState); - emit finished(); -} diff --git a/backend/flipperzero/operations/flipperzerooperation.h b/backend/flipperzero/operations/flipperzerooperation.h deleted file mode 100644 index 6609dc43..00000000 --- a/backend/flipperzero/operations/flipperzerooperation.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "abstractoperation.h" - -namespace Flipper { - -class FlipperZero; - -namespace Zero { - -class Operation : public AbstractOperation -{ - Q_OBJECT - -public: - Operation(FlipperZero *device, QObject *parent = nullptr); - virtual ~Operation(); - - FlipperZero *device() const; - - void start() override; - void finish() override; - -private slots: - virtual void transitionToNextState() = 0; - -private: - FlipperZero *m_device; -}; - -} -} diff --git a/backend/flipperzero/operations/wirelessstackdownloadoperation.cpp b/backend/flipperzero/operations/wirelessstackdownloadoperation.cpp deleted file mode 100644 index 697eb906..00000000 --- a/backend/flipperzero/operations/wirelessstackdownloadoperation.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include "wirelessstackdownloadoperation.h" - -#include -#include -#include -#include - -#include "flipperzero/flipperzero.h" -#include "flipperzero/recoverycontroller.h" - -using namespace Flipper; -using namespace Zero; - -WirelessStackDownloadOperation::WirelessStackDownloadOperation(FlipperZero *device, QIODevice *file, uint32_t targetAddress, QObject *parent): - Operation(device, parent), - m_file(file), - m_loopTimer(new QTimer(this)), - m_targetAddress(targetAddress) -{ - device->setMessage(QStringLiteral("Co-Processor firmware update pending...")); - - connect(m_loopTimer, &QTimer::timeout, this, &WirelessStackDownloadOperation::transitionToNextState); -} - -WirelessStackDownloadOperation::~WirelessStackDownloadOperation() -{ - m_file->deleteLater(); -} - -const QString WirelessStackDownloadOperation::description() const -{ - return QStringLiteral("Co-Processor Firmware Download @%1 %2").arg(device()->model(), device()->name()); -} - -void WirelessStackDownloadOperation::transitionToNextState() -{ - if(!device()->isOnline()) { - startTimeout(); - return; - } - - stopTimeout(); - - if(state() == AbstractOperation::Ready) { - setState(WirelessStackDownloadOperation::BootingToDFU); - bootToDFU(); - - } else if(state() == WirelessStackDownloadOperation::BootingToDFU) { - setState(WirelessStackDownloadOperation::SettingDFUBoot); - setDFUBoot(true); - - } else if(state() == WirelessStackDownloadOperation::SettingDFUBoot) { - setState(WirelessStackDownloadOperation::StartingFUS); - startFUS(); - - } else if(state() == WirelessStackDownloadOperation::StartingFUS) { - setState(WirelessStackDownloadOperation::DeletingWirelessStack); - deleteWirelessStack(); - - } else if(state() == WirelessStackDownloadOperation::DeletingWirelessStack) { - if(isWirelessStackDeleted()) { - setState(WirelessStackDownloadOperation::DownloadingWirelessStack); - downloadWirelessStack(); - } - - } else if(state() == WirelessStackDownloadOperation::DownloadingWirelessStack) { - setState(WirelessStackDownloadOperation::UpgradingWirelessStack); - upgradeWirelessStack(); - - } else if(state() == WirelessStackDownloadOperation::UpgradingWirelessStack) { - if(isWirelessStackUpgraded()) { - setState(WirelessStackDownloadOperation::ResettingDFUBoot); - setDFUBoot(false); - } - - } else if(state() == WirelessStackDownloadOperation::ResettingDFUBoot) { - setState(AbstractOperation::Finished); - finish(); - - } else { - finishWithError(QStringLiteral("Unexpected state.")); - device()->setError(errorString()); - } -} - -void WirelessStackDownloadOperation::onOperationTimeout() -{ - QString msg; - - if(state() == WirelessStackDownloadOperation::BootingToDFU) { - msg = QStringLiteral("Failed to enter DFU mode: Operation timeout."); - } else if(state() == WirelessStackDownloadOperation::SettingDFUBoot) { - msg = QStringLiteral("Failed to set DFU only boot mode: Operation timeout."); - } else if(state() == WirelessStackDownloadOperation::StartingFUS) { - msg = QStringLiteral("Failed to start Firmware Upgrade Service: Operation timeout."); - } else if(state() == WirelessStackDownloadOperation::DeletingWirelessStack) { - msg = QStringLiteral("Failed to delete existing Wireless Stack: Operation timeout."); - } else if(state() == WirelessStackDownloadOperation::UpgradingWirelessStack) { - msg = QStringLiteral("Failed to upgrade Wireless Stack: Operation timeout."); - } else if(state() == WirelessStackDownloadOperation::ResettingDFUBoot) { - msg = QStringLiteral("Failed to reset DFU only boot mode: Operation timeout."); - } else { - msg = QStringLiteral("Should not have timed out here, probably a bug."); - } - - finishWithError(msg); - device()->setError(errorString()); -} - -void WirelessStackDownloadOperation::bootToDFU() -{ - if(device()->isDFU()) { - transitionToNextState(); - } else if(!device()->bootToDFU()) { - finishWithError(device()->errorString()); - } else {} -} - -void WirelessStackDownloadOperation::setDFUBoot(bool set) -{ - const auto bootMode = set ? RecoveryController::BootMode::DFUOnly : RecoveryController::BootMode::Normal; - - if(!device()->recovery()->setBootMode(bootMode)) { - finishWithError(device()->errorString()); - } -} - -void WirelessStackDownloadOperation::startFUS() -{ - if(!device()->recovery()->startFUS()) { - finishWithError(device()->errorString()); - } -} - -void WirelessStackDownloadOperation::deleteWirelessStack() -{ - if(!device()->recovery()->deleteWirelessStack()) { - finishWithError(device()->errorString()); - } else { - m_loopTimer->start(1000); - } -} - -bool WirelessStackDownloadOperation::isWirelessStackDeleted() -{ - const auto status = device()->recovery()->wirelessStatus(); - - const auto waitNext = (status == RecoveryController::WirelessStatus::Invalid) || - (status == RecoveryController::WirelessStatus::UnhandledState); - if(waitNext) { - return false; - } - - m_loopTimer->stop(); - - const auto errorOccured = (status == RecoveryController::WirelessStatus::WSRunning) || - (status == RecoveryController::WirelessStatus::ErrorOccured); - if(errorOccured) { - finishWithError(QStringLiteral("Failed to finish removal of the Wireless Stack.")); - device()->setError(errorString()); - } - - return !errorOccured; -} - -void WirelessStackDownloadOperation::downloadWirelessStack() -{ - auto *watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcherBase::finished, this, [=]() { - if(watcher->result()) { - transitionToNextState(); - } else { - finishWithError(QStringLiteral("Failed to download the Wireless Stack.")); - device()->setError(errorString()); - } - - watcher->deleteLater(); - }); - - watcher->setFuture(QtConcurrent::run(device()->recovery(), &RecoveryController::downloadWirelessStack, m_file, m_targetAddress)); -} - -void WirelessStackDownloadOperation::upgradeWirelessStack() -{ - if(!device()->recovery()->upgradeWirelessStack()) { - finishWithError(device()->errorString()); - } else { - m_loopTimer->start(1000); - } -} - -bool WirelessStackDownloadOperation::isWirelessStackUpgraded() -{ - const auto status = device()->recovery()->wirelessStatus(); - - const auto waitNext = (status == RecoveryController::WirelessStatus::Invalid) || - (status == RecoveryController::WirelessStatus::UnhandledState); - if(waitNext) { - return false; - } - - m_loopTimer->stop(); - - const auto errorOccured = (status == RecoveryController::WirelessStatus::ErrorOccured); - if(errorOccured) { - finishWithError(QStringLiteral("Failed to finish installation of the Wireless Stack.")); - device()->setError(errorString()); - } - - return !errorOccured; -} diff --git a/backend/flipperzero/radiomanifest.cpp b/backend/flipperzero/radiomanifest.cpp new file mode 100644 index 00000000..da23817f --- /dev/null +++ b/backend/flipperzero/radiomanifest.cpp @@ -0,0 +1,270 @@ +#include "radiomanifest.h" + +#include + +#include +#include +#include + +using namespace Flipper; +using namespace Zero; + +RadioManifest::Header::Header(): + m_version(-1), + m_timestamp(-1) +{} + +RadioManifest::Header::Header(const QJsonValue &json) +{ + if(!json.isObject()) { + throw std::runtime_error("Expected Manifest header to be an object"); + } + + const auto obj = json.toObject(); + if(obj.isEmpty()) { + throw std::runtime_error("Manifest header is empty"); + } + + if(obj.contains(QStringLiteral("version"))) { + m_version = obj.value(QStringLiteral("version")).toInt(); + } else { + throw std::runtime_error("Failed to read manifest version"); + } + + if(obj.contains(QStringLiteral("timestamp"))) { + m_timestamp = (time_t)obj.value(QStringLiteral("timestamp")).toInt(); + } else { + throw std::runtime_error("Failed to read manifest timestamp"); + } +} + +int RadioManifest::Header::version() const +{ + return m_version; +} + +time_t RadioManifest::Header::timestamp() const +{ + return m_timestamp; +} + +RadioManifest::Condition::Condition(): + m_type(Type::Unknown) +{} + +RadioManifest::Condition::Condition(const QString &text) +{ + if(text.startsWith("==")) { + m_type = Type::Equals; + m_version = text.mid(2); + + } else if(text.startsWith('>')) { + m_type = Type::Greater; + m_version = text.mid(1); + + } else { + throw std::runtime_error("Malformed Condition"); + } +} + +RadioManifest::Condition::Type RadioManifest::Condition::type() const +{ + return m_type; +} + +const QString &RadioManifest::Condition::version() const +{ + return m_version; +} + +RadioManifest::FileInfo::FileInfo(const QJsonValue &json) +{ + if(!json.isObject()) { + throw std::runtime_error("Expected FileInfo to be an object"); + } + + const auto obj = json.toObject(); + if(obj.isEmpty()) { + throw std::runtime_error("FileInfo is empty"); + } + + const auto canConstruct = obj.contains(QStringLiteral("name")) && + obj.contains(QStringLiteral("sha256")) && + obj.contains(QStringLiteral("address")); + if(!canConstruct) { + throw std::runtime_error("Malformed FileInfo"); + } + + m_name = obj.value(QStringLiteral("name")).toString(); + m_sha256 = obj.value(QStringLiteral("sha256")).toVariant().toByteArray(); + m_address = obj.value(QStringLiteral("name")).toVariant().toULongLong(); + + if(obj.contains(QStringLiteral("condition"))) { + m_condition = Condition(obj.value(QStringLiteral("condition")).toString()); + } +} + +const QString &RadioManifest::FileInfo::name() const +{ + return m_name; +} + +const QByteArray &RadioManifest::FileInfo::sha256() const +{ + return m_sha256; +} + +const RadioManifest::Condition &RadioManifest::FileInfo::condition() const +{ + return m_condition; +} + +uint32_t RadioManifest::FileInfo::address() const +{ + return m_address; +} + +RadioManifest::Section::Section(const QJsonValue &json) +{ + if(!json.isObject()) { + throw std::runtime_error("Expected Section to be an object"); + } + + const auto obj = json.toObject(); + if(obj.isEmpty()) { + throw std::runtime_error("Section is empty"); + } + + const auto canConstruct = obj.contains(QStringLiteral("version")) && + obj.contains(QStringLiteral("files")); + if(!canConstruct) { + throw std::runtime_error("Malformed Section"); + } + + readVersion(obj.value(QStringLiteral("version"))); + readFiles(obj.value(QStringLiteral("files"))); +} + +const QString &RadioManifest::Section::version() const +{ + return m_version; +} + +const RadioManifest::FileInfoMap &RadioManifest::Section::files() const +{ + return m_files; +} + +void RadioManifest::Section::readVersion(const QJsonValue &json) +{ + if(!json.isObject()) { + throw std::runtime_error("Expected Section version to be an object"); + } + + const auto obj = json.toObject(); + if(obj.isEmpty()) { + throw std::runtime_error("Section version is empty"); + } + + const auto canConstruct = obj.contains(QStringLiteral("major")) && + obj.contains(QStringLiteral("minor")) && + obj.contains(QStringLiteral("sub")); + if(!canConstruct) { + throw std::runtime_error("Malformed section version"); + } + + const auto major = obj.value(QStringLiteral("major")).toInt(); + const auto minor = obj.value(QStringLiteral("minor")).toInt(); + const auto sub = obj.value(QStringLiteral("sub")).toInt(); + + m_version = QStringLiteral("%1.%2.%3").arg(major).arg(minor).arg(sub); +} + +void RadioManifest::Section::readFiles(const QJsonValue &json) +{ + if(!json.isArray()) { + throw std::runtime_error("Expected File list to be an array"); + } + + const auto arr = json.toArray(); + if(arr.isEmpty()) { + throw std::runtime_error("File list is empty"); + } + + for(const auto &value : arr) { + FileInfo fileInfo(value); + m_files.insert(fileInfo.name(), fileInfo); + } +} + +RadioManifest::FirmwareInfo::FirmwareInfo(const QJsonValue &json) +{ + if(!json.isObject()) { + throw std::runtime_error("Expected FirmwareInfo to be an object"); + } + + const auto obj = json.toObject(); + if(obj.isEmpty()) { + throw std::runtime_error("FirmwareInfo is empty"); + } + + const bool canConstruct = obj.contains(QStringLiteral("fus")) && + obj.contains(QStringLiteral("radio")); + if(!canConstruct) { + throw std::runtime_error("Malformed FirmwareInfo"); + } + + m_fus = Section(obj.value(QStringLiteral("fus"))); + m_radio = Section(obj.value(QStringLiteral("radio"))); +} + +const RadioManifest::Section &RadioManifest::FirmwareInfo::fus() const +{ + return m_fus; +} + +const RadioManifest::Section &RadioManifest::FirmwareInfo::radio() const +{ + return m_radio; +} + +RadioManifest::RadioManifest(const QByteArray &text) +{ + if(text.isEmpty()) { + setError("JSON text is empty"); + return; + } + + const auto doc = QJsonDocument::fromJson(text); + + if(!doc.isObject()) { + setError("Expected RadioManifest to be a JSON object"); + return; + } + + const auto obj = doc.object(); + const auto canConstruct = obj.contains(QStringLiteral("manifest")) && + obj.contains(QStringLiteral("copro")); + if(!canConstruct) { + setError("Malformed RadioManifest"); + return; + } + + try { + m_header = Header(obj.value(QStringLiteral("manifest"))); + m_firmware = FirmwareInfo(obj.value(QStringLiteral("copro"))); + + } catch(const std::runtime_error &e) { + setError(e.what()); + } +} + +const RadioManifest::Header &RadioManifest::header() const +{ + return m_header; +} + +const RadioManifest::FirmwareInfo &RadioManifest::firmware() const +{ + return m_firmware; +} diff --git a/backend/flipperzero/radiomanifest.h b/backend/flipperzero/radiomanifest.h new file mode 100644 index 00000000..198bdedd --- /dev/null +++ b/backend/flipperzero/radiomanifest.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include + +#include "failable.h" + +namespace Flipper { +namespace Zero { + +class RadioManifest : public Failable +{ +public: + class Header { + public: + Header(); + Header(const QJsonValue &json); + + int version() const; + time_t timestamp() const; + + private: + int m_version; + time_t m_timestamp; + }; + + class Condition { + public: + enum class Type { + Unknown, + Equals, + Greater + }; + + Condition(); + Condition(const QString &text); + + Type type() const; + const QString &version() const; + + private: + Type m_type; + QString m_version; + }; + + class FileInfo { + public: + FileInfo() = default; + FileInfo(const QJsonValue &json); + + const QString &name() const; + const QByteArray &sha256() const; + const Condition &condition() const; + uint32_t address() const; + + private: + QString m_name; + QByteArray m_sha256; + Condition m_condition; + uint32_t m_address; + }; + + using FileInfoMap = QMap; + + class Section { + public: + Section() = default; + Section(const QJsonValue &json); + + const QString &version() const; + const FileInfoMap &files() const; + + private: + void readVersion(const QJsonValue &json); + void readFiles(const QJsonValue &json); + + QString m_version; + FileInfoMap m_files; + }; + + class FirmwareInfo { + public: + FirmwareInfo() = default; + FirmwareInfo(const QJsonValue &json); + + const Section &fus() const; + const Section &radio() const; + + private: + Section m_fus; + Section m_radio; + }; + + RadioManifest() = default; + RadioManifest(const QByteArray &text); + + const Header &header() const; + const FirmwareInfo &firmware() const; + +private: + Header m_header; + FirmwareInfo m_firmware; +}; + +} +} + diff --git a/backend/flipperzero/recoverycontroller.cpp b/backend/flipperzero/recovery.cpp similarity index 76% rename from backend/flipperzero/recoverycontroller.cpp rename to backend/flipperzero/recovery.cpp index ee1edacc..2c0dc824 100644 --- a/backend/flipperzero/recoverycontroller.cpp +++ b/backend/flipperzero/recovery.cpp @@ -1,5 +1,6 @@ -#include "recoverycontroller.h" +#include "recovery.h" +#include "devicestate.h" #include "dfusefile.h" #include "macros.h" @@ -18,19 +19,24 @@ using namespace WB55; #define to_hex_str(num) (QString::number(num, 16)) -RecoveryController::RecoveryController(USBDeviceInfo info, QObject *parent): - SignalingFailable(parent), - m_usbInfo(info) +Recovery::Recovery(DeviceState *deviceState, QObject *parent): + QObject(parent), + m_deviceState(deviceState) {} -RecoveryController::~RecoveryController() +Recovery::~Recovery() {} -bool RecoveryController::leaveDFU() +DeviceState *Recovery::deviceState() const { - setMessage("Booting the device up..."); + return m_deviceState; +} + +bool Recovery::exitRecoveryMode() +{ + m_deviceState->setStatusString(QStringLiteral("Exiting recovery mode...")); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); const auto success = device.beginTransaction() && device.leave(); begin_ignore_block(); @@ -38,18 +44,21 @@ bool RecoveryController::leaveDFU() end_ignore_block(); if(!success) { - setError("Failed to leave DFU mode."); + setError("Failed to exit recovery mode"); } return success; } -bool RecoveryController::setBootMode(BootMode mode) +bool Recovery::setBootMode(BootMode mode) { - const auto msg = (mode == BootMode::Normal) ? "Booting the device up..." : "Setting device to DFU boot mode..."; - setMessage(msg); + const auto msg = (mode == BootMode::Normal) ? + QStringLiteral("Setting OS boot mode...") : + QStringLiteral("Setting Recovery boot mode..."); - STM32WB55 device(m_usbInfo); + m_deviceState->setStatusString(msg); + + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); if(!device.beginTransaction()) { setError("Can't set boot mode: Failed to initiate transaction."); @@ -69,7 +78,7 @@ bool RecoveryController::setBootMode(BootMode mode) const auto success = device.setOptionBytes(ob); if(!success) { - setError("Cant' set boot mode: Failed to set option bytes"); + setError("Can't set boot mode: Failed to set option bytes"); } begin_ignore_block(); @@ -79,11 +88,16 @@ bool RecoveryController::setBootMode(BootMode mode) return success; } -RecoveryController::WirelessStatus RecoveryController::wirelessStatus() +Recovery::WirelessStatus Recovery::wirelessStatus() { info_msg("Getting Co-Processor (Wireless) status..."); - STM32WB55 device(m_usbInfo); + if(!m_deviceState->isOnline()) { + info_msg("Failed to get FUS status. The device is offline at the moment."); + return WirelessStatus::Invalid; + } + + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); if(!device.beginTransaction()) { info_msg("Failed to get FUS status. This is normal if the device has just rebooted."); @@ -119,11 +133,11 @@ RecoveryController::WirelessStatus RecoveryController::wirelessStatus() } } -bool RecoveryController::startFUS() +bool Recovery::startFUS() { - setMessage("Starting firmware upgrade service (FUS)..."); + m_deviceState->setStatusString("Starting firmware upgrade service (FUS)..."); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); if(!device.beginTransaction()) { setError("Can't start FUS: Failed to initiate transaction."); @@ -162,11 +176,11 @@ bool RecoveryController::startFUS() } // TODO: check status to see if the wireless stack is present at all -bool RecoveryController::startWirelessStack() +bool Recovery::startWirelessStack() { - setMessage("Attempting to start the Wireless Stack..."); + m_deviceState->setStatusString("Attempting to start the Wireless Stack..."); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); auto success = device.beginTransaction() && device.FUSStartWirelessStack(); check_continue(device.endTransaction(), "^^^ It's probably nothing at this point... ^^^"); @@ -178,11 +192,11 @@ bool RecoveryController::startWirelessStack() return success; } -bool RecoveryController::deleteWirelessStack() +bool Recovery::deleteWirelessStack() { - setMessage("Deleting old co-processor firmware..."); + m_deviceState->setStatusString("Deleting old co-processor firmware..."); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); const auto success = device.beginTransaction() && device.FUSFwDelete() && device.endTransaction(); @@ -193,7 +207,7 @@ bool RecoveryController::deleteWirelessStack() return success; } -bool RecoveryController::downloadFirmware(QIODevice *file) +bool Recovery::downloadFirmware(QIODevice *file) { if(!file->open(QIODevice::ReadOnly)) { setError("Can't download firmware: Failed to open the file."); @@ -204,16 +218,16 @@ bool RecoveryController::downloadFirmware(QIODevice *file) return false; } else { - setMessage("Downloading the firmware, please wait..."); + m_deviceState->setStatusString("Downloading firmware, please wait..."); } DfuseFile fw(file); - DfuseDevice dev(m_usbInfo); + DfuseDevice dev(m_deviceState->deviceInfo().usbInfo); file->close(); connect(&dev, &DfuseDevice::progressChanged, this, [=](int operation, double progress) { - setProgress(progress / 2.0 + (operation == DfuseDevice::Download ? 50 : 0)); + m_deviceState->setProgress(progress / 2.0 + (operation == DfuseDevice::Download ? 50 : 0)); }); const auto success = dev.beginTransaction() && dev.download(&fw) && dev.endTransaction(); @@ -225,7 +239,7 @@ bool RecoveryController::downloadFirmware(QIODevice *file) return success; } -bool RecoveryController::downloadWirelessStack(QIODevice *file, uint32_t addr) +bool Recovery::downloadWirelessStack(QIODevice *file, uint32_t addr) { info_msg("Attempting to download CO-PROCESSOR firmware image..."); @@ -238,10 +252,10 @@ bool RecoveryController::downloadWirelessStack(QIODevice *file, uint32_t addr) return false; } else { - setMessage("Downloading co-processor firmware image..."); + m_deviceState->setStatusString("Downloading co-processor firmware image..."); } - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); if(!device.beginTransaction()) { setError("Can't download co-processor firmware image: Failed to initiate transaction."); @@ -269,7 +283,7 @@ bool RecoveryController::downloadWirelessStack(QIODevice *file, uint32_t addr) } connect(&device, &DfuseDevice::progressChanged, this, [=](int operation, double progress) { - setProgress(progress / 2.0 + (operation == DfuseDevice::Download ? 50 : 0)); + m_deviceState->setProgress(progress / 2.0 + (operation == DfuseDevice::Download ? 50 : 0)); }); bool success; @@ -287,21 +301,11 @@ bool RecoveryController::downloadWirelessStack(QIODevice *file, uint32_t addr) return success; } -void RecoveryController::setProgress(double progress) -{ - if(qFuzzyCompare(progress, m_progress)) { - return; - } - - m_progress = progress; - emit progressChanged(); -} - -bool RecoveryController::upgradeWirelessStack() +bool Recovery::upgradeWirelessStack() { info_msg("Sending FW_UPGRADE command..."); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); const auto success = device.beginTransaction() && device.FUSFwUpgrade(); check_continue(device.endTransaction(), "^^^ It's probably nothing at this point... ^^^"); @@ -309,15 +313,15 @@ bool RecoveryController::upgradeWirelessStack() if(!success) { setError("Can't upgrade Co-Processor firmware: Failed to initiate installation."); } else { - setMessage("Upgrading Co-Processor firmware, please wait..."); + m_deviceState->setStatusString("Upgrading Co-Processor firmware, please wait..."); } return success; } -bool RecoveryController::downloadOptionBytes(QIODevice *file) +bool Recovery::downloadOptionBytes(QIODevice *file) { - setMessage("Downloading Option Bytes..."); + m_deviceState->setStatusString("Downloading Option Bytes..."); check_return_bool(file->open(QIODevice::ReadOnly), "Failed to open file for reading"); const OptionBytes loaded(file); @@ -325,7 +329,7 @@ bool RecoveryController::downloadOptionBytes(QIODevice *file) check_return_bool(loaded.isValid(), "Failed to load option bytes from file"); - STM32WB55 device(m_usbInfo); + STM32WB55 device(m_deviceState->deviceInfo().usbInfo); check_return_bool(device.beginTransaction(), "Failed to initiate transaction"); const OptionBytes actual = device.optionBytes(); @@ -340,7 +344,7 @@ bool RecoveryController::downloadOptionBytes(QIODevice *file) success = device.leave(); if(!success) { - setError("Can't set boot mode: Failed to leave the DFU mode."); + setError("Can't set boot mode: Failed to leave the Recovery mode."); } } else { @@ -363,25 +367,8 @@ bool RecoveryController::downloadOptionBytes(QIODevice *file) end_ignore_block(); if(success) { - setMessage("Booting up the device..."); + m_deviceState->setStatusString(QStringLiteral("Exiting recovery mode...")); } return success; } - -const QString &RecoveryController::message() const -{ - return m_message; -} - -double RecoveryController::progress() const -{ - return m_progress; -} - -void RecoveryController::setMessage(const QString &msg) -{ - m_message = msg; - - emit messageChanged(); -} diff --git a/backend/flipperzero/recoverycontroller.h b/backend/flipperzero/recovery.h similarity index 59% rename from backend/flipperzero/recoverycontroller.h rename to backend/flipperzero/recovery.h index 956101f3..80c1287b 100644 --- a/backend/flipperzero/recoverycontroller.h +++ b/backend/flipperzero/recovery.h @@ -2,15 +2,17 @@ #include +#include "failable.h" #include "usbdeviceinfo.h" -#include "signalingfailable.h" class QIODevice; namespace Flipper { namespace Zero { -class RecoveryController : public SignalingFailable +class DeviceState; + +class Recovery : public QObject, public Failable { Q_OBJECT @@ -28,15 +30,14 @@ class RecoveryController : public SignalingFailable Invalid }; - RecoveryController(USBDeviceInfo info, QObject *parent = nullptr); - ~RecoveryController(); + Recovery(DeviceState *deviceState, QObject *parent = nullptr); + ~Recovery(); - const QString &message() const; + DeviceState *deviceState() const; WirelessStatus wirelessStatus(); - double progress() const; - bool leaveDFU(); + bool exitRecoveryMode(); bool setBootMode(BootMode mode); bool startFUS(); @@ -48,18 +49,8 @@ class RecoveryController : public SignalingFailable bool downloadOptionBytes(QIODevice *file); bool downloadWirelessStack(QIODevice *file, uint32_t addr = 0); -signals: - void messageChanged(); - void progressChanged(); - private: - void setProgress(double progress); - void setMessage(const QString &msg); - - USBDeviceInfo m_usbInfo; - - QString m_message; - double m_progress; + DeviceState *m_deviceState; }; } diff --git a/backend/flipperzero/recovery/abstractrecoveryoperation.cpp b/backend/flipperzero/recovery/abstractrecoveryoperation.cpp new file mode 100644 index 00000000..9d75e760 --- /dev/null +++ b/backend/flipperzero/recovery/abstractrecoveryoperation.cpp @@ -0,0 +1,52 @@ +#include "abstractrecoveryoperation.h" + +#include + +#include "flipperzero/recovery.h" +#include "flipperzero/devicestate.h" + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +AbstractRecoveryOperation::AbstractRecoveryOperation(Recovery *recovery, QObject *parent): + AbstractOperation(parent), + m_recovery(recovery) +{} + +void AbstractRecoveryOperation::start() +{ + if(operationState() != AbstractOperation::Ready) { + finishWithError(QStringLiteral("Trying to start an operation that is either already running or has finished.")); + } else { + connect(m_recovery->deviceState(), &DeviceState::isOnlineChanged, this, &AbstractRecoveryOperation::onDeviceOnlineChanged); + CALL_LATER(this, &AbstractRecoveryOperation::advanceOperationState); + } +} + +void AbstractRecoveryOperation::finish() +{ + disconnect(m_recovery->deviceState(), &DeviceState::isOnlineChanged, this, &AbstractRecoveryOperation::onDeviceOnlineChanged); + AbstractOperation::finish(); +} + +void AbstractRecoveryOperation::onDeviceOnlineChanged() +{ + if(m_recovery->deviceState()->isOnline()) { + stopTimeout(); + CALL_LATER(this, &AbstractRecoveryOperation::advanceOperationState); + } else { + startTimeout(); + } +} + +Recovery *AbstractRecoveryOperation::recovery() const +{ + return m_recovery; +} + +DeviceState *AbstractRecoveryOperation::deviceState() const +{ + return m_recovery->deviceState(); +} diff --git a/backend/flipperzero/recovery/abstractrecoveryoperation.h b/backend/flipperzero/recovery/abstractrecoveryoperation.h new file mode 100644 index 00000000..b21039d2 --- /dev/null +++ b/backend/flipperzero/recovery/abstractrecoveryoperation.h @@ -0,0 +1,36 @@ +#pragma once + +#include "abstractoperation.h" + +namespace Flipper { +namespace Zero { + +class Recovery; +class DeviceState; + +class AbstractRecoveryOperation : public AbstractOperation +{ + Q_OBJECT + +public: + AbstractRecoveryOperation(Recovery *recovery, QObject *parent = nullptr); + virtual ~AbstractRecoveryOperation() {} + + void start() override; + void finish() override; + +protected: + Recovery *recovery() const; + DeviceState *deviceState() const; + +private slots: + void onDeviceOnlineChanged(); + virtual void advanceOperationState() = 0; + +private: + Recovery *m_recovery; +}; + +} +} + diff --git a/backend/flipperzero/recovery/correctoptionbytesoperation.cpp b/backend/flipperzero/recovery/correctoptionbytesoperation.cpp new file mode 100644 index 00000000..59b3e26d --- /dev/null +++ b/backend/flipperzero/recovery/correctoptionbytesoperation.cpp @@ -0,0 +1,37 @@ +#include "correctoptionbytesoperation.h" + +#include + +#include "flipperzero/devicestate.h" +#include "flipperzero/recovery.h" + +using namespace Flipper; +using namespace Zero; + +CorrectOptionBytesOperation::CorrectOptionBytesOperation(Recovery *recovery, QIODevice *file, QObject *parent): + AbstractRecoveryOperation(recovery, parent), + m_file(file) +{} + +const QString CorrectOptionBytesOperation::description() const +{ + return QStringLiteral("Correct Option Bytes @%1").arg(deviceState()->name()); +} + +void CorrectOptionBytesOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(CorrectOptionBytesOperation::CorrectingOptionBytes); + correctOptionBytes(); + + } else if(operationState() == CorrectOptionBytesOperation::CorrectingOptionBytes) { + finish(); + } else {} +} + +void CorrectOptionBytesOperation::correctOptionBytes() +{ + if(!recovery()->downloadOptionBytes(m_file)) { + finishWithError(recovery()->errorString()); + } +} diff --git a/backend/flipperzero/recovery/correctoptionbytesoperation.h b/backend/flipperzero/recovery/correctoptionbytesoperation.h new file mode 100644 index 00000000..8b3bde5e --- /dev/null +++ b/backend/flipperzero/recovery/correctoptionbytesoperation.h @@ -0,0 +1,31 @@ +#pragma once + +#include "abstractrecoveryoperation.h" + +class QIODevice; + +namespace Flipper { +namespace Zero { + +class CorrectOptionBytesOperation : public AbstractRecoveryOperation +{ + Q_OBJECT + + enum OperationState { + CorrectingOptionBytes = AbstractOperation::User + }; + +public: + CorrectOptionBytesOperation(Recovery *recovery, QIODevice *file, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + +private: + void correctOptionBytes(); + QIODevice *m_file; +}; + +} +} diff --git a/backend/flipperzero/recovery/exitrecoveryoperation.cpp b/backend/flipperzero/recovery/exitrecoveryoperation.cpp new file mode 100644 index 00000000..f782ccf2 --- /dev/null +++ b/backend/flipperzero/recovery/exitrecoveryoperation.cpp @@ -0,0 +1,30 @@ +#include "exitrecoveryoperation.h" + +#include "flipperzero/recovery.h" +#include "flipperzero/devicestate.h" + +using namespace Flipper; +using namespace Zero; + +ExitRecoveryOperation::ExitRecoveryOperation(Recovery *recovery, QObject *parent): + AbstractRecoveryOperation(recovery, parent) +{} + +const QString ExitRecoveryOperation::description() const +{ + return QStringLiteral("Exit Recovery Mode @%1").arg(deviceState()->name()); +} + +void ExitRecoveryOperation::advanceOperationState() +{ + if(operationState() == BasicOperationState::Ready) { + setOperationState(OperationState::WaitingForOnline); + + if(!recovery()->exitRecoveryMode()) { + finishWithError(recovery()->errorString()); + } + + } else if(operationState() == OperationState::WaitingForOnline) { + finish(); + } +} diff --git a/backend/flipperzero/recovery/exitrecoveryoperation.h b/backend/flipperzero/recovery/exitrecoveryoperation.h new file mode 100644 index 00000000..f8cb78ea --- /dev/null +++ b/backend/flipperzero/recovery/exitrecoveryoperation.h @@ -0,0 +1,26 @@ +#pragma once + +#include "abstractrecoveryoperation.h" + +namespace Flipper { +namespace Zero { + +class ExitRecoveryOperation : public AbstractRecoveryOperation +{ + Q_OBJECT + + enum OperationState { + WaitingForOnline = BasicOperationState::User + }; + +public: + ExitRecoveryOperation(Recovery *recovery, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; +}; + +} +} + diff --git a/backend/flipperzero/recovery/firmwaredownloadoperation.cpp b/backend/flipperzero/recovery/firmwaredownloadoperation.cpp new file mode 100644 index 00000000..eec6911e --- /dev/null +++ b/backend/flipperzero/recovery/firmwaredownloadoperation.cpp @@ -0,0 +1,49 @@ +#include "firmwaredownloadoperation.h" + +#include +#include + +#include "flipperzero/devicestate.h" +#include "flipperzero/recovery.h" + +using namespace Flipper; +using namespace Zero; + +FirmwareDownloadOperation::FirmwareDownloadOperation(Recovery *recovery, QIODevice *file, QObject *parent): + AbstractRecoveryOperation(recovery, parent), + m_file(file) +{} + +const QString FirmwareDownloadOperation::description() const +{ + return QStringLiteral("Firmware Download @%1").arg(deviceState()->name()); +} + +void FirmwareDownloadOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(State::DownloadingFirmware); + downloadFirmware(); + } else if(operationState() == State::DownloadingFirmware) { + finish(); + } else { + finishWithError(QStringLiteral("Unexpected state.")); + } +} + +void FirmwareDownloadOperation::downloadFirmware() +{ + auto *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcherBase::finished, this, [=]() { + if(watcher->result()) { + advanceOperationState(); + } else { + finishWithError(recovery()->errorString()); + } + + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run(recovery(), &Recovery::downloadFirmware, m_file)); +} diff --git a/backend/flipperzero/recovery/firmwaredownloadoperation.h b/backend/flipperzero/recovery/firmwaredownloadoperation.h new file mode 100644 index 00000000..3391176d --- /dev/null +++ b/backend/flipperzero/recovery/firmwaredownloadoperation.h @@ -0,0 +1,33 @@ +#pragma once + +#include "abstractrecoveryoperation.h" + +class QIODevice; + +namespace Flipper { +namespace Zero { + +class FirmwareDownloadOperation : public AbstractRecoveryOperation +{ + Q_OBJECT + + enum State { + DownloadingFirmware = AbstractOperation::User + }; + +public: + FirmwareDownloadOperation(Recovery *recovery, QIODevice *file, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + +private: + void downloadFirmware(); + + QIODevice *m_file; +}; + +} +} + diff --git a/backend/flipperzero/recovery/fixbootissuesoperation.cpp b/backend/flipperzero/recovery/fixbootissuesoperation.cpp new file mode 100644 index 00000000..9017ce62 --- /dev/null +++ b/backend/flipperzero/recovery/fixbootissuesoperation.cpp @@ -0,0 +1,79 @@ +#include "fixbootissuesoperation.h" + +#include "flipperzero/recovery.h" +#include "flipperzero/devicestate.h" + +using namespace Flipper; +using namespace Zero; + +FixBootIssuesOperation::FixBootIssuesOperation(Recovery *recovery, QObject *parent): + AbstractRecoveryOperation(recovery, parent) +{} + +const QString FixBootIssuesOperation::description() const +{ + return QStringLiteral("Fix boot issues @%1").arg(deviceState()->name()); +} + +void FixBootIssuesOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(FixBootIssuesOperation::StartingWirelessStack); + startWirelessStack(); + + } else if(operationState() == FixBootIssuesOperation::StartingWirelessStack) { + setOperationState(FixBootIssuesOperation::FixingBootMode); + fixBootMode(); + + } else if(operationState() == FixBootIssuesOperation::FixingBootMode) { + setOperationState(AbstractOperation::Finished); + finish(); + + } else { + finishWithError(QStringLiteral("Unexpected state.")); + } +} + +void FixBootIssuesOperation::onOperationTimeout() +{ + QString msg; + + if(operationState() == FixBootIssuesOperation::StartingWirelessStack) { + msg = QStringLiteral("Failed to start the Wireless Stack: Operation timeout."); + } else if(operationState() == FixBootIssuesOperation::FixingBootMode) { + msg = QStringLiteral("Failed to set the Option Bytes: Operation timeout."); + } else { + msg = QStringLiteral("Should not have timed out here, probably a bug."); + } + + finishWithError(msg); +} + +void FixBootIssuesOperation::startWirelessStack() +{ + const auto wirelessStatus = recovery()->wirelessStatus(); + + if(wirelessStatus == Recovery::WirelessStatus::FUSRunning) { + if(!recovery()->startWirelessStack()) { + finishWithError(deviceState()->errorString()); + } else if(recovery()->wirelessStatus() == Recovery::WirelessStatus::UnhandledState) { + advanceOperationState(); + } else {} + + } else if(wirelessStatus == Recovery::WirelessStatus::WSRunning) { + advanceOperationState(); + } else if(wirelessStatus == Recovery::WirelessStatus::UnhandledState) { + finishWithError(QStringLiteral("Unhandled state. Probably a BUG.")); + } else { + finishWithError(QStringLiteral("Failed to get Wireless core status.")); + } +} + +void FixBootIssuesOperation::fixBootMode() +{ + if(!deviceState()->isRecoveryMode()) { + advanceOperationState(); + } else if (!recovery()->setBootMode(Recovery::BootMode::Normal)) { + finishWithError(recovery()->errorString()); + } else {} +} diff --git a/backend/flipperzero/operations/fixbootissuesoperation.h b/backend/flipperzero/recovery/fixbootissuesoperation.h similarity index 58% rename from backend/flipperzero/operations/fixbootissuesoperation.h rename to backend/flipperzero/recovery/fixbootissuesoperation.h index a31feecd..3557b16e 100644 --- a/backend/flipperzero/operations/fixbootissuesoperation.h +++ b/backend/flipperzero/recovery/fixbootissuesoperation.h @@ -1,14 +1,11 @@ #pragma once -#include "flipperzerooperation.h" +#include "abstractrecoveryoperation.h" namespace Flipper { - -class FlipperZero; - namespace Zero { -class FixBootIssuesOperation : public Operation +class FixBootIssuesOperation : public AbstractRecoveryOperation { Q_OBJECT @@ -18,13 +15,11 @@ class FixBootIssuesOperation : public Operation }; public: - FixBootIssuesOperation(FlipperZero *device, QObject *parent = nullptr); - ~FixBootIssuesOperation(); - + FixBootIssuesOperation(Recovery *recovery, QObject *parent = nullptr); const QString description() const override; private slots: - void transitionToNextState() override; + void advanceOperationState() override; void onOperationTimeout() override; private: diff --git a/backend/flipperzero/recovery/radioupdateoperation.cpp b/backend/flipperzero/recovery/radioupdateoperation.cpp new file mode 100644 index 00000000..ef79e9a9 --- /dev/null +++ b/backend/flipperzero/recovery/radioupdateoperation.cpp @@ -0,0 +1,160 @@ +#include "radioupdateoperation.h" + +#include +#include +#include +#include +#include + +#include "flipperzero/radiomanifest.h" +#include "flipperzero/devicestate.h" +#include "flipperzero/recovery.h" + +#include "tarziparchive.h" +#include "tararchive.h" + +using namespace Flipper; +using namespace Zero; + +RadioUpdateOperation::RadioUpdateOperation(Recovery *recovery, QFile *file, QObject *parent): + AbstractRecoveryOperation(recovery, parent), + m_sourceFile(file), + m_firmwareFile(new QBuffer(this)), + m_loopTimer(new QTimer(this)) +{ + connect(m_loopTimer, &QTimer::timeout, this, &RadioUpdateOperation::advanceOperationState); +} + +const QString RadioUpdateOperation::description() const +{ + return QStringLiteral("Radio firmware update @%1").arg(deviceState()->name()); +} + +void RadioUpdateOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(RadioUpdateOperation::UnpackingArchive); + + } else if(operationState() == RadioUpdateOperation::UnpackingArchive) { + setOperationState(RadioUpdateOperation::StartingFUS); + startFUS(); + + } else if(operationState() == RadioUpdateOperation::StartingFUS) { + setOperationState(RadioUpdateOperation::DeletingWirelessStack); + deleteWirelessStack(); + + } else if(operationState() == RadioUpdateOperation::DeletingWirelessStack) { + if(isWirelessStackDeleted()) { + setOperationState(RadioUpdateOperation::DownloadingWirelessStack); + downloadWirelessStack(); + } + + } else if(operationState() == RadioUpdateOperation::DownloadingWirelessStack) { + setOperationState(RadioUpdateOperation::UpgradingWirelessStack); + upgradeWirelessStack(); + + } else if(operationState() == RadioUpdateOperation::UpgradingWirelessStack) { + if(isWirelessStackUpgraded()) { + finish(); + } + + } else { + finishWithError(QStringLiteral("Unexpected state")); + } +} + +void RadioUpdateOperation::unpackArchive() +{ +// auto *archive = new TarZipArchive(m_sourceFile, this); + +// if(archive->isError()) { +// finishWithError(QStringLiteral("Failed to uncompress radio firmware file: %1").arg(archive->errorString())); +// return; +// } + +// connect(archive, &TarZipArchive::ready, this, [=]() { + // }); +} + +void RadioUpdateOperation::startFUS() +{ + if(!recovery()->startFUS()) { + finishWithError(recovery()->errorString()); + } +} + +void RadioUpdateOperation::deleteWirelessStack() +{ + if(!recovery()->deleteWirelessStack()) { + finishWithError(recovery()->errorString()); + } else { + m_loopTimer->start(1000); + } +} + +bool RadioUpdateOperation::isWirelessStackDeleted() +{ + const auto status = recovery()->wirelessStatus(); + + const auto waitNext = (status == Recovery::WirelessStatus::Invalid) || + (status == Recovery::WirelessStatus::UnhandledState); + if(waitNext) { + return false; + } + + m_loopTimer->stop(); + + const auto errorOccured = (status == Recovery::WirelessStatus::WSRunning) || + (status == Recovery::WirelessStatus::ErrorOccured); + if(errorOccured) { + finishWithError(QStringLiteral("Failed to finish removal of the Wireless Stack.")); + } + + return !errorOccured; +} + +void RadioUpdateOperation::downloadWirelessStack() +{ + auto *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcherBase::finished, this, [=]() { + if(watcher->result()) { + advanceOperationState(); + } else { + finishWithError(QStringLiteral("Failed to download the Wireless Stack.")); + } + + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run(recovery(), &Recovery::downloadWirelessStack, m_firmwareFile, 0)); +} + +void RadioUpdateOperation::upgradeWirelessStack() +{ + if(!recovery()->upgradeWirelessStack()) { + finishWithError(recovery()->errorString()); + } else { + m_loopTimer->start(1000); + } +} + +bool RadioUpdateOperation::isWirelessStackUpgraded() +{ + const auto status = recovery()->wirelessStatus(); + + const auto waitNext = (status == Recovery::WirelessStatus::Invalid) || + (status == Recovery::WirelessStatus::UnhandledState); + if(waitNext) { + return false; + } + + m_loopTimer->stop(); + + const auto errorOccured = (status == Recovery::WirelessStatus::ErrorOccured); + if(errorOccured) { + finishWithError(QStringLiteral("Failed to finish installation of the Wireless Stack.")); + } + + return !errorOccured; +} diff --git a/backend/flipperzero/recovery/radioupdateoperation.h b/backend/flipperzero/recovery/radioupdateoperation.h new file mode 100644 index 00000000..fa5699e5 --- /dev/null +++ b/backend/flipperzero/recovery/radioupdateoperation.h @@ -0,0 +1,48 @@ +#pragma once + +#include "abstractrecoveryoperation.h" + +class QFile; +class QTimer; +class QBuffer; + +namespace Flipper { +namespace Zero { + +class RadioUpdateOperation : public AbstractRecoveryOperation +{ + Q_OBJECT + + enum OperationState { + UnpackingArchive = AbstractOperation::User, + StartingFUS, + DeletingWirelessStack, + DownloadingWirelessStack, + UpgradingWirelessStack + }; + +public: + RadioUpdateOperation(Recovery *recovery, QFile *file, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + void onOperationTimeout() override; + +private: + void unpackArchive(); + void startFUS(); + void deleteWirelessStack(); + bool isWirelessStackDeleted(); + void downloadWirelessStack(); + void upgradeWirelessStack(); + bool isWirelessStackUpgraded(); + + QFile *m_sourceFile; + QBuffer *m_firmwareFile; + QTimer *m_loopTimer; +}; + +} +} + diff --git a/backend/flipperzero/recovery/setbootmodeoperation.cpp b/backend/flipperzero/recovery/setbootmodeoperation.cpp new file mode 100644 index 00000000..55f8d1a9 --- /dev/null +++ b/backend/flipperzero/recovery/setbootmodeoperation.cpp @@ -0,0 +1,67 @@ +#include "setbootmodeoperation.h" + +#include "flipperzero/devicestate.h" +#include "flipperzero/recovery.h" + +using namespace Flipper; +using namespace Zero; + +SetBootModeOperation::SetBootModeOperation(Recovery *recovery, QObject *parent): + AbstractRecoveryOperation(recovery, parent) +{} + +const QString SetBootModeOperation::description() const +{ + return QStringLiteral("Set %1 boot mode @%2").arg(typeString(), deviceState()->name()); +} + +void SetBootModeOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(SetBootModeOperation::WaitingForBoot); + setBootMode(); + + } else if(operationState() == SetBootModeOperation::WaitingForBoot) { + finish(); + } +} + +void SetBootModeOperation::onOperationTimeout() +{ + finishWithError(QStringLiteral("Failed to set %1 mode: operation timeout").arg(typeString())); +} + +void SetBootModeOperation::setBootMode() +{ + if(!recovery()->setBootMode((Recovery::BootMode)bootMode())) { + finishWithError(recovery()->errorString()); + } +} + +SetRecoveryBootOperation::SetRecoveryBootOperation(Recovery *recovery, QObject *parent): + SetBootModeOperation(recovery, parent) +{} + +int SetRecoveryBootOperation::bootMode() const +{ + return (int)Recovery::BootMode::DFUOnly; +} + +const QString SetRecoveryBootOperation::typeString() const +{ + return QStringLiteral("Recovery"); +} + +SetOSBootOperation::SetOSBootOperation(Recovery *recovery, QObject *parent): + SetBootModeOperation(recovery, parent) +{} + +int SetOSBootOperation::bootMode() const +{ + return (int)Recovery::BootMode::Normal; +} + +const QString SetOSBootOperation::typeString() const +{ + return QStringLiteral("OS"); +} diff --git a/backend/flipperzero/recovery/setbootmodeoperation.h b/backend/flipperzero/recovery/setbootmodeoperation.h new file mode 100644 index 00000000..d10257e3 --- /dev/null +++ b/backend/flipperzero/recovery/setbootmodeoperation.h @@ -0,0 +1,57 @@ +#pragma once + +#include "abstractrecoveryoperation.h" + +namespace Flipper { +namespace Zero { + +class SetBootModeOperation : public AbstractRecoveryOperation +{ + Q_OBJECT + + enum OperationState { + WaitingForBoot = AbstractOperation::User + }; + +public: + SetBootModeOperation(Recovery *recovery, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + void onOperationTimeout() override; + +private: + virtual int bootMode() const = 0; + virtual const QString typeString() const = 0; + + void setBootMode(); +}; + +class SetRecoveryBootOperation : public SetBootModeOperation +{ + Q_OBJECT + +public: + SetRecoveryBootOperation(Recovery *recovery, QObject *parent = nullptr); + +private: + int bootMode() const override; + const QString typeString() const override; +}; + +class SetOSBootOperation : public SetBootModeOperation +{ + Q_OBJECT + +public: + SetOSBootOperation(Recovery *recovery, QObject *parent = nullptr); + +private: + int bootMode() const override; + const QString typeString() const override; +}; + +} +} + diff --git a/backend/flipperzero/recovery/wirelessstackdownloadoperation.cpp b/backend/flipperzero/recovery/wirelessstackdownloadoperation.cpp new file mode 100644 index 00000000..e0185bc8 --- /dev/null +++ b/backend/flipperzero/recovery/wirelessstackdownloadoperation.cpp @@ -0,0 +1,156 @@ +#include "wirelessstackdownloadoperation.h" + +#include +#include +#include +#include + +#include "flipperzero/devicestate.h" +#include "flipperzero/recovery.h" + +using namespace Flipper; +using namespace Zero; + +WirelessStackDownloadOperation::WirelessStackDownloadOperation(Recovery *recovery, QIODevice *file, uint32_t targetAddress, QObject *parent): + AbstractRecoveryOperation(recovery, parent), + m_file(file), + m_loopTimer(new QTimer(this)), + m_targetAddress(targetAddress) +{ + connect(m_loopTimer, &QTimer::timeout, this, &WirelessStackDownloadOperation::advanceOperationState); +} + +const QString WirelessStackDownloadOperation::description() const +{ + return QStringLiteral("Co-Processor Firmware Download @%1").arg(deviceState()->name()); +} + +void WirelessStackDownloadOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(WirelessStackDownloadOperation::StartingFUS); + startFUS(); + + } else if(operationState() == WirelessStackDownloadOperation::StartingFUS) { + setOperationState(WirelessStackDownloadOperation::DeletingWirelessStack); + deleteWirelessStack(); + + } else if(operationState() == WirelessStackDownloadOperation::DeletingWirelessStack) { + if(isWirelessStackDeleted()) { + setOperationState(WirelessStackDownloadOperation::DownloadingWirelessStack); + downloadWirelessStack(); + } + + } else if(operationState() == WirelessStackDownloadOperation::DownloadingWirelessStack) { + setOperationState(WirelessStackDownloadOperation::UpgradingWirelessStack); + upgradeWirelessStack(); + + } else if(operationState() == WirelessStackDownloadOperation::UpgradingWirelessStack) { + if(isWirelessStackUpgraded()) { + finish(); + } + + } else { + finishWithError(QStringLiteral("Unexpected state")); + } +} + +void WirelessStackDownloadOperation::onOperationTimeout() +{ + QString msg; + + if(operationState() == WirelessStackDownloadOperation::StartingFUS) { + msg = QStringLiteral("Failed to start Firmware Upgrade Service: Operation timeout."); + } else if(operationState() == WirelessStackDownloadOperation::DeletingWirelessStack) { + msg = QStringLiteral("Failed to delete existing Wireless Stack: Operation timeout."); + } else if(operationState() == WirelessStackDownloadOperation::UpgradingWirelessStack) { + msg = QStringLiteral("Failed to upgrade Wireless Stack: Operation timeout."); + } else { + msg = QStringLiteral("Should not have timed out here, probably a bug."); + } + + finishWithError(msg); +} + +void WirelessStackDownloadOperation::startFUS() +{ + if(!recovery()->startFUS()) { + finishWithError(recovery()->errorString()); + } +} + +void WirelessStackDownloadOperation::deleteWirelessStack() +{ + if(!recovery()->deleteWirelessStack()) { + finishWithError(recovery()->errorString()); + } else { + m_loopTimer->start(1000); + } +} + +bool WirelessStackDownloadOperation::isWirelessStackDeleted() +{ + const auto status = recovery()->wirelessStatus(); + + const auto waitNext = (status == Recovery::WirelessStatus::Invalid) || + (status == Recovery::WirelessStatus::UnhandledState); + if(waitNext) { + return false; + } + + m_loopTimer->stop(); + + const auto errorOccured = (status == Recovery::WirelessStatus::WSRunning) || + (status == Recovery::WirelessStatus::ErrorOccured); + if(errorOccured) { + finishWithError(QStringLiteral("Failed to finish removal of the Wireless Stack.")); + } + + return !errorOccured; +} + +void WirelessStackDownloadOperation::downloadWirelessStack() +{ + auto *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcherBase::finished, this, [=]() { + if(watcher->result()) { + advanceOperationState(); + } else { + finishWithError(QStringLiteral("Failed to download the Wireless Stack.")); + } + + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run(recovery(), &Recovery::downloadWirelessStack, m_file, m_targetAddress)); +} + +void WirelessStackDownloadOperation::upgradeWirelessStack() +{ + if(!recovery()->upgradeWirelessStack()) { + finishWithError(recovery()->errorString()); + } else { + m_loopTimer->start(1000); + } +} + +bool WirelessStackDownloadOperation::isWirelessStackUpgraded() +{ + const auto status = recovery()->wirelessStatus(); + + const auto waitNext = (status == Recovery::WirelessStatus::Invalid) || + (status == Recovery::WirelessStatus::UnhandledState); + if(waitNext) { + return false; + } + + m_loopTimer->stop(); + + const auto errorOccured = (status == Recovery::WirelessStatus::ErrorOccured); + if(errorOccured) { + finishWithError(QStringLiteral("Failed to finish installation of the Wireless Stack.")); + } + + return !errorOccured; +} diff --git a/backend/flipperzero/operations/wirelessstackdownloadoperation.h b/backend/flipperzero/recovery/wirelessstackdownloadoperation.h similarity index 52% rename from backend/flipperzero/operations/wirelessstackdownloadoperation.h rename to backend/flipperzero/recovery/wirelessstackdownloadoperation.h index b2253e85..f8ea7390 100644 --- a/backend/flipperzero/operations/wirelessstackdownloadoperation.h +++ b/backend/flipperzero/recovery/wirelessstackdownloadoperation.h @@ -1,43 +1,33 @@ #pragma once -#include "flipperzerooperation.h" +#include "abstractrecoveryoperation.h" class QTimer; class QIODevice; namespace Flipper { - -class FlipperZero; - namespace Zero { -class WirelessStackDownloadOperation : public Operation +class WirelessStackDownloadOperation : public AbstractRecoveryOperation { Q_OBJECT enum State { - BootingToDFU = AbstractOperation::User, - SettingDFUBoot, - StartingFUS, + StartingFUS = AbstractOperation::User, DeletingWirelessStack, DownloadingWirelessStack, - UpgradingWirelessStack, - ResettingDFUBoot + UpgradingWirelessStack }; public: - WirelessStackDownloadOperation(FlipperZero *device, QIODevice *file, uint32_t targetAddress = 0, QObject *parent = nullptr); - ~WirelessStackDownloadOperation(); - + WirelessStackDownloadOperation(Recovery *recovery, QIODevice *file, uint32_t targetAddress = 0, QObject *parent = nullptr); const QString description() const override; private slots: - void transitionToNextState() override; + void advanceOperationState() override; void onOperationTimeout() override; private: - void bootToDFU(); - void setDFUBoot(bool set); void startFUS(); void deleteWirelessStack(); bool isWirelessStackDeleted(); diff --git a/backend/flipperzero/recoveryinterface.cpp b/backend/flipperzero/recoveryinterface.cpp new file mode 100644 index 00000000..078eefd9 --- /dev/null +++ b/backend/flipperzero/recoveryinterface.cpp @@ -0,0 +1,75 @@ +#include "recoveryinterface.h" + +#include "flipperzero/recovery/setbootmodeoperation.h" +#include "flipperzero/recovery/exitrecoveryoperation.h" +#include "flipperzero/recovery/fixbootissuesoperation.h" +#include "flipperzero/recovery/correctoptionbytesoperation.h" +#include "flipperzero/recovery/firmwaredownloadoperation.h" +#include "flipperzero/recovery/wirelessstackdownloadoperation.h" + +#include "recovery.h" +#include "macros.h" + +using namespace Flipper; +using namespace Zero; + +RecoveryInterface::RecoveryInterface(DeviceState *state, QObject *parent): + AbstractOperationRunner(parent), + m_recovery(new Recovery(state, this)) +{} + +ExitRecoveryOperation *RecoveryInterface::exitRecoveryMode() +{ + auto *operation = new ExitRecoveryOperation(m_recovery, this); + enqueueOperation(operation); + return operation; +} + +SetBootModeOperation *RecoveryInterface::setOSBootMode() +{ + auto *operation = new SetOSBootOperation(m_recovery, this); + enqueueOperation(operation); + return operation; +} + +SetBootModeOperation *RecoveryInterface::setRecoveryBootMode() +{ + auto *operation = new SetRecoveryBootOperation(m_recovery, this); + enqueueOperation(operation); + return operation; +} + +FirmwareDownloadOperation *RecoveryInterface::downloadFirmware(QIODevice *file) +{ + auto *operation = new FirmwareDownloadOperation(m_recovery, file, this); + enqueueOperation(operation); + return operation; +} + +WirelessStackDownloadOperation *RecoveryInterface::downloadFUS(QIODevice *file, uint32_t address) +{ + auto *operation = new WirelessStackDownloadOperation(m_recovery, file, address, this); + enqueueOperation(operation); + return operation; +} + +WirelessStackDownloadOperation *RecoveryInterface::downloadWirelessStack(QIODevice *file) +{ + auto *operation = new WirelessStackDownloadOperation(m_recovery, file, 0, this); + enqueueOperation(operation); + return operation; +} + +FixBootIssuesOperation *RecoveryInterface::fixBootIssues() +{ + auto *operation = new FixBootIssuesOperation(m_recovery, this); + enqueueOperation(operation); + return operation; +} + +CorrectOptionBytesOperation *RecoveryInterface::fixOptionBytes(QIODevice *file) +{ + auto *operation = new CorrectOptionBytesOperation(m_recovery, file, this); + enqueueOperation(operation); + return operation; +} diff --git a/backend/flipperzero/recoveryinterface.h b/backend/flipperzero/recoveryinterface.h new file mode 100644 index 00000000..f3f320b9 --- /dev/null +++ b/backend/flipperzero/recoveryinterface.h @@ -0,0 +1,46 @@ +#pragma once + +#include "abstractoperationrunner.h" + +class QIODevice; + +namespace Flipper { +namespace Zero { + +class Recovery; +class DeviceState; + +class SetBootModeOperation; +class ExitRecoveryOperation; +class FirmwareDownloadOperation; +class FixBootIssuesOperation; +class CorrectOptionBytesOperation; +class WirelessStackDownloadOperation; + +class RecoveryInterface : public AbstractOperationRunner +{ + Q_OBJECT + +public: + RecoveryInterface(DeviceState *state, QObject *parent = nullptr); + + ExitRecoveryOperation *exitRecoveryMode(); + + SetBootModeOperation *setOSBootMode(); + SetBootModeOperation *setRecoveryBootMode(); + + FirmwareDownloadOperation *downloadFirmware(QIODevice *file); + + WirelessStackDownloadOperation *downloadFUS(QIODevice *file, uint32_t address); + WirelessStackDownloadOperation *downloadWirelessStack(QIODevice *file); + + FixBootIssuesOperation *fixBootIssues(); + CorrectOptionBytesOperation *fixOptionBytes(QIODevice *file); + +private: + Recovery *m_recovery; +}; + +} +} + diff --git a/backend/flipperzero/remotecontroller.cpp b/backend/flipperzero/remotecontroller.cpp deleted file mode 100644 index 54221813..00000000 --- a/backend/flipperzero/remotecontroller.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "remotecontroller.h" - -#include - -#include "macros.h" - -namespace Flipper { -namespace Zero { - -RemoteController::RemoteController(QSerialPortInfo portInfo, QObject *parent): - QObject(parent), - m_port(new QSerialPort(portInfo, this)), - m_isEnabled(false), - m_isHeaderFound(false) -{} - -RemoteController::~RemoteController() -{ - setEnabled(false); -} - -const QByteArray &RemoteController::screenData() const -{ - return m_screenData; -} - -bool RemoteController::isEnabled() const -{ - return m_isEnabled; -} - -void RemoteController::setEnabled(bool enabled) -{ - if (m_isEnabled == enabled) { - return; - } - - if(enabled) { - const auto success = openPort(); - check_return_void(success, "Failed to open serial port"); - } else { - closePort(); - } - - m_isEnabled = enabled; - emit enabledChanged(); -} - -int RemoteController::screenWidth() -{ - return 128; -} - -int RemoteController::screenHeight() -{ - return 64; -} - -void RemoteController::sendInputEvent(InputKey key, InputType type) -{ - const char input[] = { 27, 'i', (char)key, (char)type }; - m_port->write(input, sizeof(input)); - m_port->flush(); -} - -void RemoteController::onPortReadyRead() -{ - static const auto header = QByteArrayLiteral("\xf0\xe1\xd2\xc3"); - - m_dataBuffer += m_port->readAll(); - - if (!m_isHeaderFound) { - const int pos = m_dataBuffer.indexOf(header); - if (pos >= 0) { - m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - pos - header.size()); - m_isHeaderFound = true; - } - } - - if(m_isHeaderFound) { - const auto bufSize = screenWidth() * screenHeight() / 8; - - if(m_dataBuffer.size() >= bufSize) { - m_screenData = m_dataBuffer.left(bufSize); - m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - bufSize); - m_isHeaderFound = false; - - emit screenDataChanged(); - } - } -} - -void RemoteController::onPortErrorOccured() -{ - if(m_port->error() == QSerialPort::ResourceError) { - return; - } - - error_msg(QString("Serial port error occured: %1").arg(m_port->errorString())); - setEnabled(false); -} - -bool RemoteController::openPort() -{ - const auto success = m_port->open(QIODevice::ReadWrite); - - if(success) { - connect(m_port, &QSerialPort::readyRead, this, &RemoteController::onPortReadyRead); - connect(m_port, &QSerialPort::errorOccurred, this, &RemoteController::onPortErrorOccured); - - m_port->setDataTerminalReady(true); - m_port->write("\rscreen_stream\r"); - - } else { - error_msg(QString("Failed to open serial port: %1").arg(m_port->errorString())); - } - - return success; -} - -void RemoteController::closePort() -{ - disconnect(m_port, &QSerialPort::readyRead, this, &RemoteController::onPortReadyRead); - disconnect(m_port, &QSerialPort::errorOccurred, this, &RemoteController::onPortErrorOccured); - - m_port->write("\x01\r\n"); - m_port->clear(); - m_port->close(); -} - -} // namespace Zero -} // namespace Flipper diff --git a/backend/flipperzero/screenstreamer.cpp b/backend/flipperzero/screenstreamer.cpp new file mode 100644 index 00000000..77bcce65 --- /dev/null +++ b/backend/flipperzero/screenstreamer.cpp @@ -0,0 +1,146 @@ +#include "screenstreamer.h" + +#include + +#include "devicestate.h" +#include "macros.h" + +using namespace Flipper; +using namespace Zero; + +ScreenStreamer::ScreenStreamer(DeviceState *deviceState, QObject *parent): + QObject(parent), + m_deviceState(deviceState), + m_serialPort(nullptr), + m_isEnabled(false), + m_isHeaderFound(false) +{ + connect(m_deviceState, &DeviceState::deviceInfoChanged, this, &ScreenStreamer::createPort); + createPort(); +} + +ScreenStreamer::~ScreenStreamer() +{ + setEnabled(false); +} + +const QByteArray &ScreenStreamer::screenData() const +{ + return m_screenData; +} + +bool ScreenStreamer::isEnabled() const +{ + return m_isEnabled; +} + +void ScreenStreamer::setEnabled(bool enabled) +{ + if (m_isEnabled == enabled) { + return; + } + + if(enabled) { + check_return_void(openPort(), "Failed to open serial port"); + } else { + closePort(); + } + + m_isEnabled = enabled; + emit enabledChanged(); +} + +int ScreenStreamer::screenWidth() +{ + return 128; +} + +int ScreenStreamer::screenHeight() +{ + return 64; +} + +void ScreenStreamer::sendInputEvent(InputKey key, InputType type) +{ + const char input[] = { 27, 'i', (char)key, (char)type }; + m_serialPort->write(input, sizeof(input)); + m_serialPort->flush(); +} + +void ScreenStreamer::createPort() +{ + if(m_serialPort) { + m_serialPort->deleteLater(); + } + + if(!m_deviceState->isRecoveryMode()) { + m_serialPort = new QSerialPort(m_deviceState->deviceInfo().serialInfo, this); + } else{ + m_serialPort = nullptr; + } +} + +void ScreenStreamer::onPortReadyRead() +{ + static const auto header = QByteArrayLiteral("\xf0\xe1\xd2\xc3"); + + m_dataBuffer += m_serialPort->readAll(); + + if (!m_isHeaderFound) { + const int pos = m_dataBuffer.indexOf(header); + if (pos >= 0) { + m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - pos - header.size()); + m_isHeaderFound = true; + } + } + + if(m_isHeaderFound) { + const auto bufSize = screenWidth() * screenHeight() / 8; + + if(m_dataBuffer.size() >= bufSize) { + m_screenData = m_dataBuffer.left(bufSize); + m_dataBuffer = m_dataBuffer.right(m_dataBuffer.length() - bufSize); + m_isHeaderFound = false; + + emit screenDataChanged(); + } + } +} + +void ScreenStreamer::onPortErrorOccured() +{ + if(m_serialPort->error() == QSerialPort::ResourceError) { + return; + } + + error_msg(QString("Serial port error occured: %1").arg(m_serialPort->errorString())); + setEnabled(false); +} + +bool ScreenStreamer::openPort() +{ + const auto success = m_serialPort->open(QIODevice::ReadWrite); + + if(success) { + connect(m_serialPort, &QSerialPort::readyRead, this, &ScreenStreamer::onPortReadyRead); + connect(m_serialPort, &QSerialPort::errorOccurred, this, &ScreenStreamer::onPortErrorOccured); + + m_serialPort->setDataTerminalReady(true); + m_serialPort->write("\rscreen_stream\r"); + + } else { + error_msg(QString("Failed to open serial port: %1").arg(m_serialPort->errorString())); + } + + return success; +} + +void ScreenStreamer::closePort() +{ + disconnect(m_serialPort, &QSerialPort::readyRead, this, &ScreenStreamer::onPortReadyRead); + disconnect(m_serialPort, &QSerialPort::errorOccurred, this, &ScreenStreamer::onPortErrorOccured); + + m_serialPort->write("\x01\r\n"); + m_serialPort->clear(); + m_serialPort->close(); +} diff --git a/backend/flipperzero/remotecontroller.h b/backend/flipperzero/screenstreamer.h similarity index 84% rename from backend/flipperzero/remotecontroller.h rename to backend/flipperzero/screenstreamer.h index 10d4a31e..4a0c18d8 100644 --- a/backend/flipperzero/remotecontroller.h +++ b/backend/flipperzero/screenstreamer.h @@ -1,5 +1,4 @@ -#ifndef FLIPPER_ZEROREMOTE_H -#define FLIPPER_ZEROREMOTE_H +#pragma once #include #include @@ -10,7 +9,9 @@ class QSerialPort; namespace Flipper { namespace Zero { -class RemoteController : public QObject +class DeviceState; + +class ScreenStreamer : public QObject { Q_OBJECT Q_PROPERTY(QByteArray screenData READ screenData NOTIFY screenDataChanged) @@ -40,8 +41,8 @@ class RemoteController : public QObject Q_ENUM(InputType) - RemoteController(QSerialPortInfo portInfo, QObject *parent = nullptr); - ~RemoteController(); + ScreenStreamer(DeviceState *deviceState, QObject *parent = nullptr); + ~ScreenStreamer(); const QByteArray &screenData() const; @@ -59,6 +60,7 @@ public slots: void enabledChanged(); private slots: + void createPort(); void onPortReadyRead(); void onPortErrorOccured(); @@ -66,7 +68,9 @@ private slots: bool openPort(); void closePort(); - QSerialPort *m_port; + DeviceState *m_deviceState; + QSerialPort *m_serialPort; + QByteArray m_dataBuffer; QByteArray m_screenData; @@ -74,7 +78,5 @@ private slots: bool m_isHeaderFound; }; -} // namespace Zero -} // namespace Flipper - -#endif // FLIPPER_ZEROREMOTE_H +} +} diff --git a/backend/flipperzero/storagecontroller.cpp b/backend/flipperzero/storagecontroller.cpp deleted file mode 100644 index ce2aa767..00000000 --- a/backend/flipperzero/storagecontroller.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "storagecontroller.h" - -#include -#include - -#include "common/skipmotdoperation.h" -#include "storage/removeoperation.h" -#include "storage/mkdiroperation.h" -#include "storage/writeoperation.h" -#include "storage/readoperation.h" -#include "storage/statoperation.h" -#include "storage/listoperation.h" - -#include "macros.h" - -using namespace Flipper; -using namespace Zero; - -StorageController::StorageController(const QSerialPortInfo &portInfo, QObject *parent): - SignalingFailable(parent), - m_serialPort(new QSerialPort(portInfo, this)), - m_state(State::Idle) -{} - -StorageController::~StorageController() -{} - -ListOperation *StorageController::list(const QByteArray &dirName) -{ - auto *op = new ListOperation(m_serialPort, dirName, this); - enqueueOperation(op); - return op; - -} - -StatOperation *StorageController::stat(const QByteArray &fileName) -{ - auto *op = new StatOperation(m_serialPort, fileName, this); - enqueueOperation(op); - return op; -} - -ReadOperation *StorageController::read(const QByteArray &fileName, QIODevice *file) -{ - auto *op = new ReadOperation(m_serialPort, fileName, file, this); - enqueueOperation(op); - return op; -} - -MkDirOperation *StorageController::mkdir(const QByteArray &dirName) -{ - auto *op = new MkDirOperation(m_serialPort, dirName, this); - enqueueOperation(op); - return op; -} - -WriteOperation *StorageController::write(const QByteArray &fileName, QIODevice *file) -{ - auto *op = new WriteOperation(m_serialPort, fileName, file, this); - enqueueOperation(op); - return op; -} - -RemoveOperation *StorageController::remove(const QByteArray &fileName) -{ - auto *op = new RemoveOperation(m_serialPort, fileName, this); - enqueueOperation(op); - return op; -} - -void StorageController::processQueue() -{ - if(m_operationQueue.isEmpty()) { - closePort(); - return; - } - - auto *currentOperation = m_operationQueue.dequeue(); - - connect(currentOperation, &AbstractOperation::finished, this, [=]() { - - if(currentOperation->isError()) { - clearQueue(); - processQueue(); - setError(QStringLiteral("Operation error: %1").arg(currentOperation->errorString())); - - } else { - processQueue(); - } - - currentOperation->deleteLater(); - }); - - currentOperation->start(); -} - -bool StorageController::openPort() -{ - if(!m_serialPort->open(QIODevice::ReadWrite)) { - setError(QStringLiteral("Serial port error: %1").arg(m_serialPort->errorString())); - return false; - } - - m_state = State::Running; - m_operationQueue.prepend(new SkipMOTDOperation(m_serialPort, this)); - - return true; -} - -void StorageController::closePort() -{ - m_state = State::Idle; - m_serialPort->close(); -} - -void StorageController::enqueueOperation(AbstractSerialOperation *op) -{ - m_operationQueue.enqueue(op); - - if(m_state == State::Idle) { - if(!openPort()) { - return; - } - - resetError(); - QTimer::singleShot(0, this, &StorageController::processQueue); - } -} - -void StorageController::clearQueue() -{ - while(!m_operationQueue.isEmpty()) { - m_operationQueue.dequeue()->deleteLater(); - } -} diff --git a/backend/flipperzero/toplevel/abstracttopleveloperation.cpp b/backend/flipperzero/toplevel/abstracttopleveloperation.cpp new file mode 100644 index 00000000..5ac9ef23 --- /dev/null +++ b/backend/flipperzero/toplevel/abstracttopleveloperation.cpp @@ -0,0 +1,56 @@ +#include "abstracttopleveloperation.h" + +#include + +#include "flipperzero/devicestate.h" + +using namespace Flipper; +using namespace Zero; + +AbstractTopLevelOperation::AbstractTopLevelOperation(DeviceState *deviceState, QObject *parent): + AbstractOperation(parent), + m_deviceState(deviceState) +{ + m_deviceState->setPersistent(true); +} + +AbstractTopLevelOperation::~AbstractTopLevelOperation() +{ + m_deviceState->setPersistent(false); +} + +DeviceState *AbstractTopLevelOperation::deviceState() const +{ + return m_deviceState; +} + +void AbstractTopLevelOperation::start() +{ + if(operationState() != AbstractOperation::Ready) { + finishWithError(QStringLiteral("Trying to start an operation that is either already running or has finished.")); + } else { + advanceOperationState(); + } +} + +void AbstractTopLevelOperation::advanceOperationState() +{ + QTimer::singleShot(0, this, &AbstractTopLevelOperation::nextStateLogic); +} + +void AbstractTopLevelOperation::registerOperation(AbstractOperation *operation) +{ + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + onSubOperationErrorOccured(); + finishWithError(operation->errorString()); + } else { + advanceOperationState(); + } + }); +} + +void AbstractTopLevelOperation::onSubOperationErrorOccured() +{ + //Empty default implementation +} diff --git a/backend/flipperzero/toplevel/abstracttopleveloperation.h b/backend/flipperzero/toplevel/abstracttopleveloperation.h new file mode 100644 index 00000000..a3d9c153 --- /dev/null +++ b/backend/flipperzero/toplevel/abstracttopleveloperation.h @@ -0,0 +1,37 @@ +#pragma once + +#include "abstractoperation.h" + +namespace Flipper { +namespace Zero { + +class DeviceState; + +class AbstractTopLevelOperation : public AbstractOperation +{ + Q_OBJECT + +public: + AbstractTopLevelOperation(DeviceState *deviceState, QObject *parent = nullptr); + virtual ~AbstractTopLevelOperation(); + + DeviceState *deviceState() const; + + void start() override; + +protected: + void advanceOperationState(); + void registerOperation(AbstractOperation *operation); + +private slots: + virtual void nextStateLogic() = 0; + +private: + virtual void onSubOperationErrorOccured(); + + DeviceState *m_deviceState; +}; + +} +} + diff --git a/backend/flipperzero/toplevel/firmwareupdateoperation.cpp b/backend/flipperzero/toplevel/firmwareupdateoperation.cpp new file mode 100644 index 00000000..be12af78 --- /dev/null +++ b/backend/flipperzero/toplevel/firmwareupdateoperation.cpp @@ -0,0 +1,61 @@ +#include "firmwareupdateoperation.h" + +#include + +#include "flipperzero/devicestate.h" + +#include "flipperzero/utilityinterface.h" +#include "flipperzero/utility/startrecoveryoperation.h" + +#include "flipperzero/recoveryinterface.h" +#include "flipperzero/recovery/exitrecoveryoperation.h" +#include "flipperzero/recovery/firmwaredownloadoperation.h" + +using namespace Flipper; +using namespace Zero; + +FirmwareUpdateOperation::FirmwareUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent): + AbstractTopLevelOperation(state, parent), + m_recovery(recovery), + m_utility(utility), + m_file(new QFile(filePath, this)) +{} + +const QString FirmwareUpdateOperation::description() const +{ + return QStringLiteral("Firmware Update @%1").arg(deviceState()->name()); +} + +void FirmwareUpdateOperation::nextStateLogic() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(FirmwareUpdateOperation::StartingRecovery); + startRecoveryMode(); + + } else if(operationState() == FirmwareUpdateOperation::StartingRecovery) { + setOperationState(FirmwareUpdateOperation::UpdatingFirmware); + updateFirmware(); + + } else if(operationState() == FirmwareUpdateOperation::UpdatingFirmware) { + setOperationState(FirmwareUpdateOperation::ExitingRecovery); + exitRecoveryMode(); + + } else if(operationState() == FirmwareUpdateOperation::ExitingRecovery) { + finish(); + } +} + +void FirmwareUpdateOperation::startRecoveryMode() +{ + registerOperation(m_utility->startRecoveryMode()); +} + +void FirmwareUpdateOperation::updateFirmware() +{ + registerOperation(m_recovery->downloadFirmware(m_file)); +} + +void FirmwareUpdateOperation::exitRecoveryMode() +{ + registerOperation(m_recovery->exitRecoveryMode()); +} diff --git a/backend/flipperzero/toplevel/firmwareupdateoperation.h b/backend/flipperzero/toplevel/firmwareupdateoperation.h new file mode 100644 index 00000000..17a40152 --- /dev/null +++ b/backend/flipperzero/toplevel/firmwareupdateoperation.h @@ -0,0 +1,42 @@ +#pragma once + +#include "abstracttopleveloperation.h" + +class QFile; + +namespace Flipper { +namespace Zero { + +class UtilityInterface; +class RecoveryInterface; + +class FirmwareUpdateOperation : public AbstractTopLevelOperation +{ + Q_OBJECT + + enum OperationState { + StartingRecovery = AbstractOperation::User, + UpdatingFirmware, + ExitingRecovery + }; + +public: + FirmwareUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void nextStateLogic() override; + +private: + void startRecoveryMode(); + void updateFirmware(); + void exitRecoveryMode(); + + RecoveryInterface *m_recovery; + UtilityInterface *m_utility; + QFile *m_file; +}; + +} +} + diff --git a/backend/flipperzero/toplevel/fullrepairoperation.cpp b/backend/flipperzero/toplevel/fullrepairoperation.cpp new file mode 100644 index 00000000..3a7a391e --- /dev/null +++ b/backend/flipperzero/toplevel/fullrepairoperation.cpp @@ -0,0 +1,109 @@ +#include "fullrepairoperation.h" + +#include + +#include "flipperzero/devicestate.h" + +#include "flipperzero/recoveryinterface.h" +#include "flipperzero/recovery/setbootmodeoperation.h" +#include "flipperzero/recovery/firmwaredownloadoperation.h" +#include "flipperzero/recovery/correctoptionbytesoperation.h" +#include "flipperzero/recovery/wirelessstackdownloadoperation.h" + +#include "flipperzero/utilityinterface.h" +#include "flipperzero/utility/assetsdownloadoperation.h" + +#include "flipperzero/helper/firmwarehelper.h" + +using namespace Flipper; +using namespace Zero; + +FullRepairOperation::FullRepairOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const Updates::VersionInfo &versionInfo, QObject *parent): + AbstractTopLevelOperation(state, parent), + m_recovery(recovery), + m_utility(utility), + m_versionInfo(versionInfo) +{} + +const QString FullRepairOperation::description() const +{ + return QStringLiteral("Full Restore @%1").arg(deviceState()->name()); +} + +void FullRepairOperation::nextStateLogic() +{ + if(operationState() == FullRepairOperation::Ready) { + setOperationState(FullRepairOperation::FetchingFirmware); + fetchFirmware(); + + } else if(operationState() == FullRepairOperation::FetchingFirmware) { + setOperationState(FullRepairOperation::SettingBootMode); + setBootMode(); + + } else if(operationState() == FullRepairOperation::SettingBootMode) { + setOperationState(FullRepairOperation::DownloadingRadioFirmware); + downloadRadioFirmware(); + + } else if(operationState() == FullRepairOperation::DownloadingRadioFirmware) { + setOperationState(FullRepairOperation::DownloadingFirmware); + downloadFirmware(); + + } else if(operationState() == FullRepairOperation::DownloadingFirmware) { + setOperationState(FullRepairOperation::CorrectingOptionBytes); + correctOptionBytes(); + + } else if(operationState() == FullRepairOperation::CorrectingOptionBytes) { + setOperationState(FullRepairOperation::DownloadingAssets); + downloadAssets(); + + } else if(operationState() == FullRepairOperation::DownloadingAssets) { + finish(); + } +} + +void FullRepairOperation::fetchFirmware() +{ + m_helper = new FirmwareHelper(deviceState(), m_versionInfo, this); + + connect(m_helper, &AbstractOperationHelper::finished, this, [=]() { + if(m_helper->isError()) { + finishWithError(QStringLiteral("Failed to fetch the files: %1").arg(m_helper->errorString())); + } else { + advanceOperationState(); + } + }); +} + +void FullRepairOperation::setBootMode() +{ + registerOperation(m_recovery->setRecoveryBootMode()); +} + +void FullRepairOperation::downloadRadioFirmware() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::RadioFirmware); + registerOperation(m_recovery->downloadWirelessStack(file)); +} + +void FullRepairOperation::downloadFirmware() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::Firmware); + registerOperation(m_recovery->downloadFirmware(file)); +} + +void FullRepairOperation::correctOptionBytes() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::OptionBytes); + registerOperation(m_recovery->fixOptionBytes(file)); +} + +void FullRepairOperation::downloadAssets() +{ + if(deviceState()->isRecoveryMode()) { + advanceOperationState(); + return; + } + + auto *file = m_helper->file(FirmwareHelper::FileIndex::AssetsTgz); + registerOperation(m_utility->downloadAssets(file)); +} diff --git a/backend/flipperzero/toplevel/fullrepairoperation.h b/backend/flipperzero/toplevel/fullrepairoperation.h new file mode 100644 index 00000000..10e8e788 --- /dev/null +++ b/backend/flipperzero/toplevel/fullrepairoperation.h @@ -0,0 +1,50 @@ +#pragma once + +#include "abstracttopleveloperation.h" +#include "flipperupdates.h" + +namespace Flipper { +namespace Zero { + +class FirmwareHelper; +class UtilityInterface; +class RecoveryInterface; + +class FullRepairOperation : public AbstractTopLevelOperation +{ + Q_OBJECT + + enum OperationState { + FetchingFirmware = AbstractOperation::User, + SettingBootMode, + DownloadingRadioFirmware, + DownloadingFirmware, + CorrectingOptionBytes, + DownloadingAssets + }; + +public: + FullRepairOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const Updates::VersionInfo &versionInfo, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void nextStateLogic() override; + +private: + void fetchFirmware(); + void setBootMode(); + void downloadRadioFirmware(); + void downloadFirmware(); + void correctOptionBytes(); + void downloadAssets(); + + RecoveryInterface *m_recovery; + UtilityInterface *m_utility; + FirmwareHelper *m_helper; + + Updates::VersionInfo m_versionInfo; +}; + +} +} + diff --git a/backend/flipperzero/toplevel/fullupdateoperation.cpp b/backend/flipperzero/toplevel/fullupdateoperation.cpp new file mode 100644 index 00000000..726f8e71 --- /dev/null +++ b/backend/flipperzero/toplevel/fullupdateoperation.cpp @@ -0,0 +1,168 @@ +#include "fullupdateoperation.h" + +#include +#include + +#include "flipperzero/devicestate.h" + +#include "flipperzero/utilityinterface.h" +#include "flipperzero/utility/restartoperation.h" +#include "flipperzero/utility/userbackupoperation.h" +#include "flipperzero/utility/userrestoreoperation.h" +#include "flipperzero/utility/startrecoveryoperation.h" +#include "flipperzero/utility/assetsdownloadoperation.h" + +#include "flipperzero/recoveryinterface.h" +#include "flipperzero/recovery/setbootmodeoperation.h" +#include "flipperzero/recovery/exitrecoveryoperation.h" +#include "flipperzero/recovery/correctoptionbytesoperation.h" +#include "flipperzero/recovery/firmwaredownloadoperation.h" +#include "flipperzero/recovery/wirelessstackdownloadoperation.h" + +#include "flipperzero/helper/firmwarehelper.h" + +#include "tempdirectories.h" + +using namespace Flipper; +using namespace Zero; + +FullUpdateOperation::FullUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const Updates::VersionInfo &versionInfo, QObject *parent): + AbstractTopLevelOperation(state, parent), + m_recovery(recovery), + m_utility(utility), + m_versionInfo(versionInfo) +{} + +const QString FullUpdateOperation::description() const +{ + return QStringLiteral("Full Update @%1").arg(deviceState()->name()); +} + +void FullUpdateOperation::nextStateLogic() +{ + if(operationState() == FullUpdateOperation::Ready) { + setOperationState(FullUpdateOperation::FetchingFirmware); + fetchFirmware(); + + } else if(operationState() == FullUpdateOperation::FetchingFirmware) { + setOperationState(FullUpdateOperation::SavingBackup); + saveBackup(); + + } else if(operationState() == FullUpdateOperation::SavingBackup) { + setOperationState(FullUpdateOperation::StartingRecovery); + startRecovery(); + + } else if(operationState() == FullUpdateOperation::StartingRecovery) { + + if(m_helper->hasRadioUpdate()) { + setOperationState(FullUpdateOperation::SettingBootMode); + setBootMode(); + + } else{ + setOperationState(FullUpdateOperation::DownloadingRadioFirmware); + advanceOperationState(); + } + + } else if(operationState() == FullUpdateOperation::SettingBootMode) { + setOperationState(FullUpdateOperation::DownloadingRadioFirmware); + downloadRadioFirmware(); + + } else if(operationState() == FullUpdateOperation::DownloadingRadioFirmware) { + setOperationState(FullUpdateOperation::DownloadingFirmware); + downloadFirmware(); + + } else if(operationState() == FullUpdateOperation::DownloadingFirmware) { + + if(m_helper->hasRadioUpdate()) { + setOperationState(FullUpdateOperation::CorrectingOptionBytes); + correctOptionBytes(); + + } else { + setOperationState(FullUpdateOperation::ExitingRecovery); + exitRecovery(); + } + + } else if((operationState() == FullUpdateOperation::ExitingRecovery) || + (operationState() == FullUpdateOperation::CorrectingOptionBytes)) { + setOperationState(FullUpdateOperation::DownloadingAssets); + downloadAssets(); + + } else if(operationState() == FullUpdateOperation::DownloadingAssets) { + setOperationState(FullUpdateOperation::RestoringBackup); + restoreBackup(); + + } else if(operationState() == FullUpdateOperation::RestoringBackup) { + setOperationState(FullUpdateOperation::RestartingDevice); + restartDevice(); + + } else if(operationState() == FullUpdateOperation::RestartingDevice) { + finish(); + } +} + +void FullUpdateOperation::fetchFirmware() +{ + m_helper = new FirmwareHelper(deviceState(), m_versionInfo, this); + + connect(m_helper, &AbstractOperationHelper::finished, this, [=]() { + if(m_helper->isError()) { + finishWithError(QStringLiteral("Failed to fetch the files: %1").arg(m_helper->errorString())); + } else { + advanceOperationState(); + } + }); +} + +void FullUpdateOperation::saveBackup() +{ + registerOperation(m_utility->backupInternalStorage(tempDirs()->root().absolutePath())); +} + +void FullUpdateOperation::startRecovery() +{ + registerOperation(m_utility->startRecoveryMode()); +} + +void FullUpdateOperation::setBootMode() +{ + registerOperation(m_recovery->setRecoveryBootMode()); +} + +void FullUpdateOperation::downloadFirmware() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::Firmware); + registerOperation(m_recovery->downloadFirmware(file)); +} + +void FullUpdateOperation::downloadRadioFirmware() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::RadioFirmware); + registerOperation(m_recovery->downloadWirelessStack(file)); +} + +void FullUpdateOperation::correctOptionBytes() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::OptionBytes); + registerOperation(m_recovery->fixOptionBytes(file)); +} + +void FullUpdateOperation::exitRecovery() +{ + registerOperation(m_recovery->exitRecoveryMode()); +} + +void FullUpdateOperation::downloadAssets() +{ + auto *file = m_helper->file(FirmwareHelper::FileIndex::AssetsTgz); + registerOperation(m_utility->downloadAssets(file)); +} + +void FullUpdateOperation::restoreBackup() +{ + registerOperation(m_utility->restoreInternalStorage(tempDirs()->root().absolutePath())); +} + +void FullUpdateOperation::restartDevice() +{ + registerOperation(m_utility->restartDevice()); +} diff --git a/backend/flipperzero/toplevel/fullupdateoperation.h b/backend/flipperzero/toplevel/fullupdateoperation.h new file mode 100644 index 00000000..b482bdb1 --- /dev/null +++ b/backend/flipperzero/toplevel/fullupdateoperation.h @@ -0,0 +1,64 @@ +#pragma once + +#include "abstracttopleveloperation.h" + +#include +#include + +#include "flipperupdates.h" + +namespace Flipper { +namespace Zero { + +class FirmwareHelper; +class UtilityInterface; +class RecoveryInterface; + +class FullUpdateOperation : public AbstractTopLevelOperation +{ + Q_OBJECT + + enum OperationState { + FetchingFirmware = AbstractOperation::User, + SavingBackup, + StartingRecovery, + SettingBootMode, + DownloadingRadioFirmware, + DownloadingFirmware, + CorrectingOptionBytes, + ExitingRecovery, + DownloadingAssets, + RestoringBackup, + RestartingDevice + }; + +public: + FullUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const Updates::VersionInfo &versionInfo, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void nextStateLogic() override; + +private: + void fetchFirmware(); + void saveBackup(); + void startRecovery(); + void setBootMode(); + void downloadRadioFirmware(); + void downloadFirmware(); + void correctOptionBytes(); + void exitRecovery(); + void downloadAssets(); + void restoreBackup(); + void restartDevice(); + + RecoveryInterface *m_recovery; + UtilityInterface *m_utility; + FirmwareHelper *m_helper; + + Updates::VersionInfo m_versionInfo; +}; + +} +} + diff --git a/backend/flipperzero/toplevel/wirelessstackupdateoperation.cpp b/backend/flipperzero/toplevel/wirelessstackupdateoperation.cpp new file mode 100644 index 00000000..76192fd9 --- /dev/null +++ b/backend/flipperzero/toplevel/wirelessstackupdateoperation.cpp @@ -0,0 +1,89 @@ +#include "wirelessstackupdateoperation.h" + +#include + +#include "flipperzero/devicestate.h" + +#include "flipperzero/utilityinterface.h" +#include "flipperzero/utility/startrecoveryoperation.h" + +#include "flipperzero/recoveryinterface.h" +#include "flipperzero/recovery/setbootmodeoperation.h" +#include "flipperzero/recovery/wirelessstackdownloadoperation.h" + +using namespace Flipper; +using namespace Zero; + +AbstractCore2UpdateOperation::AbstractCore2UpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent): + AbstractTopLevelOperation(state, parent), + m_recovery(recovery), + m_utility(utility), + m_file(new QFile(filePath, this)) +{} + +void AbstractCore2UpdateOperation::nextStateLogic() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(AbstractCore2UpdateOperation::StartingRecovery); + startRecoveryMode(); + + } else if(operationState() == AbstractCore2UpdateOperation::StartingRecovery) { + setOperationState(AbstractCore2UpdateOperation::SettingRecoveryBootMode); + setRecoveryBootMode(); + + } else if(operationState() == AbstractCore2UpdateOperation::SettingRecoveryBootMode) { + setOperationState(AbstractCore2UpdateOperation::UpdatingWirelessStack); + updateCore2Firmware(); + + } else if(operationState() == AbstractCore2UpdateOperation::UpdatingWirelessStack) { + setOperationState(AbstractCore2UpdateOperation::SettingOSBootMode); + setOSBootMode(); + + } else if(operationState() == AbstractCore2UpdateOperation::SettingOSBootMode) { + finish(); + } +} + +void AbstractCore2UpdateOperation::startRecoveryMode() +{ + registerOperation(m_utility->startRecoveryMode()); +} + +void AbstractCore2UpdateOperation::setRecoveryBootMode() +{ + registerOperation(m_recovery->setRecoveryBootMode()); +} + +void AbstractCore2UpdateOperation::setOSBootMode() +{ + registerOperation(m_recovery->setOSBootMode()); +} + +WirelessStackUpdateOperation::WirelessStackUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent): + AbstractCore2UpdateOperation(recovery, utility, state, filePath, parent) +{} + +const QString WirelessStackUpdateOperation::description() const +{ + return QStringLiteral("Wireless Stack Update @%1").arg(deviceState()->name()); +} + +void WirelessStackUpdateOperation::updateCore2Firmware() +{ + registerOperation(m_recovery->downloadWirelessStack(m_file)); +} + +FUSUpdateOperation::FUSUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, uint32_t address, QObject *parent): + AbstractCore2UpdateOperation(recovery, utility, state, filePath, parent), + m_address(address) +{} + +const QString FUSUpdateOperation::description() const +{ + return QStringLiteral("FUS Update @%1").arg(deviceState()->name()); +} + +void FUSUpdateOperation::updateCore2Firmware() +{ + registerOperation(m_recovery->downloadFUS(m_file, m_address)); +} diff --git a/backend/flipperzero/toplevel/wirelessstackupdateoperation.h b/backend/flipperzero/toplevel/wirelessstackupdateoperation.h new file mode 100644 index 00000000..2f326add --- /dev/null +++ b/backend/flipperzero/toplevel/wirelessstackupdateoperation.h @@ -0,0 +1,70 @@ +#pragma once + +#include "abstracttopleveloperation.h" + +class QFile; + +namespace Flipper { +namespace Zero { + +class UtilityInterface; +class RecoveryInterface; + +class AbstractCore2UpdateOperation : public AbstractTopLevelOperation +{ + Q_OBJECT + + enum OperationState { + StartingRecovery = AbstractOperation::User, + SettingRecoveryBootMode, + UpdatingWirelessStack, + SettingOSBootMode + }; + +public: + AbstractCore2UpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent = nullptr); + +private slots: + void nextStateLogic() override; + +private: + virtual void updateCore2Firmware() = 0; + + void startRecoveryMode(); + void setRecoveryBootMode(); + void setOSBootMode(); + +protected: + RecoveryInterface *m_recovery; + UtilityInterface *m_utility; + QFile *m_file; +}; + +class WirelessStackUpdateOperation : public AbstractCore2UpdateOperation +{ + Q_OBJECT + +public: + WirelessStackUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, QObject *parent = nullptr); + const QString description() const override; + +private: + void updateCore2Firmware() override; +}; + +class FUSUpdateOperation : public AbstractCore2UpdateOperation +{ + Q_OBJECT + +public: + FUSUpdateOperation(RecoveryInterface *recovery, UtilityInterface *utility, DeviceState *state, const QString &filePath, uint32_t address, QObject *parent = nullptr); + const QString description() const override; + +private: + void updateCore2Firmware() override; + uint32_t m_address; +}; + +} +} + diff --git a/backend/flipperzero/utility/abstractutilityoperation.cpp b/backend/flipperzero/utility/abstractutilityoperation.cpp new file mode 100644 index 00000000..63519a47 --- /dev/null +++ b/backend/flipperzero/utility/abstractutilityoperation.cpp @@ -0,0 +1,36 @@ +#include "abstractutilityoperation.h" + +#include + +#include "flipperzero/recovery.h" +#include "flipperzero/devicestate.h" + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +AbstractUtilityOperation::AbstractUtilityOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent): + AbstractOperation(parent), + m_cli(cli), + m_deviceState(deviceState) +{} + +void AbstractUtilityOperation::start() +{ + if(operationState() != AbstractOperation::Ready) { + finishWithError(QStringLiteral("Trying to start an operation that is either already running or has finished.")); + } else { + CALL_LATER(this, &AbstractUtilityOperation::advanceOperationState); + } +} + +CommandInterface *AbstractUtilityOperation::cli() const +{ + return m_cli; +} + +DeviceState *AbstractUtilityOperation::deviceState() const +{ + return m_deviceState; +} diff --git a/backend/flipperzero/utility/abstractutilityoperation.h b/backend/flipperzero/utility/abstractutilityoperation.h new file mode 100644 index 00000000..46142aa0 --- /dev/null +++ b/backend/flipperzero/utility/abstractutilityoperation.h @@ -0,0 +1,34 @@ +#pragma once + +#include "abstractoperation.h" + +namespace Flipper { +namespace Zero { + +class DeviceState; +class CommandInterface; + +class AbstractUtilityOperation : public AbstractOperation +{ + Q_OBJECT + +public: + AbstractUtilityOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent = nullptr); + virtual ~AbstractUtilityOperation() {} + + void start() override; + + CommandInterface *cli() const; + DeviceState *deviceState() const; + +private slots: + virtual void advanceOperationState() = 0; + +private: + CommandInterface *m_cli; + DeviceState *m_deviceState; +}; + +} +} + diff --git a/backend/flipperzero/utility/assetsdownloadoperation.cpp b/backend/flipperzero/utility/assetsdownloadoperation.cpp new file mode 100644 index 00000000..ab3378b0 --- /dev/null +++ b/backend/flipperzero/utility/assetsdownloadoperation.cpp @@ -0,0 +1,333 @@ +#include "assetsdownloadoperation.h" + +#include +#include +#include +#include + +#include "flipperzero/cli/removeoperation.h" +#include "flipperzero/cli/mkdiroperation.h" +#include "flipperzero/cli/writeoperation.h" +#include "flipperzero/cli/readoperation.h" +#include "flipperzero/cli/statoperation.h" + +#include "flipperzero/commandinterface.h" +#include "flipperzero/assetmanifest.h" +#include "flipperzero/devicestate.h" + +#include "gzipuncompressor.h" +#include "tempdirectories.h" +#include "tararchive.h" + +#include "macros.h" + +#define RESOURCES_PREFIX QByteArrayLiteral("resources") +#define DEVICE_MANIFEST QByteArrayLiteral("/ext/Manifest") + +using namespace Flipper; +using namespace Zero; + +static void print_file_list(const QString &header, const FileNode::FileInfoList &list) +{ + qDebug() << header; + + for(const auto &e : list) { + const auto icon = QStringLiteral("[%1]").arg(e.type == FileNode::Type::RegularFile ? 'F' : + e.type == FileNode::Type::Directory ? 'D' : '?'); + qDebug() << icon << e.absolutePath; + } +} + +AssetsDownloadOperation::AssetsDownloadOperation(CommandInterface *cli, DeviceState *deviceState, QIODevice *compressedFile, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent), + m_compressedFile(compressedFile), + m_uncompressedFile(new QFile(tempDirs()->root().absoluteFilePath(QStringLiteral("qFlipper-databases.tar")), this)), + m_isDeviceManifestPresent(false) +{} + +AssetsDownloadOperation::~AssetsDownloadOperation() +{} + +const QString AssetsDownloadOperation::description() const +{ + return QStringLiteral("Assets Download @%1").arg(deviceState()->name()); +} + +void AssetsDownloadOperation::advanceOperationState() +{ + if(operationState() == BasicOperationState::Ready) { + setOperationState(State::CheckingExtStorage); + checkForExtStorage(); + + } else if(operationState() == State::CheckingExtStorage) { + setOperationState(State::ExtractingArchive); + extractArchive(); + + } else if(operationState() == State::ExtractingArchive) { + setOperationState(ReadingLocalManifest); + readLocalManifest(); + + } else if(operationState() == State::ReadingLocalManifest) { + setOperationState(CheckingDeviceManifest); + checkForDeviceManifest(); + + } else if(operationState() == State::CheckingDeviceManifest) { + setOperationState(ReadingDeviceManifest); + readDeviceManifest(); + + } else if(operationState() == State::ReadingDeviceManifest) { + setOperationState(State::BuildingFileLists); + buildFileLists(); + + } else if(operationState() == State::BuildingFileLists) { + setOperationState(State::DeletingFiles); + deleteFiles(); + + } else if(operationState() == State::DeletingFiles) { + setOperationState(State::WritingFiles); + writeFiles(); + + } else if(operationState() == State::WritingFiles) { + cleanup(); + finish(); + } +} + +void AssetsDownloadOperation::checkForExtStorage() +{ + auto *op = cli()->stat(QByteArrayLiteral("/ext")); + + connect(op, &AbstractOperation::finished, this, [=]() { + if(op->isError()) { + finishWithError("Failed to perform stat operation"); + } else if(op->type() == StatOperation::Type::InternalError) { + info_msg("No external storage found, finishing early."); + finish(); + + } else if(op->type() != StatOperation::Type::Storage) { + finishWithError("/ext is not a storage"); + + } else { + info_msg(QStringLiteral("External storage is present, %1 bytes free.").arg(op->sizeFree())); + advanceOperationState(); + } + }); +} + +void AssetsDownloadOperation::extractArchive() +{ + auto *uncompressor = new GZipUncompressor(m_compressedFile, m_uncompressedFile, this); + + if(uncompressor->isError()) { + finishWithError(uncompressor->errorString()); + return; + } + + connect(uncompressor, &GZipUncompressor::finished, this, [=]() { + if(uncompressor->isError()) { + finishWithError(uncompressor->errorString()); + } else { + m_archive = std::move(TarArchive(m_uncompressedFile)); + + if(m_archive.isError()) { + finishWithError(m_archive.errorString()); + } else { + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + } + } + + uncompressor->deleteLater(); + }); +} + +void AssetsDownloadOperation::readLocalManifest() +{ + const auto text = m_archive.fileData(QStringLiteral("resources/Manifest")); + + if(text.isEmpty()) { + return finishWithError(m_archive.errorString()); + } + + m_localManifest = AssetManifest(text); + if(m_localManifest.isError()) { + return finishWithError(m_localManifest.errorString()); + } + + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); +} + +void AssetsDownloadOperation::checkForDeviceManifest() +{ + auto *operation = cli()->stat(DEVICE_MANIFEST); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + return finishWithError(operation->errorString()); + } else if(operation->type() != StatOperation::Type::RegularFile) { + setOperationState(State::ReadingDeviceManifest); + } else { + m_isDeviceManifestPresent = true; + } + + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + }); +} + +void AssetsDownloadOperation::readDeviceManifest() +{ + auto *buf = new QBuffer(this); + + if(!buf->open(QIODevice::ReadWrite)) { + return finishWithError(buf->errorString()); + } + + auto *operation = cli()->read(QByteArrayLiteral("/ext/Manifest"), buf); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(!operation->isError()) { + m_deviceManifest = AssetManifest(buf->readAll()); + } + + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + }); +} + +void AssetsDownloadOperation::buildFileLists() +{ + FileNode::FileInfoList deleted, added, changed; + + FileNode::FileInfo manifestInfo; + manifestInfo.name = QStringLiteral("Manifest"); + manifestInfo.absolutePath = manifestInfo.name; + manifestInfo.type = FileNode::Type::RegularFile; + + print_file_list("<<<<< Local manifest:", m_localManifest.tree()->toPreOrderList()); + + if(!m_isDeviceManifestPresent || m_deviceManifest.isError()) { + info_msg("Device manifest not present or corrupt, assumimg fresh install..."); + + changed.append(manifestInfo); + added.append(m_localManifest.tree()->toPreOrderList().mid(1)); + + std::copy_if(added.cbegin(), added.cend(), std::back_inserter(deleted), [](const FileNode::FileInfo &arg) { + return arg.type == FileNode::Type::RegularFile; + }); + + } else { + print_file_list(">>>>> Device manifest:", m_deviceManifest.tree()->toPreOrderList()); + + deleted.append(m_localManifest.tree()->difference(m_deviceManifest.tree())); + added.append(m_deviceManifest.tree()->difference(m_localManifest.tree())); + changed.append(m_deviceManifest.tree()->changed(m_localManifest.tree())); + + deleted.erase(std::remove_if(deleted.begin(), deleted.end(), [](const FileNode::FileInfo &arg) { + return arg.type != FileNode::Type::RegularFile; + }), deleted.end()); + + if(!deleted.isEmpty() || !added.isEmpty() || !changed.isEmpty()) { + changed.prepend(manifestInfo); + } + } + + if(!deleted.isEmpty()) { + print_file_list("----- Files deleted:", deleted); + } + + if(!added.isEmpty()) { + print_file_list("+++++ Files added:", added); + } + + if(!changed.isEmpty()) { + print_file_list("***** Files changed:", changed); + } + + m_deleteList.append(deleted); + m_deleteList.append(changed); + + m_writeList.append(added); + m_writeList.append(changed); + + // Start deleting by the farthest nodes + std::reverse(m_deleteList.begin(), m_deleteList.end()); + + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); +} + +void AssetsDownloadOperation::deleteFiles() +{ + if(m_deleteList.isEmpty()) { + info_msg("No files to delete, skipping to write"); + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + } + + deviceState()->setStatusString(tr("Deleting unneeded files...")); + + int numFiles = m_deleteList.size(); + + for(const auto &fileInfo : qAsConst(m_deleteList)) { + const auto isLastFile = (--numFiles == 0); + const auto fileName = QByteArrayLiteral("/ext/") + fileInfo.absolutePath.toLocal8Bit(); + + auto *operation = cli()->remove(fileName); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + finishWithError(operation->errorString()); + } else if(isLastFile) { + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + } + }); + } +} + +void AssetsDownloadOperation::writeFiles() +{ + if(m_writeList.isEmpty()) { + info_msg("No files to write, skipping to the end"); + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + } + + deviceState()->setStatusString(tr("Writing new files...")); + + int i = m_writeList.size(); + + for(const auto &fileInfo : qAsConst(m_writeList)) { + --i; + + AbstractOperation *op; + const auto filePath = QByteArrayLiteral("/ext/") + fileInfo.absolutePath.toLocal8Bit(); + + if(fileInfo.type == FileNode::Type::Directory) { + op = cli()->mkdir(filePath); + + } else if(fileInfo.type == FileNode::Type::RegularFile) { + auto *buf = new QBuffer(this); + if(!buf->open(QIODevice::ReadWrite)) { + return finishWithError(buf->errorString()); + } + + const auto resourcePath = QStringLiteral("resources/") + fileInfo.absolutePath; + if((buf->write(m_archive.fileData(resourcePath)) <= 0) || (!buf->seek(0))) { + return finishWithError(buf->errorString()); + } + + op = cli()->write(filePath, buf); + + } else { + return finishWithError(QStringLiteral("Unexpected file type")); + } + + connect(op, &AbstractOperation::finished, this, [=]() { + if(op->isError()) { + finishWithError(op->errorString()); + } else if(i == 0) { + QTimer::singleShot(0, this, &AssetsDownloadOperation::advanceOperationState); + } + }); + } +} + +void AssetsDownloadOperation::cleanup() +{ + m_uncompressedFile->remove(); +} diff --git a/backend/flipperzero/utility/assetsdownloadoperation.h b/backend/flipperzero/utility/assetsdownloadoperation.h new file mode 100644 index 00000000..d4afecc5 --- /dev/null +++ b/backend/flipperzero/utility/assetsdownloadoperation.h @@ -0,0 +1,63 @@ +#pragma once + +#include "tararchive.h" +#include "abstractutilityoperation.h" +#include "flipperzero/assetmanifest.h" + +class QIODevice; +class QFile; + +namespace Flipper { +namespace Zero { + +class AssetsDownloadOperation : public AbstractUtilityOperation +{ + Q_OBJECT + +public: + enum State { + CheckingExtStorage = AbstractOperation::User, + ExtractingArchive, + ReadingLocalManifest, + CheckingDeviceManifest, + ReadingDeviceManifest, + BuildingFileLists, + DeletingFiles, + WritingFiles + }; + + AssetsDownloadOperation(CommandInterface *cli, DeviceState *deviceState, QIODevice *compressedFile, QObject *parent = nullptr); + ~AssetsDownloadOperation(); + + const QString description() const override; + +private slots: + void advanceOperationState() override; + +private: + void checkForExtStorage(); + void extractArchive(); + void readLocalManifest(); + void checkForDeviceManifest(); + void readDeviceManifest(); + void buildFileLists(); + void deleteFiles(); + void writeFiles(); + void cleanup(); + + QIODevice *m_compressedFile; + QFile *m_uncompressedFile; + + TarArchive m_archive; + + AssetManifest m_localManifest; + AssetManifest m_deviceManifest; + bool m_isDeviceManifestPresent; + + FileNode::FileInfoList m_deleteList; + FileNode::FileInfoList m_writeList; +}; + +} +} + diff --git a/backend/flipperzero/operations/getfiletreeoperation.cpp b/backend/flipperzero/utility/getfiletreeoperation.cpp similarity index 60% rename from backend/flipperzero/operations/getfiletreeoperation.cpp rename to backend/flipperzero/utility/getfiletreeoperation.cpp index ba0c7b3b..7bf69461 100644 --- a/backend/flipperzero/operations/getfiletreeoperation.cpp +++ b/backend/flipperzero/utility/getfiletreeoperation.cpp @@ -2,24 +2,24 @@ #include -#include "flipperzero/flipperzero.h" -#include "flipperzero/storagecontroller.h" -#include "flipperzero/storage/listoperation.h" +#include "flipperzero/devicestate.h" +#include "flipperzero/commandinterface.h" +#include "flipperzero/cli/listoperation.h" #include "macros.h" using namespace Flipper; using namespace Zero; -GetFileTreeOperation::GetFileTreeOperation(FlipperZero *device, const QByteArray &rootPath, QObject *parent): - Operation(device, parent), +GetFileTreeOperation::GetFileTreeOperation(CommandInterface *cli, DeviceState *deviceState, const QByteArray &rootPath, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent), m_rootPath(rootPath), m_pendingCount(0) {} const QString GetFileTreeOperation::description() const { - return QStringLiteral("Get File Tree @%1").arg(QString(m_rootPath)); + return QStringLiteral("Get File Tree @%1").arg(deviceState()->name()); } const FileInfoList &GetFileTreeOperation::result() const @@ -27,13 +27,13 @@ const FileInfoList &GetFileTreeOperation::result() const return m_result; } -void GetFileTreeOperation::transitionToNextState() +void GetFileTreeOperation::advanceOperationState() { - if(state() == BasicState::Ready) { - setState(State::Running); + if(operationState() == BasicOperationState::Ready) { + setOperationState(State::Running); listDirectory(m_rootPath); - } else if(state() == State::Running) { + } else if(operationState() == State::Running) { --m_pendingCount; auto *op = qobject_cast(sender()); @@ -50,8 +50,6 @@ void GetFileTreeOperation::transitionToNextState() m_result.push_back(fileInfo); } - op->deleteLater(); - if(!m_pendingCount) { finish(); } @@ -61,6 +59,6 @@ void GetFileTreeOperation::transitionToNextState() void GetFileTreeOperation::listDirectory(const QByteArray &path) { ++m_pendingCount; - auto *op = device()->storage()->list(path); - connect(op, &AbstractOperation::finished, this, &GetFileTreeOperation::transitionToNextState); + auto *op = cli()->list(path); + connect(op, &AbstractOperation::finished, this, &GetFileTreeOperation::advanceOperationState); } diff --git a/backend/flipperzero/operations/getfiletreeoperation.h b/backend/flipperzero/utility/getfiletreeoperation.h similarity index 57% rename from backend/flipperzero/operations/getfiletreeoperation.h rename to backend/flipperzero/utility/getfiletreeoperation.h index f9d0cf0d..d78d078e 100644 --- a/backend/flipperzero/operations/getfiletreeoperation.h +++ b/backend/flipperzero/utility/getfiletreeoperation.h @@ -1,6 +1,6 @@ #pragma once -#include "flipperzerooperation.h" +#include "abstractutilityoperation.h" #include "fileinfo.h" class QSerialPort; @@ -8,21 +8,21 @@ class QSerialPort; namespace Flipper { namespace Zero { -class GetFileTreeOperation : public Operation +class GetFileTreeOperation : public AbstractUtilityOperation { Q_OBJECT enum State { - Running = BasicState::User + Running = AbstractOperation::User }; public: - GetFileTreeOperation(FlipperZero *device, const QByteArray &rootPath, QObject *parent = nullptr); + GetFileTreeOperation(CommandInterface *cli, DeviceState *deviceState, const QByteArray &rootPath, QObject *parent = nullptr); const QString description() const override; const FileInfoList &result() const; private slots: - void transitionToNextState() override; + void advanceOperationState() override; private: void listDirectory(const QByteArray &path); diff --git a/backend/flipperzero/utility/restartoperation.cpp b/backend/flipperzero/utility/restartoperation.cpp new file mode 100644 index 00000000..08345bd4 --- /dev/null +++ b/backend/flipperzero/utility/restartoperation.cpp @@ -0,0 +1,63 @@ +#include "restartoperation.h" + +#include + +#include "flipperzero/devicestate.h" +#include "flipperzero/commandinterface.h" +#include "flipperzero/cli/rebootoperation.h" + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +RestartOperation::RestartOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent) +{} + +const QString RestartOperation::description() const +{ + return QStringLiteral("Restart device @%1").arg(deviceState()->name()); +} + +void RestartOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(RestartOperation::WaitingForOSBoot); + rebootDevice(); + + } else if(operationState() == RestartOperation::WaitingForOSBoot) { + disconnect(deviceState(), &DeviceState::isOnlineChanged, this, &RestartOperation::onDeviceOnlineChanged); + finish(); + + } else {} +} + +void RestartOperation::onOperationTimeout() +{ + finishWithError(QStringLiteral("Failed to restart: timeout exceeded")); +} + +void RestartOperation::onDeviceOnlineChanged() +{ + if(deviceState()->isOnline()) { + CALL_LATER(this, &RestartOperation::advanceOperationState); + } else { + startTimeout(); + } +} + +void RestartOperation::rebootDevice() +{ + deviceState()->setStatusString(QStringLiteral("Restarting device...")); + + connect(deviceState(), &DeviceState::isOnlineChanged, this, &RestartOperation::onDeviceOnlineChanged); + + auto *operation = cli()->reboot(); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + finishWithError(operation->errorString()); + } + }); +} diff --git a/backend/flipperzero/utility/restartoperation.h b/backend/flipperzero/utility/restartoperation.h new file mode 100644 index 00000000..9cd1937a --- /dev/null +++ b/backend/flipperzero/utility/restartoperation.h @@ -0,0 +1,31 @@ +#pragma once + +#include "abstractutilityoperation.h" + +namespace Flipper { +namespace Zero { + +class RestartOperation : public AbstractUtilityOperation +{ + Q_OBJECT + + enum OperationState { + WaitingForOSBoot = AbstractOperation::User + }; + +public: + RestartOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + void onOperationTimeout() override; + void onDeviceOnlineChanged(); + +private: + void rebootDevice(); +}; + +} +} + diff --git a/backend/flipperzero/utility/startrecoveryoperation.cpp b/backend/flipperzero/utility/startrecoveryoperation.cpp new file mode 100644 index 00000000..b8bdd3f7 --- /dev/null +++ b/backend/flipperzero/utility/startrecoveryoperation.cpp @@ -0,0 +1,63 @@ +#include "startrecoveryoperation.h" + +#include + +#include "flipperzero/devicestate.h" +#include "flipperzero/commandinterface.h" +#include "flipperzero/cli/dfuoperation.h" + +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + +using namespace Flipper; +using namespace Zero; + +StartRecoveryOperation::StartRecoveryOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent) +{} + +const QString StartRecoveryOperation::description() const +{ + return QStringLiteral("Start Recovery Mode @%1").arg(deviceState()->name()); +} + +void StartRecoveryOperation::advanceOperationState() +{ + if(operationState() == AbstractOperation::Ready) { + setOperationState(StartRecoveryOperation::WaitingForRecovery); + startRecoveryMode(); + + } else if(operationState() == StartRecoveryOperation::WaitingForRecovery) { + disconnect(deviceState(), &DeviceState::isOnlineChanged, this, &StartRecoveryOperation::onDeviceOnlineChanged); + finish(); + + } else {} +} + +void StartRecoveryOperation::onDeviceOnlineChanged() +{ + if(deviceState()->isOnline()) { + CALL_LATER(this, &StartRecoveryOperation::advanceOperationState); + } else { + startTimeout(); + } +} + +void StartRecoveryOperation::startRecoveryMode() +{ + if(deviceState()->isRecoveryMode()) { + advanceOperationState(); + return; + } + + deviceState()->setStatusString(QStringLiteral("Starting Recovery mode...")); + + connect(deviceState(), &DeviceState::isOnlineChanged, this, &StartRecoveryOperation::onDeviceOnlineChanged); + + auto *operation = cli()->startRecoveryMode(); + + connect(operation, &AbstractOperation::finished, this, [=]() { + if(operation->isError()) { + finishWithError(operation->errorString()); + } + }); +} diff --git a/backend/flipperzero/utility/startrecoveryoperation.h b/backend/flipperzero/utility/startrecoveryoperation.h new file mode 100644 index 00000000..c80abc90 --- /dev/null +++ b/backend/flipperzero/utility/startrecoveryoperation.h @@ -0,0 +1,30 @@ +#pragma once + +#include "abstractutilityoperation.h" + +namespace Flipper { +namespace Zero { + +class StartRecoveryOperation : public AbstractUtilityOperation +{ + Q_OBJECT + + enum OperationState { + WaitingForRecovery = AbstractOperation::User + }; + +public: + StartRecoveryOperation(CommandInterface *cli, DeviceState *deviceState, QObject *parent = nullptr); + const QString description() const override; + +private slots: + void advanceOperationState() override; + void onDeviceOnlineChanged(); + +private: + void startRecoveryMode(); +}; + +} +} + diff --git a/backend/flipperzero/operations/userbackupoperation.cpp b/backend/flipperzero/utility/userbackupoperation.cpp similarity index 65% rename from backend/flipperzero/operations/userbackupoperation.cpp rename to backend/flipperzero/utility/userbackupoperation.cpp index 70f0a70d..4f06e570 100644 --- a/backend/flipperzero/operations/userbackupoperation.cpp +++ b/backend/flipperzero/utility/userbackupoperation.cpp @@ -4,51 +4,55 @@ #include #include -#include "flipperzero/flipperzero.h" -#include "flipperzero/storagecontroller.h" -#include "flipperzero/storage/readoperation.h" +#include "flipperzero/devicestate.h" +#include "flipperzero/commandinterface.h" +#include "flipperzero/cli/readoperation.h" #include "getfiletreeoperation.h" #include "macros.h" +#define CALL_LATER(obj, func) (QTimer::singleShot(0, obj, func)) + using namespace Flipper; using namespace Zero; -UserBackupOperation::UserBackupOperation(FlipperZero *device, const QString &backupPath, QObject *parent): - Operation(device, parent), - m_backupDir(QUrl(backupPath).toLocalFile()), +UserBackupOperation::UserBackupOperation(CommandInterface *cli, DeviceState *deviceState, const QString &backupPath, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent), + m_backupDir(backupPath), m_deviceDirName(QByteArrayLiteral("/int")) {} const QString UserBackupOperation::description() const { - return QStringLiteral("Backup user data @%1 %2").arg(device()->model(), device()->name()); + return QStringLiteral("Backup user data @%1 %2").arg(deviceState()->name()); } -void UserBackupOperation::transitionToNextState() +void UserBackupOperation::advanceOperationState() { - if(state() == BasicState::Ready) { - setState(State::CreatingDirectory); + if(operationState() == BasicOperationState::Ready) { + setOperationState(State::CreatingDirectory); + + deviceState()->setStatusString(QStringLiteral("Backing up internal storage...")); if(!m_deviceDirName.startsWith('/')) { finishWithError(QStringLiteral("Expecting absolute path for device directory")); } else if(!createBackupDirectory()) { finishWithError(QStringLiteral("Failed to create backup directory")); } else { - QTimer::singleShot(0, this, &UserBackupOperation::transitionToNextState); + CALL_LATER(this, &UserBackupOperation::advanceOperationState); } - } else if(state() == State::CreatingDirectory) { - setState(State::GettingFileTree); + } else if(operationState() == State::CreatingDirectory) { + setOperationState(State::GettingFileTree); - auto *op = new GetFileTreeOperation(device(), m_deviceDirName, this); + auto *op = new GetFileTreeOperation(cli(), deviceState(), m_deviceDirName, this); connect(op, &AbstractOperation::finished, this, [=]() { if(op->isError()) { finishWithError(op->errorString()); } else { m_fileList = op->result(); - QTimer::singleShot(0, this, &UserBackupOperation::transitionToNextState); + CALL_LATER(this, &UserBackupOperation::advanceOperationState); } op->deleteLater(); @@ -56,8 +60,8 @@ void UserBackupOperation::transitionToNextState() op->start(); - } else if(state() == State::GettingFileTree) { - setState(State::ReadingFiles); + } else if(operationState() == State::GettingFileTree) { + setOperationState(State::ReadingFiles); if(!readFiles()) { finishWithError(QStringLiteral("Failed to read files from device")); @@ -67,7 +71,7 @@ void UserBackupOperation::transitionToNextState() bool UserBackupOperation::createBackupDirectory() { - const auto &subdir = device()->name(); + const auto &subdir = deviceState()->deviceInfo().name; const QFileInfo targetDirInfo(m_backupDir, subdir); if(targetDirInfo.isDir()) { @@ -107,20 +111,16 @@ bool UserBackupOperation::readFiles() return false; } - auto *op = device()->storage()->read(fileInfo.absolutePath, file); + auto *op = cli()->read(fileInfo.absolutePath, file); connect(op, &AbstractOperation::finished, this, [=]() { if(op->isError()) { finishWithError(op->errorString()); + } else if(isLastFile) { + finish(); } - op->deleteLater(); - file->close(); file->deleteLater(); - - if(isLastFile) { - finish(); - } }); } } diff --git a/backend/flipperzero/operations/userbackupoperation.h b/backend/flipperzero/utility/userbackupoperation.h similarity index 56% rename from backend/flipperzero/operations/userbackupoperation.h rename to backend/flipperzero/utility/userbackupoperation.h index 4cca3fcf..b95a3d98 100644 --- a/backend/flipperzero/operations/userbackupoperation.h +++ b/backend/flipperzero/utility/userbackupoperation.h @@ -1,6 +1,6 @@ #pragma once -#include "flipperzerooperation.h" +#include "abstractutilityoperation.h" #include @@ -9,22 +9,22 @@ namespace Flipper { namespace Zero { -class UserBackupOperation : public Operation +class UserBackupOperation : public AbstractUtilityOperation { Q_OBJECT enum State { - CreatingDirectory = BasicState::User, + CreatingDirectory = AbstractOperation::User, GettingFileTree, ReadingFiles }; public: - UserBackupOperation(FlipperZero *device, const QString &backupPath, QObject *parent = nullptr); + UserBackupOperation(CommandInterface *cli, DeviceState *deviceState, const QString &backupPath, QObject *parent = nullptr); const QString description() const override; private slots: - void transitionToNextState() override; + void advanceOperationState() override; private: bool createBackupDirectory(); diff --git a/backend/flipperzero/operations/userrestoreoperation.cpp b/backend/flipperzero/utility/userrestoreoperation.cpp similarity index 67% rename from backend/flipperzero/operations/userrestoreoperation.cpp rename to backend/flipperzero/utility/userrestoreoperation.cpp index 10f3c164..ff8a45d3 100644 --- a/backend/flipperzero/operations/userrestoreoperation.cpp +++ b/backend/flipperzero/utility/userrestoreoperation.cpp @@ -1,65 +1,65 @@ #include "userrestoreoperation.h" -#include #include #include #include -#include "flipperzero/flipperzero.h" -#include "flipperzero/storagecontroller.h" -#include "flipperzero/storage/mkdiroperation.h" -#include "flipperzero/storage/writeoperation.h" -#include "flipperzero/storage/removeoperation.h" +#include "flipperzero/devicestate.h" +#include "flipperzero/commandinterface.h" +#include "flipperzero/cli/mkdiroperation.h" +#include "flipperzero/cli/writeoperation.h" +#include "flipperzero/cli/removeoperation.h" #include "macros.h" using namespace Flipper; using namespace Zero; -UserRestoreOperation::UserRestoreOperation(FlipperZero *device, const QString &backupPath, QObject *parent): - Operation(device, parent), - m_backupDir(QUrl(backupPath).toLocalFile()), +UserRestoreOperation::UserRestoreOperation(CommandInterface *cli, DeviceState *deviceState, const QString &backupPath, QObject *parent): + AbstractUtilityOperation(cli, deviceState, parent), + m_backupDir(backupPath), m_deviceDirName(QByteArrayLiteral("/int")) { - m_backupDir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + m_backupDir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); m_backupDir.setSorting(QDir::Name | QDir::DirsFirst); } const QString UserRestoreOperation::description() const { - return QStringLiteral("Restore user data @%1 %2").arg(device()->model(), device()->name()); + return QStringLiteral("Restore user data @%1").arg(deviceState()->name()); } -void UserRestoreOperation::transitionToNextState() +void UserRestoreOperation::advanceOperationState() { - if(state() == BasicState::Ready) { - setState(State::ReadingBackupDir); + if(operationState() == BasicOperationState::Ready) { + setOperationState(State::ReadingBackupDir); if(!readBackupDir()) { finishWithError(QStringLiteral("Failed to process backup directory")); } else { - QTimer::singleShot(0, this, &UserRestoreOperation::transitionToNextState); + QTimer::singleShot(0, this, &UserRestoreOperation::advanceOperationState); } - } else if(state() == State::ReadingBackupDir) { - setState(State::DeletingFiles); + } else if(operationState() == State::ReadingBackupDir) { + setOperationState(State::DeletingFiles); if(!deleteFiles()) { finishWithError(QStringLiteral("Failed to delete old files")); } - } else if(state() == State::DeletingFiles) { - setState(State::WritingFiles); + } else if(operationState() == State::DeletingFiles) { + setOperationState(State::WritingFiles); if(!writeFiles()) { finishWithError(QStringLiteral("Failed to write new files")); } - } else if(state() == State::WritingFiles) { + } else if(operationState() == State::WritingFiles) { finish(); } else {} } bool UserRestoreOperation::readBackupDir() { - const auto subdir = device()->name() + m_deviceDirName; + const auto &name = deviceState()->deviceInfo().name; + const auto subdir = name + m_deviceDirName; check_return_bool(m_backupDir.exists(subdir), "Requested directory not found"); check_return_bool(m_backupDir.cd(subdir), "Access denied"); @@ -77,7 +77,7 @@ bool UserRestoreOperation::readBackupDir() bool UserRestoreOperation::deleteFiles() { - device()->setMessage(QStringLiteral("Cleaning up...")); + deviceState()->setStatusString(tr("Cleaning up...")); auto numFiles = m_files.size(); for(auto it = m_files.crbegin(); it != m_files.crend(); ++it) { @@ -86,15 +86,13 @@ bool UserRestoreOperation::deleteFiles() const auto filePath = m_deviceDirName + QByteArrayLiteral("/") + m_backupDir.relativeFilePath(it->absoluteFilePath()).toLocal8Bit(); const auto isLastFile = (--numFiles == 0); - auto *op = device()->storage()->remove(filePath); + auto *op = cli()->remove(filePath); connect(op, &AbstractOperation::finished, this, [=](){ if(op->isError()) { finishWithError(op->errorString()); } else if(isLastFile) { - QTimer::singleShot(0, this, &UserRestoreOperation::transitionToNextState); + QTimer::singleShot(0, this, &UserRestoreOperation::advanceOperationState); } - - op->deleteLater(); }); } @@ -103,7 +101,7 @@ bool UserRestoreOperation::deleteFiles() bool UserRestoreOperation::writeFiles() { - device()->setMessage(QStringLiteral("Restoring backup...")); + deviceState()->setStatusString(tr("Restoring backup...")); auto numFiles = m_files.size(); @@ -122,14 +120,14 @@ bool UserRestoreOperation::writeFiles() return false; } - op = device()->storage()->write(filePath, file); + op = cli()->write(filePath, file); connect(op, &AbstractOperation::finished, this, [=]() { file->close(); file->deleteLater(); }); } else if(fileInfo.isDir()) { - op = device()->storage()->mkdir(filePath); + op = cli()->mkdir(filePath); } else { return false; } @@ -138,7 +136,7 @@ bool UserRestoreOperation::writeFiles() if(op->isError()) { finishWithError(op->errorString()); } else if(isLastFile) { - QTimer::singleShot(0, this, &UserRestoreOperation::transitionToNextState); + QTimer::singleShot(0, this, &UserRestoreOperation::advanceOperationState); } op->deleteLater(); diff --git a/backend/flipperzero/operations/userrestoreoperation.h b/backend/flipperzero/utility/userrestoreoperation.h similarity index 56% rename from backend/flipperzero/operations/userrestoreoperation.h rename to backend/flipperzero/utility/userrestoreoperation.h index 04d9cae2..795c40a7 100644 --- a/backend/flipperzero/operations/userrestoreoperation.h +++ b/backend/flipperzero/utility/userrestoreoperation.h @@ -1,6 +1,6 @@ #pragma once -#include "flipperzerooperation.h" +#include "abstractutilityoperation.h" #include #include @@ -8,22 +8,22 @@ namespace Flipper { namespace Zero { -class UserRestoreOperation : public Operation +class UserRestoreOperation : public AbstractUtilityOperation { Q_OBJECT enum State { - ReadingBackupDir = BasicState::User, + ReadingBackupDir = AbstractOperation::User, DeletingFiles, WritingFiles }; public: - UserRestoreOperation(FlipperZero *device, const QString &backupPath, QObject *parent = nullptr); + UserRestoreOperation(CommandInterface *cli, DeviceState *deviceState, const QString &backupPath, QObject *parent = nullptr); const QString description() const override; private slots: - void transitionToNextState() override; + void advanceOperationState() override; private: QDir m_backupDir; diff --git a/backend/flipperzero/utilityinterface.cpp b/backend/flipperzero/utilityinterface.cpp new file mode 100644 index 00000000..6fb560f3 --- /dev/null +++ b/backend/flipperzero/utilityinterface.cpp @@ -0,0 +1,54 @@ +#include "utilityinterface.h" + +#include "devicestate.h" +#include "commandinterface.h" + +#include "flipperzero/utility/restartoperation.h" +#include "flipperzero/utility/userbackupoperation.h" +#include "flipperzero/utility/userrestoreoperation.h" +#include "flipperzero/utility/startrecoveryoperation.h" +#include "flipperzero/utility/assetsdownloadoperation.h" + +using namespace Flipper; +using namespace Zero; + +UtilityInterface::UtilityInterface(DeviceState *deviceState, QObject *parent): + AbstractOperationRunner(parent), + m_deviceState(deviceState), + m_cli(new CommandInterface(deviceState, this)) +{} + +StartRecoveryOperation *UtilityInterface::startRecoveryMode() +{ + auto *operation = new StartRecoveryOperation(m_cli, m_deviceState, this); + enqueueOperation(operation); + return operation; +} + +AssetsDownloadOperation *UtilityInterface::downloadAssets(QIODevice *compressedFile) +{ + auto *operation = new AssetsDownloadOperation(m_cli, m_deviceState, compressedFile, this); + enqueueOperation(operation); + return operation; +} + +UserBackupOperation *UtilityInterface::backupInternalStorage(const QString &backupPath) +{ + auto *operation = new UserBackupOperation(m_cli, m_deviceState, backupPath, this); + enqueueOperation(operation); + return operation; +} + +UserRestoreOperation *UtilityInterface::restoreInternalStorage(const QString &backupPath) +{ + auto *operation = new UserRestoreOperation(m_cli, m_deviceState, backupPath, this); + enqueueOperation(operation); + return operation; +} + +RestartOperation *UtilityInterface::restartDevice() +{ + auto *operation = new RestartOperation(m_cli, m_deviceState, this); + enqueueOperation(operation); + return operation; +} diff --git a/backend/flipperzero/utilityinterface.h b/backend/flipperzero/utilityinterface.h new file mode 100644 index 00000000..14026b1e --- /dev/null +++ b/backend/flipperzero/utilityinterface.h @@ -0,0 +1,39 @@ +#pragma once + +#include "abstractoperationrunner.h" + +class QIODevice; + +namespace Flipper { +namespace Zero { + +class DeviceState; +class CommandInterface; + +class StartRecoveryOperation; +class AssetsDownloadOperation; +class UserBackupOperation; +class UserRestoreOperation; +class RestartOperation; + +class UtilityInterface : public AbstractOperationRunner +{ + Q_OBJECT + +public: + UtilityInterface(DeviceState *deviceState, QObject *parent = nullptr); + + StartRecoveryOperation *startRecoveryMode(); + AssetsDownloadOperation *downloadAssets(QIODevice *compressedFile); + UserBackupOperation *backupInternalStorage(const QString &backupPath); + UserRestoreOperation *restoreInternalStorage(const QString &backupPath); + RestartOperation *restartDevice(); + +private: + DeviceState *m_deviceState; + CommandInterface *m_cli; +}; + +} +} + diff --git a/backend/gzipuncompressor.cpp b/backend/gzipuncompressor.cpp index bdc6c8f6..7869f754 100644 --- a/backend/gzipuncompressor.cpp +++ b/backend/gzipuncompressor.cpp @@ -16,6 +16,15 @@ GZipUncompressor::GZipUncompressor(QIODevice *in, QIODevice *out, QObject *paren m_out(out), m_progress(0) { + if(!m_in->open(QIODevice::ReadOnly)) { + setError(m_in->errorString()); + return; + + } else if(!m_out->open(QIODevice::WriteOnly)) { + setError(m_out->errorString()); + return; + } + auto *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcherBase::finished, this, [=]() { @@ -98,8 +107,11 @@ void GZipUncompressor::doUncompress() } while(m_in->bytesAvailable()); inflateEnd(&stream); + closeFiles(); +} - if(!m_in->seek(0) || !m_out->seek(0)) { - setError(QStringLiteral("Failed to rewind input files")); - } +void GZipUncompressor::closeFiles() +{ + m_in->close(); + m_out->close(); } diff --git a/backend/gzipuncompressor.h b/backend/gzipuncompressor.h index b6cd1afd..7959c564 100644 --- a/backend/gzipuncompressor.h +++ b/backend/gzipuncompressor.h @@ -23,6 +23,7 @@ class GZipUncompressor : public QObject, public Failable private: void setProgress(double progress); void doUncompress(); + void closeFiles(); QIODevice *m_in; QIODevice *m_out; diff --git a/backend/preferences.cpp b/backend/preferences.cpp new file mode 100644 index 00000000..62fe71ff --- /dev/null +++ b/backend/preferences.cpp @@ -0,0 +1,54 @@ +#include "preferences.h" + +#include + +#define FIRMWARE_UPDATE_CHANNEL_KEY (QStringLiteral("FirmwareUpdateChannel")) +#define CHECK_APPLICATION_UPDATES_KEY (QStringLiteral("CheckApplicatonUpdates")) + +Preferences::Preferences(QObject *parent): + QObject(parent) +{ + if(!m_settings.contains(FIRMWARE_UPDATE_CHANNEL_KEY)) { + m_settings.setValue(FIRMWARE_UPDATE_CHANNEL_KEY, QStringLiteral("development")); + } + + if(!m_settings.contains(CHECK_APPLICATION_UPDATES_KEY)) { + m_settings.setValue(CHECK_APPLICATION_UPDATES_KEY, true); + } +} + +Preferences *Preferences::instance() +{ + static auto *prefs = new Preferences(); + return prefs; +} + +const QString Preferences::firmwareUpdateChannel() const +{ + return m_settings.value(FIRMWARE_UPDATE_CHANNEL_KEY).toString(); +} + +void Preferences::setFirmwareUpdateChannel(const QString &newUpdateChannel) +{ + if(newUpdateChannel == firmwareUpdateChannel()) { + return; + } + + m_settings.setValue(FIRMWARE_UPDATE_CHANNEL_KEY, newUpdateChannel); + emit firmwareUpdateChannelChanged(); +} + +bool Preferences::checkApplicationUpdates() const +{ + return m_settings.value(CHECK_APPLICATION_UPDATES_KEY).toBool(); +} + +void Preferences::setCheckApplicationUpdates(bool set) +{ + if(set == checkApplicationUpdates()) { + return; + } + + m_settings.setValue(CHECK_APPLICATION_UPDATES_KEY, set); + emit checkApplicationUpdatesChanged(); +} diff --git a/backend/preferences.h b/backend/preferences.h new file mode 100644 index 00000000..7f4f0e60 --- /dev/null +++ b/backend/preferences.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +class Preferences : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString updateChannel READ firmwareUpdateChannel WRITE setFirmwareUpdateChannel NOTIFY firmwareUpdateChannelChanged) + Q_PROPERTY(bool checkAppUpdates READ checkApplicationUpdates WRITE setCheckApplicationUpdates NOTIFY checkApplicationUpdatesChanged) + + Preferences(QObject *parent = nullptr); + +public: + static Preferences *instance(); + + const QString firmwareUpdateChannel() const; + void setFirmwareUpdateChannel(const QString &newUpdateChannel); + + bool checkApplicationUpdates() const; + void setCheckApplicationUpdates(bool set); + +signals: + void firmwareUpdateChannelChanged(); + void checkApplicationUpdatesChanged(); + +private: + QSettings m_settings; +}; + +#define globalPrefs() (Preferences::instance()) + diff --git a/backend/qflipperbackend.cpp b/backend/qflipperbackend.cpp index 37835630..3de4eb4c 100644 --- a/backend/qflipperbackend.cpp +++ b/backend/qflipperbackend.cpp @@ -1,9 +1,13 @@ #include "qflipperbackend.h" +#include "preferences.h" +#include "flipperupdates.h" + #include "flipperzero/flipperzero.h" +#include "flipperzero/devicestate.h" #include "flipperzero/assetmanifest.h" -#include "flipperzero/remotecontroller.h" -#include "flipperzero/recoverycontroller.h" +#include "flipperzero/firmwareupdater.h" +#include "flipperzero/screenstreamer.h" #include "macros.h" @@ -13,11 +17,14 @@ QFlipperBackend::QFlipperBackend(): firmwareUpdates("https://update.flipperzero.one/firmware/directory.json"), applicationUpdates("https://update.flipperzero.one/qFlipper/directory.json") { + qRegisterMetaType("Preferences*"); qRegisterMetaType("Flipper::Updates::FileInfo"); qRegisterMetaType("Flipper::Updates::VersionInfo"); qRegisterMetaType("Flipper::Updates::ChannelInfo"); - qRegisterMetaType("Flipper::Zero::RemoteController*"); - qRegisterMetaType("Flipper::Zero::RecoveryController*"); + + qRegisterMetaType("Flipper::Zero::DeviceState*"); + qRegisterMetaType("Flipper::Zero::FirmwareUpdater*"); + qRegisterMetaType("Flipper::Zero::ScreenStreamer*"); qRegisterMetaType(); QMetaType::registerComparators(); diff --git a/backend/qflipperbackend.h b/backend/qflipperbackend.h index 71eebea5..21b1ab7e 100644 --- a/backend/qflipperbackend.h +++ b/backend/qflipperbackend.h @@ -3,7 +3,6 @@ #include "deviceregistry.h" #include "updateregistry.h" -#include "firmwaredownloader.h" struct QFlipperBackend { @@ -13,7 +12,6 @@ struct QFlipperBackend Flipper::DeviceRegistry deviceRegistry; Flipper::UpdateRegistry firmwareUpdates; Flipper::UpdateRegistry applicationUpdates; - Flipper::FirmwareDownloader downloader; }; #endif // QFLIPPERBACKEND_H diff --git a/backend/remotefilefetcher.cpp b/backend/remotefilefetcher.cpp index 3cd245e6..a017d759 100644 --- a/backend/remotefilefetcher.cpp +++ b/backend/remotefilefetcher.cpp @@ -13,94 +13,58 @@ RemoteFileFetcher::RemoteFileFetcher(QObject *parent): m_manager(new QNetworkAccessManager(this)) {} -RemoteFileFetcher::~RemoteFileFetcher() +RemoteFileFetcher::RemoteFileFetcher(const QString &remoteUrl, QIODevice *outputFile, QObject *parent): + RemoteFileFetcher(parent) { - // TODO: correct destruction in all cases + fetch(remoteUrl, outputFile); } -bool RemoteFileFetcher::fetch(const QString &remoteUrl) +RemoteFileFetcher::RemoteFileFetcher(const Flipper::Updates::FileInfo &fileInfo, QIODevice *outputFile, QObject *parent): + RemoteFileFetcher(parent) { - auto *reply = m_manager->get(QNetworkRequest(remoteUrl)); + fetch(fileInfo, outputFile); +} - if(reply->error() != QNetworkReply::NoError) { - reply->deleteLater(); +bool RemoteFileFetcher::fetch(const QString &remoteUrl, QIODevice *outputFile) +{ + if(!outputFile->open(QIODevice::WriteOnly)) { + setError(QStringLiteral("Failed to open file for writing: %1.").arg(outputFile->errorString())); return false; } - connect(reply, &QNetworkReply::finished, this, [=]() { - QByteArray data; - - if(reply->error() == QNetworkReply::NoError) { - data = reply->readAll(); - } - - emit finished(data); - reply->deleteLater(); - }); - - connect(reply, &QNetworkReply::downloadProgress, this, &RemoteFileFetcher::onDownloadProgress); - - return true; -} - -bool RemoteFileFetcher::fetch(const Updates::FileInfo &fileInfo) -{ - auto *reply = m_manager->get(QNetworkRequest(fileInfo.url())); + auto *reply = m_manager->get(QNetworkRequest(remoteUrl)); if(reply->error() != QNetworkReply::NoError) { + setError(QStringLiteral("Network error: %1").arg(reply->errorString())); + reply->deleteLater(); return false; } connect(reply, &QNetworkReply::finished, this, [=]() { - QByteArray data; - - if(reply->error() == QNetworkReply::NoError) { - data = reply->readAll(); - - QCryptographicHash hash(QCryptographicHash::Sha256); - hash.addData(data); - - if(hash.result().toHex() != fileInfo.sha256()) { - data.clear(); - } - } - - emit finished(data); reply->deleteLater(); - }); - - connect(reply, &QNetworkReply::downloadProgress, this, &RemoteFileFetcher::onDownloadProgress); - - return true; -} - -bool RemoteFileFetcher::fetch(const Flipper::Updates::FileInfo &fileInfo, QIODevice *outputFile) -{ - auto *reply = m_manager->get(QNetworkRequest(fileInfo.url())); + outputFile->close(); - if(reply->error() != QNetworkReply::NoError) { - reply->deleteLater(); - return false; - } + if(reply->error() != QNetworkReply::NoError) { + setError(QStringLiteral("Network error: %1").arg(reply->errorString())); - connect(reply, &QNetworkReply::finished, this, [=]() { - if(reply->error() == QNetworkReply::NoError) { - outputFile->seek(0); + } else if(!m_expectedChecksum.isEmpty()) { + if(!outputFile->open(QIODevice::ReadOnly)) { + setError(QStringLiteral("Failed to open file for reading: %1.").arg(outputFile->errorString())); + return; + } QCryptographicHash hash(QCryptographicHash::Sha256); hash.addData(outputFile); - if(hash.result().toHex() != fileInfo.sha256()) { - error_msg("SHA256 sum does not match"); - } else { - outputFile->seek(0); + if(hash.result().toHex() != m_expectedChecksum) { + setError(QStringLiteral("File integrity check failed")); } + + outputFile->close(); } - // TODO: make this signal param-less - emit finished(QByteArray()); - reply->deleteLater(); + emit finished(); }); connect(reply, &QNetworkReply::readyRead, this, [=]() { @@ -112,6 +76,12 @@ bool RemoteFileFetcher::fetch(const Flipper::Updates::FileInfo &fileInfo, QIODev return true; } +bool RemoteFileFetcher::fetch(const Flipper::Updates::FileInfo &fileInfo, QIODevice *outputFile) +{ + m_expectedChecksum = fileInfo.sha256(); + return fetch(fileInfo.url(), outputFile); +} + void RemoteFileFetcher::onDownloadProgress(qint64 received, qint64 total) { emit progressChanged(((double)received / (double)total) * 100.0); diff --git a/backend/remotefilefetcher.h b/backend/remotefilefetcher.h index faf623e0..360098d7 100644 --- a/backend/remotefilefetcher.h +++ b/backend/remotefilefetcher.h @@ -3,31 +3,33 @@ #include +#include "failable.h" #include "flipperupdates.h" class QNetworkAccessManager; -class RemoteFileFetcher : public QObject +class RemoteFileFetcher : public QObject, public Failable { Q_OBJECT public: RemoteFileFetcher(QObject *parent = nullptr); - virtual ~RemoteFileFetcher(); + RemoteFileFetcher(const QString &remoteUrl, QIODevice *outputFile, QObject *parent = nullptr); + RemoteFileFetcher(const Flipper::Updates::FileInfo &fileInfo, QIODevice *outputFile, QObject *parent = nullptr); - bool fetch(const QString &remoteUrl); - bool fetch(const Flipper::Updates::FileInfo &fileInfo); + bool fetch(const QString &remoteUrl, QIODevice *outputFile); bool fetch(const Flipper::Updates::FileInfo &fileInfo, QIODevice *outputFile); signals: void progressChanged(double); - void finished(const QByteArray&); + void finished(); private slots: void onDownloadProgress(qint64 received, qint64 total); private: QNetworkAccessManager *m_manager; + QByteArray m_expectedChecksum; }; #endif // FILEFETCHER_H diff --git a/backend/simpleserialoperation.cpp b/backend/simpleserialoperation.cpp index 019b80e6..9897212a 100644 --- a/backend/simpleserialoperation.cpp +++ b/backend/simpleserialoperation.cpp @@ -51,7 +51,7 @@ bool SimpleSerialOperation::begin() } if(!commandLine().isEmpty()) { - success &= serialPort()->write(commandLine()) == commandLine().size(); + success &= (serialPort()->write(commandLine()) == commandLine().size()) && serialPort()->flush(); } if(success) { diff --git a/backend/tararchive.cpp b/backend/tararchive.cpp index b7da8fd4..d2706e94 100644 --- a/backend/tararchive.cpp +++ b/backend/tararchive.cpp @@ -39,9 +39,16 @@ TarArchive::TarArchive(QIODevice *file): m_tarFile(file), m_root(new FileNode("", FileNode::Type::Directory)) { - buildIndex(); + if(!m_tarFile->open(QIODevice::ReadOnly)) { + setError(m_tarFile->errorString()); + } else { + buildIndex(); + } } +TarArchive::~TarArchive() +{} + FileNode *TarArchive::file(const QString &fullName) { return m_root->find(fullName); @@ -52,6 +59,10 @@ QByteArray TarArchive::fileData(const QString &fullName) if(!m_tarFile) { setError(QStringLiteral("Archive file not set")); return QByteArray(); + + } else if(!m_tarFile->isOpen()) { + setError(QStringLiteral("Archive file is not open")); + return QByteArray(); } auto *node = file(fullName); diff --git a/backend/tararchive.h b/backend/tararchive.h index ad72b8e5..a04f580b 100644 --- a/backend/tararchive.h +++ b/backend/tararchive.h @@ -19,6 +19,7 @@ class TarArchive : public Failable TarArchive(); TarArchive(QIODevice *file); + virtual ~TarArchive(); FileNode *file(const QString &fullName); QByteArray fileData(const QString &fullName); diff --git a/backend/tarziparchive.cpp b/backend/tarziparchive.cpp new file mode 100644 index 00000000..9ce2947e --- /dev/null +++ b/backend/tarziparchive.cpp @@ -0,0 +1,53 @@ +#include "tarziparchive.h" + +#include +#include + +#include "tararchive.h" +#include "gzipuncompressor.h" + +TarZipArchive::TarZipArchive(QFile *tarZipFile, QObject *parent): + QObject(parent), + m_archiveIndex(nullptr) +{ + const QFileInfo tzFileInfo(*tarZipFile); + const auto tzFileDir = tzFileInfo.absoluteDir(); + const auto fileName = tzFileInfo.baseName() + QStringLiteral(".tar"); + + m_tarFile = new QFile(tzFileDir.absoluteFilePath(fileName), this); + + auto *uncompressor = new GZipUncompressor(tarZipFile, m_tarFile, this); + + if(uncompressor->isError()) { + setError(QStringLiteral("Failed to uncompress *tar.gz file: %1").arg(uncompressor->errorString())); + return; + } + + connect(uncompressor, &GZipUncompressor::finished, this, [=]() { + if(uncompressor->isError()) { + setError(QStringLiteral("Failed to uncompress *tar.gz file: %1").arg(uncompressor->errorString())); + } else { + m_archiveIndex = new TarArchive(m_tarFile); + + if(m_archiveIndex->isError()) { + setError(QStringLiteral("Failed to build archive index: %1").arg(m_archiveIndex->errorString())); + } + } + + emit ready(); + }); +} + +TarZipArchive::~TarZipArchive() +{ + if(m_archiveIndex) { + delete m_archiveIndex; + } + + m_tarFile->remove(); +} + +TarArchive *TarZipArchive::archiveIndex() const +{ + return m_archiveIndex; +} diff --git a/backend/tarziparchive.h b/backend/tarziparchive.h new file mode 100644 index 00000000..9713ed48 --- /dev/null +++ b/backend/tarziparchive.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "failable.h" + +class QFile; +class TarArchive; + +class TarZipArchive : public QObject, public Failable +{ + Q_OBJECT + +public: + TarZipArchive(QFile *tarZipFile, QObject *parent = nullptr); + ~TarZipArchive(); + + TarArchive *archiveIndex() const; + +signals: + void ready(); + +private: + QFile *m_tarFile; + TarArchive *m_archiveIndex; +}; + diff --git a/backend/tempdirectories.cpp b/backend/tempdirectories.cpp new file mode 100644 index 00000000..16c928a6 --- /dev/null +++ b/backend/tempdirectories.cpp @@ -0,0 +1,43 @@ +#include "tempdirectories.h" + +#include +#include + +TempDirectories::TempDirectories(): + m_root(QDir::temp().absoluteFilePath(QStringLiteral("qFlipper-XXXXXXXX"))) +{} + +TempDirectories *TempDirectories::instance() +{ + static TempDirectories instance; + return &instance; +} + +QDir TempDirectories::root() const +{ + return QDir(m_root.path()); +} + +QDir TempDirectories::subdir(const QString &subdirName) const +{ + auto subdir = root(); + bool success; + + if(!root().exists(subdirName)) { + success = subdir.mkdir(subdirName) && subdir.cd(subdirName); + } else { + success = subdir.cd(subdirName); + } + + return success ? subdir : QDir(); +} + +QFile *TempDirectories::createFile(const QString &fileName, QObject *parent) const +{ + return new QFile(root().absoluteFilePath(fileName), parent); +} + +QFile *TempDirectories::createTempFile() const +{ + return new QTemporaryFile(root().absoluteFilePath(QStringLiteral("temp.XXXXXXXX"))); +} diff --git a/backend/tempdirectories.h b/backend/tempdirectories.h new file mode 100644 index 00000000..26b86949 --- /dev/null +++ b/backend/tempdirectories.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +class QFile; +class QObject; + +class TempDirectories +{ + TempDirectories(); + +public: + static TempDirectories *instance(); + + QDir root() const; + QDir subdir(const QString &subdirName) const; + + QFile *createFile(const QString &fileName, QObject *parent = nullptr) const; + QFile *createTempFile() const; + +private: + QTemporaryDir m_root; +}; + +#define tempDirs() (TempDirectories::instance()) diff --git a/backend/updateregistry.cpp b/backend/updateregistry.cpp index 678d95e5..dc8f8df6 100644 --- a/backend/updateregistry.cpp +++ b/backend/updateregistry.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include "macros.h" +#include "preferences.h" #include "remotefilefetcher.h" using namespace Flipper; @@ -16,22 +18,26 @@ UpdateRegistry::UpdateRegistry(const QString &directoryUrl, QObject *parent): QObject(parent) { auto *fetcher = new RemoteFileFetcher(this); + auto *buf = new QBuffer(this); - fetcher->connect(fetcher, &RemoteFileFetcher::finished, this, [=](const QByteArray &data) { - if(!data.isEmpty()) { - info_msg(QString("Fetched update list from %1.").arg(directoryUrl)); - fillFromJson(data); + fetcher->connect(fetcher, &RemoteFileFetcher::finished, this, [=]() { + if(buf->open(QIODevice::ReadOnly)) { + info_msg(QStringLiteral("Fetched update directory from %1.").arg(directoryUrl)); + fillFromJson(buf->readAll()); + + } else { + info_msg(QStringLiteral("Failed to open a buffer for reading: %1.").arg(buf->errorString())); } fetcher->deleteLater(); + buf->deleteLater(); }); - fetcher->fetch(directoryUrl); + if(!fetcher->fetch(directoryUrl, buf)) { + buf->deleteLater(); + } } -UpdateRegistry::~UpdateRegistry() -{} - bool UpdateRegistry::fillFromJson(const QByteArray &text) { // TODO: Clear map first @@ -68,6 +74,16 @@ const QStringList UpdateRegistry::channelNames() const return m_channels.keys(); } +bool UpdateRegistry::isReady() const +{ + return !m_channels.isEmpty(); +} + +const Updates::VersionInfo UpdateRegistry::latestVersion() const +{ + return channel(globalPrefs()->firmwareUpdateChannel()).latestVersion(); +} + Updates::ChannelInfo UpdateRegistry::channel(const QString &channelName) const { return m_channels.value(channelName); diff --git a/backend/updateregistry.h b/backend/updateregistry.h index 996a86cc..dbe8ad83 100644 --- a/backend/updateregistry.h +++ b/backend/updateregistry.h @@ -2,8 +2,6 @@ #define UPDATESLISTMODEL_H #include -#include -#include #include "flipperupdates.h" @@ -13,16 +11,20 @@ class UpdateRegistry : public QObject { Q_OBJECT Q_PROPERTY(QStringList channelNames READ channelNames NOTIFY channelsChanged) + Q_PROPERTY(Flipper::Updates::VersionInfo latestVersion READ latestVersion NOTIFY channelsChanged) + Q_PROPERTY(bool isReady READ isReady NOTIFY channelsChanged) public: UpdateRegistry(const QString &directoryUrl, QObject *parent = nullptr); - ~UpdateRegistry(); - bool fillFromJson(const QByteArray &text); const QStringList channelNames() const; + bool isReady() const; + + const Flipper::Updates::VersionInfo latestVersion() const; - Q_INVOKABLE Flipper::Updates::ChannelInfo channel(const QString &channelName) const; +public slots: + Flipper::Updates::ChannelInfo channel(const QString &channelName) const; signals: void channelsChanged();