This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using System.IO.Compression;
|
||||
using LehrerApp.Templating;
|
||||
|
||||
namespace LehrerApp.TemplateDesigner;
|
||||
|
||||
public sealed record StarterTemplateItem(
|
||||
string Id, string Name, string Description, string PackagePath, DateTime UpdatedAtUtc)
|
||||
{
|
||||
public string Display => string.IsNullOrWhiteSpace(Description) ? Name : $"{Name} · {Description}";
|
||||
}
|
||||
|
||||
public sealed class StarterTemplateLibrary
|
||||
{
|
||||
private readonly string _directory;
|
||||
private readonly ITemplateLoader _loader;
|
||||
|
||||
public StarterTemplateLibrary(string? directory = null, ITemplateLoader? loader = null)
|
||||
{
|
||||
_directory = directory ?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"LehrerApp", "TemplateDesigner", "starter-templates");
|
||||
Directory.CreateDirectory(_directory);
|
||||
_loader = loader ?? new TemplateLoader();
|
||||
}
|
||||
|
||||
public IReadOnlyList<StarterTemplateItem> GetAll()
|
||||
{
|
||||
var result = new List<StarterTemplateItem>();
|
||||
foreach (var path in Directory.EnumerateFiles(_directory, $"*{TemplatePackage.Extension}"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var template = _loader.LoadFromPackage(path);
|
||||
result.Add(ToItem(template.Manifest, path));
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException) { }
|
||||
}
|
||||
return result.OrderBy(x => x.Name, StringComparer.CurrentCultureIgnoreCase).ToList();
|
||||
}
|
||||
|
||||
public StarterTemplateItem Save(TemplateManifest manifest, string layoutSource,
|
||||
IReadOnlyDictionary<string, byte[]> assets)
|
||||
{
|
||||
var path = Path.Combine(_directory, SafeId(manifest.Id) + TemplatePackage.Extension);
|
||||
TemplatePackage.Create(path, manifest, layoutSource, assets);
|
||||
return ToItem(manifest, path);
|
||||
}
|
||||
|
||||
public StarterTemplateItem Import(string sourcePath)
|
||||
{
|
||||
var (template, layout) = LoadPackage(sourcePath);
|
||||
return Save(template.Manifest, layout, template.Assets);
|
||||
}
|
||||
|
||||
public void Export(StarterTemplateItem item, string destinationPath)
|
||||
{
|
||||
if (!File.Exists(item.PackagePath)) throw new FileNotFoundException("Ausgangsvorlage nicht gefunden.", item.PackagePath);
|
||||
if (!string.Equals(Path.GetExtension(destinationPath), TemplatePackage.Extension, StringComparison.OrdinalIgnoreCase))
|
||||
destinationPath += TemplatePackage.Extension;
|
||||
var directory = Path.GetDirectoryName(destinationPath);
|
||||
if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory);
|
||||
File.Copy(item.PackagePath, destinationPath, overwrite: true);
|
||||
}
|
||||
|
||||
public StarterTemplateItem Duplicate(StarterTemplateItem item)
|
||||
{
|
||||
var (template, layout) = Load(item);
|
||||
var id = CreateUniqueId(template.Manifest.Id + "-kopie");
|
||||
var manifest = CopyManifest(template.Manifest, id, template.Manifest.Name + " - Kopie");
|
||||
return Save(manifest, layout, template.Assets);
|
||||
}
|
||||
|
||||
public (LoadedTemplate Template, string LayoutSource) Load(StarterTemplateItem item) =>
|
||||
LoadPackage(item.PackagePath);
|
||||
|
||||
public void Delete(StarterTemplateItem item)
|
||||
{
|
||||
if (File.Exists(item.PackagePath)) File.Delete(item.PackagePath);
|
||||
}
|
||||
|
||||
private (LoadedTemplate Template, string LayoutSource) LoadPackage(string path)
|
||||
{
|
||||
var template = _loader.LoadFromPackage(path);
|
||||
using var archive = ZipFile.OpenRead(path);
|
||||
var layoutEntry = archive.Entries.FirstOrDefault(x => x.FullName.Equals(
|
||||
template.Manifest.LayoutFile.Replace('\\', '/'), StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new InvalidDataException($"Layoutdatei „{template.Manifest.LayoutFile}“ fehlt.");
|
||||
using var reader = new StreamReader(layoutEntry.Open());
|
||||
return (template, reader.ReadToEnd());
|
||||
}
|
||||
|
||||
public string CreateUniqueId(string baseId)
|
||||
{
|
||||
var existing = GetAll().Select(x => x.Id).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
if (!existing.Contains(baseId)) return baseId;
|
||||
for (var index = 2; ; index++)
|
||||
{
|
||||
var candidate = $"{baseId}-{index}";
|
||||
if (!existing.Contains(candidate)) return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
private static TemplateManifest CopyManifest(TemplateManifest source, string id, string name) => new()
|
||||
{
|
||||
SchemaVersion = source.SchemaVersion,
|
||||
Id = id,
|
||||
Name = name,
|
||||
Description = source.Description,
|
||||
PageSize = new(source.PageSize.Width, source.PageSize.Height, source.PageSize.Unit),
|
||||
LayoutFile = source.LayoutFile,
|
||||
Placeholders = source.Placeholders.Select(x => new PlaceholderDefinition(x.Name, x.Type, x.Required)).ToList(),
|
||||
};
|
||||
|
||||
private static StarterTemplateItem ToItem(TemplateManifest manifest, string path) =>
|
||||
new(manifest.Id, manifest.Name, manifest.Description, path, File.GetLastWriteTimeUtc(path));
|
||||
|
||||
private static string SafeId(string id)
|
||||
{
|
||||
var safe = string.Concat(id.Select(c => char.IsAsciiLetterOrDigit(c) || c == '-' ? c : '-')).Trim('-');
|
||||
return safe.Length > 0 ? safe : throw new InvalidDataException("Die Vorlagen-ID ist ungültig.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user