142 lines
6.3 KiB
C#
142 lines
6.3 KiB
C#
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, string? continuationLayoutSource = null)
|
|
{
|
|
var path = Path.Combine(_directory, SafeId(manifest.Id) + TemplatePackage.Extension);
|
|
TemplatePackage.Create(path, manifest, layoutSource, assets, continuationLayoutSource);
|
|
return ToItem(manifest, path);
|
|
}
|
|
|
|
public StarterTemplateItem Import(string sourcePath)
|
|
{
|
|
var (template, layout, continuationLayout) = LoadPackage(sourcePath);
|
|
return Save(template.Manifest, layout, template.Assets, continuationLayout);
|
|
}
|
|
|
|
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, continuationLayout) = LoadWithContinuation(item);
|
|
var id = CreateUniqueId(template.Manifest.Id + "-kopie");
|
|
var manifest = CopyManifest(template.Manifest, id, template.Manifest.Name + " - Kopie");
|
|
return Save(manifest, layout, template.Assets, continuationLayout);
|
|
}
|
|
|
|
public (LoadedTemplate Template, string LayoutSource) Load(StarterTemplateItem item)
|
|
{
|
|
var (template, layout, _) = LoadPackage(item.PackagePath);
|
|
return (template, layout);
|
|
}
|
|
|
|
public (LoadedTemplate Template, string LayoutSource, string? ContinuationLayoutSource) LoadWithContinuation(StarterTemplateItem item) =>
|
|
LoadPackage(item.PackagePath);
|
|
|
|
public void Delete(StarterTemplateItem item)
|
|
{
|
|
if (File.Exists(item.PackagePath)) File.Delete(item.PackagePath);
|
|
}
|
|
|
|
private (LoadedTemplate Template, string LayoutSource, string? ContinuationLayoutSource) 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.");
|
|
string layoutSource;
|
|
using (var reader = new StreamReader(layoutEntry.Open())) layoutSource = reader.ReadToEnd();
|
|
string? continuationLayoutSource = null;
|
|
if (template.Manifest.ContinuationLayoutFile is { } continuationPath)
|
|
{
|
|
var continuationEntry = archive.Entries.First(x => x.FullName.Equals(
|
|
continuationPath.Replace('\\', '/'), StringComparison.OrdinalIgnoreCase));
|
|
using var reader = new StreamReader(continuationEntry.Open());
|
|
continuationLayoutSource = reader.ReadToEnd();
|
|
}
|
|
return (template, layoutSource, continuationLayoutSource);
|
|
}
|
|
|
|
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,
|
|
ContinuationLayoutFile = source.ContinuationLayoutFile,
|
|
MetadataFile = source.MetadataFile,
|
|
Metadata = new(source.Metadata, StringComparer.OrdinalIgnoreCase),
|
|
Placeholders = source.Placeholders.Select(x => new PlaceholderDefinition(x.Name, x.Type, x.Required,
|
|
x.IsConstant, x.ConstantValue, x.Bold, x.Italic, x.Underline)).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.");
|
|
}
|
|
}
|