Skip to content

Commit 6b3e21b

Browse files
committed
Use the new Unity Release Api to fetch release information
1 parent dd8e123 commit 6b3e21b

9 files changed

+304
-402
lines changed

UnityLauncherPro/Data/UnityVersion.cs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
5+
namespace UnityLauncherPro
6+
{
7+
public class UnityVersion
8+
{
9+
[JsonPropertyName("version")]
10+
public string Version { get; set; }
11+
[JsonPropertyName("stream")]
12+
[JsonConverter(typeof(UnityVersionStreamConverter))]
13+
public UnityVersionStream Stream { get; set; }
14+
[JsonPropertyName("releaseDate")]
15+
public DateTime ReleaseDate { get; set; }
16+
}
17+
18+
public class UnityVersionStreamConverter : JsonConverter<UnityVersionStream>
19+
{
20+
public override UnityVersionStream Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
21+
{
22+
string streamString = reader.GetString();
23+
if (Enum.TryParse<UnityVersionStream>(streamString, true, out var result))
24+
{
25+
return result;
26+
}
27+
throw new JsonException($"Unable to convert \"{streamString}\" to UnityVersionStream");
28+
}
29+
30+
public override void Write(Utf8JsonWriter writer, UnityVersionStream value, JsonSerializerOptions options)
31+
{
32+
writer.WriteStringValue(value.ToString().ToUpper());
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json.Serialization;
3+
4+
namespace UnityLauncherPro
5+
{
6+
public class UnityVersionResponse
7+
{
8+
[JsonPropertyName("offset")]
9+
public int Offset { get; set; }
10+
[JsonPropertyName("limit")]
11+
public int Limit { get; set; }
12+
[JsonPropertyName("total")]
13+
public int Total { get; set; }
14+
[JsonPropertyName("results")]
15+
public List<UnityVersion> Results { get; set; }
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace UnityLauncherPro
2+
{
3+
public enum UnityVersionStream
4+
{
5+
Alpha,
6+
Beta,
7+
LTS,
8+
Tech
9+
}
10+
}

UnityLauncherPro/Data/Updates.cs

-10
This file was deleted.

UnityLauncherPro/GetUnityUpdates.cs

+173-55
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,206 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Globalization;
4-
using System.Net;
3+
using System.Configuration;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Net.Http;
7+
using System.Reflection;
8+
using System.Text.Json;
59
using System.Threading.Tasks;
610

711
namespace UnityLauncherPro
812
{
913
public static class GetUnityUpdates
1014
{
11-
static bool isDownloadingUnityList = false;
12-
static readonly string unityVersionsURL = @"https://symbolserver.unity3d.com/000Admin/history.txt";
15+
private const string BaseApiUrl = "https://services.api.unity.com/unity/editor/release/v1/releases";
16+
private const int BatchSize = 25;
17+
private const int RequestsPerBatch = 10;
18+
private const int DelayBetweenBatches = 1000; // 1 second in milliseconds
19+
private const string CacheFileName = "UnityVersionCache.json";
1320

14-
public static async Task<string> Scan()
21+
private static readonly HttpClient httpClient = new HttpClient();
22+
23+
public static async Task<List<UnityVersion>> FetchAll()
1524
{
16-
if (isDownloadingUnityList == true)
25+
var cachedVersions = LoadCachedVersions();
26+
var latestCachedVersion = cachedVersions.FirstOrDefault();
27+
28+
var newVersions = await FetchNewVersions(latestCachedVersion);
29+
30+
var allVersions = newVersions.Concat(cachedVersions).ToList();
31+
32+
if (newVersions.Count > 0)
1733
{
18-
Console.WriteLine("We are already downloading ...");
19-
return null;
34+
SaveCachedVersions(allVersions);
2035
}
2136

22-
isDownloadingUnityList = true;
23-
//SetStatus("Downloading list of Unity versions ...");
24-
string result = null;
25-
// download list of Unity versions
26-
using (WebClient webClient = new WebClient())
37+
return allVersions;
38+
}
39+
40+
public static async Task<string> FetchDownloadUrl(string unityVersion, bool assistantUrl = false)
41+
{
42+
if (string.IsNullOrEmpty(unityVersion))
43+
{
44+
return null;
45+
}
46+
47+
string apiUrl = $"{BaseApiUrl}?limit=1&version={unityVersion}&architecture=X86_64&platform=WINDOWS";
48+
49+
try
50+
{
51+
string responseString = await httpClient.GetStringAsync(apiUrl);
52+
JsonDocument doc = JsonDocument.Parse(responseString);
53+
try
2754
{
28-
Task<string> downloadStringTask = webClient.DownloadStringTaskAsync(new Uri(unityVersionsURL));
29-
try
55+
var root = doc.RootElement;
56+
var results = root.GetProperty("results");
57+
58+
if (results.GetArrayLength() > 0)
3059
{
31-
result = await downloadStringTask;
60+
var entry = results[0];
61+
string downloadUrl = null;
62+
string shortRevision = null;
63+
64+
if (entry.TryGetProperty("downloads", out var downloads) &&
65+
downloads.GetArrayLength() > 0 &&
66+
downloads[0].TryGetProperty("url", out var urlProperty))
67+
{
68+
downloadUrl = urlProperty.GetString();
69+
}
70+
71+
if (entry.TryGetProperty("shortRevision", out var revisionProperty))
72+
{
73+
shortRevision = revisionProperty.GetString();
74+
}
75+
76+
if (!string.IsNullOrEmpty(downloadUrl))
77+
{
78+
if (!assistantUrl) return downloadUrl;
79+
80+
if (!string.IsNullOrEmpty(shortRevision))
81+
{
82+
var startIndex = downloadUrl.LastIndexOf(shortRevision, StringComparison.Ordinal) + shortRevision.Length + 1;
83+
var endIndex = downloadUrl.Length - startIndex;
84+
return downloadUrl.Replace(downloadUrl.Substring(startIndex, endIndex),
85+
$"UnityDownloadAssistant-{unityVersion}.exe");
86+
}
87+
else
88+
{
89+
Console.WriteLine("ShortRevision not found, returning original download URL.");
90+
return downloadUrl;
91+
}
92+
}
3293
}
33-
catch (WebException)
94+
95+
Console.WriteLine($"No download URL found for version {unityVersion}");
96+
return null;
97+
}
98+
finally
99+
{
100+
doc.Dispose();
101+
}
102+
}
103+
catch (Exception e)
104+
{
105+
Console.WriteLine($"Error fetching download URL: {e.Message}");
106+
return null;
107+
}
108+
}
109+
110+
private static async Task<List<UnityVersion>> FetchNewVersions(UnityVersion latestCachedVersion)
111+
{
112+
var newVersions = new List<UnityVersion>();
113+
int offset = 0;
114+
int total = int.MaxValue;
115+
116+
while (offset < total)
117+
{
118+
var batchUpdates = await FetchBatch(offset);
119+
if (batchUpdates?.Results == null || batchUpdates.Results.Count == 0)
120+
break;
121+
122+
foreach (var version in batchUpdates.Results)
34123
{
35-
Console.WriteLine("It's a web exception");
124+
if (version.Version == latestCachedVersion?.Version)
125+
return newVersions;
126+
127+
newVersions.Add(version);
36128
}
37-
catch (Exception)
129+
130+
total = batchUpdates.Total;
131+
offset += batchUpdates.Results.Count;
132+
133+
if (offset % (BatchSize * RequestsPerBatch) == 0)
38134
{
39-
Console.WriteLine("It's not a web exception");
135+
await Task.Delay(DelayBetweenBatches);
40136
}
41-
42-
isDownloadingUnityList = false;
43137
}
44-
return result;
138+
139+
return newVersions;
45140
}
46141

47-
public static Updates[] Parse(string items, ref List<string> updatesAsString)
142+
private static async Task<UnityVersionResponse> FetchBatch(int offset)
48143
{
49-
if (updatesAsString == null)
50-
updatesAsString = new List<string>();
51-
updatesAsString.Clear();
52-
53-
isDownloadingUnityList = false;
54-
//SetStatus("Downloading list of Unity versions ... done");
55-
var receivedList = items.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
56-
if (receivedList == null && receivedList.Length < 1) return null;
57-
Array.Reverse(receivedList);
58-
var releases = new Dictionary<string, Updates>();
59-
// parse into data
60-
string prevVersion = null;
61-
for (int i = 0, len = receivedList.Length; i < len; i++)
62-
{
63-
var row = receivedList[i].Split(',');
64-
var versionTemp = row[6].Trim('"');
144+
string url = $"{BaseApiUrl}?limit={BatchSize}&offset={offset}&architecture=X86_64&platform=WINDOWS";
65145

66-
if (versionTemp.Length < 1) continue;
67-
if (prevVersion == versionTemp) continue;
146+
try
147+
{
148+
var response = await httpClient.GetStringAsync(url);
149+
return JsonSerializer.Deserialize<UnityVersionResponse>(response);
150+
}
151+
catch (Exception e)
152+
{
153+
Console.WriteLine($"Error fetching batch: {e.Message}");
154+
return null;
155+
}
156+
}
68157

69-
if (releases.ContainsKey(versionTemp) == false)
158+
private static List<UnityVersion> LoadCachedVersions()
159+
{
160+
// Check if the file is locally saved
161+
string configFilePath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
162+
string configDirectory = Path.GetDirectoryName(configFilePath);
163+
164+
if (configDirectory != null && Path.Combine(configDirectory, CacheFileName) is string cacheFilePath)
165+
{
166+
if (File.Exists(cacheFilePath))
70167
{
71-
var u = new Updates();
72-
u.ReleaseDate = DateTime.ParseExact(row[3], "MM/dd/yyyy", CultureInfo.InvariantCulture);
73-
u.Version = versionTemp;
74-
releases.Add(versionTemp, u);
75-
updatesAsString.Add(versionTemp);
168+
var json = File.ReadAllText(cacheFilePath);
169+
return JsonSerializer.Deserialize<List<UnityVersion>>(json) ?? new List<UnityVersion>();
76170
}
77-
78-
prevVersion = versionTemp;
79171
}
172+
else
173+
{
174+
return new List<UnityVersion>();
175+
}
176+
177+
// Take the embedded file and save it locally, then rerun this method when that is successful
178+
var assembly = Assembly.GetExecutingAssembly();
179+
using (var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.Resources.{CacheFileName}"))
180+
{
181+
if (stream == null)
182+
return new List<UnityVersion>();
80183

81-
// convert to array
82-
var results = new Updates[releases.Count];
83-
releases.Values.CopyTo(results, 0);
84-
return results;
184+
using (var reader = new StreamReader(stream))
185+
{
186+
var json = reader.ReadToEnd();
187+
File.WriteAllText(cacheFilePath, json);
188+
return JsonSerializer.Deserialize<List<UnityVersion>>(json) ?? new List<UnityVersion>();
189+
}
190+
}
85191
}
192+
193+
private static void SaveCachedVersions(List<UnityVersion> versions)
194+
{
195+
var json = JsonSerializer.Serialize(versions);
196+
197+
string configFilePath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;
198+
string configDirectory = Path.GetDirectoryName(configFilePath);
86199

200+
if (configDirectory != null && Path.Combine(configDirectory, CacheFileName) is string cacheFilePath)
201+
{
202+
File.WriteAllText(cacheFilePath, json);
203+
}
204+
}
87205
}
88-
}
206+
}

UnityLauncherPro/MainWindow.xaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@
485485
<MenuItem x:Name="menuItemUpdatesReleaseNotes" Header="Open Release Notes" Click="MenuItemUpdatesReleaseNotes_Click" />
486486
</ContextMenu>
487487
</DataGrid.ContextMenu>
488-
<local:Updates ReleaseDate="2020-10-10" Version="5000.1.2.3"/>
488+
<local:UnityVersion ReleaseDate="2020-10-10" Version="5000.1.2.3"/>
489489

490490
<!-- sample data for testing -->
491491
</DataGrid>

0 commit comments

Comments
 (0)