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());

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