diff --git a/src/Digitalroot.OdinPlusModUploader/Digitalroot.OdinPlusModUploader.csproj b/src/Digitalroot.OdinPlusModUploader/Digitalroot.OdinPlusModUploader.csproj
index a1589af..e403dfd 100644
--- a/src/Digitalroot.OdinPlusModUploader/Digitalroot.OdinPlusModUploader.csproj
+++ b/src/Digitalroot.OdinPlusModUploader/Digitalroot.OdinPlusModUploader.csproj
@@ -29,7 +29,7 @@
true
False
False
- 1.0.6
+ 1.0.7
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json;
https://digitalroot-valheim-nuget.s3.us-west-2.amazonaws.com/index.json
diff --git a/src/Digitalroot.OdinPlusModUploader/Http/Message.cs b/src/Digitalroot.OdinPlusModUploader/Http/Message.cs
index b041c3a..02ad1fd 100644
--- a/src/Digitalroot.OdinPlusModUploader/Http/Message.cs
+++ b/src/Digitalroot.OdinPlusModUploader/Http/Message.cs
@@ -1,18 +1,8 @@
using Digitalroot.OdinPlusModUploader.Models;
using Digitalroot.OdinPlusModUploader.Protocol;
-using System;
namespace Digitalroot.OdinPlusModUploader.Http
{
- [Obsolete]
- public sealed class Message
- {
- public AbstractRequest Request { get; set; }
- public AbstractRequestModel RequestModel { get; set; }
- public AbstractResponse Response { get; set; }
- public AbstractResponseModel ResponseModel { get; set; }
- }
-
public sealed class Message
where TRequest : AbstractRequest
where TRequestModel : AbstractRequestModel
@@ -24,6 +14,5 @@ public sealed class Message
public TResponse Response { get; set; }
public TResponseModel ResponseModel { get; set; }
public ErrorResponseModel ErrorResponseModel { get; set; }
-
}
}
diff --git a/src/Digitalroot.OdinPlusModUploader/Program.cs b/src/Digitalroot.OdinPlusModUploader/Program.cs
index 091a549..fff1efc 100644
--- a/src/Digitalroot.OdinPlusModUploader/Program.cs
+++ b/src/Digitalroot.OdinPlusModUploader/Program.cs
@@ -61,7 +61,7 @@ public static async Task Main(string[] args)
private static IHelpBuilder GetHelpBuilder(BindingContext arg)
{
- #if DEBUG2
+ #if DEBUG
return GetUnMaskingHelpBuilder(Console.WindowWidth);
#else
return GetMaskingHelpBuilder(Console.WindowWidth);
@@ -70,7 +70,7 @@ private static IHelpBuilder GetHelpBuilder(BindingContext arg)
private static IHelpBuilder GetHelpBuilder(int maxWidth)
{
- #if DEBUG2
+ #if DEBUG
return GetUnMaskingHelpBuilder(maxWidth);
#else
return GetMaskingHelpBuilder(maxWidth);
diff --git a/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Commands/UploadCommand.cs b/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Commands/UploadCommand.cs
index af2439d..c0b9fb4 100644
--- a/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Commands/UploadCommand.cs
+++ b/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Commands/UploadCommand.cs
@@ -10,15 +10,17 @@
using Digitalroot.OdinPlusModUploader.Utils;
using Pastel;
using System;
-using System.Collections.Concurrent;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
+// ReSharper disable InconsistentNaming
+
#pragma warning disable CS1998
// ReSharper disable UseObjectOrCollectionInitializer
@@ -26,6 +28,8 @@ namespace Digitalroot.OdinPlusModUploader.Provider.NexusMods.Commands;
internal static class UploadCommand
{
+ private static readonly AutoResetEvent _autoEvent = new(false);
+
internal static ICommand GetUploadCommand()
{
var command = new Command("upload", $"Upload a file of {FileSizeFormatter.FormatSize(NexusModsRestClient.MaxFileSize).Pastel(ColorOptions.WarningColor)} or less to nexusmods.com")
@@ -51,8 +55,6 @@ internal static ICommand GetUploadCommand()
return command;
}
- private static readonly ConcurrentBag TaskList = new();
-
private static ICommandHandler GetCommandHandler()
{
return CommandHandler.Create<
@@ -114,56 +116,123 @@ private static ICommandHandler GetCommandHandler()
// Check file size and get chunkCount
var totalChunks = GetChunkCount(archiveFile);
+ // Upload all chunks but the last one.
+ var r = Parallel.For(1, totalChunks, new ParallelOptions { MaxDegreeOfParallelism = 2 }, RunUploadWorkFlow);
+ RunUploadWorkFlow(totalChunks); // Upload the last file chunk
+ _autoEvent.WaitOne();
+
+ #region Run Workflow
+
async void RunUploadWorkFlow(int i)
{
- var workflow = GetUploadWorkflow(i
- , totalChunks
- , archiveFile
- , cookie
- , modId
- , fileName
- , version
- , game
- , disableDownloadWithManager
- , disableVersionUpdate
- , disableMainVortex
- , category
- , description
- , gameInfoMessage
- , disableRequirementsPopUp
- , disableMainFileUpdate
- , oldFileId
- , !disableMainFileUpdate
- , !disableMainFileUpdate);
-
- TaskList.Add(new TaskInfo(workflow, i, "task1"));
- await workflow.WaitAsync(new TimeSpan(3, 0, 0));
+ try
+ {
+ var uploadFileChunk = await GetUploadWorkflow(i
+ , totalChunks
+ , archiveFile
+ , cookie
+ , modId
+ , fileName
+ , version
+ , game
+ , disableDownloadWithManager
+ , disableVersionUpdate
+ , disableMainVortex
+ , category
+ , description
+ , gameInfoMessage
+ , disableRequirementsPopUp
+ , disableMainFileUpdate
+ , oldFileId
+ , !disableMainFileUpdate
+ , !disableMainFileUpdate
+ );
+ if (uploadFileChunk == null) return;
+ // if (uploadFileChunk.RequestModel.ResumableChunkNumber != totalChunks) return;
+
+ // Report on the results
+ if (!uploadFileChunk.ResponseModel.Uuid.HasValue())
+ {
+ Console.WriteLine("File uploaded to Nexus Mods, but Id is null".Pastel(ColorOptions.ErrorColor));
+ Console.WriteLine();
+ Console.WriteLine("Response Details:".Pastel(ColorOptions.ErrorColor));
+ Console.WriteLine($"{nameof(uploadFileChunk.ResponseModel.UploadFileHash)}: {uploadFileChunk.ResponseModel.UploadFileHash}".Pastel(ColorOptions.ErrorColor));
+ Console.WriteLine($"{nameof(uploadFileChunk.ResponseModel.Status)}: {uploadFileChunk.ResponseModel.Status}".Pastel(ColorOptions.ErrorColor));
+ Console.WriteLine($"{nameof(uploadFileChunk.ResponseModel.Uuid)}: {uploadFileChunk.ResponseModel.Uuid}".Pastel(ColorOptions.ErrorColor));
+ }
+ else
+ {
+ Console.WriteLine($"File successfully uploaded to Nexus Mods with Id '{uploadFileChunk.ResponseModel.Uuid}'".Pastel(ColorOptions.SuccessColor));
+ }
+
+ // Check for file assemble
+ Message<
+ CheckFileStatusRequest,
+ CheckFileStatusRequestModel,
+ CheckFileStatusResponse,
+ CheckFileStatusResponseModel
+ > checkFileStatus = await CheckFileStatus(cookie, uploadFileChunk.ResponseModel);
+
+ // Attach file to Mod.
+ if (checkFileStatus.Response.IsSuccessful && checkFileStatus.ResponseModel.FileChunksReassembled)
+ {
+ Console.WriteLine($"File '{checkFileStatus.RequestModel.Uuid}' confirmed as assembled.".Pastel(ColorOptions.SuccessColor));
+
+ // ReSharper disable once UnusedVariable
+ Message<
+ AddFileToModRequest,
+ AddFileToModRequestModel,
+ AddFileToModResponse,
+ AddFileToModResponseModel
+ > addFileToMod = await AddFileToMod(cookie
+ , modId
+ , version
+ , disableVersionUpdate
+ , disableMainVortex
+ , fileName
+ , archiveFile
+ , category
+ , description
+ , disableDownloadWithManager
+ , disableRequirementsPopUp
+ , gameInfoMessage.ResponseModel.Id
+ , checkFileStatus.RequestModel.Uuid
+ , checkFileStatus.RequestModel.FileHash
+ , disableMainFileUpdate
+ , oldFileId
+ , !disableMainFileUpdate
+ , !disableMainFileUpdate
+ );
+
+ if (addFileToMod.Response.IsSuccessful)
+ {
+ Trace.WriteLine("Workflow complete.".Pastel(ColorOptions.StatusColor));
+ _autoEvent.Set();
+ return;
+ }
+
+ throw new Exception("Workflow Failed");
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.Message.Pastel(ColorOptions.ErrorColor));
+ Console.WriteLine(e.StackTrace.Pastel(ColorOptions.ErrorColor));
+ _autoEvent.Set();
+ }
}
- // Upload all chunks but the last one.
- Parallel.For(1
- , totalChunks
- , new ParallelOptions
- {
- MaxDegreeOfParallelism = 2
- }
- , RunUploadWorkFlow);
-
- ProcessAllAsyncTasks();
- RunUploadWorkFlow(totalChunks); // Upload the last file chunk
- ProcessAllAsyncTasks();
-
- Trace.WriteLine($"Tasks: {TaskList.Count}, Completed: {TaskList.Count(t => t.Task.IsCompletedSuccessfully)}, Cancelled:{TaskList.Count(t => t.Task.IsCanceled)}, Faulted: {TaskList.Count(t => t.Task.IsFaulted)}");
+ #endregion
});
}
#region Workflow
- private static Task> GetUploadWorkflow(int i,
int totalChunks,
FileInfo archiveFile,
@@ -185,169 +254,80 @@ int oldFileId
, bool removeOldVersion)
{
// Check if chunk already uploaded
- Task> task1 = CheckUploadChunkExists(archiveFile
- , cookie
- , Convert.ToUInt32(i)
- , i != totalChunks
- ? Convert.ToUInt32(NexusModsRestClient.ChunkSize)
- : Convert.ToUInt32(Convert.ToUInt64(archiveFile.Length) % NexusModsRestClient.ChunkSize)
- , Convert.ToUInt32(totalChunks)
- );
+ > checkUploadChunkExists = await CheckUploadChunkExists(archiveFile
+ , cookie
+ , Convert.ToUInt32(i)
+ , i != totalChunks
+ ? Convert.ToUInt32(NexusModsRestClient.ChunkSize)
+ : Convert.ToUInt32(Convert.ToUInt64(archiveFile.Length) % NexusModsRestClient.ChunkSize)
+ , Convert.ToUInt32(totalChunks)
+ );
+
+ if (!checkUploadChunkExists.Response.IsSuccessful) return null;
// Report Results
- var task2 = task1.ContinueWith(antecedent_task1 =>
- {
- if (antecedent_task1.Status == TaskStatus.Canceled) return;
- Console.WriteLine(task1.Result.Response.Exists
- ? $"Chunk {antecedent_task1.Result.RequestModel.ResumableChunkNumber} of {antecedent_task1.Result.RequestModel.ResumableTotalChunks} already exist".Pastel(ColorOptions.SuccessColor)
- : $"Chunk {antecedent_task1.Result.RequestModel.ResumableChunkNumber} of {antecedent_task1.Result.RequestModel.ResumableTotalChunks} does not already exist.".Pastel(ColorOptions.WarningColor));
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
+ Console.WriteLine(checkUploadChunkExists.Response.Exists
+ ? $"Chunk {checkUploadChunkExists.RequestModel.ResumableChunkNumber} of {checkUploadChunkExists.RequestModel.ResumableTotalChunks} already exist".Pastel(ColorOptions.SuccessColor)
+ : $"Chunk {checkUploadChunkExists.RequestModel.ResumableChunkNumber} of {checkUploadChunkExists.RequestModel.ResumableTotalChunks} does not already exist.".Pastel(ColorOptions.WarningColor));
// Upload a chunk of the file
- Task
+ {
+ ResponseModel = checkUploadChunkExists.ResponseModel
+ };
+ }
+
+ // Open File
+ await using var str = new FileStream(archiveFile.FullName,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.Read,
+ Convert.ToInt32(NexusModsRestClient.ChunkSize),
+ true);
+
+ var bufferSize = checkUploadChunkExists.RequestModel.ResumableChunkNumber != totalChunks
+ ? Convert.ToUInt32(NexusModsRestClient.ChunkSize)
+ : Convert.ToUInt32(Convert.ToUInt64(archiveFile.Length) % NexusModsRestClient.ChunkSize);
+
+ var buffer = new byte[bufferSize];
+ var fileOffset = Convert.ToInt64(NexusModsRestClient.ChunkSize) * (checkUploadChunkExists.RequestModel.ResumableChunkNumber - 1);
+ str.Seek(fileOffset, SeekOrigin.Begin);
+ var byteCount = str.Read(buffer);
+
+ str.Close();
+
+ if (byteCount == 0) return null; // No bytes to upload
+
+ Message<
UploadFileChunkRequest,
UploadFileChunkRequestModel,
UploadFileChunkResponse,
UploadFileChunkResponseModel
- >>> task3 = task1.ContinueWith(antecedent_task1 =>
- {
- if (antecedent_task1.Status == TaskStatus.Canceled) return null;
- if (antecedent_task1.Result.ResponseModel?.Status ?? false) return null; // Chunk already uploaded
-
- // Open File
- using var str = new FileStream(archiveFile.FullName,
- FileMode.Open,
- FileAccess.Read,
- FileShare.Read,
- Convert.ToInt32(NexusModsRestClient.ChunkSize),
- true);
-
- var bufferSize = task1.Result.RequestModel.ResumableChunkNumber != totalChunks
- ? Convert.ToUInt32(NexusModsRestClient.ChunkSize)
- : Convert.ToUInt32(Convert.ToUInt64(archiveFile.Length) % NexusModsRestClient.ChunkSize);
-
- var buffer = new byte[bufferSize];
- var fileOffset = Convert.ToInt64(NexusModsRestClient.ChunkSize) * (task1.Result.RequestModel.ResumableChunkNumber - 1);
- str.Seek(fileOffset, SeekOrigin.Begin);
- var byteCount = str.Read(buffer);
-
- str.Close();
-
- if (byteCount == 0) return null; // No bytes to upload
-
- var task = UploadFileChunk(modId
- , archiveFile
- , cookie
- , fileName
- , version
- , game
- , disableDownloadWithManager
- , disableVersionUpdate
- , disableMainVortex
- , task1.Result.RequestModel
- , buffer);
-
- TaskList.Add(new TaskInfo(task, i, nameof(UploadFileChunk)));
-
- return task;
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
- // Report on the results
- var task4 = task3.Unwrap().ContinueWith(antecedent_task3 =>
- {
- if (antecedent_task3.Status == TaskStatus.Canceled) return null;
- if (antecedent_task3.Result.RequestModel.ResumableChunkNumber != totalChunks) return null;
-
- if (!antecedent_task3.Result.ResponseModel.Uuid.HasValue())
- {
- Console.WriteLine("File uploaded to Nexus Mods, but Id is null".Pastel(ColorOptions.ErrorColor));
- Console.WriteLine();
- Console.WriteLine("Response Details:".Pastel(ColorOptions.ErrorColor));
- Console.WriteLine($"{nameof(antecedent_task3.Result.ResponseModel.UploadFileHash)}: {antecedent_task3.Result.ResponseModel.UploadFileHash}".Pastel(ColorOptions.ErrorColor));
- Console.WriteLine($"{nameof(antecedent_task3.Result.ResponseModel.Status)}: {antecedent_task3.Result.ResponseModel.Status}".Pastel(ColorOptions.ErrorColor));
- Console.WriteLine($"{nameof(antecedent_task3.Result.ResponseModel.Uuid)}: {antecedent_task3.Result.ResponseModel.Uuid}".Pastel(ColorOptions.ErrorColor));
- }
- else
- {
- Console.WriteLine($"File successfully uploaded to Nexus Mods with Id '{antecedent_task3.Result.ResponseModel.Uuid}'".Pastel(ColorOptions.SuccessColor));
- }
-
- return antecedent_task3;
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
-
- // Check for file assemble
- var task5 = task4.Unwrap().ContinueWith(antecedent_task4 =>
- {
- if (antecedent_task4.Status == TaskStatus.Canceled) return null;
- var task = CheckFileStatus(cookie, antecedent_task4.Result.ResponseModel);
- TaskList.Add(new TaskInfo(task, i, nameof(CheckFileStatus)));
- return task;
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
-
- // Attach file to Mod.
- var task6 = task5.Unwrap().ContinueWith(antecedent_task5 =>
- {
- if (antecedent_task5.Status == TaskStatus.Canceled) return null;
- if (antecedent_task5.Result.Response.IsSuccessful
- && antecedent_task5.Result.ResponseModel.FileChunksReassembled)
- {
- Console.WriteLine($"File '{antecedent_task5.Result.RequestModel.Uuid}' confirmed as assembled.".Pastel(ColorOptions.SuccessColor));
-
- // ReSharper disable once UnusedVariable
- var task = AddFileToMod(cookie
- , modId
- , version
- , disableVersionUpdate
- , disableMainVortex
- , fileName
- , archiveFile
- , categoryName
- , description
- , disableDownloadWithManager
- , disableRequirementsPopUp
- , gameInfoMessage.ResponseModel.Id
- , antecedent_task5.Result.RequestModel.Uuid
- , antecedent_task5.Result.RequestModel.FileHash
- , disableMainFileUpdate
- , oldFileId
- , newExisting
- , removeOldVersion
- );
-
- TaskList.Add(new TaskInfo(task, i, nameof(AddFileToMod)));
-
- return task;
- }
-
- Console.WriteLine($"Timeout trying to validate file assembled for '{antecedent_task5.Result.RequestModel.Uuid}'".Pastel(ColorOptions.ErrorColor));
- Console.WriteLine("File upload failed.".Pastel(ColorOptions.ErrorColor));
-
- return null;
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
-
- // ReSharper disable once EntityNameCapturedOnly.Local
- // ReSharper disable once RedundantAssignment
- var task7 = task6.Unwrap().ContinueWith(antecedent_task6 =>
- {
- if (antecedent_task6.Status != TaskStatus.Canceled)
- {
- antecedent_task6.Wait();
- Trace.WriteLine("Workflow complete.".Pastel(ColorOptions.StatusColor));
- }
- }, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.OnlyOnRanToCompletion);
-
- // ReSharper restore InconsistentNaming
- TaskList.Add(new TaskInfo(task2, i, nameof(task2)));
- TaskList.Add(new TaskInfo(task3, i, nameof(task3)));
- TaskList.Add(new TaskInfo(task4, i, nameof(task4)));
- TaskList.Add(new TaskInfo(task5, i, nameof(task5)));
- TaskList.Add(new TaskInfo(task6, i, nameof(task6)));
- TaskList.Add(new TaskInfo(task6, i, nameof(task7)));
- return task1;
+ > uploadFileChunk = await UploadFileChunk(modId
+ , archiveFile
+ , cookie
+ , fileName
+ , version
+ , game
+ , disableDownloadWithManager
+ , disableVersionUpdate
+ , disableMainVortex
+ , checkUploadChunkExists.RequestModel
+ , buffer);
+
+ return !uploadFileChunk.Response.IsSuccessful ? null : uploadFileChunk;
}
#endregion
@@ -420,7 +400,7 @@ private static async Task(message.Response.Content);
+ var msg = AbstractResponseModel.FromJson(message.Response.Content.Trim());
Console.WriteLine($"{message.Response?.StatusDescription}: {msg?.Message}.".Pastel(ColorOptions.ErrorColor));
}
}
@@ -514,7 +494,6 @@ private static async Task t.Task.IsCompletedSuccessfully)}, Cancelled:{TaskList.Count(t => t.Task.IsCanceled)}, Faulted: {TaskList.Count(t => t.Task.IsFaulted)}");
- Task.WaitAll(TaskList.Select(t => t.Task).ToArray(), new TimeSpan(0, 30, 0));
- } while (TaskList.Count != TaskList.Count(t => t.Task.IsCompleted));
- }
-
#endregion
}
internal class TaskInfo
{
internal readonly Task Task;
- internal readonly int ChunkNumberNumber;
+ internal readonly int ChunkNumber;
internal readonly string TaskName;
public TaskInfo(Task task, int chunkNumber, string taskName)
{
Task = task;
- ChunkNumberNumber = chunkNumber;
+ ChunkNumber = chunkNumber;
TaskName = taskName;
}
@@ -756,7 +726,7 @@ public TaskInfo(Task task, int chunkNumber, string taskName)
///
public override string ToString()
{
- return $"{ChunkNumberNumber}, {TaskName}, {Task.Status}";
+ return $"{ChunkNumber}, {TaskName}, {Task.Status}";
}
#endregion
diff --git a/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Models/MessageResponseModel.cs b/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Models/MessageResponseModel.cs
index 1043e44..a9325be 100644
--- a/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Models/MessageResponseModel.cs
+++ b/src/Digitalroot.OdinPlusModUploader/Provider/NexusMods/Models/MessageResponseModel.cs
@@ -1,5 +1,6 @@
using Digitalroot.OdinPlusModUploader.Models;
using Newtonsoft.Json;
+using System;
using System.Text;
using System.Text.Json.Serialization;
@@ -10,12 +11,15 @@ internal class MessageResponseModel : AbstractResponseModel
[JsonProperty(PropertyName = "message"), JsonPropertyName("message")]
public string Message { get; set; }
- [JsonProperty(PropertyName = "status"), JsonPropertyName("status")]
+ [JsonProperty(PropertyName = "status"), JsonPropertyName("status"), Newtonsoft.Json.JsonConverter(typeof(StatusConverter))]
public int Status { get; set; }
[JsonProperty(PropertyName = "error"), JsonPropertyName("error")]
public string Error { get; set; }
+ [JsonProperty(PropertyName = "redirect"), JsonPropertyName("redirect")]
+ public string Redirect { get; set; }
+
#region Overrides of Object
///
@@ -30,4 +34,30 @@ public override string ToString()
}
#endregion
+}
+
+public class StatusConverter : Newtonsoft.Json.JsonConverter
+{
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ //if (value is bool)
+ //{
+
+ //}
+ writer.WriteValue(((bool)value) ? 1 : 0);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.ValueType == typeof(bool))
+ {
+ return Convert.ToInt32(reader.Value);
+ }
+ return reader.Value;
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(bool);
+ }
}
\ No newline at end of file