Skip to content

Commit f196226

Browse files
committed
Enhance Watcher functionality with cancellation support
- Added cancellation tokens to `RunWatcher` and `ParseWatchers` methods in the `Watcher` class for improved operation management. - Introduced `_isRunning` property to track the running state and manage folder monitoring. - Updated `Analyse` method to support cancellation during file analysis. - Implemented `CancelBackgrounding` method in `WatcherViewModel` for cancelling background compaction and updating the UI. - Improved XAML layout in `FolderWatcherCard` with new buttons for user interaction. - Updated `Application` class for better service initialization and multi-instance handling. - Enhanced `WatcherViewModel` with commands for running the watcher and cancelling operations, improving UI responsiveness. - Moved host initialisation to the OnStartup() of Application.vb to avoid unecessary instantiation when opening CompactGUI while an instance is already running
1 parent adea895 commit f196226

4 files changed

Lines changed: 121 additions & 78 deletions

File tree

CompactGUI.Watcher/Watcher.vb

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
5050
_logger = logger
5151
_settingsService = settingsService
5252
_DataFolder = settingsService.DataFolder
53+
5354
WatcherJSONFile = New IO.FileInfo(IO.Path.Combine(_DataFolder.FullName, "watcher.json"))
5455

5556
IdleSettings = New IdleSettings
5657
_idleDetector = idleDetector
57-
5858
WatcherLog.WatcherStarted(logger)
5959
IsActive = True
6060

@@ -66,10 +66,6 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
6666
BGCompactor = New BackgroundCompactor(Array.Empty(Of String), _logger)
6767

6868

69-
AddHandler BGCompactor.IsCompactingEvent, Sub(sender, isCompacting)
70-
CancelBackgroundingCommand.NotifyCanExecuteChanged()
71-
End Sub
72-
7369
InitializeWatchedFoldersAsync()
7470

7571

@@ -92,39 +88,50 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
9288

9389
End Sub
9490

95-
<RelayCommand>
96-
Public Async Function RunWatcher() As Task(Of Boolean)
97-
Return Await RunWatcher(True)
98-
End Function
9991

100-
Public Async Function RunWatcher(Optional runAll As Boolean = True) As Task(Of Boolean)
92+
93+
<ObservableProperty> Private _isRunning As Boolean = False
94+
95+
Public Async Function RunWatcher(Optional runAll As Boolean = True, Optional cToken As CancellationToken = Nothing) As Task(Of Boolean)
10196
RemoveHandler _idleDetector.IsIdle, _idleHandler
10297

103-
Trace.WriteLine("Watcher: RunWatcher called")
98+
IsRunning = True
99+
104100
For Each watcher In WatchedFolders
105101
watcher.PauseMonitoring()
106102
Next
107103

108104
Try
109-
Dim now = DateTime.Now
110-
If Not IsWatchingEnabled Then Return False
111-
Dim recentThresholdDate As DateTime = DateTime.Now.AddSeconds(-IdleSettings.LastSystemModifiedTimeThresholdSeconds)
112-
If Not runAll AndAlso WatchedFolders.Any(Function(x) x.LastChangedDate > recentThresholdDate) Then Return False
105+
Await Task.Run(Async Function()
106+
_settingsService.AppSettings.ScheduledBackgroundLastRan = DateTime.Now
107+
If Not IsWatchingEnabled Then Return False
108+
Dim recentThresholdDate As DateTime = DateTime.Now.AddSeconds(-IdleSettings.LastSystemModifiedTimeThresholdSeconds)
109+
If Not runAll AndAlso WatchedFolders.Any(Function(x) x.LastChangedDate > recentThresholdDate) Then Return False
110+
111+
If _parseWatchersSemaphore.CurrentCount <> 0 Then
112+
Await ParseWatchers(runAll, cToken)
113+
End If
114+
If cToken <> Nothing AndAlso cToken.IsCancellationRequested Then
115+
_logger.LogInformation("Watcher run cancelled by user.")
116+
Return False
117+
End If
118+
If _parseWatchersSemaphore.CurrentCount <> 0 AndAlso (IsBackgroundCompactingEnabled OrElse runAll) Then
119+
Await BackgroundCompact(runAll) 'Don't need to pass the cancellation token here, as the background compactor handles it internally.
120+
End If
121+
Return True
122+
End Function, cToken)
123+
113124

114-
If _parseWatchersSemaphore.CurrentCount <> 0 Then
115-
Await ParseWatchers(runAll)
116-
End If
117-
If _parseWatchersSemaphore.CurrentCount <> 0 AndAlso (IsBackgroundCompactingEnabled OrElse runAll) Then
118-
Await BackgroundCompact(runAll)
119-
End If
120-
_settingsService.AppSettings.ScheduledBackgroundLastRan = now
121125
Return True
126+
Catch ex As OperationCanceledException
127+
Return False
122128
Finally
123129

124130
AddHandler _idleDetector.IsIdle, _idleHandler
125131
For Each watcher In WatchedFolders
126132
watcher.ResumeMonitoring()
127133
Next
134+
IsRunning = False
128135
End Try
129136
Return False
130137
End Function
@@ -347,7 +354,7 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
347354

348355

349356

350-
Public Async Function ParseWatchers(Optional ParseAll As Boolean = False) As Task
357+
Public Async Function ParseWatchers(Optional ParseAll As Boolean = False, Optional cToken As CancellationToken = Nothing) As Task
351358
Dim acquired = Await _parseWatchersSemaphore.WaitAsync(0)
352359
If Not acquired Then Return
353360

@@ -364,9 +371,11 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
364371

365372
For Each fsWatcher In WatchersQuery
366373
WatcherLog.FolderChanged(_logger, fsWatcher.DisplayName)
367-
Await Analyse(fsWatcher.Folder, ParseAll)
374+
If cToken <> Nothing AndAlso cToken.IsCancellationRequested Then Return
375+
Await Analyse(fsWatcher.Folder, ParseAll, cToken)
368376
Next
369377

378+
If cToken <> Nothing AndAlso cToken.IsCancellationRequested Then Return
370379
Await WriteToFileAsync()
371380
LastAnalysed = DateTime.Now
372381
Finally
@@ -433,37 +442,44 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
433442
End Function
434443

435444

436-
Public Async Function Analyse(folder As String, checkDiskModified As Boolean) As Task(Of Boolean)
445+
Public Async Function Analyse(folder As String, checkDiskModified As Boolean, Optional cToken As CancellationToken = Nothing) As Task(Of Boolean)
437446

438447
Using analyser As New Analyser(folder, NullLogger(Of Analyser).Instance)
439448
Dim watched = WatchedFolders.First(Function(f) f.Folder = folder)
440449
watched.IsWorking = True
450+
Try
451+
Dim analysedFiles = Await analyser.GetAnalysedFilesAsync(cToken)
452+
If cToken <> Nothing AndAlso cToken.IsCancellationRequested Then Return False
441453

442-
Dim analysedFiles = Await analyser.GetAnalysedFilesAsync(CancellationToken.None)
443-
444-
watched.LastCheckedDate = DateTime.Now
445-
watched.LastCheckedSize = analyser.CompressedBytes
446-
watched.LastUncompressedSize = analyser.UncompressedBytes
454+
watched.LastCheckedDate = DateTime.Now
455+
watched.LastCheckedSize = analyser.CompressedBytes
456+
watched.LastUncompressedSize = analyser.UncompressedBytes
447457

448-
watched.LastSystemModifiedDate = watched.LastChangedDate
458+
watched.LastSystemModifiedDate = watched.LastChangedDate
449459

450-
If analysedFiles.Count <> 0 Then
451-
Dim mainCompressionLVL = analysedFiles?.Select(Function(f) f.CompressionMode).Max
452-
watched.CompressionLevel = If(mainCompressionLVL <> WOFCompressionAlgorithm.NO_COMPRESSION, mainCompressionLVL, watched.CompressionLevel)
460+
If analysedFiles.Count <> 0 Then
461+
Dim mainCompressionLVL = analysedFiles?.Select(Function(f) f.CompressionMode).Max
462+
watched.CompressionLevel = If(mainCompressionLVL <> WOFCompressionAlgorithm.NO_COMPRESSION, mainCompressionLVL, watched.CompressionLevel)
453463

454-
If checkDiskModified Then
455-
Dim lastDiskWriteTime = analysedFiles.Select(Function(fl)
456-
Dim finfo As New IO.FileInfo(fl.FileName)
457-
Return finfo.LastWriteTime
458-
End Function).OrderByDescending(Function(f) f).First
464+
If checkDiskModified Then
465+
Dim lastDiskWriteTime = analysedFiles.Select(Function(fl)
466+
Dim finfo As New IO.FileInfo(fl.FileName)
467+
Return finfo.LastWriteTime
468+
End Function).OrderByDescending(Function(f) f).First
459469

460-
watched.LastSystemModifiedDate = If(watched.LastSystemModifiedDate < lastDiskWriteTime, lastDiskWriteTime, watched.LastSystemModifiedDate)
470+
watched.LastSystemModifiedDate = If(watched.LastSystemModifiedDate < lastDiskWriteTime, lastDiskWriteTime, watched.LastSystemModifiedDate)
461471

472+
End If
462473
End If
463-
End If
464474

465-
watched.HasTargetChanged = False
466-
watched.IsWorking = False
475+
watched.HasTargetChanged = False
476+
Catch ex As OperationCanceledException
477+
Return False
478+
Finally
479+
480+
watched.IsWorking = False
481+
End Try
482+
467483
Return True
468484

469485
End Using
@@ -480,15 +496,9 @@ Partial Public Class Watcher : Inherits ObservableRecipient : Implements IRecipi
480496

481497
End Sub
482498

483-
<RelayCommand>
484-
Public Sub CancelBackgrounding()
485-
BGCompactor.CancelCompacting()
486-
CancelBackgroundingCommand.NotifyCanExecuteChanged()
487-
End Sub
488499

489-
Public Function CanCancelBackgrounding() As Boolean
490-
Return BGCompactor.IsCompactorActive
491-
End Function
500+
501+
492502

493503
End Class
494504

CompactGUI/Application.xaml.vb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Partial Public Class Application
2626
Shared Sub New()
2727
SettingsService = New SettingsService()
2828
SettingsService.LoadSettings()
29-
InitializeHost()
3029

3130
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf OnDomainUnhandledException
3231

@@ -83,7 +82,7 @@ Partial Public Class Application
8382
services.AddSingleton(Of HomeViewModel)()
8483

8584
services.AddTransient(Of WatcherPage)()
86-
services.AddTransient(Of WatcherViewModel)()
85+
services.AddSingleton(Of WatcherViewModel)()
8786

8887
services.AddTransient(Of SettingsPage)()
8988
services.AddSingleton(Of SettingsViewModel)()
@@ -142,6 +141,8 @@ Partial Public Class Application
142141
End If
143142
End If
144143

144+
InitializeHost()
145+
145146
GetService(Of Watcher.Watcher)()
146147

147148
Await _host.StartAsync()

CompactGUI/ViewModels/WatcherViewModel.vb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
Imports CommunityToolkit.Mvvm.ComponentModel
1+
Imports System.Threading
2+
3+
Imports CommunityToolkit.Mvvm.ComponentModel
24
Imports CommunityToolkit.Mvvm.Input
35
Imports CommunityToolkit.Mvvm.Messaging
46

7+
Imports CompactGUI.Watcher
8+
59
Imports Wpf.Ui.Controls
610

711
Public NotInheritable Class WatcherViewModel : Inherits ObservableObject
@@ -15,6 +19,20 @@ Public NotInheritable Class WatcherViewModel : Inherits ObservableObject
1519
End Sub
1620

1721

22+
23+
<RelayCommand>
24+
Public Async Function RunWatcher(token As CancellationToken) As Task
25+
Await Watcher.RunWatcher(True, token)
26+
End Function
27+
28+
<RelayCommand>
29+
Public Sub CancelBackgrounding()
30+
RunWatcherCommand.Cancel()
31+
Watcher.BGCompactor.CancelCompacting()
32+
Application.Current.Dispatcher.Invoke(Sub() CancelBackgroundingCommand.NotifyCanExecuteChanged())
33+
End Sub
34+
35+
1836
<RelayCommand>
1937
Private Async Function RemoveWatcher(watchedFolder As Watcher.WatchedFolder) As Task
2038
If watchedFolder Is Nothing Then Return

CompactGUI/Views/Components/FolderWatcherCard.xaml

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,49 +29,64 @@
2929
<RowDefinition />
3030
</Grid.RowDefinitions>
3131

32+
<Grid.ColumnDefinitions>
33+
<ColumnDefinition/>
34+
<ColumnDefinition/>
35+
</Grid.ColumnDefinitions>
3236

3337

34-
<TextBlock Text="Watched folders"
38+
<StackPanel Orientation="Horizontal" Grid.Row="0">
39+
<TextBlock Text="Watched Folders"
3540
Grid.Row="0"
3641
VerticalAlignment="Top"
3742
FontSize="26"
3843
Foreground="{StaticResource CardForeground}"
3944
Visibility="Visible" />
4045

41-
<Button Content="Add Folder To Watchlist"
42-
Grid.Row="0"
43-
Height="30"
44-
HorizontalAlignment="Right" VerticalAlignment="Top"
46+
<Button
47+
48+
Height="30" Margin="20 0 0 0 "
49+
HorizontalAlignment="Left" VerticalAlignment="Center"
4550
Command="{Binding ManuallyAddFolderToWatcherCommand}">
51+
<StackPanel Orientation="Horizontal">
52+
<ui:SymbolIcon Symbol="FolderAdd24"/>
53+
<TextBlock Text="Add" Margin="8 0 0 0 " FontSize="13" FontWeight="SemiBold"/>
54+
</StackPanel>
4655
<Button.ToolTip>
4756
<ToolTip ToolTipService.InitialShowDelay="100">
4857
<TextBlock Text="Add a custom folder to the watchlist"
4958
FontSize="12" Foreground="#FFBFC7CE" TextWrapping="NoWrap" />
5059
</ToolTip>
5160
</Button.ToolTip>
5261
</Button>
62+
</StackPanel>
63+
64+
5365

5466

5567

5668
<TextBlock Grid.Row="1"
57-
HorizontalAlignment="Left" VerticalAlignment="Center"
58-
FontSize="20"
59-
Foreground="{StaticResource CardForegroundDisabled}">
69+
HorizontalAlignment="Left" VerticalAlignment="Top"
70+
FontSize="18" FontWeight="SemiBold"
71+
Foreground="#40FFFFFF">
6072
<Run Text="{Binding Watcher.TotalSaved, Mode=OneWay, Converter={StaticResource BytesToReadableConverter}}" d:Text="51.8GB" />
6173
<Run Text="saved" />
6274
</TextBlock>
6375

64-
<StackPanel Grid.Row="1"
65-
Margin="0,0,3,0" HorizontalAlignment="Right" VerticalAlignment="Center"
76+
<StackPanel Grid.Row="1" Grid.RowSpan="2"
77+
Margin="0,20,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"
6678
Orientation="Horizontal">
79+
80+
<TextBlock Text="{Binding Watcher.LastAnalysed, StringFormat=Last analysed {0}, Converter={StaticResource RelativeDateConverter}}"
81+
Margin="0,-2,0,0" VerticalAlignment="Center"
82+
d:Text="Last analysed: just now" FontSize="14" FontWeight="SemiBold" Foreground="#40FFFFFF" />
6783
<Grid Width="45" Height="25"
6884
VerticalAlignment="Center">
69-
<ui:Button Background="Transparent" BorderThickness="0"
85+
<ui:Button Background="Transparent" BorderThickness="0" Margin="2 0"
7086
Command="{Binding RefreshWatchedCommand}"
7187
Visibility="{Binding RefreshWatchedCommand.IsRunning, Converter={StaticResource BooleanToInverseVisibilityConverter}}">
72-
73-
<ui:FontIcon FontFamily="Segoe Fluent Icons, Segoe MDL2 Assets" FontSize="14"
74-
Glyph="&#xE9F3;" />
88+
89+
<ui:SymbolIcon Symbol="ArrowClockwiseDashes24" FontSize="18"/>
7590
<ui:Button.ToolTip>
7691
<ToolTip ToolTipService.InitialShowDelay="100">
7792
<TextBlock Text="Re-analyse all watched folders"
@@ -80,28 +95,27 @@
8095
</ui:Button.ToolTip>
8196
</ui:Button>
8297
<ui:ProgressRing Width="18" Height="18"
83-
Margin="-15,0,0,0"
98+
Margin="0,0,0,0"
8499
Foreground="#FFBFC7CE" IsIndeterminate="True"
85100
Visibility="{Binding RefreshWatchedCommand.IsRunning, Converter={StaticResource BoolToVisConverter}}" />
86-
87-
<Button Content="Cancel" Margin="-100" Command="{Binding Watcher.CancelBackgroundingCommand}"/>
88-
<Button Content="Run Now" Margin="-200" Command="{Binding Watcher.RunWatcherCommand}"/>
89101
</Grid>
102+
103+
</StackPanel>
90104

105+
<StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" HorizontalAlignment="Right">
91106

92-
<TextBlock Text="{Binding Watcher.LastAnalysed, StringFormat=Last analysed {0}, Converter={StaticResource RelativeDateConverter}}"
93-
Margin="0,-2,0,0" VerticalAlignment="Center"
94-
d:Text="Last analysed: just now" FontSize="14" Foreground="#FFBFC7CE" />
107+
<Button Content="Cancel Background Compressor" Command="{Binding CancelBackgroundingCommand}" Visibility="{Binding Watcher.IsRunning, Converter={StaticResource BooleanToVisibilityConverter}}"/>
108+
<Button Content="Compress All Now" Command="{Binding RunWatcherCommand}" Visibility="{Binding Watcher.IsRunning, Converter={StaticResource BooleanToInverseVisibilityConverter}}"/>
95109
</StackPanel>
110+
96111

97-
98-
<Separator Grid.Row="2"
112+
<Separator Grid.Row="2" Grid.ColumnSpan="2"
99113
Height="1"
100114
VerticalAlignment="Bottom" />
101115

102116

103117
<ListView x:Name="UiWatcherListView"
104-
Grid.Row="3"
118+
Grid.Row="3" Grid.ColumnSpan="2"
105119
Margin="-10,0,-20,0" Padding="0,0,10,0" HorizontalAlignment="Stretch"
106120
d:ItemsSource="{Binding Source={StaticResource Dtd}}"
107121
Background="Transparent" BorderThickness="0"

0 commit comments

Comments
 (0)