diff --git a/Irony.GrammarExplorer/GrammarLoader.cs b/Irony.GrammarExplorer/GrammarLoader.cs
index 0b1b079..e7dceb2 100644
--- a/Irony.GrammarExplorer/GrammarLoader.cs
+++ b/Irony.GrammarExplorer/GrammarLoader.cs
@@ -19,42 +19,64 @@
using Irony.Parsing;
using System.IO;
using System.Threading;
+using System.Windows.Forms;
namespace Irony.GrammarExplorer {
///
/// Maintains grammar assemblies, reloads updated files automatically.
///
class GrammarLoader {
- private TimeSpan _autoRefreshDelay = TimeSpan.FromMilliseconds(500);
+ private TimeSpan _autoRefreshDelay = TimeSpan.FromMilliseconds(1000);
private Dictionary _cachedGrammarAssemblies = new Dictionary();
private static HashSet _probingPaths = new HashSet();
- private static Dictionary _loadedAssemblies = new Dictionary();
- private static bool _enableAssemblyResolution = false;
+ private static Dictionary _loadedAssembliesByLocation = new Dictionary();
+ private static Dictionary _loadedAssembliesByNames = new Dictionary();
+ private static bool _enableBrowsingForAssemblyResolution = false;
static GrammarLoader() {
- AppDomain.CurrentDomain.AssemblyLoad += (s, args) => _loadedAssemblies[args.LoadedAssembly.FullName] = args.LoadedAssembly;
- AppDomain.CurrentDomain.AssemblyResolve += (s, args) => _enableAssemblyResolution ? FindAssembly(args.Name) : null;
+ AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => _loadedAssembliesByNames[args.LoadedAssembly.FullName] = args.LoadedAssembly;
+ AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => FindAssembly(args.Name);
}
static Assembly FindAssembly(string assemblyName) {
- if (_loadedAssemblies.ContainsKey(assemblyName)) {
- return _loadedAssemblies[assemblyName];
- }
- // use probing paths to look for assemblies
+ if (_loadedAssembliesByNames.ContainsKey(assemblyName))
+ return _loadedAssembliesByNames[assemblyName];
+ // ignore resource assemblies
+ if (assemblyName.ToLower().Contains(".resources, version="))
+ return _loadedAssembliesByNames[assemblyName] = null;
+ // use probing paths to look for dependency assemblies
var fileName = assemblyName.Split(',').First() + ".dll";
foreach (var path in _probingPaths) {
var fullName = Path.Combine(path, fileName);
if (File.Exists(fullName)) {
try {
- return _loadedAssemblies[assemblyName] = Assembly.LoadFrom(fullName);
+ return LoadAssembly(fullName);
}
catch {
// the file seems to be bad, let's try to find another one
}
}
}
+ // the last chance: try asking user to locate the assembly
+ if (_enableBrowsingForAssemblyResolution) {
+ fileName = BrowseFor(assemblyName);
+ if (!string.IsNullOrWhiteSpace(fileName))
+ return LoadAssembly(fileName);
+ }
// assembly not found, don't search for it again
- return _loadedAssemblies[assemblyName] = null;
+ return _loadedAssembliesByNames[assemblyName] = null;
+ }
+
+ static string BrowseFor(string assemblyName) {
+ var fileDialog = new OpenFileDialog {
+ Title = "Please locate assembly: " + assemblyName,
+ Filter = "Assemblies (*.dll)|*.dll|All files (*.*)|*.*"
+ };
+ using (fileDialog) {
+ if (fileDialog.ShowDialog() == DialogResult.OK)
+ return fileDialog.FileName;
+ }
+ return null;
}
class CachedAssembly {
@@ -62,6 +84,7 @@ class CachedAssembly {
public DateTime LastWriteTime;
public FileSystemWatcher Watcher;
public Assembly Assembly;
+ public bool UpdateScheduled;
}
public event EventHandler AssemblyUpdated;
@@ -73,13 +96,13 @@ public Grammar CreateGrammar() {
return null;
// resolve dependencies while loading and creating grammars
- _enableAssemblyResolution = true;
+ _enableBrowsingForAssemblyResolution = true;
try {
var type = SelectedGrammarAssembly.GetType(SelectedGrammar.TypeName, true, true);
return Activator.CreateInstance(type) as Grammar;
}
finally {
- _enableAssemblyResolution = false;
+ _enableBrowsingForAssemblyResolution = false;
}
}
@@ -123,22 +146,29 @@ private FileSystemWatcher CreateFileWatcher(string location) {
if (args.ChangeType != WatcherChangeTypes.Changed)
return;
- // check if assembly was changed indeed to work around multiple FileSystemWatcher event firing
- var cacheEntry = _cachedGrammarAssemblies[location];
- var fileInfo = new FileInfo(location);
- if (cacheEntry.LastWriteTime == fileInfo.LastWriteTime && cacheEntry.FileSize == fileInfo.Length)
- return;
-
- // clear cached assembly and save last file update time
- cacheEntry.LastWriteTime = fileInfo.LastWriteTime;
- cacheEntry.FileSize = fileInfo.Length;
- cacheEntry.Assembly = null;
-
- // delay auto-refresh for safety reasons
- ThreadPool.QueueUserWorkItem(_ => {
- Thread.Sleep(_autoRefreshDelay);
- OnAssemblyUpdated(location);
- });
+ lock (this) {
+ // check if assembly file was changed indeed since the last event
+ var cacheEntry = _cachedGrammarAssemblies[location];
+ var fileInfo = new FileInfo(location);
+ if (cacheEntry.LastWriteTime == fileInfo.LastWriteTime && cacheEntry.FileSize == fileInfo.Length)
+ return;
+
+ // reset cached assembly and save last file update time
+ cacheEntry.LastWriteTime = fileInfo.LastWriteTime;
+ cacheEntry.FileSize = fileInfo.Length;
+ cacheEntry.Assembly = null;
+
+ // check if file update is already scheduled (work around multiple FileSystemWatcher event firing)
+ if (!cacheEntry.UpdateScheduled) {
+ cacheEntry.UpdateScheduled = true;
+ // delay auto-refresh to make sure the file is closed by the writer
+ ThreadPool.QueueUserWorkItem(_ => {
+ Thread.Sleep(_autoRefreshDelay);
+ cacheEntry.UpdateScheduled = false;
+ OnAssemblyUpdated(location);
+ });
+ }
+ }
};
watcher.EnableRaisingEvents = true;
@@ -152,12 +182,20 @@ private void OnAssemblyUpdated(string location) {
}
public static Assembly LoadAssembly(string fileName) {
+ // normalize the filename
+ fileName = new FileInfo(fileName).FullName;
// save assembly path for dependent assemblies probing
var path = Path.GetDirectoryName(fileName);
_probingPaths.Add(path);
- // 1. Assembly.Load doesn't block the file
- // 2. Assembly.Load doesn't check if the assembly is already loaded in the current AppDomain
- return Assembly.Load(File.ReadAllBytes(fileName));
+ // try to load assembly using the standard policy
+ var assembly = Assembly.LoadFrom(fileName);
+ // if the standard policy returned the old version, force reload
+ Assembly oldVersion;
+ if (_loadedAssembliesByLocation.TryGetValue(fileName, out oldVersion) && assembly == oldVersion)
+ assembly = Assembly.Load(File.ReadAllBytes(fileName));
+ // cache the loaded assembly by its location
+ _loadedAssembliesByLocation[fileName] = assembly;
+ return assembly;
}
}
}