Skip to content

Commit

Permalink
refa (IOSFileDownloader.swift): add support for trySetBluetoothDevice…
Browse files Browse the repository at this point in the history
…() and tryInvalidateCachedTransport() similar to the IOSFileUploader
  • Loading branch information
ksidirop-laerdal committed Oct 22, 2024
1 parent 3ea6401 commit 5b52b8e
Show file tree
Hide file tree
Showing 34 changed files with 314 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -373,21 +373,25 @@ public void onError(final String errorMessage, final Exception exception)

if (!(exception instanceof McuMgrErrorException))
{
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, errorMessage /*, -1, -1*/);
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, errorMessage, -1, -1);
return;
}

McuMgrErrorException mcuMgrErrorException = (McuMgrErrorException) exception;
fatalErrorOccurredAdvertisement(
_remoteFilePathSanitized,
errorMessage
// ,mcuMgrErrorException.getCode().value(), //todo and mirror this in the ios world as well
// (mcuMgrErrorException.getGroupCode() != null ? mcuMgrErrorException.getGroupCode().group : -99) //todo
errorMessage,
mcuMgrErrorException.getCode().value(),
(mcuMgrErrorException.getGroupCode() != null ? mcuMgrErrorException.getGroupCode().group : -99)
);
}

//todo add support for mcuMgrErrorCode and fsManagerGroupReturnCode
public void fatalErrorOccurredAdvertisement(final String resource, final String errorMessage) //this method is meant to be overridden by csharp binding libraries to intercept updates
public void fatalErrorOccurredAdvertisement(
final String remoteFilePath,
final String errorMessage,
final int mcuMgrErrorCode, // io.runtime.mcumgr.McuMgrErrorCode
final int fsManagerGroupReturnCode // io.runtime.mcumgr.managers.FsManager.ReturnCode
)
{
_lastFatalErrorMessage = errorMessage;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,68 +6,92 @@ public class IOSFileDownloader: NSObject {

private var _listener: IOSListenerForFileDownloader!
private var _transporter: McuMgrBleTransport!
private var _currentState: EIOSFileDownloaderState
private var _cbPeripheral: CBPeripheral!
private var _fileSystemManager: FileSystemManager!
private var _lastFatalErrorMessage: String

private var _currentState: EIOSFileDownloaderState = .none
private var _lastBytesSend: Int = -1
private var _lastFatalErrorMessage: String = ""
private var _lastBytesSendTimestamp: Date? = nil
private var _remoteFilePathSanitized: String
private var _remoteFilePathSanitized: String = ""

@objc
public init(_ listener: IOSListenerForFileDownloader!) {
_listener = listener
}

@objc
public init(_ cbPeripheral: CBPeripheral!, _ listener: IOSListenerForFileDownloader!) {
_listener = listener
_transporter = McuMgrBleTransport(cbPeripheral)
_currentState = .none
_lastFatalErrorMessage = ""
_remoteFilePathSanitized = ""
_cbPeripheral = cbPeripheral
}

@objc
public func trySetBluetoothDevice(_ cbPeripheral: CBPeripheral!) -> Bool {
if !isIdleOrCold() {
return false
}

if !tryInvalidateCachedTransport() { //order
return false
}

_cbPeripheral = cbPeripheral //order
return true
}

@objc
public func tryInvalidateCachedTransport() -> Bool {
if _transporter == nil { //already scrapped
return true
}

if !isIdleOrCold() { //if the upload is already in progress we bail out
return false
}

disposeFilesystemManager() // order
disposeTransport() // order

return true;
}

@objc
public func beginDownload(_ remoteFilePath: String) -> EIOSFileDownloadingInitializationVerdict {
if _currentState != .none
&& _currentState != .error
&& _currentState != .complete
&& _currentState != .cancelled { //if another download is already in progress we bail out

if !isCold() { //keep first if another download is already in progress we bail out
onError("Another download is already in progress")

return EIOSFileDownloadingInitializationVerdict.failedDownloadAlreadyInProgress
}

_lastBytesSend = -1
_lastBytesSendTimestamp = nil

if remoteFilePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
setState(EIOSFileDownloaderState.error)
fatalErrorOccurredAdvertisement("", "Target-file provided is dud!")
_remoteFilePathSanitized = remoteFilePath.trimmingCharacters(in: .whitespacesAndNewlines)
if _remoteFilePathSanitized.isEmpty {
onError("Target-file provided is dud!")

return EIOSFileDownloadingInitializationVerdict.failedInvalidSettings
}

if remoteFilePath.hasSuffix("/") {
setState(EIOSFileDownloaderState.error)
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, "Target-file points to a directory instead of a file")
if _remoteFilePathSanitized.hasSuffix("/") {
onError("Target-file points to a directory instead of a file")

return EIOSFileDownloadingInitializationVerdict.failedInvalidSettings
}

if !remoteFilePath.hasPrefix("/") {
setState(EIOSFileDownloaderState.error)
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, "Target-path is not absolute!")
if !_remoteFilePathSanitized.hasPrefix("/") {
onError("Target-path is not absolute!")

return EIOSFileDownloadingInitializationVerdict.failedInvalidSettings
}

_fileSystemManager = FileSystemManager(transport: _transporter) // the delegate aspect is implemented in the extension below
_fileSystemManager.logDelegate = self
resetUploadState() //order
disposeFilesystemManager() //00 vital hack
ensureTransportIsInitializedExactlyOnce() //order
ensureFilesystemManagerIsInitializedExactlyOnce() //order

setState(EIOSFileDownloaderState.idle)
busyStateChangedAdvertisement(true)
fileDownloadProgressPercentageAndDataThroughputChangedAdvertisement(0, 0)

_remoteFilePathSanitized = remoteFilePath
let success = _fileSystemManager.download(name: remoteFilePath, delegate: self)
let success = _fileSystemManager.download(name: _remoteFilePathSanitized, delegate: self)
if !success {
setState(EIOSFileDownloaderState.error)
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, "Failed to commence file-Downloading (check logs for details)")
onError("Failed to commence file-Downloading (check logs for details)")

return EIOSFileDownloadingInitializationVerdict.failedErrorUponCommencing
}
Expand Down Expand Up @@ -106,11 +130,102 @@ public class IOSFileDownloader: NSObject {
_transporter?.close()
}

private func isIdleOrCold() -> Bool {
return _currentState == EIOSFileDownloaderState.idle || isCold();
}

private func isCold() -> Bool {
return _currentState == EIOSFileDownloaderState.none
|| _currentState == EIOSFileDownloaderState.error
|| _currentState == EIOSFileDownloaderState.complete
|| _currentState == EIOSFileDownloaderState.cancelled
}

private func resetUploadState() {
_lastBytesSend = -1
_lastBytesSendTimestamp = nil

setState(EIOSFileDownloaderState.idle)
busyStateChangedAdvertisement(true)
fileDownloadProgressPercentageAndDataThroughputChangedAdvertisement(0, 0)
}

private func ensureFilesystemManagerIsInitializedExactlyOnce() {
if _fileSystemManager != nil { //already initialized
return
}

_fileSystemManager = FileSystemManager(transport: _transporter) //00
_fileSystemManager.logDelegate = self //00

//00 this doesnt throw an error the log-delegate aspect is implemented in the extension below via IOSFileDownloader: McuMgrLogDelegate
}

private func ensureTransportIsInitializedExactlyOnce() {
if _transporter != nil {
return
}

_transporter = McuMgrBleTransport(_cbPeripheral)
}

private func disposeTransport() {
_transporter?.close()
_transporter = nil
}

private func disposeFilesystemManager() {
//_fileSystemManager?.cancelTransfer() dont
_fileSystemManager = nil
}

//@objc dont
private func fatalErrorOccurredAdvertisement(_ resource: String, _ errorMessage: String) {
private func onError(_ errorMessage: String, _ error: Error? = nil) {
setState(EIOSFileDownloaderState.error) //keep first

_lastFatalErrorMessage = errorMessage

_listener.fatalErrorOccurredAdvertisement(resource, errorMessage)
let (errorCode, _) = deduceErrorCode(errorMessage)

_listener.fatalErrorOccurredAdvertisement(
_remoteFilePathSanitized,
errorMessage,
errorCode
)
}

// unfortunately I couldnt figure out a way to deduce the error code from the error itself so I had to resort to string sniffing ugly but it works
private func deduceErrorCode(_ errorMessage: String) -> (Int, String?) {
let (matchesArray, possibleError) = matches(for: " [(]\\d+[)][.]?$", in: errorMessage) // "UNKNOWN (1)."
if possibleError != nil {
return (-99, possibleError)
}

let errorCode = matchesArray.isEmpty
? -99
: (Int(matchesArray[0].trimmingCharacters(in: .whitespaces).trimmingCharacters(in: ["(", ")", "."]).trimmingCharacters(in: .whitespaces)) ?? 0)

return (errorCode, possibleError)
}

private func matches(for regex: String, in text: String) -> ([String], String?) { //00
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))

return (
results.map {
String(text[Range($0.range, in: text)!])
},
nil
)
} catch let error {
print("invalid regex: \(error.localizedDescription)")

return ([], error.localizedDescription)
}

//00 https://stackoverflow.com/a/27880748/863651
}

//@objc dont
Expand Down Expand Up @@ -143,7 +258,7 @@ public class IOSFileDownloader: NSObject {
) {
_listener.fileDownloadProgressPercentageAndDataThroughputChangedAdvertisement(progressPercentage, averageThroughput)
}

//@objc dont
private func downloadCompletedAdvertisement(_ resource: String, _ data: [UInt8]) {
_listener.downloadCompletedAdvertisement(resource, data)
Expand Down Expand Up @@ -178,8 +293,8 @@ extension IOSFileDownloader: FileDownloadDelegate {
}

public func downloadDidFail(with error: Error) {
setState(EIOSFileDownloaderState.error)
fatalErrorOccurredAdvertisement(_remoteFilePathSanitized, error.localizedDescription)
onError(error.localizedDescription, error)

busyStateChangedAdvertisement(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import Foundation
@objc
public protocol IOSListenerForFileDownloader {
func logMessageAdvertisement(_ message: String, _ category: String, _ level: String, _ resource: String)
func fatalErrorOccurredAdvertisement(_ resource: String, _ errorMessage: String)
func fatalErrorOccurredAdvertisement(_ resource: String, _ errorMessage: String, _ errorCode: Int)

func cancelledAdvertisement()

func stateChangedAdvertisement(_ resource: String, _ oldState: EIOSFileDownloaderState, _ newState: EIOSFileDownloaderState)
func busyStateChangedAdvertisement(_ busyNotIdle: Bool)
func downloadCompletedAdvertisement(_ resource: String, _ data: [UInt8])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using FluentAssertions;
using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using GenericNativeFileDownloaderCallbacksProxy_ = Laerdal.McuMgr.FileDownloader.FileDownloader.GenericNativeFileDownloaderCallbacksProxy;

#pragma warning disable xUnit1026
Expand Down Expand Up @@ -100,18 +102,18 @@ public override EFileDownloaderVerdict BeginDownload(string remoteFilePath, int?
if (remoteFilePathUppercase.Contains("some/file/that/exist/but/is/erroring/out/when/we/try/to/download/it.bin".ToUpperInvariant()))
{
StateChangedAdvertisement(remoteFilePath, oldState: EFileDownloaderState.Downloading, newState: EFileDownloaderState.Error);
FatalErrorOccurredAdvertisement(remoteFilePath, "foobar");
FatalErrorOccurredAdvertisement(remoteFilePath, "foobar", EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset);
}
else if (remoteFilePathUppercase.Contains("some/file/that/doesnt/exist.bin".ToUpperInvariant()))
{
StateChangedAdvertisement(remoteFilePath, oldState: EFileDownloaderState.Downloading, newState: EFileDownloaderState.Error);
FatalErrorOccurredAdvertisement(remoteFilePath, "NO ENTRY (5)");
FatalErrorOccurredAdvertisement(remoteFilePath, "NO ENTRY (5)", EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset);
}
else if (remoteFilePathUppercase.Contains("some/file/that/exist/and/completes/after/a/couple/of/attempts.bin".ToUpperInvariant())
&& _retryCountForProblematicFile++ < 3)
{
StateChangedAdvertisement(remoteFilePath, oldState: EFileDownloaderState.Downloading, newState: EFileDownloaderState.Error);
FatalErrorOccurredAdvertisement(remoteFilePath, "ping pong");
FatalErrorOccurredAdvertisement(remoteFilePath, "ping pong", EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Events;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using GenericNativeFileDownloaderCallbacksProxy_ = Laerdal.McuMgr.FileDownloader.FileDownloader.GenericNativeFileDownloaderCallbacksProxy;

#pragma warning disable xUnit1026
Expand Down Expand Up @@ -126,16 +127,16 @@ public override EFileDownloaderVerdict BeginDownload(
if (_tryCounter == _maxTriesCount && initialMtuSize != AndroidTidbits.FailSafeBleConnectionSettings.InitialMtuSize)
{
BugDetected = $"[BUG DETECTED] The very last try should be with {nameof(initialMtuSize)} set to {AndroidTidbits.FailSafeBleConnectionSettings.InitialMtuSize} but it's set to {initialMtuSize?.ToString() ?? "(null)"} instead - something is wrong!";
StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error); // order
FatalErrorOccurredAdvertisement(remoteFilePath, BugDetected); // order
StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error); // order
FatalErrorOccurredAdvertisement(remoteFilePath, BugDetected, EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset); // order
return;
}

if (_tryCounter < _maxTriesCount)
{
await Task.Delay(20);
StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error); // order
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred"); // order
StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error); // order
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred", EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset); // order
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using FluentAssertions;
using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Events;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using GenericNativeFileDownloaderCallbacksProxy_ = Laerdal.McuMgr.FileDownloader.FileDownloader.GenericNativeFileDownloaderCallbacksProxy;

#pragma warning disable xUnit1026
Expand Down Expand Up @@ -102,7 +104,7 @@ public override EFileDownloaderVerdict BeginDownload(string remoteFilePath, int?
if (_tryCount < _maxNumberOfTriesForSuccess)
{
StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error);
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred");
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred", EMcuMgrErrorCode.Unknown, EFileOperationGroupReturnCode.Unset);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using FluentAssertions;
using FluentAssertions.Extensions;
using Laerdal.McuMgr.Common.Enums;
using Laerdal.McuMgr.Common.Helpers;
using Laerdal.McuMgr.FileDownloader.Contracts.Enums;
using Laerdal.McuMgr.FileDownloader.Contracts.Events;
using Laerdal.McuMgr.FileDownloader.Contracts.Exceptions;
using Laerdal.McuMgr.FileDownloader.Contracts.Native;
using Laerdal.McuMgr.FileUploader.Contracts.Enums;
using GenericNativeFileDownloaderCallbacksProxy_ = Laerdal.McuMgr.FileDownloader.FileDownloader.GenericNativeFileDownloaderCallbacksProxy;

namespace Laerdal.McuMgr.Tests.FileDownloader
Expand Down Expand Up @@ -87,7 +89,7 @@ public override EFileDownloaderVerdict BeginDownload(string remoteFilePath, int?
await Task.Delay(2_000);

StateChangedAdvertisement(remoteFilePath, EFileDownloaderState.Downloading, EFileDownloaderState.Error);
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred");
FatalErrorOccurredAdvertisement(remoteFilePath, "fatal error occurred", EMcuMgrErrorCode.BadState, EFileOperationGroupReturnCode.Unset);
});

return verdict;
Expand Down
Loading

0 comments on commit 5b52b8e

Please sign in to comment.