tesses-vfs-extras/Tesses.VirtualFilesystem.Ov.../Class1.cs

528 lines
21 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Tesses.VirtualFilesystem.Filesystems
{
public class OverlayFilesystem : AsyncFileSystem
{
IVirtualFilesystem lower;
IVirtualFilesystem upper;
public OverlayFilesystem(IVirtualFilesystem lower,IVirtualFilesystem upper)
{
this.lower = lower;
this.upper = upper;
}
public override async Task CreateDirectoryAsync(UnixPath directory, CancellationToken token = default)
{
await upper.CreateDirectoryAsync(directory,token);
}
public override async Task DeleteDirectoryAsync(UnixPath path, CancellationToken token = default)
{
if(await upper.DirectoryExistsAsync(path,token))
await upper.DeleteDirectoryAsync(path,token);
}
public override async Task DeleteFileAsync(UnixPath path, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token))
await upper.DeleteFileAsync(path,token);
}
public override async Task<bool> DirectoryExistsAsync(UnixPath path, CancellationToken token = default)
{
return await lower.DirectoryExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token);
}
public override async Task<bool> SymlinkExistsAsync(UnixPath file, CancellationToken token = default)
{
return await SymlinkExists2Async(lower,file,token) || await SymlinkExists2Async(upper,file,token);
}
public override bool CanHandleSymlinks(UnixPath path)
{
if(upper.FileExists(path) || upper.DirectoryExists(path) || (upper.CanHandleSymlinks(path) && upper.SymlinkExists(path)))
{
return upper.CanHandleSymlinks(path);
}
return lower.CanHandleSymlinks(path);
}
private async Task<bool> SymlinkExists2Async(IVirtualFilesystem fs,UnixPath p,CancellationToken token=default)
{
return fs.CanHandleSymlinks(p) && await fs.SymlinkExistsAsync(p,token);
}
public override async Task CreateSymlinkAsync(UnixPath src, UnixPath dest, CancellationToken token = default)
{
if(upper.CanHandleSymlinks(dest))
{
await upper.CreateSymlinkAsync(src,dest,token);
}
}
public override async Task CreateHardlinkAsync(UnixPath src,UnixPath dest,CancellationToken token=default)
{
if(await upper.FileExistsAsync(src,token))
{
await upper.CreateHardlinkAsync(src,dest,token);
}
else
{
if(await upper.FileExistsAsync(dest,token) || !await lower.FileExistsAsync(src,token)) return;
if(dest.ParentIsRoot)
{
await upper.CreateDirectoryAsync(dest.Parent);
}
using(var srcS = await lower.OpenAsync(src,FileMode.Open,FileAccess.Read,FileShare.Read))
{
using(var destS = await upper.OpenAsync(dest,FileMode.Create,FileAccess.Write,FileShare.Read))
{
await srcS.CopyToAsync(destS);
}
}
}
}
public override async Task SetAttributesAsync(UnixPath path, FileAttributes attributes, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token))
{
await upper.SetAttributesAsync(path, attributes, token);
}
else
{
await lower.SetAttributesAsync(path, attributes,token);
}
}
public override bool CanWatch(UnixPath path)
{
if(upper.DirectoryExists(path) && lower.DirectoryExists(path))
{
return upper.CanWatch(path) || lower.CanWatch(path);
}
else if(upper.DirectoryExists(path))
{
return upper.CanWatch(path);
}
else if(lower.DirectoryExists(path))
{
return lower.CanWatch(path);
}
else
{
return false;
}
}
public override IVirtualWatcher WatchDirectory(UnixPath dir)
{
if(upper.DirectoryExists(dir) && lower.DirectoryExists(dir))
{
return new DoubleWatcher(lower.WatchDirectory(dir),upper.WatchDirectory(dir));
}
else if(upper.DirectoryExists(dir))
{
return upper.WatchDirectory(dir);
}
else if(lower.DirectoryExists(dir))
{
return lower.WatchDirectory(dir);
}
else
{
return null;
}
}
public override async Task<UnixPath> ReadLinkAsync(UnixPath file, CancellationToken token = default)
{
if(await SymlinkExists2Async(upper,file,token))
{
return await upper.ReadLinkAsync(file,token);
}
return await lower.ReadLinkAsync(file,token);
}
public override async IAsyncEnumerable<UnixPath> EnumerateFileSystemEntriesAsync(UnixPath path, [EnumeratorCancellation] CancellationToken token = default)
{
List<UnixPath> entries = new List<UnixPath>();
if(await lower.DirectoryExistsAsync(path,token))
await foreach(var item in lower.EnumerateFileSystemEntriesAsync(path,token))
{
entries.Add(item);
yield return item;
}
if(await upper.DirectoryExistsAsync(path,token))
await foreach(var item in upper.EnumerateFileSystemEntriesAsync(path,token))
{
bool canYield=true;
foreach(var entry in entries)
{
if(entry.MyPathEquals(item)){canYield=false; break;};
}
if(canYield) yield return item;
}
}
public override async Task<bool> FileExistsAsync(UnixPath path, CancellationToken token = default)
{
return await lower.FileExistsAsync(path,token) || await upper.FileExistsAsync(path,token);
}
public override async Task<DateTime> GetCreationTimeAsync(UnixPath path, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token))
{
return await upper.GetCreationTimeAsync(path,token);
}
return await lower.GetCreationTimeAsync(path,token);
}
public override async Task<DateTime> GetLastAccessTimeAsync(UnixPath path, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token))
{
return await upper.GetLastAccessTimeAsync(path,token);
}
return await lower.GetLastAccessTimeAsync(path,token);
}
public override async Task<DateTime> GetLastWriteTimeAsync(UnixPath path, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(upper,path,token))
{
return await upper.GetLastWriteTimeAsync(path,token);
}
return await lower.GetLastWriteTimeAsync(path,token);
}
public override async Task MoveDirectoryAsync(UnixPath src, UnixPath dest)
{
if(!dest.IsRoot)
{
await upper.CreateDirectoryAsync(dest);
}
await foreach(var dir in this.EnumerateDirectoriesAsync(src))
{
await MoveDirectoryAsync(dir,dest / dir.Name);
}
await foreach(var file in this.EnumerateFilesAsync(src))
{
await MoveFileAsync(file,dest / file.Name);
}
if(await upper.DirectoryExistsAsync(src))
{
await upper.DeleteDirectoryAsync(src);
}
}
public override async Task MoveFileAsync(UnixPath src, UnixPath dest)
{
if(!dest.ParentIsRoot)
{
await upper.CreateDirectoryAsync(dest.Parent);
}
if(await upper.FileExistsAsync(src))
{
await upper.MoveFileAsync(src,dest);
}
else
{
using(var srcS = await lower.OpenAsync(src,FileMode.Open,FileAccess.Read,FileShare.Read))
{
using(var destS = await upper.OpenAsync(dest,FileMode.Create,FileAccess.ReadWrite,FileShare.Read))
{
await srcS.CopyToAsync(destS);
}
}
}
}
public override async Task<Stream> OpenAsync(UnixPath path, FileMode mode, FileAccess access, FileShare share, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token))
{
return await upper.OpenAsync(path,mode,access,share,token);
}
if(await lower.FileExistsAsync(path,token))
{
if(access != FileAccess.Read && mode != FileMode.CreateNew && mode != FileMode.Truncate && mode != FileMode.Create)
{
var strm = await lower.OpenAsync(path,mode,access,share,token);
return new CowStream(strm,path,lower,upper,mode,access,share);
}
return await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read);
}
return await upper.OpenAsync(path,mode,access,share,token);
}
public override async Task SetCreationTimeAsync(UnixPath path, DateTime time, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token))
{
await upper.SetCreationTimeAsync(path,time,token);
}
else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path) || await SymlinkExists2Async(lower,path,token))
{
if(await lower.FileExistsAsync(path,token))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token))
{
using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read))
{
await src.CopyToAsync(dest);
}
}
await upper.SetCreationTimeAsync(path,time,token);
}
else if(await lower.DirectoryExistsAsync(path,token))
{
await upper.CreateDirectoryAsync(path,token);
await upper.SetCreationTimeAsync(path,time,token);
}
else if(await SymlinkExists2Async(lower,path,token))
{
if(upper.CanHandleSymlinks(path))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token);
await upper.SetCreationTimeAsync(path,time,token);
}
}
}
else
{
await upper.SetCreationTimeAsync(path,time,token);
}
}
public override async Task SetLastAccessTimeAsync(UnixPath path, DateTime time, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token))
{
await upper.SetLastAccessTimeAsync(path,time,token);
}
else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path) || await SymlinkExists2Async(lower,path,token))
{
if(await lower.FileExistsAsync(path,token))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token))
{
using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read))
{
await src.CopyToAsync(dest);
}
}
await upper.SetLastAccessTimeAsync(path,time,token);
}
else if(await lower.DirectoryExistsAsync(path,token))
{
await upper.CreateDirectoryAsync(path,token);
await upper.SetLastAccessTimeAsync(path,time,token);
}
else if(await SymlinkExists2Async(lower,path,token))
{
if(upper.CanHandleSymlinks(path))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token);
await upper.SetLastAccessTimeAsync(path,time,token);
}
}
}
else
{
await upper.SetLastAccessTimeAsync(path,time,token);
}
}
public override async Task SetLastWriteTimeAsync(UnixPath path, DateTime time, CancellationToken token = default)
{
if(await upper.FileExistsAsync(path,token) || await upper.DirectoryExistsAsync(path) || await SymlinkExists2Async(upper,path,token))
{
await upper.SetLastWriteTimeAsync(path,time,token);
}
else if(await lower.FileExistsAsync(path,token) || await lower.DirectoryExistsAsync(path,token) || await SymlinkExists2Async(lower,path,token))
{
if(await lower.FileExistsAsync(path,token))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
using(var src = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token))
{
using(var dest = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Read))
{
await src.CopyToAsync(dest);
}
}
await upper.SetLastWriteTimeAsync(path,time,token);
}
else if(await lower.DirectoryExistsAsync(path,token))
{
await upper.CreateDirectoryAsync(path,token);
await upper.SetLastWriteTimeAsync(path,time,token);
}
else if(await SymlinkExists2Async(lower,path,token))
{
if(upper.CanHandleSymlinks(path))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
await upper.CreateSymlinkAsync(await lower.ReadLinkAsync(path,token),path,token);
await upper.SetLastWriteTimeAsync(path,time,token);
}
}
}else
{
await upper.SetLastWriteTimeAsync(path,time,token);
}
}
private class CowStream : Stream
{
private FileMode mode;
private FileAccess access;
private FileShare share;
private Stream strm;
private UnixPath path;
private IVirtualFilesystem upper;
private IVirtualFilesystem lower;
private bool inLower=true;
public CowStream(Stream strm, UnixPath path,IVirtualFilesystem lower, IVirtualFilesystem upper,FileMode mode,FileAccess access,FileShare share)
{
this.lower=lower;
this.strm = strm;
this.path = path;
this.upper = upper;
this.mode = mode;
this.access = access;
this.share = share;
}
public override bool CanRead => strm.CanRead;
public override bool CanSeek => strm.CanSeek;
public override bool CanWrite => strm.CanWrite;
public override long Length => strm.Length;
public override long Position { get => strm.Position; set => strm.Position = value;}
public override void Flush()
{
CowIt();
strm.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return strm.Read(buffer,offset,count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return strm.Seek(offset,origin);
}
public override void SetLength(long value)
{
CowIt();
strm.SetLength(value);
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return await strm.ReadAsync(buffer, offset,count);
//return base.ReadAsync(buffer, offset, count, cancellationToken);
}
public override int ReadByte()
{
return strm.ReadByte();
}
public override void WriteByte(byte value)
{
CowIt();
strm.WriteByte(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
CowIt();
strm.Write(buffer,offset,count);
}
public override async Task FlushAsync(CancellationToken cancellationToken)
{
await CowItAsync(cancellationToken);
await strm.FlushAsync(cancellationToken);
}
public override async Task WriteAsync(byte[] buffer, int offset, int count,CancellationToken token)
{
await CowItAsync(token);
await strm.WriteAsync(buffer,offset,count,token);
}
public override void Close()
{
strm.Close();
}
private async Task CowItAsync(CancellationToken token)
{
if(inLower)
{
var pos = strm.Position;
strm.Dispose();
using(var srcStrm = await lower.OpenAsync(path,FileMode.Open,FileAccess.Read,FileShare.Read,token))
{
if(!path.ParentIsRoot)
await upper.CreateDirectoryAsync(path.Parent);
using(var destStrm = await upper.OpenAsync(path,FileMode.Create,FileAccess.Write,FileShare.Write,token))
await srcStrm.CopyToAsync(destStrm,4096,token);
}
strm =await upper.OpenAsync(path,mode,access,share,token);
strm.Position = pos;
inLower=false;
}
}
private void CowIt()
{
if(inLower)
{
var pos = strm.Position;
strm.Dispose();
using(var srcStrm = lower.Open(path,FileMode.Open,FileAccess.Read,FileShare.Read))
{
if(!path.ParentIsRoot)
upper.CreateDirectory(path.Parent);
using(var destStrm = upper.Open(path,FileMode.Create,FileAccess.Write,FileShare.Write))
srcStrm.CopyTo(destStrm);
}
strm =upper.Open(path,mode,access,share);
strm.Position = pos;
inLower=false;
}
}
}
}
}