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 GetAll() { var result = new List(); 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 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, MetadataFile = source.MetadataFile, Metadata = new(source.Metadata, StringComparer.OrdinalIgnoreCase), 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."); } }