From ad96f334fb90d308861c8cf46e6cb077d628b075 Mon Sep 17 00:00:00 2001 From: Brian Luft Date: Sun, 24 Nov 2024 07:43:40 -0400 Subject: [PATCH] Move M3U8 sync settings into Options --- src/J.App/Client.cs | 21 +- src/J.App/LibraryProviderAdapter.cs | 3 - src/J.App/LoginForm.cs | 306 +++++++++++----------------- src/J.App/M3u8FolderSync.cs | 16 +- src/J.App/MainForm.cs | 43 +++- src/J.App/OptionsForm.cs | 89 ++++++-- src/J.Core/Data/AccountSettings.cs | 7 +- src/J.Core/Data/M3u8SyncSettings.cs | 10 + src/J.Core/LibraryProvider.cs | 3 +- src/J.Core/Preferences.cs | 2 + src/J.Server/Program.cs | 6 +- 11 files changed, 279 insertions(+), 227 deletions(-) create mode 100644 src/J.Core/Data/M3u8SyncSettings.cs diff --git a/src/J.App/Client.cs b/src/J.App/Client.cs index fb6e1ef..a753578 100644 --- a/src/J.App/Client.cs +++ b/src/J.App/Client.cs @@ -8,8 +8,11 @@ namespace J.App; -public sealed class Client(IHttpClientFactory httpClientFactory, AccountSettingsProvider accountSettingsProvider) - : IDisposable +public sealed class Client( + IHttpClientFactory httpClientFactory, + AccountSettingsProvider accountSettingsProvider, + Preferences preferences +) : IDisposable { private readonly HttpClient _httpClient = httpClientFactory.CreateClient(typeof(Client).FullName!); @@ -39,7 +42,10 @@ public void Start() }; Port = FindRandomUnusedPort(); - var bindHost = accountSettingsProvider.Current.EnableLocalM3u8Folder ? "*" : "localhost"; + + var m3u8Settings = preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings); + + var bindHost = m3u8Settings.EnableLocalM3u8Folder ? "*" : "localhost"; psi.Environment["ASPNETCORE_URLS"] = $"http://{bindHost}:{Port}"; psi.Environment["JACKPOT_SESSION_PASSWORD"] = SessionPassword; @@ -66,6 +72,15 @@ public void Stop() } } + public void Restart() + { + lock (_lock) + { + Stop(); + Start(); + } + } + public string GetMovieM3u8Url(Movie movie) { var query = HttpUtility.ParseQueryString(""); diff --git a/src/J.App/LibraryProviderAdapter.cs b/src/J.App/LibraryProviderAdapter.cs index 4cb0ec3..4c4dac4 100644 --- a/src/J.App/LibraryProviderAdapter.cs +++ b/src/J.App/LibraryProviderAdapter.cs @@ -46,9 +46,6 @@ public async Task DeleteTagAsync(TagId id, Action updateProgress, Cancel public byte[] GetMovieClip(MovieId movieId) => libraryProvider.GetMovieClip(movieId); - public byte[] GetM3u8(MovieId movieId, int portNumber, string sessionPassword) => - libraryProvider.GetM3u8(movieId, portNumber, sessionPassword); - public async Task NewTagTypeAsync(TagType tagType, Action updateProgress, CancellationToken cancel) { m3U8FolderSync.Invalidate(tagTypes: [tagType.Id]); diff --git a/src/J.App/LoginForm.cs b/src/J.App/LoginForm.cs index 91bfdc1..9ab8878 100644 --- a/src/J.App/LoginForm.cs +++ b/src/J.App/LoginForm.cs @@ -14,24 +14,17 @@ public sealed class LoginForm : Form _accessKeyIdText, _secretAccessKeyText, _bucketText, - _passwordText, - _m3u8FolderText, - _m3u8HostnameText; + _passwordText; private readonly Button _saveButton, _cancelButton, _toolsButton; private readonly FlowLayoutPanel _saveCancelButtonsFlow, _toolsButtonFlow, - _m3u8Flow, _bucketHelpFlow, _keyHelpFlow, _bucketFlow, _keyFlow, _encryptionFlow; - private readonly TabControl _tabControl; - private readonly TabPage _b2Page, - _m3u8Page; - private readonly CheckBox _enableM3u8FolderCheck; private readonly LinkLabel _b2BucketLink, _b2KeyLink; private readonly PictureBox _b2BucketIconPicture, @@ -55,190 +48,141 @@ public LoginForm(AccountSettingsProvider accountSettingsProvider) _formTable.Padding = ui.DefaultPadding; _formTable.Dock = DockStyle.Fill; - _formTable.Controls.Add(_tabControl = ui.NewTabControl(175), 0, 0); + _formTable.Controls.Add(_b2Table = ui.NewTable(2, 3), 0, 0); { - _formTable.SetColumnSpan(_tabControl, 2); - _formTable.RowStyles[0].SizeType = SizeType.Percent; - _formTable.RowStyles[0].Height = 100; + _formTable.SetColumnSpan(_b2Table, 2); + _b2Table.Padding = ui.LeftSpacing + ui.RightSpacing; + + _b2Table.Controls.Add( + _b2BucketIconPicture = ui.NewPictureBox( + ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Bucket.png", 32, 32)) + ), + 0, + 0 + ); + { + _b2BucketIconPicture.Padding = ui.RightSpacing; + } - _tabControl.TabPages.Add(_b2Page = ui.NewTabPage("Backblaze B2")); + _b2Table.Controls.Add(_bucketFlow = ui.NewFlowColumn(), 1, 0); { - _b2Page.Controls.Add(_b2Table = ui.NewTable(2, 3)); - { - _b2Table.Padding = ui.DefaultPadding; - - _b2Table.Controls.Add( - _b2BucketIconPicture = ui.NewPictureBox( - ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Bucket.png", 32, 32)) - ), - 0, - 0 - ); - { - _b2BucketIconPicture.Padding = ui.RightSpacing; - } + _bucketFlow.Padding += ui.BottomSpacingBig; - _b2Table.Controls.Add(_bucketFlow = ui.NewFlowColumn(), 1, 0); - { - _bucketFlow.Padding += ui.BottomSpacingBig; - - _bucketFlow.Controls.Add(_bucketHelpFlow = ui.NewFlowRow()); - { - _bucketHelpFlow.Margin += ui.BottomSpacing; - - _bucketHelpFlow.Controls.Add(label = ui.NewLabel("Create a bucket at:")); - { - label.Margin = label.Margin with { Right = 0 }; - label.Padding = label.Padding with { Right = 0 }; - label.Dock = DockStyle.Fill; - label.TextAlign = ContentAlignment.MiddleCenter; - } - - _bucketHelpFlow.Controls.Add( - _b2BucketLink = ui.NewLinkLabel("B2 Cloud Storage Buckets") - ); - { - _b2BucketLink.Margin = label.Margin with { Left = 0, Right = 0 }; - _b2BucketLink.Padding = label.Padding with { Left = 0, Right = 0 }; - _b2BucketLink.LinkClicked += B2BucketLink_LinkClicked; - _b2BucketLink.Dock = DockStyle.Fill; - _b2BucketLink.TextAlign = ContentAlignment.MiddleCenter; - } - } - - _bucketFlow.Controls.Add( - ui.NewLabeledPair("Bucket name:", _bucketText = ui.NewTextBox(400)) - ); - { - _bucketText.Margin += ui.BottomSpacing; - } - - _bucketFlow.Controls.Add( - ui.NewLabeledPair("Endpoint:", _endpointText = ui.NewTextBox(400)) - ); - { - _endpointText.Margin += ui.BottomSpacing; - } - } + _bucketFlow.Controls.Add(_bucketHelpFlow = ui.NewFlowRow()); + { + _bucketHelpFlow.Margin += ui.BottomSpacing; - _b2Table.Controls.Add( - _b2KeyIconPicture = ui.NewPictureBox( - ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Key.png", 32, 32)) - ), - 0, - 1 - ); + _bucketHelpFlow.Controls.Add(label = ui.NewLabel("Create a bucket at:")); { - _b2KeyIconPicture.Padding = ui.RightSpacing; + label.Margin = label.Margin with { Right = 0 }; + label.Padding = label.Padding with { Right = 0 }; + label.Dock = DockStyle.Fill; + label.TextAlign = ContentAlignment.MiddleCenter; } - _b2Table.Controls.Add(_keyFlow = ui.NewFlowColumn(), 1, 1); + _bucketHelpFlow.Controls.Add(_b2BucketLink = ui.NewLinkLabel("B2 Cloud Storage Buckets")); { - _keyFlow.Padding += ui.BottomSpacingBig; - - _keyFlow.Controls.Add(_keyHelpFlow = ui.NewFlowRow()); - { - _keyHelpFlow.Margin += ui.BottomSpacing; - - _keyHelpFlow.Controls.Add(label = ui.NewLabel("Create an application key at:")); - { - label.Margin = label.Margin with { Right = 0 }; - label.Padding = label.Padding with { Right = 0 }; - label.Dock = DockStyle.Fill; - label.TextAlign = ContentAlignment.MiddleCenter; - } - - _keyHelpFlow.Controls.Add(_b2KeyLink = ui.NewLinkLabel("B2 Application Keys")); - { - _b2KeyLink.LinkClicked += B2KeyLink_LinkClicked; - _b2KeyLink.Margin = label.Margin with { Left = 0, Right = 0 }; - _b2KeyLink.Padding = label.Padding with { Left = 0, Right = 0 }; - _b2KeyLink.Dock = DockStyle.Fill; - _b2KeyLink.TextAlign = ContentAlignment.MiddleCenter; - } - } - - _keyFlow.Controls.Add(ui.NewLabeledPair("keyID:", _accessKeyIdText = ui.NewTextBox(400))); - { - _accessKeyIdText.Margin += ui.BottomSpacing; - } - - _keyFlow.Controls.Add( - ui.NewLabeledPair("applicationKey:", _secretAccessKeyText = ui.NewTextBox(400)) - ); - { - _secretAccessKeyText.Margin += ui.BottomSpacing; - _secretAccessKeyText.PasswordChar = '•'; - } + _b2BucketLink.Margin = label.Margin with { Left = 0, Right = 0 }; + _b2BucketLink.Padding = label.Padding with { Left = 0, Right = 0 }; + _b2BucketLink.LinkClicked += B2BucketLink_LinkClicked; + _b2BucketLink.Dock = DockStyle.Fill; + _b2BucketLink.TextAlign = ContentAlignment.MiddleCenter; } + } - _b2Table.Controls.Add( - _encryptionIconPicture = ui.NewPictureBox( - ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Encryption.png", 32, 32)) - ), - 0, - 2 - ); - { - _encryptionIconPicture.Padding = ui.RightSpacing; - } + _bucketFlow.Controls.Add(ui.NewLabeledPair("Bucket name:", _bucketText = ui.NewTextBox(400))); + { + _bucketText.Margin += ui.BottomSpacing; + } - _b2Table.Controls.Add(_encryptionFlow = ui.NewFlowColumn(), 1, 2); - { - _encryptionFlow.Controls.Add( - _encryptionLabel = ui.NewLabel("Choose a password to encrypt your library files.") - ); - { - _encryptionLabel.Margin = ui.BottomSpacing; - } - - _encryptionFlow.Controls.Add( - ui.NewLabeledPair("Password:", _passwordText = ui.NewTextBox(400)) - ); - { - _passwordText.Margin += ui.BottomSpacing; - _passwordText.PasswordChar = '•'; - } - } + _bucketFlow.Controls.Add(ui.NewLabeledPair("Endpoint:", _endpointText = ui.NewTextBox(400))); + { + _endpointText.Margin += ui.BottomSpacing; } } - _tabControl.TabPages.Add(_m3u8Page = ui.NewTabPage("Network Sharing")); + _b2Table.Controls.Add( + _b2KeyIconPicture = ui.NewPictureBox( + ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Key.png", 32, 32)) + ), + 0, + 1 + ); { - _m3u8Page.Controls.Add(_m3u8Flow = ui.NewFlowColumn()); - { - _m3u8Flow.Padding = ui.DefaultPadding; + _b2KeyIconPicture.Padding = ui.RightSpacing; + } - _m3u8Flow.Controls.Add( - ui.NewLabel( - "Jackpot can maintain a folder of M3U8 files for non-Windows devices to\nplay via Windows file sharing." - ) - ); + _b2Table.Controls.Add(_keyFlow = ui.NewFlowColumn(), 1, 1); + { + _keyFlow.Padding += ui.BottomSpacingBig; - _m3u8Flow.Controls.Add( - _enableM3u8FolderCheck = ui.NewCheckBox("Store M3U8 files in a local folder") - ); - { - _enableM3u8FolderCheck.Margin += ui.TopSpacingBig + ui.BottomSpacing; - } + _keyFlow.Controls.Add(_keyHelpFlow = ui.NewFlowRow()); + { + _keyHelpFlow.Margin += ui.BottomSpacing; - (p, _m3u8FolderText) = ui.NewLabeledOpenFolderTextBox("Folder:", 500, _ => { }); + _keyHelpFlow.Controls.Add(label = ui.NewLabel("Create an application key at:")); { - _m3u8Flow.Controls.Add(p); - p.Margin = ui.BottomSpacing; + label.Margin = label.Margin with { Right = 0 }; + label.Padding = label.Padding with { Right = 0 }; + label.Dock = DockStyle.Fill; + label.TextAlign = ContentAlignment.MiddleCenter; } - (p, _m3u8HostnameText) = ui.NewLabeledTextBox("Host or IP address to use in M3U8 files:", 300); + _keyHelpFlow.Controls.Add(_b2KeyLink = ui.NewLinkLabel("B2 Application Keys")); { - _m3u8Flow.Controls.Add(p); + _b2KeyLink.LinkClicked += B2KeyLink_LinkClicked; + _b2KeyLink.Margin = label.Margin with { Left = 0, Right = 0 }; + _b2KeyLink.Padding = label.Padding with { Left = 0, Right = 0 }; + _b2KeyLink.Dock = DockStyle.Fill; + _b2KeyLink.TextAlign = ContentAlignment.MiddleCenter; } } + + _keyFlow.Controls.Add(ui.NewLabeledPair("keyID:", _accessKeyIdText = ui.NewTextBox(400))); + { + _accessKeyIdText.Margin += ui.BottomSpacing; + } + + _keyFlow.Controls.Add( + ui.NewLabeledPair("applicationKey:", _secretAccessKeyText = ui.NewTextBox(400)) + ); + { + _secretAccessKeyText.Margin += ui.BottomSpacing; + _secretAccessKeyText.PasswordChar = '•'; + } + } + + _b2Table.Controls.Add( + _encryptionIconPicture = ui.NewPictureBox( + ui.InvertColorsInPlace(ui.GetScaledBitmapResource("Encryption.png", 32, 32)) + ), + 0, + 2 + ); + { + _encryptionIconPicture.Padding = ui.RightSpacing; + } + + _b2Table.Controls.Add(_encryptionFlow = ui.NewFlowColumn(), 1, 2); + { + _encryptionFlow.Controls.Add( + _encryptionLabel = ui.NewLabel("Choose a password to encrypt your library files.") + ); + { + _encryptionLabel.Margin = ui.BottomSpacing; + } + + _encryptionFlow.Controls.Add(ui.NewLabeledPair("Password:", _passwordText = ui.NewTextBox(400))); + { + _passwordText.Margin += ui.BottomSpacing; + _passwordText.PasswordChar = '•'; + } } } _formTable.Controls.Add(_toolsButtonFlow = ui.NewFlowRow(), 0, 1); { - _toolsButtonFlow.Margin = ui.TopSpacing; + _toolsButtonFlow.Margin = ui.TopSpacingBig; _toolsButtonFlow.Controls.Add(_toolsButton = ui.NewButton("Tools 🞃")); { _toolsButton.Click += ToolsButton_Click; @@ -248,9 +192,9 @@ public LoginForm(AccountSettingsProvider accountSettingsProvider) _formTable.Controls.Add(_saveCancelButtonsFlow = ui.NewFlowRow(), 1, 1); { _saveCancelButtonsFlow.Dock = DockStyle.Right; - _saveCancelButtonsFlow.Margin = ui.TopSpacing; + _saveCancelButtonsFlow.Margin = ui.TopSpacingBig; - _saveCancelButtonsFlow.Controls.Add(_saveButton = ui.NewButton("Log in")); + _saveCancelButtonsFlow.Controls.Add(_saveButton = ui.NewButton("Connect")); { _saveButton.Click += SaveButton_Click; _saveButton.Margin += ui.ButtonSpacing; @@ -265,7 +209,7 @@ public LoginForm(AccountSettingsProvider accountSettingsProvider) _toolsMenu = ui.NewContextMenuStrip(); { - _toolsMenu.Items.Add(_copyJsonItem = ui.NewToolStripMenuItem("Copy JSON")); + _toolsMenu.Items.Add(_copyJsonItem = ui.NewToolStripMenuItem("Copy as JSON")); _copyJsonItem.Click += CopySettingsButton_Click; _toolsMenu.Items.Add(_pasteJsonItem = ui.NewToolStripMenuItem("Paste JSON")); @@ -273,8 +217,9 @@ public LoginForm(AccountSettingsProvider accountSettingsProvider) } Text = "Jackpot Media Library"; + AutoSize = true; + AutoSizeMode = AutoSizeMode.GrowAndShrink; StartPosition = FormStartPosition.CenterScreen; - MinimumSize = Size = ui.GetSize(600, 650); FormBorderStyle = FormBorderStyle.FixedDialog; MinimizeBox = false; MaximizeBox = false; @@ -283,11 +228,18 @@ public LoginForm(AccountSettingsProvider accountSettingsProvider) Icon = ui.GetIconResource("App.ico"); ShowIcon = true; ShowInTaskbar = true; + } - Load += delegate - { - SetAccountSettings(accountSettingsProvider.Current); - }; + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + SetAccountSettings(_accountSettingsProvider.Current); + } + + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + _saveButton.Focus(); } private void B2KeyLink_LinkClicked(object? sender, LinkLabelLinkClickedEventArgs e) @@ -335,21 +287,12 @@ private AccountSettings GetAccountSettings() if (string.IsNullOrWhiteSpace(_passwordText.Text)) throw new Exception("Please enter an encryption password."); - if (_enableM3u8FolderCheck.Checked) - { - if (string.IsNullOrWhiteSpace(_m3u8FolderText.Text)) - throw new Exception("Please enter a folder for .M3U8 files."); - } - return new( _endpointText.Text, _accessKeyIdText.Text, _secretAccessKeyText.Text, _bucketText.Text, - _passwordText.Text, - _enableM3u8FolderCheck.Checked, - _m3u8FolderText.Text, - _m3u8HostnameText.Text + _passwordText.Text ); } @@ -360,9 +303,6 @@ private void SetAccountSettings(AccountSettings settings) _secretAccessKeyText.Text = settings.SecretAccessKey; _bucketText.Text = settings.Bucket; _passwordText.Text = settings.PasswordText; - _enableM3u8FolderCheck.Checked = settings.EnableLocalM3u8Folder; - _m3u8FolderText.Text = settings.LocalM3u8FolderPath; - _m3u8HostnameText.Text = settings.M3u8Hostname; } private void CopySettingsButton_Click(object? sender, EventArgs e) diff --git a/src/J.App/M3u8FolderSync.cs b/src/J.App/M3u8FolderSync.cs index ef02cd9..02407c5 100644 --- a/src/J.App/M3u8FolderSync.cs +++ b/src/J.App/M3u8FolderSync.cs @@ -6,7 +6,8 @@ namespace J.App; public sealed partial class M3u8FolderSync( AccountSettingsProvider accountSettingsProvider, LibraryProvider libraryProvider, - Client client + Client client, + Preferences preferences ) { private readonly string _filesystemInvalidChars = @@ -18,7 +19,8 @@ Client client private readonly HashSet _invalidatedMovies = []; private bool _invalidatedAll = true; - public bool Enabled => accountSettingsProvider.Current.EnableLocalM3u8Folder; + public bool Enabled => + preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings).EnableLocalM3u8Folder; public void Invalidate( IEnumerable? tagTypes = null, @@ -47,8 +49,7 @@ public void Invalidate( public void InvalidateAll() { - if (!Enabled) - return; + // Still invalidate even if sync is disabled, so that the next time it's enabled, it will sync everything. lock (_lock) { @@ -65,7 +66,9 @@ public void Sync(Action updateProgress) { var portNumber = client.Port; var sessionPassword = client.SessionPassword; - var dir = accountSettingsProvider.Current.LocalM3u8FolderPath; + var dir = preferences + .GetJson(Preferences.Key.M3u8FolderSync_Settings) + .LocalM3u8FolderPath; var movieTags = libraryProvider.GetMovieTags().ToLookup(x => x.TagId, x => x.MovieId); var movies = libraryProvider.GetMovies().ToDictionary(x => x.Id); @@ -240,7 +243,8 @@ bool writeFile if (writeFile) { - var bytes = libraryProvider.GetM3u8(movie.Id, portNumber, sessionPassword); + var hostname = preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings).M3u8Hostname; + var bytes = libraryProvider.GetM3u8(movie.Id, portNumber, sessionPassword, hostname); File.WriteAllBytes(m3u8FilePath, bytes); } } diff --git a/src/J.App/MainForm.cs b/src/J.App/MainForm.cs index 085b09d..5dfc1fd 100644 --- a/src/J.App/MainForm.cs +++ b/src/J.App/MainForm.cs @@ -6,6 +6,7 @@ using J.Base; using J.Core; using J.Core.Data; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.DependencyInjection; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; @@ -23,6 +24,7 @@ public sealed partial class MainForm : Form private readonly SingleInstanceManager _singleInstanceManager; private readonly Preferences _preferences; private readonly MovieExporter _movieExporter; + private readonly M3u8FolderSync _m3U8FolderSync; private readonly Ui _ui; private readonly ToolStrip _toolStrip; private readonly ToolStripDropDownButton _filterButton, @@ -75,7 +77,8 @@ public MainForm( ImportProgressFormFactory importProgressFormFactory, SingleInstanceManager singleInstanceManager, Preferences preferences, - MovieExporter movieExporter + MovieExporter movieExporter, + M3u8FolderSync m3U8FolderSync ) { _serviceProvider = serviceProvider; @@ -85,6 +88,7 @@ MovieExporter movieExporter _singleInstanceManager = singleInstanceManager; _preferences = preferences; _movieExporter = movieExporter; + _m3U8FolderSync = m3U8FolderSync; Ui ui = new(this); _ui = ui; @@ -654,17 +658,11 @@ private void BrowseBackButton_Click(object? sender, EventArgs e) private void Browser_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e) { - if (!_browser.Visible) - return; - _titleLabel.Text = "Loading..."; } private void Browser_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e) { - if (!_browser.Visible) - return; - var title = _browser.CoreWebView2.DocumentTitle; if (title.Length > 100) title = title[..100] + "..."; @@ -1058,13 +1056,38 @@ private void ConvertMoviesButton_Click(object? sender, EventArgs e) private async void OptionsButton_Click(object? sender, EventArgs e) { + var oldM3u8Settings = _preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings); + using var f = _serviceProvider.GetRequiredService(); if (f.ShowDialog(this) != DialogResult.OK) return; - ApplyFullscreenPreference(); - await _client.RefreshLibraryAsync(CancellationToken.None).ConfigureAwait(true); - _browser.Reload(); + try + { + var newM3u8Settings = _preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings); + + if (!oldM3u8Settings.Equals(newM3u8Settings)) + { + _client.Restart(); + _m3U8FolderSync.InvalidateAll(); + SimpleProgressForm.Do( + this, + "Synchronizing M3U8 folder...", + (updateProgress, cancel) => + { + _m3U8FolderSync.Sync(updateProgress); + } + ); + } + + ApplyFullscreenPreference(); + await _client.RefreshLibraryAsync(CancellationToken.None).ConfigureAwait(true); + _browser.Reload(); + } + catch (Exception ex) + { + MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } private void ApplyFullscreenPreference() diff --git a/src/J.App/OptionsForm.cs b/src/J.App/OptionsForm.cs index ed1a2d9..32dbae6 100644 --- a/src/J.App/OptionsForm.cs +++ b/src/J.App/OptionsForm.cs @@ -8,14 +8,19 @@ public sealed class OptionsForm : Form private readonly Preferences _preferences; private readonly TableLayoutPanel _table; private readonly FlowLayoutPanel _buttonFlow, - _generalFlow; + _generalFlow, + _m3u8Flow; private readonly TabControl _tabControl; - private readonly TabPage _generalTab; + private readonly TabPage _generalTab, + _m3u8Page; private readonly ComboBox _vlcCombo, _windowMaximizeBehaviorCombo, _columnCountCombo; private readonly Button _okButton, _cancelButton; + private readonly CheckBox _enableM3u8FolderCheck; + private readonly TextBox _m3u8FolderText, + _m3u8HostnameText; // VLC installation to use for playback private readonly List _vlcValues = @@ -44,7 +49,7 @@ public OptionsForm(Preferences preferences) _table.RowStyles[0].SizeType = SizeType.Percent; _table.RowStyles[0].Height = 100; - _table.Controls.Add(_tabControl = ui.NewTabControl(100), 0, 0); + _table.Controls.Add(_tabControl = ui.NewTabControl(200), 0, 0); { _tabControl.TabPages.Add(_generalTab = ui.NewTabPage("General")); { @@ -92,6 +97,44 @@ public OptionsForm(Preferences preferences) } } } + + _tabControl.TabPages.Add(_m3u8Page = ui.NewTabPage("Network Sharing")); + { + var m3u8Settings = preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings); + + _m3u8Page.Controls.Add(_m3u8Flow = ui.NewFlowColumn()); + { + _m3u8Flow.Padding = ui.DefaultPadding; + + _m3u8Flow.Controls.Add( + ui.NewLabel( + "Jackpot can maintain a folder of M3U8 files for non-Windows\ndevices to play via Windows file sharing." + ) + ); + + _m3u8Flow.Controls.Add( + _enableM3u8FolderCheck = ui.NewCheckBox("Store M3U8 files in a local folder") + ); + { + _enableM3u8FolderCheck.Margin += ui.TopSpacingBig + ui.BottomSpacing; + _enableM3u8FolderCheck.Checked = m3u8Settings.EnableLocalM3u8Folder; + } + + Control p; + (p, _m3u8FolderText) = ui.NewLabeledOpenFolderTextBox("Folder:", 400, _ => { }); + { + _m3u8Flow.Controls.Add(p); + p.Margin = ui.BottomSpacing; + _m3u8FolderText.Text = m3u8Settings.LocalM3u8FolderPath; + } + + (p, _m3u8HostnameText) = ui.NewLabeledTextBox("Host or IP address to use in M3U8 files:", 300); + { + _m3u8Flow.Controls.Add(p); + _m3u8HostnameText.Text = m3u8Settings.M3u8Hostname; + } + } + } } _table.Controls.Add(_buttonFlow = ui.NewFlowRow(), 0, 1); @@ -111,7 +154,7 @@ public OptionsForm(Preferences preferences) Text = "Options"; StartPosition = FormStartPosition.CenterScreen; - MinimumSize = Size = ui.GetSize(400, 400); + MinimumSize = Size = ui.GetSize(500, 400); FormBorderStyle = FormBorderStyle.FixedDialog; MinimizeBox = false; MaximizeBox = false; @@ -124,16 +167,34 @@ public OptionsForm(Preferences preferences) private void OkButton_Click(object? sender, EventArgs e) { - _preferences.WithTransaction(() => + try + { + if (_enableM3u8FolderCheck.Checked) + { + if (string.IsNullOrWhiteSpace(_m3u8FolderText.Text)) + throw new Exception("Please enter a folder for .M3U8 files."); + } + + M3u8SyncSettings m3u8SyncSettings = + new(_enableM3u8FolderCheck.Checked, _m3u8FolderText.Text, _m3u8HostnameText.Text); + + _preferences.WithTransaction(() => + { + _preferences.SetEnum(Preferences.Key.Shared_VlcInstallationToUse, _vlcValues[_vlcCombo.SelectedIndex]); + _preferences.SetEnum( + Preferences.Key.MainForm_WindowMaximizeBehavior, + _windowMaximizeBehaviorValues[_windowMaximizeBehaviorCombo.SelectedIndex] + ); + _preferences.SetInteger(Preferences.Key.Shared_ColumnCount, _columnCountCombo.SelectedIndex + 1); + _preferences.SetJson(Preferences.Key.M3u8FolderSync_Settings, m3u8SyncSettings); + }); + + DialogResult = DialogResult.OK; + Close(); + } + catch (Exception ex) { - _preferences.SetEnum(Preferences.Key.Shared_VlcInstallationToUse, _vlcValues[_vlcCombo.SelectedIndex]); - _preferences.SetEnum( - Preferences.Key.MainForm_WindowMaximizeBehavior, - _windowMaximizeBehaviorValues[_windowMaximizeBehaviorCombo.SelectedIndex] - ); - _preferences.SetInteger(Preferences.Key.Shared_ColumnCount, _columnCountCombo.SelectedIndex + 1); - }); - DialogResult = DialogResult.OK; - Close(); + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } } diff --git a/src/J.Core/Data/AccountSettings.cs b/src/J.Core/Data/AccountSettings.cs index 9305e6d..ccd5a8e 100644 --- a/src/J.Core/Data/AccountSettings.cs +++ b/src/J.Core/Data/AccountSettings.cs @@ -5,10 +5,7 @@ public readonly record struct AccountSettings( string AccessKeyId, string SecretAccessKey, string Bucket, - string PasswordText, - bool EnableLocalM3u8Folder, - string LocalM3u8FolderPath, - string M3u8Hostname + string PasswordText ) { public Password Password => new(PasswordText); @@ -20,7 +17,7 @@ string M3u8Hostname && !string.IsNullOrWhiteSpace(Bucket) && !string.IsNullOrWhiteSpace(PasswordText); - public static AccountSettings Empty => new("", "", "", "", "", false, "", ""); + public static AccountSettings Empty => new("", "", "", "", ""); public AccountSettings Upgrade() { diff --git a/src/J.Core/Data/M3u8SyncSettings.cs b/src/J.Core/Data/M3u8SyncSettings.cs new file mode 100644 index 0000000..1aa44e2 --- /dev/null +++ b/src/J.Core/Data/M3u8SyncSettings.cs @@ -0,0 +1,10 @@ +namespace J.Core.Data; + +public readonly record struct M3u8SyncSettings( + bool EnableLocalM3u8Folder, + string LocalM3u8FolderPath, + string M3u8Hostname +) +{ + public static M3u8SyncSettings Default => new(false, "", "localhost"); +} diff --git a/src/J.Core/LibraryProvider.cs b/src/J.Core/LibraryProvider.cs index 214365f..adc423a 100644 --- a/src/J.Core/LibraryProvider.cs +++ b/src/J.Core/LibraryProvider.cs @@ -353,9 +353,8 @@ public void DeleteMovie(MovieId id) public byte[] GetMovieClip(MovieId movieId) => GetMovieFileData(movieId, "clip.mp4"); - public byte[] GetM3u8(MovieId movieId, int portNumber, string sessionPassword) + public byte[] GetM3u8(MovieId movieId, int portNumber, string sessionPassword, string hostname) { - var hostname = _accountSettingsProvider.Current.M3u8Hostname; if (string.IsNullOrWhiteSpace(hostname)) hostname = "localhost"; diff --git a/src/J.Core/Preferences.cs b/src/J.Core/Preferences.cs index 05c7d4a..54caf62 100644 --- a/src/J.Core/Preferences.cs +++ b/src/J.Core/Preferences.cs @@ -24,6 +24,7 @@ public enum Key MainForm_CompleteWindowState, Shared_LibraryViewStyle, Shared_ColumnCount, + M3u8FolderSync_Settings, } public Preferences() @@ -48,6 +49,7 @@ public Preferences() """, [Key.Shared_LibraryViewStyle] = LibraryViewStyle.Grid.ToString(), [Key.Shared_ColumnCount] = 5L, + [Key.M3u8FolderSync_Settings] = JsonSerializer.Serialize(M3u8SyncSettings.Default), }.ToFrozenDictionary(); // Make sure every default is one of the four supported types. diff --git a/src/J.Server/Program.cs b/src/J.Server/Program.cs index 9377ec9..1259614 100644 --- a/src/J.Server/Program.cs +++ b/src/J.Server/Program.cs @@ -27,6 +27,7 @@ var password = accountSettings.Password ?? throw new Exception("Encryption key not found."); var preferences = app.Services.GetRequiredService(); +var m3u8Hostname = "localhost"; Dictionary> listPages = []; Dictionary> tagPages = []; @@ -38,6 +39,9 @@ void RefreshLibrary() { var sortOrder = preferences.GetJson(Preferences.Key.Shared_SortOrder); var filter = preferences.GetJson(Preferences.Key.Shared_Filter); + m3u8Hostname = preferences.GetJson(Preferences.Key.M3u8FolderSync_Settings).M3u8Hostname; + if (string.IsNullOrWhiteSpace(m3u8Hostname)) + m3u8Hostname = "localhost"; LibraryMetadata libraryMetadata = new( @@ -360,7 +364,7 @@ CancellationToken cancel ) => { CheckSessionPassword(sessionPassword); - var m3u8 = libraryProvider.GetM3u8(new(movieId), configuredPort, configuredSessionPassword); + var m3u8 = libraryProvider.GetM3u8(new(movieId), configuredPort, configuredSessionPassword, m3u8Hostname); response.ContentType = "application/vnd.apple.mpegurl"; await response.StartAsync(cancel); await response.Body.WriteAsync(m3u8, cancel);