Skip to content

Commit

Permalink
icons, Page Title and slightly optimize docker image
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanBout committed Nov 27, 2024
1 parent d616091 commit e10712f
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 26 deletions.
5 changes: 4 additions & 1 deletion SimpleCDN.Tests/CDNLoaderTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SimpleCDN.Configuration;
using SimpleCDN.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -27,7 +28,9 @@ public class CDNLoaderTests

private static CDNLoader CreateLoader()
{
return new CDNLoader(new MockWebHostEnvironment(), new OptionsMock<CDNConfiguration>(new() { DataRoot = TempFolder }));
var options = new OptionsMock<CDNConfiguration>(new() { DataRoot = TempFolder });

return new CDNLoader(new MockWebHostEnvironment(), options, new IndexGenerator(options));
}

[SetUp]
Expand Down
62 changes: 47 additions & 15 deletions SimpleCDN/CDNLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,45 @@ public class CDNLoader(IWebHostEnvironment environment, IOptionsMonitor<CDNConfi
{
return null;
}
var mime = MimeTypeHelpers.MimeTypeFromFileName(info.PhysicalPath);

using var stream = info.CreateReadStream();
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);
var preCompressedPath = path + ".gz";

var mime = MimeTypeHelpers.MimeTypeFromFileName(info.PhysicalPath);
if (_environment.WebRootFileProvider.GetFileInfo(preCompressedPath) is { Exists: true } compressedFileInfo)
{
// file is pre-compressed using gzip

_cache[info.PhysicalPath] = new CachedFile
using var stream = compressedFileInfo.CreateReadStream();
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);

_cache[info.PhysicalPath] = new CachedFile
{
Content = bytes,
LastModified = info.LastModified,
Size = info.Length, // still use the length and mime type from the original file, not the compressed one
MimeType = mime,
IsCompressed = true
};

return new CDNFile(bytes, mime.ToContentTypeString(), info.LastModified, false);
} else
{
Content = bytes,
LastModified = info.LastModified,
MimeType = mime,
IsCompressed = false
};
using var stream = info.CreateReadStream();
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);

return new CDNFile(bytes, mime.ToContentTypeString(), info.LastModified, false);
_cache[info.PhysicalPath] = new CachedFile
{
Content = bytes,
LastModified = info.LastModified,
MimeType = mime,
Size = info.Length,
IsCompressed = false
};

return new CDNFile(bytes, mime.ToContentTypeString(), info.LastModified, false);
}
}

private CDNFile? LoadFile(string absolutePath, string rootRelativePath)
Expand All @@ -109,7 +132,8 @@ public class CDNLoader(IWebHostEnvironment environment, IOptionsMonitor<CDNConfi
{
Content = bytes,
DirectoryName = absolutePath,
MimeType = MimeType.HTML
MimeType = MimeType.HTML,
Size = content.content.Length
};
}
} else
Expand All @@ -124,7 +148,8 @@ public class CDNLoader(IWebHostEnvironment environment, IOptionsMonitor<CDNConfi
{
Content = content.content,
LastModified = lastModified,
MimeType = content.type
MimeType = content.type,
Size = content.content.Length
};

// attempt to compress the file if it's not already compressed
Expand All @@ -141,7 +166,12 @@ public class CDNLoader(IWebHostEnvironment environment, IOptionsMonitor<CDNConfi
return new CDNFile(file.Content, file.MimeType.ToContentTypeString(), file.LastModified, file.IsCompressed);
}


/// <summary>
/// Attempts to load an index file from the directory at <paramref name="absolutePath"/>. If no index file is found, generates one.
/// </summary>
/// <param name="absolutePath">The absolute path to the directory</param>
/// <param name="rootRelativePath">The path to the directory, how it was requested</param>
/// <returns></returns>
private (MimeType type, byte[]? content) TryLoadIndex(string absolutePath, string rootRelativePath)
{
if (!Directory.Exists(absolutePath)) return MimeTypeHelpers.Empty;
Expand All @@ -150,10 +180,12 @@ public class CDNLoader(IWebHostEnvironment environment, IOptionsMonitor<CDNConfi

foreach (var indexFile in indexes)
{
var substring = indexFile.AsSpan()[(indexFile.LastIndexOf('.') + 1)..];
// efficiently check if the file is an htm(l) file
var substring = indexFile.AsSpan()[(indexFile.LastIndexOf('.') + 1)..]; // + 1 to skip the dot

if (substring.SequenceEqual("html") || substring.SequenceEqual("htm"))
{
// if the file is an index file, load it
var loaded = LoadFileFromDisk(indexFile);

// check after loading to make sure the file wasn't deleted in the mean time
Expand Down
21 changes: 21 additions & 0 deletions SimpleCDN/Cache/CachedFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,28 @@
{
class CachedFile
{
private long _size;

public CachedFile() { }

public bool IsCompressed { get; set; } = false;
public required long Size
{
get
{
if (!IsCompressed || _size <= 0)
{
return Content.Length;
}

return _size;
}
set
{
_size = value;
}
}

public required byte[] Content { get; set; }
public required MimeType MimeType { get; set; }
public virtual DateTimeOffset LastModified { get; set; }
Expand Down
5 changes: 3 additions & 2 deletions SimpleCDN/Cache/SizeLimitedCache.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using SimpleCDN.Helpers;
using System.Collections;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
Expand All @@ -15,7 +16,7 @@ internal class SizeLimitedCache(long maxSize, IEqualityComparer<string>? compare
{
public SizeLimitedCache(long maxSize) : this(maxSize, null) { }

private readonly Dictionary<string, ValueWrapper> _dictionary = new(comparer);
private readonly ConcurrentDictionary<string, ValueWrapper> _dictionary = new(comparer);
private readonly long _maxSize = maxSize;

public CachedFile this[string key]
Expand Down Expand Up @@ -51,7 +52,7 @@ private void SetValue(string key, CachedFile value)
{
(var oldest, byOldest) = byOldest.RemoveFirst();

_dictionary.Remove(oldest.Key);
_dictionary.TryRemove(oldest.Key, out _);
}
}

Expand Down
1 change: 1 addition & 0 deletions SimpleCDN/Configuration/CDNConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public class CDNConfiguration
public uint MaxMemoryCacheSize { get; set; } = 500;

public string Footer { get; set; } = """<a href="https://github.com/jonathanbout/simplecdn">Powered by SimpleCDN</a>""";
public string PageTitle { get; set; } = "SimpleCDN";
}
}
19 changes: 12 additions & 7 deletions SimpleCDN/Helpers/IndexGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class IndexGenerator(IOptionsMonitor<CDNConfiguration> options)
<meta name="robots" content="noindex,nofollow">
<link rel="stylesheet" href="/_cdn/styles.css">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
<title>{1} &middot; Index of {2}</title>
</head>
<body>
<header>
Expand All @@ -34,12 +35,13 @@ public class IndexGenerator(IOptionsMonitor<CDNConfiguration> options)
<main>
<table>
<thead><tr>
<th></th>
<th>Name</th>
<th>Size</th>
<th>Last Modified (UTC)</th>
</tr></thead>
<tbody>
""", rootRelativePath.Replace("/", "<wbr>/"));
""", rootRelativePath.Replace("/", "<wbr>/"), _options.CurrentValue.PageTitle, rootRelativePath);

if (rootRelativePath is not "/" and not "" && directory.Parent is DirectoryInfo parent)
{
Expand All @@ -55,21 +57,21 @@ public class IndexGenerator(IOptionsMonitor<CDNConfiguration> options)
parentRootRelativePath = rootRelativePath[..lastSlashIndex];
}

AppendRow(index, parentRootRelativePath, "Parent Directory", -1, parent.LastWriteTimeUtc);
AppendRow(index, parentRootRelativePath, "Parent Directory", "parent", -1, parent.LastWriteTimeUtc);
}

foreach (var subDirectory in directory.EnumerateDirectories())
{
var name = subDirectory.Name;

AppendRow(index, Path.Combine(rootRelativePath, name), name, -1, subDirectory.LastWriteTimeUtc);
AppendRow(index, Path.Combine(rootRelativePath, name), name, "folder", -1, subDirectory.LastWriteTimeUtc);
}

foreach (var file in directory.EnumerateFiles())
{
var name = file.Name;

AppendRow(index, Path.Combine(rootRelativePath, name), name, file.Length, file.LastWriteTimeUtc);
AppendRow(index, Path.Combine(rootRelativePath, name), name, "file", file.Length, file.LastWriteTimeUtc);
}

index.AppendFormat("</tbody></table></main><footer>{0}</footer></body></html>", _options.CurrentValue.Footer);
Expand All @@ -79,11 +81,14 @@ public class IndexGenerator(IOptionsMonitor<CDNConfiguration> options)
return bytes;
}

private static void AppendRow(StringBuilder index, string href, string name, long size, DateTimeOffset lastModified)
private static void AppendRow(StringBuilder index, string href, string name, string icon, long size, DateTimeOffset lastModified)
{
index.AppendFormat("""<tr><td><a href="{0}">{1}</a></td>""", href, name);
index.Append("<tr>");
index.AppendFormat("""<td><img src="/_cdn/{0}.svg" alt="{0}"></img></td>""", icon);
index.AppendFormat("""<td><a href="{0}">{1}</a></td>""", href, name);
index.AppendFormat("""<td>{0}</td>""", size < 0 ? "-" : size.FormatByteCount());
index.AppendFormat("""<td>{0}</td></tr>""", lastModified.ToString("dd/MM/yyyy HH:mm"));
index.AppendFormat("""<td>{0}</td>""", lastModified.ToString("dd/MM/yyyy HH:mm"));
index.Append("</tr>");
}
}
}
5 changes: 5 additions & 0 deletions SimpleCDN/SimpleCDN.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<DebugType>none</DebugType>
</PropertyGroup>

<ItemGroup Condition="'$(Configuration)'=='Release'">
<Content Remove="wwwroot\index.html" />
<Content Remove="appsettings.Development.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
Expand Down
4 changes: 4 additions & 0 deletions SimpleCDN/wwwroot/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions SimpleCDN/wwwroot/folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions SimpleCDN/wwwroot/parent.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions SimpleCDN/wwwroot/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ thead th {

footer {
text-align: center;
}

img {
transform: translateY(0);
}

td:has(img) {
display: inline-flex;
align-items: center;
}
2 changes: 1 addition & 1 deletion docker-compose.dcproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<DockerPublishLocally>False</DockerPublishLocally>
<ProjectGuid>81dded9d-158b-e303-5f62-77a2896d2a5a</ProjectGuid>
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}/weatherforecast</DockerServiceUrl>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
<DockerServiceName>simplecdn</DockerServiceName>
</PropertyGroup>
<ItemGroup>
Expand Down

0 comments on commit e10712f

Please sign in to comment.