Added GetLastUpdated() to ISyncer interface. Updated code to use new /ping endpoint. Added GetLastUpdated() REST API call to fetch when the database was last updated. Clarified logging for when an Update is from a file, or from the REST API Server. Added logging for when a nw File Entry is processed, or when a File Entry is Updated.
241 lines
No EOL
7.4 KiB
C#
241 lines
No EOL
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Threading.Tasks;
|
|
using FreeTubeSyncer.Library;
|
|
using FreeTubeSyncer.Models.DatabaseModels;
|
|
using RestSharp;
|
|
|
|
namespace FreeTubeSyncer.REST;
|
|
|
|
public interface ISyncer
|
|
{
|
|
Task ReadDatabase();
|
|
Task FetchDatabase();
|
|
void HandleDatabaseChange(string dbName, string entryObject);
|
|
void Sync();
|
|
bool IsDirty();
|
|
void Enable();
|
|
void Disable();
|
|
bool IsEnabled();
|
|
|
|
void UpdateBaseUrl(string baseUrl);
|
|
void SetEnabled(bool enabled);
|
|
Task<bool> PingApi();
|
|
Task<DateTime> GetLastUpdated();
|
|
}
|
|
|
|
public class Syncer<T> : ISyncer where T : class, IDataModel, new()
|
|
{
|
|
private List<T> _entries = new List<T>();
|
|
private RestClient _client;
|
|
private string _dbPath;
|
|
private string _dbName;
|
|
private string _restEndpoint;
|
|
private DBSyncWatcher _watcher;
|
|
|
|
private bool _isDirty = false;
|
|
private bool _syncing = false;
|
|
private bool _enabled = true;
|
|
|
|
public Syncer(DBSyncWatcher watcher, string dbPath, string dbName, string restBaseUrl, string restEndpoint)
|
|
{
|
|
_watcher = watcher;
|
|
_watcher.WatchFiles[dbName] = typeof(T);
|
|
_watcher.OnDatabaseChange += HandleDatabaseChange;
|
|
_client = new RestClient(new RestClientOptions(restBaseUrl));
|
|
_dbPath = dbPath;
|
|
_dbName = dbName;
|
|
_restEndpoint = restEndpoint;
|
|
}
|
|
|
|
public bool IsDirty() => _isDirty;
|
|
public bool IsEnabled() => _enabled;
|
|
public void Enable() => _enabled = true;
|
|
public void Disable() => _enabled = false;
|
|
public void SetEnabled(bool enabled) => _enabled = enabled;
|
|
|
|
public void UpdateBaseUrl(string baseUrl)
|
|
{
|
|
_client.Dispose();
|
|
_client = new RestClient(new RestClientOptions(baseUrl));
|
|
}
|
|
|
|
public async Task<bool> PingApi()
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Pinging API at {_client.BuildUri(new RestRequest("/ping"))}...");
|
|
try
|
|
{
|
|
var res = await _client.GetAsync<Ping>(new RestRequest("/ping"));
|
|
if (res == null)
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Ping returned null, not the server we are looking for!");
|
|
return false;
|
|
}
|
|
|
|
if (res.AppVersion == "0.1.3")
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Server Online! {res.AppVersion}");
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Network Error: {ex.Message}, API not alive.");
|
|
return false;
|
|
}
|
|
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine("Responded with something other then 404, API not what we expected.");
|
|
return false;
|
|
}
|
|
|
|
public async Task<DateTime> GetLastUpdated()
|
|
{
|
|
var res = await _client.GetAsync<UpdateCheck>("/ping/lastUpdated");
|
|
return res?.LastUpdated ?? DateTime.MinValue;
|
|
}
|
|
|
|
public async Task ReadDatabase()
|
|
{
|
|
if (!_enabled) return;
|
|
var lines = File.ReadAllLines(_dbPath);
|
|
foreach (var entry in lines)
|
|
{
|
|
if (entry == "") continue;
|
|
T? item;
|
|
try
|
|
{
|
|
item = JsonSerializer.Deserialize<T>(entry, GlobalJsonOptions.Options);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var jobj = JsonSerializer.Deserialize<JsonObject>(entry, GlobalJsonOptions.Options);
|
|
item = new T();
|
|
item.MarshalData(jobj["_id"].GetValue<string>(), entry);
|
|
}
|
|
if (item == null) continue;
|
|
item.MarshalData(item.Id(), entry);
|
|
if (_entries.Any(x => x.EqualId(item.Id())))
|
|
_entries.RemoveAll(x => x.EqualId(item.Id()));
|
|
_entries.Add(item);
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Posting to REST: {item.Id()}");
|
|
await _client.PostJsonAsync<T>(_restEndpoint, item);
|
|
}
|
|
}
|
|
|
|
public async Task FetchDatabase()
|
|
{
|
|
if (!_enabled) return;
|
|
var entries = await _client.GetAsync<List<T>>(_restEndpoint);
|
|
if (entries == null) return;
|
|
foreach (var entry in entries)
|
|
{
|
|
if (_entries.Any(x => x.EqualId(entry.Id())))
|
|
{
|
|
var data = _entries.First(x => x.EqualId(entry.Id()));
|
|
|
|
if (data.Equals(entry)) continue;
|
|
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Updated Entry from REST for {_dbName} - {entry.Id()}");
|
|
_entries.RemoveAll(x => x.EqualId(entry.Id()));
|
|
}
|
|
else
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"New Entry from REST for {_dbName} - {entry.Id()}");
|
|
}
|
|
|
|
_entries.Add(entry);
|
|
_isDirty = true;
|
|
}
|
|
}
|
|
|
|
public async void HandleDatabaseChange(string dbName, string entryObject)
|
|
{
|
|
if (!_enabled) return;
|
|
if (dbName != _dbName)
|
|
return;
|
|
|
|
if (_syncing)
|
|
return;
|
|
|
|
T? entry;
|
|
try
|
|
{
|
|
entry = JsonSerializer.Deserialize<T>(entryObject, GlobalJsonOptions.Options);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try
|
|
{
|
|
var jobj = JsonSerializer.Deserialize<JsonObject>(entryObject, GlobalJsonOptions.Options);
|
|
entry = new T();
|
|
entry.MarshalData(jobj["_id"].GetValue<string>(), entryObject);
|
|
}
|
|
catch (Exception iex)
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Failed to parse line: {entryObject}\nMessage: {iex.Message}");
|
|
entry = null;
|
|
}
|
|
}
|
|
|
|
if (entry == null) return;
|
|
entry.MarshalData(entry.Id(), entryObject);
|
|
|
|
if (_entries.Any(x => x.EqualId(entry.Id())))
|
|
{
|
|
var data = _entries.First(x => x.EqualId(entry.Id()));
|
|
if (data.Equals(entry)) return;
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"File Entry {entry.Id()} updated for {_dbName}");
|
|
_entries.RemoveAll(x => x.EqualId(entry.Id()));
|
|
}
|
|
else
|
|
{
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"New File Entry {entry.Id()} for {_dbName}");
|
|
}
|
|
|
|
_entries.Add(entry);
|
|
await _client.PostJsonAsync<T>(_restEndpoint, entry);
|
|
}
|
|
|
|
public void Sync()
|
|
{
|
|
if (!_enabled) return;
|
|
if (!_isDirty)
|
|
return;
|
|
_syncing = true;
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Syncing {_dbPath}...");
|
|
var json = new List<string>();
|
|
foreach (var entry in _entries)
|
|
json.Add(entry.JsonData());
|
|
_watcher.Locked = true;
|
|
using (var fh = File.OpenWrite(_dbPath))
|
|
{
|
|
foreach (var line in json)
|
|
fh.Write(Encoding.UTF8.GetBytes(line + "\n"));
|
|
fh.Flush();
|
|
fh.Close();
|
|
}
|
|
_watcher.Locked = false;
|
|
// TODO: Replace with Logger
|
|
Console.WriteLine($"Updated {_dbPath}.");
|
|
_isDirty = false;
|
|
_syncing = false;
|
|
}
|
|
} |