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