/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: CMNext
* Package: Content.Publishing.Blog.Admin
* File: JsonRecordDb.cs
*
* CMNext is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* CMNext is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;
using VNLib.Utils.IO;
using VNLib.Utils.Extensions;
namespace Content.Publishing.Blog.Admin.Model
{
///
/// A json backed record database
///
/// The record type
internal class JsonRecordDb : IRecordDb where T : IRecord
{
private static readonly Version CurrentVersion = new (0, 1, 0);
private DateTimeOffset _lastModified;
private Version? _version;
/*
* Records in the list are only ever read from, any changes are made by
* creating a new list and re-ordering it.
*
* We dont need any synchronization in this case
*/
private IReadOnlyList _records;
public JsonRecordDb()
{
_lastModified = DateTimeOffset.UnixEpoch;
_records = new List();
}
///
public T? GetRecord(string id)
{
return _records.SingleOrDefault(r => r.Id!.Equals(id, StringComparison.OrdinalIgnoreCase));
}
///
public IEnumerable GetRecords()
{
return _records;
}
///
public void RemoveRecord(string id)
{
_ = id ?? throw new ArgumentNullException(nameof(id));
//Create new list without the record and re-order
_records = _records.Where(r => !id.Equals(r.Id, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(static r => r.Date)
.ToList();
}
///
public void SetRecord(T record)
{
_ = record?.Id ?? throw new ArgumentNullException(nameof(record));
//Remove record if it already exists
RemoveRecord(record.Id);
//Add the record and re-order
_records = _records.Append(record)
.OrderByDescending(static r => r.Date)
.ToList();
//Update last modified time
_lastModified = DateTimeOffset.UtcNow;
}
///
public void Load(Stream stream)
{
if (stream.Length == 0)
{
//Set defaults
_lastModified = DateTimeOffset.UnixEpoch;
_records = new List();
}
else
{
//Read stream into a doc
using JsonDocument doc = JsonDocument.Parse(stream);
//Read the last modified time
_lastModified = DateTimeOffset.FromUnixTimeSeconds(doc.RootElement.GetProperty("last_modified").GetInt64());
//Try to read the version
if (doc.RootElement.TryGetProperty("version", out JsonElement versionEl))
{
_ = Version.TryParse(versionEl.GetString(), out _version);
}
if (doc.RootElement.TryGetProperty("records", out JsonElement el))
{
//Read the records array
_records = el.Deserialize>() ?? new List();
}
else
{
//Set defaults
_records = new List();
}
}
}
///
public void Store(Stream stream)
{
using Utf8JsonWriter writer = new(stream);
writer.WriteStartObject();
//Write last modified time
writer.WriteNumber("last_modified", _lastModified.ToUnixTimeSeconds());
//Set version if not already set
_version ??= CurrentVersion;
//Write version
writer.WriteString("version", _version.ToString());
//Write the records array
writer.WritePropertyName("records");
JsonSerializer.Serialize(writer, _records, VNLib.Plugins.Essentials.Statics.SR_OPTIONS);
writer.WriteEndObject();
}
///
/// Create a new of a given type
///
/// The new record store
public static JsonRecordDb Create()
{
return new JsonRecordDb();
}
}
}