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

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

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

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

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

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

@ -357,6 +357,7 @@ @@ -357,6 +357,7 @@
<Compile Include="Src\Services\DisplayBinding\ShellExecuteDisplayBinding.cs" />
<Compile Include="Src\Services\File\FileChangeWatcher.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\IMessageLoop.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 @@ -572,8 +572,17 @@ namespace ICSharpCode.SharpDevelop.Project
return false;
}
Properties projectSpecificProperties = new Properties();
[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)
{

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

@ -0,0 +1,49 @@ @@ -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 @@ -269,6 +269,13 @@ namespace ICSharpCode.SharpDevelop.Parser
/// </summary>
event EventHandler<ParseInformationEventArgs> ParseInformationUpdated;
#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

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

@ -5,12 +5,15 @@ using System; @@ -5,12 +5,15 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using ICSharpCode.NRefactory.Utils;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
@ -33,6 +36,8 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -33,6 +36,8 @@ namespace ICSharpCode.SharpDevelop.Parser
// time necessary for loading references, in relation to time for a single C# file
const int LoadingReferencesWorkAmount = 15;
string cacheFileName;
public ParseProjectContentContainer(MSBuildBasedProject project, IProjectContent initialProjectContent)
{
if (project == null)
@ -40,6 +45,8 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -40,6 +45,8 @@ namespace ICSharpCode.SharpDevelop.Parser
this.project = project;
this.projectContent = initialProjectContent.SetAssemblyName(project.AssemblyName);
this.cacheFileName = GetCacheFileName(FileName.Create(project.FileName));
ProjectService.ProjectItemAdded += OnProjectItemAdded;
ProjectService.ProjectItemRemoved += OnProjectItemRemoved;
@ -70,8 +77,104 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -70,8 +77,104 @@ namespace ICSharpCode.SharpDevelop.Parser
foreach (var parsedFile in projectContent.Files) {
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 {
get {
lock (lockObj) {
@ -120,6 +223,7 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -120,6 +223,7 @@ namespace ICSharpCode.SharpDevelop.Parser
void ParseFiles(IReadOnlyList<FileName> filesToParse, IProgressMonitor progressMonitor)
{
IProjectContent cachedPC = TryReadFromCache(cacheFileName);
ParseableFileContentFinder finder = new ParseableFileContentFinder();
object progressLock = new object();
@ -131,9 +235,26 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -131,9 +235,26 @@ namespace ICSharpCode.SharpDevelop.Parser
CancellationToken = progressMonitor.CancellationToken
},
fileName => {
ITextSource content = finder.Create(fileName);
if (content != null) {
SD.ParserService.ParseFile(fileName, content, project);
ITextSource content = finder.CreateForOpenFile(fileName);
bool wasLoadedFromCache = false;
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) {
progressMonitor.Progress += fileCountInverse;

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

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

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

@ -140,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -140,7 +140,7 @@ namespace ICSharpCode.SharpDevelop.Parser
if (pc != null)
return pc;
LoggingService.Debug("Loading " + fileName);
//LoggingService.Debug("Loading " + fileName);
cancellationToken.ThrowIfCancellationRequested();
var param = new ReaderParameters();
param.AssemblyResolver = new DummyAssemblyResolver();
@ -246,16 +246,15 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -246,16 +246,15 @@ namespace ICSharpCode.SharpDevelop.Parser
{
if (cacheFileName == null || !File.Exists(cacheFileName))
return null;
LoggingService.Debug("Deserializing " + cacheFileName);
//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)) {
if (reader.ReadInt64() != lastWriteTime.Ticks) {
LoggingService.Debug("Timestamp mismatch, deserialization aborted.");
LoggingService.Debug("Timestamp mismatch, deserialization aborted. (" + cacheFileName + ")");
return null;
}
FastSerializer s = new FastSerializer();
s.SerializationBinder = new MySerializationBinder();
return (IUnresolvedAssembly)s.Deserialize(reader);
}
}
@ -282,7 +281,6 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -282,7 +281,6 @@ namespace ICSharpCode.SharpDevelop.Parser
using (BinaryWriter writer = new BinaryWriterWith7BitEncodedInts(fs)) {
writer.Write(lastWriteTime.Ticks);
FastSerializer s = new FastSerializer();
s.SerializationBinder = new MySerializationBinder();
s.Serialize(writer, pc);
}
}
@ -295,33 +293,6 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -295,33 +293,6 @@ namespace ICSharpCode.SharpDevelop.Parser
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
}
}

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

@ -360,5 +360,10 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -360,5 +360,10 @@ namespace ICSharpCode.SharpDevelop.Parser
{
// 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 @@ -221,6 +221,9 @@ namespace ICSharpCode.SharpDevelop.Parser
throw new NullReferenceException(parser.GetType().Name + ".Parse() returned null");
if (fullParseInformationRequested && !parseInfo.IsFullParseInformation)
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);
results[i] = new ParseInformationEventArgs(entries[i].Project, entries[i].ParsedFile, parseInfo);
}
@ -326,5 +329,25 @@ namespace ICSharpCode.SharpDevelop.Parser @@ -326,5 +329,25 @@ namespace ICSharpCode.SharpDevelop.Parser
return task;
}
#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 @@ @@ -138,6 +138,8 @@
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="ProfilingSessions\Session20120329_181957.sdps" />
<Content Include="ProfilingSessions\Session20120329_182016.sdps" />
<ProjectReference Include="..\..\Libraries\AvalonDock\AvalonDock\AvalonDock.csproj">
<Project>{2FF700C2-A38A-48BD-A637-8CAFD4FE6237}</Project>
<Name>AvalonDock</Name>

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

@ -118,7 +118,10 @@ namespace ICSharpCode.SharpDevelop.Workbench @@ -118,7 +118,10 @@ namespace ICSharpCode.SharpDevelop.Workbench
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

Loading…
Cancel
Save