diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj
index 10bedc2..987b979 100644
--- a/Torch.API/Torch.API.csproj
+++ b/Torch.API/Torch.API.csproj
@@ -39,6 +39,10 @@
..\GameBinaries\HavokWrapper.dll
False
+
+ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
+ True
+
..\packages\NLog.4.4.12\lib\net45\NLog.dll
True
@@ -189,6 +193,7 @@
+
diff --git a/Torch.API/WebAPI/PluginQuery.cs b/Torch.API/WebAPI/PluginQuery.cs
new file mode 100644
index 0000000..74da68f
--- /dev/null
+++ b/Torch.API/WebAPI/PluginQuery.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using NLog;
+
+namespace Torch.API.WebAPI
+{
+ public class PluginQuery
+ {
+ private const string ALL_QUERY = "https://torchapi.net/api/plugins";
+ private const string PLUGIN_QUERY = "https://torchapi.net/api/plugins/{0}";
+ private readonly HttpClient _client;
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
+ private static PluginQuery _instance;
+ public static PluginQuery Instance => _instance ?? (_instance = new PluginQuery());
+
+ private PluginQuery()
+ {
+ _client = new HttpClient();
+ }
+
+ public async Task QueryAll()
+ {
+ var h = await _client.GetAsync(ALL_QUERY);
+ if (!h.IsSuccessStatusCode)
+ {
+ Log.Error($"Plugin query returned response {h.StatusCode}");
+ return null;
+ }
+
+ var r = await h.Content.ReadAsStringAsync();
+
+ PluginResponse response;
+ try
+ {
+ response = JsonConvert.DeserializeObject(r);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to deserialize plugin query response!");
+ return null;
+ }
+ return response;
+ }
+
+ public async Task QueryOne(Guid guid)
+ {
+ return await QueryOne(guid.ToString());
+ }
+
+ public async Task QueryOne(string guid)
+ {
+
+ var h = await _client.GetAsync(string.Format(PLUGIN_QUERY, guid));
+ if (!h.IsSuccessStatusCode)
+ {
+ Log.Error($"Plugin query returned response {h.StatusCode}");
+ return null;
+ }
+
+ var r = await h.Content.ReadAsStringAsync();
+
+ PluginFullItem response;
+ try
+ {
+ response = JsonConvert.DeserializeObject(r);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to deserialize plugin query response!");
+ return null;
+ }
+ return response;
+ }
+
+ public async Task DownloadPlugin(Guid guid)
+ {
+ return await DownloadPlugin(guid.ToString());
+ }
+
+ public async Task DownloadPlugin(string guid)
+ {
+ var item = await QueryOne(guid);
+ return await DownloadPlugin(item);
+ }
+
+ public async Task DownloadPlugin(PluginFullItem item)
+ {
+ try
+ {
+ var h = await _client.GetAsync(string.Format(PLUGIN_QUERY, item.ID));
+ string res = await h.Content.ReadAsStringAsync();
+ var response = JsonConvert.DeserializeObject(res);
+ if (response.Versions.Length == 0)
+ {
+ Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
+ return false;
+ }
+ var version = response.Versions.FirstOrDefault(v => v.Version == response.LatestVersion);
+ if (version == null)
+ {
+ Log.Error($"Could not find latest version for selected plugin {item.Name}");
+ return false;
+ }
+ var s = await _client.GetStreamAsync(version.URL);
+ using (var f = new FileStream($"Plugins\\{item.Name}.zip", FileMode.Create))
+ {
+ await s.CopyToAsync(f);
+ await f.FlushAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to download plugin!");
+ }
+
+ return true;
+ }
+ }
+
+ public class PluginResponse
+ {
+ public PluginItem[] Plugins;
+ public int Count;
+ }
+
+ public class PluginItem
+ {
+ public string ID;
+ public string Name;
+ public string Author;
+ public string Description;
+ public string LatestVersion;
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ public class PluginFullItem : PluginItem
+ {
+ public VersionItem[] Versions;
+ }
+
+ public class VersionItem
+ {
+ public string Version;
+ public string Note;
+ public bool IsBeta;
+ public string URL;
+ }
+}
diff --git a/Torch.API/packages.config b/Torch.API/packages.config
index 9d88a31..5221eb5 100644
--- a/Torch.API/packages.config
+++ b/Torch.API/packages.config
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj
index ec1c672..506686b 100644
--- a/Torch.Server/Torch.Server.csproj
+++ b/Torch.Server/Torch.Server.csproj
@@ -51,7 +51,6 @@
MinimumRecommendedRules.ruleset
true
$(SolutionDir)\bin\x64\Release\Torch.Server.xml
- 1591
Torch.Server.Program
@@ -71,6 +70,9 @@
..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll
+
+ ..\packages\Markdown.Xaml.1.0.0\lib\net45\Markdown.Xaml.dll
+
False
..\GameBinaries\Microsoft.CodeAnalysis.dll
@@ -273,6 +275,9 @@
ModListControl.xaml
+
+ PluginBrowser.xaml
+
ThemeControl.xaml
@@ -437,6 +442,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -492,6 +501,7 @@
false
+
diff --git a/Torch.Server/Views/PluginBrowser.xaml b/Torch.Server/Views/PluginBrowser.xaml
new file mode 100644
index 0000000..e4cd24f
--- /dev/null
+++ b/Torch.Server/Views/PluginBrowser.xaml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Torch.Server/Views/PluginBrowser.xaml.cs b/Torch.Server/Views/PluginBrowser.xaml.cs
new file mode 100644
index 0000000..370ccbb
--- /dev/null
+++ b/Torch.Server/Views/PluginBrowser.xaml.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Newtonsoft.Json;
+using NLog;
+using Torch.API.WebAPI;
+using Torch.Collections;
+using Torch.Server.Annotations;
+
+namespace Torch.Server.Views
+{
+ ///
+ /// Interaction logic for PluginBrowser.xaml
+ ///
+ public partial class PluginBrowser : Window, INotifyPropertyChanged
+ {
+ private static Logger Log = LogManager.GetCurrentClassLogger();
+
+ public MtObservableList Plugins { get; set; } = new MtObservableList();
+ public PluginItem CurrentItem { get; set; }
+
+ private string _description = "Loading data from server, please wait..";
+ public string CurrentDescription
+ {
+ get { return _description; }
+ set
+ {
+ _description = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public PluginBrowser()
+ {
+ InitializeComponent();
+ Task.Run(async () =>
+ {
+ var res = await PluginQuery.Instance.QueryAll();
+ if (res == null)
+ return;
+ foreach(var item in res.Plugins)
+ Plugins.Add(item);
+ });
+ }
+
+ private void PluginsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ CurrentItem = (PluginItem)PluginsList.SelectedItem;
+ CurrentDescription = CurrentItem.Description;
+ DownloadButton.IsEnabled = !string.IsNullOrEmpty(CurrentItem.LatestVersion);
+ }
+
+ private void DownloadButton_OnClick(object sender, RoutedEventArgs e)
+ {
+ var item = CurrentItem;
+ Task.Run(async () =>
+ {
+ var result = await PluginQuery.Instance.DownloadPlugin(item.ID);
+ MessageBox.Show(result ? "Plugin downloaded successfully! Please restart the server to load changes."
+ : "Plugin failed to download! See log for details.",
+ "Plugin Downloader",
+ MessageBoxButton.OK);
+ });
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ [NotifyPropertyChangedInvocator]
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+}
diff --git a/Torch.Server/Views/PluginsControl.xaml b/Torch.Server/Views/PluginsControl.xaml
index 2dfbec0..8885eba 100644
--- a/Torch.Server/Views/PluginsControl.xaml
+++ b/Torch.Server/Views/PluginsControl.xaml
@@ -26,7 +26,14 @@
-
+
+
+
+
+
+
+
+
diff --git a/Torch.Server/Views/PluginsControl.xaml.cs b/Torch.Server/Views/PluginsControl.xaml.cs
index 3000f9c..8e522c2 100644
--- a/Torch.Server/Views/PluginsControl.xaml.cs
+++ b/Torch.Server/Views/PluginsControl.xaml.cs
@@ -71,5 +71,11 @@ namespace Torch.Server.Views
if (_plugins?.PluginDir != null)
Process.Start(_plugins.PluginDir);
}
+
+ private void BrowsPlugins_OnClick(object sender, RoutedEventArgs e)
+ {
+ var browser = new PluginBrowser();
+ browser.Show();
+ }
}
}
diff --git a/Torch.Server/packages.config b/Torch.Server/packages.config
index 643d580..11d6e4d 100644
--- a/Torch.Server/packages.config
+++ b/Torch.Server/packages.config
@@ -2,6 +2,7 @@
+