diff --git a/Knossos.NET/App.axaml b/Knossos.NET/App.axaml
index 86cf03f5..07fbaa99 100644
--- a/Knossos.NET/App.axaml
+++ b/Knossos.NET/App.axaml
@@ -15,6 +15,7 @@
+
diff --git a/Knossos.NET/Knossos.NET.csproj b/Knossos.NET/Knossos.NET.csproj
index cec9d8d6..501faaaf 100644
--- a/Knossos.NET/Knossos.NET.csproj
+++ b/Knossos.NET/Knossos.NET.csproj
@@ -70,6 +70,7 @@
+
@@ -90,6 +91,9 @@
DeveloperModsView.axaml
+
+ DevModAdvancedUploadView.axaml
+
MessageBox.axaml
diff --git a/Knossos.NET/Models/Mod.cs b/Knossos.NET/Models/Mod.cs
index 77c24543..9eab44c1 100644
--- a/Knossos.NET/Models/Mod.cs
+++ b/Knossos.NET/Models/Mod.cs
@@ -779,6 +779,7 @@ public async Task LoadFulLNebulaData()
///
/// To use with the List .Sort()
+ /// Retuns a list that is sort from older to newer
///
///
///
@@ -788,6 +789,18 @@ public static int CompareVersion(Mod mod1, Mod mod2)
return SemanticVersion.Compare(mod1.version, mod2.version);
}
+ ///
+ /// To use with the List .Sort()
+ /// Retuns a list that is sort from newer to older
+ ///
+ ///
+ ///
+ public static int CompareVersionNewerToOlder(Mod mod1, Mod mod2)
+ {
+ //inverted
+ return SemanticVersion.Compare(mod2.version, mod1.version);
+ }
+
///
/// Compares two mods and determines if the metadata is different
/// Full data must be loaded on both mods for this to work properly
diff --git a/Knossos.NET/ViewModels/TaskViewModel.cs b/Knossos.NET/ViewModels/TaskViewModel.cs
index 9daf2131..bcd7725c 100644
--- a/Knossos.NET/ViewModels/TaskViewModel.cs
+++ b/Knossos.NET/ViewModels/TaskViewModel.cs
@@ -436,7 +436,10 @@ public async void CreateModVersion(Mod oldMod, string newVersion, Action hackCal
///
///
///
- public async void UploadModVersion(Mod mod, bool isNewMod, bool metadataonly = false)
+ ///
+ ///
+ ///
+ public async void UploadModVersion(Mod mod, bool isNewMod, bool metadataonly = false, int parallelCompression = 1, int parallelUploads = 1, List? advData = null )
{
using (var cancelSource = new CancellationTokenSource())
{
@@ -446,7 +449,7 @@ public async void UploadModVersion(Mod mod, bool isNewMod, bool metadataonly = f
TaskList.Add(newTask);
taskQueue.Enqueue(newTask);
});
- await newTask.UploadModVersion(mod, isNewMod, metadataonly, cancelSource).ConfigureAwait(false);
+ await newTask.UploadModVersion(mod, isNewMod, metadataonly, cancelSource, parallelCompression, parallelUploads, advData).ConfigureAwait(false);
}
}
diff --git a/Knossos.NET/ViewModels/Templates/DevModVersionsViewModel.cs b/Knossos.NET/ViewModels/Templates/DevModVersionsViewModel.cs
index bd9aa9bb..4b95cf28 100644
--- a/Knossos.NET/ViewModels/Templates/DevModVersionsViewModel.cs
+++ b/Knossos.NET/ViewModels/Templates/DevModVersionsViewModel.cs
@@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
namespace Knossos.NET.ViewModels
{
@@ -217,7 +218,44 @@ public void HackUpdateModList()
Mods = Mods.ToList();
}
+ internal async void UploadModAdvanced()
+ {
+ var mod = editor?.ActiveVersion;
+ if (mod != null)
+ {
+ if (!mod.inNebula)
+ {
+ if (mod.type == ModType.mod || mod.type == ModType.tc)
+ {
+ var dialog = new DevModAdvancedUploadView();
+ dialog.DataContext = new DevModAdvancedUploadViewModel(mod, dialog, this);
+ await dialog.ShowDialog(MainWindow.instance!);
+ }
+ else
+ {
+ _ = MessageBox.Show(MainWindow.instance!, "Advanced uploading is only available for Mods and TCs.", "Unsupported for type: " + mod.type, MessageBox.MessageBoxButtons.OK);
+ }
+ }
+ else
+ {
+ _ = MessageBox.Show(MainWindow.instance!, "This mod version is already uploaded to nebula, if you want to update the metadata use the basic upload", "Mod already uploaded", MessageBox.MessageBoxButtons.OK);
+ }
+ }
+ }
+
+ //For UI button
internal async void UploadMod()
+ {
+ await UploadProcess();
+ }
+
+ //for the advanced upload window
+ public void AdvancedUpload(List advData, int parallelCompression, int parallelUploads)
+ {
+ _ = UploadProcess(advData, parallelCompression, parallelUploads);
+ }
+
+ private async Task UploadProcess(List? advData = null, int parallelCompression = 1, int parallelUploads = 1)
{
/*
* Pre-Upload Checks:
@@ -502,7 +540,7 @@ await MessageBox.Show(MainWindow.instance!, "Your mod depends on mod id: " + dep
//Basic check finish, create task
ButtonsEnabled = true;
- TaskViewModel.Instance!.UploadModVersion(mod, isNewMod, metaUpdOnly);
+ TaskViewModel.Instance!.UploadModVersion(mod, isNewMod, metaUpdOnly, parallelCompression, parallelUploads, advData);
}
}
catch(Exception ex)
diff --git a/Knossos.NET/ViewModels/Templates/TaskItemViewModel.cs b/Knossos.NET/ViewModels/Templates/TaskItemViewModel.cs
index 1c2a5cb4..44786f3f 100644
--- a/Knossos.NET/ViewModels/Templates/TaskItemViewModel.cs
+++ b/Knossos.NET/ViewModels/Templates/TaskItemViewModel.cs
@@ -1522,7 +1522,7 @@ private async Task PrepareModPkg(ModPackage pkg, string modFullPath, Cance
}
}
- public async Task UploadModVersion(Mod mod, bool isNewMod, bool metaOnly, CancellationTokenSource? cancelSource = null)
+ public async Task UploadModVersion(Mod mod, bool isNewMod, bool metaOnly, CancellationTokenSource? cancelSource = null, int parallelCompression = 1, int parallelUploads = 1, List? advData = null )
{
try
{
@@ -1607,13 +1607,42 @@ public async Task UploadModVersion(Mod mod, bool isNewMod, bool metaOnly,
Info = "Prepare Packages";
Directory.CreateDirectory(mod.fullPath + Path.DirectorySeparatorChar + "kn_upload");
//Prepare packages, update data on mod
- await Parallel.ForEachAsync(mod.packages, new ParallelOptions { MaxDegreeOfParallelism = Knossos.globalSettings.compressionMaxParallelism }, async (pkg, token) =>
+ await Parallel.ForEachAsync(mod.packages, new ParallelOptions { MaxDegreeOfParallelism = parallelCompression }, async (pkg, token) =>
{
- if (mod.type != ModType.mod && mod.type != ModType.tc) //Just to be sure
- pkg.isVp = false;
- var newTask = new TaskItemViewModel();
- await Dispatcher.UIThread.InvokeAsync(() => TaskList.Insert(0, newTask));
- await newTask.PrepareModPkg(pkg, mod.fullPath, cancellationTokenSource);
+ bool skipPkg = false;
+ //We should skip this?
+ if(advData != null)
+ {
+ var advDataPkg = advData.FirstOrDefault(p => p.packageInNebula != null && p.packageInNebula!.name == pkg.name);
+ if (advDataPkg != null && !advDataPkg.Upload)
+ {
+ var uploadedPkg = advDataPkg.packageInNebula;
+ if (uploadedPkg != null)
+ {
+ pkg.notes = uploadedPkg.notes;
+ pkg.isVp = uploadedPkg.isVp;
+ pkg.status = uploadedPkg.status;
+ pkg.filelist = uploadedPkg.filelist;
+ pkg.files = uploadedPkg.files;
+ pkg.dependencies = uploadedPkg.dependencies;
+ pkg.environment = uploadedPkg.environment;
+ pkg.executables = uploadedPkg.executables;
+ pkg.folder = uploadedPkg.folder;
+ pkg.checkNotes = uploadedPkg.checkNotes;
+ pkg.files?.ForEach(f => f.urls = null); //Cant send urls to Nebula or it gets rejected
+ skipPkg = true;
+ Log.Add(Log.LogSeverity.Information, "TaskItemViewModel.UploadModVersion()", "Skipping package preparation for :" + pkg.name + ". Data was loaded from Nebula.");
+ }
+ }
+ }
+ if (!skipPkg)
+ {
+ if (mod.type != ModType.mod && mod.type != ModType.tc) //Just to be sure
+ pkg.isVp = false;
+ var newTask = new TaskItemViewModel();
+ await Dispatcher.UIThread.InvokeAsync(() => TaskList.Insert(0, newTask));
+ await newTask.PrepareModPkg(pkg, mod.fullPath, cancellationTokenSource);
+ }
ProgressCurrent++;
if (cancellationTokenSource.IsCancellationRequested)
throw new TaskCanceledException();
@@ -1621,11 +1650,29 @@ public async Task UploadModVersion(Mod mod, bool isNewMod, bool metaOnly,
Info = "Upload Packages";
//Upload Packages
- await Parallel.ForEachAsync(mod.packages, new ParallelOptions { MaxDegreeOfParallelism = 1 }, async (pkg, token) =>
+ await Parallel.ForEachAsync(mod.packages, new ParallelOptions { MaxDegreeOfParallelism = parallelUploads }, async (pkg, token) =>
{
- var newTask = new TaskItemViewModel();
- await Dispatcher.UIThread.InvokeAsync(() => TaskList.Insert(0, newTask));
- await newTask.UploadModPkg(pkg, mod.fullPath, cancellationTokenSource);
+ bool skipPkg = false;
+ //We should skip this?
+ if (advData != null)
+ {
+ var advDataPkg = advData.FirstOrDefault(p => p.packageInNebula != null && p.packageInNebula!.name == pkg.name);
+ if (advDataPkg != null && !advDataPkg.Upload)
+ {
+ var uploadedPkg = advDataPkg.packageInNebula;
+ if (uploadedPkg != null)
+ {
+ skipPkg = true;
+ Log.Add(Log.LogSeverity.Information, "TaskItemViewModel.UploadModVersion()", "Skipping package upload for :" + pkg.name + ". Used the one in Nebula instead, file hash: " + advDataPkg.CustomHash );
+ }
+ }
+ }
+ if (!skipPkg)
+ {
+ var newTask = new TaskItemViewModel();
+ await Dispatcher.UIThread.InvokeAsync(() => TaskList.Insert(0, newTask));
+ await newTask.UploadModPkg(pkg, mod.fullPath, cancellationTokenSource);
+ }
ProgressCurrent++;
if (cancellationTokenSource.IsCancellationRequested)
throw new TaskCanceledException();
diff --git a/Knossos.NET/ViewModels/Windows/DevModAdvancedUploadViewModel.cs b/Knossos.NET/ViewModels/Windows/DevModAdvancedUploadViewModel.cs
new file mode 100644
index 00000000..d73db93c
--- /dev/null
+++ b/Knossos.NET/ViewModels/Windows/DevModAdvancedUploadViewModel.cs
@@ -0,0 +1,278 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Knossos.NET.Models;
+using Knossos.NET.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Knossos.NET.ViewModels
+{
+ public partial class DevModAdvancedUploadData : ObservableObject
+ {
+ [ObservableProperty]
+ internal List otherVersions = new List();
+
+ internal int otherVersionsSelectedIndex = 0;
+ internal int OtherVersionsSelectedIndex
+ {
+ get { return otherVersionsSelectedIndex; }
+ set
+ {
+ if (otherVersionsSelectedIndex != value)
+ {
+ this.SetProperty(ref otherVersionsSelectedIndex, value);
+ if (value == 0 ) // Auto
+ {
+ CustomHash = "";
+ }
+ else
+ {
+ if(package == null)
+ {
+ CustomHash = "Error: Local package was null, this should not happen.";
+ return;
+ }
+ try
+ {
+ //Get package data from nebula and let the cache do its thing
+ if (packageInNebula == null)
+ {
+ if (OtherVersions[value].DataContext == null)
+ {
+ CustomHash = "Error: Datacontext was null";
+ return;
+ }
+ CustomHash = "Loading...";
+ var m = (Mod)OtherVersions[value].DataContext!;
+ Task.Run(async () =>
+ {
+ try
+ {
+ var modTask = await Nebula.GetModData(mod!.id, m.version);
+ if (modTask == null)
+ {
+ CustomHash = "Error: Unable to get data from Nebula";
+ return;
+ }
+ packageInNebula = modTask.packages.FirstOrDefault(p => p.name == package.name);
+ if (packageInNebula == null)
+ {
+ CustomHash = "Error: Package was not found on selected version, or Nebula error.";
+ return;
+ }
+ CustomHash = packageInNebula!.files!.FirstOrDefault()!.checksum![1].ToString();
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "DevModAdvancedUploadData(OtherVersionsSelectedIndex.Setter2)", ex);
+ CustomHash = "Error in getting data from Nebula";
+ };
+ });
+ }
+ else
+ {
+ CustomHash = packageInNebula!.files!.FirstOrDefault()!.checksum![1].ToString();
+ }
+ }
+ catch (Exception ex)
+ {
+ CustomHash = "Error getting hash";
+ Log.Add(Log.LogSeverity.Error, "DevModAdvancedUploadData(OtherVersionsSelectedIndex.Setter3)", ex);
+ }
+ }
+ }
+ }
+ }
+
+ internal bool upload = true;
+ internal bool Upload
+ {
+ get { return upload; }
+ set
+ {
+ if (upload != value)
+ {
+ this.SetProperty(ref upload, value);
+ try
+ {
+ if (value == false)
+ {
+ //Enable all Versions listed in the combobox
+ OtherVersions.ForEach(o => o.IsEnabled = true);
+ OtherVersions[0].IsEnabled = false; // Disable "auto"
+ CustomHash = "";
+ //Select what should be the latest version of a mod id
+ OtherVersionsSelectedIndex = OtherVersions.Count() - 1;
+ }
+ else
+ {
+ //Disable all versions listed in the combobox
+ OtherVersions.ForEach(o => o.IsEnabled = false);
+ OtherVersions[0].IsEnabled = true; //Enable Auto
+ //Select Auto
+ OtherVersionsSelectedIndex = 0;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Add(Log.LogSeverity.Error, "DevModAdvancedUploadData(Upload.Setter)", ex);
+ }
+ }
+ }
+ }
+
+ [ObservableProperty]
+ internal string customHash = "";
+
+ public ModPackage? package;
+ public ModPackage? packageInNebula = null;
+ private Mod? mod;
+
+ public string PackageName
+ {
+ get
+ {
+ if (package != null)
+ return package.name;
+ else
+ return "package is null";
+ }
+ }
+
+
+ public DevModAdvancedUploadData(ModPackage package, Mod mod, List allVersionsOfMod)
+ {
+ this.package = package;
+ this.mod = mod;
+
+ //Fill Get Hash Combobox
+ var auto = new ComboBoxItem();
+ auto.Content = "Auto";
+ auto.IsEnabled = true;
+ OtherVersions.Add(auto);
+
+ foreach (var o in allVersionsOfMod)
+ {
+ if (o.version != mod.version)
+ {
+ //List version in the combobox
+ var item = new ComboBoxItem();
+ item.DataContext = o;
+ item.Content = o.version.ToString();
+ item.IsEnabled = false;
+ item.Foreground = o.isPrivate ? Brushes.Red : Brushes.Cyan;
+ OtherVersions.Add(item);
+ }
+ }
+ OtherVersionsSelectedIndex = 0;
+ }
+ }
+
+ /********************************************************************************/
+
+ public partial class DevModAdvancedUploadViewModel : ViewModelBase
+ {
+ [ObservableProperty]
+ internal string title = string.Empty;
+ [ObservableProperty]
+ internal bool loading = true;
+ [ObservableProperty]
+ internal int parallelCompressing = 1;
+ [ObservableProperty]
+ internal int parallelUploads = 1;
+ [ObservableProperty]
+ internal List modPackagesData = new List();
+
+ private Mod? uploadMod = null;
+ private DevModAdvancedUploadView? dialog = null;
+ private DevModVersionsViewModel? versionsViewModel = null;
+ public DevModAdvancedUploadViewModel()
+ {
+ }
+
+ public DevModAdvancedUploadViewModel(Mod mod, DevModAdvancedUploadView? dialog, DevModVersionsViewModel? versionsViewModel)
+ {
+ this.dialog = dialog;
+ this.versionsViewModel = versionsViewModel;
+ Title = "Advanced Nebula Upload: " + mod;
+ uploadMod = mod;
+ _ = Task.Run(() => { LazyLoading(); });
+ this.versionsViewModel = versionsViewModel;
+ }
+
+ private async void LazyLoading()
+ {
+ if (uploadMod != null && uploadMod.packages != null)
+ {
+ var allVersionsOfThisMod = await Nebula.GetAllModsWithID(uploadMod.id);
+ allVersionsOfThisMod.Sort(Mod.CompareVersionNewerToOlder);
+ foreach (var package in uploadMod.packages)
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ var data = new DevModAdvancedUploadData(package, uploadMod, allVersionsOfThisMod);
+ ModPackagesData.Add(data);
+ });
+ }
+ }
+ Loading = false;
+ }
+
+ internal async void UploadMod()
+ {
+ //We have to check that all packages we arent going to upload has a valid sha256 and that they had been uploaded to nebula
+ //Basic check
+ foreach (var data in ModPackagesData)
+ {
+ if (!data.upload)
+ {
+ if (String.IsNullOrWhiteSpace(data.CustomHash) || data.CustomHash.Length == 0)
+ {
+ _ = MessageBox.Show(MainWindow.instance!, "Package: " + data.PackageName + " is not set to upload but it lacks a defined sha256. Operation cancelled.", "Missing hash data", MessageBox.MessageBoxButtons.OK);
+ return;
+ }
+ else
+ {
+ //ensure it is in lowercase
+ data.CustomHash = data.CustomHash.ToLower();
+ }
+
+ if(data.packageInNebula == null)
+ {
+ _ = MessageBox.Show(MainWindow.instance!, "Package: " + data.PackageName + " is not set to upload but it lacks the package data from Nebula. Operation cancelled.", "Missing package data", MessageBox.MessageBoxButtons.OK);
+ return;
+ }
+ }
+ }
+
+ //Check with nebula if the file is uploaded
+ //Disabled, no need since we are getting the file hash from nebula the file HAS to be uploaded.
+ /*
+ foreach (var data in ModPackagesData)
+ {
+ if (!data.upload)
+ {
+ if(await Nebula.IsFileUploaded(data.CustomHash) == false)
+ {
+ _ = MessageBox.Show(MainWindow.instance!, "The provided sha256 hash for package: " + data.PackageName + " is not valid or not uploaded to Nebula, operation cancelled. Passed hash:" + data.CustomHash, "File hash is not uploaded to nebula (or it is incorrect)", MessageBox.MessageBoxButtons.OK);
+ Log.Add(Log.LogSeverity.Error,"DevModAdvancedUploadViewModel.UploadMod", "The provided sha256 hash for package: " + data.PackageName + " is not valid or not uploaded to Nebula, operation cancelled. Passed hash:"+data.CustomHash);
+ return;
+ }
+ await Task.Delay(1000);
+ }
+ }
+ */
+
+ //Send to upload and close window
+ await Dispatcher.UIThread.InvokeAsync( ()=> {
+ versionsViewModel?.AdvancedUpload(ModPackagesData, ParallelCompressing, ParallelUploads);
+ dialog?.Close();
+ });
+ }
+ }
+}
diff --git a/Knossos.NET/Views/Templates/DevModVersionsView.axaml b/Knossos.NET/Views/Templates/DevModVersionsView.axaml
index 0d497f37..b52a072c 100644
--- a/Knossos.NET/Views/Templates/DevModVersionsView.axaml
+++ b/Knossos.NET/Views/Templates/DevModVersionsView.axaml
@@ -48,9 +48,10 @@
-
+
-
+
+
diff --git a/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml b/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml
new file mode 100644
index 00000000..b3cf77b4
--- /dev/null
+++ b/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml.cs b/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml.cs
new file mode 100644
index 00000000..3b8cd273
--- /dev/null
+++ b/Knossos.NET/Views/Windows/DevModAdvancedUploadView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Knossos.NET.Views;
+
+public partial class DevModAdvancedUploadView : Window
+{
+ public DevModAdvancedUploadView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file