diff --git a/BuildMonitor/BuildMonitor.csproj b/BuildMonitor/BuildMonitor.csproj index 9cdd5bf..3697980 100644 --- a/BuildMonitor/BuildMonitor.csproj +++ b/BuildMonitor/BuildMonitor.csproj @@ -62,7 +62,6 @@ - diff --git a/BuildMonitor/IntExtensions.cs b/BuildMonitor/IntExtensions.cs deleted file mode 100644 index 9d17ba9..0000000 --- a/BuildMonitor/IntExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Linq; -using System.Collections.Generic; - -namespace BuildMonitor -{ - public static class IntExtensions - { - public static string Times(this int i, string s) - { - return string.Join("", Enumerable.Range(0, i).Select(d => s)); - } - } -} \ No newline at end of file diff --git a/BuildMonitorPackage/AnalyseBuildTimesCommand.cs b/BuildMonitorPackage/AnalyseBuildTimesCommand.cs index 9a5c38d..8481763 100644 --- a/BuildMonitorPackage/AnalyseBuildTimesCommand.cs +++ b/BuildMonitorPackage/AnalyseBuildTimesCommand.cs @@ -101,7 +101,7 @@ public static void Initialize(Package package) /// Event args. private void MenuItemCallback(object sender, EventArgs e) { - var form = new AnalyseBuildTimes(new BuildMonitor.LocalData.AnalyseBuildTimes().Calculate(File.ReadAllText(Settings.RepositoryFilename)).SolutionMonthTable()); + var form = new AnalyseBuildTimes(new BuildMonitor.LocalData.AnalyseBuildTimes().Calculate(File.ReadAllText(Settings.Instance.RepositoryPath)).SolutionMonthTable()); form.ShowDialog(); } diff --git a/BuildMonitorPackage/BuildMonitorPackage.cs b/BuildMonitorPackage/BuildMonitorPackage.cs index 4c44905..fe29e99 100644 --- a/BuildMonitorPackage/BuildMonitorPackage.cs +++ b/BuildMonitorPackage/BuildMonitorPackage.cs @@ -11,46 +11,50 @@ using System.Data.SqlClient; using System.Data; using System.Security.Principal; -using System.Threading.Tasks; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell.Settings; namespace BuildMonitorPackage { [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] [Guid(GuidList.guidBuildMonitorPackagePkgString)] [PackageRegistration(UseManagedResourcesOnly = true)] - [ProvideAutoLoad("{f1536ef8-92ec-443c-9ed7-fdadf150da82}")] + [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)] [ProvideMenuResource("Menus.ctmenu", 1)] + [ProvideOptionPage(typeof(SettingsPage), "Build Monitor", "General", 0, 0, true)] sealed class BuildMonitorPackage : Package, IVsUpdateSolutionEvents2 { private DTE dte; - private readonly Monitor monitor; - private readonly DataAdjusterWithLogging dataAdjuster; + private Monitor monitor; + private DataAdjusterWithLogging dataAdjuster; private BuildMonitor.Domain.Solution solution; private IVsSolutionBuildManager2 sbm; private uint updateSolutionEventsCookie; - private OutputWindowPane outputWindowPane; private SolutionEvents events; private IVsSolution2 vsSolution; + private OutputWindowWrapper output; - public BuildMonitorPackage() + protected override void Initialize() { - Settings.CreateRepositoryPathIfNotExist(); + base.Initialize(); + + output = new OutputWindowWrapper(this); + + SettingsManager settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); + WritableSettingsStore settingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); + Settings.Instance = new Settings(settingsStore); var factory = new BuildFactory(); - var repository = new BuildRepository(Settings.RepositoryFilename); + var repository = new BuildRepository(Settings.Instance.RepositoryPath); monitor = new Monitor(factory, repository); - dataAdjuster = new DataAdjusterWithLogging(repository, PrintLine); - } - - protected override void Initialize() - { - base.Initialize(); + dataAdjuster = new DataAdjusterWithLogging(repository, output.WriteLine); //if invalid data, adjust it dataAdjuster.Adjust(); + // Get solution build manager sbm = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2; if (sbm != null) @@ -63,18 +67,18 @@ protected override void Initialize() events.Opened += Solution_Opened; GetDTE().Events.BuildEvents.OnBuildBegin += Build_Begin; - PrintLine("Build monitor initialized"); - PrintLine("Path to persist data: {0}", Settings.RepositoryFilename); + output.WriteLine("Build monitor initialized"); + output.WriteLine("Path to persist data: {0}", Settings.Instance.RepositoryPath); monitor.SolutionBuildFinished = b => { - Print("[{0}] Time Elapsed: {1} \t\t", b.SessionBuildCount, b.SolutionBuildTime.ToTime()); - PrintLine("Session build time: {0}\n", b.SessionMillisecondsElapsed.ToTime()); - PrintLine("Rebuild All: {0}\n", b.SolutionBuild.IsRebuildAll); + output.Write("[{0}] Time Elapsed: {1} \t\t", b.SessionBuildCount, b.SolutionBuildTime.ToTime()); + output.WriteLine("Session build time: {0}\n", b.SessionMillisecondsElapsed.ToTime()); + output.WriteLine("Rebuild All: {0}\n", b.SolutionBuild.IsRebuildAll); //System.Threading.Tasks.Task.Factory.StartNew(() => SaveToDatabase(b)); }; - monitor.ProjectBuildFinished = b => PrintLine(" - {0}\t-- {1} --", b.MillisecondsElapsed.ToTime(), b.ProjectName); + monitor.ProjectBuildFinished = b => output.WriteLine(" - {0}\t-- {1} --", b.MillisecondsElapsed.ToTime(), b.ProjectName); AnalyseBuildTimesCommand.Initialize(this); } @@ -101,34 +105,10 @@ protected override void Initialize() private void Solution_Opened() { solution = new BuildMonitor.Domain.Solution { Name = GetSolutionName() }; - PrintLine("\nSolution loaded: \t{0}", solution.Name); - PrintLine("{0}", 60.Times("-")); - } - - #region Print to output window pane - - private OutputWindowPane GetOutputWindowPane() - { - if (outputWindowPane == null) - { - var outputWindow = (OutputWindow)GetDTE().Windows.Item(EnvDTEConstants.vsWindowKindOutput).Object; - outputWindowPane = outputWindow.OutputWindowPanes.Add("Build monitor"); - } - return outputWindowPane; - } - - private void Print(string format, params object[] args) - { - GetOutputWindowPane().OutputString(string.Format(format, args)); - } - - private void PrintLine(string format, params object[] args) - { - Print(format + Environment.NewLine, args); + output.WriteLine("\nSolution loaded: \t{0}", solution.Name); + output.WriteLine(new string('-', 60)); } - #endregion - #region Get objects from vs private DTE GetDTE() diff --git a/BuildMonitorPackage/BuildMonitorPackage.csproj b/BuildMonitorPackage/BuildMonitorPackage.csproj index 93a4cfb..06c2cd5 100644 --- a/BuildMonitorPackage/BuildMonitorPackage.csproj +++ b/BuildMonitorPackage/BuildMonitorPackage.csproj @@ -63,7 +63,7 @@ - True + False @@ -182,6 +182,7 @@ + True True @@ -191,6 +192,9 @@ + + Component + diff --git a/BuildMonitorPackage/OutputWindowWrapper.cs b/BuildMonitorPackage/OutputWindowWrapper.cs new file mode 100644 index 0000000..8f0e3ec --- /dev/null +++ b/BuildMonitorPackage/OutputWindowWrapper.cs @@ -0,0 +1,45 @@ +using System; +using EnvDTE; +using Microsoft.VisualStudio.Shell.Interop; + +namespace BuildMonitorPackage { + internal class OutputWindowWrapper { + private readonly OutputWindowPane outputWindowPane; + + public OutputWindowWrapper(IServiceProvider serviceContainer) + { + var dte = serviceContainer.GetService(typeof(SDTE)) as DTE; + var outputWindow = (OutputWindow)dte.Windows.Item(EnvDTEConstants.vsWindowKindOutput).Object; + + foreach (OutputWindowPane pane in outputWindow.OutputWindowPanes) + { + if (pane.Name == "Build monitor") + { + outputWindowPane = pane; + } + } + + if (outputWindowPane == null) + { + outputWindowPane = outputWindow.OutputWindowPanes.Add("Build monitor"); + } + } + + public void Write(string text) { + outputWindowPane.OutputString(text); + } + + public void Write(string format, params object[] args) { + outputWindowPane.OutputString(string.Format(format, args)); + } + + public void WriteLine(string text) { + Write(text + Environment.NewLine); + } + + public void WriteLine(string format, params object[] args) + { + Write(format + Environment.NewLine, args); + } + } +} diff --git a/BuildMonitorPackage/Settings.cs b/BuildMonitorPackage/Settings.cs index e4af1c4..cf38544 100644 --- a/BuildMonitorPackage/Settings.cs +++ b/BuildMonitorPackage/Settings.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics; using System.IO; +using Microsoft.VisualStudio.Settings; // ReSharper disable InconsistentNaming namespace BuildMonitorPackage @@ -9,37 +11,114 @@ public static class OptionKey public const string SolutionId = "bm_solution_id"; } - public static class Settings - { - public static string RepositoryFilename + public class Settings { + + private static readonly string DefaultRepositoryPath = Path.Combine("%ApplicationData%", ApplicationFolderName, JsonFileName); + + private string rawRepositoryPath = DefaultRepositoryPath; + + private const string ApplicationFolderName = "Build Monitor"; + + private const string JsonFileName = "buildtimes.json"; + + private readonly WritableSettingsStore settingsStore; + + private const string CollectionPath = "BuildMonitor"; + + public static Settings Instance { get; set; } + + public Settings(WritableSettingsStore settingsStore) { + this.settingsStore = settingsStore; + LoadSettings(); + CreateApplicationFolderIfNotExist(); + } + + public string RawRepositoryPath { - get => - string.IsNullOrWhiteSpace(BuildMonitorRepositoryFilename) - ? DefaultRepositoryFilename - : BuildMonitorRepositoryFilename; + get => rawRepositoryPath; + set + { + if (rawRepositoryPath != value) + { + rawRepositoryPath = value; + SaveSettings(); + } + } } - public static void CreateRepositoryPathIfNotExist() + public string RepositoryPath => ExpandPath(rawRepositoryPath); + + private void CreateApplicationFolderIfNotExist() { - if (!Directory.Exists(RepositoryPath)) + string folder = Path.GetDirectoryName(RepositoryPath); + if (!Directory.Exists(folder)) { - Directory.CreateDirectory(RepositoryPath); + Directory.CreateDirectory(folder); } - if (!File.Exists(RepositoryFilename)) + if (!File.Exists(RepositoryPath)) { - File.Create(RepositoryFilename).Dispose(); + using (var f = File.Create(RepositoryPath)){} } } - static string RepositoryPath => - Path.GetDirectoryName(RepositoryFilename); + private void LoadSettings() { + try + { + RawRepositoryPath = settingsStore.GetString(CollectionPath, "RepositoryPath", DefaultRepositoryPath); + } + catch (Exception ex) + { + Debug.Fail(ex.Message); + } + } - static string BuildMonitorRepositoryFilename => - Environment.GetEnvironmentVariable("BuildMonitorRepositoryFilename"); + private void SaveSettings() { + try + { + if (!settingsStore.CollectionExists(CollectionPath)) + { + settingsStore.CreateCollection(CollectionPath); + } - static string DefaultRepositoryFilename => - $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\Build Monitor\\buildtimes.json"; + settingsStore.SetString(CollectionPath, "RepositoryPath", rawRepositoryPath); + } + catch (Exception ex) + { + Debug.Fail(ex.Message); + } + } + /// + /// Expands a path possibly starting with a + /// to a full path. + /// + private static string ExpandPath(string path) + { + if (!path.StartsWith("%")) + { + return path; + } + + int splitIndex = path.IndexOf("%", 1, StringComparison.InvariantCulture) + 1; + string maybeSpecialFolder = path.Substring(0, splitIndex).Trim('%'); + string rest = path.Substring(splitIndex); + while (rest.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + // The remaining path cannot start with a rooted path as that + // will "break" Path.Combine. + rest = rest.Substring(1); + } + + foreach (var @enum in Enum.GetNames(typeof(Environment.SpecialFolder))) + { + if (@enum.Equals(maybeSpecialFolder, StringComparison.InvariantCultureIgnoreCase)) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), rest); + } + } + + return path; + } } } // ReSharper restore InconsistentNaming \ No newline at end of file diff --git a/BuildMonitorPackage/SettingsPage.cs b/BuildMonitorPackage/SettingsPage.cs new file mode 100644 index 0000000..afb75c1 --- /dev/null +++ b/BuildMonitorPackage/SettingsPage.cs @@ -0,0 +1,27 @@ +using System.ComponentModel; +using Microsoft.VisualStudio.Shell; + +namespace BuildMonitorPackage +{ + public class SettingsPage : DialogPage + { + [Category("Build Monitor")] + [DisplayName("Output File Path")] + [Description("Specifies the path to the file to which data is persisted. Can contain special folders, see https://msdn.microsoft.com/en-us/library/system.environment.specialfolder(v=vs.110).aspx, enclosed in %. Example: %ApplicationData%\\Build Monitor\\buildtimes.json (default)")] + public string RepositoryPath { get; set; } + + public SettingsPage() + { + RepositoryPath = Settings.Instance.RawRepositoryPath; + } + + protected override void OnApply(PageApplyEventArgs args) + { + base.OnApply(args); + Settings.Instance.RawRepositoryPath = RepositoryPath; + + var output = new OutputWindowWrapper(ServiceProvider.GlobalProvider); + output.WriteLine("New path to persist data: {0}", Settings.Instance.RepositoryPath); + } + } +} diff --git a/BuildMonitorPackage/source.extension.vsixmanifest b/BuildMonitorPackage/source.extension.vsixmanifest index a2a1e17..be5fb88 100644 --- a/BuildMonitorPackage/source.extension.vsixmanifest +++ b/BuildMonitorPackage/source.extension.vsixmanifest @@ -13,9 +13,9 @@ - - - + + + diff --git a/UpgradeLog.htm b/UpgradeLog.htm deleted file mode 100644 index 2b63597..0000000 Binary files a/UpgradeLog.htm and /dev/null differ