Implement plugin browser and downloader
This commit is contained in:
@@ -39,6 +39,10 @@
|
||||
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@@ -189,6 +193,7 @@
|
||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||
<Compile Include="Session\TorchSessionState.cs" />
|
||||
<Compile Include="TorchGameState.cs" />
|
||||
<Compile Include="WebAPI\PluginQuery.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
|
159
Torch.API/WebAPI/PluginQuery.cs
Normal file
159
Torch.API/WebAPI/PluginQuery.cs
Normal file
@@ -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<PluginResponse> 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<PluginResponse>(r);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to deserialize plugin query response!");
|
||||
return null;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<PluginFullItem> QueryOne(Guid guid)
|
||||
{
|
||||
return await QueryOne(guid.ToString());
|
||||
}
|
||||
|
||||
public async Task<PluginFullItem> 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<PluginFullItem>(r);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to deserialize plugin query response!");
|
||||
return null;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadPlugin(Guid guid)
|
||||
{
|
||||
return await DownloadPlugin(guid.ToString());
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadPlugin(string guid)
|
||||
{
|
||||
var item = await QueryOne(guid);
|
||||
return await DownloadPlugin(item);
|
||||
}
|
||||
|
||||
public async Task<bool> 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<PluginFullItem>(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;
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
@@ -51,7 +51,6 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Server.xml</DocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Torch.Server.Program</StartupObject>
|
||||
@@ -71,6 +70,9 @@
|
||||
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Markdown.Xaml, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Markdown.Xaml.1.0.0\lib\net45\Markdown.Xaml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
|
||||
@@ -273,6 +275,9 @@
|
||||
<Compile Include="Views\ModListControl.xaml.cs">
|
||||
<DependentUpon>ModListControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\PluginBrowser.xaml.cs">
|
||||
<DependentUpon>PluginBrowser.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ThemeControl.xaml.cs">
|
||||
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -437,6 +442,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\PluginBrowser.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\PluginsControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -492,6 +501,7 @@
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
<PropertyGroup>
|
||||
|
124
Torch.Server/Views/PluginBrowser.xaml
Normal file
124
Torch.Server/Views/PluginBrowser.xaml
Normal file
@@ -0,0 +1,124 @@
|
||||
<Window x:Class="Torch.Server.Views.PluginBrowser"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Markdown.Xaml;assembly=Markdown.Xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:views="clr-namespace:Torch.Server.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="PluginBrowser" Height="400" Width="600"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
|
||||
<Window.Resources>
|
||||
<Style TargetType="FlowDocument" x:Key="DocumentStyle">
|
||||
<Setter Property="FontFamily"
|
||||
Value="Calibri" />
|
||||
<Setter Property="TextAlignment"
|
||||
Value="Left" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="H1Style"
|
||||
TargetType="Paragraph">
|
||||
<Setter Property="FontSize"
|
||||
Value="42" />
|
||||
<Setter Property="Foreground"
|
||||
Value="#ff000000" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="Light" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="H2Style"
|
||||
TargetType="Paragraph">
|
||||
<Setter Property="FontSize"
|
||||
Value="20" />
|
||||
<Setter Property="Foreground"
|
||||
Value="#ff000000" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="Light" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="H3Style"
|
||||
TargetType="Paragraph">
|
||||
<Setter Property="FontSize"
|
||||
Value="20" />
|
||||
<Setter Property="Foreground"
|
||||
Value="#99000000" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="Light" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="H4Style"
|
||||
TargetType="Paragraph">
|
||||
<Setter Property="FontSize"
|
||||
Value="14" />
|
||||
<Setter Property="Foreground"
|
||||
Value="#99000000" />
|
||||
<Setter Property="FontWeight"
|
||||
Value="Light" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LinkStyle"
|
||||
TargetType="Hyperlink">
|
||||
<Setter Property="TextDecorations"
|
||||
Value="None" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ImageStyle"
|
||||
TargetType="Image">
|
||||
<Setter Property="RenderOptions.BitmapScalingMode"
|
||||
Value="NearestNeighbor" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Tag"
|
||||
Value="imageright">
|
||||
<Setter Property="Margin"
|
||||
Value="20,0,0,0" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SeparatorStyle"
|
||||
TargetType="Line">
|
||||
<Setter Property="X2"
|
||||
Value="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=FlowDocumentScrollViewer}}" />
|
||||
<Setter Property="Stroke"
|
||||
Value="#99000000" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="2" />
|
||||
</Style>
|
||||
<local:Markdown x:Key="Markdown"
|
||||
DocumentStyle="{StaticResource DocumentStyle}"
|
||||
Heading1Style="{StaticResource H1Style}"
|
||||
Heading2Style="{StaticResource H2Style}"
|
||||
Heading3Style="{StaticResource H3Style}"
|
||||
Heading4Style="{StaticResource H4Style}"
|
||||
LinkStyle="{StaticResource LinkStyle}"
|
||||
ImageStyle="{StaticResource ImageStyle}"
|
||||
SeparatorStyle="{StaticResource SeparatorStyle}"
|
||||
AssetPathRoot="{x:Static system:Environment.CurrentDirectory}"/>
|
||||
|
||||
<local:TextToFlowDocumentConverter x:Key="TextToFlowDocumentConverter"
|
||||
Markdown="{StaticResource Markdown}"/>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListView Name="PluginsList" Width="150" Height="Auto" Margin="3" ItemsSource="{Binding Plugins}" SelectionChanged="PluginsList_SelectionChanged">
|
||||
|
||||
</ListView>
|
||||
<Button Name="DownloadButton" Grid.Row ="1" Content="Download" Margin="3" Height="30" Click="DownloadButton_OnClick" IsEnabled="False"/>
|
||||
</Grid>
|
||||
<FlowDocumentScrollViewer Grid.Column="1" Name="MarkdownFlow" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="3" Document="{Binding CurrentDescription, Converter={StaticResource TextToFlowDocumentConverter}}"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
90
Torch.Server/Views/PluginBrowser.xaml.cs
Normal file
90
Torch.Server/Views/PluginBrowser.xaml.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PluginBrowser.xaml
|
||||
/// </summary>
|
||||
public partial class PluginBrowser : Window, INotifyPropertyChanged
|
||||
{
|
||||
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public MtObservableList<PluginItem> Plugins { get; set; } = new MtObservableList<PluginItem>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -26,7 +26,14 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" Click="OpenFolder_OnClick"/>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Row="0" Grid.Column="0" Content="Open Folder" Margin="3" Click="OpenFolder_OnClick"/>
|
||||
<Button Grid.Row="0" Grid.Column="1" Content="Browse Plugins" Margin="3" Click="BrowsPlugins_OnClick"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ScrollViewer Name="PScroll" Grid.Column="1" Margin="3">
|
||||
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<packages>
|
||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||
<package id="Markdown.Xaml" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
|
Reference in New Issue
Block a user