Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtualized filesystem access #24

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions SassAndCoffee.AspNet/CompilableFileHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace SassAndCoffee.AspNet
{
using System;
using System.IO;
using System.Web;

using SassAndCoffee.Core;
Expand All @@ -25,16 +24,8 @@ public bool IsReusable {

public void ProcessRequest(HttpContext context)
{
var fi = new FileInfo(context.Request.PhysicalPath);
var requestedFileName = fi.FullName;

if (fi.Exists) {
BuildHeaders(context.Response, _contentCompiler.GetOutputMimeType(requestedFileName), fi.LastWriteTimeUtc);
context.Response.WriteFile(requestedFileName);
return;
}

var compilationResult = _contentCompiler.GetCompiledContent(context.Request.Path);
VirtualPathCompilerFile file = new VirtualPathCompilerFile(context.Request.Path);
var compilationResult = _contentCompiler.GetCompiledContent(file);
if (compilationResult.Compiled == false) {
context.Response.StatusCode = 404;
return;
Expand All @@ -49,7 +40,7 @@ static void BuildHeaders(HttpResponse response, string mimeType, DateTime lastMo
response.StatusCode = 200;
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
response.AddHeader("content-encoding", "gzip");
response.Cache.VaryByHeaders["Accept-encoding"] = true;
response.Cache.VaryByHeaders["Accept-Encoding"] = true;
response.AddHeader("ETag", lastModified.Ticks.ToString("x"));
response.AddHeader("Content-Type", mimeType);
response.AddHeader("Content-Disposition", "inline");
Expand Down
26 changes: 11 additions & 15 deletions SassAndCoffee.AspNet/CompilableFileModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace SassAndCoffee.AspNet
using SassAndCoffee.Core.Caching;
using System.Configuration;

public class CompilableFileModule : IHttpModule, ICompilerHost
public class CompilableFileModule : IHttpModule
{
IContentCompiler _compiler;
IHttpHandler _handler;
Expand All @@ -29,7 +29,7 @@ public void Init(HttpApplication context)
_compiler = _compiler ?? initializeCompilerFromSettings(cacheType);
_handler = _handler ?? new CompilableFileHandler(_compiler);

if (!_compiler.CanCompile(app.Request.Path)) {
if (!_compiler.CanCompile(new VirtualPathCompilerFile(app.Request.Path))) {
return;
}

Expand All @@ -42,23 +42,19 @@ public string MapPath(string path)
return HttpContext.Current.Server.MapPath(path);
}

IContentCompiler initializeCompilerFromSettings(string cacheType)
{
IContentCompiler initializeCompilerFromSettings(string cacheType) {
if (string.Equals(cacheType, "NoCache", System.StringComparison.InvariantCultureIgnoreCase)) {
// NoCache
return new ContentCompiler(this, new NoCache());
} else if (string.Equals(cacheType, "InMemoryCache", System.StringComparison.InvariantCultureIgnoreCase)) {
return new ContentCompiler(new NoCache());
}
if (string.Equals(cacheType, "InMemoryCache", System.StringComparison.InvariantCultureIgnoreCase)) {
// InMemoryCache
return new ContentCompiler(this, new InMemoryCache());
} else {
// FileCache
var cachePath = Path.Combine(HostingEnvironment.MapPath("~/App_Data"), "_FileCache");
if (!Directory.Exists(cachePath)) {
Directory.CreateDirectory(cachePath);
}

return new ContentCompiler(this, new FileCache(cachePath));
return new ContentCompiler(new InMemoryCache());
}
// FileCache
var cachePath = Path.Combine(HostingEnvironment.MapPath("~/App_Data"), "_FileCache");
Directory.CreateDirectory(cachePath);
return new ContentCompiler(new FileCache(cachePath));
}

public void Dispose()
Expand Down
1 change: 1 addition & 0 deletions SassAndCoffee.AspNet/SassAndCoffee.AspNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<Compile Include="CompilableFileHandler.cs" />
<Compile Include="CompilableFileModule.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VirtualPathCompilerFile.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SassAndCoffee.Core\SassAndCoffee.Core.csproj">
Expand Down
58 changes: 58 additions & 0 deletions SassAndCoffee.AspNet/VirtualPathCompilerFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.IO;
using System.Web.Hosting;

using SassAndCoffee.Core;

namespace SassAndCoffee.AspNet {
internal class VirtualPathCompilerFile: ICompilerFile {
private static readonly DateTime unknownFileTime = DateTime.UtcNow;

private readonly string virtualPath;

public VirtualPathCompilerFile(string virtualPath) {
if (string.IsNullOrEmpty(virtualPath)) {
throw new ArgumentNullException("virtualPath");
}
this.virtualPath = virtualPath;
}

public DateTime LastWriteTimeUtc {
get {
if (!Exists) {
return default(DateTime);
}
using (Stream stream = Open()) {
FileStream file = stream as FileStream;
if (file != null) {
return File.GetLastWriteTimeUtc(file.Name);
}
}
// if the stream is not a file stream, we cannot determine the last write time and take the app startup time instead to avoid caching issues
return unknownFileTime;
}
}

public Stream Open()
{
return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open();
}

public string Name {
get {
return virtualPath;
}
}

public bool Exists {
get {
return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
}
}

public ICompilerFile GetRelativeFile(string relativePath)
{
return new VirtualPathCompilerFile(HostingEnvironment.VirtualPathProvider.CombineVirtualPaths(virtualPath, relativePath.Replace('\\', '/')));
}
}
}
1 change: 1 addition & 0 deletions SassAndCoffee.Core.Tests/SassAndCoffee.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SassFileCompilerTest.cs" />
<Compile Include="MinifyingCompilerTest.cs" />
<Compile Include="TestCompilerFile.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
9 changes: 1 addition & 8 deletions SassAndCoffee.Core.Tests/SassFileCompilerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,8 @@ string compileInput(string filename, string input)
{
var fixture = new SassFileCompiler();

using(var of = File.CreateText(filename)) {
of.WriteLine(input);
}

try {

// TODO: Fix this
// fixture.Init(TODO);
string result = fixture.ProcessFileContent(filename);
string result = fixture.ProcessFileContent(new TestCompilerFile(filename, input));
Console.WriteLine(result);
return result;
} finally {
Expand Down
59 changes: 59 additions & 0 deletions SassAndCoffee.Core.Tests/TestCompilerFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.IO;

namespace SassAndCoffee.Core.Tests {
internal class TestCompilerFile: ICompilerFile {
private readonly string _fileName;
private readonly string _content;
private readonly DateTime _lastWriteTimeUtc;

public TestCompilerFile(string fileName, string content) {
this._fileName = fileName;
this._content = content;
_lastWriteTimeUtc = DateTime.UtcNow;
}

public DateTime LastWriteTimeUtc {
get {
AssertFileExists();
return _lastWriteTimeUtc;
}
}

public Stream Open()
{
AssertFileExists();
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(_content);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}

private void AssertFileExists()
{
if (!Exists) {
throw new FileNotFoundException();
}
}

public string Name {
get {
return _fileName;
}
}

public bool Exists {
get {
return _content != null;
}
}

public ICompilerFile GetRelativeFile(string relativePath)
{
// not really canonicalizing the real path, but that's not needed here
return new TestCompilerFile(relativePath, null);
}
}
}
7 changes: 3 additions & 4 deletions SassAndCoffee.Core/Caching/FileCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ public FileCache(string basePath)
public CompilationResult GetOrAdd(string filename, Func<string, CompilationResult> compilationDelegate, string mimeType)
{
var outputFileName = Path.Combine(_basePath, filename);
FileInfo fi;
FileInfo fi = new FileInfo(outputFileName);

if (File.Exists(outputFileName)) {
fi = new FileInfo(outputFileName);
if (fi.Exists) {
return new CompilationResult(true, File.ReadAllText(outputFileName), mimeType, fi.LastWriteTimeUtc);
}

Expand All @@ -32,7 +31,7 @@ public CompilationResult GetOrAdd(string filename, Func<string, CompilationResul
File.WriteAllText(outputFileName, result.Contents);

// XXX: Is this needed?
fi = new FileInfo(outputFileName);
fi.Refresh();
fi.LastWriteTimeUtc = result.SourceLastModifiedUtc;
} catch (IOException) {
// NB: If we get here, this means that two threads are trying to
Expand Down
28 changes: 13 additions & 15 deletions SassAndCoffee.Core/Compilers/CoffeeScriptFileCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace SassAndCoffee.Core.Compilers
{
using System.IO;
using System;
using System.Collections.Generic;

using SassAndCoffee.Core.Extensions;

namespace SassAndCoffee.Core.Compilers
{
// NB: This class seems stupid, but it makes it easier for other projects
// to reuse the CoffeeScript compilation bits without committing to the caching
// logic
Expand All @@ -12,10 +15,10 @@ public CoffeeScriptCompiler() : base("SassAndCoffee.Core.lib.coffee-script.js",

public class CoffeeScriptFileCompiler : ISimpleFileCompiler
{
CoffeeScriptCompiler _engine;
private readonly Lazy<CoffeeScriptCompiler> _engine;

public string[] InputFileExtensions {
get { return new[] { ".coffee" }; }
public IEnumerable<string> InputFileExtensions {
get { yield return ".coffee"; }
}

public string OutputFileExtension {
Expand All @@ -28,20 +31,15 @@ public string OutputMimeType {

public CoffeeScriptFileCompiler(CoffeeScriptCompiler engine = null)
{
_engine = engine;
}

public void Init(ICompilerHost host)
{
_engine = _engine ?? new CoffeeScriptCompiler();
_engine = new Lazy<CoffeeScriptCompiler>(() => engine ?? new CoffeeScriptCompiler());
}

public string ProcessFileContent(string inputFileContent)
public string ProcessFileContent(ICompilerFile inputFileContent)
{
return _engine.Compile(File.ReadAllText(inputFileContent));
return _engine.Value.Compile(inputFileContent.ReadAllText());
}

public string GetFileChangeToken(string inputFileContent)
public string GetFileChangeToken(ICompilerFile inputFileContent)
{
return "";
}
Expand Down
Loading