文件缓存支持缓存上级目录列表信息到index.csv,即使本地没有该文件,也能在列表中显示,首次下载后即可缓存访问该文件

This commit is contained in:
大石头 2023-04-05 11:48:49 +08:00
parent ddefcca7b0
commit 0fa21dfbb0
6 changed files with 199 additions and 8 deletions

View File

@ -0,0 +1,95 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using NewLife;
using NewLife.IO;
namespace Stardust.Extensions.Caches;
class CacheDirectoryContents : IDirectoryContents, IEnumerable<IFileInfo>, IEnumerable
{
private IEnumerable<IFileInfo> _entries;
private readonly String _directory;
private readonly ExclusionFilters _filters;
public Boolean Exists => Directory.Exists(_directory);
/// <summary>索引信息文件。列出扩展显示的文件内容</summary>
public String IndexInfoFile { get; set; }
public CacheDirectoryContents(String directory)
: this(directory, ExclusionFilters.Sensitive)
{
}
public CacheDirectoryContents(String directory, ExclusionFilters filters)
{
_directory = directory ?? throw new ArgumentNullException(nameof(directory));
_filters = filters;
}
public IEnumerator<IFileInfo> GetEnumerator()
{
EnsureInitialized();
return _entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
EnsureInitialized();
return _entries.GetEnumerator();
}
private void EnsureInitialized()
{
try
{
_entries = (from info in new DirectoryInfo(_directory).EnumerateFileSystemInfos()
where !CacheFileProvider.IsExcluded(info, _filters)
select info).Select(info =>
{
if (info is FileInfo fileInfo)
return new PhysicalFileInfo(fileInfo);
return info is DirectoryInfo directoryInfo
? (IFileInfo)new PhysicalDirectoryInfo(directoryInfo)
: throw new InvalidOperationException("UnexpectedFileSystemInfo");
});
if (!IndexInfoFile.IsNullOrEmpty())
{
var fi = _directory.CombinePath(IndexInfoFile).GetBasePath().AsFile();
if (fi.Exists)
{
var csv = new CsvDb<FileInfoModel>((x, y) => x.Name.EqualIgnoreCase(y.Name))
{
FileName = fi.FullName
};
var fis = csv.FindAll();
if (fis.Count > 0)
{
var list = _entries.ToList();
foreach (var item in fis)
{
// 把fis里面的项添加到list
if (!list.Any(e => e.Name.EqualIgnoreCase(item.Name)))
list.Add(item);
}
_entries = list;
}
}
}
}
catch (Exception ex) when (ex is DirectoryNotFoundException or IOException)
{
_entries = Enumerable.Empty<IFileInfo>();
}
}
}

View File

@ -2,17 +2,21 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Policy;
using Microsoft.AspNetCore.Mvc.Razor.Infrastructure;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Internal;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Primitives;
using NewLife;
using NewLife.Http;
using NewLife.IO;
using NewLife.Log;
using NewLife.Web;
namespace Stardust.Extensions;
namespace Stardust.Extensions.Caches;
/// <summary>文件缓存提供者。本地文件不存在时,从上级拉取</summary>
public class CacheFileProvider : IFileProvider
class CacheFileProvider : IFileProvider
{
#region
private static readonly Char[] _pathSeparators = new Char[2]
@ -28,10 +32,21 @@ public class CacheFileProvider : IFileProvider
/// <summary>服务端地址。本地文件不存在时,将从这里下载</summary>
public String Server { get; set; }
/// <summary>索引信息文件。列出扩展显示的文件内容</summary>
public String IndexInfoFile { get; set; }
#endregion
//public CacheFileProvider(String root) : this(root, ExclusionFilters.Sensitive) { }
/// <summary>
/// 实例化
/// </summary>
/// <param name="root"></param>
/// <param name="server"></param>
/// <param name="filters"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="DirectoryNotFoundException"></exception>
public CacheFileProvider(String root, String server, ExclusionFilters filters = ExclusionFilters.Sensitive)
{
if (!Path.IsPathRooted(root)) throw new ArgumentException("The path must be absolute.", nameof(root));
@ -60,6 +75,11 @@ public class CacheFileProvider : IFileProvider
return !fullPath.StartsWithIgnoreCase(Root) ? null : fullPath;
}
/// <summary>
/// 获取文件信息
/// </summary>
/// <param name="subpath"></param>
/// <returns></returns>
public IFileInfo GetFileInfo(String subpath)
{
if (String.IsNullOrEmpty(subpath) || HasInvalidPathChars(subpath)) return new NotFoundFileInfo(subpath);
@ -71,7 +91,9 @@ public class CacheFileProvider : IFileProvider
if (fullPath == null) return new NotFoundFileInfo(subpath);
// 本地不存在时,从服务器下载
if (!File.Exists(fullPath) && Path.GetFileName(fullPath).Contains('.'))
var fi = fullPath.AsFile();
//if ((!fi.Exists || fi.LastWriteTime.AddDays(1) < DateTime.Now) && Path.GetFileName(fullPath).Contains('.'))
if (!fi.Exists && Path.GetFileName(fullPath).Contains('.'))
{
var url = subpath.Replace("\\", "/");
url = Server.Contains("{0}") ? Server.Replace("{0}", url) : Server + url.EnsureStart("/");
@ -100,6 +122,12 @@ public class CacheFileProvider : IFileProvider
return IsExcluded(fileInfo, _filters) ? new NotFoundFileInfo(subpath) : new PhysicalFileInfo(fileInfo);
}
/// <summary>
/// 是否存在
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="filters"></param>
/// <returns></returns>
public static Boolean IsExcluded(FileSystemInfo fileInfo, ExclusionFilters filters)
{
if (filters == ExclusionFilters.None) return false;
@ -112,6 +140,11 @@ public class CacheFileProvider : IFileProvider
);
}
/// <summary>
/// 获取文件内容
/// </summary>
/// <param name="subpath"></param>
/// <returns></returns>
public IDirectoryContents GetDirectoryContents(String subpath)
{
try
@ -122,9 +155,43 @@ public class CacheFileProvider : IFileProvider
if (Path.IsPathRooted(subpath)) return NotFoundDirectoryContents.Singleton;
var fullPath = GetFullPath(subpath);
// 下载信息文件
if (!IndexInfoFile.IsNullOrEmpty() && !Server.IsNullOrEmpty())
{
var fi = fullPath.CombinePath(IndexInfoFile).GetBasePath().AsFile();
if (!fi.Exists || fi.LastWriteTime.AddDays(1) < DateTime.Now)
{
try
{
var url = subpath.Replace("\\", "/");
url = Server.Contains("{0}") ? Server.Replace("{0}", url) : Server + url.EnsureStart("/");
using var client = new HttpClient();
var html = client.GetString(url);
var links = Link.Parse(html, url);
var list = links.Select(e => new FileInfoModel
{
Name = e.FullName,
LastModified = e.Time.Year > 2000 ? e.Time : DateTime.Now,
Exists = true,
IsDirectory = false,
}).ToList();
var csv = new CsvDb<FileInfoModel> { FileName = fi.FullName };
csv.Write(list, false);
}
catch (Exception ex)
{
XTrace.Log?.Debug("下载目录信息出错:{0}", ex.Message);
}
}
}
return fullPath == null || !Directory.Exists(fullPath)
? NotFoundDirectoryContents.Singleton
: new PhysicalDirectoryContents(fullPath, _filters);
? NotFoundDirectoryContents.Singleton
: new CacheDirectoryContents(fullPath, _filters) { IndexInfoFile = IndexInfoFile };
}
catch (DirectoryNotFoundException) { }
catch (IOException) { }
@ -132,6 +199,11 @@ public class CacheFileProvider : IFileProvider
return NotFoundDirectoryContents.Singleton;
}
/// <summary>
/// 监控文件改变
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public IChangeToken Watch(String filter) => NullChangeToken.Singleton;
internal static Boolean HasInvalidPathChars(String path) => path.IndexOfAny(_invalidFileNameChars) != -1;

View File

@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using NewLife.Log;
namespace Stardust.Extensions;
namespace Stardust.Extensions.Caches;
/// <summary>
/// 文件缓存扩展
@ -32,7 +32,7 @@ public static class FileCacheExtensions
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(requestPath),
FileProvider = new CacheFileProvider(sdk, uplinkServer),
FileProvider = new CacheFileProvider(sdk, uplinkServer) { IndexInfoFile = "index.csv" },
});
}
}

View File

@ -0,0 +1,22 @@
using System.IO;
using System;
using Microsoft.Extensions.FileProviders;
namespace Stardust.Extensions.Caches;
class FileInfoModel : IFileInfo
{
public String Name { get; set; }
public Boolean Exists { get; set; }
public Boolean IsDirectory { get; set; }
public DateTimeOffset LastModified { get; set; }
public Int64 Length { get; set; }
public String PhysicalPath { get; set; }
public Stream CreateReadStream() => throw new NotImplementedException();
}

View File

@ -10,6 +10,7 @@ using NewLife.Log;
using NewLife.Serialization;
using Stardust.Data.Nodes;
using Stardust.Extensions;
using Stardust.Extensions.Caches;
using Stardust.Monitors;
using Stardust.Server.Services;
using XCode;

View File

@ -7,6 +7,7 @@ using NewLife.Cube.Extensions;
using NewLife.Log;
using Stardust.Data.Configs;
using Stardust.Extensions;
using Stardust.Extensions.Caches;
using Stardust.Server.Services;
using Stardust.Web.Services;
using XCode;