/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.Linq; using System.Security; using dnlib.PE; using dnSpy.AsmEditor.UndoRedo; using dnSpy.Contracts.Hex; namespace dnSpy.AsmEditor.Hex { interface IHexBufferService { HexBuffer GetOrCreate(IPEImage peImage); HexBuffer? GetOrCreate(string filename); HexBuffer[] GetBuffers(); HexBuffer? TryGet(string filename); HexBuffer[] Clear(); } interface IHexBufferServiceListener { void BufferCreated(HexBuffer buffer); void BuffersCleared(IEnumerable buffers); } [Export(typeof(IHexBufferService))] sealed class HexBufferService : IHexBufferService { readonly object lockObj = new object(); readonly Dictionary filenameToBuffer = new Dictionary(StringComparer.OrdinalIgnoreCase); readonly HexBufferFactoryService hexBufferFactoryService; readonly Lazy[] hexBufferServiceListeners; [ImportingConstructor] HexBufferService(IUndoCommandService undoCommandService, HexBufferFactoryService hexBufferFactoryService, [ImportMany] IEnumerable> hexBufferServiceListeners) { this.hexBufferFactoryService = hexBufferFactoryService; this.hexBufferServiceListeners = hexBufferServiceListeners.ToArray(); undoCommandService.OnEvent += UndoCommandService_OnEvent; } void UndoCommandService_OnEvent(object? sender, UndoCommandServiceEventArgs e) { var buffer = HexUndoableDocumentsProvider.TryGetHexBuffer(e.UndoObject); if (buffer is null) return; if (e.Type == UndoCommandServiceEventType.Saved) OnDocumentSaved(buffer); else if (e.Type == UndoCommandServiceEventType.Dirty) OnDocumentDirty(buffer); } void OnDocumentSaved(HexBuffer buffer) { lock (lockObj) { bool b = filenameToBuffer.TryGetValue(buffer.Name, out var dictObj); Debug.Assert(b); if (!b) return; if (dictObj is WeakReference) { Debug.Assert(((WeakReference)dictObj).Target == buffer); return; } Debug.Assert(buffer == dictObj); filenameToBuffer[buffer.Name] = new WeakReference(buffer); } } void OnDocumentDirty(HexBuffer buffer) { lock (lockObj) { bool b = filenameToBuffer.TryGetValue(buffer.Name, out var dictObj); Debug.Assert(b); if (!b) return; filenameToBuffer[buffer.Name] = buffer; } } HexBuffer[] IHexBufferService.Clear() { object[] objs; lock (lockObj) { objs = filenameToBuffer.Values.ToArray(); filenameToBuffer.Clear(); } var buffersToDispose = new List(objs.Length); foreach (var obj in objs) { var buffer = TryGetBuffer(obj); if (buffer is not null) buffersToDispose.Add(buffer); } foreach (var lz in hexBufferServiceListeners) lz.Value.BuffersCleared(buffersToDispose); return buffersToDispose.ToArray(); } HexBuffer? IHexBufferService.TryGet(string filename) { filename = GetFullPath(filename); lock (lockObj) return TryGet_NoLock(filename); } HexBuffer? TryGet_NoLock(string filename) { if (!filenameToBuffer.TryGetValue(filename, out var obj)) return null; return TryGetBuffer(obj); } HexBuffer? TryGetBuffer(object obj) { if (obj is HexBuffer buffer) return buffer; var weakRef = obj as WeakReference; Debug2.Assert(weakRef is not null); return weakRef?.Target as HexBuffer; } HexBuffer? GetOrCreate(string filename) { if (!File.Exists(filename)) return null; filename = GetFullPath(filename); HexBuffer? buffer; lock (lockObj) { buffer = TryGet_NoLock(filename); if (buffer is not null) return buffer; byte[] data; try { data = File.ReadAllBytes(filename); } catch { return null; } buffer = hexBufferFactoryService.Create(data, filename, hexBufferFactoryService.DefaultFileTags); filenameToBuffer[filename] = new WeakReference(buffer); } return NotifyBufferCreated(buffer); } HexBuffer NotifyBufferCreated(HexBuffer buffer) { foreach (var lz in hexBufferServiceListeners) lz.Value.BufferCreated(buffer); return buffer; } HexBuffer GetOrCreate(IPEImage peImage) { var filename = GetFullPath(peImage.Filename); HexBuffer? buffer; lock (lockObj) { buffer = TryGet_NoLock(filename); if (buffer is not null) return buffer; buffer = hexBufferFactoryService.Create(peImage.CreateReader().ToArray(), filename, hexBufferFactoryService.DefaultFileTags); filenameToBuffer[filename] = new WeakReference(buffer); } return NotifyBufferCreated(buffer); } HexBuffer IHexBufferService.GetOrCreate(IPEImage peImage) => GetOrCreate(peImage); HexBuffer? IHexBufferService.GetOrCreate(string filename) => GetOrCreate(filename); HexBuffer[] IHexBufferService.GetBuffers() { lock (lockObj) return filenameToBuffer.Values.Select(a => TryGetBuffer(a)).OfType().ToArray(); } static string GetFullPath(string filename) { if (!File.Exists(filename)) return filename ?? string.Empty; try { return Path.GetFullPath(filename); } catch (ArgumentException) { } catch (IOException) { } catch (SecurityException) { } catch (NotSupportedException) { } return filename; } } }