Add "Open Folder" functionality to plugins tab, crash-safe plugin updating

This commit is contained in:
John Gross
2017-09-14 21:09:20 -07:00
parent 3fd7b66905
commit a97542e649
6 changed files with 76 additions and 33 deletions

View File

@@ -10,7 +10,7 @@ namespace Torch.API.Plugins
/// <summary> /// <summary>
/// Indicates that the given type should be loaded by the plugin manager as a plugin. /// Indicates that the given type should be loaded by the plugin manager as a plugin.
/// </summary> /// </summary>
[Obsolete] [Obsolete("All plugin meta-information is now defined in the manifest.xml.")]
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class PluginAttribute : Attribute public class PluginAttribute : Attribute
{ {

View File

@@ -12,7 +12,7 @@
</UserControl.DataContext> </UserControl.DataContext>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/> <ColumnDefinition Width="200"/>
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0"> <Grid Grid.Column="0">
@@ -27,7 +27,7 @@
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"/> <Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" Click="OpenFolder_OnClick"/>
</Grid> </Grid>
<Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/> <Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
</Grid> </Grid>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,6 +16,8 @@ using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using NLog; using NLog;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
namespace Torch.Server.Views namespace Torch.Server.Views
@@ -24,6 +27,9 @@ namespace Torch.Server.Views
/// </summary> /// </summary>
public partial class PluginsControl : UserControl public partial class PluginsControl : UserControl
{ {
private ITorchServer _server;
private PluginManager _plugins;
public PluginsControl() public PluginsControl()
{ {
InitializeComponent(); InitializeComponent();
@@ -31,8 +37,15 @@ namespace Torch.Server.Views
public void BindServer(ITorchServer server) public void BindServer(ITorchServer server)
{ {
var pluginManager = new PluginManagerViewModel(server.Plugins); _server = server;
_plugins = _server.Managers.GetManager<PluginManager>();
var pluginManager = new PluginManagerViewModel(_plugins);
DataContext = pluginManager; DataContext = pluginManager;
} }
private void OpenFolder_OnClick(object sender, RoutedEventArgs e)
{
Process.Start("explorer.exe", _plugins.PluginDir);
}
} }
} }

View File

@@ -99,30 +99,42 @@ namespace Torch.Managers
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip").Union(Directory.EnumerateDirectories(PluginDir)); var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip").Union(Directory.EnumerateDirectories(PluginDir));
Parallel.ForEach(pluginItems, async item => Parallel.ForEach(pluginItems, async item =>
{ {
var path = Path.Combine(PluginDir, item); PluginManifest manifest = null;
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase); try
var manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
if (manifest == null)
{ {
_log.Warn($"Item '{item}' is missing a manifest, skipping update check."); var path = Path.Combine(PluginDir, item);
return; var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
if (manifest == null)
{
_log.Warn($"Item '{item}' is missing a manifest, skipping update check.");
return;
}
manifest.Version.TryExtractVersion(out Version currentVersion);
var latest = await GetLatestArchiveAsync(manifest.Repository).ConfigureAwait(false);
if (currentVersion == null || latest.Item1 == null)
{
_log.Error($"Error parsing version from manifest or GitHub for plugin '{manifest.Name}.'");
return;
}
if (latest.Item1 <= currentVersion)
{
_log.Debug($"{manifest.Name} {manifest.Version} is up to date.");
return;
}
_log.Info($"Updating plugin '{manifest.Name}' from {currentVersion} to {latest.Item1}.");
await UpdatePluginAsync(path, latest.Item2).ConfigureAwait(false);
count++;
} }
catch (Exception e)
manifest.Version.TryExtractVersion(out Version currentVersion);
var latest = await GetLatestArchiveAsync(manifest.Repository).ConfigureAwait(false);
if (currentVersion == null || latest.Item1 == null)
{ {
_log.Error($"Error parsing version from manifest or GitHub for plugin '{manifest.Name}.'"); _log.Error($"An error occurred updating the plugin {manifest.Name}.");
return; _log.Error(e);
} }
if (latest.Item1 <= currentVersion)
return;
_log.Info($"Updating plugin '{manifest.Name}' from {currentVersion} to {latest.Item1}.");
await UpdatePlugin(path, latest.Item2).ConfigureAwait(false);
count++;
}); });
_log.Info($"Updated {count} plugins."); _log.Info($"Updated {count} plugins.");
@@ -155,7 +167,7 @@ namespace Torch.Managers
} }
} }
private Task UpdatePlugin(string localPath, string downloadUrl) private Task UpdatePluginAsync(string localPath, string downloadUrl)
{ {
if (File.Exists(localPath)) if (File.Exists(localPath))
File.Delete(localPath); File.Delete(localPath);
@@ -238,9 +250,7 @@ namespace Torch.Managers
using (var stream = new StreamReader(entry.Open())) using (var stream = new StreamReader(entry.Open()))
{ {
var ser = new XmlSerializer(typeof(PluginManifest)); return PluginManifest.Load(stream);
var manifest = (PluginManifest)ser.Deserialize(stream);
return manifest;
} }
} }
} }
@@ -266,7 +276,7 @@ namespace Torch.Managers
if (pluginType != null) if (pluginType != null)
{ {
_log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}."); _log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading.");
return; return;
} }
@@ -276,7 +286,7 @@ namespace Torch.Managers
if (pluginType == null) if (pluginType == null)
{ {
_log.Error($"The plugin '{manifest.Name}' does not have an implementation of {nameof(ITorchPlugin)}."); _log.Error($"The plugin '{manifest.Name}' does not have an implementation of {nameof(ITorchPlugin)}, not loading.");
return; return;
} }
@@ -301,6 +311,7 @@ namespace Torch.Managers
_commandManager.RegisterPluginCommands(plugin); _commandManager.RegisterPluginCommands(plugin);
} }
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
public IEnumerator<ITorchPlugin> GetEnumerator() public IEnumerator<ITorchPlugin> GetEnumerator()
{ {
return Plugins.Values.GetEnumerator(); return Plugins.Values.GetEnumerator();

View File

@@ -10,11 +10,30 @@ namespace Torch
{ {
public class PluginManifest public class PluginManifest
{ {
/// <summary>
/// The display name of the plugin.
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// A unique identifier for the plugin.
/// </summary>
public Guid Guid { get; set; } public Guid Guid { get; set; }
/// <summary>
/// A GitHub repository in the format of Author/Repository to retrieve plugin updates.
/// </summary>
public string Repository { get; set; } public string Repository { get; set; }
/// <summary>
/// The plugin version. This must include a string in the format of #[.#[.#]] for update checking purposes.
/// </summary>
public string Version { get; set; } public string Version { get; set; }
public List<Guid> Dependencies { get; } = new List<Guid>();
/// <summary>
/// A list of dependent plugin repositories. This may be updated to include GUIDs in the future.
/// </summary>
public List<string> Dependencies { get; } = new List<string>();
public void Save(string path) public void Save(string path)
{ {

View File

@@ -181,7 +181,7 @@
<Compile Include="Managers\MultiplayerManager.cs" /> <Compile Include="Managers\MultiplayerManager.cs" />
<Compile Include="Managers\UpdateManager.cs" /> <Compile Include="Managers\UpdateManager.cs" />
<Compile Include="Persistent.cs" /> <Compile Include="Persistent.cs" />
<Compile Include="PluginManifest.cs" /> <Compile Include="Plugins\PluginManifest.cs" />
<Compile Include="Utils\Reflection.cs" /> <Compile Include="Utils\Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" /> <Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="Utils\TorchAssemblyResolver.cs" /> <Compile Include="Utils\TorchAssemblyResolver.cs" />
@@ -198,7 +198,7 @@
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="ViewModels\PlayerViewModel.cs" /> <Compile Include="ViewModels\PlayerViewModel.cs" />
<Compile Include="ViewModels\ViewModel.cs" /> <Compile Include="ViewModels\ViewModel.cs" />
<Compile Include="Managers\PluginManager.cs" /> <Compile Include="Plugins\PluginManager.cs" />
<Compile Include="ViewModels\PluginViewModel.cs" /> <Compile Include="ViewModels\PluginViewModel.cs" />
<Compile Include="Views\CollectionEditor.xaml.cs"> <Compile Include="Views\CollectionEditor.xaml.cs">
<DependentUpon>CollectionEditor.xaml</DependentUpon> <DependentUpon>CollectionEditor.xaml</DependentUpon>