mirror of
https://github.com/RRQM/TouchSocket.git
synced 2025-12-20 02:16:42 +08:00
发布:4.0.0
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<BaseVersion>4.0.0-rc.50</BaseVersion>
|
||||
<BaseVersion>4.0.0</BaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
|
||||
@@ -988,20 +988,6 @@ public static class SystemExtension
|
||||
/// <exception cref="IOException">当读取的字节数与流的长度不匹配时抛出。</exception>
|
||||
public static byte[] ReadAllToByteArray(this Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream), "输入的 Stream 不能为 null。");
|
||||
}
|
||||
|
||||
// 如果流支持长度属性,并且流的位置在起始位置,可以直接创建对应长度的数组
|
||||
if (stream.CanSeek && stream.Length > 0 && stream.Position == 0)
|
||||
{
|
||||
var buffer = new byte[stream.Length];
|
||||
var bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
return bytesRead != buffer.Length ? throw new IOException("读取的字节数与流的长度不匹配。") : buffer;
|
||||
}
|
||||
|
||||
// 如果流不支持长度属性或位置不在起始位置,使用 MemoryStream 来读取数据
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
@@ -1013,6 +999,20 @@ public static class SystemExtension
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ReadAllToByteArrayAsync(this Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, cancellationToken)) > 0)
|
||||
{
|
||||
memoryStream.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IEnumerable
|
||||
|
||||
@@ -11,283 +11,54 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 文件池。
|
||||
/// 文件池
|
||||
/// </summary>
|
||||
|
||||
public static partial class FilePool
|
||||
{
|
||||
private static readonly Lock s_locker = new Lock();
|
||||
private static readonly ConcurrentDictionary<string, FileStorage> s_storages = new ConcurrentDictionary<string, FileStorage>();
|
||||
|
||||
private static readonly ConcurrentDictionary<string, FileStorage> m_pathStorage = new ConcurrentDictionary<string, FileStorage>();
|
||||
|
||||
private static readonly Timer s_timer;
|
||||
|
||||
static FilePool()
|
||||
/// <summary>
|
||||
/// 获取文件存储器
|
||||
/// </summary>
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <returns>文件存储器</returns>
|
||||
public static FileStorage GetStorage(string path)
|
||||
{
|
||||
s_timer = new Timer(OnTimer, null, 1000 * 60, 1000 * 60);
|
||||
ThrowHelper.ThrowArgumentNullExceptionIfStringIsNullOrEmpty(path, nameof(path));
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
|
||||
var storage = s_storages.GetOrAdd(fullPath, p => new FileStorage(p));
|
||||
Interlocked.Increment(ref storage.m_referenceCount);
|
||||
return storage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有的路径。
|
||||
/// 获取文件流
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<string> GetAllPaths()
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <returns>文件流</returns>
|
||||
public static Stream GetStream(string path)
|
||||
{
|
||||
return m_pathStorage.Keys;
|
||||
var storage = GetStorage(path);
|
||||
return new FileStorageStream(storage);
|
||||
}
|
||||
|
||||
private static void ThrowIfPathIsNull(string path)
|
||||
/// <summary>
|
||||
/// 释放文件
|
||||
/// </summary>
|
||||
/// <param name="storage">文件存储器</param>
|
||||
internal static void ReleaseFile(FileStorage storage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
if (Interlocked.Decrement(ref storage.m_referenceCount) <= 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentNullException(nameof(path));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载文件为读取流
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorage GetFileStorageForRead(string path)
|
||||
{
|
||||
ThrowIfPathIsNull(path);
|
||||
return GetFileStorageForRead(new FileInfo(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载文件为读取流
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorage GetFileStorageForRead(FileInfo fileInfo)
|
||||
{
|
||||
if (m_pathStorage.TryGetValue(fileInfo.FullName, out var storage))
|
||||
{
|
||||
if (storage.FileAccess != FileAccess.Read)
|
||||
if (s_storages.TryRemove(storage.Path, out _))
|
||||
{
|
||||
ThrowHelper.ThrowException(TouchSocketCoreResource.FileOnlyWrittenTo.Format(fileInfo.FullName));
|
||||
}
|
||||
Interlocked.Increment(ref storage.m_reference);
|
||||
return storage;
|
||||
}
|
||||
lock (s_locker)
|
||||
{
|
||||
storage = new FileStorage(fileInfo, FileAccess.Read);
|
||||
if (m_pathStorage.TryAdd(fileInfo.FullName, storage))
|
||||
{
|
||||
Interlocked.Increment(ref storage.m_reference);
|
||||
return storage;
|
||||
}
|
||||
return GetFileStorageForRead(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载文件为写流
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorage GetFileStorageForWrite(string path)
|
||||
{
|
||||
ThrowIfPathIsNull(path);
|
||||
return GetFileStorageForWrite(new FileInfo(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载文件为写流
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorage GetFileStorageForWrite(FileInfo fileInfo)
|
||||
{
|
||||
if (m_pathStorage.TryGetValue(fileInfo.FullName, out var storage))
|
||||
{
|
||||
if (storage.FileAccess != FileAccess.Write)
|
||||
{
|
||||
ThrowHelper.ThrowException(TouchSocketCoreResource.FileReadOnly.Format(fileInfo.FullName));
|
||||
}
|
||||
Interlocked.Increment(ref storage.m_reference);
|
||||
return storage;
|
||||
}
|
||||
lock (s_locker)
|
||||
{
|
||||
storage = new FileStorage(fileInfo, FileAccess.Write);
|
||||
if (m_pathStorage.TryAdd(fileInfo.FullName, storage))
|
||||
{
|
||||
Interlocked.Increment(ref storage.m_reference);
|
||||
return storage;
|
||||
}
|
||||
return GetFileStorageForWrite(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个可读可写的Stream对象。
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageStream GetFileStorageStream(string path)
|
||||
{
|
||||
return new FileStorageStream(GetFileStorageForWrite(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个可读可写的Stream对象。
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageStream GetFileStorageStream(FileInfo fileInfo)
|
||||
{
|
||||
return new FileStorageStream(GetFileStorageForWrite(fileInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个文件读取访问器
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageReader GetReader(string path)
|
||||
{
|
||||
return new FileStorageReader(GetFileStorageForRead(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个文件读取访问器
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageReader GetReader(FileInfo fileInfo)
|
||||
{
|
||||
return new FileStorageReader(GetFileStorageForRead(fileInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取引用次数。
|
||||
/// </summary>
|
||||
/// <param name="path">必须是全路径。</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static int GetReferenceCount(string path)
|
||||
{
|
||||
ThrowIfPathIsNull(path);
|
||||
return m_pathStorage.TryGetValue(path, out var fileStorage) ? fileStorage.m_reference : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个文件写入访问器
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageWriter GetWriter(string path)
|
||||
{
|
||||
return new FileStorageWriter(GetFileStorageForWrite(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个文件写入访问器
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static FileStorageWriter GetWriter(FileInfo fileInfo)
|
||||
{
|
||||
return new FileStorageWriter(GetFileStorageForWrite(fileInfo));
|
||||
}
|
||||
|
||||
private static void DelayRunReleaseFile(string path, int time)
|
||||
{
|
||||
_ = EasyTask.SafeRun(async () =>
|
||||
{
|
||||
await Task.Delay(time).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
if (GetReferenceCount(path) == 0)
|
||||
{
|
||||
if (m_pathStorage.TryRemove(path, out var fileStorage))
|
||||
{
|
||||
fileStorage.Dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少引用次数,并尝试释放流。
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="delayTime">延迟释放时间。当设置为0时,立即释放,单位毫秒。</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static Result TryReleaseFile(string path, int delayTime = 0)
|
||||
{
|
||||
ThrowIfPathIsNull(path);
|
||||
path = Path.GetFullPath(path);
|
||||
if (m_pathStorage.TryGetValue(path, out var fileStorage))
|
||||
{
|
||||
if (Interlocked.Decrement(ref fileStorage.m_reference) <= 0)
|
||||
{
|
||||
if (delayTime > 0)
|
||||
{
|
||||
DelayRunReleaseFile(path, delayTime);
|
||||
return new Result(ResultCode.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_pathStorage.TryRemove(path, out fileStorage))
|
||||
{
|
||||
fileStorage.Dispose();
|
||||
}
|
||||
return new Result(ResultCode.Success);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Result(ResultCode.Error, TouchSocketCoreResource.StreamReferencing.Format(path, fileStorage.m_reference));
|
||||
storage.DisposeInternal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Result(ResultCode.Success, TouchSocketCoreResource.StreamNotFind.Format(path));
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTimer(object state)
|
||||
{
|
||||
var keys = new List<string>();
|
||||
foreach (var item in m_pathStorage)
|
||||
{
|
||||
if (DateTimeOffset.UtcNow - item.Value.AccessTime > item.Value.AccessTimeout)
|
||||
{
|
||||
keys.Add(item.Key);
|
||||
}
|
||||
}
|
||||
foreach (var item in keys)
|
||||
{
|
||||
TryReleaseFile(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,178 +9,283 @@
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using TouchSocket.Resources;
|
||||
|
||||
namespace TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储器。在该存储器中,读写线程安全。
|
||||
/// 简化版文件存储器
|
||||
/// </summary>
|
||||
public partial class FileStorage
|
||||
public sealed partial class FileStorage : IDisposable
|
||||
{
|
||||
internal volatile int m_reference;
|
||||
private readonly ReaderWriterLockSlim m_lockSlim;
|
||||
private bool m_disposedValue;
|
||||
private readonly FileStream m_fileStream;
|
||||
private readonly SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1);
|
||||
internal int m_referenceCount;
|
||||
private bool m_disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化一个文件存储器。在该存储器中,读写线程安全。
|
||||
/// </summary>
|
||||
internal FileStorage(FileInfo fileInfo, FileAccess fileAccess) : this()
|
||||
internal FileStorage(string path)
|
||||
{
|
||||
this.FileAccess = fileAccess;
|
||||
this.FileInfo = fileInfo;
|
||||
this.Path = fileInfo.FullName;
|
||||
this.m_reference = 0;
|
||||
this.FileStream = fileInfo.Open(FileMode.OpenOrCreate, fileAccess, FileShare.ReadWrite);
|
||||
this.m_lockSlim = new ReaderWriterLockSlim();
|
||||
this.Path = path;
|
||||
this.m_fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
private FileStorage()
|
||||
{
|
||||
this.AccessTime = DateTimeOffset.UtcNow;
|
||||
this.AccessTimeout = TimeSpan.FromSeconds(60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最后访问时间。
|
||||
/// </summary>
|
||||
public DateTimeOffset AccessTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 访问超时时间。默认60s
|
||||
/// </summary>
|
||||
public TimeSpan AccessTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 访问属性
|
||||
/// </summary>
|
||||
public FileAccess FileAccess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件信息
|
||||
/// </summary>
|
||||
public FileInfo FileInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件流。
|
||||
/// 一般情况下,请不要直接访问该对象。否则有可能会产生不可预测的错误。
|
||||
/// </summary>
|
||||
public FileStream FileStream { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件长度
|
||||
/// </summary>
|
||||
public long Length => this.FileStream.Length;
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string Path { get; private set; }
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 引用次数。
|
||||
/// 文件长度
|
||||
/// </summary>
|
||||
public int Reference => this.m_reference;
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
return this.m_fileStream.Length;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanRead => this.m_fileStream.CanRead;
|
||||
public bool CanSeek => this.m_fileStream.CanSeek;
|
||||
public bool CanWrite => this.m_fileStream.CanWrite;
|
||||
|
||||
/// <summary>
|
||||
/// 写入时清空缓存区
|
||||
/// 设置文件长度
|
||||
/// </summary>
|
||||
/// <param name="length">新长度</param>
|
||||
public void SetLength(long length)
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.SetLength(length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓冲区
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
this.AccessTime = DateTimeOffset.UtcNow;
|
||||
this.FileStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前文件中读取字节。
|
||||
/// </summary>
|
||||
/// <param name="startPos">开始读取的位置。</param>
|
||||
/// <param name="span">用于接收读取数据的字节跨度。</param>
|
||||
/// <returns>实际读取的字节数。</returns>
|
||||
/// <exception cref="ObjectDisposedException">如果当前对象已被处置。</exception>
|
||||
/// <exception cref="System.IO.IOException">如果文件仅被写入。</exception>
|
||||
public int Read(long startPos, Span<byte> span)
|
||||
{
|
||||
// 更新访问时间,用于跟踪文件的最近访问时间。
|
||||
this.AccessTime = DateTimeOffset.UtcNow;
|
||||
// 使用写锁保护共享资源,确保读操作的线程安全性。
|
||||
using (var writeLock = new WriteLock(this.m_lockSlim))
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
// 检查对象是否已被处置,如果是,则抛出异常。
|
||||
if (this.m_disposedValue)
|
||||
{
|
||||
ThrowHelper.ThrowObjectDisposedException(this);
|
||||
}
|
||||
// 检查文件访问模式,如果是写模式,则抛出异常。
|
||||
if (this.FileAccess == FileAccess.Write)
|
||||
{
|
||||
ThrowHelper.ThrowException(TouchSocketCoreResource.FileOnlyWrittenTo.Format(this.FileInfo.FullName));
|
||||
}
|
||||
|
||||
|
||||
this.FileStream.Position = startPos;
|
||||
return this.FileStream.Read(span);
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少引用次数,并尝试释放流。
|
||||
/// </summary>
|
||||
/// <param name="delayTime">延迟释放时间。当设置为0时,立即释放,单位毫秒。</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public Result TryReleaseFile(int delayTime = 0)
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
return FilePool.TryReleaseFile(this.Path, delayTime);
|
||||
// 通过文件池释放,确保引用计数正确
|
||||
FilePool.ReleaseFile(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据到文件的特定位置。
|
||||
/// </summary>
|
||||
/// <param name="startPos">开始写入的位置。</param>
|
||||
/// <param name="span">要写入的字节跨度。</param>
|
||||
public void Write(long startPos, ReadOnlySpan<byte> span)
|
||||
internal void DisposeInternal()
|
||||
{
|
||||
// 更新文件的访问时间。
|
||||
this.AccessTime = DateTimeOffset.UtcNow;
|
||||
|
||||
// 使用写锁确保线程安全。
|
||||
using (var writeLock = new WriteLock(this.m_lockSlim))
|
||||
{
|
||||
// 检查对象是否已释放。
|
||||
if (this.m_disposedValue)
|
||||
{
|
||||
// 如果对象已释放,抛出ObjectDisposedException。
|
||||
ThrowHelper.ThrowObjectDisposedException(this);
|
||||
}
|
||||
|
||||
// 检查文件访问权限。
|
||||
if (this.FileAccess == FileAccess.Read)
|
||||
{
|
||||
// 如果文件只读,抛出异常。
|
||||
ThrowHelper.ThrowException(TouchSocketCoreResource.FileReadOnly.Format(this.FileInfo.FullName));
|
||||
}
|
||||
|
||||
// 设置文件流的位置为指定的开始写入位置。
|
||||
this.FileStream.Position = startPos;
|
||||
|
||||
// 将数据写入文件。
|
||||
this.FileStream.Write(span);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
if (this.m_disposedValue)
|
||||
if (this.m_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (var writeLock = new WriteLock(this.m_lockSlim))
|
||||
|
||||
// 等待获得独占访问,确保没有读写在进行
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.m_disposedValue = true;
|
||||
this.FileStream.SafeDispose();
|
||||
if (this.m_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.m_disposed = true;
|
||||
this.m_fileStream.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 释放并处置信号量
|
||||
try
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略释放异常
|
||||
}
|
||||
|
||||
this.m_semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (this.m_disposed)
|
||||
{
|
||||
ThrowHelper.ThrowObjectDisposedException(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取数据
|
||||
/// </summary>
|
||||
/// <param name="position">读取位置</param>
|
||||
/// <param name="buffer">缓冲区</param>
|
||||
/// <param name="offset">缓冲区偏移量</param>
|
||||
/// <param name="count">读取字节数</param>
|
||||
/// <returns>实际读取的字节数</returns>
|
||||
public int Read(long position, byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
return this.m_fileStream.Read(buffer, offset, count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="position">写入位置</param>
|
||||
/// <param name="buffer">数据</param>
|
||||
/// <param name="offset">缓冲区偏移量</param>
|
||||
/// <param name="count">写入字节数</param>
|
||||
public void Write(long position, byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
this.m_fileStream.Write(buffer, offset, count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步读取数据
|
||||
/// </summary>
|
||||
/// <param name="position">读取位置</param>
|
||||
/// <param name="memory">缓冲区</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>实际读取的字节数</returns>
|
||||
public async Task<int> ReadAsync(long position, Memory<byte> memory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.m_semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
return await this.m_fileStream.ReadAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入数据
|
||||
/// </summary>
|
||||
/// <param name="position">写入位置</param>
|
||||
/// <param name="memory">数据</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task WriteAsync(long position, ReadOnlyMemory<byte> memory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.m_semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
await this.m_fileStream.WriteAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步刷新缓冲区
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task FlushAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await this.m_semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
await this.m_fileStream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取数据
|
||||
/// </summary>
|
||||
/// <param name="position">读取位置</param>
|
||||
/// <param name="buffer">缓冲区</param>
|
||||
/// <returns>实际读取的字节数</returns>
|
||||
public int Read(long position, Span<byte> buffer)
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
return this.m_fileStream.Read(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入数据
|
||||
/// </summary>
|
||||
/// <param name="position">写入位置</param>
|
||||
/// <param name="buffer">数据</param>
|
||||
public void Write(long position, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
this.m_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_fileStream.Position = position;
|
||||
this.m_fileStream.Write(buffer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.m_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:https://touchsocket.net/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 文件读取器
|
||||
/// </summary>
|
||||
public partial class FileStorageReader : SafetyDisposableObject
|
||||
{
|
||||
private long m_position;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FileStorageReader实例。
|
||||
/// </summary>
|
||||
/// <param name="fileStorage">文件存储对象,用于处理文件读取操作。</param>
|
||||
/// <remarks>
|
||||
/// 此构造函数接收一个FileStorage对象作为参数,用于后续的文件读取操作。
|
||||
/// 如果传入的FileStorage对象为<see langword="null"/>,则抛出ArgumentNullException异常,
|
||||
/// 这确保了文件读取操作不会在不合法的参数状态下执行,提高了代码的健壮性。
|
||||
/// </remarks>
|
||||
public FileStorageReader(FileStorage fileStorage)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage));
|
||||
this.FileStorage = fileStorage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~FileStorageReader()
|
||||
{
|
||||
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
|
||||
this.Dispose(disposing: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储器
|
||||
/// </summary>
|
||||
public FileStorage FileStorage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 游标位置
|
||||
/// </summary>
|
||||
public long Position { get => this.m_position; set => this.m_position = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 从文件存储中读取字节到指定的字节跨度。
|
||||
/// </summary>
|
||||
/// <param name="span">要读取数据的字节跨度。</param>
|
||||
/// <returns>实际读取到的字节数。</returns>
|
||||
public int Read(Span<byte> span)
|
||||
{
|
||||
// 调用文件存储从当前位置读取数据到字节跨度中
|
||||
var r = this.FileStorage.Read(this.Position, span);
|
||||
// 使用Interlocked类原子性地更新当前位置,以确保多线程环境下的安全性
|
||||
Interlocked.Add(ref this.m_position, r);
|
||||
// 返回实际读取到的字节数
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
FilePool.TryReleaseFile(this.FileStorage.Path);
|
||||
this.FileStorage = null;
|
||||
}
|
||||
}
|
||||
@@ -10,111 +10,237 @@
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// FileStorageStream。
|
||||
/// 文件存储流
|
||||
/// </summary>
|
||||
public partial class FileStorageStream : Stream
|
||||
internal sealed partial class FileStorageStream : Stream
|
||||
{
|
||||
private long m_position;
|
||||
private int m_dis = 1;
|
||||
private FileStorage m_storage;
|
||||
private bool m_disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="fileStorage"></param>
|
||||
public FileStorageStream(FileStorage fileStorage)
|
||||
internal FileStorageStream(FileStorage storage)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage));
|
||||
this.FileStorage = fileStorage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~FileStorageStream()
|
||||
{
|
||||
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
|
||||
this.Dispose(disposing: false);
|
||||
this.m_storage = storage;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead => this.FileStorage.FileStream.CanRead;
|
||||
public override bool CanRead => this.m_storage.CanRead;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek => this.FileStorage.FileStream.CanSeek;
|
||||
public override bool CanSeek => this.m_storage.CanSeek;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanWrite => this.FileStorage.FileStream.CanWrite;
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储器
|
||||
/// </summary>
|
||||
public FileStorage FileStorage { get; private set; }
|
||||
public override bool CanWrite => this.m_storage.CanWrite;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length => this.FileStorage.FileStream.Length;
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
return this.m_storage.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position { get => this.m_position; set => this.m_position = value; }
|
||||
public override long Position
|
||||
{
|
||||
get => this.m_position;
|
||||
set => this.m_position = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
this.FileStorage.Flush();
|
||||
this.ThrowIfDisposed();
|
||||
this.m_storage.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var r = this.FileStorage.Read(this.m_position, new System.Span<byte>(buffer, offset, count));
|
||||
this.m_position += r;
|
||||
return r;
|
||||
this.ThrowIfDisposed();
|
||||
#if NET6_0_OR_GREATER
|
||||
var span = buffer.AsSpan(offset, count);
|
||||
var readCount = this.m_storage.Read(this.m_position, span);
|
||||
this.m_position += readCount;
|
||||
return readCount;
|
||||
#else
|
||||
return this.ReadCore(buffer, offset, count);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
this.ThrowIfDisposed();
|
||||
this.m_position = origin switch
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
this.m_position = offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
this.m_position += offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
this.m_position = this.Length + offset;
|
||||
break;
|
||||
}
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => this.m_position + offset,
|
||||
SeekOrigin.End => this.m_storage.Length + offset,
|
||||
_ => throw new ArgumentException("Invalid seek origin", nameof(origin))
|
||||
};
|
||||
return this.m_position;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.FileStorage.FileStream.SetLength(value);
|
||||
this.ThrowIfDisposed();
|
||||
this.m_storage.SetLength(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.FileStorage.Write(this.m_position, new System.ReadOnlySpan<byte>(buffer, offset, count));
|
||||
this.ThrowIfDisposed();
|
||||
#if NET6_0_OR_GREATER
|
||||
var span = buffer.AsSpan(offset, count);
|
||||
this.m_storage.Write(this.m_position, span);
|
||||
this.m_position += count;
|
||||
#else
|
||||
this.WriteCore(buffer, offset, count);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (Interlocked.Decrement(ref this.m_dis) == 0)
|
||||
if (this.m_disposed)
|
||||
{
|
||||
FilePool.TryReleaseFile(this.FileStorage.Path);
|
||||
this.FileStorage = null;
|
||||
return;
|
||||
}
|
||||
this.m_disposed = true;
|
||||
|
||||
if (disposing && this.m_storage != null)
|
||||
{
|
||||
FilePool.ReleaseFile(this.m_storage);
|
||||
this.m_storage = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (this.m_disposed)
|
||||
{
|
||||
ThrowHelper.ThrowObjectDisposedException(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
internal sealed partial class FileStorageStream
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var count = this.m_storage.Read(this.m_position, buffer);
|
||||
this.m_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.m_storage.Write(this.m_position, buffer);
|
||||
this.m_position += buffer.Length;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
return this.m_storage.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var memory = buffer.AsMemory(offset, count);
|
||||
var readCount = await this.m_storage.ReadAsync(this.m_position, memory, cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var count = await this.m_storage.ReadAsync(this.m_position, buffer, cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var memory = buffer.AsMemory(offset, count);
|
||||
await this.m_storage.WriteAsync(this.m_position, memory, cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
await this.m_storage.WriteAsync(this.m_position, buffer, cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += buffer.Length;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET462
|
||||
|
||||
|
||||
internal sealed partial class FileStorageStream
|
||||
{
|
||||
private int ReadCore(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var readCount = this.m_storage.Read(this.m_position, buffer, offset, count);
|
||||
this.m_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
private void WriteCore(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.m_storage.Write(this.m_position, buffer, offset, count);
|
||||
this.m_position += count;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
return this.m_storage.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var readCount = await this.m_storage.ReadAsync(this.m_position, new Memory<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
await this.m_storage.WriteAsync(this.m_position, new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
|
||||
this.m_position += count;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
|
||||
// CSDN博客:https://blog.csdn.net/qq_40374647
|
||||
// 哔哩哔哩视频:https://space.bilibili.com/94253567
|
||||
// Gitee源代码仓库:https://gitee.com/RRQM_Home
|
||||
// Github源代码仓库:https://github.com/RRQM
|
||||
// API首页:https://touchsocket.net/
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace TouchSocket.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 文件写入器。
|
||||
/// </summary>
|
||||
public partial class FileStorageWriter : SafetyDisposableObject
|
||||
{
|
||||
private long m_position;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FileStorageWriter的实例。
|
||||
/// </summary>
|
||||
/// <param name="fileStorage">文件存储服务的实例,用于后续的文件写入操作。</param>
|
||||
public FileStorageWriter(FileStorage fileStorage)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(fileStorage, nameof(fileStorage));
|
||||
this.FileStorage = fileStorage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~FileStorageWriter()
|
||||
{
|
||||
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
|
||||
this.Dispose(disposing: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件存储器
|
||||
/// </summary>
|
||||
public FileStorage FileStorage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 游标位置
|
||||
/// </summary>
|
||||
public long Position { get => this.m_position; set => this.m_position = value; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将文件指针移动到文件末尾。
|
||||
/// </summary>
|
||||
/// <returns>返回文件末尾的位置。</returns>
|
||||
public long SeekToEnd()
|
||||
{
|
||||
// 设置文件指针到文件末尾,并返回该位置
|
||||
return this.Position = this.FileStorage.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一组只读字节写入文件存储。
|
||||
/// </summary>
|
||||
/// <param name="span">要写入的只读字节范围。</param>
|
||||
public void Write(ReadOnlySpan<byte> span)
|
||||
{
|
||||
// 调用FileStorage的Write方法,将当前Position位置的span长度字节写入。
|
||||
this.FileStorage.Write(this.Position, span);
|
||||
// 使用Interlocked类的Add方法,线程安全地更新当前Position。
|
||||
Interlocked.Add(ref this.m_position, span.Length);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SafetyDispose(bool disposing)
|
||||
{
|
||||
FilePool.TryReleaseFile(this.FileStorage.Path);
|
||||
this.FileStorage = null;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace TouchSocket.Core;
|
||||
public sealed class FileLogger : LoggerBase, IDisposable
|
||||
{
|
||||
private const int MaxRetryCount = 10;
|
||||
private readonly ConcurrentDictionary<string, FileStorageWriter> m_writers = new ConcurrentDictionary<string, FileStorageWriter>();
|
||||
private readonly ConcurrentDictionary<string, Stream> m_writers = new();
|
||||
private Func<LogLevel, string> m_createLogFolder;
|
||||
private bool m_disposedValue;
|
||||
private int m_retryCount;
|
||||
@@ -117,7 +117,7 @@ public sealed class FileLogger : LoggerBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private FileStorageWriter GetFileStorageWriter(string dirPath)
|
||||
private Stream GetFileStorageWriter(string dirPath)
|
||||
{
|
||||
if (this.m_writers.TryGetValue(dirPath, out var writer))
|
||||
{
|
||||
@@ -136,9 +136,8 @@ public sealed class FileLogger : LoggerBase, IDisposable
|
||||
var filePath = Path.Combine(dirPath, $"{count.ToString(this.FileNameFormat)}" + ".log");
|
||||
if (!(File.Exists(filePath) && new FileInfo(filePath).Length > this.MaxSize))
|
||||
{
|
||||
writer = FilePool.GetWriter(filePath);
|
||||
writer.SeekToEnd();
|
||||
writer.FileStorage.AccessTimeout = TimeSpan.MaxValue;
|
||||
writer = FilePool.GetStream(filePath);
|
||||
writer.Seek(writer.Length, SeekOrigin.Begin);
|
||||
|
||||
return this.m_writers.TryAdd(dirPath, writer) ? writer : this.GetFileStorageWriter(dirPath);
|
||||
}
|
||||
@@ -158,8 +157,8 @@ public sealed class FileLogger : LoggerBase, IDisposable
|
||||
try
|
||||
{
|
||||
writer1.Write((Encoding.UTF8.GetBytes(logString)));
|
||||
writer1.FileStorage.Flush();
|
||||
if (writer1.FileStorage.Length > this.MaxSize)
|
||||
writer1.Flush();
|
||||
if (writer1.Length > this.MaxSize)
|
||||
{
|
||||
if (this.m_writers.TryRemove(dirPath, out var fileStorageWriter))
|
||||
{
|
||||
|
||||
@@ -591,91 +591,51 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe
|
||||
/// <inheritdoc/>
|
||||
public async Task<FinishedResult> FinishedFileResourceInfoAsync(string targetId, FileResourceInfo fileResourceInfo, ResultCode code, Metadata metadata, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetId))
|
||||
{
|
||||
return await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.FinishedFileResourceInfoAsync(fileResourceInfo, code, metadata, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken);
|
||||
}
|
||||
return string.IsNullOrEmpty(targetId)
|
||||
? await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken)
|
||||
: this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.FinishedFileResourceInfoAsync(fileResourceInfo, code, metadata, cancellationToken)
|
||||
: await this.PrivateFinishedFileResourceInfoAsync(targetId, fileResourceInfo, code, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileResourceInfoResult> PullFileResourceInfoAsync(string targetId, string path, Metadata metadata, int fileSectionSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetId))
|
||||
{
|
||||
return await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken);
|
||||
}
|
||||
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PullFileResourceInfoAsync(path, metadata, fileSectionSize, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken);
|
||||
}
|
||||
return string.IsNullOrEmpty(targetId)
|
||||
? await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken)
|
||||
: this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PullFileResourceInfoAsync(path, metadata, fileSectionSize, cancellationToken)
|
||||
: await this.PrivatePullFileResourceInfoAsync(targetId, path, metadata, fileSectionSize, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileSectionResult> PullFileSectionAsync(string targetId, FileSection fileSection, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetId))
|
||||
{
|
||||
return await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken);
|
||||
}
|
||||
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PullFileSectionAsync(fileSection, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken);
|
||||
}
|
||||
return string.IsNullOrEmpty(targetId)
|
||||
? await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken)
|
||||
: this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PullFileSectionAsync(fileSection, cancellationToken)
|
||||
: await this.PrivatePullFileSectionAsync(targetId, fileSection, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Result> PushFileResourceInfoAsync(string targetId, string savePath, FileResourceLocator fileResourceLocator, Metadata metadata, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetId))
|
||||
{
|
||||
return await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PushFileResourceInfoAsync(savePath, fileResourceLocator, metadata, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken);
|
||||
}
|
||||
return string.IsNullOrEmpty(targetId)
|
||||
? await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken)
|
||||
: this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PushFileResourceInfoAsync(savePath, fileResourceLocator, metadata, cancellationToken)
|
||||
: await this.PrivatePushFileResourceInfoAsync(targetId, savePath, fileResourceLocator, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Result> PushFileSectionAsync(string targetId, FileResourceLocator fileResourceLocator, FileSection fileSection, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetId))
|
||||
{
|
||||
return await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken);
|
||||
}
|
||||
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PushFileSectionAsync(fileResourceLocator, fileSection, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken);
|
||||
}
|
||||
return string.IsNullOrEmpty(targetId)
|
||||
? await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken)
|
||||
: this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PushFileSectionAsync(fileResourceLocator, fileSection, cancellationToken)
|
||||
: await this.PrivatePushFileSectionAsync(targetId, fileResourceLocator, fileSection, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion Id传输
|
||||
@@ -1263,7 +1223,7 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe
|
||||
if (this.FileController.TryGetFileResourceLocator(waitFileSection.FileSection.ResourceHandle,
|
||||
out var locator))
|
||||
{
|
||||
var r = locator.ReadBytes(waitFileSection.FileSection.Offset, bufferByteBlock.TotalMemory.Span.Slice(0, length));
|
||||
var r = await locator.ReadAsync(waitFileSection.FileSection.Offset, bufferByteBlock.TotalMemory.Slice(0, length));
|
||||
if (r == length)
|
||||
{
|
||||
waitFileSection.Status = TouchSocketDmtpStatus.Success.ToValue();
|
||||
@@ -1435,14 +1395,9 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe
|
||||
/// <inheritdoc/>
|
||||
public async Task<PullSmallFileResult> PullSmallFileAsync(string targetId, string path, Metadata metadata = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PullSmallFileAsync(path, metadata, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePullSmallFileAsync(targetId, path, metadata, cancellationToken);
|
||||
}
|
||||
return this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PullSmallFileAsync(path, metadata, cancellationToken)
|
||||
: await this.PrivatePullSmallFileAsync(targetId, path, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -1454,14 +1409,9 @@ internal sealed class DmtpFileTransferActor : DisposableObject, IDmtpFileTransfe
|
||||
/// <inheritdoc/>
|
||||
public async Task<Result> PushSmallFileAsync(string targetId, string savePath, FileInfo fileInfo, Metadata metadata = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor)
|
||||
{
|
||||
return await actor.PushSmallFileAsync(savePath, fileInfo, metadata, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await this.PrivatePushSmallFileAsync(targetId, savePath, fileInfo, metadata, cancellationToken);
|
||||
}
|
||||
return this.DmtpActor.AllowRoute && (await this.TryFindDmtpFileTransferActor(targetId)) is DmtpFileTransferActor actor
|
||||
? await actor.PushSmallFileAsync(savePath, fileInfo, metadata, cancellationToken)
|
||||
: await this.PrivatePushSmallFileAsync(targetId, savePath, fileInfo, metadata, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -30,7 +30,7 @@ public class FileResourceLocator : DisposableObject
|
||||
{
|
||||
this.FileResourceInfo = fileResourceInfo;
|
||||
this.FileAccess = FileAccess.Read;
|
||||
this.FileStorage = FilePool.GetFileStorageForRead(fileResourceInfo.FileInfo.FullName);
|
||||
this.FileStorage = FilePool.GetStorage(fileResourceInfo.FileInfo.FullName);
|
||||
this.LocatorPath = fileResourceInfo.FileInfo.FullName;
|
||||
this.LastActiveTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class FileResourceLocator : DisposableObject
|
||||
this.LocatorPath = locatorPath;
|
||||
this.FileResourceInfo = fileResourceInfo;
|
||||
this.FileAccess = FileAccess.Write;
|
||||
this.FileStorage = FilePool.GetFileStorageForWrite(this.LocatorPath + ExtensionName);
|
||||
this.FileStorage = FilePool.GetStorage(this.LocatorPath + ExtensionName);
|
||||
this.LastActiveTime = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
@@ -115,9 +115,7 @@ public class FileResourceLocator : DisposableObject
|
||||
// 根据文件访问模式决定返回值
|
||||
// 如果是读取模式,则返回空数组
|
||||
// 否则,返回所有未完成状态的文件片段集合
|
||||
return this.FileAccess == FileAccess.Read
|
||||
? (new FileSection[0])
|
||||
: this.FileResourceInfo.FileSections.Where(a => a.Status != FileSectionStatus.Finished).ToArray();
|
||||
return this.FileAccess == FileAccess.Read ? [] : this.FileResourceInfo.FileSections.Where(a => a.Status != FileSectionStatus.Finished).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,12 +124,10 @@ public class FileResourceLocator : DisposableObject
|
||||
/// <param name="pos">要开始读取的文件位置。</param>
|
||||
/// <param name="span">用于存储读取的字节的跨度。</param>
|
||||
/// <returns>读取的字节数。</returns>
|
||||
public int ReadBytes(long pos, Span<byte> span)
|
||||
public async Task<int> ReadAsync(long pos, Memory<byte> span)
|
||||
{
|
||||
// 更新最后一次活动时间,用于跟踪访问时间
|
||||
this.LastActiveTime = DateTimeOffset.UtcNow;
|
||||
// 调用底层文件存储系统的读取方法,读取字节并返回读取的字节数
|
||||
return this.FileStorage.Read(pos, span);
|
||||
return await this.FileStorage.ReadAsync(pos, span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -234,14 +230,7 @@ public class FileResourceLocator : DisposableObject
|
||||
return new Result(ResultCode.Failure, "文件长度不一致。");
|
||||
}
|
||||
|
||||
// 尝试释放文件,最多尝试10次
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (this.FileStorage.TryReleaseFile().IsSuccess)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.FileStorage.Dispose();
|
||||
|
||||
// 确保文件移动并重命名,最多尝试10次
|
||||
for (var i = 0; i < 10; i++)
|
||||
@@ -350,7 +339,7 @@ public class FileResourceLocator : DisposableObject
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.FileStorage.TryReleaseFile();
|
||||
this.FileStorage.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
// 交流QQ群:234762506
|
||||
// 感谢您的下载和使用
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace TouchSocket.Dmtp.FileTransfer;
|
||||
|
||||
/// <summary>
|
||||
@@ -55,8 +54,9 @@ public class PullSmallFileResult : ResultBase
|
||||
/// </summary>
|
||||
/// <param name="path">要保存文件的路径。</param>
|
||||
/// <param name="overwrite">是否覆盖同名文件,默认为<see langword="true"/>。</param>
|
||||
/// <param name="cancellationToken">可取消令箭</param>
|
||||
/// <returns>返回保存结果。</returns>
|
||||
public Result Save(string path, bool overwrite = true)
|
||||
public async Task<Result> SaveAsync(string path, bool overwrite = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -64,11 +64,13 @@ public class PullSmallFileResult : ResultBase
|
||||
{
|
||||
FileUtility.Delete(path);
|
||||
}
|
||||
using (var byteBlock = FilePool.GetWriter(path))
|
||||
using (var stream = FilePool.GetStream(path))
|
||||
{
|
||||
byteBlock.Write(this.Value);
|
||||
return Result.Success;
|
||||
await stream.WriteAsync(this.Value, cancellationToken);
|
||||
await stream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -95,9 +95,9 @@ public class FileResourceController : DisposableObject, IFileResourceController
|
||||
public virtual int ReadAllBytes(FileInfo fileInfo, byte[] buffer)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
using (var byteBlock = FilePool.GetReader(fileInfo))
|
||||
using (var stream = FilePool.GetStream(fileInfo.FullName))
|
||||
{
|
||||
return byteBlock.Read(buffer);
|
||||
return stream.Read(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,9 +125,9 @@ public class FileResourceController : DisposableObject, IFileResourceController
|
||||
public virtual void WriteAllBytes(string path, byte[] buffer, int offset, int length)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
using (var byteBlock = FilePool.GetWriter(path))
|
||||
using (var stream = FilePool.GetStream(path))
|
||||
{
|
||||
byteBlock.Write(new ReadOnlySpan<byte>(buffer, offset, length));
|
||||
stream.Write(new ReadOnlySpan<byte>(buffer, offset, length));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -298,7 +298,6 @@ public class DmtpRpcActor : DisposableObject, IDmtpRpcActor
|
||||
}
|
||||
else
|
||||
{
|
||||
//首先移除调用上下文。
|
||||
this.m_callContextDic.TryRemove(rpcRequestPackage.Sign, out _);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,22 +65,6 @@ public abstract partial class HttpSessionClient : TcpSessionClientBase, IHttpSes
|
||||
return base.OnTcpConnecting(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override async ValueTask<bool> OnTcpReceiving(IBytesReader reader)
|
||||
{
|
||||
var webSocket = this.m_webSocket;
|
||||
if (webSocket is null)
|
||||
{
|
||||
await this.m_httpAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.m_webSocketAdapter.ReceivedInputAsync(reader).ConfigureAwait(EasyTask.ContinueOnCapturedContext);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task OnReceivingHttpRequest(ServerHttpRequest request)
|
||||
{
|
||||
if (this.m_httpContext == null)
|
||||
|
||||
Reference in New Issue
Block a user