From 8a38300cf1ce6fa2b7d13ce73bd44df20e37edc8 Mon Sep 17 00:00:00 2001 From: nebula Date: Sat, 17 May 2025 19:17:45 -0500 Subject: [PATCH] pushing stuff --- App.xaml.cs | 4 +- Bink.cs | 84 ++++++++++++++++++---- BinkPage.xaml | 4 +- BinkPage.xaml.cs | 69 +++--------------- Feels.cs | 139 ++++++++++++++++++++++++++++++++---- FeelsFeedPage.xaml | 4 +- FeelsFeedPage.xaml.cs | 30 ++------ FeelsNavigationPage.xaml | 33 +++++++-- FeelsNavigationPage.xaml.cs | 54 ++++++++++---- MainWindow.xaml.cs | 9 +-- NavigationPage.xaml.cs | 2 + Package.appxmanifest | 23 +++++- SshConnection.cs | 32 +++++---- StartupSshPage.xaml.cs | 11 ++- wakka.csproj | 13 +++- 15 files changed, 348 insertions(+), 163 deletions(-) diff --git a/App.xaml.cs b/App.xaml.cs index 74ad738..418a9f6 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -3,6 +3,8 @@ using Microsoft.UI.Xaml.Controls; using System; using System.IO; using WinUIEx; +using Microsoft.Windows.AppNotifications; + // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -12,7 +14,7 @@ namespace wakka public partial class App : Application { public Window m_window; - + public readonly static Windows.Storage.ApplicationDataContainer LocalSettingsData = Windows.Storage.ApplicationData.Current.LocalSettings; diff --git a/Bink.cs b/Bink.cs index df91445..5f4e7d7 100644 --- a/Bink.cs +++ b/Bink.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using Microsoft.UI.Xaml; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text.Json; using System.Text.Json.Serialization; @@ -7,36 +10,89 @@ namespace wakka public class BinkPost { [JsonPropertyName("user")] - public string? User { get; set; } + public required string User { get; set; } [JsonPropertyName("body")] - public string? Body { get; set; } + public required string Body { get; set; } [JsonPropertyName("time")] - public long Time { get; set; } + public required long Time { get; set; } public string TimeString { get; set; } = string.Empty; } - public class Bink + public static class Bink { - private static SshConnection Ssh { get; set; } = new SshConnection(); + private static DispatcherTimer? timer = null; + public static readonly ObservableCollection LoadedBinks = []; + private static List? BinkData = null; - public void PostBink(string message) + public static void Initialize() { - Ssh.RunCommandWithInput("town bink --pipe", message); + if (timer == null) + StartTimer(); + if (BinkData == null) + { + BinkData = AllBinks(); + if (BinkData != null) + { + foreach (var bink in BinkData) + { + SetBinkDateString(bink); + LoadedBinks.Add(bink); + } + } + } } - public List? AllBinks() + public static void PostBink(string message) { - return JsonSerializer.Deserialize>(Ssh.RunCommand("town bink --dump")); + SshConnection.RunCommandWithInput("town bink --pipe", message); } - public List? BinksBefore(long TimeStamp) + public static List? AllBinks() { - return JsonSerializer.Deserialize>(Ssh.RunCommand($"town bink --dump-before {TimeStamp}")); + return JsonSerializer.Deserialize>(SshConnection.RunCommand("town bink --dump")); } - public List? BinksAfter(long TimeStamp) + public static List? BinksBefore(long TimeStamp) { - return JsonSerializer.Deserialize>(Ssh.RunCommand($"town bink --dump-after {TimeStamp}")); + return JsonSerializer.Deserialize>(SshConnection.RunCommand($"town bink --dump-before {TimeStamp}")); + } + + public static List? BinksAfter(long TimeStamp) + { + return JsonSerializer.Deserialize>(SshConnection.RunCommand($"town bink --dump-after {TimeStamp}")); + } + + public static void SetBinkDateString(BinkPost bink) + { + var date = DateTimeOffset.FromUnixTimeSeconds(bink.Time / 1000000000).DateTime.ToLocalTime(); + bink.TimeString = date.ToString("HH:mm (dddd, MMMM dd, yyyy)"); + } + + public static void GetNewBinks() + { + var BinkList = BinksAfter(LoadedBinks[0].Time); + if (BinkList != null) + { + BinkList.Reverse(); + foreach (var bink in BinkList) + { + SetBinkDateString(bink); + LoadedBinks.Insert(0, bink); + } + } + } + + private static void StartTimer() + { + timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromSeconds(20); + timer.Tick += Timer_Tick; + timer.Start(); + } + + private static void Timer_Tick(object? sender, object? e) + { + GetNewBinks(); } } } diff --git a/BinkPage.xaml b/BinkPage.xaml index 3051548..f932142 100644 --- a/BinkPage.xaml +++ b/BinkPage.xaml @@ -15,8 +15,8 @@ - - + + public sealed partial class BinkPage : Page { - private DispatcherTimer timer; - - private readonly ObservableCollection Binks = []; - private static Bink Bink { get; set; } = new Bink(); - public BinkPage() { this.InitializeComponent(); - StartTimer(); - var BinkList = Bink.AllBinks(); - if (BinkList != null) - { - foreach (var bink in BinkList) - { - SetBinkDateString(bink); - Binks.Add(bink); - } - } - binksListView.ItemsSource = Binks; - } - public void SetBinkDateString (BinkPost bink) - { - var date = DateTimeOffset.FromUnixTimeSeconds(bink.Time / 1000000000).DateTime.ToLocalTime(); - bink.TimeString = date.ToString("HH:mm (dddd, MMMM dd, yyyy)"); - } - public void GetNewBinks() - { - var BinkList = Bink.BinksAfter(Binks[0].Time); - if (BinkList != null) - { - BinkList.Reverse(); - foreach (var bink in BinkList) - { - SetBinkDateString(bink); - Binks.Insert(0, bink); - } - } + binksListView.ItemsSource = Bink.LoadedBinks; } + private void BinkSubmit(ContentDialog sender, ContentDialogButtonClickEventArgs args) { var message = binkComposeBox.Text; @@ -63,10 +25,10 @@ namespace wakka { Bink.PostBink(message); binkComposeBox.Text = string.Empty; - GetNewBinks(); + Bink.GetNewBinks(); } - } + private void OnTextChanged(object sender, TextChangedEventArgs e) { if (string.IsNullOrEmpty(binkComposeBox.Text)) @@ -79,27 +41,16 @@ namespace wakka } } - private async void OnBinkCreate(object sender, RoutedEventArgs e) + private async void OnBinkCreateButton(object sender, RoutedEventArgs e) { binkComposeDialog.IsPrimaryButtonEnabled = false; await binkComposeDialog.ShowAsync(); } - public void GetNewBinksButton(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + public void RefreshButton(object sender, RoutedEventArgs e) { - GetNewBinks(); + Bink.GetNewBinks(); } - private void StartTimer() - { - timer = new DispatcherTimer(); - timer.Interval = TimeSpan.FromSeconds(20); - timer.Tick += Timer_Tick; - timer.Start(); - } - - private void Timer_Tick(object? sender, object? e) - { - GetNewBinks(); - } + } } diff --git a/Feels.cs b/Feels.cs index f454d36..2c62da6 100644 --- a/Feels.cs +++ b/Feels.cs @@ -1,44 +1,157 @@ -using System.Collections.Generic; +using Microsoft.UI.Xaml; +using Renci.SshNet; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; +using Windows.ApplicationModel.VoiceCommands; +using Windows.Storage.Search; namespace wakka { public class FeelsPost { [JsonPropertyName("user")] - public string? User { get; set; } + public required string User { get; set; } [JsonPropertyName("path")] - public string? Path { get; set; } + public required string Path { get; set; } [JsonPropertyName("m_time")] - public long M_Time { get; set; } + public required long M_Time { get; set; } + [JsonPropertyName("reported_date")] + public required long ReportedDate { get; set; } public string TimeString { get; set; } = string.Empty; public string Body { get; set; } = string.Empty; + public int WordCount { get; set; } = 0; } - internal class Feels + public static class Feels { - private static SshConnection Ssh { get; set; } = new SshConnection(); + public static readonly ObservableCollection LoadedFeelsPosts = []; + private static DispatcherTimer? Timer = null; + private static List>? LoadTimeMetadata = null; + private static int CurrentChunk = 0; - public List? AllFeels() + public static void Initialize() { - return JsonSerializer.Deserialize>(Ssh.RunCommand("~nebula/bin/dumpfeels")); + if (Timer == null) + StartTimer(); + if (LoadTimeMetadata == null) + { + var feels = AllFeels(); + if (feels != null) + { + LoadTimeMetadata = (List>)feels.Chunk(10).Select(chunk => chunk.ToList()).ToList(); + LoadFeelsChunk(); + } + } } - public List? FeelsAfter(long TimeStamp) + public static void LoadFeelsChunk() { - return JsonSerializer.Deserialize>(Ssh.RunCommand($"~nebula/bin/dumpfeels --after {TimeStamp}")); + if (LoadTimeMetadata == null) + return; + var chunk = LoadTimeMetadata[CurrentChunk]; + foreach (var feel in chunk) + { + ConstructFeel(feel); + LoadedFeelsPosts.Add(feel); + } + CurrentChunk++; } - public List? FeelsFromUser(string User, List Feels) + private static void StartTimer() + { + Timer = new DispatcherTimer(); + Timer.Interval = TimeSpan.FromSeconds(20); + Timer.Tick += Timer_Tick; + Timer.Start(); + } + + private static void Timer_Tick(object? sender, object? e) + { + GetNewFeels(); + } + + public static void GetNewFeels() + { + var newFeels = FeelsAfter(LoadedFeelsPosts[0].M_Time); + if (newFeels != null) + { + var additions = new List(); + foreach (var newFeel in newFeels) + { + ConstructFeel(newFeel); + FeelsPost? toRemove = null; + foreach (var liveFeel in LoadedFeelsPosts) + { + if (liveFeel.Path.Equals(newFeel.Path)) + { + toRemove = liveFeel; + break; + } + } + if (toRemove != null) + LoadedFeelsPosts.Remove(toRemove); + additions.Add(newFeel); + } + additions.Reverse(); + foreach (var feel in additions) + { + LoadedFeelsPosts.Insert(0, feel); + } + } + } + + public static string GetTodayFeelPath() + { + var dateString = DateTime.Now.ToString("yyyyMMdd"); + return $"/home/{SshConnection.User}/.ttbp/entries/{dateString}.txt"; + } + + public static string GetTodayFeelBody() + { + + return SshConnection.RunCommand($"cat {GetTodayFeelPath()}"); + } + + public static void PostFeel(string body) + { + SshConnection.RunCommandWithInput($"cat > {GetTodayFeelPath()}", body); + } + + public static List? AllFeels() + { + return JsonSerializer.Deserialize>(SshConnection.RunCommand("~nebula/bin/dumpfeels")); + } + + public static List? FeelsAfter(long TimeStamp) + { + return JsonSerializer.Deserialize>(SshConnection.RunCommand($"~nebula/bin/dumpfeels --after {TimeStamp}")); + } + + public static List? FeelsFromUser(string User, List Feels) { return Feels.FindAll(i => i.User == User); } - public string GetFeelsBody(FeelsPost Post) + public static string GetFeelsBody(FeelsPost Post) { - return Ssh.RunCommand($"cat {Post.Path}"); + return SshConnection.RunCommand($"cat {Post.Path}"); + } + + private static void ConstructFeel(FeelsPost feel) + { + var date = DateTimeOffset.FromUnixTimeSeconds(feel.M_Time).DateTime.ToLocalTime(); + feel.TimeString = date.ToString("HH:mm (dddd, MMMM dd, yyyy)"); + var body = GetFeelsBody(feel); + char[] delimiters = new char[] { ' ', '\n' }; + feel.WordCount = body.Split(delimiters, StringSplitOptions.RemoveEmptyEntries).Length; + feel.Body = body; } } } diff --git a/FeelsFeedPage.xaml b/FeelsFeedPage.xaml index 23d13e9..15b4646 100644 --- a/FeelsFeedPage.xaml +++ b/FeelsFeedPage.xaml @@ -11,10 +11,10 @@ - + - + diff --git a/FeelsFeedPage.xaml.cs b/FeelsFeedPage.xaml.cs index b13c80c..cb4038f 100644 --- a/FeelsFeedPage.xaml.cs +++ b/FeelsFeedPage.xaml.cs @@ -1,8 +1,10 @@ +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using Microsoft.Windows.AppNotifications; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -14,34 +16,10 @@ namespace wakka /// public sealed partial class FeelsFeedPage : Page { - private readonly Feels Feels = new Feels(); - private readonly ObservableCollection LiveFeelsPosts = []; - private readonly List> AllFeels; - private int CurrentChunk = 0; - public FeelsFeedPage() { this.InitializeComponent(); - var feels = Feels.AllFeels(); - AllFeels = (List>)feels.Chunk(4).Select(chunk => chunk.ToList()).ToList(); - LoadFeelsChunk(); - feelsListView.ItemsSource = LiveFeelsPosts; - } - - private void LoadFeelsChunk() - { - var chunk = AllFeels[CurrentChunk]; - foreach (var feel in chunk) - { - var date = DateTimeOffset.FromUnixTimeSeconds(feel.M_Time).DateTime.ToLocalTime(); - feel.TimeString = date.ToString("HH:mm (dddd, MMMM dd, yyyy)"); - var body = Feels.GetFeelsBody(feel); - char[] delimiters = new char[] { ' ', '\n' }; - feel.WordCount = body.Split(delimiters, StringSplitOptions.RemoveEmptyEntries).Length; - feel.Body = body; - LiveFeelsPosts.Add(feel); - } - CurrentChunk++; + feelsListView.ItemsSource = Feels.LoadedFeelsPosts; } private void ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) @@ -51,7 +29,7 @@ namespace wakka { if (scrollViewer.VerticalOffset >= scrollViewer.ScrollableHeight) { - LoadFeelsChunk(); + Feels.LoadFeelsChunk(); } } } diff --git a/FeelsNavigationPage.xaml b/FeelsNavigationPage.xaml index 90d0898..ff91c42 100644 --- a/FeelsNavigationPage.xaml +++ b/FeelsNavigationPage.xaml @@ -14,11 +14,34 @@ - - - - - + + + + + + + + + + + + + + + + diff --git a/FeelsNavigationPage.xaml.cs b/FeelsNavigationPage.xaml.cs index 28fa9f2..dae9d2c 100644 --- a/FeelsNavigationPage.xaml.cs +++ b/FeelsNavigationPage.xaml.cs @@ -1,18 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; -using Microsoft.UI.Xaml.Media.Animation; +using System; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -33,5 +20,44 @@ namespace wakka { FeelsNavigationFrame.Navigate(typeof(FeelsFeedPage)); } + + private async void OnFeelsCreateButton(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + var path = Feels.GetTodayFeelPath(); + if (SshConnection.DoesFileExist(path)) + { + feelsComposeBox.Text = SshConnection.RunCommand($"cat {path}"); + feelsComposeDialog.IsPrimaryButtonEnabled = true; + } + else + { + feelsComposeBox.Text = string.Empty; + feelsComposeDialog.IsPrimaryButtonEnabled = false; + } + await feelsComposeDialog.ShowAsync(); + } + + private void OnFeelsSubmitButton(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + var message = feelsComposeBox.Text; + if (!string.IsNullOrEmpty(message)) + { + Feels.PostFeel(message); + feelsComposeBox.Text = string.Empty; + Feels.GetNewFeels(); + } + } + + private void OnTextChanged(object sender, TextChangedEventArgs e) + { + if (string.IsNullOrEmpty(feelsComposeBox.Text)) + { + feelsComposeDialog.IsPrimaryButtonEnabled = false; + } + else + { + feelsComposeDialog.IsPrimaryButtonEnabled = true; + } + } } } diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 53f68d0..5513049 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -13,18 +13,19 @@ namespace wakka public sealed partial class MainWindow : Window { - private static SshConnection Ssh { get; set; } = new SshConnection(); public Frame RootFrame; public MainWindow() { this.InitializeComponent(); RootFrame = rootFrame; - Ssh.DeleteSshKey(); - if (Ssh.KeyExists() & (string)App.LocalSettingsData.Values["username"] != null) + //Ssh.DeleteSshKey(); + if (SshConnection.KeyExists() & (string)App.LocalSettingsData.Values["username"] != null) { try { - Ssh.InitializeConnection((string)App.LocalSettingsData.Values["username"]); + SshConnection.InitializeConnection((string)App.LocalSettingsData.Values["username"]); + Bink.Initialize(); + Feels.Initialize(); RootFrame.Content = new NavigationPage(); } catch (Renci.SshNet.Common.SshAuthenticationException) diff --git a/NavigationPage.xaml.cs b/NavigationPage.xaml.cs index 6af7980..6e20e57 100644 --- a/NavigationPage.xaml.cs +++ b/NavigationPage.xaml.cs @@ -34,10 +34,12 @@ namespace wakka { if (selectedItem == binkPageNav) { + Bink.GetNewBinks(); navigationFrame.Navigate(typeof(BinkPage)); } else if (selectedItem == feelsPageNav) { + Feels.GetNewFeels(); navigationFrame.Navigate(typeof(FeelsNavigationPage)); } } diff --git a/Package.appxmanifest b/Package.appxmanifest index d59f119..c69234e 100644 --- a/Package.appxmanifest +++ b/Package.appxmanifest @@ -5,12 +5,14 @@ xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" + xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" + xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap rescap"> + Publisher="CN=helixnebula" + Version="0.0.1.0" /> @@ -42,6 +44,23 @@ + + + + + + + + + + + + + + + + + diff --git a/SshConnection.cs b/SshConnection.cs index 7904e88..97fc7bd 100644 --- a/SshConnection.cs +++ b/SshConnection.cs @@ -1,19 +1,14 @@ -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; -using Renci.SshNet; +using Renci.SshNet; using Renci.SshNet.Common; using System; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using Windows.Media.Protection.PlayReady; namespace wakka { - public class SshConnection + public static class SshConnection { public static string User { get; set; } = string.Empty; @@ -25,9 +20,9 @@ namespace wakka public static string? PublicKey { get; set; } = null; - public bool KeyExists() => File.Exists(SshKeyPath); + public static bool KeyExists() => File.Exists(SshKeyPath); - public void InitializeConnection(string user) + public static void InitializeConnection(string user) { if (!File.Exists(SshKeyPath) || user == string.Empty) { @@ -50,7 +45,7 @@ namespace wakka } } - public string RunCommand(string command) + public static string RunCommand(string command) { if (Client == null || !Client.IsConnected) { @@ -60,7 +55,18 @@ namespace wakka return rc.Result; } - public async void RunCommandWithInput(string command, string input) + public static bool DoesFileExist(string path) + { + if (Client == null || !Client.IsConnected) + { + throw new InvalidOperationException("SSH client is not connected."); + } + var command = Client.CreateCommand($"test -e {path} && echo 1 || echo 0"); + var result = command.Execute(); + return result.Trim() == "1"; + } + + public static async void RunCommandWithInput(string command, string input) { if (Client == null || !Client.IsConnected) { @@ -79,7 +85,7 @@ namespace wakka } } - public string CreateSshKey() + public static string CreateSshKey() { var keygen = new SshKeyGenerator.SshKeyGenerator(2048); var privateKey = keygen.ToPrivateKey(); @@ -87,7 +93,7 @@ namespace wakka return keygen.ToRfcPublicKey("wakka"); } - public void DeleteSshKey() + public static void DeleteSshKey() { if (File.Exists(SshKeyPath)) { diff --git a/StartupSshPage.xaml.cs b/StartupSshPage.xaml.cs index 94f2616..3e60e75 100644 --- a/StartupSshPage.xaml.cs +++ b/StartupSshPage.xaml.cs @@ -2,12 +2,10 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Renci.SshNet.Common; using System; -using System.Threading.Tasks; using Windows.ApplicationModel.DataTransfer; using Windows.Storage; using Windows.Storage.Pickers; using WinRT.Interop; -using static System.Net.Mime.MediaTypeNames; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -19,8 +17,6 @@ namespace wakka public sealed partial class StartupSshPage : Page { - private static SshConnection Ssh { get; set; } = new SshConnection(); - public StartupSshPage() { this.InitializeComponent(); @@ -58,7 +54,7 @@ namespace wakka private void CreateSshKey(object sender, RoutedEventArgs e) { - var publicKey = Ssh.CreateSshKey(); + var publicKey = SshConnection.CreateSshKey(); if (publicKey != null) { var dataPackage = new DataPackage(); @@ -66,7 +62,6 @@ namespace wakka Clipboard.SetContent(dataPackage); sshKeyCreateButton.Content = "Public key copied!"; } - } private void OnTextChanged(object sender, TextChangedEventArgs e) @@ -98,7 +93,9 @@ namespace wakka { try { - Ssh.InitializeConnection((string)App.LocalSettingsData.Values["username"]); + SshConnection.InitializeConnection((string)App.LocalSettingsData.Values["username"]); + Bink.Initialize(); + Feels.Initialize(); var mainWindow = (App.Current as App)?.m_window; if (mainWindow != null) { diff --git a/wakka.csproj b/wakka.csproj index a576576..c8d37dc 100644 --- a/wakka.csproj +++ b/wakka.csproj @@ -1,4 +1,4 @@ - + WinExe net8.0-windows10.0.19041.0 @@ -94,5 +94,16 @@ True False True + False + True + 158AC0CEA4875FEA7A385A58844BE2EA095E5FCE + SHA256 + False + False + True + Always + x86|x64|arm64 + 0 + 12.0 \ No newline at end of file