Compare commits

...

10 commits

Author SHA1 Message Date
ea2fb4d818 Updated Program.cs
While still testing things out, remain in Console mode.
Removed un-used using statements.
Fixed SearchHistorySyner to properly use /searchHistory API end-point.
Added print statement to see when FreeTube closes, and wait for 1.5
seconds before attempting to sync everything up.  May need to increase
this more, needs testing.  (maybe a good 5 seconds would be enough)
2025-07-24 04:32:51 -05:00
fb8284b5e8 Updated Syncer
Updated Syncer class to properly sync all classes, even with special
requierments needed for Settings.
Now properly handles all classes, and ensures that the data is properly
stored in the database, and can be written out correctly to the FreeTube
database files.
2025-07-24 04:30:54 -05:00
8e65cc3a4b Updated Setting
Updated Seting class to use string for teh data, and the data is the
full setting line, to allow for the flexability for Any data value in
the field.  Implemented MarshalData() and JsonData() functions.
2025-07-24 04:29:13 -05:00
09cd72b278 Updated Profile
removed un-used function.
2025-07-24 04:28:06 -05:00
25676baca4 Updated SearchHistory
Added MarshalData() and JsonData() functions.
2025-07-24 04:27:56 -05:00
ae9a1d14bf Updated Profile
Added functions for MarshalData() and JsonData()
2025-07-24 04:26:46 -05:00
cce80d1d37 Updated Playlist
Updated to implement MarshalData() and JsonData()
2025-07-24 04:26:28 -05:00
679449e4ec Updated History
Updated History to implement MarshalData() and JsonData() functions.
2025-07-24 04:25:59 -05:00
102a1d5d62 Updated IDataModel
Added two functions, MarshalData() and JsonData() to handle
deserialization and serialization.  (Special case with Setting)
2025-07-24 04:25:25 -05:00
b2b4eeff05 Updated DBSyncWatcher
Changed delegate to pass string instead of object.
Now instead of attempting to deserialize the data in DBSyncWatcher,
deserialization happens in th Syncer class.
2025-07-24 04:24:45 -05:00
9 changed files with 142 additions and 34 deletions

View file

@ -10,7 +10,7 @@ public class DBSyncWatcher
private FileSystemWatcher _watcher;
public Dictionary<string, Type> WatchFiles { get; set; } = [];
public delegate void DatabaseChange(string db, object value);
public delegate void DatabaseChange(string db, string value);
public event DatabaseChange OnDatabaseChange;
public DBSyncWatcher(string path)
@ -43,9 +43,7 @@ public class DBSyncWatcher
{
if (line == "") continue;
var type = WatchFiles[dbName];
var item = JsonSerializer.Deserialize(line, type);
if (item == null) continue;
OnDatabaseChange?.Invoke(dbName, item);
OnDatabaseChange?.Invoke(dbName, line);
}
}
@ -58,9 +56,7 @@ public class DBSyncWatcher
foreach (var line in data.Split('\n'))
{
var type = WatchFiles[dbName];
var item = JsonSerializer.Deserialize(line, type);
if (item == null) continue;
OnDatabaseChange?.Invoke(dbName, item);
OnDatabaseChange?.Invoke(dbName, line);
}
}

View file

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using FreeTubeSyncer.Library;
namespace FreeTubeSyncer.Models.DatabaseModels;
@ -23,4 +25,31 @@ public class History : IDataModel
public string Id() => _id;
public bool EqualId(string oid) => _id == oid;
public void MarshalData(string id, string data)
{
var tobject = JsonSerializer.Deserialize<History>(data, GlobalJsonOptions.Options);
if (tobject == null)
return;
this._id = id;
this.videoId = tobject.videoId;
this.title = tobject.title;
this.author = tobject.author;
this.authorId = tobject.authorId;
this.published = tobject.published;
this.description = tobject.description;
this.viewCount = tobject.viewCount;
this.lengthSeconds = tobject.lengthSeconds;
this.watchProgress = tobject.watchProgress;
this.timeWatched = tobject.timeWatched;
this.isLive = tobject.isLive;
this.type = tobject.type;
this.lastViewedPlaylistType = tobject.lastViewedPlaylistType;
this.lastViewedPlaylistItemId = tobject.lastViewedPlaylistItemId;
}
public string JsonData()
{
return JsonSerializer.Serialize(this);
}
}

View file

@ -4,4 +4,6 @@ public interface IDataModel
{
string Id();
bool EqualId(string oid);
void MarshalData(string id, string data);
string JsonData();
}

View file

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using FreeTubeSyncer.Library;
namespace FreeTubeSyncer.Models.DatabaseModels;
@ -15,4 +17,21 @@ public class Playlist : IDataModel
public string Id() => _id;
public bool EqualId(string oid) => _id == oid;
public void MarshalData(string id, string data)
{
var tobject = JsonSerializer.Deserialize<Playlist>(data, GlobalJsonOptions.Options);
if (tobject == null)
return;
this._id = id;
this.playlistName = tobject.playlistName;
this.@protected = tobject.@protected;
this.videos = tobject.videos;
this.createdAt = tobject.createdAt;
this.lastUpdatedAt = tobject.lastUpdatedAt;
}
public string JsonData()
{
return JsonSerializer.Serialize(this);
}
}

View file

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using FreeTubeSyncer.Library;
namespace FreeTubeSyncer.Models.DatabaseModels;
@ -14,4 +16,20 @@ public class Profile : IDataModel
public string Id() => _id;
public bool EqualId(string oid) => _id == oid;
public void MarshalData(string id, string data)
{
var tobject = JsonSerializer.Deserialize<Profile>(data, GlobalJsonOptions.Options);
if (tobject == null)
return;
this._id = tobject._id;
this.name = tobject.name;
this.bgColor = tobject.bgColor;
this.textColor = tobject.textColor;
this.subscriptions = tobject.subscriptions;
}
public string JsonData()
{
return JsonSerializer.Serialize(this);
}
}

View file

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using FreeTubeSyncer.Library;
namespace FreeTubeSyncer.Models.DatabaseModels;
@ -10,4 +12,17 @@ public class SearchHistory : IDataModel
public string Id() => _id;
public bool EqualId(string oid) => _id == oid;
public void MarshalData(string id, string data)
{
var tobject = JsonSerializer.Deserialize<SearchHistory>(data, GlobalJsonOptions.Options);
if (tobject == null)
return;
this._id = tobject._id;
this.lastUpdatedAt = tobject.lastUpdatedAt;
}
public string JsonData()
{
return JsonSerializer.Serialize(this);
}
}

View file

@ -8,17 +8,19 @@ public class Setting : IDataModel
{
#pragma warning disable CS8618
public string _id { get; set; } = string.Empty;
public string? ValueJson { get; set; }
#pragma warning restore CS8618
public object Value
{
#pragma warning disable CS8603
get => string.IsNullOrEmpty(ValueJson) ? null : JsonSerializer.Deserialize<object>(ValueJson);
#pragma warning restore CS8603
set => ValueJson = JsonSerializer.Serialize(value);
}
public string value { get; set; } = string.Empty;
public string Id() => _id;
public bool EqualId(string oid) => _id == oid;
public void MarshalData(string id, string data)
{
_id = id;
value = data;
}
public string JsonData()
{
return value;
}
}

View file

@ -1,7 +1,5 @@
using Avalonia;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@ -40,7 +38,7 @@ class Program
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 searchHistorySyncer = new Syncer<SearchHistory>(dbWatcher, Path.Join(path, "search-history.db"), "search-history.db", "/searchHistory");
var settingsSyncer = new Syncer<Setting>(dbWatcher, Path.Join(path, "settings.db"), "settings.db", "/settings");
Task.Run(() => CheckCanSync(historySyncer, playlistSyncer, profileSyncer, searchHistorySyncer, settingsSyncer));
@ -56,6 +54,9 @@ class Program
{
Thread.Sleep(100);
if (Process.GetProcessesByName("FreeTube").Length > 0) continue;
Console.WriteLine("FreeTube has closed, we're going to try and update.");
Thread.Sleep(1500);
if (historySyncer is { IsDirty: true })
historySyncer.Sync();
if (playlistSyncer is { IsDirty: true })

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using FreeTubeSyncer.Library;
using FreeTubeSyncer.Models.DatabaseModels;
@ -10,11 +11,12 @@ using RestSharp;
namespace FreeTubeSyncer.REST;
public class Syncer<T> where T : class, IDataModel
public class Syncer<T> where T : class, IDataModel, new()
{
private List<T> _entries = new List<T>();
private RestClient _client;
private string _dbPath;
private string _dbName;
private string _restEndpoint;
public bool IsDirty = false;
@ -25,6 +27,7 @@ public class Syncer<T> where T : class, IDataModel
watcher.OnDatabaseChange += HandleDatabaseChange;
_client = new RestClient(new RestClientOptions("http://localhost:5183"));
_dbPath = dbPath;
_dbName = dbName;
_restEndpoint = restEndpoint;
ReadDatabase().Wait();
FetchDatabase().Wait();
@ -36,20 +39,24 @@ public class Syncer<T> where T : class, IDataModel
foreach (var entry in lines)
{
if (entry == "") continue;
T? item;
try
{
var item = JsonSerializer.Deserialize<T>(entry, GlobalJsonOptions.Options);
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);
item = JsonSerializer.Deserialize<T>(entry, GlobalJsonOptions.Options);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to parse: {entry}");
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);
Console.WriteLine($"Posting {item.Id()}");
await _client.PostJsonAsync<T>(_restEndpoint, item);
}
}
@ -66,9 +73,25 @@ public class Syncer<T> where T : class, IDataModel
}
}
private async void HandleDatabaseChange(string dbName, object entryObject)
private async void HandleDatabaseChange(string dbName, string entryObject)
{
var entry = (T)entryObject;
if (dbName != _dbName)
return;
T? entry;
try
{
entry = JsonSerializer.Deserialize<T>(entryObject, GlobalJsonOptions.Options);
}
catch (Exception ex)
{
var jobj = JsonSerializer.Deserialize<JsonObject>(entryObject, GlobalJsonOptions.Options);
entry = new T();
entry.MarshalData(jobj["_id"].GetValue<string>(), entryObject);
}
if (entry == null) return;
entry.MarshalData(entry.Id(), entryObject);
if (_entries.Any(x => x.EqualId(entry.Id())))
_entries.RemoveAll(x => x.EqualId(entry.Id()));
_entries.Add(entry);
@ -78,11 +101,14 @@ public class Syncer<T> where T : class, IDataModel
public void Sync()
{
if (!IsDirty)
return;
Console.WriteLine($"Syncing {_dbPath}...");
var json = new List<string>();
foreach (var entry in _entries)
json.Add(JsonSerializer.Serialize(entry));
json.Add(entry.JsonData());
File.WriteAllLines(_dbPath, json);
Console.WriteLine($"Updated {_dbPath}");
Console.WriteLine($"Updated {_dbPath}.");
IsDirty = false;
}
}