Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix exception on saving many tab groups #28

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
image: Visual Studio 2019

install:
- ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex

before_build:
- ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion
- ps: Vsix-TokenReplacement src\SaveAllTheTabs\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"'

build_script:
- nuget restore src\SaveAllTheTabs\SaveAllTheTabs.csproj -SolutionDirectory src -Verbosity quiet
- msbuild src\SaveAllTheTabs.sln /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m

after_test:
- ps: Vsix-PushArtifacts | Vsix-PublishToGallery
4 changes: 4 additions & 0 deletions src/SaveAllTheTabs/Commands/PackageCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static void Initialize(SaveAllTheTabsPackage package)
/// <param name="package">Owner package, not null.</param>
private PackageCommands(SaveAllTheTabsPackage package)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (package == null)
{
throw new ArgumentNullException(nameof(package));
Expand All @@ -74,6 +75,7 @@ private PackageCommands(SaveAllTheTabsPackage package)

private void SetupCommands(OleMenuCommandService commandService)
{
ThreadHelper.ThrowIfNotOnUIThread();
var guid = typeof(CommandIds).GUID;

var commandId = new CommandID(guid, (int)CommandIds.SaveTabs);
Expand Down Expand Up @@ -114,6 +116,7 @@ private void SetupCommands(OleMenuCommandService commandService)

private void CommandOnBeforeQueryStatus(object sender, EventArgs eventArgs)
{
ThreadHelper.ThrowIfNotOnUIThread();
var command = sender as OleMenuCommand;
if (command == null)
{
Expand All @@ -138,6 +141,7 @@ private void ExecuteSaveTabsCommand(object sender, EventArgs e)

private void ExecuteSavedTabsWindowCommand(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
// Get the instance number 0 of this tool window. This window is single instance so this instance
// is actually the only one.
// The last flag is set to true so that if the tool window does not exists it will be created.
Expand Down
1 change: 1 addition & 0 deletions src/SaveAllTheTabs/Commands/RestoreTabsListCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public RestoreTabsListCommands(SaveAllTheTabsPackage package)

public void SetupCommands(OleMenuCommandService commandService)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (Package.DocumentManager == null)
{
return;
Expand Down
2 changes: 2 additions & 0 deletions src/SaveAllTheTabs/Commands/SavedTabsWindowCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private void SetupCommands(OleMenuCommandService commandService)

private void SaveToCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs)
{
ThreadHelper.ThrowIfNotOnUIThread();
var command = sender as OleMenuCommand;
if (command == null)
{
Expand All @@ -93,6 +94,7 @@ private void SaveToCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs

private void CloseCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs)
{
ThreadHelper.ThrowIfNotOnUIThread();
var command = sender as OleMenuCommand;
if (command == null)
{
Expand Down
103 changes: 82 additions & 21 deletions src/SaveAllTheTabs/DocumentManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
Expand All @@ -8,8 +9,12 @@
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell.Settings;
using Newtonsoft.Json;
Expand Down Expand Up @@ -67,28 +72,39 @@ internal class DocumentManager : IDocumentManager

private const string StorageCollectionPath = "SaveAllTheTabs";
private const string SavedTabsStoragePropertyFormat = "SavedTabs.{0}";
private const string ProjectGroupsKeyPlaceholder = StorageCollectionPath + "\\{0}\\groups";

private SaveAllTheTabsPackage Package { get; }
private IServiceProvider ServiceProvider => Package;
private IVsUIShellDocumentWindowMgr DocumentWindowMgr { get; }
private string SolutionName => Package.Environment.Solution?.FullName;
private string SolutionName
{
get
{
ThreadHelper.ThrowIfNotOnUIThread();
return Package.Environment.Solution?.FullName;
}
}

public ObservableCollection<DocumentGroup> Groups { get; private set; }

public DocumentManager(SaveAllTheTabsPackage package)
{
ThreadHelper.ThrowIfNotOnUIThread();
Package = package;

package.SolutionChanged += (sender, args) => LoadGroups();
LoadGroups();

DocumentWindowMgr = ServiceProvider.GetService(typeof(IVsUIShellDocumentWindowMgr)) as IVsUIShellDocumentWindowMgr;
Assumes.Present(DocumentWindowMgr);
}

private IDisposable _changeSubscription;

private void LoadGroups()
{
ThreadHelper.ThrowIfNotOnUIThread();
_changeSubscription?.Dispose();

// Load presets for the current solution
Expand Down Expand Up @@ -121,6 +137,7 @@ private void LoadGroups()

public void SaveGroup(string name, int? slot = null)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (DocumentWindowMgr == null)
{
Debug.Assert(false, "IVsUIShellDocumentWindowMgr", String.Empty, 0);
Expand Down Expand Up @@ -212,11 +229,13 @@ private void TrySetSlot(DocumentGroup group, int? slot)

public void RestoreGroup(int slot)
{
ThreadHelper.ThrowIfNotOnUIThread();
RestoreGroup(Groups.FindBySlot(slot));
}

public void RestoreGroup(DocumentGroup group)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (group == null)
{
return;
Expand All @@ -228,13 +247,15 @@ public void RestoreGroup(DocumentGroup group)
SaveUndoGroup();
}

Package.Environment.Documents.CloseAll();
if (windows.Any())
windows.CloseAll();

OpenGroup(group);
}

public void OpenGroup(int slot)
{
ThreadHelper.ThrowIfNotOnUIThread();
OpenGroup(Groups.FindBySlot(slot));
}

Expand All @@ -247,6 +268,7 @@ public void OpenGroup(DocumentGroup group)

using (var stream = new VsOleStream())
{
ThreadHelper.ThrowIfNotOnUIThread();
stream.Write(group.Positions, 0, group.Positions.Length);
stream.Seek(0, SeekOrigin.Begin);

Expand All @@ -265,6 +287,7 @@ public void CloseGroup(DocumentGroup group)
return;
}

ThreadHelper.ThrowIfNotOnUIThread();
var documents = from d in Package.Environment.GetDocuments()
where @group.Files.Contains(d.FullName)
select d;
Expand Down Expand Up @@ -333,6 +356,7 @@ public void RemoveGroup(DocumentGroup group, bool confirm = true)

private void SaveUndoGroup()
{
ThreadHelper.ThrowIfNotOnUIThread();
SaveGroup(UndoGroupName);
}

Expand Down Expand Up @@ -362,16 +386,19 @@ private void SaveUndoGroup(DocumentGroup group)

public void SaveStashGroup()
{
ThreadHelper.ThrowIfNotOnUIThread();
SaveGroup(StashGroupName);
}

public void OpenStashGroup()
{
ThreadHelper.ThrowIfNotOnUIThread();
OpenGroup(Groups.FindByName(StashGroupName));
}

public void RestoreStashGroup()
{
ThreadHelper.ThrowIfNotOnUIThread();
RestoreGroup(Groups.FindByName(StashGroupName));
}

Expand Down Expand Up @@ -401,31 +428,49 @@ public void RestoreStashGroup()

private List<DocumentGroup> LoadGroupsForSolution()
{
ThreadHelper.ThrowIfNotOnUIThread();
var solution = SolutionName;
if (!string.IsNullOrWhiteSpace(solution))
{
try
{
var settingsMgr = new ShellSettingsManager(ServiceProvider);
var store = settingsMgr.GetReadOnlySettingsStore(SettingsScope.UserSettings);
List<DocumentGroup> groups = new List<DocumentGroup>();
if (string.IsNullOrWhiteSpace(solution)) {
return groups;
}

try {
var settingsMgr = new ShellSettingsManager(ServiceProvider);
var store = settingsMgr.GetReadOnlySettingsStore(SettingsScope.UserSettings);

string projectKeyHash = GetHashString(solution);
string projectGroupsKey = string.Format(ProjectGroupsKeyPlaceholder, projectKeyHash);

if (!store.CollectionExists(projectGroupsKey))
{
// try load tabs from older versions
var propertyName = String.Format(SavedTabsStoragePropertyFormat, solution);
if (store.PropertyExists(StorageCollectionPath, propertyName))
{
var tabs = store.GetString(StorageCollectionPath, propertyName);
return JsonConvert.DeserializeObject<List<DocumentGroup>>(tabs);
groups = JsonConvert.DeserializeObject<List<DocumentGroup>>(tabs);
}

return groups;
}
catch (Exception ex)

var groupProperties = store.GetPropertyNamesAndValues(projectGroupsKey);
foreach (var groupProperty in groupProperties)
{
Debug.Assert(false, nameof(LoadGroupsForSolution), ex.ToString());
DocumentGroup group = JsonConvert.DeserializeObject<DocumentGroup>(groupProperty.Value.ToString());
groups.Add(group);
}
} catch (Exception ex) {
Debug.Assert(false, nameof(LoadGroupsForSolution), ex.ToString());
}

return groups;
}
return new List<DocumentGroup>();
}

private void SaveGroupsForSolution(IList<DocumentGroup> groups = null)
{
ThreadHelper.ThrowIfNotOnUIThread();
var solution = SolutionName;
if (string.IsNullOrWhiteSpace(solution))
{
Expand All @@ -440,20 +485,36 @@ private void SaveGroupsForSolution(IList<DocumentGroup> groups = null)
var settingsMgr = new ShellSettingsManager(ServiceProvider);
var store = settingsMgr.GetWritableSettingsStore(SettingsScope.UserSettings);

if (!store.CollectionExists(StorageCollectionPath))
string projectKeyHash = GetHashString(solution);
string projectGroupsKey = string.Format(ProjectGroupsKeyPlaceholder, projectKeyHash);

if (store.CollectionExists(projectGroupsKey))
{
store.CreateCollection(StorageCollectionPath);
store.DeleteProperty(StorageCollectionPath, projectGroupsKey);
}
store.CreateCollection(projectGroupsKey);

var propertyName = String.Format(SavedTabsStoragePropertyFormat, solution);
if (!groups.Any())
foreach (DocumentGroup group in groups)
{
store.DeleteProperty(StorageCollectionPath, propertyName);
return;
var serializedGroup = JsonConvert.SerializeObject(group);
store.SetString(projectGroupsKey, group.Name, serializedGroup);
}
}

var tabs = JsonConvert.SerializeObject(groups);
store.SetString(StorageCollectionPath, propertyName, tabs);
private static string GetHashString(string source)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(source);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// Convert the byte array to hexadecimal string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes [i].ToString("X2"));
}
return sb.ToString();
}
}

public static bool IsStashGroup(string name)
Expand Down
Loading