Use new plugin logic for existing plugin window (still missing actual update check)

This commit is contained in:
niksedk 2023-08-19 15:59:59 +02:00
parent 462fb7271d
commit 076b527e81
18 changed files with 508 additions and 594 deletions

View File

@ -1,81 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nikse.SubtitleEdit.Plugins;
using NSubstitute;
using NSubstitute.Extensions;
//using System.Collections.Generic;
//using System.Linq;
//using System.Threading.Tasks;
//using Microsoft.VisualStudio.TestTools.UnitTesting;
//using Nikse.SubtitleEdit.Logic.Plugins;
//using NSubstitute;
//using NSubstitute.Extensions;
namespace Test.Core
{
[TestClass]
public class PluginUpdateCheckerTest
{
[TestMethod]
public async Task CheckUpdateTest()
{
// Arrange
var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
//namespace Test.Core
//{
// [TestClass]
// public class PluginUpdateCheckerTest
// {
// [TestMethod]
// public async Task CheckUpdateTest()
// {
// // Arrange
// var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
// var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
// configure
onlinePluginProvider.Configure()
.GetPluginsAsync()
.Returns(new List<PluginInfo>()
{
new LocalPlugin("foobar", "foobar", 2.0m)
});
localPluginProvider.Configure()
.GetInstalledPlugins()
.Returns(new List<LocalPlugin>()
{
new LocalPlugin("foobar", "foobar", 1.0m)
});
// // configure
// onlinePluginProvider.Configure()
// .GetPluginsAsync()
// .Returns(new List<PluginInfoItem>()
// {
// new LocalPlugin("foobar", "foobar", 2.0m)
// });
// localPluginProvider.Configure()
// .GetInstalledPlugins()
// .Returns(new List<LocalPlugin>()
// {
// new LocalPlugin("foobar", "foobar", 1.0m)
// });
var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
// var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
// Act
var updateCheckResult = await sut.CheckAsync().ConfigureAwait(false);
// // Act
// var updateCheckResult = await sut.CheckAsync().ConfigureAwait(false);
// Assert
Assert.AreEqual(true, updateCheckResult.Available);
}
// // Assert
// Assert.AreEqual(true, updateCheckResult.Available);
// }
[TestMethod]
public async Task NoPluginInstalledTest()
{
// Arrange
var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
// [TestMethod]
// public async Task NoPluginInstalledTest()
// {
// // Arrange
// var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
// var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
// var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
// Act
var updateCheckResult = await sut.CheckAsync();
// // Act
// var updateCheckResult = await sut.CheckAsync();
// Assert
Assert.AreEqual(false, updateCheckResult.Available);
Assert.AreEqual(false, updateCheckResult.PluginUpdates.Any());
}
// // Assert
// Assert.AreEqual(false, updateCheckResult.Available);
// Assert.AreEqual(false, updateCheckResult.PluginUpdates.Any());
// }
[TestMethod]
public async Task NoOnlinePluginAvailableTest()
{
// Arrange
var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
localPluginProvider.Configure().GetInstalledPlugins().Returns(new List<LocalPlugin>()
{
new LocalPlugin("foobar", "foobar", 1m)
});
// [TestMethod]
// public async Task NoOnlinePluginAvailableTest()
// {
// // Arrange
// var onlinePluginProvider = Substitute.For<IOnlinePluginMetadataProvider>();
// var localPluginProvider = Substitute.For<ILocalPluginMetadataProvider>();
// localPluginProvider.Configure().GetInstalledPlugins().Returns(new List<LocalPlugin>()
// {
// new LocalPlugin("foobar", "foobar", 1m)
// });
var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
var updateCheckResult = await sut.CheckAsync();
// var sut = new PluginUpdateChecker(localPluginProvider, onlinePluginProvider);
// var updateCheckResult = await sut.CheckAsync();
// Act
// // Act
// Assert
Assert.AreEqual(false, updateCheckResult.Available);
Assert.AreEqual(false, updateCheckResult.PluginUpdates.Any());
}
}
}
// // Assert
// Assert.AreEqual(false, updateCheckResult.Available);
// Assert.AreEqual(false, updateCheckResult.PluginUpdates.Any());
// }
// }
//}

View File

@ -1,47 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="netstandard" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" />
<assemblyIdentity name="netstandard" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="System" publicKeyToken="b77a5c561934e089" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0" />
<assemblyIdentity name="System.Collections" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion="4.2.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0" />
<assemblyIdentity name="System.Net.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0" />
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.11.0" newVersion="4.0.11.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
</configuration>

View File

@ -37,6 +37,8 @@ namespace Nikse.SubtitleEdit.Forms
this.buttonDownloadAndInstall = new System.Windows.Forms.Button();
this.timerCheckForUpdates = new System.Windows.Forms.Timer(this.components);
this.buttonDontCheckUpdates = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.linkLabel1 = new System.Windows.Forms.LinkLabel();
this.SuspendLayout();
//
// labelStatus
@ -54,12 +56,13 @@ namespace Nikse.SubtitleEdit.Forms
this.textBoxChangeLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxChangeLog.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215)))));
this.textBoxChangeLog.Location = new System.Drawing.Point(13, 31);
this.textBoxChangeLog.Multiline = true;
this.textBoxChangeLog.Name = "textBoxChangeLog";
this.textBoxChangeLog.ReadOnly = true;
this.textBoxChangeLog.ScrollBars = ScrollBars.Both;
this.textBoxChangeLog.Size = new System.Drawing.Size(618, 53);
this.textBoxChangeLog.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBoxChangeLog.Size = new System.Drawing.Size(618, 106);
this.textBoxChangeLog.TabIndex = 4;
//
// buttonOK
@ -67,7 +70,7 @@ namespace Nikse.SubtitleEdit.Forms
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.buttonOK.ImeMode = System.Windows.Forms.ImeMode.NoControl;
this.buttonOK.Location = new System.Drawing.Point(556, 90);
this.buttonOK.Location = new System.Drawing.Point(556, 168);
this.buttonOK.Name = "buttonOK";
this.buttonOK.Size = new System.Drawing.Size(75, 23);
this.buttonOK.TabIndex = 2;
@ -78,7 +81,7 @@ namespace Nikse.SubtitleEdit.Forms
//
this.buttonDownloadAndInstall.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonDownloadAndInstall.ImeMode = System.Windows.Forms.ImeMode.NoControl;
this.buttonDownloadAndInstall.Location = new System.Drawing.Point(224, 90);
this.buttonDownloadAndInstall.Location = new System.Drawing.Point(224, 168);
this.buttonDownloadAndInstall.Name = "buttonDownloadAndInstall";
this.buttonDownloadAndInstall.Size = new System.Drawing.Size(160, 23);
this.buttonDownloadAndInstall.TabIndex = 0;
@ -94,7 +97,7 @@ namespace Nikse.SubtitleEdit.Forms
//
this.buttonDontCheckUpdates.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.buttonDontCheckUpdates.ImeMode = System.Windows.Forms.ImeMode.NoControl;
this.buttonDontCheckUpdates.Location = new System.Drawing.Point(390, 90);
this.buttonDontCheckUpdates.Location = new System.Drawing.Point(390, 168);
this.buttonDontCheckUpdates.Name = "buttonDontCheckUpdates";
this.buttonDontCheckUpdates.Size = new System.Drawing.Size(160, 23);
this.buttonDontCheckUpdates.TabIndex = 1;
@ -102,11 +105,32 @@ namespace Nikse.SubtitleEdit.Forms
this.buttonDontCheckUpdates.UseVisualStyleBackColor = true;
this.buttonDontCheckUpdates.Click += new System.EventHandler(this.buttonDontCheckUpdates_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 144);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 5;
this.label1.Text = "label1";
//
// linkLabel1
//
this.linkLabel1.AutoSize = true;
this.linkLabel1.Location = new System.Drawing.Point(55, 143);
this.linkLabel1.Name = "linkLabel1";
this.linkLabel1.Size = new System.Drawing.Size(55, 13);
this.linkLabel1.TabIndex = 6;
this.linkLabel1.TabStop = true;
this.linkLabel1.Text = "linkLabel1";
//
// CheckForUpdates
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(643, 123);
this.ClientSize = new System.Drawing.Size(643, 201);
this.Controls.Add(this.linkLabel1);
this.Controls.Add(this.label1);
this.Controls.Add(this.buttonDontCheckUpdates);
this.Controls.Add(this.buttonOK);
this.Controls.Add(this.buttonDownloadAndInstall);
@ -133,5 +157,7 @@ namespace Nikse.SubtitleEdit.Forms
private System.Windows.Forms.Button buttonDownloadAndInstall;
private System.Windows.Forms.Timer timerCheckForUpdates;
private System.Windows.Forms.Button buttonDontCheckUpdates;
private Label label1;
private LinkLabel linkLabel1;
}
}

View File

@ -1,8 +1,8 @@
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Core.Forms;
using Nikse.SubtitleEdit.Logic;
using System;
using System.Windows.Forms;
using CheckForUpdatesHelper = Nikse.SubtitleEdit.Logic.CheckForUpdatesHelper;
namespace Nikse.SubtitleEdit.Forms
{

View File

@ -46,7 +46,8 @@ using System.Windows.Forms;
using Nikse.SubtitleEdit.Core.AudioToText;
using Nikse.SubtitleEdit.Forms.AudioToText;
using Nikse.SubtitleEdit.Forms.VTT;
using Nikse.SubtitleEdit.Plugins;
using Nikse.SubtitleEdit.Logic.Plugins;
using CheckForUpdatesHelper = Nikse.SubtitleEdit.Logic.CheckForUpdatesHelper;
using Timer = System.Windows.Forms.Timer;
using MessageBox = Nikse.SubtitleEdit.Forms.SeMsgBox.MessageBox;
@ -33115,7 +33116,7 @@ namespace Nikse.SubtitleEdit.Forms
return _subtitle;
}
private async void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
@ -33126,6 +33127,7 @@ namespace Nikse.SubtitleEdit.Forms
}
catch
{
// ignore
}
using (var form = new CheckForUpdates(this))
@ -33133,19 +33135,6 @@ namespace Nikse.SubtitleEdit.Forms
form.ShowDialog(this);
}
var pluginUpdateChecker = new PluginUpdateChecker(new PluginUpdateCheckerOptions()
{
GithubUrl = "https://raw.githubusercontent.com/SubtitleEdit/plugins/master/Plugins4.xml",
PluginDirectory = Configuration.PluginsDirectory
});
var updateCheckResult = await pluginUpdateChecker.CheckAsync();
if (updateCheckResult.Available)
{
MessageBox.Show(this, string.Join(Environment.NewLine, updateCheckResult.PluginUpdates),
"Updates Available For Plugins", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Configuration.Settings.General.LastCheckForUpdates = DateTime.Now;
}

View File

@ -262,7 +262,7 @@
//
this.buttonSearchClear.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonSearchClear.Enabled = false;
this.buttonSearchClear.Location = new System.Drawing.Point(672, 38);
this.buttonSearchClear.Location = new System.Drawing.Point(672, 39);
this.buttonSearchClear.Name = "buttonSearchClear";
this.buttonSearchClear.Size = new System.Drawing.Size(111, 23);
this.buttonSearchClear.TabIndex = 8;
@ -283,7 +283,8 @@
// textBoxSearch
//
this.textBoxSearch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.textBoxSearch.Location = new System.Drawing.Point(515, 40);
this.textBoxSearch.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(120)))), ((int)(((byte)(215)))));
this.textBoxSearch.Location = new System.Drawing.Point(515, 39);
this.textBoxSearch.Name = "textBoxSearch";
this.textBoxSearch.Size = new System.Drawing.Size(151, 20);
this.textBoxSearch.TabIndex = 5;

View File

@ -1,6 +1,7 @@
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Core.Http;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Plugins;
using System;
using System.Collections.Generic;
using System.Drawing;
@ -15,16 +16,6 @@ namespace Nikse.SubtitleEdit.Forms
{
public sealed partial class PluginsGet : Form
{
public class PluginInfoItem
{
public string Version { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Date { get; set; }
public string Url { get; set; }
}
private List<PluginInfoItem> _downloadList;
private string _downloadedPluginName;
private readonly LanguageStructure.PluginsGet _language;
@ -34,11 +25,6 @@ namespace Nikse.SubtitleEdit.Forms
private bool _fetchingData;
private readonly CancellationTokenSource _cancellationTokenSource;
private static string GetPluginXmlFileUrl()
{
return "https://raw.github.com/SubtitleEdit/plugins/master/Plugins4.xml";
}
public PluginsGet()
{
UiUtil.PreInitialize(this);
@ -72,87 +58,61 @@ namespace Nikse.SubtitleEdit.Forms
buttonUpdateAll.Visible = false;
_cancellationTokenSource = new CancellationTokenSource();
DownloadPluginMetadataInfos();
}
private void DownloadPluginMetadataInfos()
private static string GetPluginXmlFileUrl()
{
return "https://raw.github.com/SubtitleEdit/plugins/master/Plugins4.xml";
}
private static string GetPluginFolder()
{
var pluginsFolder = Configuration.PluginsDirectory;
if (!Directory.Exists(pluginsFolder))
{
try
{
Directory.CreateDirectory(pluginsFolder);
}
catch (Exception exception)
{
MessageBox.Show($"Unable to create plugin folder {pluginsFolder}: {exception.Message}");
return null;
}
}
return pluginsFolder;
}
private void GetAndShowAllPluginInfo()
{
var url = GetPluginXmlFileUrl();
try
{
_fetchingData = true;
labelPleaseWait.Text = LanguageSettings.Current.General.PleaseWait;
Refresh();
ShowInstalledPlugins();
using (var httpClient = DownloaderFactory.MakeHttpClient())
using (var downloadStream = new MemoryStream())
{
var downloadTask = httpClient.DownloadAsync(url, downloadStream, new Progress<float>((progress) =>
{
var pct = (int)Math.Round(progress * 100.0, MidpointRounding.AwayFromZero);
labelPleaseWait.Text = LanguageSettings.Current.General.PleaseWait + " " + pct + "%";
}), _cancellationTokenSource.Token);
while (!downloadTask.IsCompleted && !downloadTask.IsCanceled)
{
Application.DoEvents();
}
if (downloadTask.IsCanceled)
{
DialogResult = DialogResult.Cancel;
labelPleaseWait.Refresh();
return;
}
CompleteDownloadString(downloadStream);
}
ShowOnlinePlugins();
labelPleaseWait.Text = string.Empty;
}
catch (Exception exception)
{
labelPleaseWait.Text = string.Empty;
ChangeControlsState(true);
MessageBox.Show($"Unable to download {url}!" + Environment.NewLine + Environment.NewLine +
MessageBox.Show($"Unable to get plugin list!" + Environment.NewLine + Environment.NewLine +
exception.Message + Environment.NewLine + Environment.NewLine + exception.StackTrace);
}
_fetchingData = false;
}
private void CompleteDownloadString(MemoryStream downloadStream)
private void ShowOnlinePlugins()
{
if (downloadStream.Length == 0)
{
throw new Exception("No content downloaded - missing file or no internet connection!");
}
_fetchingData = false;
labelPleaseWait.Text = string.Empty;
var pluginDoc = new XmlDocument();
_downloadList = new List<PluginInfoItem>();
listViewGetPlugins.BeginUpdate();
try
{
downloadStream.Position = 0;
pluginDoc.Load(downloadStream);
var arr = pluginDoc.DocumentElement.SelectSingleNode("SubtitleEditVersion").InnerText.Split(new[] { '.', ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
var requiredVersion = Convert.ToDouble(arr[0] + "." + arr[1], CultureInfo.InvariantCulture);
var versionInfo = Utilities.AssemblyVersion.Split('.');
var currentVersion = Convert.ToDouble(versionInfo[0] + "." + versionInfo[1], CultureInfo.InvariantCulture);
if (currentVersion < requiredVersion)
{
MessageBox.Show(_language.NewVersionOfSubtitleEditRequired);
DialogResult = DialogResult.Cancel;
return;
}
_updateAllListUrls = new List<string>();
LoadAvailablePlugins(pluginDoc);
ShowAvailablePlugins();
}
catch (Exception exception)
{
MessageBox.Show(string.Format(_language.UnableToDownloadPluginListX, exception.Source + ": " + exception.Message + Environment.NewLine + Environment.NewLine + exception.StackTrace));
}
_updateAllListUrls = new List<string>();
var onlinePluginInfo = new OnlinePluginMetadataProvider(GetPluginXmlFileUrl());
LoadAvailablePlugins(onlinePluginInfo.GetPlugins());
ShowAvailablePlugins();
listViewGetPlugins.EndUpdate();
if (_updateAllListUrls.Count > 0)
@ -171,26 +131,18 @@ namespace Nikse.SubtitleEdit.Forms
}
}
private void LoadAvailablePlugins(XmlDocument doc)
private void LoadAvailablePlugins(IReadOnlyCollection<PluginInfoItem> plugins)
{
foreach (XmlNode node in doc.DocumentElement.SelectNodes("Plugin"))
foreach (var item in plugins)
{
var item = new PluginInfoItem
{
Name = node.SelectSingleNode("Name").InnerText.Trim('.'),
Description = node.SelectSingleNode("Description").InnerText.Trim('.'),
Version = node.SelectSingleNode("Version").InnerText,
Date = node.SelectSingleNode("Date").InnerText,
Url = node.SelectSingleNode("Url").InnerText,
};
_downloadList.Add(item);
foreach (ListViewItem installed in listViewInstalledPlugins.Items)
{
if (string.Compare(installed.Text, node.SelectSingleNode("Name").InnerText.Trim('.'), StringComparison.OrdinalIgnoreCase) == 0)
if (string.Compare(installed.Text, item.Name.Trim('.'), StringComparison.OrdinalIgnoreCase) == 0)
{
var installedVer = MakeComparableVersionNumber(installed.SubItems[2].Text);
var currentVer = MakeComparableVersionNumber(node.SelectSingleNode("Version").InnerText);
var currentVer = MakeComparableVersionNumber(item.Version.ToString(CultureInfo.InvariantCulture));
if (installedVer < currentVer)
{
installed.BackColor = Configuration.Settings.General.UseDarkTheme ? Color.IndianRed : Color.LightPink;
@ -245,13 +197,13 @@ namespace Nikse.SubtitleEdit.Forms
{
var item = new ListViewItem(plugin.Name) { Tag = plugin };
item.SubItems.Add(plugin.Description);
item.SubItems.Add(plugin.Version);
item.SubItems.Add(plugin.Version.ToString(CultureInfo.InvariantCulture));
item.SubItems.Add(plugin.Date);
if (!search ||
plugin.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
plugin.Description.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
plugin.Version.Contains(searchText, StringComparison.OrdinalIgnoreCase))
plugin.Version.ToString(CultureInfo.InvariantCulture).Contains(searchText, StringComparison.OrdinalIgnoreCase))
{
listViewGetPlugins.Items.Add(item);
}
@ -263,24 +215,14 @@ namespace Nikse.SubtitleEdit.Forms
{
listViewInstalledPlugins.BeginUpdate();
listViewInstalledPlugins.Items.Clear();
foreach (var pluginFileName in Configuration.GetPlugins())
var localPluginInfo = new InstalledPluginMetadataProvider();
foreach (var pluginInfo in localPluginInfo.GetPlugins())
{
Main.GetPropertiesAndDoAction(pluginFileName, out var name, out _, out var version, out var description, out var actionType, out _, out var mi);
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(actionType) && mi != null)
{
try
{
var item = new ListViewItem(name.Trim('.')) { Tag = pluginFileName };
item.SubItems.Add(description);
item.SubItems.Add(version.ToString(CultureInfo.InvariantCulture));
item.SubItems.Add(actionType);
listViewInstalledPlugins.Items.Add(item);
}
catch (Exception exception)
{
MessageBox.Show($"Error loading plugin \"{pluginFileName}\": {exception.Message}");
}
}
var item = new ListViewItem(pluginInfo.Name) { Tag = pluginInfo };
item.SubItems.Add(pluginInfo.Description);
item.SubItems.Add(pluginInfo.Version.ToString(CultureInfo.InvariantCulture));
item.SubItems.Add(pluginInfo.ActionType);
listViewInstalledPlugins.Items.Add(item);
}
listViewInstalledPlugins.EndUpdate();
}
@ -344,21 +286,7 @@ namespace Nikse.SubtitleEdit.Forms
throw new Exception("No content downloaded - missing file or no internet connection!");
}
var pluginsFolder = Configuration.PluginsDirectory;
if (!Directory.Exists(pluginsFolder))
{
try
{
Directory.CreateDirectory(pluginsFolder);
}
catch (Exception exception)
{
MessageBox.Show($"Unable to create plugin folder {pluginsFolder}: {exception.Message}");
ChangeControlsState(true);
Cursor = Cursors.Default;
return;
}
}
var pluginsFolder = GetPluginFolder();
downloadStream.Position = 0;
using (var zip = ZipExtractor.Open(downloadStream))
@ -419,20 +347,7 @@ namespace Nikse.SubtitleEdit.Forms
private void linkLabelOpenDictionaryFolder_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
var pluginsFolder = Configuration.PluginsDirectory;
if (!Directory.Exists(pluginsFolder))
{
try
{
Directory.CreateDirectory(pluginsFolder);
}
catch (Exception exception)
{
MessageBox.Show($"Unable to create plugin folder {pluginsFolder}: {exception.Message}");
return;
}
}
UiUtil.OpenFolder(pluginsFolder);
UiUtil.OpenFolder(GetPluginFolder());
}
private void PluginsGet_KeyDown(object sender, KeyEventArgs e)
@ -448,7 +363,7 @@ namespace Nikse.SubtitleEdit.Forms
}
else if (e.KeyCode == Keys.F5 && !_fetchingData)
{
DownloadPluginMetadataInfos();
GetAndShowAllPluginInfo();
}
}
@ -460,6 +375,7 @@ namespace Nikse.SubtitleEdit.Forms
private void PluginsGet_Shown(object sender, EventArgs e)
{
GetAndShowAllPluginInfo();
PluginsGet_ResizeEnd(sender, e);
}
@ -496,7 +412,7 @@ namespace Nikse.SubtitleEdit.Forms
listViewInstalledPlugins.Items[index].Focused = true;
}
buttonUpdateAll.Visible = false;
DownloadPluginMetadataInfos();
GetAndShowAllPluginInfo();
}
private void buttonUpdateAll_Click(object sender, EventArgs e)

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Core.Http;
namespace Nikse.SubtitleEdit.Logic
{
public class CheckForUpdatesHelper
{
private static readonly Regex VersionNumberRegex = new Regex(@"\d\.\d", RegexOptions.Compiled); // 3.4.0 (xth June 2014)
readonly List<string> _months = new List<string> { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
private const string ChangeLogUrl = "https://raw.githubusercontent.com/SubtitleEdit/subtitleedit/main/Changelog.txt";
private string _changeLog;
private int _successCount;
public string Error { get; set; }
public bool Done => _successCount == 1;
public string LatestVersionNumber { get; set; }
public string LatestChangeLog { get; set; }
public CheckForUpdatesHelper()
{
Error = null;
_successCount = 0;
}
private string GetLatestVersionNumber(string latestChangeLog)
{
foreach (var line in latestChangeLog.Replace(Environment.NewLine, "\n").Split('\n'))
{
var s = line.Trim();
if (!s.Contains("BETA", StringComparison.OrdinalIgnoreCase) &&
!s.Contains('x') &&
!s.Contains('*') &&
s.Contains('(') &&
s.Contains(')') &&
_months.Any(month=>s.Contains(month)) &&
VersionNumberRegex.IsMatch(s))
{
var indexOfSpace = s.IndexOf(' ');
if (indexOfSpace > 0)
{
return s.Substring(0, indexOfSpace).Trim();
}
}
}
return null;
}
private string GetLatestChangeLog(string changeLog)
{
var releaseOn = false;
var sb = new StringBuilder();
foreach (var line in changeLog.Replace(Environment.NewLine, "\n").Split('\n'))
{
var s = line.Trim();
if (s.Length == 0 && releaseOn)
{
return sb.ToString();
}
if (!releaseOn)
{
if (!s.Contains('x') &&
!s.Contains('*') &&
s.Contains('(') &&
s.Contains(')') &&
_months.Any(month => s.Contains(month)) &&
VersionNumberRegex.IsMatch(s))
{
releaseOn = true;
}
}
if (releaseOn)
{
sb.AppendLine(line);
}
}
return sb.ToString();
}
public void CheckForUpdates()
{
try
{
using (var httpClient = DownloaderFactory.MakeHttpClient())
{
_changeLog = httpClient.GetStringAsync(ChangeLogUrl).Result;
}
LatestChangeLog = GetLatestChangeLog(_changeLog);
LatestVersionNumber = GetLatestVersionNumber(LatestChangeLog);
_successCount = 1;
}
catch (Exception exception)
{
if (Error == null)
{
Error = exception.Message;
}
}
}
public bool IsUpdateAvailable()
{
if (!string.IsNullOrEmpty(Error))
{
return false;
}
try
{
//string[] currentVersionInfo = "3.3.14".Split('.'); // for testing...
return Version.Parse(LatestVersionNumber) > Version.Parse(Utilities.AssemblyVersion);
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Nikse.SubtitleEdit.Logic.Plugins
{
public interface IPluginMetadataProvider
{
IReadOnlyCollection<PluginInfoItem> GetPlugins();
}
}

View File

@ -0,0 +1,31 @@
using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Forms;
using System.Collections.Generic;
using System.Globalization;
namespace Nikse.SubtitleEdit.Logic.Plugins
{
public class InstalledPluginMetadataProvider : IPluginMetadataProvider
{
public IReadOnlyCollection<PluginInfoItem> GetPlugins()
{
var installedPlugins = new List<PluginInfoItem>();
foreach (var pluginFileName in Configuration.GetPlugins())
{
Main.GetPropertiesAndDoAction(pluginFileName, out var name, out _, out var version, out var description, out var actionType, out _, out var mi);
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(actionType) && mi != null)
{
installedPlugins.Add(new PluginInfoItem
{
Name = name.Trim('.', ' '),
Description = description,
Version = version.ToString(CultureInfo.InvariantCulture),
ActionType = actionType,
});
}
}
return installedPlugins;
}
}
}

View File

@ -0,0 +1,65 @@
using Nikse.SubtitleEdit.Core.Http;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Xml.Linq;
namespace Nikse.SubtitleEdit.Logic.Plugins
{
public class OnlinePluginMetadataProvider : IPluginMetadataProvider
{
private readonly string _githubUrl;
public OnlinePluginMetadataProvider(string githubUrl)
{
_githubUrl = githubUrl ?? throw new ArgumentNullException(nameof(githubUrl));
}
public IReadOnlyCollection<PluginInfoItem> GetPlugins()
{
XDocument xDocument;
using (var httpClient = DownloaderFactory.MakeHttpClient())
using (var downloadStream = new MemoryStream())
{
var downloadTask = httpClient.DownloadAsync(_githubUrl, downloadStream, null, CancellationToken.None);
while (!downloadTask.IsCompleted && !downloadTask.IsCanceled)
{
Application.DoEvents();
}
downloadStream.Position = 0;
xDocument = XDocument.Load(downloadStream);
}
var pluginInfos = new List<PluginInfoItem>();
if (xDocument.Root == null)
{
return pluginInfos;
}
foreach (var xElement in xDocument.Root.Elements("Plugin"))
{
var pluginInfo = ReadFormXElement(xElement);
if (pluginInfo != null)
{
pluginInfos.Add(pluginInfo);
}
}
return pluginInfos;
}
private static PluginInfoItem ReadFormXElement(XElement element)
{
var name = element.Element(nameof(PluginInfoItem.Name))?.Value.Trim('.', ' ');
var description = element.Element(nameof(PluginInfoItem.Description))?.Value;
var version = element.Element(nameof(PluginInfoItem.Version))?.Value;
var date = element.Element(nameof(PluginInfoItem.Date))?.Value;
var url = element.Element(nameof(PluginInfoItem.Url))?.Value;
return new PluginInfoItem { Name = name, Description = description, Version = version, Date = date, Url = url };
}
}
}

View File

@ -0,0 +1,12 @@
namespace Nikse.SubtitleEdit.Logic.Plugins
{
public class PluginInfoItem
{
public string Name { get; set; }
public string Version { get; set; }
public string Description { get; set; }
public string Date { get; set; }
public string Url { get; set; }
public string ActionType { get; set; }
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Nikse.SubtitleEdit.Logic.Plugins
{
public class PluginUpdateChecker
{
private readonly IPluginMetadataProvider _localPluginMetadataProvider;
private readonly IPluginMetadataProvider _onlinePluginMetadataProvider;
public PluginUpdateChecker(
IPluginMetadataProvider localPluginMetadataProvider,
IPluginMetadataProvider onlinePluginMetadataProvider)
{
_localPluginMetadataProvider = localPluginMetadataProvider;
_onlinePluginMetadataProvider = onlinePluginMetadataProvider;
}
// public PluginUpdateCheckResult Check()
// {
// var installedPlugins = _localPluginMetadataProvider.GetPlugins();
// if (!installedPlugins.Any())
// {
// return new PluginUpdateCheckResult();
// }
// // plugin repository
// var onlinePlugins = _onlinePluginMetadataProvider.GetPlugins();
// if (!onlinePlugins.Any())
// {
// return new PluginUpdateCheckResult();
// }
// var pluginUpdateCheckResult = new PluginUpdateCheckResult();
// foreach (var installedPlugin in installedPlugins)
// {
// var updateCheckInfo = installedPlugin.CheckUpdate(onlinePlugins);
// if (updateCheckInfo.IsNewUpdateAvailable())
// {
// pluginUpdateCheckResult.Add(updateCheckInfo);
// }
// }
// return pluginUpdateCheckResult;
// }
//}
//public class PluginUpdateCheckResult
//{
// private ICollection<PluginUpdate> _pluginUpdates;
// public bool Available => PluginUpdates.Any();
// public IEnumerable<PluginUpdate> PluginUpdates => _pluginUpdates ?? Array.Empty<PluginUpdate>();
// /// <summary>
// /// Adds available update information
// /// </summary>
// public void Add(PluginUpdate pluginUpdate)
// {
// _pluginUpdates = _pluginUpdates ?? Array.Empty<PluginUpdate>();
// _pluginUpdates.Add(pluginUpdate);
// }
}
}

View File

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Nikse.SubtitleEdit.Plugins
{
public interface ILocalPluginMetadataProvider
{
IReadOnlyCollection<LocalPlugin> GetInstalledPlugins();
}
public class LocalPluginMetadataProvider : ILocalPluginMetadataProvider
{
private const string KnownPluginExtension = "*.dll";
private readonly string _pluginFolder;
public LocalPluginMetadataProvider(string pluginFolder)
{
_pluginFolder = pluginFolder ?? throw new ArgumentNullException(nameof(pluginFolder));
}
public IReadOnlyCollection<LocalPlugin> GetInstalledPlugins()
{
var pluginFiles = Directory.GetFiles(_pluginFolder, KnownPluginExtension);
// no plugin installed
if (pluginFiles.Length == 0)
{
return new List<LocalPlugin>();
}
var installedPlugins = new List<LocalPlugin>();
const string plugin = "IPlugin";
foreach (var pluginFile in pluginFiles)
{
try
{
var assembly = Assembly.Load(File.ReadAllBytes(pluginFile));
// all plugins must implement the "IPlugin" interface
var pluginType = assembly.GetTypes().FirstOrDefault(type => type.GetInterface(plugin) != null);
var localPlugin = ParseFromType(pluginType);
if (localPlugin?.IsValid() == true)
{
installedPlugins.Add(localPlugin);
}
}
catch (Exception)
{
// ignore
}
}
return installedPlugins;
}
private static LocalPlugin ParseFromType(Type pluginType)
{
if (pluginType is null)
{
return null;
}
var instance = Activator.CreateInstance(pluginType);
var symbolReader = new SymbolReader(pluginType, instance);
// read the following properties from plugin assembly
var text = symbolReader.ReadFromProperty<string>(SymbolReader.Text);
var description = symbolReader.ReadFromProperty<string>(SymbolReader.Description);
var version = symbolReader.ReadFromProperty<decimal>(SymbolReader.Version);
return new LocalPlugin(text, description, version);
}
private class SymbolReader
{
public const string Text = "Text";
public const string Description = "Description";
public const string Version = "Version";
private readonly Type _pluginType;
private readonly object _instance;
private const BindingFlags BindingFlags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
public SymbolReader(Type pluginType, object instance)
{
_pluginType = pluginType;
_instance = instance;
}
public TValue ReadFromProperty<TValue>(string propertyName)
{
var propInfo = _pluginType.GetProperty(propertyName, BindingFlags);
if (propInfo is null)
{
return default;
}
try
{
return (TValue)propInfo.GetValue(_instance, null);
}
catch (Exception)
{
return default;
}
}
}
}
}

View File

@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Nikse.SubtitleEdit.Plugins
{
public interface IOnlinePluginMetadataProvider
{
Task<IReadOnlyCollection<PluginInfo>> GetPluginsAsync();
}
public class OnlinePluginMetadataProvider : IOnlinePluginMetadataProvider
{
private readonly string _githubUrl;
public OnlinePluginMetadataProvider(string githubUrl)
{
_githubUrl = githubUrl ?? throw new ArgumentNullException(nameof(githubUrl));
}
public Task<IReadOnlyCollection<PluginInfo>> GetPluginsAsync()
{
// get from metadata file in github
var xdoc = XDocument.Load(_githubUrl);
var pluginInfos = new List<PluginInfo>();
foreach (var xElement in xdoc.Root.Elements("Plugin"))
{
var pluginInfo = ReadFormXElement(xElement);
if (pluginInfo is null)
{
continue;
}
pluginInfos.Add(pluginInfo);
}
return Task.FromResult<IReadOnlyCollection<PluginInfo>>(pluginInfos);
}
private PluginInfo ReadFormXElement(XElement element)
{
// try parse version
if(!decimal.TryParse(element.Element(nameof(PluginInfo.Version))?.Value, out var version))
{
return null;
}
// read name
var name = element.Element(nameof(PluginInfo.Name))?.Value;
// read description
var description = element.Element(nameof(PluginInfo.Description))?.Value;
return new PluginInfo(name, description, version);
}
}
}

View File

@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
namespace Nikse.SubtitleEdit.Plugins
{
public class PluginInfo
{
public PluginInfo(string name, string description, decimal version)
{
Name = name;
Version = version;
Description = description;
}
public string Name { get; }
public decimal Version { get; }
public string Description { get; }
}
public class LocalPlugin : PluginInfo
{
public LocalPlugin(string name, string description, decimal version)
: base(name, description, version)
{
}
public PluginUpdate CheckUpdate(IEnumerable<PluginInfo> onlinePlugins)
{
foreach (var onlinePlugin in onlinePlugins)
{
if (onlinePlugin is null)
{
continue;
}
if (!Name.Equals(onlinePlugin.Name, StringComparison.OrdinalIgnoreCase))
{
continue;
}
return PluginUpdate(onlinePlugin.Version);
}
return PluginUpdate(Version);
}
private PluginUpdate PluginUpdate(decimal newVersion) => new PluginUpdate
{
Name = Name,
OldVersion = Version,
NewVersion = newVersion,
};
public bool IsValid() => !string.IsNullOrEmpty(Name) && Version > 0;
}
public class PluginUpdate
{
public string Name { get; set; }
public decimal OldVersion { get; set; }
public decimal NewVersion { get; set; }
public bool IsNewUpdateAvailable() => OldVersion < NewVersion;
public override string ToString() => $"{Name} - {OldVersion} -> {NewVersion}*";
}
}

View File

@ -1,97 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Nikse.SubtitleEdit.Plugins
{
public class PluginUpdateChecker
{
private readonly ILocalPluginMetadataProvider _localPluginMetadataProvider;
private readonly IOnlinePluginMetadataProvider _onlinePluginMetadataProvider;
private DateTime _lastCheckDate;
/// <summary>
/// Stores the timestamp of last time check update check was made.
/// </summary>
public DateTime LastCheckDate => _lastCheckDate;
public PluginUpdateChecker(PluginUpdateCheckerOptions options)
: this(new LocalPluginMetadataProvider(options.PluginDirectory),
new OnlinePluginMetadataProvider(options.GithubUrl))
{
}
public PluginUpdateChecker(ILocalPluginMetadataProvider localPluginMetadataProvider,
IOnlinePluginMetadataProvider onlinePluginMetadataProvider)
{
_localPluginMetadataProvider = localPluginMetadataProvider;
_onlinePluginMetadataProvider = onlinePluginMetadataProvider;
}
public async Task<PluginUpdateCheckResult> CheckAsync()
{
_lastCheckDate = DateTime.Now;
var installedPlugins = _localPluginMetadataProvider.GetInstalledPlugins();
// no plugin is currently installed
if (!installedPlugins.Any())
{
return new PluginUpdateCheckResult();
}
// plugin repository
var onlinePlugins = await _onlinePluginMetadataProvider.GetPluginsAsync();
if (!onlinePlugins.Any())
{
return new PluginUpdateCheckResult();
}
var pluginUpdateCheckResult = new PluginUpdateCheckResult();
foreach (var installedPlugin in installedPlugins)
{
var updateCheckInfo = installedPlugin.CheckUpdate(onlinePlugins);
if (updateCheckInfo.IsNewUpdateAvailable())
{
pluginUpdateCheckResult.Add(updateCheckInfo);
}
}
return pluginUpdateCheckResult;
}
}
public class PluginUpdateCheckResult
{
private ICollection<PluginUpdate> _pluginUpdates;
public bool Available => PluginUpdates.Any();
public IEnumerable<PluginUpdate> PluginUpdates => _pluginUpdates ?? Array.Empty<PluginUpdate>();
/// <summary>
/// Adds available update information
/// </summary>
public void Add(PluginUpdate pluginUpdate)
{
_pluginUpdates = _pluginUpdates ?? new List<PluginUpdate>();
_pluginUpdates.Add(pluginUpdate);
}
}
/// <summary>
/// Represents the update checker options for a plugin.
/// </summary>
public class PluginUpdateCheckerOptions
{
/// <summary>
/// Gets or sets the directory path where the plugin is located.
/// </summary>
public string PluginDirectory { get; set; }
/// <summary>
/// Gets or sets the GitHub URL where the plugin's updates are being tracked.
/// </summary>
public string GithubUrl { get; set; }
}
}

View File

@ -1494,6 +1494,7 @@
<DependentUpon>YouTubeAnnotationsImport.cs</DependentUpon>
</Compile>
<Compile Include="Logic\AssaTagHelper.cs" />
<Compile Include="Logic\CheckForUpdatesHelper.cs" />
<Compile Include="Logic\ColorChooser\ColorChangedEventArgs.cs" />
<Compile Include="Logic\ColorChooser\ColorHandler.cs" />
<Compile Include="Logic\ColorChooser\ColorWheel.cs" />
@ -1521,6 +1522,7 @@
<Compile Include="Logic\ListViewHelper.cs" />
<Compile Include="Logic\Networking\SeNetworkService.cs" />
<Compile Include="Logic\PathHelper.cs" />
<Compile Include="Logic\Plugins\IPluginMetadataProvider.cs" />
<Compile Include="Logic\ProgressHelper.cs" />
<Compile Include="Logic\ReplaceAllHelper.cs" />
<Compile Include="Logic\ShotChangesGenerator.cs" />
@ -1591,10 +1593,10 @@
<Compile Include="Logic\VideoPreviewGenerator.cs" />
<Compile Include="Logic\WindowsHelper.cs" />
<Compile Include="Logic\WordSpellChecker.cs" />
<Compile Include="Plugins\LocalPluginMetadataProvider.cs" />
<Compile Include="Plugins\OnlinePluginMetadataProvider.cs" />
<Compile Include="Plugins\PluginInfo.cs" />
<Compile Include="Plugins\PluginUpdateChecker.cs" />
<Compile Include="Logic\Plugins\InstalledPluginMetadataProvider.cs" />
<Compile Include="Logic\Plugins\OnlinePluginMetadataProvider.cs" />
<Compile Include="Logic\Plugins\PluginInfoItem.cs" />
<Compile Include="Logic\Plugins\PluginUpdateChecker.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Forms\SyncPointsSync.cs">