354 lines
9.8 KiB
C#
354 lines
9.8 KiB
C#
using System.Text;
|
|
using ICSharpCode.SharpZipLib.GZip;
|
|
using ICSharpCode.SharpZipLib.Tar;
|
|
using ICSharpCode.SharpZipLib.Zip;
|
|
using Mono.Unix.Native;
|
|
using MoonCore.Unix.SecureFs;
|
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers;
|
|
using MoonlightServers.DaemonShared.Enums;
|
|
|
|
namespace MoonlightServers.Daemon.Helpers;
|
|
|
|
public class ServerFileSystem
|
|
{
|
|
private readonly SecureFileSystem FileSystem;
|
|
|
|
public ServerFileSystem(SecureFileSystem fileSystem)
|
|
{
|
|
FileSystem = fileSystem;
|
|
}
|
|
|
|
public Task<ServerFileSystemResponse[]> ListAsync(string inputPath)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
var entries = FileSystem.ReadDir(path);
|
|
|
|
IEnumerable<SecureFsEntry> entryQuery = entries;
|
|
|
|
// Filter all lost+found directories on the root of the file system
|
|
// to hide the folder shown by virtual disk volumes
|
|
if (string.IsNullOrEmpty(inputPath) || inputPath == "/")
|
|
entryQuery = entryQuery.Where(x => x.Name != "lost+found");
|
|
|
|
var result = entryQuery
|
|
.Select(x => new ServerFileSystemResponse()
|
|
{
|
|
Name = x.Name,
|
|
IsFolder = x.IsDirectory,
|
|
Size = x.Size,
|
|
UpdatedAt = x.LastChanged,
|
|
CreatedAt = x.CreatedAt
|
|
})
|
|
.ToArray();
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
public Task MoveAsync(string inputOldPath, string inputNewPath)
|
|
{
|
|
var oldPath = Normalize(inputOldPath);
|
|
var newPath = Normalize(inputNewPath);
|
|
|
|
FileSystem.Rename(oldPath, newPath);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task DeleteAsync(string inputPath)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
FileSystem.RemoveAll(path);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task MkdirAsync(string inputPath)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
FileSystem.MkdirAll(path, FilePermissions.ACCESSPERMS);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task TouchAsync(string inputPath)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
var parentDirectory = Path.GetDirectoryName(path);
|
|
|
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
|
|
|
FileSystem.OpenFileWrite(
|
|
path,
|
|
_ => { },
|
|
OpenFlags.O_CREAT
|
|
); // We use these custom flags to ensure we aren't overwriting the file
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task CreateChunkAsync(string inputPath, long totalSize, long positionToSkip, Stream chunkStream)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
var parentDirectory = Path.GetDirectoryName(path);
|
|
|
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
|
|
|
FileSystem.OpenFileWrite(path, fileStream =>
|
|
{
|
|
if (fileStream.Length != totalSize)
|
|
fileStream.SetLength(totalSize);
|
|
|
|
fileStream.Position = positionToSkip;
|
|
|
|
chunkStream.CopyTo(fileStream);
|
|
fileStream.Flush();
|
|
}, OpenFlags.O_CREAT | OpenFlags.O_RDWR); // We use these custom flags to ensure we aren't overwriting the file
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task CreateAsync(string inputPath, Stream dataStream)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
var parentDirectory = Path.GetDirectoryName(path);
|
|
|
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
|
|
|
FileSystem.OpenFileWrite(path, stream =>
|
|
{
|
|
stream.Position = 0;
|
|
dataStream.CopyTo(stream);
|
|
|
|
stream.Flush();
|
|
});
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task ReadAsync(string inputPath, Func<Stream, Task> onHandle)
|
|
{
|
|
var path = Normalize(inputPath);
|
|
|
|
FileSystem.OpenFileRead(path, stream =>
|
|
{
|
|
// No try catch here because the safe fs abstraction already handles every error occuring in the handle
|
|
onHandle.Invoke(stream).Wait();
|
|
});
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
#region Compression
|
|
|
|
public Task CompressAsync(string[] itemsInput, string destinationInput, CompressType type)
|
|
{
|
|
var destination = Normalize(destinationInput);
|
|
var items = itemsInput.Select(Normalize);
|
|
|
|
if (type == CompressType.Zip)
|
|
{
|
|
FileSystem.OpenFileWrite(destination, stream =>
|
|
{
|
|
using var zipStream = new ZipOutputStream(stream);
|
|
|
|
foreach (var item in items)
|
|
AddItemToZip(item, zipStream);
|
|
|
|
zipStream.Flush();
|
|
stream.Flush();
|
|
|
|
zipStream.Close();
|
|
});
|
|
}
|
|
else if (type == CompressType.TarGz)
|
|
{
|
|
FileSystem.OpenFileWrite(destination, stream =>
|
|
{
|
|
using var gzStream = new GZipOutputStream(stream);
|
|
using var tarStream = new TarOutputStream(gzStream, Encoding.UTF8);
|
|
|
|
foreach (var item in items)
|
|
AddItemToTar(item, tarStream);
|
|
|
|
tarStream.Flush();
|
|
gzStream.Flush();
|
|
stream.Flush();
|
|
|
|
tarStream.Close();
|
|
gzStream.Close();
|
|
});
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task DecompressAsync(string pathInput, string destinationInput, CompressType type)
|
|
{
|
|
var path = Normalize(pathInput);
|
|
var destination = Normalize(destinationInput);
|
|
|
|
if (type == CompressType.Zip)
|
|
{
|
|
FileSystem.OpenFileRead(path, fileStream =>
|
|
{
|
|
var zipInputStream = new ZipInputStream(fileStream);
|
|
|
|
ExtractZip(zipInputStream, destination);
|
|
});
|
|
}
|
|
else if (type == CompressType.TarGz)
|
|
{
|
|
FileSystem.OpenFileRead(path, fileStream =>
|
|
{
|
|
var gzInputStream = new GZipInputStream(fileStream);
|
|
var zipInputStream = new TarInputStream(gzInputStream, Encoding.UTF8);
|
|
|
|
ExtractTar(zipInputStream, destination);
|
|
});
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void AddItemToZip(string path, ZipOutputStream outputStream)
|
|
{
|
|
var item = FileSystem.Stat(path);
|
|
|
|
if (item.IsDirectory)
|
|
{
|
|
var contents = FileSystem.ReadDir(path);
|
|
|
|
foreach (var content in contents)
|
|
{
|
|
AddItemToZip(
|
|
Path.Combine(path, content.Name),
|
|
outputStream
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var entry = new ZipEntry(path)
|
|
{
|
|
Size = item.Size,
|
|
DateTime = item.LastChanged
|
|
};
|
|
|
|
outputStream.PutNextEntry(entry);
|
|
|
|
FileSystem.OpenFileRead(path, stream => { stream.CopyTo(outputStream); });
|
|
|
|
outputStream.CloseEntry();
|
|
}
|
|
}
|
|
|
|
private void AddItemToTar(string path, TarOutputStream outputStream)
|
|
{
|
|
var item = FileSystem.Stat(path);
|
|
|
|
if (item.IsDirectory)
|
|
{
|
|
var contents = FileSystem.ReadDir(path);
|
|
|
|
foreach (var content in contents)
|
|
{
|
|
AddItemToTar(
|
|
Path.Combine(path, content.Name),
|
|
outputStream
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var entry = TarEntry.CreateTarEntry(path);
|
|
|
|
entry.Name = path;
|
|
entry.Size = item.Size;
|
|
entry.ModTime = item.LastChanged;
|
|
|
|
outputStream.PutNextEntry(entry);
|
|
|
|
FileSystem.OpenFileRead(path, stream => { stream.CopyTo(outputStream); });
|
|
|
|
outputStream.CloseEntry();
|
|
}
|
|
}
|
|
|
|
private void ExtractZip(ZipInputStream inputStream, string destination)
|
|
{
|
|
while (true)
|
|
{
|
|
var entry = inputStream.GetNextEntry();
|
|
|
|
if (entry == null)
|
|
break;
|
|
|
|
if (entry.IsDirectory)
|
|
continue;
|
|
|
|
var fileDestination = Path.Combine(destination, entry.Name);
|
|
|
|
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
|
|
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
|
|
|
FileSystem.OpenFileWrite(fileDestination, stream =>
|
|
{
|
|
stream.Position = 0;
|
|
|
|
inputStream.CopyTo(stream);
|
|
|
|
stream.Flush();
|
|
}); // This will override the file if it exists
|
|
}
|
|
}
|
|
|
|
private void ExtractTar(TarInputStream inputStream, string destination)
|
|
{
|
|
while (true)
|
|
{
|
|
var entry = inputStream.GetNextEntry();
|
|
|
|
if (entry == null)
|
|
break;
|
|
|
|
if (entry.IsDirectory)
|
|
continue;
|
|
|
|
var fileDestination = Path.Combine(destination, entry.Name);
|
|
|
|
var parentDirectory = Path.GetDirectoryName(fileDestination);
|
|
|
|
if (!string.IsNullOrEmpty(parentDirectory) && parentDirectory != "/")
|
|
FileSystem.MkdirAll(parentDirectory, FilePermissions.ACCESSPERMS);
|
|
|
|
FileSystem.OpenFileWrite(fileDestination, stream =>
|
|
{
|
|
stream.Position = 0;
|
|
|
|
inputStream.CopyTo(stream);
|
|
|
|
stream.Flush();
|
|
}); // This will override the file if it exists
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private string Normalize(string path)
|
|
{
|
|
return path
|
|
.Replace("//", "/")
|
|
.Replace("..", "")
|
|
.TrimStart('/');
|
|
}
|
|
} |