Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ui/Features/Tools/AiReview/AiReviewWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
using Nikse.SubtitleEdit.Features.Files.Compare;
using Nikse.SubtitleEdit.Features.Main;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.Config;
using Nikse.SubtitleEdit.Logic.Platform.Progress;

namespace Nikse.SubtitleEdit.Features.Tools.AiReview;

Expand Down
2 changes: 1 addition & 1 deletion src/ui/Features/Video/BurnIn/BurnInWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
using Nikse.SubtitleEdit.Controls;
using Nikse.SubtitleEdit.Features.Main.Layout;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.Config;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.ValueConverters;
using Nikse.SubtitleEdit.UiLogic.Export;
using System;
Expand Down
2 changes: 1 addition & 1 deletion src/ui/Features/Video/ShotChanges/ShotChangesWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
using Avalonia.Layout;
using Avalonia.Styling;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.Config;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.ValueConverters;

namespace Nikse.SubtitleEdit.Features.Video.ShotChanges;
Expand Down
2 changes: 1 addition & 1 deletion src/ui/Features/Video/SpeechToText/SpeechToTextWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
using System.Linq;
using Nikse.SubtitleEdit.Features.Video.SpeechToText.Engines;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.Config;
using Nikse.SubtitleEdit.Logic.Download;
using Nikse.SubtitleEdit.Logic.Platform.Progress;
using Nikse.SubtitleEdit.Logic.ValueConverters;

namespace Nikse.SubtitleEdit.Features.Video.SpeechToText;
Expand Down
5 changes: 3 additions & 2 deletions src/ui/Logic/Platform/Progress/PlatformProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ public static class PlatformProgress
AvaloniaProperty.RegisterAttached<ProgressBar, bool>("IsActive", typeof(PlatformProgress));

private static readonly ConditionalWeakTable<Window, PlatformProgressGroup> WindowGroups = new();
private static readonly PlatformProgressGroup ApplicationGroup = new(() =>
OperatingSystem.IsMacOS() ? new MacDockProgress() : new LinuxLauncherProgress());
private static readonly PlatformProgressGroup ApplicationGroup = new(
() => OperatingSystem.IsMacOS() ? new MacDockProgress() : new LinuxLauncherProgress(),
keepBackendWhenIdle: OperatingSystem.IsLinux());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow Linux progress to retry a failed connection

When the first Linux progress operation starts while the session bus is temporarily unavailable or ConnectAsync fails, LinuxLauncherProgress permanently stores a completed task containing null. Retaining that backend while idle means every later operation reuses the failed instance and can never display launcher progress; previously, finishing the operation disposed it so the next operation attempted a new connection. Retain only a successfully connected backend, or make the Linux backend retry failed connections.

Useful? React with 👍 / 👎.


public static ProgressBar WithPlatformProgress(this ProgressBar progressBar, Window window,
string activeProperty, IValueConverter? converter = null)
Expand Down
22 changes: 18 additions & 4 deletions src/ui/Logic/Platform/Progress/PlatformProgressGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace Nikse.SubtitleEdit.Logic.Platform.Progress;

internal sealed class PlatformProgressGroup(Func<IPlatformProgress> createProgress)
internal sealed class PlatformProgressGroup(Func<IPlatformProgress> createProgress, bool keepBackendWhenIdle = false)
{
private readonly List<(object Source, double? Percentage, bool Indeterminate)> _sources = [];
private IPlatformProgress? _progress;
private (object Source, int? Percentage, bool Indeterminate)? _lastUpdate;

public void Update(object source, double? percentage, bool indeterminate)
{
Expand All @@ -30,14 +31,27 @@ public void Update(object source, double? percentage, bool indeterminate)
if (_sources.Count == 0)
{
_progress?.Update(null, false);
_progress?.Dispose();
_progress = null;
_lastUpdate = null;
// Keep the Linux connection alive so asynchronous clear requests can be sent.
if (!keepBackendWhenIdle)
{
_progress?.Dispose();
_progress = null;
}
return;
}

_progress ??= createProgress();
var current = _sources[^1];
_progress.Update(current.Percentage, current.Indeterminate);
var effectivePercentage = !current.Indeterminate && current.Percentage is { } value
? (int?)value : null;
// Source changes must be published even when their visible state is the same.
if (_lastUpdate is { } last && ReferenceEquals(last.Source, current.Source) &&
last.Percentage == effectivePercentage && last.Indeterminate == current.Indeterminate)
return;

_progress.Update(effectivePercentage, current.Indeterminate);
_lastUpdate = (current.Source, effectivePercentage, current.Indeterminate);
}
catch (Exception exception)
{
Expand Down
134 changes: 123 additions & 11 deletions tests/UI/Logic/Platform/PlatformProgressTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ private sealed class FakeProgress : IPlatformProgress
public void Dispose() => IsDisposed = true;
}

[Fact]
public void CompletingCurrentSourceRestoresOtherActiveSource()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CompletingCurrentSourceRestoresOtherActiveSource(bool keepBackendWhenIdle)
{
var progress = new FakeProgress();
var group = new PlatformProgressGroup(() => progress);
var group = new PlatformProgressGroup(() => progress, keepBackendWhenIdle);
var first = new object();
var second = new object();

Expand All @@ -58,7 +60,7 @@ public void CompletingCurrentSourceRestoresOtherActiveSource()

group.Update(first, null, false);
Assert.Equal(((double?)null, false), progress.Updates[^1]);
Assert.True(progress.IsDisposed);
Assert.Equal(!keepBackendWhenIdle, progress.IsDisposed);
}

[Fact]
Expand All @@ -73,6 +75,7 @@ public void CompletingBackgroundSourceKeepsCurrentSource()
group.Update(second, 20, false);
group.Update(first, null, false);

Assert.Equal(2, progress.Updates.Count);
Assert.Equal(((double?)20, false), progress.Updates[^1]);
Assert.False(progress.IsDisposed);
group.Update(second, null, false);
Expand Down Expand Up @@ -111,28 +114,137 @@ public void IndeterminateSourceRemainsActive()
group.Update(source, null, false);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void RetainedBackendClearsAndIsReusedForNextOperation(bool indeterminate)
{
var instances = new List<FakeProgress>();
var group = new PlatformProgressGroup(() =>
{
var progress = new FakeProgress();
instances.Add(progress);
return progress;
}, keepBackendWhenIdle: true);
var first = new object();
var second = new object();
double? percentage = indeterminate ? null : 10;

group.Update(first, null, false);
Assert.Empty(instances);

group.Update(first, percentage, indeterminate);
var backend = Assert.Single(instances);
group.Update(first, null, false);

Assert.Equal(2, backend.Updates.Count);
Assert.Equal((percentage, indeterminate), backend.Updates[0]);
Assert.Equal(((double?)null, false), backend.Updates[1]);
Assert.False(backend.IsDisposed);

group.Update(second, 20, false);

Assert.Same(backend, Assert.Single(instances));
Assert.Equal(((double?)20, false), backend.Updates[^1]);
group.Update(second, null, false);
Assert.Equal(4, backend.Updates.Count);
Assert.Equal(((double?)null, false), backend.Updates[^1]);
Assert.False(backend.IsDisposed);
}

[Fact]
public void UpdatesWithinSameWholePercentageAreSkipped()
{
var progress = new FakeProgress();
var group = new PlatformProgressGroup(() => progress);
var source = new object();

group.Update(source, 10.1, false);
group.Update(source, 10.99, false);
group.Update(source, 11, false);
group.Update(source, 11.9, false);
group.Update(source, 10.9, false);

Assert.Equal(new[] { ((double?)10, false), ((double?)11, false), ((double?)10, false) }, progress.Updates);
group.Update(source, null, false);
Assert.Equal(((double?)null, false), progress.Updates[^1]);
}

[Fact]
public void NewOperationCreatesNewBackendAfterCleanup()
public void IndeterminateTransitionsAreSentButNumericChangesAreSkipped()
{
var progress = new FakeProgress();
var group = new PlatformProgressGroup(() => progress);
var source = new object();

group.Update(source, 35.2, false);
group.Update(source, 35.2, true);
group.Update(source, 80.9, true);
group.Update(source, null, true);
group.Update(source, 35.9, false);

Assert.Equal(new[] { ((double?)35, false), ((double?)null, true), ((double?)35, false) }, progress.Updates);
group.Update(source, null, false);
Assert.Equal(((double?)null, false), progress.Updates[^1]);
}

[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public void NewSourceAndRestorationAreSentEvenWithSameEffectiveState(bool keepBackendWhenIdle, bool indeterminate)
{
var progress = new FakeProgress();
var group = new PlatformProgressGroup(() => progress, keepBackendWhenIdle);
var first = new object();
var second = new object();

group.Update(first, 20.2, indeterminate);
group.Update(second, 20.9, indeterminate);
group.Update(first, 20.7, indeterminate);
Assert.Equal(2, progress.Updates.Count);

group.Update(second, null, false);

double? effectivePercentage = indeterminate ? null : 20;
Assert.Equal(new[] { (effectivePercentage, indeterminate), (effectivePercentage, indeterminate),
(effectivePercentage, indeterminate) }, progress.Updates);
group.Update(first, null, false);
Assert.Equal(((double?)null, false), progress.Updates[^1]);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void ClearAndRestartOfSameSourceAreSentEvenWithSameWholePercentage(bool keepBackendWhenIdle)
{
var instances = new List<FakeProgress>();
var group = new PlatformProgressGroup(() =>
{
var progress = new FakeProgress();
instances.Add(progress);
return progress;
});
}, keepBackendWhenIdle);
var source = new object();

group.Update(source, null, false);
Assert.Empty(instances);
group.Update(source, 10, false);

group.Update(source, 20.2, false);
group.Update(source, 20.9, false);
group.Update(source, null, false);
group.Update(source, 20, false);

Assert.Equal(2, instances.Count);
Assert.True(instances[0].IsDisposed);
Assert.Equal(((double?)20, false), instances[1].Updates[^1]);
Assert.Equal(new[] { ((double?)20, false), ((double?)null, false) }, instances[0].Updates);
Assert.Equal(!keepBackendWhenIdle, instances[0].IsDisposed);

group.Update(source, 20.7, false);

Assert.Equal(keepBackendWhenIdle ? 1 : 2, instances.Count);
Assert.Equal(keepBackendWhenIdle ? 3 : 1, instances[^1].Updates.Count);
Assert.Equal(((double?)20, false), instances[^1].Updates[^1]);
group.Update(source, null, false);
Assert.Equal(((double?)null, false), instances[^1].Updates[^1]);
}

[Fact]
Expand Down
Loading