Worked on Syncing Logic
Created Syncer Generic Class to handle Monitoring, and Syncing between local database, and remote REST server. Temporarily changed Program.cs from an Avalonia UI app, to a Console App for testing and debugging purposes.
This commit is contained in:
parent
44a89ad589
commit
24fae2b7ac
10 changed files with 162 additions and 109 deletions
|
|
@ -41,6 +41,7 @@ public class DBSyncWatcher
|
||||||
var data = File.ReadAllText(e.FullPath);
|
var data = File.ReadAllText(e.FullPath);
|
||||||
foreach (var line in data.Split('\n'))
|
foreach (var line in data.Split('\n'))
|
||||||
{
|
{
|
||||||
|
if (line == "") continue;
|
||||||
var type = WatchFiles[dbName];
|
var type = WatchFiles[dbName];
|
||||||
var item = JsonSerializer.Deserialize(line, type);
|
var item = JsonSerializer.Deserialize(line, type);
|
||||||
if (item == null) continue;
|
if (item == null) continue;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public class History
|
public class History : IDataModel
|
||||||
{
|
{
|
||||||
public string _id { get; set; } = string.Empty;
|
public string _id { get; set; } = string.Empty;
|
||||||
public string videoId { get; set; } = string.Empty;
|
public string videoId { get; set; } = string.Empty;
|
||||||
|
|
@ -20,4 +20,7 @@ public class History
|
||||||
public string type { get; set; } = string.Empty;
|
public string type { get; set; } = string.Empty;
|
||||||
public string lastViewedPlaylistType { get; set; } = string.Empty;
|
public string lastViewedPlaylistType { get; set; } = string.Empty;
|
||||||
public string? lastViewedPlaylistItemId { get; set; }
|
public string? lastViewedPlaylistItemId { get; set; }
|
||||||
|
|
||||||
|
public string Id() => _id;
|
||||||
|
public bool EqualId(string oid) => _id == oid;
|
||||||
}
|
}
|
||||||
7
FreeTubeSyncer/Models/DatabaseModels/IDataModel.cs
Normal file
7
FreeTubeSyncer/Models/DatabaseModels/IDataModel.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
|
public interface IDataModel
|
||||||
|
{
|
||||||
|
string Id();
|
||||||
|
bool EqualId(string oid);
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public class Playlist
|
public class Playlist : IDataModel
|
||||||
{
|
{
|
||||||
public string _id { get; set; } = string.Empty;
|
public string _id { get; set; } = string.Empty;
|
||||||
public string playlistName { get; set; } = string.Empty;
|
public string playlistName { get; set; } = string.Empty;
|
||||||
|
|
@ -12,4 +12,7 @@ public class Playlist
|
||||||
public List<Video> videos { get; set; } = [];
|
public List<Video> videos { get; set; } = [];
|
||||||
public long createdAt { get; set; }
|
public long createdAt { get; set; }
|
||||||
public long lastUpdatedAt { get; set; }
|
public long lastUpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public string Id() => _id;
|
||||||
|
public bool EqualId(string oid) => _id == oid;
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,14 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public class Profile
|
public class Profile : IDataModel
|
||||||
{
|
{
|
||||||
public string _id { get; set; } = string.Empty;
|
public string _id { get; set; } = string.Empty;
|
||||||
public string name { get; set; } = string.Empty;
|
public string name { get; set; } = string.Empty;
|
||||||
public string bgColor { get; set; } = string.Empty;
|
public string bgColor { get; set; } = string.Empty;
|
||||||
public string textColor { get; set; } = string.Empty;
|
public string textColor { get; set; } = string.Empty;
|
||||||
public List<Subscription> subscriptions { get; set; } = [];
|
public List<Subscription> subscriptions { get; set; } = [];
|
||||||
|
|
||||||
|
public string Id() => _id;
|
||||||
|
public bool EqualId(string oid) => _id == oid;
|
||||||
}
|
}
|
||||||
|
|
@ -3,8 +3,11 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public class SearchHistory
|
public class SearchHistory : IDataModel
|
||||||
{
|
{
|
||||||
public string _id { get; set; } = string.Empty;
|
public string _id { get; set; } = string.Empty;
|
||||||
public long lastUpdatedAt { get; set; }
|
public long lastUpdatedAt { get; set; }
|
||||||
|
|
||||||
|
public string Id() => _id;
|
||||||
|
public bool EqualId(string oid) => _id == oid;
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Text.Json;
|
||||||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
public class Setting
|
public class Setting : IDataModel
|
||||||
{
|
{
|
||||||
#pragma warning disable CS8618
|
#pragma warning disable CS8618
|
||||||
public string _id { get; set; } = string.Empty;
|
public string _id { get; set; } = string.Empty;
|
||||||
|
|
@ -18,4 +18,7 @@ public class Setting
|
||||||
#pragma warning restore CS8603
|
#pragma warning restore CS8603
|
||||||
set => ValueJson = JsonSerializer.Serialize(value);
|
set => ValueJson = JsonSerializer.Serialize(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Id() => _id;
|
||||||
|
public bool EqualId(string oid) => _id == oid;
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FreeTubeSyncer.Library;
|
||||||
|
using FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
using FreeTubeSyncer.REST;
|
||||||
|
|
||||||
namespace FreeTubeSyncer;
|
namespace FreeTubeSyncer;
|
||||||
|
|
||||||
|
|
@ -9,8 +18,43 @@ class Program
|
||||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
// yet and stuff might break.
|
// yet and stuff might break.
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
public static void Main(string[] args)
|
||||||
.StartWithClassicDesktopLifetime(args);
|
{
|
||||||
|
var path = "/home/eumario/.var/app/io.freetubeapp.FreeTube/config/FreeTube/";
|
||||||
|
var dbWatcher = new DBSyncWatcher(path);
|
||||||
|
var historySyncer = new Syncer<History>(dbWatcher, Path.Join(path, "history.db"), "history.db", "/history");
|
||||||
|
var playlistSyncer = new Syncer<Playlist>(dbWatcher, Path.Join(path, "playlists.db"), "playlists.db", "/playlist");
|
||||||
|
var profileSyncer = new Syncer<Profile>(dbWatcher, Path.Join(path, "profiles.db"), "profiles.db", "/profile");
|
||||||
|
var searchHistorySyncer = new Syncer<SearchHistory>(dbWatcher, Path.Join(path, "search-history.db"), "search-history.db", "/search");
|
||||||
|
var settingsSyncer = new Syncer<Setting>(dbWatcher, Path.Join(path, "settings.db"), "settings.db", "/settings");
|
||||||
|
|
||||||
|
Task.Run(() => CheckCanSync(historySyncer, playlistSyncer, profileSyncer, searchHistorySyncer, settingsSyncer));
|
||||||
|
Console.WriteLine("Watching databases... Press return to exit...");
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckCanSync(Syncer<History>? historySyncer = null, Syncer<Playlist>? playlistSyncer = null,
|
||||||
|
Syncer<Profile>? profileSyncer = null, Syncer<SearchHistory>? searchHistorySyncer = null,
|
||||||
|
Syncer<Setting>? settingsSyncer = null)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
if (Process.GetProcessesByName("FreeTube").Length > 0) continue;
|
||||||
|
if (historySyncer is { IsDirty: true })
|
||||||
|
historySyncer.Sync();
|
||||||
|
if (playlistSyncer is { IsDirty: true })
|
||||||
|
playlistSyncer.Sync();
|
||||||
|
if (profileSyncer is { IsDirty: true})
|
||||||
|
profileSyncer.Sync();
|
||||||
|
if (searchHistorySyncer is { IsDirty: true})
|
||||||
|
searchHistorySyncer.Sync();
|
||||||
|
if (settingsSyncer is { IsDirty: true})
|
||||||
|
settingsSyncer.Sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
// .StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FreeTubeSyncer.Library;
|
|
||||||
using FreeTubeSyncer.Models.DatabaseModels;
|
|
||||||
using RestSharp;
|
|
||||||
|
|
||||||
namespace FreeTubeSyncer.REST;
|
|
||||||
|
|
||||||
public class RestSync
|
|
||||||
{
|
|
||||||
private List<History> _history = [];
|
|
||||||
private List<Playlist> _playlist = [];
|
|
||||||
private List<Profile> _profile = [];
|
|
||||||
private List<SearchHistory> _searchHistory = [];
|
|
||||||
private List<Setting> _setting = [];
|
|
||||||
|
|
||||||
private bool _dirtyHistory = false;
|
|
||||||
private bool _dirtyPlaylist = false;
|
|
||||||
private bool _dirtyProfile = false;
|
|
||||||
private bool _dirtySearchHistory = false;
|
|
||||||
private bool _dirtySetting = false;
|
|
||||||
private RestClient _client;
|
|
||||||
|
|
||||||
public RestSync(DBSyncWatcher watcher)
|
|
||||||
{
|
|
||||||
watcher.OnDatabaseChange += HandleDatabaseChange;
|
|
||||||
var options = new RestClientOptions
|
|
||||||
{
|
|
||||||
BaseUrl = new Uri("http://localhost:5183/")
|
|
||||||
};
|
|
||||||
_client = new RestClient(options);
|
|
||||||
PrePopulate();
|
|
||||||
CheckCanSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PrePopulate()
|
|
||||||
{
|
|
||||||
var resHistory = await _client.GetAsync<List<History>>("/history");
|
|
||||||
_history = resHistory;
|
|
||||||
var resPlaylist = await _client.GetAsync<List<Playlist>>("/playlist");
|
|
||||||
_playlist = resPlaylist;
|
|
||||||
var resProfile = await _client.GetAsync<List<Profile>>("/profile");
|
|
||||||
_profile = resProfile;
|
|
||||||
var resSearchHistory = await _client.GetAsync<List<SearchHistory>>("/searchHistory");
|
|
||||||
_searchHistory = resSearchHistory;
|
|
||||||
var resSetting = await _client.GetAsync<List<Setting>>("/setting");
|
|
||||||
_setting = resSetting;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckCanSync()
|
|
||||||
{
|
|
||||||
if (Process.GetProcessesByName("FreeTube").ToList().Count > 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine("FreeTube is running, awaiting till we can sync.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDatabaseChange(string dbName, object obj)
|
|
||||||
{
|
|
||||||
switch (dbName)
|
|
||||||
{
|
|
||||||
case "history.db":
|
|
||||||
var history = (History?)obj;
|
|
||||||
if (history == null) return;
|
|
||||||
if (_history.Any(x => x._id == history._id)) return;
|
|
||||||
_history.Add(history);
|
|
||||||
_dirtyHistory = true;
|
|
||||||
break;
|
|
||||||
case "playlist.db":
|
|
||||||
var playlist = (Playlist?)obj;
|
|
||||||
if (playlist == null) return;
|
|
||||||
if (_playlist.Any(x => x._id == playlist._id)) return;
|
|
||||||
_playlist.Add(playlist);
|
|
||||||
_dirtyPlaylist = true;
|
|
||||||
break;
|
|
||||||
case "profile.db":
|
|
||||||
var profile = (Profile?)obj;
|
|
||||||
if (profile == null) return;
|
|
||||||
if (_profile.Any(x => x._id == profile._id)) return;
|
|
||||||
_profile.Add(profile);
|
|
||||||
_dirtyProfile = true;
|
|
||||||
break;
|
|
||||||
case "search.db":
|
|
||||||
var search = (SearchHistory?)obj;
|
|
||||||
if (search == null) return;
|
|
||||||
if (_searchHistory.Any(x => x._id == search._id)) return;
|
|
||||||
_searchHistory.Add(search);
|
|
||||||
_dirtySearchHistory = true;
|
|
||||||
break;
|
|
||||||
case "setting.db":
|
|
||||||
var setting = (Setting?)obj;
|
|
||||||
if (setting == null) return;
|
|
||||||
if (_setting.Any(x => x._id == setting._id)) return;
|
|
||||||
_setting.Add(setting);
|
|
||||||
_dirtySetting = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
FreeTubeSyncer/REST/Syncer.cs
Normal file
88
FreeTubeSyncer/REST/Syncer.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FreeTubeSyncer.Library;
|
||||||
|
using FreeTubeSyncer.Models.DatabaseModels;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace FreeTubeSyncer.REST;
|
||||||
|
|
||||||
|
public class Syncer<T> where T : class, IDataModel
|
||||||
|
{
|
||||||
|
private List<T> _entries = new List<T>();
|
||||||
|
private RestClient _client;
|
||||||
|
private string _dbPath;
|
||||||
|
private string _restEndpoint;
|
||||||
|
|
||||||
|
public bool IsDirty = false;
|
||||||
|
|
||||||
|
public Syncer(DBSyncWatcher watcher, string dbPath, string dbName, string restEndpoint)
|
||||||
|
{
|
||||||
|
watcher.WatchFiles[dbName] = typeof(T);
|
||||||
|
watcher.OnDatabaseChange += HandleDatabaseChange;
|
||||||
|
_client = new RestClient(new RestClientOptions("http://localhost:5183"));
|
||||||
|
_dbPath = dbPath;
|
||||||
|
_restEndpoint = restEndpoint;
|
||||||
|
ReadDatabase().Wait();
|
||||||
|
FetchDatabase().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReadDatabase()
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(_dbPath);
|
||||||
|
foreach (var entry in lines)
|
||||||
|
{
|
||||||
|
if (entry == "") continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = JsonSerializer.Deserialize<T>(entry);
|
||||||
|
if (item == null) continue;
|
||||||
|
if (_entries.Any(x => x.EqualId(item.Id())))
|
||||||
|
_entries.RemoveAll(x => x.EqualId(item.Id()));
|
||||||
|
_entries.Add(item);
|
||||||
|
Console.WriteLine($"Posting {item.Id()}");
|
||||||
|
await _client.PostJsonAsync<T>(_restEndpoint, item);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Failed to parse: {entry}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchDatabase()
|
||||||
|
{
|
||||||
|
var entries = await _client.GetAsync<IEnumerable<T>>(_restEndpoint);
|
||||||
|
if (entries == null) return;
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
if (_entries.Any(x => x.EqualId(entry.Id())))
|
||||||
|
_entries.RemoveAll(x => x.EqualId(entry.Id()));
|
||||||
|
|
||||||
|
_entries.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleDatabaseChange(string dbName, object entryObject)
|
||||||
|
{
|
||||||
|
var entry = (T)entryObject;
|
||||||
|
if (_entries.Any(x => x.EqualId(entry.Id())))
|
||||||
|
_entries.RemoveAll(x => x.EqualId(entry.Id()));
|
||||||
|
_entries.Add(entry);
|
||||||
|
await _client.PostJsonAsync<T>(_restEndpoint, entry);
|
||||||
|
IsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sync()
|
||||||
|
{
|
||||||
|
var json = new List<string>();
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
json.Add(JsonSerializer.Serialize(entry));
|
||||||
|
File.WriteAllLines(_dbPath, json);
|
||||||
|
Console.WriteLine($"Updated {_dbPath}");
|
||||||
|
IsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue