Skip to content

Commit d967df2

Browse files
committed
Update Commands
1 parent d1a10ef commit d967df2

8 files changed

Lines changed: 427 additions & 11 deletions

File tree

UncomplicatedCustomItems/API/Features/Helper/LogManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public static void Error(string message, string error = "CS0000")
3939
History.Add(new(DateTimeOffset.Now.ToUnixTimeMilliseconds(), LogLevel.Warn.ToString(), message, error));
4040
Log.Error(message);
4141
}
42+
public static void Raw(string message, ConsoleColor color)
43+
{
44+
History.Add(new(DateTimeOffset.Now.ToUnixTimeMilliseconds(), LogLevel.Warn.ToString(), message));
45+
Log.SendRaw($"[Updater] [{Plugin.Instance.GetType().Assembly.GetName().Name}] {message}", color);
46+
}
4247

4348
public static void Silent(string message) => History.Add(new(DateTimeOffset.Now.ToUnixTimeMilliseconds(), "SILENT", message));
4449

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
using CommandSystem;
2+
using System.Threading.Tasks;
3+
using System;
4+
using UncomplicatedCustomItems.API.Features.Helper;
5+
using System.IO;
6+
using System.Net.Http;
7+
using Newtonsoft.Json;
8+
using System.Linq;
9+
using System.Runtime.InteropServices;
10+
using Exiled.API.Features;
11+
12+
namespace UncomplicatedCustomItems.Commands.Admin
13+
{
14+
public class GitHubAssetInfo
15+
{
16+
[JsonProperty("name")]
17+
public string Name { get; set; }
18+
19+
[JsonProperty("browser_download_url")]
20+
public string BrowserDownloadUrl { get; set; }
21+
}
22+
23+
[CommandHandler(typeof(GameConsoleCommandHandler))]
24+
public class Update : ParentCommand
25+
{
26+
private const string PluginDllName = "UncomplicatedCustomItems-LabApi.dll";
27+
28+
public Update() => LoadGeneratedCommands();
29+
30+
public override string Command { get; } = "uciupdate";
31+
public override string[] Aliases { get; } = new string[] { "uciselfupdate" };
32+
public override string Description { get; } = "Downloads and installs the latest version of UncomplicatedCustomItems, then restarts the server round.";
33+
34+
public override void LoadGeneratedCommands() { }
35+
36+
protected override bool ExecuteParent(ArraySegment<string> arguments, ICommandSender sender, out string response)
37+
{
38+
Version version = Plugin.Instance.Version;
39+
LogManager.Info($"Current UncomplicatedCustomItems version: {version}. Attempting to update...");
40+
response = $"Attempting to update UncomplicatedCustomItems from version {version}. Check console for details.";
41+
42+
Task.Run(async () =>
43+
{
44+
try
45+
{
46+
using (HttpClient client = new HttpClient())
47+
{
48+
client.DefaultRequestHeaders.Add("User-Agent", "UncomplicatedCustomItems-Updater/1.0");
49+
string apiUrl = "https://api.github.com/repos/UncomplicatedCustomServer/UncomplicatedCustomItems/releases/latest";
50+
HttpResponseMessage httpResponse = await client.GetAsync(apiUrl);
51+
52+
if (!httpResponse.IsSuccessStatusCode)
53+
{
54+
string errorContent = await httpResponse.Content.ReadAsStringAsync();
55+
LogManager.Error($"Failed to fetch latest release info from GitHub. Status: {httpResponse.StatusCode}. Response: {errorContent}");
56+
return;
57+
}
58+
59+
string jsonResponse = await httpResponse.Content.ReadAsStringAsync();
60+
GitHubReleaseInfo latestRelease = JsonConvert.DeserializeObject<GitHubReleaseInfo>(jsonResponse);
61+
62+
if (latestRelease == null || latestRelease.Assets == null || !latestRelease.Assets.Any())
63+
{
64+
LogManager.Error("Failed to parse release information or no assets found in the latest release.");
65+
return;
66+
}
67+
68+
GitHubAssetInfo asset = latestRelease.Assets.FirstOrDefault(asset => asset.Name.Equals(PluginDllName, StringComparison.OrdinalIgnoreCase));
69+
70+
if (asset == null || string.IsNullOrEmpty(asset.BrowserDownloadUrl))
71+
{
72+
LogManager.Error($"Could not find the plugin DLL ('{PluginDllName}') in the latest GitHub release assets, or download URL is missing.");
73+
return;
74+
}
75+
76+
string latestVersionTag = latestRelease.TagName;
77+
if (latestVersionTag.StartsWith("v", StringComparison.OrdinalIgnoreCase))
78+
latestVersionTag = latestVersionTag.Substring(1);
79+
80+
if (Version.TryParse(latestVersionTag, out Version latestGitHubVersion))
81+
{
82+
LogManager.Raw($"Latest version on GitHub: {latestGitHubVersion} (Tag: {latestRelease.TagName}). Current version: {version}.", ConsoleColor.Blue);
83+
if (latestGitHubVersion <= version && arguments.FirstOrDefault()?.ToLower() != "force")
84+
{
85+
LogManager.Raw($"You are already running version {version} or newer. To force update, use 'uciupdate force'.", ConsoleColor.Blue);
86+
return;
87+
}
88+
}
89+
else
90+
LogManager.Warn($"Could not parse latest GitHub version tag '{latestRelease.TagName}'. Proceeding with download if forced or newer by asset name.");
91+
92+
93+
LogManager.Raw($"Downloading {PluginDllName} from {asset.BrowserDownloadUrl}...", ConsoleColor.Blue);
94+
byte[] fileBytes = await client.GetByteArrayAsync(asset.BrowserDownloadUrl);
95+
96+
if (fileBytes == null || fileBytes.Length == 0)
97+
{
98+
LogManager.Error("Downloaded file is empty.");
99+
return;
100+
}
101+
102+
string pluginPath = string.Empty;
103+
ushort serverPort = 0;
104+
serverPort = Server.Port;
105+
106+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
107+
{
108+
try
109+
{
110+
string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
111+
if (string.IsNullOrEmpty(homeDirectory))
112+
LogManager.Error("Could not determine user home directory for Linux path.");
113+
else
114+
{
115+
string portSpecificLinuxPath = Path.Combine(homeDirectory, ".config", "SCP Secret Laboratory", "LabAPI", "plugins", serverPort.ToString(), PluginDllName);
116+
if (File.Exists(portSpecificLinuxPath))
117+
pluginPath = portSpecificLinuxPath;
118+
else
119+
{
120+
LogManager.Warn($"Linux LabAPI Port-Specific plugin path not found: {portSpecificLinuxPath}. Trying global LabAPI path.");
121+
string globalLinuxPath = Path.Combine(homeDirectory, ".config", "SCP Secret Laboratory", "LabAPI", "plugins", PluginDllName);
122+
if (File.Exists(globalLinuxPath))
123+
pluginPath = globalLinuxPath;
124+
else
125+
LogManager.Warn($"Linux LabAPI Global plugin path not found: {globalLinuxPath}");
126+
}
127+
}
128+
}
129+
catch (Exception ex)
130+
{
131+
LogManager.Error($"Error determining Linux LabAPI plugin paths: {ex.Message}");
132+
}
133+
}
134+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
135+
{
136+
try
137+
{
138+
string appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
139+
if (string.IsNullOrEmpty(appDataDirectory))
140+
LogManager.Error("Could not determine AppData directory for Windows path.");
141+
else
142+
{
143+
string portSpecificWindowsPath = Path.Combine(appDataDirectory, "SCP Secret Laboratory", "LabAPI", "plugins", serverPort.ToString(), PluginDllName);
144+
if (File.Exists(portSpecificWindowsPath))
145+
pluginPath = portSpecificWindowsPath;
146+
else
147+
{
148+
LogManager.Warn($"Windows LabAPI Port-Specific plugin path not found: {portSpecificWindowsPath}. Trying global LabAPI path.");
149+
string globalWindowsPath = Path.Combine(appDataDirectory, "SCP Secret Laboratory", "LabAPI", "plugins", PluginDllName);
150+
if (File.Exists(globalWindowsPath))
151+
pluginPath = globalWindowsPath;
152+
else
153+
LogManager.Warn($"Windows LabAPI Global plugin path not found: {globalWindowsPath}");
154+
}
155+
}
156+
}
157+
catch (Exception ex)
158+
{
159+
LogManager.Error($"Error determining Windows LabAPI plugin paths: {ex.Message}");
160+
}
161+
}
162+
163+
if (string.IsNullOrEmpty(pluginPath))
164+
{
165+
LogManager.Error("Could not determine the path of the current plugin DLL using LabAPI paths. Update aborted.");
166+
return;
167+
}
168+
169+
LogManager.Raw("Attempting to overwrite plugin DLL. The server will attempt to restart the round after this.", ConsoleColor.Blue);
170+
171+
try
172+
{
173+
File.WriteAllBytes(pluginPath, fileBytes);
174+
LogManager.Raw($"{PluginDllName} downloaded and replaced successfully ({fileBytes.Length} bytes).", ConsoleColor.Blue);
175+
LogManager.Raw("Executing 'rnr' command to reload plugins and restart the round...", ConsoleColor.Blue);
176+
Server.ExecuteCommand("rnr");
177+
}
178+
catch (IOException ex)
179+
{
180+
LogManager.Error($"IO Error writing plugin file: {ex.Message}. Ensure the server has write permissions and the file is not locked. A manual restart might be required after placing the DLL.");
181+
}
182+
catch (UnauthorizedAccessException ex)
183+
{
184+
LogManager.Error($"Unauthorized Access Error writing plugin file: {ex.Message}. Ensure the server process has write permissions to the plugins directory.");
185+
}
186+
catch (Exception ex)
187+
{
188+
LogManager.Error($"Error saving plugin DLL or executing rnr: {ex.ToString()}");
189+
}
190+
}
191+
}
192+
catch (HttpRequestException ex)
193+
{
194+
LogManager.Error($"A network error occurred during update: {ex.Message}");
195+
}
196+
catch (JsonException ex)
197+
{
198+
LogManager.Error($"Error parsing JSON response from GitHub: {ex.Message}");
199+
}
200+
catch (Exception ex)
201+
{
202+
LogManager.Error($"An unexpected error occurred during update: {ex.ToString()}");
203+
}
204+
});
205+
return true;
206+
}
207+
}
208+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using CommandSystem;
2+
using System;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
using Newtonsoft.Json;
6+
using UncomplicatedCustomItems.API.Features.Helper;
7+
8+
namespace UncomplicatedCustomItems.Commands.Admin
9+
{
10+
public class GitHubReleaseInfo
11+
{
12+
[JsonProperty("tag_name")]
13+
public string TagName { get; set; }
14+
15+
[JsonProperty("assets")]
16+
public GitHubAssetInfo[] Assets { get; set; }
17+
}
18+
19+
[CommandHandler(typeof(GameConsoleCommandHandler))]
20+
public class UpdateCheck : ParentCommand
21+
{
22+
public UpdateCheck() => LoadGeneratedCommands();
23+
24+
public override string Command { get; } = "uciupdatecheck";
25+
public override string[] Aliases { get; } = new string[] { "ucicheckupdate" };
26+
public override string Description { get; } = "Checks if a new version of UncomplicatedCustomItems is available.";
27+
28+
public override void LoadGeneratedCommands() { }
29+
30+
protected override bool ExecuteParent(ArraySegment<string> arguments, ICommandSender sender, out string response)
31+
{
32+
Version version = null;
33+
try
34+
{
35+
version = Plugin.Instance.Version;
36+
}
37+
catch (Exception ex)
38+
{
39+
response = $"Error: Could not retrieve local plugin version. Details: {ex.Message}";
40+
LogManager.Error($"Exception while retrieving local plugin version: {ex}");
41+
return false;
42+
}
43+
44+
response = $"Currently running UncomplicatedCustomItems version {version}. Checking for updates...";
45+
LogManager.Info($"Current version: {version}. Checking GitHub for latest release...");
46+
47+
Task.Run(async () =>
48+
{
49+
try
50+
{
51+
using (HttpClient client = new HttpClient())
52+
{
53+
client.DefaultRequestHeaders.Add("User-Agent", "UncomplicatedCustomItems-UpdateChecker/1.0");
54+
string apiUrl = "https://api.github.com/repos/UncomplicatedCustomServer/UncomplicatedCustomItems/releases/latest";
55+
HttpResponseMessage httpResponse = await client.GetAsync(apiUrl);
56+
57+
if (httpResponse.IsSuccessStatusCode)
58+
{
59+
string jsonResponse = await httpResponse.Content.ReadAsStringAsync();
60+
GitHubReleaseInfo latestRelease = JsonConvert.DeserializeObject<GitHubReleaseInfo>(jsonResponse);
61+
62+
if (latestRelease != null && !string.IsNullOrEmpty(latestRelease.TagName))
63+
{
64+
string latestVersionTag = latestRelease.TagName;
65+
if (latestVersionTag.StartsWith("v", StringComparison.OrdinalIgnoreCase))
66+
latestVersionTag = latestVersionTag.Substring(1);
67+
68+
if (Version.TryParse(latestVersionTag, out Version githubVersion))
69+
{
70+
LogManager.Info($"Latest version on GitHub: {githubVersion} (Tag: {latestRelease.TagName})");
71+
if (githubVersion > version)
72+
{
73+
LogManager.Raw($"An update is available for UncomplicatedCustomItems!", ConsoleColor.Blue);
74+
LogManager.Raw($"Current version: {version}, Latest version: {githubVersion}.", ConsoleColor.Blue);
75+
LogManager.Raw($"Please use the 'uciupdate' command to update the plugin.", ConsoleColor.Blue);
76+
}
77+
else if (githubVersion == version)
78+
LogManager.Raw($"You are running the latest version of UncomplicatedCustomItems ({version}).", ConsoleColor.Blue);
79+
else
80+
LogManager.Raw($"You are running a newer version ({version}) than the latest stable release on GitHub. This is a development or pre-release version.", ConsoleColor.Blue);
81+
}
82+
else
83+
LogManager.Error($"Failed to parse the latest version tag '{latestVersionTag}' from GitHub into a valid version format.");
84+
}
85+
else
86+
LogManager.Error("Failed to parse release information from GitHub or tag name was empty.");
87+
}
88+
else
89+
{
90+
string errorContent = await httpResponse.Content.ReadAsStringAsync();
91+
LogManager.Error($"Failed to fetch latest release from GitHub. Status code: {httpResponse.StatusCode}. Response: {errorContent}");
92+
}
93+
}
94+
}
95+
catch (HttpRequestException ex)
96+
{
97+
LogManager.Error($"A network error occurred while checking for updates: {ex.Message}");
98+
}
99+
catch (JsonException ex)
100+
{
101+
LogManager.Error($"Error parsing JSON response from GitHub: {ex.Message}");
102+
}
103+
catch (Exception ex)
104+
{
105+
LogManager.Error($"An unexpected error occurred: {ex.ToString()}");
106+
}
107+
});
108+
return true;
109+
}
110+
}
111+
}

UncomplicatedCustomItems/Events/EventHandler.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,16 +1347,19 @@ public void OnPickupCreation(PickupAddedEventArgs ev)
13471347
{
13481348
if (Utilities.TryGetSummonedCustomItem(ev.Pickup.Serial, out SummonedCustomItem SummonedCustomItem))
13491349
{
1350-
try
1351-
{
1352-
ev.Pickup.GameObject.transform.localScale = SummonedCustomItem.CustomItem.Scale;
1353-
ev.Pickup.Weight = SummonedCustomItem.CustomItem.Weight;
1354-
}
1355-
catch (Exception ex)
1350+
Timing.CallDelayed(1f, () =>
13561351
{
1357-
LogManager.Silent($"{SummonedCustomItem.CustomItem.Name} - {SummonedCustomItem.CustomItem.Id} - {SummonedCustomItem.CustomItem.CustomFlags}");
1358-
LogManager.Error($"Couldnt set CustomItem Pickup Scale or CustomItem Pickup Weight\n Error: {ex.Message}\n Code: {ex.HResult}\n Please send this in the bug-report forum in our Discord!");
1359-
}
1352+
try
1353+
{
1354+
ev.Pickup.Scale = SummonedCustomItem.CustomItem.Scale;
1355+
ev.Pickup.Weight = SummonedCustomItem.CustomItem.Weight;
1356+
}
1357+
catch (Exception ex)
1358+
{
1359+
LogManager.Silent($"{SummonedCustomItem.CustomItem.Name} - {SummonedCustomItem.CustomItem.Id} - {SummonedCustomItem.CustomItem.CustomFlags}");
1360+
LogManager.Error($"Couldnt set CustomItem Pickup Scale or CustomItem Pickup Weight\n Error: {ex.Message}\n Code: {ex.HResult}\n Please send this in the bug-report forum in our Discord!");
1361+
}
1362+
});
13601363
}
13611364

13621365
if (!Utilities.TryGetSummonedCustomItem(ev.Pickup.Serial, out SummonedCustomItem customItem) || !customItem.CustomItem.CustomFlags.HasValue)

0 commit comments

Comments
 (0)