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);
|
||||
foreach (var line in data.Split('\n'))
|
||||
{
|
||||
if (line == "") continue;
|
||||
var type = WatchFiles[dbName];
|
||||
var item = JsonSerializer.Deserialize(line, type);
|
||||
if (item == null) continue;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
namespace FreeTubeSyncer.Models.DatabaseModels;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class History
|
||||
public class History : IDataModel
|
||||
{
|
||||
public string _id { 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 lastViewedPlaylistType { get; set; } = string.Empty;
|
||||
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;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class Playlist
|
||||
public class Playlist : IDataModel
|
||||
{
|
||||
public string _id { 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 long createdAt { 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;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class Profile
|
||||
public class Profile : IDataModel
|
||||
{
|
||||
public string _id { get; set; } = string.Empty;
|
||||
public string name { get; set; } = string.Empty;
|
||||
public string bgColor { get; set; } = string.Empty;
|
||||
public string textColor { get; set; } = string.Empty;
|
||||
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;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class SearchHistory
|
||||
public class SearchHistory : IDataModel
|
||||
{
|
||||
public string _id { get; set; } = string.Empty;
|
||||
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;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class Setting
|
||||
public class Setting : IDataModel
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
public string _id { get; set; } = string.Empty;
|
||||
|
|
@ -18,4 +18,7 @@ public class Setting
|
|||
#pragma warning restore CS8603
|
||||
set => ValueJson = JsonSerializer.Serialize(value);
|
||||
}
|
||||
|
||||
public string Id() => _id;
|
||||
public bool EqualId(string oid) => _id == oid;
|
||||
}
|
||||
|
|
@ -1,5 +1,14 @@
|
|||
using Avalonia;
|
||||
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;
|
||||
|
||||
|
|
@ -9,8 +18,43 @@ class Program
|
|||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
public static void Main(string[] 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.
|
||||
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