From 44a89ad5892f53c06ba5b5685bac85daea3ac1da Mon Sep 17 00:00:00 2001 From: Mario Steele Date: Mon, 21 Jul 2025 13:31:24 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 7 ++ FreeTubeSyncer.sln | 16 +++ FreeTubeSyncer/App.axaml | 25 +++++ FreeTubeSyncer/App.axaml.cs | 53 +++++++++ FreeTubeSyncer/Assets/awaiting_sync.png | Bin 0 -> 1182 bytes FreeTubeSyncer/Assets/freetube.png | Bin 0 -> 1355 bytes FreeTubeSyncer/Assets/sync_complete.png | Bin 0 -> 1174 bytes FreeTubeSyncer/FreeTubeSyncer.csproj | 34 ++++++ FreeTubeSyncer/Library/DBSyncWatcher.cs | 70 ++++++++++++ FreeTubeSyncer/MainWindow.axaml | 9 ++ FreeTubeSyncer/MainWindow.axaml.cs | 11 ++ .../Models/DatabaseModels/History.cs | 23 ++++ .../Models/DatabaseModels/Playlist.cs | 15 +++ .../Models/DatabaseModels/Profile.cs | 14 +++ .../Models/DatabaseModels/SearchHistory.cs | 10 ++ .../Models/DatabaseModels/Setting.cs | 21 ++++ .../Models/DatabaseModels/Subscription.cs | 11 ++ FreeTubeSyncer/Models/DatabaseModels/Video.cs | 17 +++ FreeTubeSyncer/Program.cs | 21 ++++ FreeTubeSyncer/REST/RestSync.cs | 102 ++++++++++++++++++ FreeTubeSyncer/app.manifest | 18 ++++ 21 files changed, 477 insertions(+) create mode 100644 .gitignore create mode 100644 FreeTubeSyncer.sln create mode 100644 FreeTubeSyncer/App.axaml create mode 100644 FreeTubeSyncer/App.axaml.cs create mode 100644 FreeTubeSyncer/Assets/awaiting_sync.png create mode 100644 FreeTubeSyncer/Assets/freetube.png create mode 100644 FreeTubeSyncer/Assets/sync_complete.png create mode 100644 FreeTubeSyncer/FreeTubeSyncer.csproj create mode 100644 FreeTubeSyncer/Library/DBSyncWatcher.cs create mode 100644 FreeTubeSyncer/MainWindow.axaml create mode 100644 FreeTubeSyncer/MainWindow.axaml.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/History.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/Playlist.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/Profile.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/SearchHistory.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/Setting.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/Subscription.cs create mode 100644 FreeTubeSyncer/Models/DatabaseModels/Video.cs create mode 100644 FreeTubeSyncer/Program.cs create mode 100644 FreeTubeSyncer/REST/RestSync.cs create mode 100644 FreeTubeSyncer/app.manifest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a5f2ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea/ +*.user diff --git a/FreeTubeSyncer.sln b/FreeTubeSyncer.sln new file mode 100644 index 0000000..412411f --- /dev/null +++ b/FreeTubeSyncer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeTubeSyncer", "FreeTubeSyncer\FreeTubeSyncer.csproj", "{B32732C0-EEA7-4149-90D0-933E4DACE3B0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B32732C0-EEA7-4149-90D0-933E4DACE3B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B32732C0-EEA7-4149-90D0-933E4DACE3B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B32732C0-EEA7-4149-90D0-933E4DACE3B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B32732C0-EEA7-4149-90D0-933E4DACE3B0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/FreeTubeSyncer/App.axaml b/FreeTubeSyncer/App.axaml new file mode 100644 index 0000000..bf82d18 --- /dev/null +++ b/FreeTubeSyncer/App.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FreeTubeSyncer/App.axaml.cs b/FreeTubeSyncer/App.axaml.cs new file mode 100644 index 0000000..dded1f1 --- /dev/null +++ b/FreeTubeSyncer/App.axaml.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using FreeTubeSyncer.Library; +using FreeTubeSyncer.Models.DatabaseModels; + +namespace FreeTubeSyncer; + +public partial class App : Application +{ + private DBSyncWatcher _watcher; + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + //desktop.MainWindow = new MainWindow(); + var path = ""; + if (OperatingSystem.IsWindows()) + path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTube"); + else if (OperatingSystem.IsLinux()) + { + path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", + "FreeTube"); + if (!Path.Exists(path)) + { + path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".var", "app", "io.freetubeapp.FreeTube", "config", + "FreeTube"); + if (!Path.Exists(path)) + Console.WriteLine("Failed to find Path for FreeTube!"); + } + } + else if (OperatingSystem.IsMacOS()) + path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FreeTube"); + + _watcher = new DBSyncWatcher(path); + _watcher.WatchFiles.Add("history.db", typeof(History)); + _watcher.WatchFiles.Add("playlist.db", typeof(Playlist)); + _watcher.WatchFiles.Add("profiles.db", typeof(Profile)); + _watcher.WatchFiles.Add("search-history.db", typeof(SearchHistory)); + _watcher.WatchFiles.Add("settings.db", typeof(Setting)); + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/FreeTubeSyncer/Assets/awaiting_sync.png b/FreeTubeSyncer/Assets/awaiting_sync.png new file mode 100644 index 0000000000000000000000000000000000000000..32337e52d8dbbd47ab6a7a7868c1ca336046eab6 GIT binary patch literal 1182 zcmV;P1Y!G$P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk2NVDn0E}EoX3FexU8$nx^AVTEskoFQ8vVz;gxRW zViiBwtl|(sVH*g$GTw;jps)+wWUIocBW)81tO{G*@}bamT}_s>Nlre!D8e-DXiq*o z={f(akaOMx{~zZ(@AE#-`+_1xisUTSa(`yOAiFNjzM1P+B^UE6K~T&66Hovs02BZU z09mU-%%J(%lT^6f5R2trtf?WjV+V%EKgu09H;l&ZMzy$DR8uim|cf z%>%?@RE&*rZ*Djj+SRK?H(&HmK;w3=?mm}G)VTaJ9aV7wsH0I)_o-HTV+yQvZLPe6 zpwQ4DZvb(NMZN%%dOe#Xuge!e&}x-#B9y7FF6#3`ot-rObx67ZqAe{&oAyHc_F*!d zEL|oj3c$euvT8N8Q&X#+N*|__R!hQUBGlQ*voiywO@+m%X`&sTg6RX7iC1a!eqMDV zLX)SPCuYu;el3s}2v4`N8k8k~!(07qF?g{z`zR=<3M;6fVC`7&%ZcsbD;knb#T zKLzJ}X_}0s10l(2FqB*Z@r=>O$Il1kn+F7wS~{P0g3SUA+IP5f3^x(f6pWFYr_CM?M*k`tI_;_$t2PW3rd4V4r zFP-k`>3JWBmR&_CU%A>Qe;C-a=hKzUuj}scgk}6fUtiy`Xf%4KY-@sIb!p4RGp}@X zoE8MZzvfif)i!ydoXvkYH~d;}Z|^69Ao%{%lU-Jt+&xFC+Xpte-zYcZj+!rzw;#P| z9~~Wa0wMA^Hdkr0*>(>N4IOK9?dq3oUcd9&&%W9D$&rzfUmOm{4PbUH#vgia5U2#6 w1RjxCoX7$hAOXaI1>pX28J;3VidLn+0Ro{&S|tCkzW@LL07*qoM6N<$f_r-wr2qf` literal 0 HcmV?d00001 diff --git a/FreeTubeSyncer/Assets/freetube.png b/FreeTubeSyncer/Assets/freetube.png new file mode 100644 index 0000000000000000000000000000000000000000..98f1e6240a78abd0a88667f7be7e18b07ffb821b GIT binary patch literal 1355 zcmV-R1+@B!P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk2NM%HEm$ZBZ2$lS zp-DtRRCwC$n@vm`M;M0RU3=I6CNajLJ%lK|CAlR6E+85N0^FAHrzD3cm6B^C4&~HC zdugk-9Kwx55mg9Rq)N1hwj7YC3KEta0f$xzbrLK?+{y+oyPoy#Ob?Bel(>LxY<5|{ zM|;Nd`|Ql@H{W~%R8>_~Ru-4&s_JVsT3Y=3@uty?*qP+RN%LVQ7 zWn^#N0$i?F!%tD*n4X3$5&^fnTUJxjg9qgU;CT2D_JxIg0ofN8;NW?XyLVspo0{6c zpIN4WEfP6gyXEDg`W?YaS6%?TEEm;|%Qs!hAl>7!_995mowGK8?3F9l1&|8_z?z$_ z3m`Qx0EgRcOaRpBEbH&5+uC4nYBFvlC||l%w%1Q!Wih9HV!1tP&BmAP>PjOXE5 zT|M-xbU$f6AJm2hq}$rSU$_9T*IS`bxJY^H>LA^|jlZ&497Vo?<2Z<-2)Ek}K@cjq z7bxKm`+Pq5e7@>#(2@W~)^xNidV?aoq~ZQLMLNf<1K@S5 zSsnQ9nU3y74P#GqY-<)>0~8&Yd`=PmEr;=#4oPFyTm&T@7>`ke7d4E>C?wsYZ-H$M z7?0_AZ!w4Q7{zV{MuZ_>gX!E$5KB5HpHmQO(KsNLV;Fm)iE-s92T+wAT>atRr~K>ww2|e8wvd;Insh z5F*3rbRL^=N~wNq0Q`PG3{WVwTXH0*8Nf)fB7|&}3_xvYFf9N@Q685JKSX(|AjOy<$>@bJo!Y=^x*AIP<9 z*t~NGxj?`a09IF5zXd=JtQOzc*f?||PE}Q8(rLJ&QFxXfeA!JLEq(9^FcywlF=GV!| z$=?BN6=HnfdxHQ30B_^pJ2pnj95VnEfC?ZBKmxFxFT$&;s;Um9e*p80*iVEpz@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk2NVDn9|2TE5C8xJ z>`6pHRCwC$n_XyIWf;eQNm|wtldLg=AwsKe9gbozT6MU7*%D}bVYQ9U;;uGC5FKKg zn|6pWRyPo~DH{cAqTnj*qA)=x>%!Q`WFw7@Fog}awK}l2mZTr~IQcm7VmD(=XPPfg z@}B=?a?TIMda)-# zsjg0J0MX`VaRJ25W}e;it+)UJ0|UwpgygEKf<8ak(LsIFxN-r68yX6>>;*eJseAiM z@gl)&v^3aHN~=YG^Jd0N>Eo0z7>L!@671+;cf;x8hQisXQ9l9)U@9__XM@>j@Scgm3;#a%0+#<8P<9b3h~suD@jJeB&HmaEdz#t|eh?R_3{1$2q*< z5_c6?PQch~k{vU}1EI)eFc^OTV&}})99eRS8wUj9DNfx8j^0j+ z3&2t-^WpYTX-9(D$fQeQvqTxN#Cb{ou{Z#XI+-^u5^tDe>@nCmUJkiHX2w(WnI#4+ z5-kNbt|$O{ZHoRUqu4Al_8RP*D93CtovO4c`f4QhZIfs-7T0*u0cajdh_+}`^qFJq zvqWh(+BuB?^?+ARF{all=`u+SZj{O3RP*oIczfy@p}l-YTKpYJ+Oc6WD= z0O69W2<0m8^0A-V``eFiWWKC-hi?Q%&-eEB9twxU^Cepo%vKlf44rCwz3rqVNpqVH zg}uwi&X= + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + None + All + + + + + + + + + + + + + + diff --git a/FreeTubeSyncer/Library/DBSyncWatcher.cs b/FreeTubeSyncer/Library/DBSyncWatcher.cs new file mode 100644 index 0000000..865885a --- /dev/null +++ b/FreeTubeSyncer/Library/DBSyncWatcher.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace FreeTubeSyncer.Library; + +public class DBSyncWatcher +{ + private FileSystemWatcher _watcher; + public Dictionary WatchFiles { get; set; } = []; + + public delegate void DatabaseChange(string db, object value); + public event DatabaseChange OnDatabaseChange; + + public DBSyncWatcher(string path) + { + _watcher = new FileSystemWatcher(path); + _watcher.NotifyFilter = NotifyFilters.LastWrite | + NotifyFilters.CreationTime; + + _watcher.Changed += HandleChanged; + _watcher.Created += HandleCreated; + _watcher.Error += HandleError; + + _watcher.Filter = "*.db"; + _watcher.IncludeSubdirectories = true; + _watcher.EnableRaisingEvents = true; + } + + private void HandleChanged(object sender, FileSystemEventArgs e) + { + if (e.ChangeType != WatcherChangeTypes.Changed) return; + + var dbName = Path.GetFileName(e.FullPath); + + if (!WatchFiles.Keys.Contains(dbName)) return; + + Console.WriteLine("New Change in {0}", dbName); + + var data = File.ReadAllText(e.FullPath); + 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); + } + } + + private void HandleCreated(object sender, FileSystemEventArgs e) + { + if (e.ChangeType != WatcherChangeTypes.Created) return; + var dbName = Path.GetFileName(e.FullPath); + if (!WatchFiles.Keys.Contains(dbName)) return; + var data = File.ReadAllText(e.FullPath); + 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); + } + } + + private void HandleError(object sender, ErrorEventArgs e) + { + Console.WriteLine("Error: {0}\n{1}", e.GetException().Message, e.GetException().StackTrace); + } +} \ No newline at end of file diff --git a/FreeTubeSyncer/MainWindow.axaml b/FreeTubeSyncer/MainWindow.axaml new file mode 100644 index 0000000..797d249 --- /dev/null +++ b/FreeTubeSyncer/MainWindow.axaml @@ -0,0 +1,9 @@ + + Welcome to Avalonia! + diff --git a/FreeTubeSyncer/MainWindow.axaml.cs b/FreeTubeSyncer/MainWindow.axaml.cs new file mode 100644 index 0000000..fe27c5f --- /dev/null +++ b/FreeTubeSyncer/MainWindow.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace FreeTubeSyncer; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/FreeTubeSyncer/Models/DatabaseModels/History.cs b/FreeTubeSyncer/Models/DatabaseModels/History.cs new file mode 100644 index 0000000..be3eba3 --- /dev/null +++ b/FreeTubeSyncer/Models/DatabaseModels/History.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; + +namespace FreeTubeSyncer.Models.DatabaseModels; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public class History +{ + public string _id { get; set; } = string.Empty; + public string videoId { get; set; } = string.Empty; + public string title { get; set; } = string.Empty; + public string author { get; set; } = string.Empty; + public string authorId { get; set; } = string.Empty; + public long published { get; set; } + public string description { get; set; } = string.Empty; + public long viewCount { get; set; } + public long lengthSeconds { get; set; } + public float watchProgress { get; set; } + public long timeWatched { get; set; } + public bool isLive { get; set; } + public string type { get; set; } = string.Empty; + public string lastViewedPlaylistType { get; set; } = string.Empty; + public string? lastViewedPlaylistItemId { get; set; } +} \ No newline at end of file diff --git a/FreeTubeSyncer/Models/DatabaseModels/Playlist.cs b/FreeTubeSyncer/Models/DatabaseModels/Playlist.cs new file mode 100644 index 0000000..4e0c6cc --- /dev/null +++ b/FreeTubeSyncer/Models/DatabaseModels/Playlist.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace FreeTubeSyncer.Models.DatabaseModels; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public class Playlist +{ + public string _id { get; set; } = string.Empty; + public string playlistName { get; set; } = string.Empty; + public bool @protected { get; set; } + public List