Repository: IridiumIO/CompactGUI Branch: master Commit: d3fb7150815f Files: 113 Total size: 549.2 KB Directory structure: gitextract_9ujaogzw/ ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── CompactGUI/ │ ├── Application.xaml │ ├── Application.xaml.vb │ ├── AssemblyInfo.vb │ ├── CompactGUI.vbproj │ ├── Components/ │ │ ├── Behaviors/ │ │ │ └── FocusOnMouseOverBehavior.vb │ │ ├── Converters/ │ │ │ ├── Converters.xaml │ │ │ ├── IValueConverters.vb │ │ │ └── MyConverters.vb │ │ ├── Custom/ │ │ │ ├── ImageControl.vb │ │ │ └── TokenizedTextBox.vb │ │ ├── FolderActionStateTemplateSelector.vb │ │ └── Settings/ │ │ ├── Settings_skiplistflyout.xaml │ │ └── Settings_skiplistflyout.xaml.vb │ ├── Helper.vb │ ├── MainWindow.xaml │ ├── MainWindow.xaml.vb │ ├── Messages/ │ │ └── WatcherAddedFolderToQueueMessage.vb │ ├── Models/ │ │ ├── CompressableFolders/ │ │ │ ├── CompressableFolder.vb │ │ │ ├── CompressableFolderFactory.vb │ │ │ ├── StandardFolder.vb │ │ │ └── SteamFolder.vb │ │ ├── CompressionResult.vb │ │ ├── NewModels/ │ │ │ ├── CompressionOptions.vb │ │ │ ├── DatabaseCompressionResult.vb │ │ │ └── WikiCompressionResults.vb │ │ ├── SemVersion.vb │ │ ├── SteamACFResult.vb │ │ ├── SteamResultsData.vb │ │ └── SteamSubmissionData.vb │ ├── My Project/ │ │ ├── Application.myapp │ │ └── app.manifest │ ├── Services/ │ │ ├── ApplicationHostService.vb │ │ ├── CompressableFolderService.vb │ │ ├── CustomSnackBarService.vb │ │ ├── SchedulerService.vb │ │ ├── SettingsService.vb │ │ ├── SteamACFParser.vb │ │ ├── TrayNotifierService.vb │ │ ├── UpdaterService.vb │ │ ├── WikiService.vb │ │ └── WindowService.vb │ ├── Themes/ │ │ └── Generic.xaml │ ├── ViewModels/ │ │ ├── DatabaseViewModel.vb │ │ ├── FolderViewModel.vb │ │ ├── HomeViewModel.vb │ │ ├── MainWindowViewModel.vb │ │ ├── SettingsViewModel.vb │ │ └── WatcherViewModel.vb │ └── Views/ │ ├── Components/ │ │ ├── CompressionMode_Radio.xaml │ │ ├── CompressionMode_Radio.xaml.vb │ │ ├── FolderWatcherCard.xaml │ │ └── FolderWatcherCard.xaml.vb │ ├── Pages/ │ │ ├── DatabasePage.xaml │ │ ├── DatabasePage.xaml.vb │ │ ├── FolderView.xaml │ │ ├── FolderView.xaml.vb │ │ ├── HomePage.xaml │ │ ├── HomePage.xaml.vb │ │ ├── PendingCompression.xaml │ │ ├── PendingCompression.xaml.vb │ │ ├── ResultsTemplate.xaml │ │ ├── ResultsTemplate.xaml.vb │ │ ├── WatcherPage.xaml │ │ └── WatcherPage.xaml.vb │ ├── SettingsPage.xaml │ └── SettingsPage.xaml.vb ├── CompactGUI.Core/ │ ├── Analyser.cs │ ├── CompactGUI.Core.csproj │ ├── Compactor.cs │ ├── Estimator.cs │ ├── FolderChangeMonitor.cs │ ├── ICompressor.cs │ ├── NTFSInterop.cs │ ├── NativeMethods.txt │ ├── Settings/ │ │ ├── ISettingsService.cs │ │ └── Settings.cs │ ├── SharedMethods.cs │ ├── SharedObjects.cs │ ├── Uncompactor.cs │ └── WOFHelper.cs ├── CompactGUI.CoreVB/ │ ├── Analyser.vb │ ├── CompactGUI.CoreVB.vbproj │ ├── Compactor.vb │ ├── Estimator.vb │ ├── FodyWeavers.xml │ ├── ICompressor.vb │ ├── NtfsInterop.vb │ ├── SharedMethods.vb │ ├── SharedObjects.vb │ ├── Uncompactor.vb │ └── WOFHelper.vb ├── CompactGUI.Logging/ │ ├── CompactGUI.Logging.csproj │ ├── Core/ │ │ ├── AnalyserLog.cs │ │ ├── CompactorLog.cs │ │ └── UncompactorLog.cs │ ├── HomeViewModelLog.cs │ ├── SchedulerServiceLog.cs │ ├── SettingsLog.cs │ ├── SnackbarServiceLog.cs │ └── Watcher/ │ └── WatcherLog.cs ├── CompactGUI.Watcher/ │ ├── BackgroundCompactor.vb │ ├── CompactGUI.Watcher.vbproj │ ├── IdleDetector.vb │ ├── IdleSettings.vb │ ├── WatchedFolder.vb │ └── Watcher.vb ├── CompactGUI.slnx ├── LICENSE ├── README.md └── Version.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: IridiumIO patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: IridiumIO tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates CompactGUI.TestingGround/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Oo]ut/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd /CompactGUI/My Project/launchSettings.json # IntelliJ .idea/ /CompactGUI.WatcherCS ================================================ FILE: CompactGUI/Application.xaml ================================================  M110.5,87.3c0,0.2,0,0.4,0,0.6L82,129.3c-4.6-0.2-9.3,0.6-13.6,2.4c-1.9,0.8-3.8,1.8-5.5,2.9L0.3,108.8 c0,0-1.4,23.8,4.6,41.6l44.3,18.3c2.2,9.9,9,18.6,19.1,22.8c16.4,6.9,35.4-1,42.2-17.4c1.8-4.3,2.6-8.8,2.5-13.3l40.8-29.1 c0.3,0,0.7,0,1,0c24.4,0,44.3-19.9,44.3-44.3c0-24.4-19.8-44.3-44.3-44.3C130.4,43,110.5,62.9,110.5,87.3z M103.7,171.2 c-5.3,12.7-19.9,18.7-32.6,13.4c-5.9-2.4-10.3-6.9-12.8-12.2l14.4,6c9.4,3.9,20.1-0.5,24-9.9c3.9-9.4-0.5-20.1-9.9-24l-14.9-6.2 c5.7-2.2,12.3-2.3,18.4,0.3c6.2,2.6,10.9,7.4,13.5,13.5S106.2,165.1,103.7,171.2 M154.8,116.9c-16.3,0-29.5-13.3-29.5-29.5 c0-16.3,13.2-29.5,29.5-29.5c16.3,0,29.5,13.3,29.5,29.5C184.2,103.6,171,116.9,154.8,116.9 M132.7,87.3c0-12.3,9.9-22.2,22.1-22.2 c12.2,0,22.1,9.9,22.1,22.2c0,12.3-9.9,22.2-22.1,22.2C142.6,109.5,132.7,99.5,132.7,87.3z M233,116.5c0,64.3-52.2,116.5-116.5,116.5S0,180.8,0,116.5c0-30.4,11-60.2,30.7-78.8C53.5,16.1,82.5,0,116.5,0 C180.8,0,233,52.2,233,116.5z ================================================ FILE: CompactGUI/Application.xaml.vb ================================================ Imports System.IO Imports System.IO.Pipes Imports System.Threading Imports System.Windows.Threading Imports Wpf.Ui Imports Wpf.Ui.DependencyInjection Imports Microsoft.Extensions.Hosting Imports Microsoft.Extensions.Logging Imports Microsoft.Extensions.DependencyInjection Imports Microsoft.Extensions.Configuration Imports System.Drawing Imports CompactGUI.Core.Settings Imports Coravel Imports CompactGUI.Watcher Imports Coravel.Scheduling.Schedule.Interfaces Imports Coravel.Scheduling.Schedule Partial Public Class Application Public Shared ReadOnly AppVersion As New SemVersion(4, 0, 0, "beta", 6) Private Shared _host As IHost Private Shared ReadOnly SettingsService As ISettingsService Shared Sub New() SettingsService = New SettingsService() SettingsService.LoadSettings() AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf OnDomainUnhandledException End Sub Private Shared Sub InitializeHost() _host = Host.CreateDefaultBuilder() _ .ConfigureAppConfiguration(Sub(context, configBuilder) ' Set base path using IConfigurationBuilder configBuilder.SetBasePath(AppContext.BaseDirectory) End Sub) _ .ConfigureServices(Sub(context, services) services.AddHostedService(Of ApplicationHostService)() 'Settings handler services.AddSingleton(Of ISettingsService)(SettingsService) services.AddLogging(Sub(logging) logging.SetMinimumLevel(SettingsService.AppSettings.LogLevel) logging.AddConsole() logging.AddDebug() logging.AddFile( Path.Combine(SettingsService.DataFolder.FullName, "log.log"), SettingsService.AppSettings.LogLevel, retainedFileCountLimit:=2, fileSizeLimitBytes:=1000000, outputTemplate:="{Timestamp:o} {RequestId,13} [{Level:u3}] {Message}{NewLine}{Exception}" ) End Sub) ' Theme manipulation services.AddSingleton(Of IThemeService, ThemeService)() ' TaskBar manipulation services.AddSingleton(Of ITaskBarService, TaskBarService)() ' Service containing navigation, same as INavigationWindow... but without window services.AddNavigationViewPageProvider() services.AddSingleton(Of INavigationService, NavigationService)() services.AddSingleton(Of CustomSnackBarService)() services.AddSingleton(Of IWindowService, WindowService)() services.AddSingleton(Of IUpdaterService, UpdaterService)() services.AddSingleton(Of IWikiService, WikiService)() services.AddSingleton(Of INavigationWindow, MainWindow)() services.AddSingleton(Of MainWindow)() services.AddSingleton(Of MainWindowViewModel)() ' Views and ViewModels services.AddTransient(Of HomePage)() services.AddSingleton(Of HomeViewModel)() services.AddTransient(Of WatcherPage)() services.AddSingleton(Of WatcherViewModel)() services.AddTransient(Of SettingsPage)() services.AddSingleton(Of SettingsViewModel)() services.AddTransient(Of DatabasePage)() services.AddTransient(Of DatabaseViewModel)() 'Other services services.AddSingleton(Of TrayNotifierService)(Function(sp) Return New TrayNotifierService(sp.GetRequiredService(Of MainWindow)(), Icon.ExtractAssociatedIcon(Environment.ProcessPath), "CompactGUI") End Function) services.AddSingleton(Of CompressableFolderService) services.AddSingleton(Of IdleDetector)(Function() Dim idleDetector = New IdleDetector(New IdleSettings) idleDetector.Start() Return idleDetector End Function) services.AddSingleton(Of SchedulerService)() services.AddSingleton(Of Watcher.Watcher)() services.AddScheduler() End Sub) _ .Build() End Sub Public Shared Function GetService(Of T As Class)() As T Return TryCast(_host?.Services.GetService(GetType(T)), T) End Function Public Shared ReadOnly mutex As New Mutex(False, "Global\CompactGUI") Private pipeServerCancellation As New CancellationTokenSource() Private pipeServerTask As Task Private Shadows Async Sub OnStartup(sender As Object, e As StartupEventArgs) AddHandler Dispatcher.CurrentDispatcher.UnhandledException, AddressOf OnDispatcherUnhandledException Dim acquiredMutex As Boolean = mutex.WaitOne(0, False) If Not acquiredMutex Then If Not SettingsService.AppSettings.AllowMultiInstance Then HandleSecondInstance(e.Args) Return End If Else If Not SettingsService.AppSettings.AllowMultiInstance Then pipeServerTask = ProcessNextInstanceMessage() End If End If InitializeHost() GetService(Of Watcher.Watcher)() Await _host.StartAsync() Await GetService(Of SettingsViewModel).InitializeEnvironment() GetService(Of SchedulerService).RegenerateSchedule() Dim UpdateTask = GetService(Of IUpdaterService).CheckForUpdate(SettingsService.AppSettings.EnablePreReleaseUpdates) Dim WikiTask = GetService(Of IWikiService).GetUpdatedJSONAsync() Await Task.WhenAll(UpdateTask, WikiTask) End Sub Private Sub HandleSecondInstance(args As String()) If args.Length > 0 AndAlso args(0) <> "-tray" Then Using client = New NamedPipeClientStream(".", "CompactGUI", PipeDirection.Out) client.Connect() Using writer = New StreamWriter(client) writer.WriteLine(args(0)) End Using End Using Else MessageBox.Show("An instance of CompactGUI is already running") End If Current.Shutdown() End Sub Private Async Function ProcessNextInstanceMessage() As Task While Not pipeServerCancellation.IsCancellationRequested Using server = New NamedPipeServerStream("CompactGUI", PipeDirection.In, -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous) Try Await server.WaitForConnectionAsync(pipeServerCancellation.Token) Using reader = New StreamReader(server) Dim message = Await reader.ReadLineAsync() Await MainWindow.Dispatcher.InvokeAsync(Async Function() If message IsNot Nothing Then MainWindow.Show() MainWindow.WindowState = WindowState.Normal MainWindow.Activate() Await GetService(Of HomeViewModel).AddFoldersAsync({message}) 'Await MainWindow.ViewModel.SelectFolderAsync(message) End If End Function).Task End Using Catch ex As OperationCanceledException Return Finally If server.IsConnected Then server.Disconnect() End Try End Using End While End Function Public Async Function ShutdownPipeServer() As Task If pipeServerTask IsNot Nothing Then pipeServerCancellation.Cancel() Await pipeServerTask End If End Function Private Shadows Async Sub OnExit(sender As Object, e As ExitEventArgs) Await _host.StopAsync() _host.Dispose() End Sub Private Sub OnDispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) GetService(Of ILogger(Of Application))().LogCritical(e.Exception, "Unhandled exception in application: {Message}", e.Exception.Message) End Sub Private Shared Sub OnDomainUnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Dim ex = TryCast(e.ExceptionObject, Exception) Dim logger = GetService(Of ILogger(Of Application))() If logger IsNot Nothing AndAlso ex IsNot Nothing Then logger.LogCritical(ex, "Unhandled domain exception: {Message}", ex.Message) End If End Sub 'Public Shared ReadOnly mutex As New Mutex(False, "Global\CompactGUI") 'Private pipeServerCancellation As New CancellationTokenSource() 'Private pipeServerTask As Task 'Private Shadows mainWindow As MainWindow 'Private Async Sub Application_Startup(sender As Object, e As StartupEventArgs) ' SettingsHandler.InitialiseSettings() ' Dim acquiredMutex As Boolean = mutex.WaitOne(0, False) ' If Not acquiredMutex Then ' If Not SettingsHandler.AppSettings.AllowMultiInstance Then ' HandleSecondInstance(e.Args) ' Return ' End If ' Else ' If Not SettingsHandler.AppSettings.AllowMultiInstance Then ' pipeServerTask = ProcessNextInstanceMessage() ' End If ' End If ' mainWindow = New MainWindow() ' Dim shouldMinimizeToTray As Boolean = (e.Args.Length = 1 AndAlso e.Args(0).ToString = "-tray") OrElse ' (SettingsHandler.AppSettings.StartInSystemTray AndAlso e.Args.Length = 0) ' If shouldMinimizeToTray Then ' mainWindow.Show() ' mainWindow.ViewModel.ClosingCommand.Execute(New ComponentModel.CancelEventArgs(True)) ' Else ' If e.Args.Length = 1 Then ' Await mainWindow.ViewModel.SelectFolderAsync(e.Args(0)) ' End If ' mainWindow.Show() ' End If ' Await SettingsViewModel.InitializeEnvironment() 'End Sub 'Private Sub HandleSecondInstance(args As String()) ' If args.Length > 0 AndAlso args(0) <> "-tray" Then ' Using client = New NamedPipeClientStream(".", "CompactGUI", PipeDirection.Out) ' client.Connect() ' Using writer = New StreamWriter(client) ' writer.WriteLine(args(0)) ' End Using ' End Using ' Else ' MessageBox.Show("An instance of CompactGUI is already running") ' End If ' Application.Current.Shutdown() 'End Sub 'Private Async Function ProcessNextInstanceMessage() As Task ' While Not pipeServerCancellation.IsCancellationRequested ' Using server = New NamedPipeServerStream("CompactGUI", PipeDirection.In, -1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous) ' Try ' Await server.WaitForConnectionAsync(pipeServerCancellation.Token) ' Using reader = New StreamReader(server) ' Dim message = Await reader.ReadLineAsync() ' Await mainWindow.Dispatcher.InvokeAsync(Async Function() ' If message IsNot Nothing Then ' mainWindow.Show() ' mainWindow.WindowState = WindowState.Normal ' mainWindow.Activate() ' Await mainWindow.ViewModel.SelectFolderAsync(message) ' End If ' End Function).Task ' End Using ' Catch ex As OperationCanceledException ' Return ' Finally ' If server.IsConnected Then server.Disconnect() ' End Try ' End Using ' End While 'End Function 'Public Async Function ShutdownPipeServer() As Task ' If pipeServerTask IsNot Nothing Then ' pipeServerCancellation.Cancel() ' Await pipeServerTask ' End If 'End Function End Class ================================================ FILE: CompactGUI/AssemblyInfo.vb ================================================ Imports System.Windows 'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries can be found. '1st parameter: where theme specific resource dictionaries are located '(used if a resource is not found in the page, ' or application resource dictionaries) '2nd parameter: where the generic resource dictionary is located '(used if a resource is not found in the page, 'app, and any theme specific resource dictionaries) ================================================ FILE: CompactGUI/CompactGUI.vbproj ================================================  WinExe net9.0-windows CompactGUI true Off My Project\app.manifest 4.0.0 IridiumIO IridiumIO GUI for the Windows compact.exe command-line tool. Copyright © 2025 https://github.com/IridiumIO/CompactGUI/ icon.ico 41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 none ..\..\FunctionalConverters\FunctionalConverters\bin\Release\net6.0-windows\FunctionalConverters.dll $(ProjectDir)bin\publish\FinalOutput\ ================================================ FILE: CompactGUI/Components/Behaviors/FocusOnMouseOverBehavior.vb ================================================ Imports Microsoft.Xaml.Behaviors Public Class FocusOnMouseOverBehavior : Inherits Behavior(Of ComboBox) Protected Overrides Sub OnAttached() MyBase.OnAttached() AddHandler AssociatedObject.MouseEnter, AddressOf OnMouseEnter End Sub Protected Overrides Sub OnDetaching() MyBase.OnDetaching() RemoveHandler AssociatedObject.MouseEnter, AddressOf OnMouseEnter End Sub Private Sub OnMouseEnter(sender As Object, e As MouseEventArgs) AssociatedObject.Focus() End Sub End Class ================================================ FILE: CompactGUI/Components/Converters/Converters.xaml ================================================  ================================================ FILE: CompactGUI/Components/Converters/IValueConverters.vb ================================================ Imports System.Globalization Public Class DecimalToPercentageConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert 'IF = invert and format, to show the "percentage smaller" text If parameter = "IF" Then Return CInt(100 - (CType(value, Decimal) * 100)) & "%" If parameter = "I" Then Return CInt(100 - (CType(value, Decimal) * 100)) Return CInt(CType(value, Decimal) * 100) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class BytesToReadableConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim suf As String() = {" B", " KB", " MB", " GB", " TB", " PB", " EB"} If value = 1010101010101010 Then Return "?" If value = 0 Then Return "0" & suf(0) Dim bytes As Long = Math.Abs(value) Dim place As Integer = CInt(Math.Floor(Math.Log(bytes, 1024))) 'Dim roundingPrecision As Integer = 1 'If parameter IsNot Nothing AndAlso Integer.TryParse(parameter.ToString(), roundingPrecision) Then ' roundingPrecision = Math.Max(0, roundingPrecision) ' 'We want to round to 1 decimal place if the value is in the GB range or higher ' If Array.IndexOf(suf, suf(place)) > 2 AndAlso roundingPrecision = 0 Then ' roundingPrecision = 1 ' End If 'End If 'Dim num As Double = Math.Round(bytes / Math.Pow(1024, place), roundingPrecision) 'Return (Math.Sign(value) * num).ToString() & suf(place) Dim roundingSigDigits As Integer = 3 ' Default significant digits If parameter IsNot Nothing AndAlso Integer.TryParse(parameter.ToString(), roundingSigDigits) Then roundingSigDigits = Math.Max(1, roundingSigDigits) End If Dim num As Double = bytes / Math.Pow(1024, place) Dim absNum As Double = Math.Abs(num) Dim digitsBeforeDecimal As Integer = If(absNum < 1, 0, Math.Floor(Math.Log10(absNum)) + 1) Dim decimalPlaces As Integer = Math.Max(0, roundingSigDigits - digitsBeforeDecimal) Dim roundedNum As Double = Math.Round(num, decimalPlaces) Return (Math.Sign(value) * roundedNum).ToString() & suf(place) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class StrippedFolderPathConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert If value Is Nothing Then Return Nothing Dim Str = CType(value, String) Return Str.Substring(Str.LastIndexOf("\"c) + 1) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class TokenisedFolderPathConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert If value Is Nothing Then Return Nothing Dim Str = CType(value, String) Dim formattedString = Str.Replace("\"c, " 🢒 ") Return formattedString End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class RelativeDateConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim dt = CType(value, DateTime) Dim ts As TimeSpan = DateTime.Now - dt If ts > TimeSpan.FromDays(19000) Then Return String.Format("Unknown") End If If ts > TimeSpan.FromDays(2) Then Return String.Format("{0:0} days ago", ts.TotalDays) ElseIf ts > TimeSpan.FromHours(2) Then Return String.Format("{0:0} hours ago", ts.TotalHours) ElseIf ts > TimeSpan.FromMinutes(2) Then Return String.Format("{0:0} minutes ago", ts.TotalMinutes) Else Return "just now" End If End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class CompressionLevelAbbreviatedConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim clvl = CType(value, Core.WOFCompressionAlgorithm) Select Case clvl Case Core.WOFCompressionAlgorithm.NO_COMPRESSION : Return "NIL" Case Core.WOFCompressionAlgorithm.LZNT1 : Return "NT" Case Core.WOFCompressionAlgorithm.XPRESS4K : Return "X4" Case Core.WOFCompressionAlgorithm.XPRESS8K : Return "X8" Case Core.WOFCompressionAlgorithm.XPRESS16K : Return "X16" Case Core.WOFCompressionAlgorithm.LZX : Return "LZX" Case Else : Return "NIL" End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class ConfidenceIntToStringConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Select Case value Case 0 Return "▬" Case 1 Return "▬▬" Case 2 Return "▬▬▬" Case Else Return "▭▭▭" End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class ConfidenceIntToColorConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Select Case value Case 0 Return New SolidColorBrush(ColorConverter.ConvertFromString("#FF996B6B")) Case 1 Return New SolidColorBrush(ColorConverter.ConvertFromString("#F1CE92")) Case 2 Return New SolidColorBrush(ColorConverter.ConvertFromString("#92F1AB")) Case Else Return New SolidColorBrush(ColorConverter.ConvertFromString("#BAC2CA")) End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class WindowScalingConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim dimension = CInt(parameter) Return CInt(value * dimension) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class WikiCompressionLevelAbbreviatedConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim clvl = CType(value, Integer) Select Case clvl Case 0 : Return "X4" Case 1 : Return "X8" Case 2 : Return "X16" Case 3 : Return "LZX" Case Else : Return "NIL" End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class NonZeroToVisConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim clvl = CType(value, Integer) If clvl = 0 Then Return Visibility.Collapsed Return Visibility.Visible End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class ProgressBarColorConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim progress As Decimal = DirectCast(value, Decimal) If progress > 0.6 Then Return New SolidColorBrush(Color.FromRgb(239, 146, 146)) ElseIf progress > 0.2 Then Return New SolidColorBrush(Color.FromRgb(239, 239, 146)) Else Return New SolidColorBrush(Color.FromRgb(146, 241, 171)) End If End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class BooleanToInverseVisibilityConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim b = CType(value, Boolean) If b Then Return Visibility.Collapsed Return Visibility.Visible End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class EnumToRadioButtonConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim enumValue = CType(value, [Enum]) Dim parameterValue = CType(parameter, [Enum]) Return enumValue.Equals(parameterValue) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack If value Then Return parameter End If Return Binding.DoNothing End Function End Class Public Class FolderStatusToColorConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim status = CType(value, ActionState) Select Case status Case ActionState.Idle Return New SolidColorBrush(ColorConverter.ConvertFromString("#92e7f1")) Case ActionState.Analysing, ActionState.Working, ActionState.Paused Return New SolidColorBrush(ColorConverter.ConvertFromString("#F1CE92")) Case ActionState.Results Return New SolidColorBrush(ColorConverter.ConvertFromString("#92F1AB")) Case Else Return New SolidColorBrush(ColorConverter.ConvertFromString("#FFBAC2CA")) End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class FolderStatusToStringConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim status = CType(value, ActionState) Select Case status Case ActionState.Idle Return "Awaiting Compression" Case ActionState.Analysing Return "Analysing" Case ActionState.Working, ActionState.Paused Return "Working" Case ActionState.Results Return "Compressed" Case Else Return "Unknown" End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class FolderWorkingStateToPauseSymbolConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim status = CType(value, ActionState) Select Case status Case ActionState.Paused Return "Play12" Case Else Return "Pause12" End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class IsSteamFolderConverter : Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim folder = CType(value, CompressableFolder) If folder Is Nothing Then Return False Return TypeOf (folder) Is SteamFolder End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class IsSteamFolderAndFreshlyCompressedMultiConverter : Implements IMultiValueConverter Public Function Convert(values As Object(), targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert ' Ensure both properties are provided If values.Length < 2 OrElse values(0) Is Nothing OrElse values(1) Is Nothing OrElse values(0) Is DependencyProperty.UnsetValue OrElse values(1) Is DependencyProperty.UnsetValue Then Return Visibility.Collapsed End If ' Example logic: Both properties must be True for the element to be visible Dim isFreshlyCompressed As Boolean = CType(values(0), Boolean) Dim isSteamFolder As Boolean = CType(values(1), Boolean) If isFreshlyCompressed AndAlso isSteamFolder Then Return Visibility.Visible End If Return Visibility.Collapsed End Function Public Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class AnimationFactorToValueConverter Implements IMultiValueConverter Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert If TypeOf values(0) IsNot Double Then Return 0.0 End If Dim completeValue As Double = DirectCast(values(0), Double) If TypeOf values(1) IsNot Double Then Return 0.0 End If Dim factor As Double = DirectCast(values(1), Double) If parameter IsNot Nothing AndAlso parameter.ToString() = "negative" Then factor = -factor End If Return factor * completeValue End Function Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class FolderActionStateWorkingToVisibilityConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim status = CType(value, ActionState) Select Case status Case ActionState.Working Return Visibility.Visible Case Else Return Visibility.Collapsed End Select End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class ZeroCountToVisibilityConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim val = CType(value, Long) If parameter IsNot Nothing AndAlso parameter.ToString() = "invert" Then If val = 0 Then val = 1 Else val = 0 End If If val = 0 Then Return Visibility.Collapsed Else Return Visibility.Visible End If End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException() End Function End Class Public Class NumberWithSpacesConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert If value Is Nothing Then Return String.Empty Dim number As Long If Long.TryParse(value.ToString(), number) Then Return number.ToString("#,0", CultureInfo.InvariantCulture).Replace(","c, " "c) End If Return value.ToString() End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack If value Is Nothing Then Return 0 Dim s = value.ToString().Replace(" ", "") Dim result As Long If Long.TryParse(s, result) Then Return result End If Return 0 End Function End Class Public Class BackgroundModeToVisibilityConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert Dim index As Integer = If(value, 0) Return If(index > 1, Visibility.Visible, Visibility.Collapsed) End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack Throw New NotImplementedException End Function End Class Public Class EnumToIntConverter Implements IValueConverter Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert If value Is Nothing OrElse Not value.GetType().IsEnum Then Return 0 Return CType(value, [Enum]).GetHashCode() End Function Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack If targetType Is Nothing OrElse Not targetType.IsEnum OrElse value Is Nothing Then Return Binding.DoNothing Return [Enum].ToObject(targetType, value) End Function End Class ================================================ FILE: CompactGUI/Components/Converters/MyConverters.vb ================================================ Imports FunctionalConverters Public Class MyConverters : Inherits ExtensibleConverter Public Sub New(ConverterName As String) MyBase.New(ConverterName) End Sub Public Shared Function BytesToProgressMultiConverter() As MultiConverter(Of Long, Integer) Dim convert = Function(folderBytes As Long()) As Integer Return 25 End Function Return CreateMultiConverter(convert) End Function End Class ================================================ FILE: CompactGUI/Components/Custom/ImageControl.vb ================================================ Imports System.Windows.Media.Animation Public Class ImageControl : Inherits Image Public Shared ReadOnly SourceChangingEvent As RoutedEvent = EventManager.RegisterRoutedEvent("SourceChanging", RoutingStrategy.Direct, GetType(RoutedEventHandler), GetType(ImageControl)) Public Shared ReadOnly SourceChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("SourceChanged", RoutingStrategy.Direct, GetType(RoutedEventHandler), GetType(ImageControl)) Public Shared ReadOnly NewSourceProperty As DependencyProperty = DependencyProperty.Register("NewSource", GetType(ImageSource), GetType(ImageControl), New PropertyMetadata(Nothing, AddressOf OnNewSourceChanged)) Public Property NewSource As ImageSource Get Return CType(GetValue(NewSourceProperty), ImageSource) End Get Set(value As ImageSource) SetValue(NewSourceProperty, value) End Set End Property Private Shared Async Sub OnNewSourceChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) Dim control = TryCast(d, ImageControl) If control Is Nothing Then Return control.RaiseEvent(New RoutedEventArgs(SourceChangingEvent)) ' Animate fade out Dim fadeOut As New DoubleAnimation(0, TimeSpan.FromMilliseconds(300)) Await control.BeginAnimationAsync(OpacityProperty, fadeOut) ' Now update the actual image source control.Source = CType(e.NewValue, ImageSource) control.RaiseEvent(New RoutedEventArgs(SourceChangedEvent)) End Sub Shared Sub New() SourceProperty.OverrideMetadata(GetType(ImageControl), New FrameworkPropertyMetadata(Nothing, AddressOf OnSourcePropertyChanged, AddressOf OnSourceCoerceValue)) End Sub Public Custom Event SourceChanging As RoutedEventHandler AddHandler(value As RoutedEventHandler) [AddHandler](SourceChangingEvent, value) End AddHandler RemoveHandler(value As RoutedEventHandler) [RemoveHandler](SourceChangingEvent, value) End RemoveHandler RaiseEvent(sender As Object, e As RoutedEventArgs) [RaiseEvent](e) End RaiseEvent End Event Public Custom Event SourceChanged As RoutedEventHandler AddHandler(value As RoutedEventHandler) [AddHandler](SourceChangedEvent, value) End AddHandler RemoveHandler(value As RoutedEventHandler) [RemoveHandler](SourceChangedEvent, value) End RemoveHandler RaiseEvent(sender As Object, e As RoutedEventArgs) [RaiseEvent](e) End RaiseEvent End Event Private Shared Function OnSourceCoerceValue(d As DependencyObject, baseValue As Object) As Object Dim image = TryCast(d, ImageControl) If image IsNot Nothing Then image.RaiseEvent(New RoutedEventArgs(SourceChangingEvent)) End If Return baseValue End Function Private Shared Sub OnSourcePropertyChanged(obj As DependencyObject, e As DependencyPropertyChangedEventArgs) Dim image = TryCast(obj, ImageControl) If image IsNot Nothing Then image.RaiseEvent(New RoutedEventArgs(SourceChangedEvent)) End If End Sub End Class Public Module AnimationHelper Public Async Function BeginAnimationAsync(target As UIElement, dp As DependencyProperty, animation As AnimationTimeline) As Task Dim tcs As New TaskCompletionSource(Of Boolean)() If animation Is Nothing Then tcs.SetResult(True) Return End If animation.FillBehavior = FillBehavior.Stop AddHandler animation.Completed, Sub(s, e) tcs.TrySetResult(True) End Sub target.BeginAnimation(dp, animation) Await tcs.Task End Function End Module ================================================ FILE: CompactGUI/Components/Custom/TokenizedTextBox.vb ================================================ 'Credit: https://blog.pixelingene.com/2010/10/tokenizing-control-convert-text-to-tokens Public Class TokenizedTextBox : Inherits RichTextBox Public Shared ReadOnly TokenTemplateProperty As DependencyProperty = DependencyProperty.Register("TokenTemplate", GetType(DataTemplate), GetType(TokenizedTextBox)) Public Property TokenTemplate As DataTemplate Get Return GetValue(TokenTemplateProperty) End Get Set(value As DataTemplate) SetValue(TokenTemplateProperty, value) End Set End Property Public Property TokenMatcher As Func(Of String, Object) Public Sub New() AddHandler TextChanged, AddressOf OnTokenTextChanged End Sub Private Sub OnTokenTextChanged(sender As Object, e As TextChangedEventArgs) Dim text = CaretPosition.GetTextInRun(LogicalDirection.Backward) If TokenMatcher Is Nothing Then Return Dim token = TokenMatcher(text) If token IsNot Nothing Then ReplaceTextWithToken(text, token) End If End Sub Public Sub InsertText(text As String) text &= " " AppendText(text) If TokenMatcher Is Nothing Then Return Dim token = TokenMatcher(text) If token IsNot Nothing Then ReplaceTextWithToken(text, token) End If End Sub Private Sub ReplaceTextWithToken(inputText As String, token As Object) RemoveHandler TextChanged, AddressOf OnTokenTextChanged Dim para = CaretPosition.Paragraph Dim matchedRun As Run = para.Inlines.FirstOrDefault(Function(inline) Dim run = TryCast(inline, Run) Return (run IsNot Nothing AndAlso run.Text.EndsWith(inputText)) End Function) If matchedRun IsNot Nothing Then Dim tokenContainer = CreateTokenContainer(inputText, token) para.Inlines.InsertBefore(matchedRun, tokenContainer) If matchedRun.Text = inputText Then para.Inlines.Remove(matchedRun) Else Dim index = matchedRun.Text.IndexOf(inputText) + inputText.Length Dim tailEnd = New Run(matchedRun.Text.Substring(index)) para.Inlines.InsertAfter(matchedRun, tailEnd) para.Inlines.Remove(matchedRun) End If End If AddHandler TextChanged, AddressOf OnTokenTextChanged End Sub Private Function CreateTokenContainer(inputText As String, token As Object) As InlineUIContainer Dim presenter = New ContentPresenter() With { .Content = token, .ContentTemplate = TokenTemplate } Return New InlineUIContainer(presenter) With {.BaselineAlignment = BaselineAlignment.Bottom} End Function End Class ================================================ FILE: CompactGUI/Components/FolderActionStateTemplateSelector.vb ================================================ Public Class FolderActionStateTemplateSelector Inherits DataTemplateSelector Public Property IdleTemplate As DataTemplate Public Property AnalysingTemplate As DataTemplate Public Property CompressingTemplate As DataTemplate Public Property ResultsTemplate As DataTemplate Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate If item Is Nothing Then Return MyBase.SelectTemplate(item, container) Dim action = DirectCast(item, ActionState) ' Dim folderVM = TryCast(item, CompressableFolder) Select Case action Case ActionState.Idle Return IdleTemplate Case ActionState.Analysing Return AnalysingTemplate Case ActionState.Working, ActionState.Paused Return CompressingTemplate Case ActionState.Results Return ResultsTemplate Case Else Return MyBase.SelectTemplate(item, container) End Select End Function End Class Public Class HomeViewStateTemplateSelector Inherits DataTemplateSelector Public Property IdleTemplate As DataTemplate Public Property AnalysingTemplate As DataTemplate Public Property CompressingTemplate As DataTemplate Public Property ResultsTemplate As DataTemplate Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate If item Is Nothing Then Return MyBase.SelectTemplate(item, container) Dim action = DirectCast(item, ActionState) Select Case action Case ActionState.Idle Return IdleTemplate Case ActionState.Analysing Return AnalysingTemplate Case ActionState.Working, ActionState.Paused Return CompressingTemplate Case ActionState.Results Return ResultsTemplate Case Else Return MyBase.SelectTemplate(item, container) End Select End Function End Class ================================================ FILE: CompactGUI/Components/Settings/Settings_skiplistflyout.xaml ================================================  ================================================ FILE: CompactGUI/Views/Components/FolderWatcherCard.xaml.vb ================================================ Imports System.Windows.Media.Animation Public Class FolderWatcherCard : Inherits UserControl Private currentlyExpandedBorder As Border = Nothing Private Sub ToggleBorderHeight(sender As Object, e As RoutedEventArgs) Dim border As Border = DirectCast(sender, Border) Dim newHeight As Double = If(border.Height = 110, 70, 110) Dim childSavedText = FindChild(Of TextBlock)(border, "SavedText") Dim childDecayedText = FindChild(Of TextBlock)(border, "DecayedText") Dim previousBorderChildSavedText = FindChild(Of TextBlock)(currentlyExpandedBorder, "SavedText") Dim previousBorderChildDecayedText = FindChild(Of TextBlock)(currentlyExpandedBorder, "DecayedText") If currentlyExpandedBorder Is border AndAlso border.Height = 110 AndAlso TypeOf (e) IsNot MouseButtonEventArgs Then ' Do nothing, keep it expanded Return End If If currentlyExpandedBorder IsNot Nothing AndAlso currentlyExpandedBorder IsNot border Then AnimateBorderHeight(currentlyExpandedBorder, 70) previousBorderChildSavedText.Visibility = Visibility.Collapsed previousBorderChildDecayedText.Visibility = Visibility.Visible End If AnimateBorderHeight(border, newHeight) childSavedText.Visibility = If(newHeight = 110, Visibility.Visible, Visibility.Collapsed) childDecayedText.Visibility = If(newHeight = 110, Visibility.Collapsed, Visibility.Visible) currentlyExpandedBorder = If(newHeight = 110, border, Nothing) End Sub Private Sub AnimateBorderHeight(border As Border, targetHeight As Double) Dim animation As New DoubleAnimation() With { .From = border.ActualHeight, .To = targetHeight, .Duration = TimeSpan.FromSeconds(0.2) } Dim storyboard As New Storyboard() Storyboard.SetTarget(animation, border) Storyboard.SetTargetProperty(animation, New PropertyPath(HeightProperty)) storyboard.Children.Add(animation) storyboard.Begin() End Sub Public Shared Function FindChild(Of T As DependencyObject)(parent As DependencyObject, childName As String) As T If parent Is Nothing Then Return Nothing Dim foundChild As T = Nothing Dim childrenCount As Integer = VisualTreeHelper.GetChildrenCount(parent) For i As Integer = 0 To childrenCount - 1 Dim child As DependencyObject = VisualTreeHelper.GetChild(parent, i) Dim childType As T = TryCast(child, T) If childType Is Nothing Then ' The child is not of the request type, so recurse down the tree foundChild = FindChild(Of T)(child, childName) If foundChild IsNot Nothing Then Exit For ElseIf Not String.IsNullOrEmpty(childName) Then Dim frameworkElement As FrameworkElement = TryCast(child, FrameworkElement) ' If the child has the correct name and type If frameworkElement IsNot Nothing AndAlso frameworkElement.Name = childName Then foundChild = DirectCast(child, T) Exit For End If Else ' Child is of the requested type but has no name, return it foundChild = DirectCast(child, T) Exit For End If Next Return foundChild End Function Private Sub DisplayNameTextBlock_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) If e.ClickCount = 2 Then Dim fe = TryCast(sender, FrameworkElement) If fe IsNot Nothing AndAlso fe.DataContext IsNot Nothing Then Dim vm = TryCast(fe.DataContext, Watcher.WatchedFolder) If vm IsNot Nothing Then vm.IsEditing = True End If End If End If End Sub Private Sub DisplayNameTextBox_LostFocus(sender As Object, e As RoutedEventArgs) Dim fe = TryCast(sender, FrameworkElement) If fe IsNot Nothing AndAlso fe.DataContext IsNot Nothing Then Dim vm = TryCast(fe.DataContext, Watcher.WatchedFolder) If vm IsNot Nothing Then vm.IsEditing = False Application.GetService(Of Watcher.Watcher).WriteToFile() End If End If End Sub Private Sub DisplayNameTextBox_KeyDown(sender As Object, e As KeyEventArgs) If e.Key = Key.Enter Then Dim fe = TryCast(sender, FrameworkElement) If fe IsNot Nothing AndAlso fe.DataContext IsNot Nothing Then Dim vm = TryCast(fe.DataContext, Watcher.WatchedFolder) If vm IsNot Nothing Then vm.IsEditing = False Application.GetService(Of Watcher.Watcher).WriteToFile() End If End If End If End Sub End Class ================================================ FILE: CompactGUI/Views/Pages/DatabasePage.xaml ================================================  ================================================ FILE: CompactGUI/Views/Pages/DatabasePage.xaml.vb ================================================ Public Class DatabasePage Public Property viewModel As DatabaseViewModel Sub New(VM As DatabaseViewModel) InitializeComponent() DataContext = VM viewModel = VM ScrollViewer.SetCanContentScroll(Me, False) End Sub End Class ================================================ FILE: CompactGUI/Views/Pages/FolderView.xaml ================================================  ================================================ FILE: CompactGUI/Views/Pages/FolderView.xaml.vb ================================================ Public Class FolderView End Class ================================================ FILE: CompactGUI/Views/Pages/HomePage.xaml ================================================  ================================================ FILE: CompactGUI/Views/Pages/HomePage.xaml.vb ================================================ Class HomePage Private _viewModel As HomeViewModel Sub New(viewmodel As HomeViewModel) ' This call is required by the designer. InitializeComponent() _viewModel = viewmodel DataContext = viewmodel ScrollViewer.SetCanContentScroll(Me, False) ' Add any initialization after the InitializeComponent() call. End Sub Private Async Sub AddFolderButton_Click(sender As Object, e As RoutedEventArgs) Handles BtnAddFolder1.Click, BtnAddFolder2.Click Dim folderBrowser As New Microsoft.Win32.OpenFolderDialog With { .Title = "Select a folder to compress", .Multiselect = True, .ValidateNames = True } folderBrowser.ShowDialog() If folderBrowser.FolderNames.Length > 0 Then Await _viewModel.AddFoldersAsync(folderBrowser.FolderNames) End If End Sub Private Sub Root_DragOver(sender As Object, e As DragEventArgs) If e.Data.GetDataPresent(DataFormats.FileDrop) Then Dim paths As String() = e.Data.GetData(DataFormats.FileDrop) If paths.All(Function(path) IO.Directory.Exists(path)) Then e.Effects = DragDropEffects.Copy Else e.Effects = DragDropEffects.None End If Else e.Effects = DragDropEffects.None End If e.Handled = True End Sub Private Sub Root_Drop(sender As Object, e As DragEventArgs) If e.Data.GetDataPresent(DataFormats.FileDrop) Then Dim paths As String() = e.Data.GetData(DataFormats.FileDrop) If paths.All(Function(path) IO.Directory.Exists(path)) Then _viewModel.AddFoldersAsync(paths).ConfigureAwait(False) End If End If End Sub End Class ================================================ FILE: CompactGUI/Views/Pages/PendingCompression.xaml ================================================  For Steam Games: estimate is based on database results For Non-Steam Folders: estimate is calculated by block analysis. If estimation is disabled, this will always show 0% Skip file types likely to compress poorly For Steam Games: skips files based on database results For Non-Steam Folders: skips files based on compression estimate ================================================ FILE: CompactGUI/Views/Pages/PendingCompression.xaml.vb ================================================ Imports CompactGUI.Core.Settings Public Class PendingCompression Private ReadOnly _settingsService As ISettingsService Sub New() InitializeComponent() _settingsService = Application.GetService(Of ISettingsService) End Sub Private Sub CompressionMode_Radio_Checked(sender As Object, e As RoutedEventArgs) Dim radio As RadioButton = CType(sender, RadioButton) Dim ret As FolderViewModel = CType(radio.DataContext, FolderViewModel) _settingsService.AppSettings.SelectedCompressionMode = ret.Folder.CompressionOptions.SelectedCompressionMode _settingsService.SaveSettings() End Sub Private Sub UiChkSkipPoorlyCompressed_Checked(sender As Object, e As RoutedEventArgs) _settingsService.AppSettings.SkipNonCompressable = True _settingsService.SaveSettings() End Sub Private Sub UiChkSkipPoorlyCompressed_Unchecked(sender As Object, e As RoutedEventArgs) If Not IsVisible Then Return ' Prevents issues when the page is not fully loaded _settingsService.AppSettings.SkipNonCompressable = False _settingsService.SaveSettings() End Sub Private Sub UiChkSkipUserPoorlyCompressed_Checked(sender As Object, e As RoutedEventArgs) _settingsService.AppSettings.SkipUserNonCompressable = True _settingsService.SaveSettings() End Sub Private Sub UiChkSkipUserPoorlyCompressed_Unchecked(sender As Object, e As RoutedEventArgs) If Not IsVisible Then Return _settingsService.AppSettings.SkipUserNonCompressable = False _settingsService.SaveSettings() End Sub Private Sub uiChkWatchFolderForChanges_Checked(sender As Object, e As RoutedEventArgs) _settingsService.AppSettings.WatchFolderForChanges = True _settingsService.SaveSettings() End Sub Private Sub uiChkWatchFolderForChanges_Unchecked(sender As Object, e As RoutedEventArgs) If Not IsVisible Then Return _settingsService.AppSettings.WatchFolderForChanges = False _settingsService.SaveSettings() End Sub End Class ================================================ FILE: CompactGUI/Views/Pages/ResultsTemplate.xaml ================================================  ================================================ FILE: CompactGUI/Views/Pages/ResultsTemplate.xaml.vb ================================================ Public Class ResultsTemplate End Class ================================================ FILE: CompactGUI/Views/Pages/WatcherPage.xaml ================================================  ================================================ FILE: CompactGUI/Views/Pages/WatcherPage.xaml.vb ================================================ Class WatcherPage Public Property viewModel As WatcherViewModel Sub New(VM As WatcherViewModel) InitializeComponent() DataContext = VM viewModel = VM ScrollViewer.SetCanContentScroll(Me, False) End Sub End Class ================================================ FILE: CompactGUI/Views/SettingsPage.xaml ================================================