Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<PublishSingleFile>true</PublishSingleFile>
Expand All @@ -12,14 +10,16 @@
<ApplicationManifest>app.manifest</ApplicationManifest>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup>

</PropertyGroup>

<PropertyGroup Label="Avalonia">
<ApplicationIcon>reuven.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<Content Include="reuven.ico" />
<Content Include="config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions CSAUSBTool.CrossPlatform.Desktop/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"repo_api_lists_url": "https://api.github.com/repos/JamieSinn/CSA-USB-Tool/contents/Lists"
}
6 changes: 0 additions & 6 deletions CSAUSBTool.CrossPlatform.Desktop/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@
"Avalonia.X11": "11.2.2"
}
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.11, )",
"resolved": "8.0.11",
"contentHash": "zk5lnZrYJgtuJG8L4v17Ej8rZ3PUcR2iweNV08BaO5LbYHIi2wNaVNcJoLxvqgQdnjLlKnCCfVGLDr6QHeAarQ=="
},
"Avalonia.Angle.Windows.Natives": {
"type": "Transitive",
"resolved": "2.1.22045.20230930",
Expand Down
24 changes: 24 additions & 0 deletions CSAUSBTool.CrossPlatform/CSAUSBTool.CrossPlatform.sln
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - why add a solution file here? There is already one at the root? https://github.com/JamieSinn/CSA-USB-Tool/blob/main/CSAUSBTool.sln

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSAUSBTool.CrossPlatform", "CSAUSBTool.CrossPlatform.csproj", "{75FFD5CF-8C16-F521-0919-9F39B01CE15F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{75FFD5CF-8C16-F521-0919-9F39B01CE15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75FFD5CF-8C16-F521-0919-9F39B01CE15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75FFD5CF-8C16-F521-0919-9F39B01CE15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75FFD5CF-8C16-F521-0919-9F39B01CE15F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0F4532DC-CDDD-4BDC-97C9-67893ED1F27A}
EndGlobalSection
EndGlobal
146 changes: 100 additions & 46 deletions CSAUSBTool.CrossPlatform/Models/ControlSystemSoftware.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CSAUSBTool.CrossPlatform.Core;
Expand All @@ -13,76 +13,128 @@ namespace CSAUSBTool.CrossPlatform.Models
{
public class ControlSystemSoftware : ReactiveObject
{
public string Name { get; set; }
[JsonPropertyName("Name")]
public string Name { get; set; } = string.Empty;

[JsonPropertyName("FileName")]
public string? FileName { get; set; }
public string Description { get; set; }
public List<string> Tags { get; set; }
public string Uri { get; set; }

[JsonPropertyName("Description")]
public string Description { get; set; } = string.Empty;

[JsonPropertyName("Tags")]
public List<string> Tags { get; set; } = [];

[JsonPropertyName("Uri")]
public string? Uri { get; set; }

[JsonPropertyName("Hash")]
public string? Hash { get; set; }
public string Platform { get; set; }

private double _DownloadProgress;
[JsonPropertyName("Platform")]
public string? Platform { get; set; }

private double _downloadProgress;
public double DownloadProgress
{
get => _DownloadProgress;
set => this.RaiseAndSetIfChanged(ref _DownloadProgress, value);
get => _downloadProgress;
set => this.RaiseAndSetIfChanged(ref _downloadProgress, value);
}

public ControlSystemSoftware()
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set => this.RaiseAndSetIfChanged(ref _isChecked, value);
}

public async Task Download(string outputPath, CancellationToken token)
private string _statusText = "Pending";
public string StatusText
{
get => _statusText;
set => this.RaiseAndSetIfChanged(ref _statusText, value);
}

private string _displayText = string.Empty;
public string DisplayText
{
get => _displayText;
set => this.RaiseAndSetIfChanged(ref _displayText, value);
}

[JsonIgnore]
public bool IsSelectable => !string.IsNullOrWhiteSpace(Uri);

public void RefreshDisplayText()
{
if (Uri == null)
var tags = Tags.Count > 0 ? $" [{string.Join(", ", Tags)}]" : string.Empty;
DisplayText = IsSelectable ? $"{Name}{tags}" : $"{Name}{tags} (no download URI)";
}

public string ResolveFileName()
{
if (!string.IsNullOrWhiteSpace(FileName))
{
throw new ArgumentNullException("Uri", "must not be null");
return FileName!;
}
FileName ??= Uri.Split('/').Last();

var outputUri = new Uri(new Uri(outputPath), FileName);
try
if (!string.IsNullOrWhiteSpace(Uri))
{
await using var existingFile = File.OpenRead(System.Uri.UnescapeDataString(outputUri.AbsolutePath));

if (existingFile is { Length: > 0 })
var parsed = new global::System.Uri(Uri);
var fromUrl = global::System.Uri.UnescapeDataString(Path.GetFileName(parsed.LocalPath));
if (!string.IsNullOrWhiteSpace(fromUrl))
{
if (Hash != null)
{
var currentHash = CalculateMD5(existingFile);
if (currentHash == Hash)
{
DownloadProgress = 100;
return;
}

File.Delete(outputUri.AbsolutePath);
}
return fromUrl;
}
}
catch (FileNotFoundException e)

return $"{Name}.bin";
}

// Legacy compatibility for older views still calling Download().
public async Task Download(string outputPath, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(Uri))
{
// Silently catch this - this is ignored.
throw new InvalidOperationException("No download URI.");
}


using var client =
new HttpClientDownloadWithProgress(Uri, outputUri.AbsolutePath);
//client.ProgressChanged += _8kbBuffer;
client.ProgressChanged += UpdateProgress;
var resolvedName = ResolveFileName();
var outputFile = Path.Combine(outputPath, resolvedName);
using var client = new HttpClientDownloadWithProgress(Uri, outputFile);
client.ProgressChanged += (_, _, p) => DownloadProgress = p ?? 0;
await client.StartDownload(token);
}

private void UpdateProgress(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage)
public static string CalculateHash(string filePath, string algorithmName)
{
Debug.WriteLine($"Downloaded {totalBytesDownloaded} of {totalFileSize} - {progressPercentage}%");
DownloadProgress = progressPercentage ?? 0;
using var stream = File.OpenRead(filePath);

byte[] hash = algorithmName switch
{
"MD5" => MD5.HashData(stream),
"SHA1" => SHA1.HashData(stream),
"SHA256" => SHA256.HashData(stream),
_ => throw new InvalidOperationException($"Invalid hash algorithm: {algorithmName}")
};

return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
}
private string CalculateMD5(FileStream stream)

public static string? GetHashAlgorithmFromLength(string? hash)
{
using var md5 = MD5.Create();
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
if (string.IsNullOrWhiteSpace(hash))
{
return null;
}

return hash.Length switch
{
32 => "MD5",
40 => "SHA1",
64 => "SHA256",
_ => null
};
}
}

Expand All @@ -93,6 +145,8 @@ public DesignControlSystemSoftware()
Name = "FRC Driver Station";
Tags = ["Driver Station", "FRC"];
Description = "The FRC Driver Station is the software used to control your robot during a match.";
Uri = "https://example.com/driverstation.exe";
RefreshDisplayText();
}
}
}
}
Loading
Loading