pkNX/pkNX.Containers/FolderContainer.cs
Kurt 0936c08eb1 LZA 1.0.2
Cumulative changes from the team.

Co-Authored-By: Matt <17801814+sora10pls@users.noreply.github.com>
Co-Authored-By: SciresM <8676005+SciresM@users.noreply.github.com>
Co-Authored-By: Lusamine <30205550+Lusamine@users.noreply.github.com>
2025-11-16 15:56:12 -06:00

139 lines
4.0 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace pkNX.Containers;
public class FolderContainer : IFileContainer
{
private readonly List<string> Paths = [];
private readonly List<byte[]?> Data = [];
private readonly List<bool> TrackModify = [];
public string? FilePath { get; set; }
public FolderContainer() { }
public FolderContainer(IEnumerable<string> files) => AddFiles(files);
public FolderContainer(string path) => FilePath = path;
public FolderContainer(string path, Func<string, bool> filter) : this(path) => Initialize(filter);
public IReadOnlyList<string> GetPaths() => Paths;
public IReadOnlyList<string> GetFileNames() => Paths.ConvertAll(Path.GetFileName)!;
public void Initialize(Func<string, bool>? filter = null)
{
if (Paths.Count > 0)
return; // already initialized
if (FilePath is null)
throw new ArgumentException("No path specified to initialize from.");
IEnumerable<string> files = Directory.GetFiles(FilePath, "*", SearchOption.AllDirectories);
if (filter != null)
files = files.Where(filter);
files = files.OrderBy(z => Path.GetFileName(z).Length); // alphabetical sorting doesn't play nice with 100 & 1000
AddFiles(files);
}
public void AddFile(string file, byte[]? data = null)
{
Paths.Add(file);
Data.Add(data);
TrackModify.Add(data != null);
}
public void AddFiles(IEnumerable<string> files)
{
foreach (var f in files)
AddFile(f);
}
public bool TryGetFileData(string file, out ReadOnlySpan<byte> data)
{
data = [];
var index = GetFileIndex(file);
if (index < 0)
return false;
string path = Paths[index];
data = Data[index] ??= FileMitm.ReadAllBytes(path);
return true;
}
public ReadOnlySpan<byte> GetFileData(string file)
{
if (TryGetFileData(file, out var data))
return data;
throw new ArgumentException($"File not found: {file}", nameof(file));
}
public byte[] GetFileData(int index)
{
var data = Data[index] ??= FileMitm.ReadAllBytes(Paths[index]);
return (byte[])data.Clone();
}
public byte[] this[int index]
{
get => GetFileData(index);
set
{
var current = Data[index] ??= GetFileData(index);
TrackModify[index] = !value.SequenceEqual(current);
Data[index] = value;
}
}
public void ResetIndex(int index)
{
Data[index] = null;
TrackModify[index] = false;
}
public string GetFileName(int index) => Paths[index];
public int GetFileIndex(string fileName) => Paths.FindIndex(z => Path.GetFileName(z) == fileName);
public bool Modified
{
get => TrackModify.Contains(true);
set { if (!value) CancelEdits(); }
}
public int Count => Paths.Count;
public Task<byte[][]> GetFiles() => Task.FromResult(Paths.Select(FileMitm.ReadAllBytes).ToArray());
public Task<byte[]> GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(SaveAll, token);
private void SaveAll()
{
for (int i = 0; i < Paths.Count; i++)
{
if (!TrackModify[i])
continue;
var data = Data[i];
if (data == null)
continue;
FileMitm.WriteAllBytes(Paths[i], data);
}
}
public void CancelEdits()
{
for (int i = 0; i < TrackModify.Count; i++)
{
TrackModify[i] = false;
Data[i] = null;
}
}
public void Dump(string? path = null, ContainerHandler? handler = null)
{
SaveAll(); // there's really nothing to dump, just save any modified
}
}