├── assets ├── icon32x32.ico └── SharpUpdater-logo.png ├── src ├── CLI │ ├── icon32x32.ico │ ├── Properties │ │ └── launchSettings.json │ ├── ConsoleExtensions.cs │ ├── CommandHandlers │ │ ├── InitManifestFileCommandHandler.cs │ │ ├── PushCommandHandler.cs │ │ ├── InitIgnoreFileCommandHandler.cs │ │ ├── GlobalSourceCommandHandler.cs │ │ └── PackCommandHandler.cs │ ├── README.md │ ├── SharpUpdater.CLI.csproj │ ├── ProjectHolder.cs │ └── Program.cs ├── VSIX │ ├── Resources │ │ ├── folder.png │ │ ├── Command2Package.ico │ │ └── SharpUpdater-logo16x16.ico │ ├── SharpUpdater-logo.png │ ├── SharpUpdater-VsTool-Menus.png │ ├── SharpUpdater-VsTool-Dialog.png │ ├── release_notes.txt │ ├── FileListItem.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── README.md │ ├── Util │ │ ├── CacheHelper.cs │ │ ├── SolutionDataCache.cs │ │ └── ManifestGatherer.cs │ ├── SharpUpdaterPackage1.cs │ ├── source.extension.vsixmanifest │ ├── SharpUpdaterPackage.vsct │ ├── Resource.Designer.cs │ ├── Commands │ │ ├── AddIgnoreFileCommand.cs │ │ ├── AddManifestFileCommand.cs │ │ └── SharpPackCommand.cs │ ├── Wizard │ │ ├── ManifestGrid.Designer.cs │ │ ├── ProductInfoControl.cs │ │ ├── ProductInfoControl.resx │ │ └── ManifestGrid.resx │ ├── Resource.resx │ ├── Common.cs │ └── SharpUpdaterPackage.cs ├── Clients │ ├── WinForms-DotNet8 │ │ ├── update.ico │ │ ├── Program.cs │ │ ├── Properties │ │ │ ├── PublishProfiles │ │ │ │ └── FolderProfile.pubxml │ │ │ ├── Resources.Designer.cs │ │ │ └── Resources.resx │ │ ├── Common.cs │ │ ├── ZipHelper.cs │ │ ├── Bootstrapper.cs │ │ ├── WinForms-DotNet8.csproj │ │ ├── BaseForm.cs │ │ ├── ConnectionForm.Designer.cs │ │ ├── Installer.cs │ │ ├── UpdateForm.cs │ │ ├── ConnectionForm.cs │ │ └── UpdateForm.designer.cs │ └── WinForms-DotNet4 │ │ ├── FodyWeavers.xml │ │ ├── App.config │ │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ │ ├── packages.config │ │ └── Program.cs └── Core │ ├── ReleaseFile.cs │ ├── UpdateInfo.cs │ ├── Packaging │ ├── PackageFeedSummary.cs │ ├── PackageFeedResolver.cs │ └── PackageBuilder.cs │ ├── Templates.cs │ ├── UpdateLog.cs │ ├── Settings.cs │ ├── Constants.cs │ ├── Util │ ├── HttpUtil.cs │ ├── Monitor.cs │ ├── PackageUploader.cs │ ├── CmdHelper.cs │ ├── Logger.cs │ ├── UriUtility.cs │ ├── IgnoreFileParser.cs │ ├── XmlSerializerHelper.cs │ ├── ProjectHelper.cs │ ├── ManifestGatherer.cs │ ├── VersionUtil.cs │ ├── Downloader.cs │ └── FileUtil.cs │ └── SharpUpdater.Core.csproj ├── README.md ├── SharpUpdater.sln └── .gitignore /assets/icon32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/assets/icon32x32.ico -------------------------------------------------------------------------------- /src/CLI/icon32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/CLI/icon32x32.ico -------------------------------------------------------------------------------- /assets/SharpUpdater-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/assets/SharpUpdater-logo.png -------------------------------------------------------------------------------- /src/VSIX/Resources/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/Resources/folder.png -------------------------------------------------------------------------------- /src/VSIX/SharpUpdater-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/SharpUpdater-logo.png -------------------------------------------------------------------------------- /src/VSIX/Resources/Command2Package.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/Resources/Command2Package.ico -------------------------------------------------------------------------------- /src/VSIX/SharpUpdater-VsTool-Menus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/SharpUpdater-VsTool-Menus.png -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/update.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/Clients/WinForms-DotNet8/update.ico -------------------------------------------------------------------------------- /src/VSIX/SharpUpdater-VsTool-Dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/SharpUpdater-VsTool-Dialog.png -------------------------------------------------------------------------------- /src/CLI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DotNetTool": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/VSIX/Resources/SharpUpdater-logo16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cnsharp/SharpUpdater/HEAD/src/VSIX/Resources/SharpUpdater-logo16x16.ico -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/VSIX/release_notes.txt: -------------------------------------------------------------------------------- 1 | V17.0.3 2025/4/25 2 | - Output .sp/.manifest files only other than all files. 3 | 4 | V17.0 2025/3/2 5 | - Support Visual Studio 2022 (VS17) 6 | 7 | V4.0 2016/6/24 (Has been offline) 8 | - A bran-new version to make package similar to NuGet package -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/VSIX/FileListItem.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.VisualStudio.SharpUpdater 2 | { 3 | public class FileListItem 4 | { 5 | public string Dir { get; set; } 6 | public bool IsFile { get; set; } 7 | public bool Selected { get; set; } 8 | public string RelativeFileName { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Core/ReleaseFile.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.Updater 2 | { 3 | public class ReleaseFile 4 | { 5 | public string FileName { get; set; } 6 | 7 | /// 8 | /// File size in Byte 9 | /// 10 | public long FileSize { get; set; } 11 | 12 | public string Version { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/CLI/ConsoleExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.Updater.CLI 2 | { 3 | public static class ConsoleExtensions 4 | { 5 | public static void WriteError(string message) 6 | { 7 | Console.ForegroundColor = ConsoleColor.Red; 8 | Console.WriteLine(message); 9 | Console.ResetColor(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Core/UpdateInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CnSharp.Updater 4 | { 5 | public struct UpdateInfo 6 | { 7 | public string Version { get; set; } 8 | public string UpdateLog { get; set; } 9 | public string PackUrl { get; set; } 10 | public bool NewVersionFound { get; set; } 11 | public bool Success { get; set; } 12 | public Exception Exception { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace CnSharp.Windows.Updater 5 | { 6 | static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | static void Main() 13 | { 14 | Application.EnableVisualStyles(); 15 | Application.SetCompatibleTextRenderingDefault(false); 16 | Bootstrapper.Start(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/VSIX/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("SharpUpdater")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("SharpUpdater")] 9 | [assembly: AssemblyCopyright("")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | 15 | 16 | [assembly: AssemblyVersion("17.0.3.0")] 17 | [assembly: AssemblyFileVersion("17.0.3.0")] -------------------------------------------------------------------------------- /src/Core/Packaging/PackageFeedSummary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CnSharp.Updater.Packaging 4 | { 5 | public class PackageFeedSummary 6 | { 7 | public string Id { get; set; } 8 | public string Version { get; set; } 9 | public DateTimeOffset Updated { get; set; } 10 | public string ReleaseNotes { get; set; } 11 | public string PackageUrl { get; set; } 12 | public long PackageSize { get; set; } 13 | 14 | public bool IsNew(Manifest manifest) 15 | { 16 | return manifest.Version != Version; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Program.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.Windows.Updater 2 | { 3 | internal static class Program 4 | { 5 | 6 | /// 7 | /// The main entry point for the application. 8 | /// 9 | [STAThread] 10 | static void Main() 11 | { 12 | // To customize application configuration such as set high DPI settings or default font, 13 | // see https://aka.ms/applicationconfiguration. 14 | ApplicationConfiguration.Initialize(); 15 | Bootstrapper.Start(); 16 | } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Release 6 | Any CPU 7 | bin\Release\net8.0-windows\publish\win-x64\ 8 | FileSystem 9 | <_TargetId>Folder 10 | net8.0-windows 11 | win-x64 12 | false 13 | false 14 | 15 | -------------------------------------------------------------------------------- /src/VSIX/README.md: -------------------------------------------------------------------------------- 1 | # SharpUpdater VSIX 2 | 3 | This is a Visual Studio extension to build/deploy SharpUpdater package (.sp) for your desktop applications. 4 | 5 | ## Features 6 | 7 | - Commands to add ignore files and manifest files. 8 | - Commands to pack and push the SharpUpdater package (.sp) to the server. 9 | 10 | ## Screenshots 11 | 12 | ![Sharp Updater VSIX Menus](SharpUpdater-VsTool-Menus.png) 13 | 14 | ![Sharp Updater VSIX Dialog](SharpUpdater-VsTool-Dialog.png) 15 | 16 | ## Fork on GitHub 17 | [https://github.com/cnsharp/SharpUpdater](https://github.com/cnsharp/SharpUpdater) 18 | 19 | ## Feedback 20 | [https://github.com/cnsharp/SharpUpdater/issues](https://github.com/cnsharp/SharpUpdater/issues) -------------------------------------------------------------------------------- /src/Core/Templates.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.Updater 2 | { 3 | public sealed class Templates 4 | { 5 | public const string ManifestXml = 6 | @" 7 | $id$ 8 | 9 | $owner$ 10 | 11 | $copyright$ 12 | $version$ 13 | 14 | $version$ 15 | 16 | 17 | 18 | 19 | 20 | "; 21 | 22 | public const string IgnoreFiles = @".log 23 | .pdb 24 | *.vshost. 25 | updater.exe 26 | "; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Core/UpdateLog.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using System.Xml.Serialization; 3 | 4 | namespace CnSharp.Updater 5 | { 6 | public class UpdateLog 7 | { 8 | public string Version { get; set; } 9 | public string ReleaseDate { get; set; } 10 | private string _description; 11 | [XmlIgnore] 12 | public string Description 13 | { 14 | get { return _description; } 15 | set { _description = value; } 16 | } 17 | 18 | [XmlElement("Description")] //注意生成的节点名要同XmlIgnore的名称一致 19 | public XmlNode TextCData //不要使用XmlCDataSection,否则反序列化会出异常 20 | { 21 | get 22 | { 23 | return new XmlDocument().CreateCDataSection(_description); 24 | //注意此处不能写成 this.Description,否则将会得到null 25 | } 26 | set { _description = value.Value; } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/VSIX/Util/CacheHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using CnSharp.Updater.Util; 4 | 5 | namespace CnSharp.VisualStudio.SharpUpdater.Util 6 | { 7 | public class CacheHelper where T : new() 8 | { 9 | 10 | public T Get(string dir) 11 | { 12 | if (!File.Exists(dir)) 13 | { 14 | throw new FileNotFoundException("cache file not found.", dir); 15 | } 16 | var obj = XmlSerializerHelper.LoadObjectFromXml(dir); 17 | return obj; 18 | } 19 | 20 | public void Save(T project, string dir) 21 | { 22 | SaveXml(project, dir); 23 | } 24 | 25 | 26 | private void SaveXml(T cache, string dir) 27 | { 28 | var xml = XmlSerializerHelper.GetXmlStringFromObject(cache); 29 | File.WriteAllText(dir, xml, Encoding.UTF8); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/Core/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CnSharp.Updater.Util; 4 | 5 | namespace CnSharp.Updater 6 | { 7 | public class Settings 8 | { 9 | public const string FileName = "settings.xml"; 10 | static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Constants.ProductName, FileName); 11 | 12 | public static Settings Load() 13 | { 14 | if (!File.Exists(FilePath)) return null; 15 | return XmlSerializerHelper.LoadObjectFromXml(FilePath); 16 | } 17 | 18 | public string GlobalSource { get; set; } 19 | 20 | 21 | public void Save() 22 | { 23 | var dir = Path.GetDirectoryName(FilePath); 24 | if (!Directory.Exists(dir)) 25 | Directory.CreateDirectory(dir); 26 | XmlSerializerHelper.SerializeToXmlFile(this, FilePath); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/CLI/CommandHandlers/InitManifestFileCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Invocation; 3 | using System.Text; 4 | 5 | namespace CnSharp.Updater.CLI.CommandHandlers; 6 | 7 | internal class InitManifestFileCommandHandler : ICommandHandler 8 | { 9 | 10 | public int Invoke(InvocationContext context) 11 | { 12 | var dir = Directory.GetCurrentDirectory(); 13 | string filePath = Path.Combine(dir, Constants.ManifestFileName); 14 | if (File.Exists(filePath)) 15 | { 16 | ConsoleExtensions.WriteError($"File '{filePath}' is already exists."); 17 | return -1; 18 | } 19 | File.WriteAllText(filePath, Templates.ManifestXml, Encoding.UTF8); 20 | context.Console.WriteLine($"File '{filePath}' created."); 21 | return 0; 22 | } 23 | 24 | public Task InvokeAsync(InvocationContext context) 25 | { 26 | Invoke(context); 27 | return Task.FromResult(0); 28 | } 29 | } -------------------------------------------------------------------------------- /src/CLI/CommandHandlers/PushCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater; 2 | using CnSharp.Updater.Util; 3 | 4 | namespace CnSharp.Updater.CLI.CommandHandlers; 5 | 6 | internal class PushCommandHandler 7 | { 8 | public static async Task Invoke(string packageFile, string source, string apiKey) 9 | { 10 | var globalSource = Settings.Load()?.GlobalSource; 11 | var currentSource = !string.IsNullOrWhiteSpace(source) ? source : globalSource; 12 | if (string.IsNullOrWhiteSpace(currentSource)) 13 | { 14 | ConsoleExtensions.WriteError("--source is required."); 15 | return; 16 | } 17 | Console.WriteLine("Start..."); 18 | try 19 | { 20 | await PackageUploader.Upload(packageFile, currentSource, apiKey); 21 | } 22 | catch(Exception e) 23 | { 24 | ConsoleExtensions.WriteError(e.Message); 25 | return; 26 | } 27 | 28 | Console.WriteLine("Push successfully."); 29 | } 30 | } -------------------------------------------------------------------------------- /src/CLI/CommandHandlers/InitIgnoreFileCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Invocation; 3 | using System.Text; 4 | 5 | namespace CnSharp.Updater.CLI.CommandHandlers; 6 | 7 | internal class InitIgnoreFileCommandHandler : ICommandHandler 8 | { 9 | 10 | public int Invoke(InvocationContext context) 11 | { 12 | var dir = Directory.GetCurrentDirectory(); 13 | string ignoreFilePath = Path.Combine(dir, Constants.IgnoreFileName); 14 | if (File.Exists(ignoreFilePath)) 15 | { 16 | ConsoleExtensions.WriteError($"File '{ignoreFilePath}' is already exists."); 17 | return -1; 18 | } 19 | File.WriteAllText(ignoreFilePath, Templates.IgnoreFiles, Encoding.UTF8); 20 | context.Console.WriteLine($"File '{ignoreFilePath}' created."); 21 | return 0; 22 | } 23 | 24 | public Task InvokeAsync(InvocationContext context) 25 | { 26 | Invoke(context); 27 | return Task.FromResult(0); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using CnSharp.Updater; 4 | 5 | namespace CnSharp.Windows.Updater 6 | { 7 | public class Common 8 | { 9 | public const string AppName = "SharpUpdater"; 10 | 11 | public static string[] IgnoreFiles = 12 | { 13 | "Updater.exe", 14 | "[Content_Types].xml" 15 | }; 16 | 17 | public static string[] IgnoreFolders = {"_rels"}; 18 | 19 | public static string GetLocalText(string key) 20 | { 21 | return Properties.Resources.ResourceManager.GetString(key); 22 | } 23 | 24 | 25 | public static void Start(Manifest manifest,Exception e = null) 26 | { 27 | if (e != null) 28 | { 29 | Console.WriteLine(e.Message); 30 | Console.WriteLine(e.StackTrace); 31 | } 32 | Process.Start(manifest.EntryPoint, e != null ? "exception" : "ok"); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Core/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.Updater 2 | { 3 | public static class Constants 4 | { 5 | public const string ApiKeyHeader = "X-SHARPUPDATER-APIKEY"; 6 | public const string HashAlgorithm = "SHA512"; 7 | public const string ProductName = "SharpUpdater"; 8 | public const string ManifestRelationType = "manifest"; 9 | public const string ManifestExtension = Manifest.ManifestExt; 10 | public const string ManifestFileName = Manifest.ManifestFileName; 11 | public const string IgnoreFileName = ProductName+".ignore"; 12 | public const string PackageRelationshipNamespace = "http://schemas.cnsharp.com/sharpupdater/2016/06/"; 13 | public const string UriId = "sp"; 14 | public const string UpdaterFileName = "updater.exe"; 15 | 16 | /// 17 | /// Represents the ".sp" extension. 18 | /// 19 | public const string PackageExtension = Manifest.PackageFileExt; 20 | 21 | /// 22 | /// Represents the ".sp.sha512" extension. 23 | /// 24 | public const string HashFileExtension = PackageExtension + ".sha512"; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CnSharp.Windows.Updater.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.13.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/CLI/CommandHandlers/GlobalSourceCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater.Util; 2 | 3 | namespace CnSharp.Updater.CLI.CommandHandlers 4 | { 5 | internal class GlobalSourceCommandHandler 6 | { 7 | public static async Task Set(string source) 8 | { 9 | try 10 | { 11 | await HttpUtil.CheckUrl(source); 12 | } 13 | catch (Exception e) 14 | { 15 | ConsoleExtensions.WriteError(e.Message); 16 | return; 17 | } 18 | 19 | var settings = Settings.Load() ?? new Settings(); 20 | settings.GlobalSource = source; 21 | settings.Save(); 22 | Console.WriteLine("Global source set successfully."); 23 | } 24 | 25 | public static async Task Remove() 26 | { 27 | var settings = Settings.Load(); 28 | if (settings == null) 29 | { 30 | Console.WriteLine("Global source is not set."); 31 | return; 32 | } 33 | settings.GlobalSource = null; 34 | settings.Save(); 35 | Console.WriteLine("Global source removed."); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Core/Util/HttpUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Text.RegularExpressions; 4 | using System.Threading.Tasks; 5 | 6 | namespace CnSharp.Updater.Util 7 | { 8 | public class HttpUtil 9 | { 10 | public static async Task CheckUrl(string url) 11 | { 12 | if (!IsValidUrl(url)) 13 | throw new ArgumentException("The provided URL is not a valid URL."); 14 | if (!await IsUrlAccessible(url)) 15 | throw new HttpRequestException("The provided URL is not accessible."); 16 | } 17 | 18 | public static bool IsValidUrl(string url) 19 | { 20 | var pattern = @"^(http|https)://"; 21 | return Regex.IsMatch(url, pattern); 22 | } 23 | 24 | public static async Task IsUrlAccessible(string url) 25 | { 26 | using (var client = new HttpClient()) 27 | { 28 | try 29 | { 30 | var response = await client.GetAsync(url); 31 | return response.IsSuccessStatusCode; 32 | } 33 | catch 34 | { 35 | return false; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WinForms-DotNet4")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WinForms-DotNet4")] 13 | [assembly: AssemblyCopyright("Copyright © 2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e27df114-21a2-4eb5-ade9-bf6559950b8e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/ZipHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Packaging; 3 | using CnSharp.Updater.Util; 4 | 5 | namespace CnSharp.Windows.Updater 6 | { 7 | public class ZipHelper 8 | { 9 | public static void Unzip(string compressedFileName, string folderName, bool overrideExisting) 10 | { 11 | var directoryInfo = new DirectoryInfo(folderName); 12 | if (!directoryInfo.Exists) 13 | directoryInfo.Create(); 14 | 15 | using (var package = Package.Open(compressedFileName, FileMode.Open, FileAccess.Read)) 16 | { 17 | foreach (var packagePart in package.GetParts()) 18 | { 19 | ExtractPart(packagePart, folderName, overrideExisting); 20 | } 21 | } 22 | } 23 | 24 | private static void ExtractPart(PackagePart packagePart, string targetDirectory, bool overrideExisting) 25 | { 26 | var stringPart = targetDirectory + packagePart.Uri.ToString().Replace('\\', '/'); 27 | 28 | if (!Directory.Exists(Path.GetDirectoryName(stringPart))) 29 | Directory.CreateDirectory(Path.GetDirectoryName(stringPart)); 30 | 31 | if (!overrideExisting && File.Exists(stringPart)) 32 | return; 33 | using (var fileStream = new FileStream(stringPart, FileMode.Create)) 34 | { 35 | packagePart.GetStream().CopyTo(fileStream); 36 | } 37 | } 38 | 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /src/Core/Util/Monitor.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.IO; 3 | 4 | namespace CnSharp.Updater.Util 5 | { 6 | public class Monitor : BackgroundWorker 7 | { 8 | private readonly string _manifestPath; 9 | private readonly string _tempDir; 10 | 11 | public Monitor(string manifestPath, string tempDir) 12 | { 13 | _manifestPath = manifestPath; 14 | _tempDir = tempDir; 15 | } 16 | 17 | public Manifest LocalManifest { get; private set; } 18 | public Manifest RemoteManifest { get; private set; } 19 | public bool HasNewVersion { get; private set; } 20 | 21 | protected override void OnDoWork(DoWorkEventArgs e) 22 | { 23 | base.OnDoWork(e); 24 | LocalManifest = FileUtil.ReadManifest(_manifestPath); 25 | RemoteManifest = FileUtil.ReadManifest(LocalManifest.ReleaseUrl + "/sp/FindPackagesById()?id="+LocalManifest.Id); 26 | 27 | 28 | if (LocalManifest.CompareTo(RemoteManifest) != 0) 29 | { 30 | HasNewVersion = true; 31 | if (!string.IsNullOrEmpty(_tempDir)) 32 | { 33 | if (!Directory.Exists(_tempDir)) 34 | Directory.CreateDirectory(_tempDir); 35 | RemoteManifest.Save(Path.Combine(_tempDir, Manifest.ManifestFileName)); 36 | //download update files silently 37 | var loader = new Downloader(LocalManifest, RemoteManifest, _tempDir); 38 | loader.RunWorkerAsync(); 39 | } 40 | } 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater; 2 | using System; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Windows.Forms; 6 | 7 | namespace CnSharp.Windows.Updater 8 | { 9 | public class Bootstrapper 10 | { 11 | private static StreamWriter sw; 12 | public static void Start() 13 | { 14 | var logFile = Path.Combine(Application.StartupPath, "_logs", 15 | $"updater_{DateTime.Today:yyyyMMdd}.log"); 16 | var dir = Path.GetDirectoryName(logFile); 17 | if (!Directory.Exists(dir)) 18 | Directory.CreateDirectory(dir); 19 | sw = new StreamWriter(logFile); 20 | Console.SetOut(sw); 21 | var manifestFile = Path.Combine(Application.StartupPath, Manifest.ManifestFileName); 22 | if (!File.Exists(manifestFile)) 23 | { 24 | Console.WriteLine("manifest not found."); 25 | return; 26 | } 27 | Application.ThreadException += Application_ThreadException; 28 | Application.ApplicationExit += ApplicationOnApplicationExit; 29 | var connForm = new ConnectionForm(); 30 | Application.Run(connForm); 31 | } 32 | 33 | private static void ApplicationOnApplicationExit(object sender, EventArgs e) 34 | { 35 | sw.Flush(); 36 | sw.Close(); 37 | } 38 | 39 | static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) 40 | { 41 | Console.WriteLine(e.Exception.Message); 42 | Console.WriteLine(e.Exception.StackTrace); 43 | } 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /src/Core/SharpUpdater.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | CnSharp.Updater 5 | 5.0.0.0 6 | 5.0.0.0 7 | True 8 | sn.snk 9 | False 10 | False 11 | False 12 | CnSharp Studio 13 | Copyright © CnSharp Studio 2012-2025 14 | Core library of SharpUpdater. 15 | SharpUpdater.Core 16 | Apache-2.0 17 | https://github.com/cnsharp/SharpUpdater 18 | Add settings 19 | git 20 | https://github.com/cnsharp/SharpUpdater.git 21 | 5.0.1 22 | CnSharp Studio 23 | SharpUpdater-logo.png 24 | Winforms,WPF,Desktop,Updater,AutoUpdate 25 | 26 | 27 | 28 | True 29 | \ 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Core/Util/PackageUploader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net.Http.Headers; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace CnSharp.Updater.Util 7 | { 8 | public class PackageUploader 9 | { 10 | public static async Task Upload(string packageFile, string source, string apiKey) 11 | { 12 | if (!packageFile.EndsWith(Constants.PackageExtension)) 13 | throw new InvalidDataException("Invalid package format."); 14 | using (var httpClient = new HttpClient()) 15 | { 16 | httpClient.DefaultRequestHeaders.Add(Constants.ApiKeyHeader, apiKey); 17 | 18 | using (var content = new MultipartFormDataContent()) 19 | { 20 | var fileContent = new ByteArrayContent(await ReadAllBytesAsync(packageFile)); 21 | fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); 22 | content.Add(fileContent, "file", Path.GetFileName(packageFile)); 23 | 24 | var response = await httpClient.PutAsync(source, content); 25 | response.EnsureSuccessStatusCode(); 26 | } 27 | } 28 | } 29 | 30 | public static async Task ReadAllBytesAsync(string filePath) 31 | { 32 | using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true)) 33 | using (var memoryStream = new MemoryStream()) 34 | { 35 | await fileStream.CopyToAsync(memoryStream); 36 | return memoryStream.ToArray(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/CLI/README.md: -------------------------------------------------------------------------------- 1 | # CnSharp.Updater.CLI 2 | 3 | This is a packaging & deployment CLI of `SharpUpdater`. 4 | 5 | ## Installation 6 | ``` 7 | dotnet tool install --global SharpUpdater.CLI 8 | ``` 9 | 10 | ## Root Command 11 | ``` 12 | su 13 | ``` 14 | 15 | ## Commands 16 | ### `global` 17 | 18 | Sets the global settings. 19 | 20 | **Options:** 21 | - `--source`, `-s` : Set the global SharpUpdater.Server source URL (Required) 22 | 23 | ``` 24 | su global -s http://your.server 25 | ``` 26 | 27 | ### `RemoveSource` 28 | 29 | Removes the global source. 30 | 31 | ``` 32 | su RemoveSource 33 | ``` 34 | 35 | ### `init` 36 | 37 | Generates a manifest file in the current directory. 38 | 39 | ``` 40 | su init 41 | ``` 42 | 43 | ### `ignore` 44 | 45 | Generates an ignore file in the current directory. 46 | 47 | ``` 48 | su ignore 49 | ``` 50 | 51 | ### `pack` 52 | 53 | Packs the project. 54 | 55 | **Options:** 56 | - `--source`, `-s` : Specify the SharpUpdater.Server source URL (Optional, follows global source if not inputed) 57 | - `--project`, `-p` : Specify the project directory 58 | - `--output`, `-o` : Specify the output directory (Default: `bin\SharpUpdater\`) 59 | - `--version`, `-v` : Specify the package version 60 | - `--MinimumVersion`, `-mv` : Specify the minimum version that must be updated 61 | - `--ReleaseNotes`, `-rn` : Input release notes 62 | - `--no-build` : Skip build 63 | 64 | ``` 65 | su pack -v 1.0.0 66 | ``` 67 | 68 | ### `push` 69 | 70 | Pushes the .sp package to SharpUpdater.Server. 71 | 72 | **Options:** 73 | - `--package`, `-p` : Specify the .sp file path (Required) 74 | - `--source`, `-s` : Specify the SharpUpdater.Server source URL (Optional, follows global source if not inputed) 75 | - `--apikey`, `-k` : Specify the ApiKey of SharpUpdater.Server (Required) 76 | 77 | ``` 78 | su push -p some.sp -k YOUR_KEY 79 | ``` -------------------------------------------------------------------------------- /src/VSIX/SharpUpdaterPackage1.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace CnSharp.VisualStudio.SharpUpdater 7 | { 8 | using System; 9 | 10 | /// 11 | /// Helper class that exposes all GUIDs used across VS Package. 12 | /// 13 | internal sealed partial class PackageGuids 14 | { 15 | public const string guidSharpUpdaterPackageString = "85ecc0d6-9c3a-432f-ac95-7a9583195cf5"; 16 | public static Guid guidSharpUpdaterPackage = new Guid(guidSharpUpdaterPackageString); 17 | 18 | public const string guidProjectCmdSetString = "9065ee1d-60e4-44a1-9303-2679b24fa055"; 19 | public static Guid guidProjectCmdSet = new Guid(guidProjectCmdSetString); 20 | 21 | public const string guidProjectAddCmdSetString = "307e403c-3431-45b9-86d6-3dff0810d838"; 22 | public static Guid guidProjectAddCmdSet = new Guid(guidProjectAddCmdSetString); 23 | 24 | public const string guidImagesString = "d524bb98-8a0c-4aa9-95ea-87ece9af81f8"; 25 | public static Guid guidImages = new Guid(guidImagesString); 26 | } 27 | /// 28 | /// Helper class that encapsulates all CommandIDs uses across VS Package. 29 | /// 30 | internal sealed partial class PackageIds 31 | { 32 | public const int ProjectMenuGroup = 0x1020; 33 | public const int SharpPackCommandId = 0x0100; 34 | public const int ProjectAddMenuGroup = 0x1020; 35 | public const int AddIgnoreFileCommandId = 0x0100; 36 | public const int AddManifestFileCommandId = 0x0101; 37 | public const int icon1 = 0x0001; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/WinForms-DotNet8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | CnSharp.Windows.Updater 7 | enable 8 | true 9 | enable 10 | Updater 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Form 35 | 36 | 37 | Form 38 | 39 | 40 | True 41 | True 42 | Resources.resx 43 | 44 | 45 | Form 46 | 47 | 48 | 49 | 50 | 51 | Designer 52 | Resources.Designer.cs 53 | ResXFileCodeGenerator 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Core/Packaging/PackageFeedResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | 4 | namespace CnSharp.Updater.Packaging 5 | { 6 | public class PackageFeedResolver 7 | { 8 | private readonly XmlDocument _xmlDoc; 9 | private XmlNamespaceManager _namespaceManager; 10 | 11 | private void AddNamespaceManager() 12 | { 13 | _namespaceManager = new XmlNamespaceManager(_xmlDoc.NameTable); 14 | _namespaceManager.AddNamespace("ns", "http://www.w3.org/2005/Atom"); 15 | _namespaceManager.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"); 16 | _namespaceManager.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); 17 | } 18 | 19 | public PackageFeedResolver(string xml) 20 | { 21 | _xmlDoc = new XmlDocument(); 22 | _xmlDoc.LoadXml(xml); 23 | AddNamespaceManager(); 24 | } 25 | 26 | public PackageFeedResolver(XmlDocument doc) 27 | { 28 | _xmlDoc = doc; 29 | AddNamespaceManager(); 30 | } 31 | 32 | public PackageFeedSummary GetSummary() 33 | { 34 | var root = _xmlDoc.ChildNodes[1]; 35 | return new PackageFeedSummary 36 | { 37 | Id = root.SelectSingleNode("//ns:entry/ns:title",_namespaceManager).InnerText, 38 | Version = root.SelectSingleNode("//ns:entry/m:properties/d:Version", _namespaceManager).InnerText, 39 | Updated = DateTimeOffset.Parse(root.SelectSingleNode("//ns:entry/ns:updated", _namespaceManager).InnerText), 40 | ReleaseNotes = root.SelectSingleNode("//ns:entry/m:properties/d:ReleaseNotes", _namespaceManager).InnerText, 41 | PackageUrl = root.SelectSingleNode("//ns:entry/ns:content", _namespaceManager).Attributes["src"].Value, 42 | PackageSize = long.Parse(root.SelectSingleNode("//ns:entry/m:properties/d:PackageSize", _namespaceManager).InnerText) 43 | }; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Core/Util/CmdHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | 5 | namespace CnSharp.Updater.Util 6 | { 7 | public class CmdHelper 8 | { 9 | public static bool Run(string fileName, string arguments, Action outputMessageHandler = null, Action errorMessageHandler = null) 10 | { 11 | var startInfo = new ProcessStartInfo 12 | { 13 | FileName = fileName, 14 | Arguments = arguments, 15 | RedirectStandardOutput = true, 16 | RedirectStandardError = true, 17 | UseShellExecute = false, 18 | CreateNoWindow = true 19 | }; 20 | 21 | using (var process = new Process { StartInfo = startInfo }) 22 | { 23 | using (var outputWaitHandle = new AutoResetEvent(false)) 24 | using (var errorWaitHandle = new AutoResetEvent(false)) 25 | { 26 | process.OutputDataReceived += (sender, e) => { 27 | if (e.Data == null) 28 | { 29 | outputWaitHandle.Set(); 30 | } 31 | else 32 | { 33 | outputMessageHandler?.Invoke(e.Data + Environment.NewLine); 34 | } 35 | }; 36 | process.ErrorDataReceived += (sender, e) => 37 | { 38 | if (e.Data == null) 39 | { 40 | errorWaitHandle.Set(); 41 | } 42 | else 43 | { 44 | errorMessageHandler?.Invoke(e.Data + Environment.NewLine); 45 | } 46 | }; 47 | 48 | process.Start(); 49 | process.BeginOutputReadLine(); 50 | process.BeginErrorReadLine(); 51 | process.WaitForExit(); 52 | return process.ExitCode == 0; 53 | } 54 | } 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/CLI/SharpUpdater.CLI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | enable 6 | enable 7 | SharpUpdater.CLI 8 | CnSharp.Updater.CLI 9 | icon32x32.ico 10 | True 11 | su 12 | 1.0.0 13 | False 14 | False 15 | False 16 | CnSharp Studio 17 | CnSharp Studio 18 | Copyright © CnSharp Studio 2025 19 | SharpUpdater CLI to build/deploy packages. 20 | SharpUpdater.CLI 21 | Apache-2.0 22 | https://github.com/cnsharp/SharpUpdater 23 | initial version 24 | git 25 | https://github.com/cnsharp/SharpUpdater.git 26 | SharpUpdater-logo.png 27 | Winforms,WPF,Desktop,Updater,AutoUpdate 28 | README.md 29 | 30 | 31 | 32 | True 33 | \ 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | True 47 | \ 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/CLI/ProjectHolder.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater.Util; 2 | 3 | namespace CnSharp.Updater.CLI 4 | { 5 | public class ProjectHolder 6 | { 7 | static readonly List ProjectFileExtensions = [".csproj", ".vbproj"]; 8 | static readonly List ProjectFilePatterns = ProjectFileExtensions.Select(e => "*" + e).ToList(); 9 | private static readonly Dictionary ProjectFileDict = new(); 10 | private static readonly Dictionary ProjectHelperDict = new(); 11 | 12 | public static bool CheckExistsIfProject(string file) 13 | { 14 | if (ProjectFileExtensions.Any(file.EndsWith)) 15 | { 16 | if (!File.Exists(file)) 17 | { 18 | throw new FileNotFoundException($"File {file} does not exist."); 19 | } 20 | return true; 21 | } 22 | if (!Directory.Exists(file)) 23 | { 24 | throw new DirectoryNotFoundException($"Directory {file} does not exist."); 25 | } 26 | return false; 27 | } 28 | 29 | public static string? GetProjectFile(string dir) 30 | { 31 | if (!ProjectFileDict.ContainsKey(dir)) 32 | { 33 | foreach (var pattern in ProjectFilePatterns) 34 | { 35 | var projectFiles = Directory.GetFiles(dir, pattern, SearchOption.TopDirectoryOnly); 36 | if (projectFiles.Length > 0) 37 | { 38 | ProjectFileDict.Add(dir, projectFiles[0]); 39 | return projectFiles[0]; 40 | } 41 | } 42 | 43 | ProjectFileDict.Add(dir, null); 44 | return null; 45 | } 46 | return ProjectFileDict[dir]; 47 | } 48 | 49 | public static ProjectHelper GetProjectHelper(string projectFile) 50 | { 51 | if (!ProjectHelperDict.ContainsKey(projectFile)) 52 | { 53 | var helper = new ProjectHelper(projectFile); 54 | ProjectHelperDict.Add(projectFile, helper); 55 | return helper; 56 | } 57 | return ProjectHelperDict[projectFile]; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/BaseForm.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics; 3 | using System.Windows.Forms; 4 | 5 | namespace CnSharp.Windows.Updater 6 | { 7 | public class BaseForm : Form 8 | { 9 | #region Constants and Fields 10 | 11 | protected bool isEnableCloseButton; 12 | 13 | #endregion 14 | 15 | public BaseForm() 16 | { 17 | InitializeComponent(); 18 | } 19 | 20 | #region Properties 21 | 22 | protected override CreateParams CreateParams 23 | { 24 | get 25 | { 26 | if (isEnableCloseButton) 27 | { 28 | CreateParams parameters = base.CreateParams; 29 | return parameters; 30 | } 31 | else 32 | { 33 | int CS_NOCLOSE = 0x200; 34 | CreateParams parameters = base.CreateParams; 35 | parameters.ClassStyle |= CS_NOCLOSE; 36 | return parameters; 37 | } 38 | } 39 | } 40 | 41 | #endregion 42 | 43 | #region Public Methods 44 | 45 | public DialogResult CheckProcessing(string exeName) 46 | { 47 | if (Process.GetProcessesByName(exeName).Length > 0) 48 | { 49 | DialogResult rs = MessageBox.Show( 50 | string.Format(Common.GetLocalText("CloseRunning"), exeName), 51 | Common.GetLocalText("Warning"), 52 | MessageBoxButtons.RetryCancel, 53 | MessageBoxIcon.Warning, 54 | MessageBoxDefaultButton.Button1); 55 | if (rs == DialogResult.Retry) 56 | { 57 | return CheckProcessing(exeName); 58 | } 59 | return rs; 60 | } 61 | return DialogResult.OK; 62 | } 63 | 64 | #endregion 65 | 66 | 67 | private void InitializeComponent() 68 | { 69 | ComponentResourceManager resources = new ComponentResourceManager(typeof(BaseForm)); 70 | this.SuspendLayout(); 71 | // 72 | // BaseForm 73 | // 74 | resources.ApplyResources(this, "$this"); 75 | this.Name = "BaseForm"; 76 | this.ResumeLayout(false); 77 | 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/Core/Util/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CnSharp.Updater.Util 5 | { 6 | public class Logger 7 | { 8 | /// 9 | /// 多线程锁 10 | /// 11 | private readonly object _locker = new object(); 12 | 13 | private readonly string _logFolder; 14 | 15 | public Logger(string logFolder) 16 | { 17 | this._logFolder = logFolder; 18 | } 19 | 20 | public string LogFolder 21 | { 22 | get { return _logFolder; } 23 | } 24 | 25 | 26 | private void Write(string fileName, string type, string msg) 27 | { 28 | EnsureLogFolder(); 29 | string file = Path.Combine(_logFolder, fileName); 30 | lock (_locker) 31 | { 32 | using (var sw = new StreamWriter(file, true)) 33 | { 34 | sw.WriteLine("{0} - [{2}]: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg, type); 35 | } 36 | } 37 | } 38 | 39 | /// 40 | /// 写调试日志 41 | /// 42 | /// 日志消息 43 | public void WriteDebugLog(string msg) 44 | { 45 | Write("Debug.log", "DEBUG", msg); 46 | } 47 | 48 | /// 49 | /// 写异常日志 50 | /// 51 | /// 日志消息 52 | public void WriteExceptionLog(string msg) 53 | { 54 | Write("Exception.log", "Exception", msg); 55 | } 56 | 57 | /// 58 | /// 写异常日志 59 | /// 60 | /// 异常 61 | public void WriteExceptionLog(Exception exception) 62 | { 63 | Write("Exception.log", "Exception", exception.Message + Environment.NewLine + exception.StackTrace); 64 | } 65 | 66 | /// 67 | /// 写日志 68 | /// 69 | /// 日志消息 70 | public void WriteLog(string msg) 71 | { 72 | Write("Log.log", "Log", msg); 73 | } 74 | 75 | /// 76 | /// 创建日志存储文件夹 77 | /// 78 | private void EnsureLogFolder() 79 | { 80 | if (Directory.Exists(_logFolder) != true) 81 | Directory.CreateDirectory(_logFolder); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/Core/Util/UriUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Packaging; 4 | using System.Linq; 5 | 6 | namespace CnSharp.Updater.Util 7 | { 8 | /// 9 | /// Copy from https://github.com/NuGet/NuGet2/blob/main/src/Core/Utility/UriUtility.cs 10 | /// 11 | public static class UriUtility 12 | { 13 | /// 14 | /// Converts a uri to a path. Only used for local paths. 15 | /// 16 | public static string GetPath(Uri uri) 17 | { 18 | string path = uri.OriginalString; 19 | if (path.StartsWith("/", StringComparison.Ordinal)) 20 | { 21 | path = path.Substring(1); 22 | } 23 | 24 | // We need the unescaped uri string to ensure that all characters are valid for a path. 25 | // Change the direction of the slashes to match the filesystem. 26 | return Uri.UnescapeDataString(path.Replace('/', Path.DirectorySeparatorChar)); 27 | } 28 | 29 | public static Uri CreatePartUri(string path) 30 | { 31 | // Only the segments between the path separators should be escaped 32 | var segments = path.Split(new[] { '/', Path.DirectorySeparatorChar }, StringSplitOptions.None) 33 | .Select(Uri.EscapeDataString); 34 | var escapedPath = string.Join("/", segments.ToArray()); 35 | return PackUriHelper.CreatePartUri(new Uri(escapedPath, UriKind.Relative)); 36 | } 37 | 38 | // Bug 2379: SettingsCredentialProvider does not work 39 | private static Uri CreateODataAgnosticUri(string uri) 40 | { 41 | if (uri.EndsWith("$metadata", StringComparison.OrdinalIgnoreCase)) 42 | { 43 | uri = uri.Substring(0, uri.Length - 9).TrimEnd('/'); 44 | } 45 | return new Uri(uri); 46 | } 47 | 48 | /// 49 | /// Determines if the scheme, server and path of two Uris are identical. 50 | /// 51 | public static bool UriEquals(Uri uri1, Uri uri2) 52 | { 53 | uri1 = CreateODataAgnosticUri(uri1.OriginalString.TrimEnd('/')); 54 | uri2 = CreateODataAgnosticUri(uri2.OriginalString.TrimEnd('/')); 55 | 56 | return Uri.Compare(uri1, uri2, UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0; 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/ConnectionForm.Designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace CnSharp.Windows.Updater 4 | { 5 | partial class ConnectionForm 6 | { 7 | /// 8 | /// Required designer variable. 9 | /// 10 | private System.ComponentModel.IContainer components = null; 11 | 12 | /// 13 | /// Clean up any resources being used. 14 | /// 15 | /// true if managed resources should be disposed; otherwise, false. 16 | protected override void Dispose(bool disposing) 17 | { 18 | if (disposing && (components != null)) 19 | { 20 | components.Dispose(); 21 | } 22 | base.Dispose(disposing); 23 | } 24 | 25 | #region Windows Form Designer generated code 26 | 27 | /// 28 | /// Required method for Designer support - do not modify 29 | /// the contents of this method with the code editor. 30 | /// 31 | private void InitializeComponent() 32 | { 33 | ComponentResourceManager resources = new ComponentResourceManager(typeof(ConnectionForm)); 34 | this.lblStatus = new System.Windows.Forms.Label(); 35 | this.progressBar1 = new System.Windows.Forms.ProgressBar(); 36 | this.SuspendLayout(); 37 | // 38 | // lblStatus 39 | // 40 | resources.ApplyResources(this.lblStatus, "lblStatus"); 41 | this.lblStatus.Name = "lblStatus"; 42 | // 43 | // progressBar1 44 | // 45 | resources.ApplyResources(this.progressBar1, "progressBar1"); 46 | this.progressBar1.Name = "progressBar1"; 47 | this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee; 48 | // 49 | // ConnectionForm 50 | // 51 | resources.ApplyResources(this, "$this"); 52 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 53 | this.Controls.Add(this.progressBar1); 54 | this.Controls.Add(this.lblStatus); 55 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 56 | this.MaximizeBox = false; 57 | this.MinimizeBox = false; 58 | this.Name = "ConnectionForm"; 59 | this.Load += new System.EventHandler(this.ConnectionFormLoad); 60 | this.Shown += new System.EventHandler(this.ConnectionForm_Shown); 61 | this.ResumeLayout(false); 62 | this.PerformLayout(); 63 | 64 | } 65 | 66 | #endregion 67 | 68 | private System.Windows.Forms.Label lblStatus; 69 | private System.Windows.Forms.ProgressBar progressBar1; 70 | } 71 | } -------------------------------------------------------------------------------- /src/Core/Util/IgnoreFileParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace CnSharp.Updater.Util 8 | { 9 | public class IgnoreFileParser 10 | { 11 | private readonly List _files = new List { "updater.exe", Manifest.ManifestFileName, ".pdb" }; 12 | private readonly List _regex = new List(); 13 | 14 | public IgnoreFileParser(string ignoreFile) 15 | { 16 | if (string.IsNullOrWhiteSpace(ignoreFile)) 17 | return; 18 | 19 | if (!File.Exists(ignoreFile)) 20 | throw new FileNotFoundException(".ignore file not found.", ignoreFile); 21 | 22 | using (var sr = new StreamReader(ignoreFile)) 23 | { 24 | while (!sr.EndOfStream) 25 | { 26 | var line = sr.ReadLine(); 27 | if (string.IsNullOrWhiteSpace(line)) 28 | continue; 29 | if (WildCardRegex.IsMatch(line)) 30 | { 31 | var regex = GetWildcardRegexString(line); 32 | _regex.Add(regex); 33 | } 34 | else 35 | { 36 | _files.Add(line); 37 | } 38 | } 39 | } 40 | } 41 | 42 | public bool IsExcluded(string dir) 43 | { 44 | if (_files.Any(f => dir.EndsWith(f, StringComparison.CurrentCultureIgnoreCase))) 45 | return true; 46 | if (_regex.Any(r => Regex.IsMatch(dir, r, RegexOptions.Compiled | RegexOptions.IgnoreCase))) 47 | return true; 48 | return false; 49 | } 50 | 51 | static readonly Regex WildCardRegex = new Regex("[.$^{\\[(|)*+?\\\\]"); 52 | 53 | /// 54 | /// 将通配符字符串转换成等价的正则表达式 55 | /// 这可以用正则表达式来实现通配符匹配 56 | /// 57 | static string GetWildcardRegexString(string wildcardStr) 58 | { 59 | 60 | return WildCardRegex.Replace(wildcardStr, 61 | delegate (Match m) 62 | { 63 | switch (m.Value) 64 | { 65 | 66 | case "?": 67 | return ".?"; 68 | 69 | case "*": 70 | return ".*"; 71 | 72 | default: 73 | return "\\" + m.Value; 74 | 75 | } 76 | }); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Core/Util/XmlSerializerHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Serialization; 3 | 4 | namespace CnSharp.Updater.Util 5 | { 6 | /// 7 | /// serialize/deserialize object by xml formatter 8 | /// 9 | public class XmlSerializerHelper 10 | { 11 | /// 12 | /// copy a instance 13 | /// 14 | /// 15 | /// 16 | /// 17 | public static T Copy(T t) 18 | { 19 | string xml = GetXmlStringFromObject(t); 20 | return LoadObjectFromXmlString(xml); 21 | } 22 | 23 | /// 24 | /// serialize object to xml string 25 | /// 26 | /// object 27 | /// XML string 28 | public static string GetXmlStringFromObject(T obj) 29 | { 30 | var serializer = new XmlSerializer(typeof(T)); 31 | using (var writer = new StringWriter()) 32 | { 33 | serializer.Serialize(writer, obj); 34 | return writer.ToString(); 35 | } 36 | } 37 | 38 | /// 39 | /// Deserialize object from xml file 40 | /// 41 | /// 42 | /// xml file name 43 | /// 44 | public static T LoadObjectFromXml(string filename) 45 | { 46 | var serializer = new XmlSerializer(typeof(T)); 47 | using (var reader = new StreamReader(filename)) 48 | { 49 | return (T)serializer.Deserialize(reader); 50 | } 51 | } 52 | 53 | /// 54 | /// Deserialize object from xml string 55 | /// 56 | /// 57 | /// xml string 58 | /// 59 | public static T LoadObjectFromXmlString(string xml) 60 | { 61 | var serializer = new XmlSerializer(typeof(T)); 62 | using (var reader = new StringReader(xml)) 63 | { 64 | return (T)serializer.Deserialize(reader); 65 | } 66 | } 67 | 68 | public static void SerializeToXmlFile(T obj, string filePath) 69 | { 70 | var serializer = new XmlSerializer(typeof(T)); 71 | using (var writer = new StreamWriter(filePath)) 72 | { 73 | serializer.Serialize(writer, obj); 74 | } 75 | } 76 | 77 | } 78 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpUpdater 2 | A windows application automatic update solution,includes updater clients,update pack repository,CLI and VSIX packaging tool. 3 | 4 | ## Quick start 5 | 6 | ### Build your desktop application 7 | Take winforms for example. 8 | 9 | Fork this repo and clone it to your local machine.Then you can find the clients source code in the location [\src\Clients](src/Clients). 10 | 11 | The clients are not pulished as NuGet package so far, so you can customize the updater as you need. 12 | 13 | Build your updater.exe,publish the client in a single file. 14 | 15 | Create a new winforms project, and integrate the updater.exe by adding it to the main project and set its property 'Copy to Output Directory' as 'Copy Always'. 16 | 17 | Start the updater.exe in the main program. 18 | ```csharp 19 | static void Main(string[] args) 20 | { 21 | ApplicationConfiguration.Initialize(); 22 | 23 | // Check if the application is running from the updater, if not, run the updater. 24 | // The updater.exe will pass the args to the application after updating to avoid checking again. 25 | if (args.Length == 0) 26 | { 27 | var updaterPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Updater.exe"); 28 | if (File.Exists(updaterPath)) 29 | { 30 | var processStartInfo = new ProcessStartInfo 31 | { 32 | FileName = "Updater.exe" 33 | }; 34 | var proc = Process.Start(processStartInfo); 35 | proc?.WaitForExit(); 36 | return; 37 | } 38 | } 39 | 40 | Application.Run(new MainForm()); 41 | } 42 | ``` 43 | 44 | ### Publish your application 45 | This solution focuses on On-premises deployment, so you can publish your application as a pure exe file or make a installer using Windows Application Packaging Project, 46 | Visual Studio Install Project,Inno Setup, etc. 47 | 48 | ### Create a update pack 49 | There are two approaches to create a update pack. 50 | 1. Use the [VSIX packaging tool](https://marketplace.visualstudio.com/items?itemName=CnSharpStudio.SharpUpdater) of SharpUpdater. 51 | 2. Use [SharpUpdater.CLI](https://www.nuget.org/packages/SharpUpdater.CLI). 52 | ``` 53 | dotnet tool install --global SharpUpdater.CLI 54 | 55 | su pack -p C:\path\to\your.csproj -s http://YOUR_SERVER_HOST/sp -v 1.0.0 56 | ``` 57 | 58 | ### Deploy the update pack 59 | First, you need to deploy a [SharpUpdater.Server](https://github.com/cnsharp/SharpUpdater.Server) to host the update packs. 60 | Then, you can deploy the update pack to the server by using the SharpUpdater.CLI or the VSIX. 61 | The command like below: 62 | ``` 63 | su push -p C:\path\to\your\updatepack.sp -s http://YOUR_SERVER_HOST/sp -k YOUR_KEY 64 | ``` -------------------------------------------------------------------------------- /src/VSIX/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SharpUpdater 6 | A wizard tool to build/deploy SharpUpdater packages. SharpUpdater is a full featured auto-update solution for desktop applications. 7 | https://github.com/cnsharp/SharpUpdater 8 | LICENSE.txt 9 | release_notes.txt 10 | SharpUpdater-logo.png 11 | SharpUpdater-VsTool-Dialog.png 12 | AutoUpdate,SharpUpdater,Winforms,WPF 13 | 14 | 15 | 16 | amd64 17 | 18 | 19 | amd64 20 | 21 | 22 | amd64 23 | 24 | 25 | arm64 26 | 27 | 28 | arm64 29 | 30 | 31 | arm64 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CnSharp.Windows.Updater.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CnSharp.Windows.Updater.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CnSharp.Windows.Updater.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CnSharp.Windows.Updater.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Core/Util/ProjectHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | 5 | namespace CnSharp.Updater.Util 6 | { 7 | public class ProjectHelper 8 | { 9 | private readonly XDocument _doc; 10 | private readonly string _projectFile; 11 | 12 | public ProjectHelper(string projectFile) 13 | { 14 | _doc = XDocument.Load(projectFile); 15 | _projectFile = projectFile; 16 | } 17 | 18 | public string ProjectFile => _projectFile; 19 | 20 | public bool IsDotnetFramework 21 | { 22 | get 23 | { 24 | var ns = _doc.Root.GetDefaultNamespace(); 25 | return _doc.Descendants(ns + "TargetFrameworkVersion").FirstOrDefault() != null; 26 | } 27 | } 28 | 29 | public string GetTargetFramework() 30 | { 31 | return _doc.Element("Project") 32 | ?.Element("PropertyGroup") 33 | ?.Element("TargetFramework") 34 | ?.Value; 35 | } 36 | 37 | public string GetAssemblyName() 38 | { 39 | return _doc.Element("Project") 40 | ?.Element("PropertyGroup") 41 | ?.Element("AssemblyName") 42 | ?.Value; 43 | } 44 | 45 | public string GetReleaseDir() 46 | { 47 | var dir = Path.GetDirectoryName(_projectFile); 48 | var path = Path.Combine(dir, "bin", "Release"); 49 | var targetFramework = GetTargetFramework(); 50 | if (!string.IsNullOrEmpty(targetFramework)) 51 | path = Path.Combine(path, targetFramework); 52 | return path; 53 | } 54 | 55 | public string GetBinDir() 56 | { 57 | var dir = Path.GetDirectoryName(_projectFile); 58 | return Path.Combine(dir, "bin"); 59 | } 60 | 61 | public string GetExeFileReleased() 62 | { 63 | var dir = GetReleaseDir(); 64 | if (!Directory.Exists(dir)) 65 | throw new DirectoryNotFoundException("Release directory not found."); 66 | string exe; 67 | var assemblyName = GetAssemblyName(); 68 | if (assemblyName != null) 69 | { 70 | exe = Path.Combine(dir, assemblyName, ".exe"); 71 | if (!File.Exists(exe)) 72 | throw new FileNotFoundException("None of .exe file found in Release directory."); 73 | return exe; 74 | } 75 | var files = Directory.GetFiles(dir, "*.exe", SearchOption.TopDirectoryOnly); 76 | if (files.Length == 0) 77 | throw new FileNotFoundException("None of .exe file found in Release directory."); 78 | exe = files.Where(f => !Path.GetFileName(f).Equals(Constants.UpdaterFileName, System.StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); 79 | if (exe == null) 80 | throw new FileNotFoundException("None of valid .exe file found in Release directory."); 81 | return exe; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/VSIX/Util/SolutionDataCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using CnSharp.VisualStudio.Extensions; 6 | using EnvDTE; 7 | 8 | namespace CnSharp.VisualStudio.SharpUpdater.Util 9 | { 10 | public class SolutionDataCache : ConcurrentDictionary 11 | { 12 | private static SolutionDataCache instance; 13 | protected SolutionDataCache() 14 | { 15 | 16 | } 17 | 18 | public static SolutionDataCache Instance => instance ?? (instance = new SolutionDataCache()); 19 | 20 | public SolutionProperties GetSolutionProperties(string solutionFile,int retryTimes = 3) 21 | { 22 | int i = 0; 23 | SolutionProperties sp; 24 | while (!TryGetValue(solutionFile,out sp)) 25 | { 26 | System.Threading.Thread.Sleep(500); 27 | i++; 28 | if(i == retryTimes) 29 | { 30 | throw new ApplicationException("Load projects failed."); 31 | } 32 | } 33 | return sp; 34 | } 35 | } 36 | 37 | public class SolutionProperties 38 | { 39 | private List _projects; 40 | 41 | public List Projects 42 | { 43 | get { return _projects; } 44 | set 45 | { 46 | _projects = value; 47 | if (_projects != null) 48 | { 49 | ClassicProjects.AddRange(_projects?.Where(p => p.IsNetFrameworkProject())); 50 | SdkBasedProjects.AddRange(_projects?.Where(p => p.IsSdkBased())); 51 | } 52 | else 53 | { 54 | ClassicProjects.Clear(); 55 | SdkBasedProjects.Clear(); 56 | } 57 | } 58 | } 59 | 60 | public List ClassicProjects { get; private set; } = new List(); 61 | public List SdkBasedProjects { get; private set; } = new List(); 62 | public bool HasClassicProjects => ClassicProjects?.Any() == true; 63 | public bool HasSdkBasedProjects => SdkBasedProjects?.Any() == true; 64 | 65 | public void AddProject(Project project) 66 | { 67 | if(_projects == null) _projects = new List(); 68 | _projects.Add(project); 69 | if (project.IsNetFrameworkProject()) 70 | { 71 | ClassicProjects.Add(project); 72 | } 73 | else if (project.IsSdkBased()) 74 | { 75 | SdkBasedProjects.Add(project); 76 | } 77 | } 78 | 79 | public void RemoveProject(Project project) 80 | { 81 | _projects.Remove(project); 82 | if (project.IsNetFrameworkProject()) 83 | { 84 | ClassicProjects.Remove(project); 85 | } 86 | else if (project.IsSdkBased()) 87 | { 88 | SdkBasedProjects.Remove(project); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Core/Util/ManifestGatherer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | 6 | namespace CnSharp.Updater.Util 7 | { 8 | public class ManifestGatherer 9 | { 10 | private readonly string _releaseDir; 11 | private readonly string _projectDir; 12 | 13 | public ManifestGatherer(string releaseDir, string projectDir) 14 | { 15 | _releaseDir = releaseDir; 16 | _projectDir = projectDir; 17 | } 18 | 19 | public List GatherFiles(bool deleteExclusions) 20 | { 21 | return GatherFilesInFolder(_releaseDir, deleteExclusions); 22 | } 23 | 24 | private List GatherFilesInFolder(string dir, bool deleteExclusions) 25 | { 26 | var list = new List(); 27 | 28 | if (IsExcluded(dir)) 29 | { 30 | if (deleteExclusions) 31 | { 32 | try 33 | { 34 | Directory.Delete(dir, true); 35 | } 36 | catch(Exception ex) 37 | { 38 | Console.WriteLine($"Delete directory {dir} failed: {ex.Message}"); 39 | } 40 | } 41 | return list; 42 | } 43 | 44 | string[] files = Directory.GetFiles(dir); 45 | foreach (string file in files) 46 | { 47 | if (IsExcluded(file)) 48 | { 49 | if (deleteExclusions) 50 | { 51 | try 52 | { 53 | File.Delete(file); 54 | } 55 | catch (Exception ex) 56 | { 57 | Console.WriteLine($"Delete file {file} failed: {ex.Message}"); 58 | } 59 | } 60 | continue; 61 | } 62 | list.Add(new ReleaseFile 63 | { 64 | FileName = file.Substring(dir.Length), 65 | FileSize = new FileInfo(file).Length, 66 | Version = FileVersionInfo.GetVersionInfo(file).FileVersion ?? "-" 67 | }); 68 | } 69 | 70 | string[] folders = Directory.GetDirectories(dir); 71 | foreach (string folder in folders) 72 | { 73 | list.AddRange(GatherFilesInFolder(folder, deleteExclusions).ToArray()); 74 | } 75 | return list; 76 | } 77 | 78 | private IgnoreFileParser _ignoreFileParser; 79 | 80 | private void GetIgnoreFileParser() 81 | { 82 | if (_ignoreFileParser != null) 83 | return; 84 | var ignoreFile = Path.Combine(_projectDir, Constants.IgnoreFileName); 85 | _ignoreFileParser = new IgnoreFileParser(File.Exists(ignoreFile) ? ignoreFile : null); 86 | } 87 | 88 | protected virtual bool IsExcluded(string file) 89 | { 90 | GetIgnoreFileParser(); 91 | return _ignoreFileParser.IsExcluded(file); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Installer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using CnSharp.Updater.Util; 7 | 8 | namespace CnSharp.Windows.Updater 9 | { 10 | public class Installer 11 | { 12 | private readonly string _packagePath; 13 | private readonly string _appDir; 14 | private readonly string[] _ignoreFiles; 15 | private string _tempDir; 16 | private string _tempFile; 17 | 18 | public event ProgressChangedEventHandler DownloadProgressChanged; 19 | public event AsyncCompletedEventHandler DownloadCompleted; 20 | 21 | public Installer(string packagePath, string appDir, string[] ignoreFiles) 22 | { 23 | _packagePath = packagePath; 24 | _appDir = appDir; 25 | _ignoreFiles = ignoreFiles; 26 | } 27 | 28 | public async Task InstallAsync() 29 | { 30 | 31 | var client = new HttpClient(); 32 | _tempDir = Path.Combine(Path.GetTempPath(), Common.AppName); 33 | _tempFile = Path.Combine(_tempDir, Guid.NewGuid() + ".zip"); 34 | 35 | var response = await client.GetAsync(_packagePath, HttpCompletionOption.ResponseHeadersRead); 36 | response.EnsureSuccessStatusCode(); 37 | 38 | var totalBytes = response.Content.Headers.ContentLength ?? -1L; 39 | var canReportProgress = totalBytes != -1; 40 | 41 | using (var stream = await response.Content.ReadAsStreamAsync()) 42 | using (var fileStream = new FileStream(_tempFile, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) 43 | { 44 | var buffer = new byte[8192]; 45 | long totalRead = 0; 46 | int bytesRead; 47 | while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0) 48 | { 49 | await fileStream.WriteAsync(buffer, 0, bytesRead); 50 | totalRead += bytesRead; 51 | if (canReportProgress) 52 | { 53 | DownloadProgressChanged?.Invoke(this, new ProgressChangedEventArgs((int)(totalRead * 100 / totalBytes), null)); 54 | } 55 | } 56 | } 57 | 58 | Unzip(); 59 | DownloadCompleted.Invoke(this, new AsyncCompletedEventArgs(null, false, null)); 60 | 61 | } 62 | 63 | private void Unzip() 64 | { 65 | var dir = Path.GetDirectoryName(_tempFile); 66 | dir += "\\" + Guid.NewGuid(); 67 | ZipHelper.Unzip(_tempFile, dir, true); 68 | ClearIgnoreFiles(dir, _ignoreFiles); 69 | FileUtil.CopyFiles(dir, _appDir); 70 | Directory.Delete(dir, true); 71 | } 72 | 73 | private void ClearIgnoreFiles(string dir, string[] ignoreFiles) 74 | { 75 | if (ignoreFiles == null) return; 76 | foreach (var ignoreFile in ignoreFiles) 77 | { 78 | var delFile = Path.Combine(dir, ignoreFile); 79 | if (File.Exists(delFile)) 80 | File.Delete(delFile); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/VSIX/Util/ManifestGatherer.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater.Util; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace CnSharp.VisualStudio.SharpUpdater.Util 6 | { 7 | public class ManifestGatherer 8 | { 9 | 10 | private int _rootLength; 11 | private readonly string _dir; 12 | private string _projectDir; 13 | private readonly NuPackSettings _settings; 14 | 15 | public ManifestGatherer(string dir, string projectDir, NuPackSettings settings) 16 | { 17 | _rootLength = dir.Length; 18 | _dir = dir; 19 | _projectDir = projectDir; 20 | _settings = settings; 21 | } 22 | 23 | public List GatherFiles() 24 | { 25 | 26 | return GatherFilesInFolder(_dir, true,_settings.UnselectedFolders,_settings.UnselectedFiles); 27 | } 28 | 29 | private List GatherFilesInFolder(string dir, bool isFirst,IList unselectedFolders,IList unselectedFiles ) 30 | { 31 | var list = new List(); 32 | 33 | if (IsExcluded(dir)) 34 | return list; 35 | 36 | string dirShortName = dir.Substring(_rootLength); 37 | bool folderUnselected = unselectedFolders?.Contains(dirShortName) ?? false; 38 | if (!isFirst) 39 | { 40 | var folderItem = new FileListItem { Dir = dir, Selected = !folderUnselected }; 41 | list.Add(folderItem); 42 | } 43 | 44 | string[] files = Directory.GetFiles(dir); 45 | foreach (string file in files) 46 | { 47 | if (IsExcluded(file)) 48 | continue; 49 | string shortName = dir.Substring(_rootLength); 50 | bool selected = !folderUnselected && 51 | (unselectedFiles == null || !unselectedFiles.Contains(shortName)) 52 | && !IsFileUnselected(file); 53 | list.Add(new FileListItem 54 | { 55 | Dir = file, 56 | IsFile = true, 57 | Selected = selected 58 | }); 59 | } 60 | 61 | string[] folders = Directory.GetDirectories(dir); 62 | foreach (string folder in folders) 63 | { 64 | list.AddRange(GatherFilesInFolder(folder, false,unselectedFolders,unselectedFiles).ToArray()); 65 | } 66 | return list; 67 | } 68 | 69 | private IgnoreFileParser _filter; 70 | 71 | private void GetManifestFilter() 72 | { 73 | if (_filter != null) 74 | return; 75 | var ignoreFile = Path.Combine(_projectDir,Common.IgnoreFileName); 76 | _filter = new IgnoreFileParser(File.Exists(ignoreFile) ? ignoreFile : null); 77 | } 78 | 79 | protected virtual bool IsExcluded(string file) 80 | { 81 | GetManifestFilter(); 82 | return _filter.IsExcluded(file); 83 | } 84 | 85 | protected virtual bool IsFileUnselected(string file) 86 | { 87 | var ext = Path.GetExtension(file).ToLower(); 88 | return (ext == ".xml" && File.Exists(file.Substring(0, file.Length - 4) + ".dll")); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/Core/Util/VersionUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace CnSharp.Updater.Util 5 | { 6 | public static class VersionUtil 7 | { 8 | 9 | public const string SemanticVersionPattern = 10 | "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"; 11 | 12 | private static readonly Regex SemanticVersionRegex = new Regex(SemanticVersionPattern, RegexOptions.Compiled); 13 | public static int CompareVersion(this string version, string otherVersion) 14 | { 15 | return CompareVersions(version, otherVersion); 16 | } 17 | 18 | public static int CompareVersions(string version1, string version2) 19 | { 20 | // Split version number and pre-release identifier 21 | var v1Parts = version1.Split('-'); 22 | var v2Parts = version2.Split('-'); 23 | 24 | // Parse the main version number 25 | Version v1 = Version.Parse(v1Parts[0]); 26 | Version v2 = Version.Parse(v2Parts[0]); 27 | 28 | // Compare the main version numbers 29 | int mainComparison = v1.CompareTo(v2); 30 | if (mainComparison != 0) 31 | return mainComparison; 32 | 33 | // If the main version numbers are the same, compare the pre-release identifiers 34 | string preRelease1 = v1Parts.Length > 1 ? v1Parts[1] : string.Empty; 35 | string preRelease2 = v2Parts.Length > 1 ? v2Parts[1] : string.Empty; 36 | 37 | return ComparePreRelease(preRelease1, preRelease2); 38 | } 39 | 40 | private static int ComparePreRelease(string preRelease1, string preRelease2) 41 | { 42 | // If neither has a pre-release identifier, they are equal 43 | if (string.IsNullOrEmpty(preRelease1) && string.IsNullOrEmpty(preRelease2)) 44 | return 0; 45 | 46 | // If one version has a pre-release identifier, it is considered smaller 47 | if (string.IsNullOrEmpty(preRelease1)) 48 | return 1; // version1 is a stable release, greater than version2 49 | if (string.IsNullOrEmpty(preRelease2)) 50 | return -1; // version2 is a stable release, greater than version1 51 | 52 | // Compare pre-release identifiers 53 | return string.Compare(preRelease1, preRelease2, StringComparison.OrdinalIgnoreCase); 54 | } 55 | 56 | public static string ToSemanticVersion(this string versionNumber) 57 | { 58 | if (versionNumber.IsSemanticVersion()) 59 | return versionNumber; 60 | // Split the version number 61 | string[] parts = versionNumber.Split('.'); 62 | 63 | // Ensure at least MAJOR.MINOR.PATCH 64 | if (parts.Length < 3) 65 | { 66 | throw new ArgumentException("Version number must have at least 3 parts (MAJOR.MINOR.PATCH)."); 67 | } 68 | 69 | // Extract MAJOR, MINOR, PATCH 70 | string major = parts[0]; 71 | string minor = parts[1]; 72 | string patch = parts[2]; 73 | 74 | // Build the Semantic Version 75 | return $"{major}.{minor}.{patch}"; 76 | } 77 | 78 | public static bool IsSemanticVersion(this string version) 79 | { 80 | return SemanticVersionRegex.IsMatch(version); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/VSIX/SharpUpdaterPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 36 | 45 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/VSIX/Resource.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CnSharp.VisualStudio.SharpUpdater { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resource { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resource() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CnSharp.VisualStudio.SharpUpdater.Resource", typeof(Resource).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap folder { 67 | get { 68 | object obj = ResourceManager.GetObject("folder", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 75 | /// 76 | internal static System.Drawing.Icon logo { 77 | get { 78 | object obj = ResourceManager.GetObject("logo", resourceCulture); 79 | return ((System.Drawing.Icon)(obj)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Core/Util/Downloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.IO; 4 | using System.Net; 5 | 6 | namespace CnSharp.Updater.Util 7 | { 8 | public class Downloader : BackgroundWorker 9 | { 10 | private readonly Manifest _remoteManifest; 11 | private readonly string _tempDir; 12 | private readonly long _totalSize; 13 | private readonly ReleaseFile[] _diff; 14 | 15 | public long TotalBytes 16 | { 17 | get { return _totalSize; } 18 | } 19 | 20 | public long ReceivedBytes { get; private set; } 21 | 22 | 23 | public Downloader(Manifest localManifest, Manifest remoteManifest, string tempDir) 24 | { 25 | _remoteManifest = remoteManifest; 26 | _tempDir = tempDir; 27 | _diff = localManifest.GetDifferences(_remoteManifest, out _totalSize); 28 | if (!Directory.Exists(_tempDir)) 29 | Directory.CreateDirectory(_tempDir); 30 | } 31 | 32 | 33 | public Downloader(Manifest remoteManifest,ReleaseFile[] files,long totalSize, string tempDir) 34 | { 35 | _remoteManifest = remoteManifest; 36 | _tempDir = tempDir; 37 | _diff = files; 38 | _totalSize = totalSize; 39 | if (!Directory.Exists(_tempDir)) 40 | Directory.CreateDirectory(_tempDir); 41 | } 42 | 43 | public new void RunWorkerAsync() 44 | { 45 | if (_diff == null || _diff.Length == 0) 46 | return; 47 | var di = new DownloadInfo 48 | { 49 | WebRoot = _remoteManifest.VersionRoot,//todo 50 | TempDir = _tempDir, 51 | Files = _diff 52 | }; 53 | 54 | base.RunWorkerAsync(di); 55 | } 56 | 57 | 58 | 59 | //public event DownloadProgressChangedEventHandler DownloadProgressChanged; 60 | 61 | 62 | protected override void OnDoWork(DoWorkEventArgs e) 63 | { 64 | base.OnDoWork(e); 65 | var info = e.Argument as DownloadInfo; 66 | if (info == null) 67 | return; 68 | foreach (var file in info.Files) 69 | { 70 | try 71 | { 72 | using (var wc = new WebClient()) 73 | { 74 | var fileName = Path.Combine(info.TempDir, file.FileName); 75 | var dir = Path.GetDirectoryName(fileName); 76 | if (!Directory.Exists(dir)) 77 | Directory.CreateDirectory(dir); 78 | 79 | wc.DownloadFile(new Uri(info.WebRoot + "/" + file.FileName), 80 | fileName); 81 | if (base.WorkerReportsProgress) 82 | { 83 | SendProgress(file.FileSize); 84 | } 85 | } 86 | } 87 | catch 88 | { 89 | e.Result = file.FileName; 90 | throw; 91 | } 92 | } 93 | } 94 | 95 | private void SendProgress(long bytes) 96 | { 97 | ReceivedBytes += bytes; 98 | 99 | var progress = Math.Min(ReceivedBytes, _totalSize); 100 | var percentage = (int)((Math.Round(Convert.ToDecimal(progress) / Convert.ToDecimal(_totalSize) * 1.0M, 2)) * 100); 101 | base.ReportProgress(percentage); 102 | } 103 | 104 | 105 | 106 | public class DownloadInfo 107 | { 108 | public string WebRoot { get; set; } 109 | public string TempDir { get; set; } 110 | public ReleaseFile[] Files { get; set; } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/Core/Packaging/PackageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Packaging; 5 | using CnSharp.Updater.Util; 6 | 7 | namespace CnSharp.Updater.Packaging 8 | { 9 | public class PackageBuilder 10 | { 11 | private readonly Manifest _manifest; 12 | private readonly string _baseDir; 13 | private const string DefaultContentType = "application/octet"; 14 | 15 | public PackageBuilder(Manifest manifest,string baseDir) 16 | { 17 | _manifest = manifest; 18 | _baseDir = baseDir; 19 | } 20 | 21 | public Package CreatePackage(string targetPath) 22 | { 23 | using (Package package = Package.Open(targetPath, FileMode.Create)) 24 | { 25 | WriteManifest(package,1); 26 | WriteFiles(package); 27 | 28 | package.PackageProperties.Creator = _manifest.Owner; 29 | package.PackageProperties.Description = _manifest.Description; 30 | package.PackageProperties.Identifier = _manifest.Id; 31 | package.PackageProperties.Version = _manifest.Version; 32 | package.PackageProperties.Language = _manifest.Language; 33 | package.PackageProperties.Keywords = _manifest.Tags; 34 | package.PackageProperties.Title = _manifest.AppName; 35 | package.PackageProperties.LastModifiedBy = CreatorInfo(); 36 | 37 | return package; 38 | } 39 | } 40 | 41 | 42 | private static string CreatorInfo() 43 | { 44 | List creatorInfo = new List(); 45 | var assembly = typeof(PackageBuilder).Assembly; 46 | creatorInfo.Add(assembly.FullName); 47 | creatorInfo.Add(Environment.OSVersion.ToString()); 48 | 49 | return string.Join(";", creatorInfo.ToArray()); 50 | } 51 | 52 | 53 | 54 | private void WriteManifest(Package package, int minimumManifestVersion) 55 | { 56 | Uri uri = UriUtility.CreatePartUri(Manifest.ManifestFileName); 57 | 58 | // Create the manifest relationship 59 | package.CreateRelationship(uri, TargetMode.Internal, Constants.PackageRelationshipNamespace + Constants.ManifestRelationType); 60 | 61 | // Create the part 62 | PackagePart packagePart = package.CreatePart(uri, DefaultContentType, CompressionOption.Maximum); 63 | 64 | using (Stream stream = packagePart.GetStream()) 65 | { 66 | _manifest.Save(stream, minimumManifestVersion); 67 | } 68 | } 69 | 70 | private void WriteFiles(Package package) 71 | { 72 | var files = Directory.GetFiles(_baseDir, @"*.*", SearchOption.AllDirectories); 73 | var dirLen = _baseDir.Length; 74 | foreach (var each in files) 75 | { 76 | 77 | var relPath = each.Substring(dirLen).Replace("\\", "/"); 78 | if (relPath.StartsWith("/")) 79 | relPath = relPath.TrimStart('/'); 80 | string destFilename = "/" + relPath; 81 | Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative)); 82 | if (package.PartExists(uri)) 83 | { 84 | package.DeletePart(uri); 85 | } 86 | PackagePart part = package.CreatePart(uri, "", CompressionOption.Normal); 87 | using (FileStream fileStream = new FileStream(each, FileMode.Open, FileAccess.Read)) 88 | { 89 | using (Stream dest = part.GetStream()) 90 | { 91 | fileStream.CopyTo(dest); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/UpdateForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Windows.Forms; 4 | using CnSharp.Updater; 5 | using CnSharp.Updater.Packaging; 6 | using CnSharp.Updater.Util; 7 | 8 | namespace CnSharp.Windows.Updater 9 | { 10 | public partial class UpdateForm : BaseForm 11 | { 12 | #region Constants and Fields 13 | 14 | private static readonly string FinishText = Common.GetLocalText("Completed"); 15 | private static readonly string RetryText = Common.GetLocalText("Retry"); 16 | private readonly PackageFeedSummary _packageFeedSummary; 17 | private Manifest _localManifest; 18 | private bool _downloaded; 19 | private readonly Installer _installer; 20 | 21 | #endregion 22 | 23 | #region Constructors and Destructors 24 | 25 | public UpdateForm(Manifest localManifest, PackageFeedSummary packageFeedSummary) 26 | { 27 | InitializeComponent(); 28 | 29 | btnUpgrade.Focus(); 30 | 31 | _localManifest = localManifest; 32 | _packageFeedSummary = packageFeedSummary; 33 | 34 | _installer = new Installer(packageFeedSummary.PackageUrl, Application.StartupPath, Common.IgnoreFiles); 35 | _installer.DownloadProgressChanged += (sender, e) => { progressBar.Value = e.ProgressPercentage; }; 36 | _installer.DownloadCompleted += (sender, args) => { Finish(); }; 37 | } 38 | 39 | 40 | private void Finish() 41 | { 42 | btnUpgrade.Enabled = true; 43 | btnUpgrade.Text = FinishText; 44 | _downloaded = true; 45 | 46 | Application.Exit(); 47 | Common.Start(_localManifest); 48 | } 49 | 50 | #endregion 51 | 52 | #region Properties 53 | 54 | private bool OptionalUpdate 55 | { 56 | set => isEnableCloseButton = btnUpgrade.Enabled = btnCancel.Enabled = value; 57 | } 58 | 59 | #endregion 60 | 61 | #region Methods 62 | 63 | private async Task DoUpgrade() 64 | { 65 | _downloaded = false; 66 | progressBar.Value = 0; 67 | await _installer.InstallAsync(); 68 | } 69 | 70 | private void FormUpdate_FormClosing(object sender, FormClosingEventArgs e) 71 | { 72 | } 73 | 74 | private async void FormUpdate_Load(object sender, EventArgs e) 75 | { 76 | lblSize.Text = string.Empty; 77 | await Init(); 78 | } 79 | 80 | private async Task Init() 81 | { 82 | OptionalUpdate = _localManifest.CompareTo(_packageFeedSummary.Version) >= 0; 83 | lblTitle.Text = string.Format( 84 | lblTitle.Text, 85 | _localManifest.Version, 86 | _packageFeedSummary.Version, 87 | _packageFeedSummary.Updated); 88 | 89 | boxDes.Text = _packageFeedSummary.ReleaseNotes; 90 | 91 | progressBar.Maximum = 100; 92 | if (!btnUpgrade.Enabled) 93 | { 94 | await DoUpgrade(); 95 | } 96 | } 97 | 98 | private void btnCancel_Click(object sender, EventArgs e) 99 | { 100 | //Directory.Delete(tempPath, true); 101 | Application.Exit(); 102 | Common.Start(_localManifest); 103 | } 104 | 105 | private async void btnUpgrade_Click(object sender, EventArgs e) 106 | { 107 | btnUpgrade.Enabled = false; 108 | btnCancel.Enabled = false; 109 | if (!_downloaded) 110 | { 111 | await DoUpgrade(); 112 | } 113 | else 114 | { 115 | Application.Exit(); 116 | Common.Start(_localManifest); 117 | } 118 | } 119 | 120 | #endregion 121 | } 122 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/ConnectionForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Windows.Forms; 6 | using System.Xml; 7 | using CnSharp.Updater; 8 | using CnSharp.Updater.Packaging; 9 | using CnSharp.Updater.Util; 10 | 11 | namespace CnSharp.Windows.Updater 12 | { 13 | public partial class ConnectionForm : BaseForm 14 | { 15 | #region Constants and Fields 16 | 17 | private readonly BackgroundWorker _worker; 18 | 19 | #endregion 20 | 21 | #region Constructors and Destructors 22 | 23 | public ConnectionForm() 24 | { 25 | InitializeComponent(); 26 | 27 | var manifestFileName = Path.Combine(Application.StartupPath, Manifest.ManifestFileName); 28 | if (!File.Exists(manifestFileName)) 29 | { 30 | return; 31 | } 32 | 33 | LocalManifest = FileUtil.ReadManifest(manifestFileName); 34 | _worker = new BackgroundWorker(); 35 | _worker.DoWork += DoWork; 36 | _worker.RunWorkerCompleted += WorkerOnRunWorkerCompleted; 37 | } 38 | 39 | private void DoWork(object sender, DoWorkEventArgs e) 40 | { 41 | //var feedUrl = $"{LocalManifest.ReleaseUrl}/GetUpdates()?packageIds='{LocalManifest.Id}'&versions='{LocalManifest.Version}'&includePrerelease=false"; 42 | var feedUrl = $"{LocalManifest.ReleaseUrl}/Search()?$filter=IsLatestVersion&searchTerm='{LocalManifest.Id}'&includePrerelease=false"; 43 | var doc = new XmlDocument(); 44 | doc.Load(feedUrl); 45 | var feedResolver = new PackageFeedResolver(doc); 46 | var sum = feedResolver.GetSummary(); 47 | e.Result = sum; 48 | } 49 | 50 | private void WorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 51 | { 52 | if (e.Error != null) 53 | { 54 | Application.Exit(); 55 | Common.Start(LocalManifest, e.Error); 56 | return; 57 | } 58 | 59 | var sum = e.Result as PackageFeedSummary; 60 | 61 | if(!sum.IsNew(LocalManifest)) 62 | { 63 | Application.Exit(); 64 | Common.Start(LocalManifest); 65 | return; 66 | } 67 | //Hide(); 68 | 69 | if (CheckProcessing() != DialogResult.OK) 70 | { 71 | Application.Exit(); 72 | return; 73 | } 74 | var form = new UpdateForm(LocalManifest, sum); 75 | form.ShowDialog(); 76 | } 77 | 78 | #endregion 79 | 80 | public Manifest LocalManifest { get; } 81 | 82 | #region Methods 83 | 84 | private DialogResult CheckProcessing() 85 | { 86 | string exeName = LocalManifest.EntryPoint.Substring(0, LocalManifest.EntryPoint.Length - 4); 87 | if (Process.GetProcessesByName(exeName).Length > 0) 88 | { 89 | DialogResult rs = MessageBox.Show( 90 | string.Format(Common.GetLocalText("CloseRunning"), exeName), 91 | Common.GetLocalText("Warning"), 92 | MessageBoxButtons.RetryCancel, 93 | MessageBoxIcon.Warning, 94 | MessageBoxDefaultButton.Button1); 95 | if (rs == DialogResult.Retry) 96 | { 97 | return CheckProcessing(); 98 | } 99 | return rs; 100 | } 101 | return DialogResult.OK; 102 | } 103 | 104 | private void ConnectionFormLoad(object sender, EventArgs e) 105 | { 106 | _worker.RunWorkerAsync(); 107 | } 108 | 109 | #endregion 110 | 111 | private void ConnectionForm_Shown(object sender, EventArgs e) 112 | { 113 | 114 | } 115 | 116 | } 117 | } -------------------------------------------------------------------------------- /SharpUpdater.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35707.178 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpUpdater.Core", "src\Core\SharpUpdater.Core.csproj", "{1599F1D6-AA67-43AB-B544-0B73720564A5}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{9B68B470-2460-43EF-9956-EB1B2C718363}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VSIX", "VSIX", "{6E2DEA9D-B2A7-46D7-8D33-9EE8E9013D95}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinForms-DotNet8", "src\Clients\WinForms-DotNet8\WinForms-DotNet8.csproj", "{B1EDD1A8-5AA7-4E12-89B7-97D63EF78555}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CLI", "CLI", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinForms-DotNet4", "src\Clients\WinForms-DotNet4\WinForms-DotNet4.csproj", "{E27DF114-21A2-4EB5-ADE9-BF6559950B8E}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpUpdater.CLI", "src\CLI\SharpUpdater.CLI.csproj", "{798C7344-36E3-4DB8-510E-96A2449418DA}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpUpdater.VsTool", "src\VSIX\SharpUpdater.VsTool.csproj", "{B322241D-ACFC-48C2-AF34-849E5DF8401D}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5108B42C-B72C-4F01-9857-8D9862AD6000}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{7D804308-3112-479E-B82B-C411B334D08F}" 25 | ProjectSection(SolutionItems) = preProject 26 | .gitignore = .gitignore 27 | LICENSE = LICENSE 28 | README.md = README.md 29 | EndProjectSection 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {1599F1D6-AA67-43AB-B544-0B73720564A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {1599F1D6-AA67-43AB-B544-0B73720564A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {1599F1D6-AA67-43AB-B544-0B73720564A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {1599F1D6-AA67-43AB-B544-0B73720564A5}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {B1EDD1A8-5AA7-4E12-89B7-97D63EF78555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {B1EDD1A8-5AA7-4E12-89B7-97D63EF78555}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {B1EDD1A8-5AA7-4E12-89B7-97D63EF78555}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {B1EDD1A8-5AA7-4E12-89B7-97D63EF78555}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {E27DF114-21A2-4EB5-ADE9-BF6559950B8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {E27DF114-21A2-4EB5-ADE9-BF6559950B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {E27DF114-21A2-4EB5-ADE9-BF6559950B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {E27DF114-21A2-4EB5-ADE9-BF6559950B8E}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {798C7344-36E3-4DB8-510E-96A2449418DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {798C7344-36E3-4DB8-510E-96A2449418DA}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {798C7344-36E3-4DB8-510E-96A2449418DA}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {798C7344-36E3-4DB8-510E-96A2449418DA}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {B322241D-ACFC-48C2-AF34-849E5DF8401D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {B322241D-ACFC-48C2-AF34-849E5DF8401D}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {B322241D-ACFC-48C2-AF34-849E5DF8401D}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {B322241D-ACFC-48C2-AF34-849E5DF8401D}.Release|Any CPU.Build.0 = Release|Any CPU 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(NestedProjects) = preSolution 62 | {1599F1D6-AA67-43AB-B544-0B73720564A5} = {5108B42C-B72C-4F01-9857-8D9862AD6000} 63 | {B1EDD1A8-5AA7-4E12-89B7-97D63EF78555} = {9B68B470-2460-43EF-9956-EB1B2C718363} 64 | {E27DF114-21A2-4EB5-ADE9-BF6559950B8E} = {9B68B470-2460-43EF-9956-EB1B2C718363} 65 | {798C7344-36E3-4DB8-510E-96A2449418DA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} 66 | {B322241D-ACFC-48C2-AF34-849E5DF8401D} = {6E2DEA9D-B2A7-46D7-8D33-9EE8E9013D95} 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {27D99C64-A781-4606-9D8C-0633934CDEF9} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /src/VSIX/Commands/AddIgnoreFileCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.IO; 4 | using System.Text; 5 | using CnSharp.Updater; 6 | using CnSharp.VisualStudio.Extensions; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace CnSharp.VisualStudio.SharpUpdater.Commands 11 | { 12 | /// 13 | /// Command handler 14 | /// 15 | internal sealed class AddIgnoreFileCommand 16 | { 17 | /// 18 | /// Command ID. 19 | /// 20 | public const int CommandId = PackageIds.AddIgnoreFileCommandId; 21 | 22 | /// 23 | /// Command menu group (command set GUID). 24 | /// 25 | public static readonly Guid CommandSet = PackageGuids.guidProjectAddCmdSet; 26 | 27 | /// 28 | /// VS Package that provides this command, not null. 29 | /// 30 | private readonly AsyncPackage package; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Adds our command handlers for menu (commands must exist in the command table file) 35 | /// 36 | /// Owner package, not null. 37 | /// Command service to add command to, not null. 38 | private AddIgnoreFileCommand(AsyncPackage package, OleMenuCommandService commandService) 39 | { 40 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 41 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 42 | 43 | var menuCommandID = new CommandID(CommandSet, CommandId); 44 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 45 | commandService.AddCommand(menuItem); 46 | } 47 | 48 | /// 49 | /// Gets the instance of the command. 50 | /// 51 | public static AddIgnoreFileCommand Instance 52 | { 53 | get; 54 | private set; 55 | } 56 | 57 | /// 58 | /// Gets the service provider from the owner package. 59 | /// 60 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider 61 | { 62 | get 63 | { 64 | return this.package; 65 | } 66 | } 67 | 68 | /// 69 | /// Initializes the singleton instance of the command. 70 | /// 71 | /// Owner package, not null. 72 | public static async Task InitializeAsync(AsyncPackage package) 73 | { 74 | // Switch to the main thread - the call to AddCommand in AddIgnoreFileCommand's constructor requires 75 | // the UI thread. 76 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 77 | 78 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 79 | Instance = new AddIgnoreFileCommand(package, commandService); 80 | } 81 | 82 | /// 83 | /// This function is the callback used to execute the command when the menu item is clicked. 84 | /// See the constructor to see how the menu item is associated with this function using 85 | /// OleMenuCommandService service and MenuCommand class. 86 | /// 87 | /// Event sender. 88 | /// Event args. 89 | private void Execute(object sender, EventArgs e) 90 | { 91 | ThreadHelper.ThrowIfNotOnUIThread(); 92 | 93 | var dte = Host.Instance.Dte2; 94 | var project = dte.GetActiveProject(); 95 | var file = Path.Combine(project.GetDirectory(), Common.IgnoreFileName); 96 | if (File.Exists(file)) 97 | { 98 | Common.ShowError($"File {Common.IgnoreFileName} already exists."); 99 | return; 100 | } 101 | 102 | File.WriteAllText(file, Templates.IgnoreFiles, Encoding.UTF8); 103 | 104 | project.ProjectItems.AddFromFile(file); 105 | dte.ItemOperations.OpenFile(file); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/UpdateForm.designer.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace CnSharp.Windows.Updater 6 | { 7 | partial class UpdateForm 8 | { 9 | /// 10 | /// Required designer variable. 11 | /// 12 | private System.ComponentModel.IContainer components = null; 13 | 14 | /// 15 | /// Clean up any resources being used. 16 | /// 17 | /// true if managed resources should be disposed; otherwise, false. 18 | protected override void Dispose(bool disposing) 19 | { 20 | if (disposing && (this.components != null)) 21 | { 22 | this.components.Dispose(); 23 | } 24 | base.Dispose(disposing); 25 | } 26 | 27 | #region Windows Form Designer generated code 28 | 29 | /// 30 | /// Required method for Designer support - do not modify 31 | /// the contents of this method with the code editor. 32 | /// 33 | private void InitializeComponent() 34 | { 35 | ComponentResourceManager resources = new ComponentResourceManager(typeof(UpdateForm)); 36 | panel1 = new Panel(); 37 | boxDes = new RichTextBox(); 38 | btnCancel = new Button(); 39 | btnUpgrade = new Button(); 40 | lblSize = new Label(); 41 | progressBar = new ProgressBar(); 42 | lblTitle = new Label(); 43 | panel1.SuspendLayout(); 44 | SuspendLayout(); 45 | // 46 | // panel1 47 | // 48 | panel1.Controls.Add(boxDes); 49 | panel1.Controls.Add(btnCancel); 50 | panel1.Controls.Add(btnUpgrade); 51 | panel1.Controls.Add(lblSize); 52 | panel1.Controls.Add(progressBar); 53 | panel1.Controls.Add(lblTitle); 54 | resources.ApplyResources(panel1, "panel1"); 55 | panel1.Name = "panel1"; 56 | // 57 | // boxDes 58 | // 59 | boxDes.BackColor = SystemColors.Window; 60 | boxDes.BorderStyle = BorderStyle.FixedSingle; 61 | resources.ApplyResources(boxDes, "boxDes"); 62 | boxDes.Name = "boxDes"; 63 | boxDes.ReadOnly = true; 64 | // 65 | // btnCancel 66 | // 67 | resources.ApplyResources(btnCancel, "btnCancel"); 68 | btnCancel.Name = "btnCancel"; 69 | btnCancel.UseVisualStyleBackColor = true; 70 | btnCancel.Click += btnCancel_Click; 71 | // 72 | // btnUpgrade 73 | // 74 | resources.ApplyResources(btnUpgrade, "btnUpgrade"); 75 | btnUpgrade.Name = "btnUpgrade"; 76 | btnUpgrade.UseVisualStyleBackColor = true; 77 | btnUpgrade.Click += btnUpgrade_Click; 78 | // 79 | // lblSize 80 | // 81 | resources.ApplyResources(lblSize, "lblSize"); 82 | lblSize.Name = "lblSize"; 83 | // 84 | // progressBar 85 | // 86 | resources.ApplyResources(progressBar, "progressBar"); 87 | progressBar.Name = "progressBar"; 88 | // 89 | // lblTitle 90 | // 91 | resources.ApplyResources(lblTitle, "lblTitle"); 92 | lblTitle.Name = "lblTitle"; 93 | // 94 | // UpdateForm 95 | // 96 | resources.ApplyResources(this, "$this"); 97 | AutoScaleMode = AutoScaleMode.Font; 98 | Controls.Add(panel1); 99 | FormBorderStyle = FormBorderStyle.FixedDialog; 100 | MaximizeBox = false; 101 | MinimizeBox = false; 102 | Name = "UpdateForm"; 103 | FormClosing += FormUpdate_FormClosing; 104 | Load += FormUpdate_Load; 105 | panel1.ResumeLayout(false); 106 | panel1.PerformLayout(); 107 | ResumeLayout(false); 108 | 109 | } 110 | 111 | #endregion 112 | 113 | private System.Windows.Forms.Label lblTitle; 114 | private System.Windows.Forms.ProgressBar progressBar; 115 | private System.Windows.Forms.Button btnUpgrade; 116 | private System.Windows.Forms.Button btnCancel; 117 | private System.Windows.Forms.Label lblSize; 118 | private System.Windows.Forms.RichTextBox boxDes; 119 | private System.Windows.Forms.Panel panel1; 120 | } 121 | } -------------------------------------------------------------------------------- /src/VSIX/Commands/AddManifestFileCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.IO; 4 | using System.Text; 5 | using CnSharp.Updater; 6 | using CnSharp.VisualStudio.Extensions; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace CnSharp.VisualStudio.SharpUpdater.Commands 11 | { 12 | /// 13 | /// Command handler 14 | /// 15 | internal sealed class AddManifestFileCommand 16 | { 17 | /// 18 | /// Command ID. 19 | /// 20 | public const int CommandId = PackageIds.AddManifestFileCommandId; 21 | 22 | /// 23 | /// Command menu group (command set GUID). 24 | /// 25 | public static readonly Guid CommandSet = PackageGuids.guidProjectAddCmdSet; 26 | 27 | /// 28 | /// VS Package that provides this command, not null. 29 | /// 30 | private readonly AsyncPackage package; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Adds our command handlers for menu (commands must exist in the command table file) 35 | /// 36 | /// Owner package, not null. 37 | /// Command service to add command to, not null. 38 | private AddManifestFileCommand(AsyncPackage package, OleMenuCommandService commandService) 39 | { 40 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 41 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 42 | 43 | var menuCommandID = new CommandID(CommandSet, CommandId); 44 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 45 | commandService.AddCommand(menuItem); 46 | } 47 | 48 | /// 49 | /// Gets the instance of the command. 50 | /// 51 | public static AddManifestFileCommand Instance 52 | { 53 | get; 54 | private set; 55 | } 56 | 57 | /// 58 | /// Gets the service provider from the owner package. 59 | /// 60 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider 61 | { 62 | get 63 | { 64 | return this.package; 65 | } 66 | } 67 | 68 | /// 69 | /// Initializes the singleton instance of the command. 70 | /// 71 | /// Owner package, not null. 72 | public static async Task InitializeAsync(AsyncPackage package) 73 | { 74 | // Switch to the main thread - the call to AddCommand in AddIgnoreFileCommand's constructor requires 75 | // the UI thread. 76 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 77 | 78 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 79 | Instance = new AddManifestFileCommand(package, commandService); 80 | } 81 | 82 | /// 83 | /// This function is the callback used to execute the command when the menu item is clicked. 84 | /// See the constructor to see how the menu item is associated with this function using 85 | /// OleMenuCommandService service and MenuCommand class. 86 | /// 87 | /// Event sender. 88 | /// Event args. 89 | private void Execute(object sender, EventArgs e) 90 | { 91 | ThreadHelper.ThrowIfNotOnUIThread(); 92 | 93 | var dte = Host.Instance.Dte2; 94 | var project = dte.GetActiveProject(); 95 | var file = Path.Combine(project.GetDirectory(), Manifest.ManifestFileName); 96 | if (File.Exists(file)) 97 | { 98 | Common.ShowError($"File {Manifest.ManifestFileName} already exists."); 99 | return; 100 | } 101 | 102 | File.WriteAllText(file, Templates.ManifestXml, Encoding.UTF8); 103 | 104 | project.ProjectItems.AddFromFile(file); 105 | dte.ItemOperations.OpenFile(file); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/VSIX/Commands/SharpPackCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Windows.Forms; 4 | using CnSharp.Updater; 5 | using CnSharp.VisualStudio.Extensions; 6 | using CnSharp.VisualStudio.SharpUpdater.Wizard; 7 | using Microsoft.VisualStudio.Shell; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace CnSharp.VisualStudio.SharpUpdater.Commands 11 | { 12 | /// 13 | /// Command handler 14 | /// 15 | internal sealed class SharpPackCommand 16 | { 17 | /// 18 | /// Command ID. 19 | /// 20 | public const int CommandId = PackageIds.SharpPackCommandId; 21 | 22 | /// 23 | /// Command menu group (command set GUID). 24 | /// 25 | public static readonly Guid CommandSet = PackageGuids.guidProjectCmdSet; 26 | 27 | /// 28 | /// VS Package that provides this command, not null. 29 | /// 30 | private readonly AsyncPackage package; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Adds our command handlers for menu (commands must exist in the command table file) 35 | /// 36 | /// Owner package, not null. 37 | /// Command service to add command to, not null. 38 | private SharpPackCommand(AsyncPackage package, OleMenuCommandService commandService) 39 | { 40 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 41 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 42 | 43 | var menuCommandID = new CommandID(CommandSet, CommandId); 44 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 45 | commandService.AddCommand(menuItem); 46 | } 47 | 48 | /// 49 | /// Gets the instance of the command. 50 | /// 51 | public static SharpPackCommand Instance 52 | { 53 | get; 54 | private set; 55 | } 56 | 57 | /// 58 | /// Gets the service provider from the owner package. 59 | /// 60 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider 61 | { 62 | get 63 | { 64 | return this.package; 65 | } 66 | } 67 | 68 | /// 69 | /// Initializes the singleton instance of the command. 70 | /// 71 | /// Owner package, not null. 72 | public static async Task InitializeAsync(AsyncPackage package) 73 | { 74 | // Switch to the main thread - the call to AddCommand in SharpPackCommand's constructor requires 75 | // the UI thread. 76 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 77 | 78 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 79 | Instance = new SharpPackCommand(package, commandService); 80 | } 81 | 82 | /// 83 | /// This function is the callback used to execute the command when the menu item is clicked. 84 | /// See the constructor to see how the menu item is associated with this function using 85 | /// OleMenuCommandService service and MenuCommand class. 86 | /// 87 | /// Event sender. 88 | /// Event args. 89 | private void Execute(object sender, EventArgs e) 90 | { 91 | ThreadHelper.ThrowIfNotOnUIThread(); 92 | 93 | var wizard = new PackingWizard(); 94 | if (wizard.ShowDialog() == DialogResult.OK) 95 | { 96 | Task.Run(() => wizard.PackingAsync().ContinueWith(task => 97 | { 98 | if (task.Exception != null) 99 | { 100 | Host.Instance.Dte2.OutputMessage(Constants.ProductName, 101 | task.Exception.InnerExceptions.Count > 0 ? 102 | task.Exception.InnerExceptions[0].Message : 103 | task.Exception.Message); 104 | } 105 | })); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/VSIX/Wizard/ManifestGrid.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace CnSharp.VisualStudio.SharpUpdater.Wizard 2 | { 3 | partial class ManifestGrid 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.gridFileList = new System.Windows.Forms.DataGridView(); 32 | this.ColSelect = new System.Windows.Forms.DataGridViewCheckBoxColumn(); 33 | this.ColFileName = new System.Windows.Forms.DataGridViewTextBoxColumn(); 34 | this.ColSize = new System.Windows.Forms.DataGridViewTextBoxColumn(); 35 | this.ColFileVersion = new System.Windows.Forms.DataGridViewTextBoxColumn(); 36 | ((System.ComponentModel.ISupportInitialize)(this.gridFileList)).BeginInit(); 37 | this.SuspendLayout(); 38 | // 39 | // gridFileList 40 | // 41 | this.gridFileList.AllowUserToAddRows = false; 42 | this.gridFileList.AllowUserToDeleteRows = false; 43 | this.gridFileList.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; 44 | this.gridFileList.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 45 | this.gridFileList.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 46 | this.ColSelect, 47 | this.ColFileName, 48 | this.ColSize, 49 | this.ColFileVersion}); 50 | this.gridFileList.Dock = System.Windows.Forms.DockStyle.Fill; 51 | this.gridFileList.Location = new System.Drawing.Point(0, 0); 52 | this.gridFileList.Name = "gridFileList"; 53 | this.gridFileList.RowHeadersVisible = false; 54 | this.gridFileList.RowTemplate.Height = 23; 55 | this.gridFileList.Size = new System.Drawing.Size(734, 494); 56 | this.gridFileList.TabIndex = 17; 57 | this.gridFileList.CellClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.gridFileList_CellClick); 58 | // 59 | // ColSelect 60 | // 61 | this.ColSelect.FillWeight = 10F; 62 | this.ColSelect.HeaderText = "Select"; 63 | this.ColSelect.Name = "ColSelect"; 64 | this.ColSelect.Resizable = System.Windows.Forms.DataGridViewTriState.True; 65 | this.ColSelect.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; 66 | // 67 | // ColFileName 68 | // 69 | this.ColFileName.FillWeight = 50F; 70 | this.ColFileName.HeaderText = "Name"; 71 | this.ColFileName.Name = "ColFileName"; 72 | this.ColFileName.ReadOnly = true; 73 | // 74 | // ColSize 75 | // 76 | this.ColSize.FillWeight = 20F; 77 | this.ColSize.HeaderText = "File Size(K)"; 78 | this.ColSize.Name = "ColSize"; 79 | this.ColSize.ReadOnly = true; 80 | // 81 | // ColFileVersion 82 | // 83 | this.ColFileVersion.FillWeight = 20F; 84 | this.ColFileVersion.HeaderText = "Version"; 85 | this.ColFileVersion.Name = "ColFileVersion"; 86 | // 87 | // ManifestGrid 88 | // 89 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 90 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 91 | this.Controls.Add(this.gridFileList); 92 | this.Name = "ManifestGrid"; 93 | this.Size = new System.Drawing.Size(734, 494); 94 | ((System.ComponentModel.ISupportInitialize)(this.gridFileList)).EndInit(); 95 | this.ResumeLayout(false); 96 | 97 | } 98 | 99 | #endregion 100 | 101 | private System.Windows.Forms.DataGridView gridFileList; 102 | private System.Windows.Forms.DataGridViewCheckBoxColumn ColSelect; 103 | private System.Windows.Forms.DataGridViewTextBoxColumn ColFileName; 104 | private System.Windows.Forms.DataGridViewTextBoxColumn ColSize; 105 | private System.Windows.Forms.DataGridViewTextBoxColumn ColFileVersion; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | *.pubxml 143 | 144 | # TODO: Un-comment the next line if you do not want to checkin 145 | # your web deploy settings because they may include unencrypted 146 | # passwords 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # Uncomment if necessary however generally it will be regenerated when needed 157 | #!**/packages/repositories.config 158 | # NuGet v3's project.json files produces more ignoreable files 159 | *.nuget.props 160 | *.nuget.targets 161 | 162 | # Microsoft Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Microsoft Azure Emulator 167 | ecf/ 168 | rcf/ 169 | 170 | # Microsoft Azure ApplicationInsights config file 171 | ApplicationInsights.config 172 | 173 | # Windows Store app package directory 174 | AppPackages/ 175 | BundleArtifacts/ 176 | 177 | # Visual Studio cache files 178 | # files ending in .cache can be ignored 179 | *.[Cc]ache 180 | # but keep track of directories ending in .cache 181 | !*.[Cc]ache/ 182 | 183 | # Others 184 | ClientBin/ 185 | [Ss]tyle[Cc]op.* 186 | ~$* 187 | *~ 188 | *.dbmdl 189 | *.dbproj.schemaview 190 | *.pfx 191 | *.publishsettings 192 | node_modules/ 193 | orleans.codegen.cs 194 | 195 | # RIA/Silverlight projects 196 | Generated_Code/ 197 | 198 | # Backup & report files from converting an old project file 199 | # to a newer Visual Studio version. Backup files are not needed, 200 | # because we have git ;-) 201 | _UpgradeReport_Files/ 202 | Backup*/ 203 | UpgradeLog*.XML 204 | UpgradeLog*.htm 205 | 206 | # SQL Server files 207 | *.mdf 208 | *.ldf 209 | 210 | # Business Intelligence projects 211 | *.rdl.data 212 | *.bim.layout 213 | *.bim_*.settings 214 | 215 | # Microsoft Fakes 216 | FakesAssemblies/ 217 | 218 | # GhostDoc plugin setting file 219 | *.GhostDoc.xml 220 | 221 | # Node.js Tools for Visual Studio 222 | .ntvs_analysis.dat 223 | 224 | # Visual Studio 6 build log 225 | *.plg 226 | 227 | # Visual Studio 6 workspace options file 228 | *.opt 229 | 230 | # Visual Studio LightSwitch build output 231 | **/*.HTMLClient/GeneratedArtifacts 232 | **/*.DesktopClient/GeneratedArtifacts 233 | **/*.DesktopClient/ModelManifest.xml 234 | **/*.Server/GeneratedArtifacts 235 | **/*.Server/ModelManifest.xml 236 | _Pvt_Extensions 237 | 238 | # LightSwitch generated files 239 | GeneratedArtifacts/ 240 | ModelManifest.xml 241 | 242 | # Paket dependency manager 243 | .paket/paket.exe 244 | 245 | # FAKE - F# Make 246 | .fake/ 247 | 248 | # keys 249 | *.snk 250 | 251 | # CnSharp Tools 252 | .nupack/ 253 | .[Ss]harp[Uu]pdater/ -------------------------------------------------------------------------------- /src/CLI/Program.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.Updater.CLI.CommandHandlers; 2 | using System.CommandLine; 3 | 4 | namespace CnSharp.Updater.CLI 5 | { 6 | internal class Program 7 | { 8 | static async Task Main(string[] args) 9 | { 10 | var rootCommand = new RootCommand($"This is a packaging & deployment tool of {Constants.ProductName}."); 11 | rootCommand.AddCommand(NewSetGlobalSourceCommand()); 12 | rootCommand.AddCommand(NewRemoveGlobalSourceCommand()); 13 | rootCommand.AddCommand(NewInitManifestFileCommand()); 14 | rootCommand.AddCommand(NewInitIgnoreFileCommand()); 15 | rootCommand.AddCommand(NewPackCommand()); 16 | rootCommand.AddCommand(NewPushCommand()); 17 | return await rootCommand.InvokeAsync(args); 18 | } 19 | 20 | private static Command NewInitManifestFileCommand() 21 | { 22 | return new Command("init", $"Generate a {Constants.ManifestFileName} file in the current directory") 23 | { 24 | Handler = new InitManifestFileCommandHandler() 25 | }; 26 | } 27 | 28 | private static Command NewInitIgnoreFileCommand() 29 | { 30 | return new Command("ignore", $"Generate a {Constants.IgnoreFileName} file in the current directory") 31 | { 32 | Handler = new InitIgnoreFileCommandHandler() 33 | }; 34 | } 35 | 36 | private static Command NewSetGlobalSourceCommand() 37 | { 38 | var sourceOption = new Option("--source", "Set the global SharpUpdater.Server source URL") 39 | { 40 | IsRequired = true 41 | }; 42 | sourceOption.AddAlias("-s"); 43 | 44 | var command = new Command("global", "Set the global settings") 45 | { 46 | sourceOption 47 | }; 48 | command.SetHandler(GlobalSourceCommandHandler.Set, sourceOption); 49 | return command; 50 | } 51 | 52 | private static Command NewRemoveGlobalSourceCommand() 53 | { 54 | var command = new Command("RemoveSource", "Remove the global source"); 55 | command.SetHandler(GlobalSourceCommandHandler.Remove); 56 | return command; 57 | } 58 | 59 | private static Command NewPackCommand() 60 | { 61 | var sourceOption = new Option("--source", "Specify the SharpUpdater.Server source URL"); 62 | sourceOption.AddAlias("-s"); 63 | 64 | var projectDirOption = new Option("--project", "Specify the project directory"); 65 | projectDirOption.AddAlias("-p"); 66 | 67 | var outputDirOption = new Option("--output", () => $"bin\\{Constants.ProductName}\\", "Specify the output directory"); 68 | outputDirOption.AddAlias("-o"); 69 | 70 | var versionOption = new Option("--version", "Specify the package version"); 71 | versionOption.AddAlias("-v"); 72 | 73 | var minVersionOption = new Option("--MinimumVersion", "Specify the minimum version must be updated"); 74 | minVersionOption.AddAlias("-mv"); 75 | 76 | var releaseNotesOption = new Option("--ReleaseNotes", "Input release notes"); 77 | releaseNotesOption.AddAlias("-rn"); 78 | 79 | var noBuildOption = new Option("--no-build", "Skip build"); 80 | 81 | var command = new Command("pack", "Pack the project") 82 | { 83 | sourceOption, 84 | projectDirOption, 85 | outputDirOption, 86 | versionOption, 87 | minVersionOption, 88 | releaseNotesOption, 89 | noBuildOption 90 | }; 91 | command.SetHandler(PackCommandHandler.Invoke, 92 | sourceOption, 93 | projectDirOption, 94 | outputDirOption, 95 | versionOption, 96 | minVersionOption, 97 | releaseNotesOption, 98 | noBuildOption); 99 | return command; 100 | } 101 | 102 | private static Command NewPushCommand() 103 | { 104 | var packageOption = new Option("--package", "Specify the .sp file path") 105 | { 106 | IsRequired = true 107 | }; 108 | packageOption.AddAlias("-p"); 109 | 110 | var sourceOption = new Option("--source", "Specify the SharpUpdater.Server source URL"); 111 | sourceOption.AddAlias("-s"); 112 | 113 | var keyOption = new Option("--apikey", "Specify the ApiKey of SharpUpdater.Server") 114 | { 115 | IsRequired = true 116 | }; 117 | keyOption.AddAlias("-k"); 118 | 119 | var pushCommand = new Command("push", "Push .sp package to SharpUpdater.Server") 120 | { 121 | packageOption, 122 | sourceOption, 123 | keyOption 124 | }; 125 | pushCommand.SetHandler(PushCommandHandler.Invoke, 126 | packageOption, 127 | sourceOption, 128 | keyOption); 129 | return pushCommand; 130 | } 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /src/Core/Util/FileUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using System.Xml; 8 | 9 | namespace CnSharp.Updater.Util 10 | { 11 | public static class FileUtil 12 | { 13 | 14 | public static Manifest ReadManifest(string fileName) 15 | { 16 | return XmlSerializerHelper.LoadObjectFromXml(fileName); 17 | } 18 | 19 | public static void Merge(this Manifest manifest, string fileName) 20 | { 21 | if (!File.Exists(fileName)) 22 | { 23 | throw new FileNotFoundException($"{fileName} not found."); 24 | } 25 | 26 | var current = XmlSerializerHelper.LoadObjectFromXml(fileName); 27 | var props = typeof(Manifest).GetProperties() 28 | .Where(p => p.CanRead && p.CanWrite && p.GetType() != typeof(XmlNode)) 29 | .ToList(); 30 | foreach (var prop in props) 31 | { 32 | var currentValue = prop.GetValue(current)?.ToString(); 33 | var newValue = prop.GetValue(manifest); 34 | if (currentValue != null && currentValue.StartsWith("$") && currentValue.EndsWith("$")) 35 | continue; 36 | if (newValue != null) 37 | { 38 | prop.SetValue(current, newValue); 39 | } 40 | } 41 | XmlSerializerHelper.SerializeToXmlFile(current, fileName); 42 | } 43 | 44 | public static void Save(this Manifest manifest, string fileName) 45 | { 46 | XmlSerializerHelper.SerializeToXmlFile(manifest, fileName); 47 | } 48 | 49 | 50 | public static void CopyFiles(string sourceDirectory, string targetDirectory) 51 | { 52 | if (!Directory.Exists(sourceDirectory)) return; 53 | if (!Directory.Exists(targetDirectory)) 54 | Directory.CreateDirectory(targetDirectory); 55 | 56 | string[] directories = Directory.GetDirectories(sourceDirectory); 57 | if (directories.Length > 0) 58 | { 59 | foreach (string d in directories) 60 | { 61 | CopyFiles(d, targetDirectory + d.Substring(d.LastIndexOf("\\"))); 62 | } 63 | } 64 | 65 | string[] files = Directory.GetFiles(sourceDirectory); 66 | if (files.Length > 0) 67 | { 68 | foreach (string s in files) 69 | { 70 | File.Copy(s, targetDirectory + s.Substring(s.LastIndexOf("\\")), true); 71 | } 72 | } 73 | } 74 | 75 | public static void DeleteFolder(string path) 76 | { 77 | DeleteDirectory(new DirectoryInfo(path)); 78 | } 79 | 80 | public static void DeleteDirectory(DirectoryInfo dir) 81 | { 82 | if (dir.Exists) 83 | { 84 | DirectoryInfo[] childs = dir.GetDirectories(); 85 | foreach (DirectoryInfo child in childs) 86 | { 87 | DeleteDirectory(child); 88 | } 89 | dir.Delete(true); 90 | } 91 | } 92 | 93 | /// 94 | /// 加密 95 | /// 96 | /// 97 | /// 98 | public static string MD5(this string input) 99 | { 100 | var md5 = new MD5CryptoServiceProvider(); 101 | 102 | byte[] inBytes = Encoding.Default.GetBytes(input); 103 | 104 | byte[] outBytes = md5.ComputeHash(inBytes); 105 | 106 | var output = new StringBuilder(); 107 | 108 | for (int i = 0; i < outBytes.Length; i++) 109 | { 110 | output.Append(outBytes[i].ToString("x2")); 111 | } 112 | 113 | return output.ToString(); 114 | } 115 | 116 | public static string FormatFileSizeDescription(long bytes) 117 | { 118 | if (bytes > 1024 * 1024) 119 | return string.Format("{0}M", Math.Round((double)bytes / (1024 * 1024), 2, MidpointRounding.AwayFromZero)); 120 | if (bytes > 1024) 121 | return string.Format("{0}K", Math.Round((double)bytes / 1024, 2, MidpointRounding.AwayFromZero)); 122 | return string.Format("{0}B", bytes); 123 | } 124 | 125 | 126 | public static long CopyTo(this Stream source, Stream target) 127 | { 128 | const int bufSize = 0x1000; 129 | 130 | var buf = new byte[bufSize]; 131 | 132 | long totalBytes = 0; 133 | 134 | var bytesRead = 0; 135 | 136 | while ((bytesRead = source.Read(buf, 0, bufSize)) > 0) 137 | { 138 | target.Write(buf, 0, bytesRead); 139 | totalBytes += bytesRead; 140 | } 141 | 142 | return totalBytes; 143 | } 144 | 145 | public static string JoinFilePath(string baseDir, string targetDir) 146 | { 147 | var dir = targetDir.Trim().Replace('/', Path.DirectorySeparatorChar); 148 | if (!dir.Contains(":" + Path.DirectorySeparatorChar)) 149 | { 150 | dir = Path.Combine(baseDir, dir); 151 | } 152 | return dir; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/CLI/CommandHandlers/PackCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using CnSharp.Updater.Packaging; 3 | using CnSharp.Updater.Util; 4 | using NuGet.Versioning; 5 | 6 | namespace CnSharp.Updater.CLI.CommandHandlers; 7 | 8 | internal class PackCommandHandler 9 | { 10 | public static async Task Invoke(string source, string? project, string output, string? version, string? minimumVersion, string releaseNotes, bool noBuild) 11 | { 12 | SemanticVersion? v = null; 13 | if (!string.IsNullOrWhiteSpace(version) && !SemanticVersion.TryParse(version, out v)) 14 | { 15 | ConsoleExtensions.WriteError("Version number is not semantic."); 16 | return; 17 | } 18 | 19 | SemanticVersion? mv = null; 20 | if (!string.IsNullOrWhiteSpace(minimumVersion) && !SemanticVersion.TryParse(minimumVersion, out mv)) 21 | { 22 | ConsoleExtensions.WriteError("Minimum version number is not semantic."); 23 | return; 24 | } 25 | var dir = project ?? Directory.GetCurrentDirectory(); 26 | string projectFile; 27 | if (ProjectHolder.CheckExistsIfProject(dir)) 28 | { 29 | projectFile = dir; 30 | dir = Path.GetDirectoryName(dir); 31 | } 32 | else 33 | { 34 | projectFile = ProjectHolder.GetProjectFile(dir); 35 | if (projectFile == null) 36 | { 37 | ConsoleExtensions.WriteError("Project file not found."); 38 | return; 39 | } 40 | } 41 | 42 | var manifest = GetManifest(dir); 43 | var globalSource = Settings.Load()?.GlobalSource; 44 | var currentSource = !string.IsNullOrWhiteSpace(source) ? source : globalSource; 45 | if (string.IsNullOrWhiteSpace(manifest.ReleaseUrl) && string.IsNullOrWhiteSpace(currentSource)) 46 | { 47 | ConsoleExtensions.WriteError("--source is required."); 48 | return; 49 | } 50 | 51 | var projectHelper = ProjectHolder.GetProjectHelper(projectFile); 52 | if (!noBuild) 53 | { 54 | Console.WriteLine("Building..."); 55 | if (!Build(projectHelper)) 56 | { 57 | return; 58 | } 59 | } 60 | 61 | var releaseDir = projectHelper.GetReleaseDir(); 62 | var exeFile = projectHelper.GetExeFileReleased(); 63 | UpdateManifest(manifest, exeFile, releaseDir, dir, currentSource, v, mv, releaseNotes); 64 | var outputPath = output; 65 | if (string.IsNullOrWhiteSpace(outputPath)) 66 | outputPath = $"bin\\{Constants.ProductName}"; 67 | var outputDir = EnsureOutputDir(dir, outputPath); 68 | var zipName = $"{manifest.AppName}_{manifest.Version}{Manifest.PackageFileExt}"; 69 | var zipPath = $"{outputDir}\\{zipName}"; 70 | var pb = new PackageBuilder(manifest, releaseDir); 71 | pb.CreatePackage(zipPath); 72 | Console.WriteLine($"Package file generated in {zipPath}."); 73 | } 74 | 75 | protected static string EnsureOutputDir(string baseDir, string outputDir) 76 | { 77 | var dir = outputDir.Trim().Replace('/', Path.DirectorySeparatorChar); 78 | dir = FileUtil.JoinFilePath(baseDir, dir); 79 | if (!Directory.Exists(dir)) 80 | Directory.CreateDirectory(dir); 81 | return dir; 82 | } 83 | 84 | private static Manifest GetManifest(string projectDir) 85 | { 86 | var manifestFilePath = Path.Combine(projectDir, Constants.ManifestFileName); 87 | return File.Exists(manifestFilePath) ? XmlSerializerHelper.LoadObjectFromXml(manifestFilePath) : new Manifest(); 88 | } 89 | 90 | private static void UpdateManifest(Manifest manifest, string exeFile, string releaseDir, string projectDir, string? source, SemanticVersion? version, SemanticVersion? minVersion, string releaseNotes) 91 | { 92 | var fileVersionInfo = FileVersionInfo.GetVersionInfo(exeFile); 93 | var fileVersion = fileVersionInfo.FileVersion ?? fileVersionInfo.ProductVersion; 94 | var packageVersion = version?.ToString() ?? fileVersion.ToSemanticVersion(); 95 | var fileName = Path.GetFileNameWithoutExtension(fileVersionInfo.FileName); 96 | manifest.SetIdIfBlank(fileName); 97 | manifest.SetAppNameIfBlank(!string.IsNullOrWhiteSpace(fileVersionInfo.ProductName) ? fileVersionInfo.ProductName : fileName); 98 | manifest.SetCopyrightIfBlank(fileVersionInfo.LegalCopyright); 99 | manifest.SetDescriptionIfBlank(fileVersionInfo.FileDescription); 100 | manifest.SetEntryPointIfBlank(Path.GetFileName(exeFile)); 101 | manifest.SetIdIfBlank(fileVersionInfo.CompanyName); 102 | manifest.SetVersionIfBlank(packageVersion); 103 | if (!string.IsNullOrWhiteSpace(source)) 104 | manifest.ReleaseUrl = source; 105 | if (!string.IsNullOrWhiteSpace(releaseNotes)) 106 | manifest.ReleaseNotes = releaseNotes; 107 | manifest.MinVersion = minVersion?.ToString() ?? manifest.Version; 108 | manifest.Files = new ManifestGatherer(releaseDir, projectDir).GatherFiles(true); 109 | var manifestFilePath = Path.Combine(releaseDir, Manifest.ManifestFileName); 110 | manifest.Save(manifestFilePath); 111 | } 112 | 113 | private static bool Build(ProjectHelper projectHelper) 114 | { 115 | if (projectHelper.IsDotnetFramework) 116 | return CmdHelper.Run("msbuild", $"\"{projectHelper.ProjectFile}\" /p:Configuration=Release", Console.WriteLine, Console.WriteLine); 117 | return CmdHelper.Run("dotnet", $" build \"{projectHelper.ProjectFile}\" --configuration Release", Console.WriteLine, Console.WriteLine); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/VSIX/Wizard/ProductInfoControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Windows.Forms; 4 | using CnSharp.Updater; 5 | using CnSharp.Updater.Util; 6 | using CnSharp.VisualStudio.Extensions.Projects; 7 | 8 | namespace CnSharp.VisualStudio.SharpUpdater.Wizard 9 | { 10 | public partial class ProductInfoControl : UserControl 11 | { 12 | public ProductInfoControl() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | private Manifest _manifest; 18 | 19 | public Manifest Manifest 20 | { 21 | set 22 | { 23 | _manifest = value; 24 | if(_manifest != null) 25 | BindManifest(); 26 | } 27 | get 28 | { 29 | if(_manifest != null) 30 | SyncManifest(); 31 | return _manifest; 32 | } 33 | } 34 | 35 | private ProjectAssemblyInfo _projectAssemblyInfo; 36 | public ProjectAssemblyInfo ProjectAssemblyInfo 37 | { 38 | set 39 | { 40 | _projectAssemblyInfo = value; 41 | if (_projectAssemblyInfo != null) 42 | { 43 | var ver = _projectAssemblyInfo.Version.ToSemanticVersion(); 44 | txtReleaseVer.Text = ver; 45 | txtMinVer.Text = ver; 46 | } 47 | else 48 | { 49 | txtReleaseVer.Clear(); 50 | txtMinVer.Clear(); 51 | } 52 | } 53 | } 54 | 55 | 56 | private void SyncManifest() 57 | { 58 | _manifest.Id = txtId.Text.Trim(); 59 | _manifest.AppName = txtAppName.Text.Trim(); 60 | _manifest.Owner = txtOwner.Text.Trim(); 61 | _manifest.WebSite = txtWebsite.Text.Trim(); 62 | _manifest.Version = txtReleaseVer.Text.Trim(); 63 | _manifest.MinVersion = txtMinVer.Text.Trim(); 64 | _manifest.ReleaseNotes = txtNote.Text; 65 | _manifest.Language = txtLang.Text.Trim(); 66 | _manifest.Copyright = txtCopyright.Text.Trim(); 67 | _manifest.Tags = txtTags.Text.Trim(); 68 | } 69 | 70 | 71 | private void BindManifest() 72 | { 73 | txtId.Text = _manifest.Id; 74 | txtAppName.Text = _manifest.AppName; 75 | txtOwner.Text = _manifest.Owner; 76 | txtWebsite.Text = _manifest.WebSite; 77 | txtReleaseVer.Text = _manifest.Version; 78 | txtMinVer.Text = _manifest.MinVersion; 79 | var desc = _manifest.ReleaseNotes; 80 | if (!string.IsNullOrEmpty(desc)) 81 | { 82 | desc = desc.Replace("\n", Environment.NewLine); 83 | } 84 | txtNote.Text = desc; 85 | txtLang.Text = _manifest.Language; 86 | txtCopyright.Text = _manifest.Copyright; 87 | txtTags.Text = _manifest.Tags; 88 | } 89 | 90 | private void AddTextBoxEvents() 91 | { 92 | txtId.Validating += TextBoxRequiredValidating; 93 | txtId.Validated += TextBoxRequiredValidated; 94 | txtAppName.Validating += TextBoxRequiredValidating; 95 | txtAppName.Validated += TextBoxRequiredValidated; 96 | txtOwner.Validating += TextBoxRequiredValidating; 97 | txtOwner.Validated += TextBoxRequiredValidated; 98 | txtNote.Validating += TextBoxRequiredValidating; 99 | txtNote.Validated += TextBoxRequiredValidated; 100 | 101 | txtReleaseVer.Validating += TextBoxRequiredValidating; 102 | txtReleaseVer.Validating += VersionTextBoxValidating; 103 | txtReleaseVer.Validated += TextBoxRequiredValidated; 104 | 105 | txtMinVer.Validating += VersionTextBoxValidating; 106 | txtMinVer.Validating += TextBoxRequiredValidating; 107 | txtMinVer.Validated += TextBoxRequiredValidated; 108 | } 109 | 110 | private void VersionTextBoxValidating(object sender, CancelEventArgs e) 111 | { 112 | var box = sender as TextBox; 113 | if (box == null) 114 | return; 115 | if (!string.IsNullOrWhiteSpace(box.Text) && !box.Text.IsSemanticVersion()) 116 | { 117 | errorProvider.SetError(box, "invalid version number"); 118 | e.Cancel = true; 119 | return; 120 | } 121 | if (!string.IsNullOrWhiteSpace(txtReleaseVer.Text) && !string.IsNullOrWhiteSpace(txtMinVer.Text) && 122 | txtMinVer.Text.CompareVersion(txtReleaseVer.Text) > 0) 123 | { 124 | errorProvider.SetError(box, "Minimum Version > Release Version ?"); 125 | e.Cancel = true; 126 | } 127 | } 128 | 129 | private void TextBoxRequiredValidating(object sender, CancelEventArgs e) 130 | { 131 | var box = sender as TextBox; 132 | if (box == null) 133 | return; 134 | if (string.IsNullOrWhiteSpace(box.Text)) 135 | { 136 | errorProvider.SetError(box, "*"); 137 | e.Cancel = true; 138 | } 139 | } 140 | 141 | 142 | private void TextBoxRequiredValidated(object sender, EventArgs e) 143 | { 144 | var box = sender as TextBox; 145 | if (box == null) 146 | return; 147 | errorProvider.SetError(box, null); 148 | } 149 | 150 | private void ProductInfoForm_Load(object sender, EventArgs e) 151 | { 152 | ActiveControl = txtAppName; 153 | AddTextBoxEvents(); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet4/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/Clients/WinForms-DotNet8/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/VSIX/Wizard/ProductInfoControl.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /src/VSIX/Resource.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | Resources\folder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /src/VSIX/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Windows.Forms; 7 | using CnSharp.Updater.Util; 8 | using CnSharp.VisualStudio.Extensions; 9 | using EnvDTE; 10 | using Constants = CnSharp.Updater.Constants; 11 | 12 | namespace CnSharp.VisualStudio.SharpUpdater 13 | { 14 | public class Common 15 | { 16 | public static readonly string IgnoreFileName = $"{Constants.ProductName}.ignore"; 17 | public static readonly string[] SupportedProjectTypes = { ".csproj", ".vbproj", ".fsproj" }; 18 | public static readonly string[] SupportedFileTypes = { ".exe"}; 19 | 20 | public static bool Contains(Enum keys, Enum flag) 21 | { 22 | ulong keysVal = Convert.ToUInt64(keys); 23 | ulong flagVal = Convert.ToUInt64(flag); 24 | 25 | return (keysVal & flagVal) == flagVal; 26 | } 27 | 28 | public static DialogResult ShowError(string message) 29 | { 30 | return MessageBox.Show(message, Constants.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); 31 | } 32 | 33 | public static DialogResult ShowWarning(string message) 34 | { 35 | return MessageBox.Show(message, Constants.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning); 36 | } 37 | } 38 | 39 | public class ProductInfo 40 | { 41 | public string Name { get; set; } 42 | 43 | public string CompanyName { get; set; } 44 | 45 | public string Version { get; set; } 46 | } 47 | 48 | public class Paths 49 | { 50 | public static string AddinRoot = 51 | Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", ""); 52 | 53 | public static string GetSettingsFileLocation(Project project) 54 | { 55 | var dir = Path.GetDirectoryName(Host.Instance.DTE.Solution.FileName); 56 | dir = Path.Combine(dir, $".{Constants.ProductName}"); 57 | 58 | if (!Directory.Exists(dir)) 59 | { 60 | DirectoryInfo di = Directory.CreateDirectory(dir); 61 | di.Attributes = FileAttributes.Directory | FileAttributes.Hidden; 62 | } 63 | 64 | return Path.Combine(dir, "settings.xml"); 65 | } 66 | } 67 | 68 | public class NuPackSettings 69 | { 70 | public NuPackSettings() 71 | { 72 | UnselectedFiles = new List(); 73 | UnselectedFolders = new List(); 74 | } 75 | public List UnselectedFolders { get; set; } 76 | public List UnselectedFiles { get; set; } 77 | 78 | public string PackageOutputDirectory { get; set; } = "bin\\{version}\\"; 79 | public bool OpenPackageOutputDirectoryAfterBuild { get; set; } 80 | public string DeployServer { get; set; } 81 | public string DeployKey { get; set; } 82 | 83 | } 84 | 85 | class Validation 86 | { 87 | public static bool HasValidationErrors(System.Windows.Forms.Control.ControlCollection controls) 88 | { 89 | bool hasError = false; 90 | 91 | // Now we need to loop through the controls and deterime if any of them have errors 92 | foreach (Control control in controls) 93 | { 94 | // check the control and see what it returns 95 | bool validControl = IsValid(control); 96 | // If it's not valid then set the flag and keep going. We want to get through all 97 | // the validators so they will display on the screen if errorProviders were used. 98 | if (!validControl) 99 | hasError = true; 100 | 101 | // If its a container control then it may have children that need to be checked 102 | if (control.HasChildren) 103 | { 104 | if (HasValidationErrors(control.Controls)) 105 | hasError = true; 106 | } 107 | } 108 | return hasError; 109 | } 110 | 111 | // Here, let's determine if the control has a validating method attached to it 112 | // and if it does, let's execute it and return the result 113 | private static bool IsValid(object eventSource) 114 | { 115 | string name = "EventValidating"; 116 | 117 | Type targetType = eventSource.GetType(); 118 | 119 | do 120 | { 121 | FieldInfo[] fields = targetType.GetFields( 122 | BindingFlags.Static | 123 | BindingFlags.Instance | 124 | BindingFlags.NonPublic); 125 | 126 | foreach (FieldInfo field in fields) 127 | { 128 | if (field.Name == name) 129 | { 130 | EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events", 131 | (BindingFlags.FlattenHierarchy | 132 | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null))); 133 | 134 | Delegate d = eventHandlers[field.GetValue(eventSource)]; 135 | 136 | if (d != null) 137 | { 138 | Delegate[] subscribers = d.GetInvocationList(); 139 | 140 | // ok we found the validation event, let's get the event method and call it 141 | foreach (Delegate d1 in subscribers) 142 | { 143 | // create the parameters 144 | object sender = eventSource; 145 | CancelEventArgs eventArgs = new CancelEventArgs(); 146 | eventArgs.Cancel = false; 147 | object[] parameters = new object[2]; 148 | parameters[0] = sender; 149 | parameters[1] = eventArgs; 150 | // call the method 151 | d1.DynamicInvoke(parameters); 152 | // if the validation failed we need to return that failure 153 | return !eventArgs.Cancel; 154 | } 155 | } 156 | } 157 | } 158 | 159 | targetType = targetType.BaseType; 160 | 161 | } while (targetType != null); 162 | 163 | return true; 164 | } 165 | 166 | } 167 | } -------------------------------------------------------------------------------- /src/VSIX/Wizard/ManifestGrid.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | True 122 | 123 | 124 | True 125 | 126 | 127 | True 128 | 129 | 130 | True 131 | 132 | -------------------------------------------------------------------------------- /src/VSIX/SharpUpdaterPackage.cs: -------------------------------------------------------------------------------- 1 | using CnSharp.VisualStudio.Extensions; 2 | using CnSharp.VisualStudio.SharpUpdater.Util; 3 | using EnvDTE; 4 | using EnvDTE80; 5 | using Microsoft.VisualStudio; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.Shell.Interop; 8 | using System; 9 | using System.Diagnostics; 10 | using System.Globalization; 11 | using System.Linq; 12 | using System.Reflection; 13 | using System.Runtime.InteropServices; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using CnSharp.VisualStudio.SharpUpdater.Commands; 17 | using Task = System.Threading.Tasks.Task; 18 | 19 | namespace CnSharp.VisualStudio.SharpUpdater 20 | { 21 | /// 22 | /// This is the class that implements the package exposed by this assembly. 23 | /// 24 | /// 25 | /// 26 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 27 | /// is to implement the IVsPackage interface and register itself with the shell. 28 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 29 | /// to do it: it derives from the Package class that provides the implementation of the 30 | /// IVsPackage interface and uses the registration attributes defined in the framework to 31 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 32 | /// utility what data to put into .pkgdef file. 33 | /// 34 | /// 35 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 36 | /// 37 | /// 38 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 39 | [Guid(SharpUpdaterPackage.PackageGuidString)] 40 | [ProvideMenuResource("Menus.ctmenu", 1)] 41 | public sealed class SharpUpdaterPackage : AsyncPackage 42 | { 43 | /// 44 | /// SharpUpdaterPackage GUID string. 45 | /// 46 | public const string PackageGuidString = PackageGuids.guidSharpUpdaterPackageString; 47 | 48 | public SharpUpdaterPackage() 49 | { 50 | RedirectAssembly("System.IO.Packaging", new Version("9.0.0.2"), "b03f5f7f11d50a3a"); 51 | } 52 | 53 | #region Package Members 54 | 55 | /// 56 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 57 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 58 | /// 59 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 60 | /// A provider for progress updates. 61 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 62 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 63 | { 64 | // When initialized asynchronously, the current thread may be a background thread at this point. 65 | // Do any initialization that requires the UI thread after switching to the UI thread. 66 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 67 | 68 | var dte = GetGlobalService(typeof(DTE)) as DTE2; 69 | Host.Instance.DTE = dte; 70 | 71 | bool isSolutionLoaded = await IsSolutionLoadedAsync(); 72 | 73 | if (isSolutionLoaded) 74 | { 75 | HandleOpenSolution(); 76 | } 77 | 78 | // Listen for subsequent solution events 79 | Microsoft.VisualStudio.Shell.Events.SolutionEvents.OnAfterOpenSolution += HandleOpenSolution; 80 | 81 | 82 | dte.Events.SolutionEvents.ProjectAdded += p => 83 | { 84 | if (string.IsNullOrWhiteSpace(p.FileName) || 85 | !Common.SupportedProjectTypes.Any(t => p.FileName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) 86 | return; 87 | var sln = Host.Instance.Solution2; 88 | SolutionDataCache.Instance.TryGetValue(sln.FileName, out var sp); 89 | sp?.AddProject(p); 90 | }; 91 | dte.Events.SolutionEvents.ProjectRemoved += p => 92 | { 93 | var sln = Host.Instance.Solution2; 94 | SolutionDataCache.Instance.TryGetValue(sln.FileName, out var sp); 95 | sp?.RemoveProject(p); 96 | }; 97 | 98 | 99 | 100 | await AddIgnoreFileCommand.InitializeAsync(this); 101 | await AddManifestFileCommand.InitializeAsync(this); 102 | await SharpPackCommand.InitializeAsync(this); 103 | 104 | } 105 | 106 | private async Task IsSolutionLoadedAsync() 107 | { 108 | await JoinableTaskFactory.SwitchToMainThreadAsync(); 109 | var solService = await GetServiceAsync(typeof(SVsSolution)) as IVsSolution; 110 | 111 | ErrorHandler.ThrowOnFailure(solService.GetProperty((int)__VSPROPID.VSPROPID_IsSolutionOpen, out object value)); 112 | 113 | return value is bool isSolOpen && isSolOpen; 114 | } 115 | 116 | private void HandleOpenSolution(object sender = null, EventArgs e = null) 117 | { 118 | var sln = Host.Instance.Solution2; 119 | var projects = Host.Instance.DTE.GetSolutionProjects() 120 | .Where( 121 | p => 122 | !string.IsNullOrWhiteSpace(p.FileName) && 123 | Common.SupportedProjectTypes.Any( 124 | t => p.FileName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) 125 | .ToList(); 126 | var sp = new SolutionProperties 127 | { 128 | Projects = projects 129 | }; 130 | SolutionDataCache.Instance.AddOrUpdate(sln.FileName, sp, (k, v) => 131 | { 132 | v = sp; 133 | return v; 134 | }); 135 | } 136 | 137 | 138 | #endregion 139 | 140 | public static void RedirectAssembly(string shortName, Version targetVersion, string publicKeyToken) 141 | { 142 | ResolveEventHandler handler = null; 143 | 144 | handler = (sender, args) => { 145 | // Use latest strong name & version when trying to load SDK assemblies 146 | var requestedAssembly = new AssemblyName(args.Name); 147 | if (requestedAssembly.Name != shortName) 148 | return null; 149 | 150 | Debug.WriteLine("Redirecting assembly load of " + args.Name 151 | + ",\tloaded by " + (args.RequestingAssembly == null ? "(unknown)" : args.RequestingAssembly.FullName)); 152 | 153 | requestedAssembly.Version = targetVersion; 154 | requestedAssembly.SetPublicKeyToken(new AssemblyName(shortName + ", PublicKeyToken=" + publicKeyToken).GetPublicKeyToken()); 155 | requestedAssembly.CultureInfo = CultureInfo.InvariantCulture; 156 | 157 | AppDomain.CurrentDomain.AssemblyResolve -= handler; 158 | 159 | return Assembly.Load(requestedAssembly); 160 | }; 161 | AppDomain.CurrentDomain.AssemblyResolve += handler; 162 | } 163 | } 164 | } 165 | --------------------------------------------------------------------------------