Skip to content

Commit

Permalink
Merge pull request #4601 from comintern/refs
Browse files Browse the repository at this point in the history
💯
  • Loading branch information
retailcoder committed Dec 18, 2018
2 parents c5be87b + 3943988 commit 7733e38
Show file tree
Hide file tree
Showing 88 changed files with 6,899 additions and 2,314 deletions.
194 changes: 194 additions & 0 deletions Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs
@@ -0,0 +1,194 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Rubberduck.Parsing.ComReflection;
using Rubberduck.VBEditor;
using Rubberduck.VBEditor.SafeComWrappers;
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
using Rubberduck.VBEditor.Utility;

namespace Rubberduck.AddRemoveReferences
{
public class ReferenceModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private ReferenceModel()
{
_info = new Lazy<ReferenceInfo>(GenerateInfo);
}

public ReferenceModel(ReferenceInfo info, ReferenceKind type, bool recent = false, bool pinned = false) : this()
{
Guid = info.Guid;
Name = info.Name;
Description = Name;
FullPath = info.FullPath;
Major = info.Major;
Minor = info.Minor;
IsRecent = recent;
IsPinned = pinned;
Type = type;
}

public ReferenceModel(IVBProject project, int priority) : this()
{
Name = project.Name ?? string.Empty;
Priority = priority;
Guid = Guid.Empty;
Description = project.Description ?? project.Name;
FullPath = project.FileName ?? string.Empty;
IsBuiltIn = false;
Type = ReferenceKind.Project;
}

public ReferenceModel(RegisteredLibraryInfo info) : this()
{
Name = info.Name ?? string.Empty;
Guid = info.Guid;
Description = string.IsNullOrEmpty(info.Description) ? Path.GetFileNameWithoutExtension(info.FullPath) : info.Description;
Major = info.Major;
Minor = info.Minor;
FullPath = info.FullPath;
LocaleName = info.LocaleName;
IsBuiltIn = false;
Type = ReferenceKind.TypeLibrary;
Flags = (TypeLibTypeFlags)info.Flags;
IsRegistered = true;
}

public ReferenceModel(RegisteredLibraryInfo info, IReference reference, int priority) : this(info)
{
Priority = priority;
IsBuiltIn = reference.IsBuiltIn;
IsBroken = reference.IsBroken;
IsReferenced = true;
}

public ReferenceModel(IReference reference, int priority) : this()
{
Priority = priority;
Name = reference.Name;
Guid = Guid.TryParse(reference.Guid, out var guid) ? guid : Guid.Empty;
Description = string.IsNullOrEmpty(reference.Description) ? Path.GetFileNameWithoutExtension(reference.FullPath) : reference.Description;
Major = reference.Major;
Minor = reference.Minor;
FullPath = reference.FullPath;
IsBuiltIn = reference.IsBuiltIn;
IsBroken = reference.IsBroken;
IsReferenced = true;
Type = reference.Type;
}

public ReferenceModel(string path, ITypeLib reference, IComLibraryProvider provider) : this()
{
FullPath = path;

var documentation = provider.GetComDocumentation(reference);
Name = documentation.Name;
Description = documentation.DocString;

var info = provider.GetReferenceInfo(reference, Name, path);
Guid = info.Guid;
Major = info.Major;
Minor = info.Minor;
}

public ReferenceModel(string path, bool broken = false) : this()
{
FullPath = path;
try
{
Name = Path.GetFileName(path) ?? path;
Description = Name;
}
catch
{
// Yeah, that's probably busted.
IsBroken = true;
return;
}

IsBroken = broken;
}

private bool _pinned;
public bool IsPinned
{
get => _pinned;
set
{
_pinned = value;
NotifyPropertyChanged();
}
}

public bool IsRecent { get; set; }
public bool IsRegistered { get; set; }
public bool IsReferenced { get; set; }

public int? Priority { get; set; }

public string Name { get; } = string.Empty;
public Guid Guid { get; }
public string Description { get; } = string.Empty;
public string FullPath { get; }
public string LocaleName { get; } = string.Empty;

public bool IsBuiltIn { get; set; }
public bool IsBroken { get; }
public TypeLibTypeFlags Flags { get; set; }
public ReferenceKind Type { get; }

private string FullPath32 { get; } = string.Empty;
private string FullPath64 { get; } = string.Empty;
public int Major { get; set; }
public int Minor { get; set; }
public string Version => $"{Major}.{Minor}";

public ReferenceStatus Status
{
get
{
var status = IsPinned ? ReferenceStatus.Pinned : ReferenceStatus.None;
if (!Priority.HasValue)
{
return IsRecent ? status | ReferenceStatus.Recent : status;
}

if (IsBroken)
{
return status | ReferenceStatus.Broken;
}

if (IsBuiltIn)
{
return status | ReferenceStatus.BuiltIn;
}

return status | (IsReferenced ? ReferenceStatus.Loaded : ReferenceStatus.Added);
}
}

private readonly Lazy<ReferenceInfo> _info;
private ReferenceInfo GenerateInfo() => new ReferenceInfo(Guid, Name, FullPath, Major, Minor);
public ReferenceInfo ToReferenceInfo() => _info.Value;

public bool Matches(ReferenceInfo info)
{
return Major == info.Major && Minor == info.Minor &&
FullPath.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) ||
FullPath32.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) ||
FullPath64.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) ||
Guid.Equals(info.Guid);
}

private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
179 changes: 179 additions & 0 deletions Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs
@@ -0,0 +1,179 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Rubberduck.Interaction;
using Rubberduck.Parsing.ComReflection;
using Rubberduck.Resources;
using Rubberduck.Settings;
using Rubberduck.SettingsProvider;
using Rubberduck.UI.AddRemoveReferences;
using Rubberduck.VBEditor.SafeComWrappers.Abstract;

namespace Rubberduck.AddRemoveReferences
{
public interface IReferenceReconciler
{
List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model);
List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model, List<ReferenceModel> allReferences);
ReferenceModel TryAddReference(IVBProject project, string path);
ReferenceModel TryAddReference(IVBProject project, ReferenceModel reference);
ReferenceModel GetLibraryInfoFromPath(string path);
void UpdateSettings(IAddRemoveReferencesModel model, bool recent = false);
}

public class ReferenceReconciler : IReferenceReconciler
{
public static readonly List<string> TypeLibraryExtensions = new List<string> { ".olb", ".tlb", ".dll", ".ocx", ".exe" };

private readonly IMessageBox _messageBox;
private readonly IConfigProvider<ReferenceSettings> _settings;
private readonly IComLibraryProvider _libraryProvider;

public ReferenceReconciler(
IMessageBox messageBox,
IConfigProvider<ReferenceSettings> settings,
IComLibraryProvider libraryProvider)
{
_messageBox = messageBox;
_settings = settings;
_libraryProvider = libraryProvider;
}

public List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model)
{
if (model?.NewReferences is null || !model.NewReferences.Any())
{
return new List<ReferenceModel>();
}

return ReconcileReferences(model, model.NewReferences.ToList());
}

//TODO test for simple adds.
public List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model, List<ReferenceModel> allReferences)
{
if (model is null || allReferences is null || !allReferences.Any())
{
return new List<ReferenceModel>();
}

var selected = allReferences.Where(reference => !reference.IsBuiltIn && reference.Priority.HasValue)
.ToDictionary(reference => reference.FullPath);

var output = selected.Values.Where(reference => reference.IsBuiltIn).ToList();

var project = model.Project.Project;
using (var references = project.References)
{
foreach (var reference in references)
{
try
{
if (!reference.IsBuiltIn)
{
references.Remove(reference);
}
}
finally
{
reference.Dispose();
}
}
output.AddRange(selected.Values.OrderBy(selection => selection.Priority)
.Select(reference => TryAddReference(project, reference)).Where(added => added != null));
}

UpdateSettings(model, true);
return output;
}

public ReferenceModel GetLibraryInfoFromPath(string path)
{
try
{
var extension = Path.GetExtension(path)?.ToLowerInvariant() ?? string.Empty;
if (string.IsNullOrEmpty(extension))
{
return null;
}

// LoadTypeLibrary will attempt to open files in the host, so only attempt on possible COM servers.
if (TypeLibraryExtensions.Contains(extension))
{
var type = _libraryProvider.LoadTypeLibrary(path);
return new ReferenceModel(path, type, _libraryProvider);
}
return new ReferenceModel(path);
}
catch
{
// Most likely this is unloadable. If not, it we can't fail here because it could have come from the Apply
// button in the AddRemoveReferencesDialog. Wait for it... :-P
return new ReferenceModel(path, true);
}
}

public ReferenceModel TryAddReference(IVBProject project, string path)
{
using (var references = project.References)
{
try
{
using (var reference = references.AddFromFile(path))
{
return reference is null ? null : new ReferenceModel(reference, references.Count) { IsRecent = true };
}
}
catch (COMException ex)
{
_messageBox.NotifyWarn(ex.Message, RubberduckUI.References_AddFailedCaption);
}
return null;
}
}

public ReferenceModel TryAddReference(IVBProject project, ReferenceModel reference)
{
using (var references = project.References)
{
try
{
using (references.AddFromFile(reference.FullPath))
{
reference.Priority = references.Count;
reference.IsRecent = true;
return reference;
}
}
catch (COMException ex)
{
_messageBox.NotifyWarn(ex.Message, RubberduckUI.References_AddFailedCaption);
}
return null;
}
}

public void UpdateSettings(IAddRemoveReferencesModel model, bool recent = false)
{
if (model?.Settings is null || model.References is null)
{
return;
}

if (recent)
{
model.Settings.UpdateRecentReferencesForHost(model.HostApplication,
model.References.Where(reference => reference.IsReferenced && !reference.IsBuiltIn)
.Select(reference => reference.ToReferenceInfo()).ToList());

}

model.Settings.UpdatePinnedReferencesForHost(model.HostApplication,
model.References.Where(reference => reference.IsPinned).Select(reference => reference.ToReferenceInfo())
.ToList());

_settings.Save(model.Settings);
}
}
}
16 changes: 16 additions & 0 deletions Rubberduck.Core/AddRemoveReferences/ReferenceStatus.cs
@@ -0,0 +1,16 @@
using System;

namespace Rubberduck.AddRemoveReferences
{
[Flags]
public enum ReferenceStatus
{
None = 0,
BuiltIn = 1 << 1,
Loaded = 1 << 2,
Broken = 1 << 3,
Pinned = 1 << 4,
Recent = 1 << 5,
Added = 1 << 6
}
}

0 comments on commit 7733e38

Please sign in to comment.