Skip to content

Commit 4a2a078

Browse files
2 parents b014b18 + 068bc9f commit 4a2a078

File tree

12 files changed

+218
-77
lines changed

12 files changed

+218
-77
lines changed

MSUScripter/Configs/MsuSongMsuPcmInfo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public List<string> GetFiles()
9999
return files;
100100
}
101101

102-
[YamlIgnore]
102+
[YamlIgnore, JsonSchemaIgnore]
103103
public bool HasBothSubTracksAndSubChannels
104104
{
105105
get
@@ -110,7 +110,7 @@ public bool HasBothSubTracksAndSubChannels
110110
}
111111
}
112112

113-
[YamlIgnore]
113+
[YamlIgnore, JsonSchemaIgnore]
114114
public bool HasValidSubChannelCount
115115
{
116116
get
@@ -120,7 +120,7 @@ public bool HasValidSubChannelCount
120120
}
121121
}
122122

123-
[YamlIgnore]
123+
[YamlIgnore, JsonSchemaIgnore]
124124
public bool HasValidChildTypes
125125
{
126126
get

MSUScripter/MSUScripter.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
99
<ApplicationIcon>MSUScripterIcon.ico</ApplicationIcon>
1010
<PackageIcon>MSUScripterIcon.ico</PackageIcon>
11-
<Version>5.0.0</Version>
11+
<Version>5.0.1</Version>
1212
<RuntimeFrameworkVersion>9.0.0</RuntimeFrameworkVersion>
1313
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
1414
<LangVersion>12</LangVersion>

MSUScripter/Services/ControlServices/MainWindowService.cs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Linq;
5+
using System.Net.Http;
46
using System.Runtime.Versioning;
57
using System.Threading.Tasks;
68
using AvaloniaControls.ControlServices;
@@ -254,12 +256,10 @@ private bool CleanDirectory(string path, TimeSpan? timeout = null)
254256

255257
if (newerGitHubRelease != null)
256258
{
257-
if (OperatingSystem.IsLinux())
258-
{
259-
return (newerGitHubRelease.Url,
260-
newerGitHubRelease.Asset.FirstOrDefault(x => x.Url.ToLower().EndsWith(".appimage"))?.Url);
261-
}
262-
return (newerGitHubRelease.Url, null);
259+
var downloadUrl = OperatingSystem.IsLinux()
260+
? newerGitHubRelease.Asset.FirstOrDefault(x => x.Url.ToLower().EndsWith(".appimage"))?.Url
261+
: newerGitHubRelease.Asset.FirstOrDefault(x => x.Url.ToLower().EndsWith(".exe"))?.Url;
262+
return (newerGitHubRelease.Url, downloadUrl);
263263
}
264264

265265
return null;
@@ -296,6 +296,72 @@ public void IgnoreFutureUpdates()
296296
settingsService.SaveSettings();
297297
}
298298

299+
public async Task<string?> InstallWindowsUpdate(string url)
300+
{
301+
var filename = Path.GetFileName(new Uri(url).AbsolutePath);
302+
var localPath = Path.Combine(Path.GetTempPath(), filename);
303+
304+
logger.LogInformation("Downloading {Url} to {LocalPath}", url, localPath);
305+
306+
var response = await DownloadFileAsyncAttempt(url, localPath);
307+
308+
if (!response.Item1)
309+
{
310+
logger.LogInformation("Download failed: {Error}", response.Item2);
311+
return response.Item2;
312+
}
313+
314+
try
315+
{
316+
logger.LogInformation("Launching setup file");
317+
318+
var psi = new ProcessStartInfo
319+
{
320+
FileName = localPath,
321+
UseShellExecute = true,
322+
RedirectStandardOutput = false,
323+
RedirectStandardError = false,
324+
RedirectStandardInput = false,
325+
CreateNoWindow = true
326+
};
327+
328+
Process.Start(psi);
329+
return null;
330+
}
331+
catch (Exception e)
332+
{
333+
logger.LogError(e, "Failed to start setup file");
334+
return "Failed to start setup file";
335+
}
336+
}
337+
338+
private async Task<(bool, string?)> DownloadFileAsyncAttempt(string url, string target, int attemptNumber = 0, int totalAttempts = 3)
339+
{
340+
341+
using var httpClient = new HttpClient();
342+
343+
try
344+
{
345+
await using var downloadStream = await httpClient.GetStreamAsync(url);
346+
await using var fileStream = new FileStream(target, FileMode.Create);
347+
await downloadStream.CopyToAsync(fileStream);
348+
return (true, null);
349+
}
350+
catch (Exception ex)
351+
{
352+
logger.LogError(ex, "Download failed");
353+
if (attemptNumber < totalAttempts)
354+
{
355+
await Task.Delay(TimeSpan.FromSeconds(attemptNumber));
356+
return await DownloadFileAsyncAttempt(url, target, attemptNumber + 1, totalAttempts);
357+
}
358+
else
359+
{
360+
return (false, $"Download failed: {ex.Message}");
361+
}
362+
}
363+
}
364+
299365
private async Task CleanUpFolders()
300366
{
301367
await ITaskService.Run(() =>

MSUScripter/Services/DependencyInstallerService.cs

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,63 +32,73 @@ public async Task<bool> InstallPyApp(Action<string> response, Func<string, strin
3232
{
3333
response.Invoke("Setting up directories");
3434
var destination = Path.Combine(Directories.Dependencies, "python");
35+
36+
var pythonPath = OperatingSystem.IsWindows()
37+
? Path.Combine(destination, "python.exe")
38+
: Path.Combine(destination, "bin", "python3.13");
39+
var doesHavePython = false;
3540

36-
if (Directory.Exists(destination))
41+
if (File.Exists(pythonPath))
3742
{
38-
logger.LogInformation("Deleting prior Python installation");
39-
try
43+
var checkPyVersionResult = await runPyFunc(pythonPath, "--version");
44+
doesHavePython = checkPyVersionResult.Success && checkPyVersionResult.Result.StartsWith("Python 3.13");
45+
}
46+
47+
if (!doesHavePython)
48+
{
49+
if (Directory.Exists(destination))
4050
{
41-
await ITaskService.Run(() =>
51+
logger.LogInformation("Deleting prior Python installation");
52+
try
4253
{
43-
Directory.Delete(destination, true);
44-
});
45-
}
46-
catch (TaskCanceledException)
47-
{
48-
// Do Nothing
54+
await ITaskService.Run(() =>
55+
{
56+
Directory.Delete(destination, true);
57+
});
58+
}
59+
catch (TaskCanceledException)
60+
{
61+
// Do Nothing
62+
}
4963
}
50-
}
51-
52-
EnsureFolders(destination);
5364

54-
var tempFile = Path.Combine(Directories.TempFolder, "python.tar.gz");
55-
var url = OperatingSystem.IsWindows() ? PythonWindowsDownloadUrl : PythonLinuxDownloadUrl;
65+
EnsureFolders(destination);
5666

57-
response.Invoke("Downloading Python");
58-
if (!await DownloadFileAsync(url, tempFile))
59-
{
60-
return false;
61-
}
67+
var tempFile = Path.Combine(Directories.TempFolder, "python.tar.gz");
68+
var url = OperatingSystem.IsWindows() ? PythonWindowsDownloadUrl : PythonLinuxDownloadUrl;
6269

63-
response.Invoke("Extracting Python files");
64-
if (!await ExtractTarGzFile(tempFile, Directories.Dependencies))
65-
{
66-
return false;
67-
}
70+
response.Invoke("Downloading Python");
71+
if (!await DownloadFileAsync(url, tempFile))
72+
{
73+
return false;
74+
}
6875

69-
var pythonPath = OperatingSystem.IsWindows()
70-
? Path.Combine(destination, "python.exe")
71-
: Path.Combine(destination, "bin", "python3.13");
76+
response.Invoke("Extracting Python files");
77+
if (!await ExtractTarGzFile(tempFile, Directories.Dependencies))
78+
{
79+
return false;
80+
}
7281

73-
if (OperatingSystem.IsLinux())
74-
{
75-
File.SetUnixFileMode(pythonPath,
76-
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
77-
}
82+
if (OperatingSystem.IsLinux())
83+
{
84+
File.SetUnixFileMode(pythonPath,
85+
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
86+
}
7887

79-
response.Invoke("Verifying Python version");
88+
response.Invoke("Verifying Python version");
8089

81-
var runPyResult = await runPyFunc(pythonPath, "--version");
82-
if (!runPyResult.Success || !runPyResult.Result.StartsWith("Python 3"))
83-
{
84-
logger.LogError("Python version response incorrect: {Response} | {Error}", runPyResult.Result,
85-
runPyResult.Error);
86-
return false;
90+
var verifyPythonInstallResult = await runPyFunc(pythonPath, "--version");
91+
if (!verifyPythonInstallResult.Success || !verifyPythonInstallResult.Result.StartsWith("Python 3.13"))
92+
{
93+
logger.LogError("Python version response incorrect: {Response} | {Error}", verifyPythonInstallResult.Result,
94+
verifyPythonInstallResult.Error);
95+
return false;
96+
}
8797
}
88-
98+
8999
response.Invoke("Installing companion app");
90100

91-
runPyResult = await runPyFunc(pythonPath, "-m pip install py-msu-scripter-app");
101+
var runPyResult = await runPyFunc(pythonPath, "-m pip install --upgrade py-msu-scripter-app");
92102
if (!runPyResult.Success && !runPyResult.Error.StartsWith("[notice]"))
93103
{
94104
logger.LogError("Failed to install Python companion app: {Error}", runPyResult.Error);

MSUScripter/Services/PythonCompanionService.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
using AvaloniaControls.Services;
99
using Microsoft.Extensions.Logging;
1010
using MSUScripter.Models;
11+
using MSUScripter.Tools;
1112

1213
namespace MSUScripter.Services;
1314

1415
public class PythonCompanionService(ILogger<PythonCompanionService> logger, YamlService yamlService, DependencyInstallerService dependencyInstallerService)
1516
{
1617
private const string BaseCommand = "py_msu_scripter_app";
17-
private const string MinVersion = "v0.1.5";
18+
private const string MinVersion = "v0.1.9";
1819
private RunMethod _runMethod = RunMethod.Unknown;
1920
private string? _pythonExecutablePath;
2021
private string? _ffmpegPath;
@@ -34,7 +35,7 @@ public async Task<bool> VerifyInstalledAsync()
3435
_pythonExecutablePath = null;
3536
var response = await RunCommandAsync("--version");
3637

37-
IsValid = response.Success && response.Result.EndsWith(MinVersion) &&
38+
IsValid = response.Success && VerifyVersionNumber(response.Result) &&
3839
!response.Error.Contains("Couldn't find ffmpeg");
3940

4041
if (IsValid)
@@ -49,6 +50,13 @@ public async Task<bool> VerifyInstalledAsync()
4950
return IsValid;
5051
}
5152

53+
private bool VerifyVersionNumber(string versionString)
54+
{
55+
var minVersion = MinVersion.VersionStringToDecimal();
56+
var currentVersion = versionString.Substring(versionString.IndexOf('v')).VersionStringToDecimal();
57+
return currentVersion >= minVersion;
58+
}
59+
5260
public async Task<bool> VerifyFfMpegAsync()
5361
{
5462
var ffmpegFolder = Path.Combine(Directories.Dependencies, "ffmpeg", "bin");
@@ -603,7 +611,7 @@ private async Task<RunPyResult> RunInternalAsync(string command, string argument
603611
await process.WaitForExitAsync(cancellationToken ?? CancellationToken.None);
604612

605613
var resultText = "";
606-
var errorText = "";
614+
string errorText;
607615

608616
if (isPipInstall)
609617
{

MSUScripter/Tools/StringExtensions.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,33 @@ public static int GetUnicodeLength(this string str)
1414
{
1515
return new StringInfo(str.CleanString()).LengthInTextElements;
1616
}
17+
18+
public static decimal? VersionStringToDecimal(this string str)
19+
{
20+
if (!str.Contains('.'))
21+
{
22+
return null;
23+
}
24+
25+
if (str.StartsWith('v'))
26+
{
27+
str = str[1..];
28+
}
29+
30+
var parts = str.Split('.');
31+
if (parts.Length <= 2)
32+
{
33+
return decimal.Parse(str);
34+
}
35+
else if (parts.Length == 3)
36+
{
37+
return decimal.Parse(parts[0]) * 1000 + decimal.Parse(parts[1]) + decimal.Parse(parts[2]) / 1000;
38+
}
39+
else if (parts.Length == 4)
40+
{
41+
return decimal.Parse(parts[0]) * 1000000 + decimal.Parse(parts[1]) * 1000 + decimal.Parse(parts[2]) + decimal.Parse(parts[3]) / 1000;
42+
}
43+
44+
return null;
45+
}
1746
}

MSUScripter/Views/MainWindow.axaml.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ private async Task ShowDesktopFileWindow()
126126

127127
private async Task ShowNewReleaseWindow(string releaseUrl, string? downloadUrl)
128128
{
129+
if (_service == null)
130+
{
131+
return;
132+
}
133+
129134
downloadUrl ??= "";
130135
var hasDownloadUrl = !string.IsNullOrEmpty(downloadUrl);
131136

@@ -151,7 +156,7 @@ private async Task ShowNewReleaseWindow(string releaseUrl, string? downloadUrl)
151156

152157
if (messageWindow.DialogResult.CheckedBox)
153158
{
154-
_service?.IgnoreFutureUpdates();
159+
_service.IgnoreFutureUpdates();
155160
}
156161

157162
if (messageWindow.DialogResult.PressedAcceptButton)
@@ -178,7 +183,15 @@ private async Task ShowNewReleaseWindow(string releaseUrl, string? downloadUrl)
178183
}
179184
else
180185
{
181-
throw new InvalidOperationException("Not supported on Windows");
186+
var result = await _service.InstallWindowsUpdate(downloadUrl);
187+
if (!string.IsNullOrEmpty(result))
188+
{
189+
await MessageWindow.ShowErrorDialog(result);
190+
}
191+
else
192+
{
193+
Close();;
194+
}
182195
}
183196
}
184197
}

PyMsuScripterApp/py_msu_scripter_app/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def cli():
3838
print("Error: the input YAML file was not found")
3939
exit(1)
4040

41-
with open(args.input, "r") as stream:
41+
with open(args.input, "r", encoding="utf-8") as stream:
4242
try:
4343
yaml_file = yaml.safe_load(stream)
4444
except yaml.YAMLError as exc:

0 commit comments

Comments
 (0)