Navigation Menu

Skip to content
This repository has been archived by the owner on Jul 15, 2019. It is now read-only.

Commit

Permalink
User defined reference paths for assembly dependency resolution.
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Vuyka committed Nov 1, 2011
1 parent 9bbca2a commit a3e7fed
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 12 deletions.
1 change: 1 addition & 0 deletions IL.View/IL.View.csproj
Expand Up @@ -130,6 +130,7 @@
<Compile Include="Model\AssemblyStream.cs" />
<Compile Include="Model\BusyIndicatorContext.cs" />
<Compile Include="Model\CSharpLanguage.cs" />
<Compile Include="Model\FileService.cs" />
<Compile Include="Model\FormattingUtils.cs" />
<Compile Include="Model\LanguageInfo.cs" />
<Compile Include="Model\ReferencePathsSettings.cs" />
Expand Down
169 changes: 169 additions & 0 deletions IL.View/Model/FileService.cs
@@ -0,0 +1,169 @@
/*
* The MIT License
*
* Copyright © 2011, Denys Vuika
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* */

using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.InteropServices.Automation;
using System.Windows;
using System.Windows.Threading;
using Mono.Cecil;

namespace IL.View.Model
{
internal static class FileService
{
private const string ReferenceCachePrefix = "REF_";

// TODO: add possibility to clear cache from UI
public static AssemblyDefinition FindExternalAssembly(AssemblyNameReference reference, Dispatcher dispatcher)
{
// TODO: add support User folders
/*
* User folders can be accessed without Automation Factory so it should be possible to
* define a reference path based on User folders hierarchy. In this case this feature will work
* for MacOs platforms.
*
* Possible ways to implement:
*
* 1. Allow "Reference Paths" tab to be visible when running on Macs
* 2. When adding reference path a check should be performed (whether path belongs to User folder hierarchy)
* 3. The paths that do not belong to User folder hierachy should not be added (Macs only, provide some warning/notification)
*
* Reference caching should also work fine for User folders.
*/
if (!Application.Current.HasElevatedPermissions) return null;
if (!AutomationFactory.IsAvailable) return null;
if (reference == null) return null;

var referencePaths = ReferencePathsSettings.Current.Folders.ToArray();
var targetName = string.Format("{0}.dll", reference.Name);

try
{
var savedPath = GetCachedPath(reference);
// TODO: optimize code and remove duplication
if (!string.IsNullOrEmpty(savedPath))
{
using (var stream = LoadFile(savedPath))
{
var definition = AssemblyDefinition.ReadAssembly(stream);

if (definition != null && definition.FullName == reference.FullName)
{
Debug.WriteLine("Successfully resolved external assembly from cache: {0}", savedPath);
var assemblyStream = new AssemblyMemoryStream(targetName, stream);
dispatcher.BeginInvoke(() => ApplicationModel.Current.AssemblyCache.LoadAssembly(assemblyStream, definition));
return definition;
}
}
}

var fso = AutomationFactory.CreateObject("Scripting.FileSystemObject");

foreach (var referenceFolder in referencePaths)
{
var file = FindFile(fso, referenceFolder.Path, targetName, referenceFolder.RecursiveSearch);
if (file == null) continue;

string path = file.Path;
using (var stream = LoadFile(path))
{
var definition = AssemblyDefinition.ReadAssembly(stream);

if (definition == null) continue;
if (definition.FullName != reference.FullName) continue;

Debug.WriteLine("Successfully resolved external assembly: {0}", path);

AddCachedPath(reference, path);

var assemblyStream = new AssemblyMemoryStream(targetName, stream);
dispatcher.BeginInvoke(() => ApplicationModel.Current.AssemblyCache.LoadAssembly(assemblyStream, definition));
return definition;
}
}
}
catch
{
if (Debugger.IsAttached) Debugger.Break();
return null;
}

return null;
}

private static dynamic FindFile(dynamic fso, string rootPath, string fileName, bool recursive)
{
if (!fso.FolderExists(rootPath)) return null;
var folder = fso.GetFolder(rootPath);

var targetPath = Path.Combine(rootPath, fileName);
if (fso.FileExists(targetPath)) return fso.GetFile(targetPath);

if (!recursive) return null;

foreach (var subFolder in folder.SubFolders)
{
var assembly = FindFile(fso, subFolder.Path, fileName, true);
if (assembly != null) return assembly;
}

return null;
}

private static string GetCachedPath(AssemblyNameReference reference)
{
if (reference == null) return null;
var cachedName = ReferenceCachePrefix + reference.FullName;
string cachedPath;
return IsolatedStorageSettings.ApplicationSettings.TryGetValue(cachedName, out cachedPath) ? cachedPath : null;
}

private static void AddCachedPath(AssemblyNameReference reference, string path)
{
if (reference == null) return;
if (string.IsNullOrWhiteSpace(path)) return;
IsolatedStorageSettings.ApplicationSettings[ReferenceCachePrefix + reference.FullName] = path;
}

// http://stackoverflow.com/questions/3462039/scripting-filesystemobject-write-method-fails
private static Stream LoadFile(string path)
{
byte[] data;
const int adTypeBinary = 1;

using (var adoCom = AutomationFactory.CreateObject("ADODB.Stream"))
{
adoCom.Type = adTypeBinary;
adoCom.Open();
adoCom.LoadFromFile(path);
data = adoCom.Read();
}

return data == null ? Stream.Null : new MemoryStream(data);
}
}
}
48 changes: 38 additions & 10 deletions IL.View/Model/ReferencePathsSettings.cs
Expand Up @@ -35,15 +35,42 @@
namespace IL.View.Model
{
[DebuggerDisplay("{Path}")]
public sealed class ReferenceFolder
public sealed class ReferenceFolder : INotifyPropertyChanged
{
private readonly ReferencePathsSettings _settings;
private bool _recursiveSearch;

public string Path { get; set; }

public ReferenceFolder(string path)
public bool RecursiveSearch
{
get { return _recursiveSearch; }
set
{
if (_recursiveSearch == value) return;
_recursiveSearch = value;
OnPropertyChanged("RecursiveSearch");
if (_settings.AutoSave) _settings.Save();
}
}

internal ReferenceFolder(ReferencePathsSettings settings, string path, bool recursiveSearch = false)
{
if (settings == null) throw new ArgumentNullException("settings");
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path");

_settings = settings;
_recursiveSearch = recursiveSearch;
Path = path;
}

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

public sealed class ReferencePathsSettings : INotifyPropertyChanged
Expand All @@ -54,7 +81,7 @@ public static ReferencePathsSettings Current
}

public bool AutoSave { get; set; }

private ObservableCollection<ReferenceFolder> _folders = new ObservableCollection<ReferenceFolder>();

public ObservableCollection<ReferenceFolder> Folders
Expand Down Expand Up @@ -86,7 +113,7 @@ public bool AddFolder(string path)

if (!Folders.Any(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase)))
{
Folders.Add(new ReferenceFolder(path));
Folders.Add(new ReferenceFolder(this, path));
return true;
}

Expand All @@ -102,9 +129,10 @@ public void Save()
{
var data = new XElement("ReferencePaths");

foreach (var address in Folders.Select(d => d.Path).Distinct())
foreach (var folder in Folders)
data.Add(new XElement("ReferenceFolder",
new XAttribute("Path", address)));
new XAttribute("Path", folder.Path),
new XAttribute("RecursiveSearch", folder.RecursiveSearch)));

IsolatedStorageSettings.ApplicationSettings["ReferencePaths"] = data.ToString();
IsolatedStorageSettings.ApplicationSettings.Save();
Expand All @@ -116,7 +144,7 @@ public void Load()
OnPropertyChanged("Folders");
}

private static IEnumerable<ReferenceFolder> LoadSettings()
private IEnumerable<ReferenceFolder> LoadSettings()
{
string settings;

Expand All @@ -128,9 +156,9 @@ private static IEnumerable<ReferenceFolder> LoadSettings()
{
foreach (var element in data.Elements())
{
var path = element.Attribute("Path");
if (path != null)
yield return new ReferenceFolder(path.Value);
var path = (string)element.Attribute("Path");
var recursiveSearch = (bool)element.Attribute("RecursiveSearch");
yield return new ReferenceFolder(this, path, recursiveSearch);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions IL.View/Views/Home.xaml.cs
Expand Up @@ -373,6 +373,10 @@ private AssemblyDefinition TryResolveAssembly(object sender, AssemblyNameReferen
if (definition == null)
definition = TryResolveHigherVersionAssembly(_decompileTask.CallingAssembly, reference);

// try to resolve assembly from user-defined reference paths
if (definition == null)
definition = FileService.FindExternalAssembly(reference, Dispatcher);

// ask user to resolve assembly manually
if (definition == null)
{
Expand Down
12 changes: 10 additions & 2 deletions IL.View/Views/Settings.xaml
Expand Up @@ -40,6 +40,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Grid Grid.Row="0">
Expand All @@ -51,9 +52,11 @@
<Button Grid.Column="1" Content="Add" Click="OnAddReferenceFolderClick" Height="25" Margin="2,0,0,0"/>
</Grid>

<ListBox Grid.Row="1"
<ListBox Grid.Row="1" x:Name="ReferencePathList"
ItemsSource="{Binding Path=Folders, Source={StaticResource ReferencePathsSettings}}"
Margin="0,5,0,0">
Margin="0,5,0,0"
SelectionMode="Single"
SelectionChanged="OnReferenceFolderSelected">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
Expand All @@ -67,6 +70,11 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

<CheckBox Grid.Row="2" x:Name="RecursiveSearch"
Content="Recursive Search"
IsEnabled="False"
ToolTipService.ToolTip="Recursively check all subfolders for the given path. Warning, this may be a time consuming process."/>
</Grid>
</sdk:TabItem>

Expand Down
21 changes: 21 additions & 0 deletions IL.View/Views/Settings.xaml.cs
Expand Up @@ -24,6 +24,8 @@

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Navigation;
using IL.View.Model;
using System.Runtime.InteropServices.Automation;
Expand Down Expand Up @@ -82,5 +84,24 @@ private void OnRemoveEntryClick(object sender, RoutedEventArgs e)
if (referenceFolder != null) _referencePathsSettings.Folders.Remove(referenceFolder);
}

private void OnReferenceFolderSelected(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
RecursiveSearch.IsEnabled = true;
RecursiveSearch.SetBinding(ToggleButton.IsCheckedProperty, new Binding("RecursiveSearch")
{
Mode = BindingMode.TwoWay,
Source = e.AddedItems[0]
});
}
else
{
RecursiveSearch.IsEnabled = false;
RecursiveSearch.DataContext = null;
RecursiveSearch.ClearValue(ToggleButton.IsCheckedProperty);
}
}

}
}

0 comments on commit a3e7fed

Please sign in to comment.