Browse Source

Add support for caching project contents on disk.

newNRvisualizers
Daniel Grunwald 14 years ago
parent
commit
326283c975
  1. 5
      src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlParsedFile.cs
  2. 4
      src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpParsedFile.cs
  3. 2
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IParsedFile.cs
  4. 1
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  5. 11
      src/Main/Base/Project/Src/Project/AbstractProject.cs
  6. 49
      src/Main/Base/Project/Src/Services/File/OnDiskTextSourceVersion.cs
  7. 7
      src/Main/Base/Project/Src/Services/ParserService/IParserService.cs
  8. 127
      src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs
  9. 18
      src/Main/Base/Project/Src/Services/ProjectService/ParseableFileContentFinder.cs
  10. 35
      src/Main/SharpDevelop/Parser/AssemblyParserService.cs
  11. 5
      src/Main/SharpDevelop/Parser/ParserService.cs
  12. 23
      src/Main/SharpDevelop/Parser/ParserServiceEntry.cs
  13. 2
      src/Main/SharpDevelop/SharpDevelop.csproj
  14. 5
      src/Main/SharpDevelop/Workbench/FileService.cs

5
src/AddIns/BackendBindings/XamlBinding/XamlBinding/XamlParsedFile.cs

@ -53,10 +53,11 @@ namespace ICSharpCode.XamlBinding
get { return fileName; } get { return fileName; }
} }
DateTime lastWriteTime = DateTime.UtcNow; DateTime? lastWriteTime;
public DateTime LastWriteTime { public DateTime? LastWriteTime {
get { return lastWriteTime; } get { return lastWriteTime; }
set { lastWriteTime = value; }
} }
public IList<IUnresolvedTypeDefinition> TopLevelTypeDefinitions { public IList<IUnresolvedTypeDefinition> TopLevelTypeDefinitions {

4
src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp/TypeSystem/CSharpParsedFile.cs

@ -73,9 +73,9 @@ namespace ICSharpCode.NRefactory.CSharp.TypeSystem
get { return fileName; } get { return fileName; }
} }
DateTime lastWriteTime = DateTime.UtcNow; DateTime? lastWriteTime;
public DateTime LastWriteTime { public DateTime? LastWriteTime {
get { return lastWriteTime; } get { return lastWriteTime; }
set { set {
FreezableHelper.ThrowIfFrozen(this); FreezableHelper.ThrowIfFrozen(this);

2
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/IParsedFile.cs

@ -34,7 +34,7 @@ namespace ICSharpCode.NRefactory.TypeSystem
/// <summary> /// <summary>
/// Gets the time when the file was last written. /// Gets the time when the file was last written.
/// </summary> /// </summary>
DateTime LastWriteTime { get; } DateTime? LastWriteTime { get; set; }
/// <summary> /// <summary>
/// Gets all top-level type definitions. /// Gets all top-level type definitions.

1
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -357,6 +357,7 @@
<Compile Include="Src\Services\DisplayBinding\ShellExecuteDisplayBinding.cs" /> <Compile Include="Src\Services\DisplayBinding\ShellExecuteDisplayBinding.cs" />
<Compile Include="Src\Services\File\FileChangeWatcher.cs" /> <Compile Include="Src\Services\File\FileChangeWatcher.cs" />
<Compile Include="Src\Services\File\IFileService.cs" /> <Compile Include="Src\Services\File\IFileService.cs" />
<Compile Include="Src\Services\File\OnDiskTextSourceVersion.cs" />
<Compile Include="Src\Services\File\OpenedFile.cs" /> <Compile Include="Src\Services\File\OpenedFile.cs" />
<Compile Include="Src\Services\IMessageLoop.cs" /> <Compile Include="Src\Services\IMessageLoop.cs" />
<Compile Include="Src\Services\LanguageBinding\AggregatedLanguageBinding.cs" /> <Compile Include="Src\Services\LanguageBinding\AggregatedLanguageBinding.cs" />

11
src/Main/Base/Project/Src/Project/AbstractProject.cs

@ -572,8 +572,17 @@ namespace ICSharpCode.SharpDevelop.Project
return false; return false;
} }
Properties projectSpecificProperties = new Properties();
[Browsable(false)] [Browsable(false)]
public Properties ProjectSpecificProperties { get; protected set; } public Properties ProjectSpecificProperties {
get { return projectSpecificProperties; }
set {
if (value == null)
throw new ArgumentNullException();
projectSpecificProperties = value;
}
}
public virtual string GetDefaultNamespace(string fileName) public virtual string GetDefaultNamespace(string fileName)
{ {

49
src/Main/Base/Project/Src/Services/File/OnDiskTextSourceVersion.cs

@ -0,0 +1,49 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Editor;
namespace ICSharpCode.SharpDevelop
{
/// <summary>
/// Signifies that the text source matches the version of the file on disk.
/// </summary>
public sealed class OnDiskTextSourceVersion : ITextSourceVersion
{
public readonly DateTime LastWriteTime;
public OnDiskTextSourceVersion(DateTime lastWriteTime)
{
this.LastWriteTime = lastWriteTime;
}
public bool BelongsToSameDocumentAs(ITextSourceVersion other)
{
return this == other;
}
public int CompareAge(ITextSourceVersion other)
{
if (this != other)
throw new ArgumentException("other belongs to different document");
return 0;
}
public IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other)
{
if (this != other)
throw new ArgumentException("other belongs to different document");
return EmptyList<TextChangeEventArgs>.Instance;
}
public int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement)
{
if (this != other)
throw new ArgumentException("other belongs to different document");
return oldOffset;
}
}
}

7
src/Main/Base/Project/Src/Services/ParserService/IParserService.cs

@ -269,6 +269,13 @@ namespace ICSharpCode.SharpDevelop.Parser
/// </summary> /// </summary>
event EventHandler<ParseInformationEventArgs> ParseInformationUpdated; event EventHandler<ParseInformationEventArgs> ParseInformationUpdated;
#endregion #endregion
/// <summary>
/// Registers parse information for the specified file.
/// The file must belong to the specified project, otherwise this method does nothing.
/// </summary>
/// <remarks>This method is intended for restoring parse information cached on disk.</remarks>
void RegisterParsedFile(FileName fileName, IProject project, IParsedFile parsedFile);
} }
public interface ILoadSolutionProjectsThread public interface ILoadSolutionProjectsThread

127
src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs

@ -5,12 +5,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.Editor;
using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.TypeSystem.Implementation;
using ICSharpCode.NRefactory.Utils;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Project;
@ -33,6 +36,8 @@ namespace ICSharpCode.SharpDevelop.Parser
// time necessary for loading references, in relation to time for a single C# file // time necessary for loading references, in relation to time for a single C# file
const int LoadingReferencesWorkAmount = 15; const int LoadingReferencesWorkAmount = 15;
string cacheFileName;
public ParseProjectContentContainer(MSBuildBasedProject project, IProjectContent initialProjectContent) public ParseProjectContentContainer(MSBuildBasedProject project, IProjectContent initialProjectContent)
{ {
if (project == null) if (project == null)
@ -40,6 +45,8 @@ namespace ICSharpCode.SharpDevelop.Parser
this.project = project; this.project = project;
this.projectContent = initialProjectContent.SetAssemblyName(project.AssemblyName); this.projectContent = initialProjectContent.SetAssemblyName(project.AssemblyName);
this.cacheFileName = GetCacheFileName(FileName.Create(project.FileName));
ProjectService.ProjectItemAdded += OnProjectItemAdded; ProjectService.ProjectItemAdded += OnProjectItemAdded;
ProjectService.ProjectItemRemoved += OnProjectItemRemoved; ProjectService.ProjectItemRemoved += OnProjectItemRemoved;
@ -70,8 +77,104 @@ namespace ICSharpCode.SharpDevelop.Parser
foreach (var parsedFile in projectContent.Files) { foreach (var parsedFile in projectContent.Files) {
SD.ParserService.RemoveOwnerProject(FileName.Create(parsedFile.FileName), project); SD.ParserService.RemoveOwnerProject(FileName.Create(parsedFile.FileName), project);
} }
var pc = projectContent;
Task.Run(
delegate {
pc = pc.RemoveAssemblyReferences(pc.AssemblyReferences);
int serializableFileCount = 0;
List<IParsedFile> nonSerializableParsedFiles = new List<IParsedFile>();
foreach (var parsedFile in pc.Files) {
if (!parsedFile.GetType().IsSerializable || parsedFile.LastWriteTime == default(DateTime))
nonSerializableParsedFiles.Add(parsedFile);
else
serializableFileCount++;
}
// remove non-serializable parsed files
if (nonSerializableParsedFiles.Count > 0)
pc = pc.UpdateProjectContent(nonSerializableParsedFiles, null);
if (serializableFileCount > 3)
SaveToCache(cacheFileName, pc);
else
RemoveCache(cacheFileName);
}).FireAndForget();
}
#region Caching logic (serialization)
static string GetCacheFileName(FileName projectFileName)
{
string persistencePath = SD.AssemblyParserService.DomPersistencePath;
if (persistencePath == null)
return null;
string cacheFileName = Path.GetFileNameWithoutExtension(projectFileName);
if (cacheFileName.Length > 32)
cacheFileName = cacheFileName.Substring(cacheFileName.Length - 32); // use 32 last characters
cacheFileName = Path.Combine(persistencePath, cacheFileName + "." + projectFileName.GetHashCode().ToString("x8") + ".prj");
return cacheFileName;
}
static IProjectContent TryReadFromCache(string cacheFileName)
{
if (cacheFileName == null || !File.Exists(cacheFileName))
return null;
LoggingService.Debug("Deserializing " + cacheFileName);
try {
using (FileStream fs = new FileStream(cacheFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.SequentialScan)) {
using (BinaryReader reader = new BinaryReaderWith7BitEncodedInts(fs)) {
FastSerializer s = new FastSerializer();
return (IProjectContent)s.Deserialize(reader);
}
}
} catch (IOException ex) {
LoggingService.Warn(ex);
return null;
} catch (UnauthorizedAccessException ex) {
LoggingService.Warn(ex);
return null;
} catch (SerializationException ex) {
LoggingService.Warn(ex);
return null;
}
} }
static void SaveToCache(string cacheFileName, IProjectContent pc)
{
if (cacheFileName == null)
return;
LoggingService.Debug("Serializing to " + cacheFileName);
try {
Directory.CreateDirectory(Path.GetDirectoryName(cacheFileName));
using (FileStream fs = new FileStream(cacheFileName, FileMode.Create, FileAccess.Write)) {
using (BinaryWriter writer = new BinaryWriterWith7BitEncodedInts(fs)) {
FastSerializer s = new FastSerializer();
s.Serialize(writer, pc);
}
}
} catch (IOException ex) {
LoggingService.Warn(ex);
// Can happen if two SD instances are trying to access the file at the same time.
// We'll just let one of them win, and instance that got the exception won't write to the cache at all.
// Similarly, we also ignore the other kinds of IO exceptions.
} catch (UnauthorizedAccessException ex) {
LoggingService.Warn(ex);
}
}
void RemoveCache(string cacheFileName)
{
if (cacheFileName == null)
return;
try {
File.Delete(cacheFileName);
} catch (IOException ex) {
LoggingService.Warn(ex);
} catch (UnauthorizedAccessException ex) {
LoggingService.Warn(ex);
}
}
#endregion
public IProjectContent ProjectContent { public IProjectContent ProjectContent {
get { get {
lock (lockObj) { lock (lockObj) {
@ -120,6 +223,7 @@ namespace ICSharpCode.SharpDevelop.Parser
void ParseFiles(IReadOnlyList<FileName> filesToParse, IProgressMonitor progressMonitor) void ParseFiles(IReadOnlyList<FileName> filesToParse, IProgressMonitor progressMonitor)
{ {
IProjectContent cachedPC = TryReadFromCache(cacheFileName);
ParseableFileContentFinder finder = new ParseableFileContentFinder(); ParseableFileContentFinder finder = new ParseableFileContentFinder();
object progressLock = new object(); object progressLock = new object();
@ -131,9 +235,26 @@ namespace ICSharpCode.SharpDevelop.Parser
CancellationToken = progressMonitor.CancellationToken CancellationToken = progressMonitor.CancellationToken
}, },
fileName => { fileName => {
ITextSource content = finder.Create(fileName); ITextSource content = finder.CreateForOpenFile(fileName);
if (content != null) { bool wasLoadedFromCache = false;
SD.ParserService.ParseFile(fileName, content, project); if (content == null && cachedPC != null) {
IParsedFile parsedFile = cachedPC.GetFile(fileName);
if (parsedFile != null && parsedFile.LastWriteTime == File.GetLastWriteTimeUtc(fileName)) {
SD.ParserService.RegisterParsedFile(fileName, project, parsedFile);
wasLoadedFromCache = true;
}
}
if (!wasLoadedFromCache) {
if (content == null) {
try {
content = SD.FileService.GetFileContentFromDisk(fileName);
} catch (IOException) {
} catch (UnauthorizedAccessException) {
}
}
if (content != null) {
SD.ParserService.ParseFile(fileName, content, project);
}
} }
lock (progressLock) { lock (progressLock) {
progressMonitor.Progress += fileCountInverse; progressMonitor.Progress += fileCountInverse;

18
src/Main/Base/Project/Src/Services/ProjectService/ParseableFileContentFinder.cs

@ -19,17 +19,25 @@ namespace ICSharpCode.SharpDevelop.Project
{ {
FileName[] viewContentFileNamesCollection = SD.MainThread.InvokeIfRequired(() => SD.FileService.OpenedFiles.Select(f => f.FileName).ToArray()); FileName[] viewContentFileNamesCollection = SD.MainThread.InvokeIfRequired(() => SD.FileService.OpenedFiles.Select(f => f.FileName).ToArray());
public ITextSource CreateForOpenFile(FileName fileName)
{
foreach (FileName name in viewContentFileNamesCollection) {
if (FileUtility.IsEqualFileName(name, fileName))
return SD.FileService.GetFileContentForOpenFile(fileName);
}
return null;
}
/// <summary> /// <summary>
/// Retrieves the file contents for the specified project items. /// Retrieves the file contents for the specified project items.
/// </summary> /// </summary>
public ITextSource Create(FileName fileName) public ITextSource Create(FileName fileName)
{ {
foreach (FileName name in viewContentFileNamesCollection) { ITextSource textSource = CreateForOpenFile(fileName);
if (FileUtility.IsEqualFileName(name, fileName)) if (textSource != null)
return SD.FileService.GetFileContent(fileName); return textSource;
}
try { try {
return new StringTextSource(ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(fileName, SD.FileService.DefaultFileEncoding)); return SD.FileService.GetFileContentFromDisk(fileName);
} catch (IOException) { } catch (IOException) {
return null; return null;
} catch (UnauthorizedAccessException) { } catch (UnauthorizedAccessException) {

35
src/Main/SharpDevelop/Parser/AssemblyParserService.cs

@ -140,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Parser
if (pc != null) if (pc != null)
return pc; return pc;
LoggingService.Debug("Loading " + fileName); //LoggingService.Debug("Loading " + fileName);
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var param = new ReaderParameters(); var param = new ReaderParameters();
param.AssemblyResolver = new DummyAssemblyResolver(); param.AssemblyResolver = new DummyAssemblyResolver();
@ -246,16 +246,15 @@ namespace ICSharpCode.SharpDevelop.Parser
{ {
if (cacheFileName == null || !File.Exists(cacheFileName)) if (cacheFileName == null || !File.Exists(cacheFileName))
return null; return null;
LoggingService.Debug("Deserializing " + cacheFileName); //LoggingService.Debug("Deserializing " + cacheFileName);
try { try {
using (FileStream fs = new FileStream(cacheFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.SequentialScan)) { using (FileStream fs = new FileStream(cacheFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.SequentialScan)) {
using (BinaryReader reader = new BinaryReaderWith7BitEncodedInts(fs)) { using (BinaryReader reader = new BinaryReaderWith7BitEncodedInts(fs)) {
if (reader.ReadInt64() != lastWriteTime.Ticks) { if (reader.ReadInt64() != lastWriteTime.Ticks) {
LoggingService.Debug("Timestamp mismatch, deserialization aborted."); LoggingService.Debug("Timestamp mismatch, deserialization aborted. (" + cacheFileName + ")");
return null; return null;
} }
FastSerializer s = new FastSerializer(); FastSerializer s = new FastSerializer();
s.SerializationBinder = new MySerializationBinder();
return (IUnresolvedAssembly)s.Deserialize(reader); return (IUnresolvedAssembly)s.Deserialize(reader);
} }
} }
@ -282,7 +281,6 @@ namespace ICSharpCode.SharpDevelop.Parser
using (BinaryWriter writer = new BinaryWriterWith7BitEncodedInts(fs)) { using (BinaryWriter writer = new BinaryWriterWith7BitEncodedInts(fs)) {
writer.Write(lastWriteTime.Ticks); writer.Write(lastWriteTime.Ticks);
FastSerializer s = new FastSerializer(); FastSerializer s = new FastSerializer();
s.SerializationBinder = new MySerializationBinder();
s.Serialize(writer, pc); s.Serialize(writer, pc);
} }
} }
@ -295,33 +293,6 @@ namespace ICSharpCode.SharpDevelop.Parser
LoggingService.Warn(ex); LoggingService.Warn(ex);
} }
} }
sealed class MySerializationBinder : SerializationBinder
{
public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
if (serializedType.Assembly == typeof(IProjectContent).Assembly) {
assemblyName = "NRefactory";
} else {
assemblyName = serializedType.Assembly.FullName;
}
typeName = serializedType.FullName;
}
public override Type BindToType(string assemblyName, string typeName)
{
Assembly asm;
switch (assemblyName) {
case "NRefactory":
asm = typeof(IProjectContent).Assembly;
break;
default:
asm = Assembly.Load(assemblyName);
break;
}
return asm.GetType(typeName);
}
}
#endregion #endregion
} }
} }

5
src/Main/SharpDevelop/Parser/ParserService.cs

@ -360,5 +360,10 @@ namespace ICSharpCode.SharpDevelop.Parser
{ {
// TODO // TODO
} }
public void RegisterParsedFile(FileName fileName, IProject project, IParsedFile parsedFile)
{
GetFileEntry(fileName, true).RegisterParsedFile(project, parsedFile);
}
} }
} }

23
src/Main/SharpDevelop/Parser/ParserServiceEntry.cs

@ -221,6 +221,9 @@ namespace ICSharpCode.SharpDevelop.Parser
throw new NullReferenceException(parser.GetType().Name + ".Parse() returned null"); throw new NullReferenceException(parser.GetType().Name + ".Parse() returned null");
if (fullParseInformationRequested && !parseInfo.IsFullParseInformation) if (fullParseInformationRequested && !parseInfo.IsFullParseInformation)
throw new InvalidOperationException(parser.GetType().Name + ".Parse() did not return full parse info as requested."); throw new InvalidOperationException(parser.GetType().Name + ".Parse() did not return full parse info as requested.");
OnDiskTextSourceVersion onDiskVersion = fileContent.Version as OnDiskTextSourceVersion;
if (onDiskVersion != null)
parseInfo.ParsedFile.LastWriteTime = onDiskVersion.LastWriteTime;
FreezableHelper.Freeze(parseInfo.ParsedFile); FreezableHelper.Freeze(parseInfo.ParsedFile);
results[i] = new ParseInformationEventArgs(entries[i].Project, entries[i].ParsedFile, parseInfo); results[i] = new ParseInformationEventArgs(entries[i].Project, entries[i].ParsedFile, parseInfo);
} }
@ -326,5 +329,25 @@ namespace ICSharpCode.SharpDevelop.Parser
return task; return task;
} }
#endregion #endregion
public void RegisterParsedFile(IProject project, IParsedFile parsedFile)
{
if (project == null)
throw new ArgumentNullException("project");
if (parsedFile == null)
throw new ArgumentNullException("parsedFile");
FreezableHelper.Freeze(parsedFile);
var newParseInfo = new ParseInformation(parsedFile, false);
lock (this) {
int index = FindIndexForProject(project);
if (index >= 0) {
currentVersion = null;
var args = new ParseInformationEventArgs(project, entries[index].ParsedFile, newParseInfo);
entries[index] = new ProjectEntry(project, parsedFile, null);
project.OnParseInformationUpdated(args);
parserService.RaiseParseInformationUpdated(args);
}
}
}
} }
} }

2
src/Main/SharpDevelop/SharpDevelop.csproj

@ -138,6 +138,8 @@
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="ProfilingSessions\Session20120329_181957.sdps" />
<Content Include="ProfilingSessions\Session20120329_182016.sdps" />
<ProjectReference Include="..\..\Libraries\AvalonDock\AvalonDock\AvalonDock.csproj"> <ProjectReference Include="..\..\Libraries\AvalonDock\AvalonDock\AvalonDock.csproj">
<Project>{2FF700C2-A38A-48BD-A637-8CAFD4FE6237}</Project> <Project>{2FF700C2-A38A-48BD-A637-8CAFD4FE6237}</Project>
<Name>AvalonDock</Name> <Name>AvalonDock</Name>

5
src/Main/SharpDevelop/Workbench/FileService.cs

@ -118,7 +118,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
public ITextSource GetFileContentFromDisk(FileName fileName, CancellationToken cancellationToken) public ITextSource GetFileContentFromDisk(FileName fileName, CancellationToken cancellationToken)
{ {
return new StringTextSource(FileReader.ReadFileContent(fileName, DefaultFileEncoding)); cancellationToken.ThrowIfCancellationRequested();
string text = FileReader.ReadFileContent(fileName, DefaultFileEncoding);
DateTime lastWriteTime = File.GetLastWriteTimeUtc(fileName);
return new StringTextSource(text, new OnDiskTextSourceVersion(lastWriteTime));
} }
#endregion #endregion

Loading…
Cancel
Save