Full Code of IridiumIO/CompactGUI for AI

master d3fb7150815f cached
113 files
549.2 KB
122.8k tokens
175 symbols
1 requests
Download .txt
Showing preview only (583K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<Application x:Class="Application"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
             Startup="OnStartup">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Components/Converters/Converters.xaml" />
                <ResourceDictionary Source="pack://application:,,,/Wpf.Ui;component/Resources/Theme/Dark.xaml" />
                <ResourceDictionary Source="pack://application:,,,/Wpf.Ui;component/Resources/Wpf.Ui.xaml" />
                <ui:ControlsDictionary />
                <!--  Other merged dictionaries here  -->

                <ResourceDictionary>
                    <Geometry x:Key="SteamLogo">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</Geometry>
                </ResourceDictionary>

            </ResourceDictionary.MergedDictionaries>

            <Style x:Key="RoundedButton" TargetType="{x:Type Button}">
                <Style.Setters>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type Button}">
                                <Border x:Name="button"
                                        Background="{TemplateBinding Background}"
                                        BorderBrush="Transparent" BorderThickness="0" CornerRadius="20">
                                    <Border.Effect>
                                        <DropShadowEffect BlurRadius="16" Direction="-90" Opacity="0.26" ShadowDepth="6" />
                                    </Border.Effect>

                                    <TextBlock Text="{TemplateBinding Button.Content}"
                                               Margin="0,-2,0,0" HorizontalAlignment="Center" VerticalAlignment="Center"
                                               FontFamily="Segoe UI" FontSize="15" />

                                    <VisualStateManager.VisualStateGroups>
                                        <VisualStateGroup x:Name="Common">
                                            <VisualState x:Name="MouseOver">
                                                <Storyboard>
                                                    <ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF597186"
                                                                    Duration="0:00:0.3" />
                                                </Storyboard>
                                            </VisualState>
                                            <VisualState x:Name="Normal">
                                                <Storyboard>
                                                    <ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                                                                    To="{TemplateBinding Background}"
                                                                    Duration="0:00:0.3" />
                                                </Storyboard>
                                            </VisualState>

                                        </VisualStateGroup>
                                    </VisualStateManager.VisualStateGroups>
                                </Border>


                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsPressed" Value="True">
                                        <Setter TargetName="button" Property="Background" Value="#FF4B6175" />
                                    </Trigger>

                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter TargetName="button" Property="Opacity" Value="0.7" />
                                        <Setter Property="Foreground" Value="White" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style.Setters>
            </Style>

            <Style x:Key="RoundedCheckBox" TargetType="CheckBox">
                <Setter Property="Cursor" Value="Hand" />
                <Setter Property="Content" Value="" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type CheckBox}">
                            <Grid>

                                <Border x:Name="cBox"
                                        Width="36" Height="36"
                                        VerticalAlignment="Center"
                                        Background="Transparent" BorderBrush="#98A9B9" BorderThickness="2" CornerRadius="18">
                                    <Grid>
                                        <Ellipse x:Name="outerEllipse"
                                                 Grid.Column="0"
                                                 Margin="3"
                                                 Fill="#FFFFFF" />
                                    </Grid>
                                </Border>
                                <ContentPresenter x:Name="content"
                                                  Margin="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsChecked" Value="True">
                                    <Trigger.EnterActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <ColorAnimation Storyboard.TargetName="outerEllipse"
                                                                Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" From="#FFFFFF"
                                                                To="#98A9B9" Duration="0:0:0.2" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.EnterActions>
                                    <Trigger.ExitActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <ColorAnimation Storyboard.TargetName="outerEllipse"
                                                                Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" From="#98A9B9"
                                                                To="#FFFFFF" Duration="0:0:0.2" />
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.ExitActions>
                                </Trigger>

                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
                <Setter Property="Control.Template" Value="{x:Null}" />
            </Style>

            <Style x:Key="CustomCardExpanderStyle"
                   BasedOn="{StaticResource DefaultUiCardExpanderStyle}"
                   TargetType="{x:Type ui:CardExpander}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ui:CardExpander}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="Auto" />
                                </Grid.RowDefinitions>

                                <!--  Top level controls always visible  -->
                                <Border x:Name="ToggleButtonBorder"
                                        Grid.Row="0"
                                        Background="{TemplateBinding Background}"
                                        BorderBrush="{TemplateBinding BorderBrush}"
                                        BorderThickness="1"
                                        CornerRadius="{TemplateBinding CornerRadius}">
                                    <ToggleButton x:Name="ExpanderToggleButton"
                                                  Margin="0"
                                                  Padding="{TemplateBinding Padding}"
                                                  HorizontalAlignment="Stretch"
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                                  HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
                                                  FontSize="{TemplateBinding FontSize}"
                                                  Foreground="{TemplateBinding Foreground}"
                                                  IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                                  IsEnabled="{TemplateBinding IsEnabled}"
                                                  OverridesDefaultStyle="True"
                                                  Template="{StaticResource DefaultUiCardExpanderToggleButtonStyle}">
                                        <ToggleButton.Content>
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="Auto" />
                                                    <ColumnDefinition Width="*" />
                                                </Grid.ColumnDefinitions>

                                                <ContentControl x:Name="ControlIcon"
                                                                Content="{TemplateBinding Icon}"
                                                                Grid.Column="0"
                                                                Margin="{StaticResource CardExpanderIconMargin}"
                                                                VerticalAlignment="Center"
                                                                Focusable="False"
                                                                FontSize="{StaticResource CardExpanderIconSize}"
                                                                Foreground="{TemplateBinding Foreground}"
                                                                KeyboardNavigation.IsTabStop="False" />

                                                <ContentPresenter x:Name="HeaderContentPresenter"
                                                                  Content="{TemplateBinding Header}"
                                                                  Grid.Column="1"
                                                                  TextElement.Foreground="{TemplateBinding Foreground}" />
                                            </Grid>
                                        </ToggleButton.Content>
                                    </ToggleButton>
                                </Border>

                                <!--  Collapsed content to expand  -->
                                <Grid Grid.Row="1" ClipToBounds="True">
                                    <Border x:Name="ContentPresenterBorder"
                                            Background="Transparent"
                                            BorderBrush="{TemplateBinding BorderBrush}"
                                            BorderThickness="1,0,1,1" CornerRadius="0,0,4,4" Visibility="Collapsed">
                                        <ContentPresenter x:Name="ContentPresenter"
                                                          Content="{TemplateBinding Content}"
                                                          Margin="{TemplateBinding ContentPadding}"
                                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                                        <Border.Tag>
                                            <system:Double>0.0</system:Double>
                                        </Border.Tag>
                                        <Border.RenderTransform>
                                            <TranslateTransform>
                                                <TranslateTransform.Y>
                                                    <MultiBinding Converter="{StaticResource AnimationFactorToValueConverter}" ConverterParameter="negative">
                                                        <Binding ElementName="ContentPresenterBorder" Path="ActualHeight" />
                                                        <Binding ElementName="ContentPresenterBorder" Path="Tag" />
                                                    </MultiBinding>
                                                </TranslateTransform.Y>
                                            </TranslateTransform>
                                        </Border.RenderTransform>
                                    </Border>
                                </Grid>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsExpanded" Value="True">
                                    <!--  TODO: Update  -->
                                    <Setter TargetName="ToggleButtonBorder" Property="CornerRadius" Value="4,4,0,0" />
                                    <Trigger.EnterActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterBorder" Storyboard.TargetProperty="(Border.Visibility)">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterBorder" Storyboard.TargetProperty="Tag">
                                                    <DiscreteDoubleKeyFrame KeyTime="0" Value="1.0" />
                                                    <SplineDoubleKeyFrame KeySpline="0.0, 0.0, 0.0, 1.0" KeyTime="0:0:0.333"
                                                                          Value="0.0" />
                                                </DoubleAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.EnterActions>
                                    <Trigger.ExitActions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterBorder" Storyboard.TargetProperty="(Border.Visibility)">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
                                                    <DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenterBorder" Storyboard.TargetProperty="Tag">
                                                    <DiscreteDoubleKeyFrame KeyTime="0" Value="0.0" />
                                                    <SplineDoubleKeyFrame KeySpline="1.0, 1.0, 0.0, 1.0" KeyTime="0:0:0.167"
                                                                          Value="1.0" />
                                                </DoubleAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </Trigger.ExitActions>
                                </Trigger>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter Property="Background" Value="{DynamicResource CardBackgroundDisabled}" />
                                    <Setter Property="BorderBrush" Value="{DynamicResource CardBorderBrushDisabled}" />
                                    <Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource CardForegroundDisabled}" />
                                    <Setter TargetName="ExpanderToggleButton" Property="Foreground" Value="{DynamicResource CardForegroundDisabled}" />
                                </Trigger>
                                <Trigger Property="Icon" Value="{x:Null}">
                                    <Setter TargetName="ControlIcon" Property="Margin" Value="0" />
                                    <Setter TargetName="ControlIcon" Property="Visibility" Value="Collapsed" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>


            <!--  Other app resources here  -->

        </ResourceDictionary>

    </Application.Resources>
</Application>


================================================
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)
<Assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)>


================================================
FILE: CompactGUI/CompactGUI.vbproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    
    <OutputType>WinExe</OutputType>
    <TargetFramework>net9.0-windows</TargetFramework>
    <RootNamespace>CompactGUI</RootNamespace>
    <UseWPF>true</UseWPF>
    <OptionStrict>Off</OptionStrict>
    <ApplicationManifest>My Project\app.manifest</ApplicationManifest>
    <Version>4.0.0</Version>
    <Authors>IridiumIO</Authors>
    <Company>IridiumIO</Company>
    <Description>GUI for the Windows compact.exe command-line tool.</Description>
    <Copyright>Copyright © 2025</Copyright>
    <PackageProjectUrl>https://github.com/IridiumIO/CompactGUI/</PackageProjectUrl>
    <ApplicationIcon>icon.ico</ApplicationIcon>
 
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <NoWarn>41999,42016,42017,42018,42019,42020,42021,42022,42032,42036</NoWarn>
    <WarningsAsErrors></WarningsAsErrors>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <NoWarn>41999,42016,42017,42018,42019,42020,42021,42022,42032,42036</NoWarn>
    <WarningsAsErrors></WarningsAsErrors>    
    <DebugType>none</DebugType>
  </PropertyGroup>


  <ItemGroup>
    <Import Include="System.Windows" />
    <Import Include="System.Windows.Controls" />
    <Import Include="System.Windows.Data" />
    <Import Include="System.Windows.Documents" />
    <Import Include="System.Windows.Input" />
    <Import Include="System.Windows.Media" />
    <Import Include="System.Windows.Media.Imaging" />
    <Import Include="System.Windows.Navigation" />
    <Import Include="System.Windows.Shapes" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
    <PackageReference Include="Coravel" Version="6.0.2" />
    <PackageReference Include="diskdetector-net" Version="0.3.2" />
    <PackageReference Include="Gameloop.Vdf" Version="0.6.2" />
    <PackageReference Include="IridiumIO.MVVM.VBSourceGenerators" Version="0.6.1" PrivateAssets="All" />
    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.7" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
    <PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
    <PackageReference Include="ValueConverters" Version="3.1.22" />
    <PackageReference Include="WPF-UI" Version="4.0.3" />
    <PackageReference Include="WPF-UI.DependencyInjection" Version="4.0.3" />
    <PackageReference Include="WPF-UI.Tray" Version="4.0.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CompactGUI.Core\CompactGUI.Core.csproj" />
    <ProjectReference Include="..\CompactGUI.Logging\CompactGUI.Logging.csproj" />
    <ProjectReference Include="..\CompactGUI.Watcher\CompactGUI.Watcher.vbproj" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="FunctionalConverters">
      <HintPath>..\..\FunctionalConverters\FunctionalConverters\bin\Release\net6.0-windows\FunctionalConverters.dll</HintPath>
    </Reference>
  </ItemGroup>

  <Target Name="RenamePublishedExe" AfterTargets="Publish" Condition="'$(IsMonolithic)' == 'true'">
    <Move SourceFiles="$(PublishDir)CompactGUI.exe" DestinationFiles="$(PublishDir)CompactGUI.mono.exe" />

  </Target>
  
  <PropertyGroup>
    <FinalPublishDir>$(ProjectDir)bin\publish\FinalOutput\</FinalPublishDir>
  </PropertyGroup>

  <Target Name="MovePublishedFiles" AfterTargets="Publish">
    <Message Text="Moving published files to FinalOutput..." Importance="high" />
    
    <ItemGroup>
        <PublishedFiles Include="$(PublishDir)**\*" />
    </ItemGroup>

    <Copy SourceFiles="@(PublishedFiles)" DestinationFolder="$(FinalPublishDir)" SkipUnchangedFiles="true" />
  </Target>

  
</Project>


================================================
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
================================================
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:converters="clr-namespace:ValueConverters;assembly=ValueConverters"
                    xmlns:local="clr-namespace:CompactGUI">

    <converters:BoolToVisibilityConverter x:Key="BoolToVisConverter" />
    <!-- <converters:EnumToBoolConverter x:Key="XEnumToBoolConverter" /> -->
    <local:EnumToRadioButtonConverter x:Key="EnumToRadioButtonConverter" />
    <local:DecimalToPercentageConverter x:Key="DecimalToPercentageConverter" />
    <local:BytesToReadableConverter x:Key="BytesToReadableConverter" />
    <local:StrippedFolderPathConverter x:Key="StrippedFolderPathConverter" />
    <local:RelativeDateConverter x:Key="RelativeDateConverter" />
    <local:CompressionLevelAbbreviatedConverter x:Key="CompressionLevelAbbreviatedConverter" />
    <!-- <local:ConfidenceIntToColorConverter x:Key="ConfidenceIntToColorConverter" /> -->
    <!-- <local:ConfidenceIntToStringConverter x:Key="ConfidenceIntToStringConverter" /> -->
    <!-- <local:WikiCompressionLevelAbbreviatedConverter x:Key="WikiCompressionLevelAbbreviatedConverter" /> -->
    <!-- <local:RatioConverter x:Key="RatioConverter" /> -->
    <local:ProgressBarColorConverter x:Key="ProgressBarColorConverter" />
    <!-- <local:NonZeroToVisConverter x:Key="NonZeroToVisConverter" /> -->
    <!-- <local:WindowScalingConverter x:Key="WindowScalingConverter" /> -->
    <local:BooleanToInverseVisibilityConverter x:Key="BooleanToInverseVisibilityConverter" />
    <local:TokenisedFolderPathConverter x:Key="TokenisedFolderPathConverter" />
    <local:FolderStatusToColorConverter x:Key="FolderStatusToColorConverter" />
    <local:FolderStatusToStringConverter x:Key="FolderStatusToStringConverter" />
    <local:FolderWorkingStateToPauseSymbolConverter x:Key="FolderWorkingStateToPauseSymbolConverter"/>
    <local:IsSteamFolderAndFreshlyCompressedMultiConverter x:Key="IsSteamFolderAndFreshlyCompressedMultiConverter" />
    <local:IsSteamFolderConverter x:Key="IsSteamFolderConverter"/>
    <local:AnimationFactorToValueConverter x:Key="AnimationFactorToValueConverter" />
    <local:FolderActionStateWorkingToVisibilityConverter x:Key="FolderActionStateWorkingToVisibilityConverter" />
    <local:ZeroCountToVisibilityConverter x:Key="ZeroCountToVisibilityConverter"/>
    <local:NumberWithSpacesConverter x:Key="NumberWithSpacesConverter"/>
    <local:BackgroundModeToVisibilityConverter x:Key="BackgroundModeToVisibilityConverter"/>
    <local:EnumToIntConverter x:Key="EnumToIntConverter" />

    <converters:ValueConverterGroup x:Key="IsSteamFolderToVisibilityConverter">
        <local:IsSteamFolderConverter/>
        <converters:BoolToVisibilityConverter/>
    </converters:ValueConverterGroup>
    <converters:IntegerToBoolConverter x:Key="IntegerToBoolConverter" />

</ResourceDictionary>


================================================
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
    <Runtime.CompilerServices.Extension()>
    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
================================================
<ui:FluentWindow x:Class="Settings_skiplistflyout"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:local="clr-namespace:CompactGUI"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
                 Width="620" Height="400"
                 d:DesignHeight="400" d:DesignWidth="500"
                 Background="{DynamicResource ApplicationBackgroundBrush}"
                 ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
                 ExtendsContentIntoTitleBar="True"
                 Foreground="{DynamicResource TextFillColorPrimaryBrush}"
                 RenderOptions.BitmapScalingMode="HighQuality" RenderOptions.ClearTypeHint="Enabled"
                 WindowBackdropType="Mica" WindowStartupLocation="CenterScreen"
                 mc:Ignorable="d">
    <ui:FluentWindow.Resources>
        <DataTemplate x:Key="NameTokenTemplate">
            <DataTemplate.Resources>
                <Storyboard x:Key="OnLoaded1">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <SplineDoubleKeyFrame KeyTime="0" Value="0" />
                        <SplineDoubleKeyFrame KeyTime="0:0:0.4" Value="1" />
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </DataTemplate.Resources>
            <Border x:Name="border"
                    Height="28" MinWidth="50"
                    Margin="5,8,5,0" Padding="10,0"
                    Background="#7AB3C6D8" BorderThickness="0" CornerRadius="5">

                <TextBlock Text="{Binding}"
                           Grid.Column="0"
                           Margin="0,-1,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"
                           FontSize="13" Foreground="Black" TextWrapping="NoWrap" />

            </Border>
            <DataTemplate.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard Storyboard="{StaticResource OnLoaded1}" />
                </EventTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ui:FluentWindow.Resources>
    <Grid x:Name="MainGrid">
        <ui:TitleBar Panel.ZIndex="10"
                     ShowMaximize="False" ShowMinimize="False" />
        <TextBlock Text="edit skipped filetypes"
                   Margin="10"
                   FontSize="22" FontWeight="SemiBold" />
        <local:TokenizedTextBox x:Name="UiTokenizedText"
                                Margin="10,50,10,80"
                                AcceptsTab="False"
                                Background="{StaticResource CardBackground}"
                                CaretBrush="{StaticResource CardForeground}"
                                Foreground="{StaticResource CardForeground}"
                                TokenTemplate="{DynamicResource NameTokenTemplate}"
                                VerticalScrollBarVisibility="Visible">
            <FlowDocument>
                <Paragraph>
                    <Run />
                </Paragraph>
            </FlowDocument>
        </local:TokenizedTextBox>

        <Button x:Name="UiSave"
                Content="Save"
                Width="100"
                Margin="0,0,130,20" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                Click="UISave_Click" />
        <Button x:Name="UiReset"
                Content="Reset"
                Width="100"
                Margin="0,0,10,20" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                Click="UIReset_Click" />

    </Grid>
</ui:FluentWindow>


================================================
FILE: CompactGUI/Components/Settings/Settings_skiplistflyout.xaml.vb
================================================
Imports CompactGUI.Core.Settings

Public Class Settings_skiplistflyout

    Private _settingsService As ISettingsService

    Sub New()

        InitializeComponent()
        _settingsService = Application.GetService(Of ISettingsService)()
        UiTokenizedText.TokenMatcher = Function(text) If(text.EndsWith(" "c) OrElse text.EndsWith(";"c) OrElse text.EndsWith(","c), text.Substring(0, text.Length - 1).Trim(), Nothing)
        PopulateTokens()
    End Sub

    Private Sub PopulateTokens()
        UiTokenizedText.Document.Blocks.Clear()
        For Each i In _settingsService.AppSettings.NonCompressableList
            UiTokenizedText.InsertText(i)
        Next
    End Sub

    Private Sub UIReset_Click(sender As Object, e As RoutedEventArgs)
        _settingsService.AppSettings.NonCompressableList = New Settings().NonCompressableList
        _settingsService.SaveSettings()
        PopulateTokens()
    End Sub

    Private Sub UISave_Click(sender As Object, e As RoutedEventArgs)
        Dim items = UiTokenizedText.Document.Blocks

        Dim inlineI As Paragraph = items(0)
        Dim allObj = inlineI.Inlines _
            .Where(Function(c) c.GetType = GetType(InlineUIContainer)) _
            .Select(Function(f As InlineUIContainer)
                        Dim cl As ContentPresenter = f.Child
                        Return cl.Content.ToString
                    End Function).ToList

        _settingsService.AppSettings.NonCompressableList = allObj.Where(Function(c) c.StartsWith("."c) AndAlso c.Length > 1).Distinct().ToList
        _settingsService.SaveSettings()

        PopulateTokens()

    End Sub
End Class


================================================
FILE: CompactGUI/Helper.vb
================================================
Imports System.IO
Imports System.Net.NetworkInformation
Imports System.Runtime.InteropServices
Imports System.Text

Imports CommunityToolkit.Mvvm.Input

Imports CompactGUI.Core.SharedMethods

Imports Gameloop.Vdf


Public Module Helper
    Function GetSteamNameAndIDFromFolder(path As String) As (appID As Integer, gameName As String, installDir As String)

        Dim workingDir = New DirectoryInfo(path)
        Dim parentfolder = workingDir.Parent.Parent

        If Not parentfolder?.Name = "steamapps" Then Return Nothing

        For Each fl In parentfolder.EnumerateFiles("*.acf").Where(Function(f) f.Length > 0)
            Try
                Dim vConv = VdfConvert.Deserialize(IO.File.ReadAllText(fl.FullName))
                If vConv.Value.Item("installdir").ToString = workingDir.Name Then
                    Dim appID = CInt(vConv.Value.Item("appid").ToString)
                    Dim sName = vConv.Value.Item("name").ToString
                    Dim sInstallDir = vConv.Value.Item("installdir").ToString
                    Return (appID, sName, sInstallDir)
                    'TODO: Maybe add check to see when game was last updated?
                End If
            Catch
                Debug.WriteLine($"VDF file unsupported: {fl.FullName}")
            End Try
        Next

        Return Nothing
    End Function


    Function getUID() As String
        Dim macAddress As String = String.Empty

        For Each nic As NetworkInterface In NetworkInterface.GetAllNetworkInterfaces()
            If nic.OperationalStatus = OperationalStatus.Up AndAlso
           nic.NetworkInterfaceType <> NetworkInterfaceType.Loopback AndAlso
           Not String.IsNullOrEmpty(nic.GetPhysicalAddress().ToString()) Then

                Dim raw = nic.GetPhysicalAddress().ToString()
                macAddress = String.Join(":", Enumerable.Range(0, raw.Length \ 2).Select(Function(i) raw.Substring(i * 2, 2)))
                Exit For
            End If
        Next

        Return Convert.ToBase64String(Encoding.UTF8.GetBytes(macAddress))
    End Function

    Function LoadImageFromDisk(imagePath As String) As BitmapImage
        Dim bImg As New BitmapImage(New Uri(imagePath))
        Return bImg
    End Function

    Function LoadImageFromMemoryStream(imageData As Byte()) As BitmapImage
        Dim bImg As New BitmapImage()
        Using ms As New MemoryStream(imageData)
            bImg.BeginInit()
            bImg.CacheOption = BitmapCacheOption.OnLoad
            bImg.StreamSource = ms
            bImg.EndInit()
        End Using
        Return bImg
    End Function


    Public Function GetInvalidFolders(folderPaths() As String) As (InvalidFolders As List(Of String), InvalidMessages As List(Of FolderVerificationResult))

        Dim invalidFolders As New List(Of String)
        Dim invalidMessages As New List(Of FolderVerificationResult)

        For Each folder In folderPaths
            Dim validation = Core.SharedMethods.VerifyFolder(folder)

            If validation <> FolderVerificationResult.Valid Then
                invalidFolders.Add(folder)
                invalidMessages.Add(validation)
            End If
        Next


        Return (invalidFolders, invalidMessages)
    End Function



    Public Sub RunAsAdmin(FolderName As String)
        Dim myproc As New Process With {
            .StartInfo = New ProcessStartInfo With {
                .FileName = Environment.ProcessPath,
                .UseShellExecute = True,
                .Arguments = $"""{FolderName}""",
                .Verb = "runas"}
        }
        Dim app As Application = Application.Current

        app.ShutdownPipeServer().ContinueWith(
            Sub()
                app.Dispatcher.Invoke(
                    Sub()
                        Application.mutex.ReleaseMutex()
                        Application.mutex.Dispose()
                    End Sub
                )
                myproc.Start()
                app.Dispatcher.Invoke(Sub() app.Shutdown())
            End Sub
        )
    End Sub

End Module

'https://stackoverflow.com/questions/4897655/create-a-shortcut-on-desktop
Module ShortcutCreator

    <ComImport>
    <Guid("00021401-0000-0000-C000-000000000046")>
    Friend Class ShellLink
    End Class

    <ComImport>
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    <Guid("000214F9-0000-0000-C000-000000000046")>
    Friend Interface IShellLink
        Sub GetPath(<Out, MarshalAs(UnmanagedType.LPWStr)> pszFile As StringBuilder, cchMaxPath As Integer, ByRef pfd As IntPtr, fFlags As Integer)
        Sub GetIDList(ByRef ppidl As IntPtr)
        Sub SetIDList(pidl As IntPtr)
        Sub GetDescription(<Out, MarshalAs(UnmanagedType.LPWStr)> pszName As StringBuilder, cchMaxName As Integer)
        Sub SetDescription(<MarshalAs(UnmanagedType.LPWStr)> pszName As String)
        Sub GetWorkingDirectory(<Out, MarshalAs(UnmanagedType.LPWStr)> pszDir As StringBuilder, cchMaxPath As Integer)
        Sub SetWorkingDirectory(<MarshalAs(UnmanagedType.LPWStr)> pszDir As String)
        Sub GetArguments(<Out, MarshalAs(UnmanagedType.LPWStr)> pszArgs As StringBuilder, cchMaxPath As Integer)
        Sub SetArguments(<MarshalAs(UnmanagedType.LPWStr)> pszArgs As String)
        Sub GetHotkey(ByRef pwHotkey As Short)
        Sub SetHotkey(wHotkey As Short)
        Sub GetShowCmd(ByRef piShowCmd As Integer)
        Sub SetShowCmd(iShowCmd As Integer)
        Sub GetIconLocation(<Out, MarshalAs(UnmanagedType.LPWStr)> pszIconPath As StringBuilder, cchIconPath As Integer, ByRef piIcon As Integer)
        Sub SetIconLocation(<MarshalAs(UnmanagedType.LPWStr)> pszIconPath As String, iIcon As Integer)
        Sub SetRelativePath(<MarshalAs(UnmanagedType.LPWStr)> pszPathRel As String, dwReserved As Integer)
        Sub Resolve(hwnd As IntPtr, fFlags As Integer)
        Sub SetPath(<MarshalAs(UnmanagedType.LPWStr)> pszFile As String)
    End Interface

    <ComImport>
    <Guid("0000010B-0000-0000-C000-000000000046")>
    <InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    Friend Interface IPersistFile
        Sub GetClassID(ByRef pClassID As Guid)
        Sub IsDirty()
        Sub Load(<MarshalAs(UnmanagedType.LPWStr)> pszFileName As String, dwMode As UInteger)
        Sub Save(<MarshalAs(UnmanagedType.LPWStr)> pszFileName As String, fRemember As Boolean)
        Sub SaveCompleted(<MarshalAs(UnmanagedType.LPWStr)> pszFileName As String)
        Sub GetCurFile(<MarshalAs(UnmanagedType.LPWStr)> ByRef ppszFileName As String)
    End Interface

    Public Sub CreateShortcut(shortcutPath As String, targetPath As String, Optional description As String = "", Optional workingDirectory As String = "", Optional iconPath As String = "")
        Dim link As IShellLink = CType(New ShellLink(), IShellLink)

        link.SetDescription(description)
        link.SetPath(targetPath)
        link.SetWorkingDirectory(If(String.IsNullOrWhiteSpace(workingDirectory), IO.Path.GetDirectoryName(targetPath), workingDirectory))

        If Not String.IsNullOrWhiteSpace(iconPath) Then
            link.SetIconLocation(iconPath, 0)
        End If

        Dim file As IPersistFile = CType(link, IPersistFile)
        file.Save(shortcutPath, True)
    End Sub

End Module

================================================
FILE: CompactGUI/MainWindow.xaml
================================================
<ui:FluentWindow x:Class="MainWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:local="clr-namespace:CompactGUI"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:tray="http://schemas.lepo.co/wpfui/2022/xaml/tray"
                 xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
                 x:Name="MainWindow" Title="CompactGUI"
                 Width="1300" Height="700" MinWidth="768" MinHeight="600"
                 d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
                 ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
                 ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
                 Closing="MainWindow_Closing" ExtendsContentIntoTitleBar="True"
                 Foreground="{DynamicResource TextFillColorPrimaryBrush}"
                 RenderOptions.BitmapScalingMode="HighQuality" WindowBackdropType="Mica"
                 WindowCornerPreference="Round" WindowStartupLocation="CenterScreen"
                 mc:Ignorable="d">
    <bh:Interaction.Triggers>

        <bh:EventTrigger EventName="Closing">
            <bh:InvokeCommandAction Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
        </bh:EventTrigger>
    </bh:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2"
                     IsHitTestVisible="True" />



        <ui:NavigationView x:Name="NavigationView"
                           Grid.Row="1"
                           Margin="0,10,0,0" 
                           IsBackButtonVisible="Collapsed" PaneDisplayMode="Top" Transition="FadeIn">
            <ui:NavigationView.MenuItems>


                <ui:NavigationViewItem Content="Home"
                                       Margin="15,2,15,10"
                                       NavigationCacheMode="Required"
                                       Tag="{Binding}"
                                       TargetPageType="{x:Type local:HomePage}">
                    <ui:NavigationViewItem.Icon>
                        <ui:SymbolIcon Symbol="Home32" />
                    </ui:NavigationViewItem.Icon>
                </ui:NavigationViewItem>


                <ui:NavigationViewItem Content="Watcher"
                                       Margin="15,2,15,10"
                                       NavigationCacheMode="Required"
                                       Tag="{Binding}"
                                       TargetPageType="{x:Type local:WatcherPage}">
                    <ui:NavigationViewItem.Icon>
                        <ui:SymbolIcon Filled="False" Symbol="Eye32" />
                    </ui:NavigationViewItem.Icon>
                </ui:NavigationViewItem>

                <ui:NavigationViewItem Content="Compression DB"
                                       Margin="15,2,15,10"
                                       NavigationCacheMode="Disabled"
                                       Tag="{Binding}"
                                       TargetPageType="{x:Type local:DatabasePage}">
                    <ui:NavigationViewItem.Icon>
                        <ui:SymbolIcon Filled="False" Symbol="Database16" />
                    </ui:NavigationViewItem.Icon>
                </ui:NavigationViewItem>

            </ui:NavigationView.MenuItems>

            <ui:NavigationView.FooterMenuItems>

                <ui:NavigationViewItem Margin="-5,0,15,1"
                                       NavigationCacheMode="Required"
                                       Tag="{Binding}"
                                       TargetPageType="{x:Type local:SettingsPage}">
                    <!--  TargetPageType="{x:Type local:SettingsPage}"  -->

                    <ui:SymbolIcon Margin="0,0,-3,0"
                                   FontSize="16" Symbol="Settings48" />

                </ui:NavigationViewItem>
            </ui:NavigationView.FooterMenuItems>


            <ui:NavigationView.ContentOverlay>
                <Grid>
                    <ui:SnackbarPresenter x:Name="SnackbarPresenter" />
                </Grid>
            </ui:NavigationView.ContentOverlay>


        </ui:NavigationView>


        <Grid x:Name="RootContentDialog"
              Grid.Row="0" Grid.RowSpan="2"
              Margin="0" Panel.ZIndex="-1"
              ClipToBounds="True">

            <Border Background="#ffffff" />
            <Border>
                <Border.Background>
                    <LinearGradientBrush Opacity="1" StartPoint="0.5,0.5" EndPoint="0.5,1">
                        <GradientStop Offset="0" Color="#500D2A41" />
                        <GradientStop Offset="1" Color="#FF0D2A41" />
                    </LinearGradientBrush>
                </Border.Background>
            </Border>

            <Border Background="#1f3448" Opacity="0.75" />

            <!--<ui:Image x:Name="DefaultBG" Stretch="UniformToFill" Opacity="0.66" Margin="-20" RenderOptions.BitmapScalingMode="HighQuality">
                <ui:Image.Effect>
                    <BlurEffect KernelType="Box" Radius="18" RenderingBias="Quality" />
                </ui:Image.Effect>
            </ui:Image>-->

            <local:ImageControl x:Name="SteamBg"
                                Height="{Binding ActualHeight, ElementName=MainWindow}"
                                Margin="-75" HorizontalAlignment="Center" VerticalAlignment="Center"
                                ClipToBounds="True"
                                NewSource="{Binding BackgroundImage}"
                                Opacity="1" RenderOptions.BitmapScalingMode="HighQuality" Stretch="UniformToFill">
                <local:ImageControl.OpacityMask>
                    <LinearGradientBrush StartPoint="0,0.2" EndPoint="0,1">
                        <GradientStop Offset="0.0" Color="#00FFFFFF" />
                        <GradientStop Offset="1.0" Color="#FFFFFFFF" />
                    </LinearGradientBrush>
                </local:ImageControl.OpacityMask>
                <local:ImageControl.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1.2" ScaleY="1.2" />
                    </TransformGroup>

                </local:ImageControl.RenderTransform>
                <local:ImageControl.RenderTransformOrigin>
                    <Point X="0.5" Y="0.5" />
                </local:ImageControl.RenderTransformOrigin>
                <local:ImageControl.Triggers>
                    <EventTrigger RoutedEvent="local:ImageControl.SourceChanged">
                        <BeginStoryboard>
                            <Storyboard>

                                <DoubleAnimation Storyboard.TargetProperty="(Image.Opacity)" From="0" To="0.8" Duration="0:0:0.5">
                                    <DoubleAnimation.EasingFunction>
                                        <QuadraticEase EasingMode="EaseInOut" />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetProperty="(Effect).Radius" From="100" To="22" Duration="0:0:2">
                                    <DoubleAnimation.EasingFunction>
                                        <ExponentialEase />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
                                                 To="1.2" Duration="0:0:6">
                                    <DoubleAnimation.EasingFunction>
                                        <ExponentialEase EasingMode="EaseIn" />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                                                 To="1.2" Duration="0:0:8">
                                    <DoubleAnimation.EasingFunction>
                                        <QuinticEase EasingMode="EaseOut" />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>

                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <!--<EventTrigger RoutedEvent="local:ImageControl.SourceChanging">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="(Effect).Radius" To="100" Duration="0:0:0" BeginTime="0:0:0"/>
                                <DoubleAnimation Storyboard.TargetProperty="(Image.Opacity)" To="0" Duration="0:0:0"/>

                                <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" To="2.0"  Duration="0:0:0"/>
                                <DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" To="1.2" Duration="0:0:0"/>

                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>-->
                </local:ImageControl.Triggers>


                <local:ImageControl.Effect>
                    <BlurEffect Radius="0" RenderingBias="Quality" />
                </local:ImageControl.Effect>


            </local:ImageControl>

            <Label x:Name="ProgTitle"
                   Margin="20,16" HorizontalAlignment="Left"
                   d:Visibility="Visible" FontSize="14" Visibility="Collapsed">
                <Label.RenderTransform>
                    <TranslateTransform x:Name="TransformL" Y="20" />
                </Label.RenderTransform>
                <Label.Style>
                    <Style TargetType="Label">
                        <Setter Property="Opacity" Value="0" />
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Visibility, RelativeSource={RelativeSource Self}}" Value="Visible">
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="0" />
                                            <!--  Fade-in animation  -->
                                            <DoubleAnimation BeginTime="0:0:0.5"
                                                             Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1.5" />

                                            <!--  Move upwards animation  -->
                                            <DoubleAnimation BeginTime="0:0:0.5"
                                                             Storyboard.TargetProperty="(RenderTransform).(TranslateTransform.Y)" From="10" To="0"
                                                             Duration="0:0:0.5">
                                                <DoubleAnimation.EasingFunction>
                                                    <CircleEase />
                                                </DoubleAnimation.EasingFunction>
                                            </DoubleAnimation>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Label.Style>
                <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
                    <TextBlock Text="CompactGUI"
                               Margin="0,0,20,0" VerticalAlignment="Center"
                               Foreground="#20FFFFFF"
                               Visibility="{Binding IsAdmin, Converter={StaticResource BooleanToInverseVisibilityConverter}}" />
                    <Border Width="80" Height="22"
                            Background="#20f44336" CornerRadius="10"
                            Visibility="{Binding IsAdmin, Converter={StaticResource BooleanToVisibilityConverter}}">
                        <TextBlock Text="Admin"
                                   HorizontalAlignment="Center" VerticalAlignment="Center"
                                   Foreground="White" />

                    </Border>
                </StackPanel>
            </Label>


        </Grid>
        <ui:SnackbarPresenter x:Name="RootSnackbar"
                              Grid.Row="1" Grid.Column="0"
                              MaxWidth="600" />
        <tray:NotifyIcon x:Name="NotifyIconTray"
                         Grid.Row="0"
                         FocusOnLeftClick="True" MenuOnRightClick="True" TooltipText="CompactGUI">
            <tray:NotifyIcon.Menu>
                <ContextMenu x:Name="NotifyIconTrayMenu">
                    <MenuItem Command="{Binding NotifyIconOpenCommand}" Header="Open" />
                    <MenuItem Command="{Binding NotifyIconExitCommand}" Header="Exit" />
                </ContextMenu>
            </tray:NotifyIcon.Menu>
        </tray:NotifyIcon>

    </Grid>
</ui:FluentWindow>


================================================
FILE: CompactGUI/MainWindow.xaml.vb
================================================
Imports System.ComponentModel

Imports CompactGUI.Core.Settings

Imports Wpf.Ui
Imports Wpf.Ui.Abstractions
Imports Wpf.Ui.Controls

Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged

    Private ReadOnly _NavigationService As INavigationService
    Private _MainWindowViewModel As MainWindowViewModel
    Private _SettingsService As ISettingsService
    Public Sub New(settingsService As ISettingsService, navigationService As INavigationService, serviceProvider As IServiceProvider, snackbarService As CustomSnackBarService, viewmodel As MainWindowViewModel)

        ' This call is required by the designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.

        snackbarService.SetSnackbarPresenter(RootSnackbar)
        navigationService.SetNavigationControl(NavigationView)
        NavigationView.SetServiceProvider(serviceProvider)

        _NavigationService = navigationService
        _MainWindowViewModel = viewmodel
        _SettingsService = settingsService
        DataContext = viewmodel

        NotifyIconTrayMenu.DataContext = viewmodel

        AddHandler Application.GetService(Of HomeViewModel)().PropertyChanged, AddressOf HVPropertyChanged
        AddHandler navigationService.GetNavigationControl.Navigated, AddressOf OnNavigated

        If _SettingsService.AppSettings.WindowWidth > 0 Then
            Width = _SettingsService.AppSettings.WindowWidth
            Height = _SettingsService.AppSettings.WindowHeight
            Left = _SettingsService.AppSettings.WindowLeft
            Top = _SettingsService.AppSettings.WindowTop
            If _SettingsService.AppSettings.WindowState = Core.Settings.WindowState.Maximized Then
                WindowState = Core.Settings.WindowState.Maximized
            Else
                WindowState = Core.Settings.WindowState.Normal
            End If
        End If


    End Sub


    Private _isOnHomePage As Boolean

    Private Sub OnNavigated(sender As NavigationView, args As NavigatedEventArgs)
        If args.Page.GetType Is GetType(HomePage) Then
            _isOnHomePage = True
            _MainWindowViewModel.IsActive = True
            HVPropertyChanged(Application.GetService(Of HomeViewModel)(), New PropertyChangedEventArgs(NameOf(HomeViewModel.HomeViewIsFresh)))
        Else
            _isOnHomePage = False
            _MainWindowViewModel.IsActive = False
            ProgTitle.Visibility = Visibility.Visible
        End If
    End Sub

    Private Sub HVPropertyChanged(sender As Object, e As PropertyChangedEventArgs)

        Dim homeVM As HomeViewModel = CType(sender, HomeViewModel)


        If _isOnHomePage AndAlso e.PropertyName = NameOf(homeVM.HomeViewIsFresh) Then
            If homeVM.HomeViewIsFresh Then
                ProgTitle.Visibility = Visibility.Collapsed
            Else
                ProgTitle.Visibility = Visibility.Visible
            End If
        End If
    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub SetServiceProvider(serviceProvider As IServiceProvider) Implements INavigationWindow.SetServiceProvider
        Throw New NotImplementedException()
    End Sub

    Public Sub SetPageService(navigationViewPageProvider As INavigationViewPageProvider) Implements INavigationWindow.SetPageService
        Throw New NotImplementedException()
    End Sub

    Public Sub ShowWindow() Implements INavigationWindow.ShowWindow
        Throw New NotImplementedException()
    End Sub

    Public Sub CloseWindow() Implements INavigationWindow.CloseWindow
        Throw New NotImplementedException()
    End Sub

    Public Function GetNavigation() As INavigationView Implements INavigationWindow.GetNavigation
        Throw New NotImplementedException()
    End Function

    Public Function Navigate(pageType As Type) As Boolean Implements INavigationWindow.Navigate
        Throw New NotImplementedException()
    End Function

    Private Sub MainWindow_Closing(sender As Object, e As CancelEventArgs)
        If Not IsVisible Then Return
        _SettingsService.AppSettings.WindowState = WindowState
        _SettingsService.AppSettings.WindowWidth = If(Width > 0, Width, 1300)
        _SettingsService.AppSettings.WindowHeight = If(Height > 0, Height, 700)
        _SettingsService.AppSettings.WindowLeft = Left
        _SettingsService.AppSettings.WindowTop = Top
        _SettingsService.SaveSettings()
    End Sub
End Class


================================================
FILE: CompactGUI/Messages/WatcherAddedFolderToQueueMessage.vb
================================================
Imports CommunityToolkit.Mvvm.Messaging.Messages

Public Class WatcherAddedFolderToQueueMessage : Inherits ValueChangedMessage(Of String)

    Public Sub New(value As String)
        MyBase.New(value)
    End Sub
End Class


================================================
FILE: CompactGUI/Models/CompressableFolders/CompressableFolder.vb
================================================
Imports System.Collections.ObjectModel
Imports System.IO
Imports System.Threading

Imports CommunityToolkit.Mvvm.ComponentModel

Imports CompactGUI.Core
Imports CompactGUI.Core.Settings
Imports CompactGUI.Core.WOFHelper

Imports Microsoft.Extensions.Logging

Imports PropertyChanged


'Need this abstract class so we can use it in XAML
Public MustInherit Class CompressableFolder : Inherits ObservableObject : Implements IDisposable

    <ObservableProperty> Private _FolderName As String
    <ObservableProperty> Private _DisplayName As String
    <ObservableProperty> Private _CurrentCompression As CompressionMode

    <NotifyPropertyChangedFor(NameOf(BytesSaved), NameOf(CompressionRatio))>
    <ObservableProperty> Private _FolderActionState As ActionState

    <NotifyPropertyChangedFor(NameOf(BytesSaved), NameOf(CompressionRatio))>
    <ObservableProperty> Private _UncompressedBytes As Long = 0

    <NotifyPropertyChangedFor(NameOf(BytesSaved), NameOf(CompressionRatio))>
    <ObservableProperty> Private _CompressedBytes As Long = 0

    <ObservableProperty> Private _AnalysisResults As New ObservableCollection(Of AnalysedFileDetails)
    <ObservableProperty> Private _PoorlyCompressedFiles As List(Of ExtensionResult)
    <ObservableProperty> Private _CompressionOptions As New CompressionOptions
    <ObservableProperty> Private _IsFreshlyCompressed As Boolean

    <ObservableProperty> Private _FolderBGImage As BitmapImage = Nothing


    <ObservableProperty> Private _IsGettingEstimate As Boolean = False

    <ObservableProperty> Private _WikiCompressionResults As WikiCompressionResults
    <ObservableProperty> Private _WikiPoorlyCompressedFiles As New List(Of String)


    Public ReadOnly Property BytesSaved As Long
        Get
            Return UncompressedBytes - CompressedBytes
        End Get
    End Property


    Public ReadOnly Property CompressionRatio As Decimal
        Get
            If CompressedBytes = 0 Then Return 0
            Return CompressedBytes / UncompressedBytes
        End Get
    End Property


    Public ReadOnly Property GlobalPoorlyCompressedFileCount
        Get
            If AnalysisResults Is Nothing OrElse Application.GetService(Of ISettingsService).AppSettings.NonCompressableList.Count = 0 Then Return 0
            Return AnalysisResults.Where(Function(fl) Application.GetService(Of ISettingsService).AppSettings.NonCompressableList.Contains(New IO.FileInfo(fl.FileName).Extension)).Count
        End Get
    End Property

    Public ReadOnly Property WikiPoorlyCompressedFilesCount As Integer
        Get
            If AnalysisResults Is Nothing OrElse WikiPoorlyCompressedFiles Is Nothing Then Return 0
            Return WikiPoorlyCompressedFiles.Count
        End Get
    End Property


    <ObservableProperty> Private _CompressionProgress As CompressionProgress


    Public Compressor As ICompressor
    Public Analyser As Analyser


    Public Sub NotifyPropertyChanged(name As String)
        OnPropertyChanged(name)
    End Sub


    Public Sub Dispose() Implements IDisposable.Dispose
        Compressor?.Dispose()
        Analyser?.Dispose()

        AnalysisResults?.Clear()
        PoorlyCompressedFiles?.Clear()
        WikiPoorlyCompressedFiles?.Clear()


        GC.SuppressFinalize(Me)
    End Sub
End Class



Public Enum ActionState
    Idle
    Analysing
    Working
    Results
    Paused
    Waiting
End Enum

================================================
FILE: CompactGUI/Models/CompressableFolders/CompressableFolderFactory.vb
================================================
Public Class CompressableFolderFactory
    Public Shared Async Function CreateCompressableFolder(path As String) As Task(Of CompressableFolder)
        Dim folderInfo = New IO.DirectoryInfo(path)

        If IsSteamFolder(folderInfo) Then
            Return If(Await CreateSteamFolder(folderInfo), New StandardFolder(path))
        Else
            Return New StandardFolder(path)
        End If

    End Function


    Private Shared Function IsSteamFolder(folderPath As IO.DirectoryInfo) As Boolean
        Return folderPath.Parent?.Parent?.Name.ToLowerInvariant = "steamapps"
    End Function


    Private Shared Async Function CreateSteamFolder(folderInfo As IO.DirectoryInfo) As Task(Of CompressableFolder)

        Dim SteamFolderData? = SteamACFParser.GetSteamNameAndIDFromFolder(folderInfo)

        If SteamFolderData Is Nothing Then Return Nothing

        Dim steamFolder As New SteamFolder(folderInfo.FullName, If(SteamFolderData?.GameName, folderInfo.FullName), SteamFolderData?.AppID)
        Await steamFolder.InitializeAsync()

        Return steamFolder
    End Function




End Class



================================================
FILE: CompactGUI/Models/CompressableFolders/StandardFolder.vb
================================================


Public NotInheritable Class StandardFolder : Inherits CompressableFolder

    Public Sub New(path As String)
        FolderName = path
        DisplayName = IO.Path.GetFileName(path.TrimEnd(IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar))

    End Sub

End Class


================================================
FILE: CompactGUI/Models/CompressableFolders/SteamFolder.vb
================================================

Imports System.IO
Imports System.Net.Http

Imports CommunityToolkit.Mvvm.ComponentModel

Imports CompactGUI.Core.Settings

Public Class SteamFolder : Inherits CompressableFolder


    'Steam-Specific
    <ObservableProperty> Private _SteamAppID As Integer


    Public Sub New(folderName As String, displayName As String, steamappId As Integer)
        Me.FolderName = folderName
        Me.SteamAppID = steamappId
        Me.DisplayName = displayName

        If Not CompressableFolderService.IsHDD(Me) AndAlso Core.SharedMethods.IsDirectStorageGameFolder(folderName) Then Application.GetService(Of CustomSnackBarService).ShowDirectStorageWarning(displayName)

    End Sub


    Public Async Function InitializeAsync() As Task
        Try
            Await GetSteamHeaderAsync(Me)
        Catch ex As Exception
            Debug.WriteLine($"Error getting Steam header: {ex.Message}")
        End Try
    End Function


    Public Overloads ReadOnly Property WikiPoorlyCompressedFilesCount As Integer
        Get
            If AnalysisResults Is Nothing OrElse WikiPoorlyCompressedFiles Is Nothing Then Return 0
            Return AnalysisResults.Where(Function(fl) WikiPoorlyCompressedFiles.Contains(New FileInfo(fl.FileName).Extension)).Count
        End Get
    End Property


    Public Async Function GetWikiResults() As Task
        ' Dim wikihandler = Application.GetService(Of WikiHandler)()
        Dim res = Await Application.GetService(Of IWikiService).ParseData(SteamAppID)

        WikiPoorlyCompressedFiles = res.poorlyCompressedList?.Where(Function(k) k.Value > 100 AndAlso k.Key <> "").Select(Function(k) k.Key).ToList

        WikiCompressionResults = If(res.compressionResults IsNot Nothing, New WikiCompressionResults(res.compressionResults), Nothing)
        If WikiCompressionResults Is Nothing Then Return

        Dim tempX4KLvl = WikiCompressionResults.XPress4K.CompressionPercent
        WikiCompressionResults.XPress4K.BeforeBytes = UncompressedBytes
        WikiCompressionResults.XPress4K.AfterBytes = UncompressedBytes * tempX4KLvl / 100

        Dim tempX8KLvl = WikiCompressionResults.XPress8K.CompressionPercent
        WikiCompressionResults.XPress8K.BeforeBytes = UncompressedBytes
        WikiCompressionResults.XPress8K.AfterBytes = UncompressedBytes * tempX8KLvl / 100

        Dim tempX16KLvl = WikiCompressionResults.XPress16K.CompressionPercent
        WikiCompressionResults.XPress16K.BeforeBytes = UncompressedBytes
        WikiCompressionResults.XPress16K.AfterBytes = UncompressedBytes * tempX16KLvl / 100

        Dim tempLZXLvl = WikiCompressionResults.LZX.CompressionPercent
        WikiCompressionResults.LZX.BeforeBytes = UncompressedBytes
        WikiCompressionResults.LZX.AfterBytes = UncompressedBytes * tempLZXLvl / 100

    End Function

    Public Shared Async Function GetSteamHeaderAsync(folder As SteamFolder) As Task

        If folder.SteamAppID = 0 Then Return

        Dim tempImg As BitmapImage = Nothing

        Dim EnvironmentPath = Environment.GetEnvironmentVariable("IridiumIO", EnvironmentVariableTarget.User)
        Dim imageDir = Path.Combine(EnvironmentPath, "CompactGUI", "SteamCache")
        Dim imagePath = Path.Combine(imageDir, $"{folder.SteamAppID}.jpg")

        If Not Directory.Exists(imageDir) Then Directory.CreateDirectory(imageDir)

        If File.Exists(imagePath) Then
            tempImg = LoadImageFromDisk(imagePath)
            Debug.WriteLine("Loaded Steam header image from disk")
        Else

            Dim url As String = $"https://steamcdn-a.akamaihd.net/steam/apps/{folder.SteamAppID}/page_bg_generated_v6b.jpg"
            'If FolderBGImage?.UriSource IsNot Nothing AndAlso FolderBGImage.UriSource.ToString() = url Then Return

            Try
                Using client As New HttpClient()
                    Dim imageData As Byte() = Await client.GetByteArrayAsync(url)
                    tempImg = LoadImageFromMemoryStream(imageData)
                    Await File.WriteAllBytesAsync(imagePath, imageData)
                End Using
            Catch ex As Exception
                Debug.WriteLine($"Failed to load Steam header image: {ex.Message}")
            End Try

        End If

        Application.Current.Dispatcher.Invoke(Sub()
                                                  If tempImg IsNot Nothing Then
                                                      folder.FolderBGImage = tempImg
                                                  End If
                                              End Sub)


    End Function


End Class


================================================
FILE: CompactGUI/Models/CompressionResult.vb
================================================

Imports CommunityToolkit.Mvvm.ComponentModel

Public Class CompressionResult : Inherits ObservableObject

    <ObservableProperty> Private _CompType As Core.CompressionMode

    <NotifyPropertyChangedFor(NameOf(CompressionSavings), NameOf(CompressionSavings), NameOf(BytesSaved), NameOf(IsNonExistent))>
    <ObservableProperty> Private _BeforeBytes As Long = 0

    <NotifyPropertyChangedFor(NameOf(CompressionSavings), NameOf(CompressionSavings), NameOf(BytesSaved), NameOf(IsNonExistent))>
    <ObservableProperty> Private _AfterBytes As Long = 0

    <ObservableProperty> Private _TotalResults As Integer = 0


    Public ReadOnly Property CompressionPercent As Integer
        Get
            If BeforeBytes = 0 OrElse AfterBytes = 0 Then Return 0
            Return Math.Round((AfterBytes / BeforeBytes) * 100, 2)
        End Get
    End Property

    Public ReadOnly Property CompressionSavings As Integer
        Get
            If BeforeBytes = 0 OrElse AfterBytes = 0 Then Return 0
            Return Math.Round((BeforeBytes - AfterBytes) / BeforeBytes * 100, 2)
        End Get
    End Property

    Public ReadOnly Property BytesSaved As Long
        Get
            Return BeforeBytes - AfterBytes
        End Get
    End Property

    Private ReadOnly Property IsNonExistent As Boolean
        Get
            Return BeforeBytes = 0 OrElse AfterBytes = 0
        End Get
    End Property

End Class



================================================
FILE: CompactGUI/Models/NewModels/CompressionOptions.vb
================================================
Imports CommunityToolkit.Mvvm.ComponentModel

Public Class CompressionOptions : Inherits ObservableObject
    <ObservableProperty> Private _SelectedCompressionMode As Core.CompressionMode = Core.CompressionMode.XPRESS4K
    <ObservableProperty> Private _SkipPoorlyCompressedFileTypes As Boolean
    <ObservableProperty> Private _SkipUserSubmittedFiletypes As Boolean
    <ObservableProperty> Private _WatchFolderForChanges As Boolean


    Public Function Clone() As CompressionOptions
        Dim copy As New CompressionOptions With {
            .SelectedCompressionMode = SelectedCompressionMode,
            .SkipPoorlyCompressedFileTypes = SkipPoorlyCompressedFileTypes,
            .SkipUserSubmittedFiletypes = SkipUserSubmittedFiletypes,
            .WatchFolderForChanges = WatchFolderForChanges
        }

        Return copy
    End Function

End Class


================================================
FILE: CompactGUI/Models/NewModels/DatabaseCompressionResult.vb
================================================
Imports CommunityToolkit.Mvvm.ComponentModel

Public Class DatabaseCompressionResult : Inherits ObservableObject

    <ObservableProperty> Private _GameName As String
    <ObservableProperty> Private _SteamID As Integer
    <ObservableProperty> Private _Confidence As DBResultConfidence

    <ObservableProperty> Private _Result_X4K As CompressionResult
    <ObservableProperty> Private _Result_X8K As CompressionResult
    <ObservableProperty> Private _Result_X16K As CompressionResult
    <ObservableProperty> Private _Result_LZX As CompressionResult

    <ObservableProperty> Private _PoorlyCompressedExtensions As List(Of DBPoorlyCompressedExtension)

    Public ReadOnly Property MaxSavings As Decimal
        Get
            Return Math.Max(
            Math.Max(
                If(Result_X4K IsNot Nothing, Result_X4K.CompressionSavings, 0D),
                If(Result_X8K IsNot Nothing, Result_X8K.CompressionSavings, 0D)
            ),
            Math.Max(
                If(Result_X16K IsNot Nothing, Result_X16K.CompressionSavings, 0D),
                If(Result_LZX IsNot Nothing, Result_LZX.CompressionSavings, 0D)
            )
        )
        End Get
    End Property


End Class

Public Enum DBResultConfidence
    Low = 0
    Medium = 1
    High = 2
End Enum

Public Structure DBPoorlyCompressedExtension
    Public Property Extension As String
    Public Property Count As Integer
End Structure

================================================
FILE: CompactGUI/Models/NewModels/WikiCompressionResults.vb
================================================
Imports CommunityToolkit.Mvvm.ComponentModel

Public Class WikiCompressionResults : Inherits ObservableObject
    <ObservableProperty> Private _XPress4K As New CompressionResult With {.CompType = Core.CompressionMode.XPRESS4K}
    <ObservableProperty> Private _XPress8K As New CompressionResult With {.CompType = Core.CompressionMode.XPRESS8K}
    <ObservableProperty> Private _XPress16K As New CompressionResult With {.CompType = Core.CompressionMode.XPRESS16K}
    <ObservableProperty> Private _LZX As New CompressionResult With {.CompType = Core.CompressionMode.LZX}

    Sub New(compressionResults As List(Of CompressionResult))
        For Each result In compressionResults
            Select Case result.CompType
                Case Core.CompressionMode.XPRESS4K
                    XPress4K = result
                Case Core.CompressionMode.XPRESS8K
                    XPress8K = result
                Case Core.CompressionMode.XPRESS16K
                    XPress16K = result
                Case Core.CompressionMode.LZX
                    LZX = result
            End Select
        Next

    End Sub


End Class



================================================
FILE: CompactGUI/Models/SemVersion.vb
================================================
Public Class SemVersion : Implements IComparable(Of SemVersion)
    Property Major As Integer
    Property Minor As Integer
    Property Patch As Integer
    Property PreRelease As String
    Property PreReleaseMinor As Integer

    Sub New()
    End Sub

    Sub New(major As Integer, minor As Integer, patch As Integer)
        Me.Major = major
        Me.Minor = minor
        Me.Patch = patch
    End Sub

    Sub New(major As Integer, minor As Integer, patch As Integer, prerelease As String, prereleaseminor As Integer)
        Me.Major = major
        Me.Minor = minor
        Me.Patch = patch
        Me.PreRelease = prerelease.ToLower
        Me.PreReleaseMinor = prereleaseminor
    End Sub

    Public Function CompareTo(other As SemVersion) As Integer Implements IComparable(Of SemVersion).CompareTo
        If other.Major - Major <> 0 Then Return other.Major - Major
        If other.Minor - Minor <> 0 Then Return other.Minor - Minor
        If other.Patch - Patch <> 0 Then Return other.Patch - Patch
        If Not String.Equals(PreRelease, other.PreRelease) Then
            If PreRelease = "" Then Return -1
            If other.PreRelease = "" Then Return 1
            Return String.Compare(other.PreRelease, PreRelease)
        End If
        If other.PreReleaseMinor - PreReleaseMinor <> 0 Then Return other.PreReleaseMinor - PreReleaseMinor
        Return 0

    End Function

    Public Shared Operator <(lhs As SemVersion, rhs As SemVersion) As Boolean
        Dim comparer = lhs.CompareTo(rhs)
        If comparer <= 0 Then Return False
        Return True
    End Operator

    Public Shared Operator >(lhs As SemVersion, rhs As SemVersion) As Boolean
        Dim comparer = lhs.CompareTo(rhs)
        If comparer >= 0 Then Return False
        Return True
    End Operator

    Public Overrides Function ToString() As String
        Return $"{Major}.{Minor}.{Patch}-{PreRelease}.{PreReleaseMinor}"
    End Function

    Public Function IsPreRelease() As Boolean
        If PreRelease = "" OrElse PreRelease = Nothing OrElse PreRelease = "r" Then Return False
        Return True
    End Function

    Public Function Friendly() As String
        Return If(PreRelease = "" OrElse PreRelease = Nothing OrElse PreRelease = "r",
            $"{Major}.{Minor}.{Patch}",
            $"{Major}.{Minor}.{Patch} {PreRelease} {PreReleaseMinor}")

    End Function

End Class


================================================
FILE: CompactGUI/Models/SteamACFResult.vb
================================================
Public Structure SteamACFResult
    Public AppID As Integer
    Public GameName As String
    Public InstallDirectory As String

    ' Special placeholder for "no result"
    Public Shared ReadOnly NoResult As New SteamACFResult With {
        .AppID = -1,
        .GameName = String.Empty,
        .InstallDirectory = String.Empty
    }

End Structure

================================================
FILE: CompactGUI/Models/SteamResultsData.vb
================================================

' Object to get results from existing wiki file
Public Class SteamResultsData

    Public SteamID As Integer
    Public GameName As String
    Public FolderName As String
    Public Confidence As Integer '0=Low, 1=Moderate, 2=High
    Public CompressionResults As New List(Of CompressionResult)
    Public PoorlyCompressedExtensions As Dictionary(Of String, Integer)

End Class



================================================
FILE: CompactGUI/Models/SteamSubmissionData.vb
================================================

' Object used to build submission data to send online after compression
Public Class SteamSubmissionData
    Public Property UID As String
    Public Property SteamID As Integer
    Public Property GameName As String
    Public Property FolderName As String
    Public Property CompressionMode As Integer
    Public Property BeforeBytes As Long
    Public Property AfterBytes As Long
    Public Property PoorlyCompressedExt As List(Of Core.ExtensionResult)

End Class



================================================
FILE: CompactGUI/My Project/Application.myapp
================================================
<?xml version="1.0" encoding="utf-8"?>
<MyApplicationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <MySubMain>true</MySubMain>
  <MainForm>Form1</MainForm>
  <SingleInstance>false</SingleInstance>
  <ShutdownMode>0</ShutdownMode>
  <EnableVisualStyles>true</EnableVisualStyles>
  <AuthenticationMode>0</AuthenticationMode>
  <SaveMySettingsOnExit>true</SaveMySettingsOnExit>
</MyApplicationData>

================================================
FILE: CompactGUI/My Project/app.manifest
================================================
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC Manifest Options
             If you want to change the Windows User Account Control level replace the 
             requestedExecutionLevel node with one of the following.

        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

            Specifying requestedExecutionLevel element will disable file and registry virtualization. 
            Remove this element if your application requires this virtualization for backwards
            compatibility.
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->

      <!-- Windows Vista -->
      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->

      <!-- Windows 7 -->
      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->

      <!-- Windows 8 -->
      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->

      <!-- Windows 8.1 -->
      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->

      <!-- Windows 10 -->
      <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->

    </application>
  </compatibility>

  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. 
       
       Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
  <!--
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
    </windowsSettings>
  </application>
  -->

  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
  <!--
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
          type="win32"
          name="Microsoft.Windows.Common-Controls"
          version="6.0.0.0"
          processorArchitecture="*"
          publicKeyToken="6595b64144ccf1df"
          language="*"
        />
    </dependentAssembly>
  </dependency>
  -->

</assembly>


================================================
FILE: CompactGUI/Services/ApplicationHostService.vb
================================================
Imports CompactGUI.Core.Settings

Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Imports Microsoft.Extensions.Logging

Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Windows

Public Class ApplicationHostService
    Implements IHostedService

    Private ReadOnly _serviceProvider As IServiceProvider
    Private ReadOnly _settingsService As ISettingsService

    Public Sub New(serviceProvider As IServiceProvider, settingsService As ISettingsService)
        _serviceProvider = serviceProvider
        _settingsService = settingsService
    End Sub

    ''' <summary>
    ''' Triggered when the application host is ready to start the service.
    ''' </summary>
    ''' <param name="cancellationToken">Indicates that the start process has been aborted.</param>
    Public Async Function StartAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StartAsync
        Await HandleActivationAsync()
    End Function

    ''' <summary>
    ''' Triggered when the application host is performing a graceful shutdown.
    ''' </summary>
    ''' <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
    Public Async Function StopAsync(cancellationToken As CancellationToken) As Task Implements IHostedService.StopAsync
        Await Task.CompletedTask
    End Function

    ''' <summary>
    ''' Creates main window during activation.
    ''' </summary>
    Private Async Function HandleActivationAsync() As Task
        Await Task.CompletedTask

        Application.GetService(Of ILogger(Of ApplicationHostService))().LogInformation("Logging Level: {LogLevel}", _settingsService.AppSettings.LogLevel)


        Dim args As String() = Environment.GetCommandLineArgs()

        Dim shouldMinimizeToTray As Boolean = (args.Length = 2 AndAlso args(1).ToString = "-tray") OrElse
                                          (_settingsService.AppSettings.StartInSystemTray AndAlso args.Length = 1)

        If Not Application.Current.Windows.OfType(Of MainWindow)().Any() Then

            Dim navigationWindow = _serviceProvider.GetRequiredService(Of MainWindow)()
            AddHandler navigationWindow.Loaded, AddressOf OnNavigationWindowLoaded
            navigationWindow.Show()

            If shouldMinimizeToTray Then
                Application.GetService(Of MainWindowViewModel).ClosingCommand.Execute(New ComponentModel.CancelEventArgs(True))
            ElseIf args.Length = 2 Then
                Await Application.GetService(Of HomeViewModel).AddFoldersAsync({args(1)})
            End If


        End If


    End Function

    Private Sub OnNavigationWindowLoaded(sender As Object, e As RoutedEventArgs)
        If TypeOf sender IsNot MainWindow Then
            Return
        End If

        Dim navigationWindow = DirectCast(sender, MainWindow)
        navigationWindow.NavigationView.Navigate(GetType(HomePage))
    End Sub
End Class

================================================
FILE: CompactGUI/Services/CompressableFolderService.vb
================================================
Imports System.Collections.ObjectModel
Imports System.Threading

Imports CompactGUI.Core
Imports CompactGUI.Core.Settings

Imports Microsoft.CodeAnalysis.Diagnostics

Imports Microsoft.Extensions.Logging

Public Class CompressableFolderService


    Private Shared ReadOnly CompactorLogger As ILogger = Application.GetService(Of ILogger(Of Compactor))()
    Private Shared ReadOnly UncompactorLogger As ILogger = Application.GetService(Of ILogger(Of Uncompactor))()
    Private Shared ReadOnly AnalyserLogger As ILogger = Application.GetService(Of ILogger(Of Analyser))()

    Private folderTokens As New Dictionary(Of CompressableFolder, CancellationTokenSource)


    Public Async Function CompressFolder(folder As CompressableFolder) As Task(Of Boolean)
        folder.Compressor = New Compactor(folder.FolderName, WOFHelper.WOFConvertCompressionLevel(folder.CompressionOptions.SelectedCompressionMode), GetSkipList(folder), folder.Analyser, CompactorLogger)
        Return Await RunCompressionAsync(folder, folder.Compressor, Nothing, True)


    End Function


    Public Async Function UncompressFolder(folder As CompressableFolder) As Task(Of Boolean)

        folder.Compressor = New Uncompactor(UncompactorLogger)
        Dim compressedFilesList = folder.AnalysisResults.Where(Function(rs) rs.CompressedSize < rs.UncompressedSize).Select(Of String)(Function(f) f.FileName).ToList
        Return Await RunCompressionAsync(folder, folder.Compressor, compressedFilesList, isCompressing:=False)


    End Function

    Private Async Function RunCompressionAsync(folder As CompressableFolder, compressor As ICompressor, filesList As List(Of String), isCompressing As Boolean) As Task(Of Boolean)
        folder.FolderActionState = ActionState.Working

        CancelEstimation(folder)
        Dim cts = New CancellationTokenSource()
        folderTokens(folder) = cts
        Dim progress As IProgress(Of CompressionProgress) = New Progress(Of CompressionProgress)(Sub(x) folder.CompressionProgress = x)

        progress.Report(New CompressionProgress(0, ""))

        Dim res = Await compressor.RunAsync(filesList, progress, GetThreadCount(folder))

        If isCompressing Then
            folder.FolderActionState = ActionState.Results
            folder.IsFreshlyCompressed = res
        Else
            folder.FolderActionState = ActionState.Idle
            folder.IsFreshlyCompressed = False
            Await AnalyseFolderAsync(folder)
        End If
        compressor.Dispose()


        folderTokens(folder).Dispose()
        folderTokens.Remove(folder)


        Return res
    End Function


    Public Async Function AnalyseFolderAsync(folder As CompressableFolder) As Task(Of Integer)

        folder.FolderActionState = ActionState.Analysing
        CancelEstimation(folder)

        Dim cts = New CancellationTokenSource()
        folderTokens(folder) = cts
        Dim token = cts.Token


        folder.Analyser?.Dispose()
        folder.Analyser = New Analyser(folder.FolderName, AnalyserLogger)

        If Not Core.SharedMethods.HasDirectoryWritePermission(folder.FolderName) Then
            folder.FolderActionState = ActionState.Idle
            Return -1
        End If

        Dim retAnalysisResults = Await folder.Analyser.GetAnalysedFilesAsync(token)
        If cts.IsCancellationRequested Then
            folder.FolderActionState = ActionState.Idle
            Return 1
        End If

        folder.AnalysisResults = New ObservableCollection(Of AnalysedFileDetails)(retAnalysisResults)
        folder.UncompressedBytes = folder.Analyser.UncompressedBytes
        folder.CompressedBytes = folder.Analyser.CompressedBytes

        If folder.Analyser.ContainsCompressedFiles OrElse folder.IsFreshlyCompressed Then
            folder.FolderActionState = ActionState.Results
        Else
            folder.FolderActionState = ActionState.Idle
        End If
        folder.PoorlyCompressedFiles = folder.Analyser.GetPoorlyCompressedExtensions()

        Return 0

    End Function

    Public Overridable Async Function GetEstimatedCompression(folder As CompressableFolder) As Task
        folder.IsGettingEstimate = True

        CancelEstimation(folder)
        Dim cts = New CancellationTokenSource()
        folderTokens(folder) = cts

        Dim estimator As New Estimator
        Dim estimatedData As List(Of (AnalysedFile As AnalysedFileDetails, CompressionRatio As Single)) = Nothing

        Try
            estimatedData = Await Task.Run(Function() estimator.EstimateCompression(folder.AnalysisResults.ToList, IsHDD(folder), GetThreadCount(folder), Core.SharedMethods.GetClusterSize(folder.FolderName), cts.Token))

        Catch ex As AggregateException
            folder.IsGettingEstimate = False
            Return
        End Try

        For Each item In estimatedData
            If item.CompressionRatio >= 0.98 AndAlso item.AnalysedFile.FileName <> "" Then
                folder.WikiPoorlyCompressedFiles.Add(item.AnalysedFile.FileName)
            End If
        Next

        Dim estimatedAfterBytes = estimatedData.Sum(Function(x) x.AnalysedFile.UncompressedSize * x.CompressionRatio)

        'This is absolutely stupid

        Dim X4KResult As New CompressionResult
        X4KResult.CompType = CompressionMode.XPRESS4K
        X4KResult.BeforeBytes = folder.UncompressedBytes
        X4KResult.AfterBytes = Math.Min(estimatedAfterBytes * 1.01, folder.UncompressedBytes)
        X4KResult.TotalResults = 1

        Dim X8KResult As New CompressionResult
        X8KResult.CompType = CompressionMode.XPRESS8K
        X8KResult.BeforeBytes = folder.UncompressedBytes
        X8KResult.AfterBytes = Math.Min(estimatedAfterBytes * 1.0, folder.UncompressedBytes)
        X8KResult.TotalResults = 1

        Dim X16KResult As New CompressionResult
        X16KResult.CompType = CompressionMode.XPRESS16K
        X16KResult.BeforeBytes = folder.UncompressedBytes
        X16KResult.AfterBytes = Math.Min(estimatedAfterBytes * 0.98, folder.UncompressedBytes)
        X16KResult.TotalResults = 1

        Dim LZXResult As New CompressionResult
        LZXResult.CompType = CompressionMode.LZX
        LZXResult.BeforeBytes = folder.UncompressedBytes
        LZXResult.AfterBytes = Math.Min(estimatedAfterBytes * 0.95, folder.UncompressedBytes)
        LZXResult.TotalResults = 1

        folder.WikiCompressionResults = New WikiCompressionResults(New List(Of CompressionResult) From {X4KResult, X8KResult, X16KResult, LZXResult})

        folder.IsGettingEstimate = False


        folder.NotifyPropertyChanged(NameOf(folder.WikiCompressionResults))
        folder.NotifyPropertyChanged(NameOf(folder.WikiPoorlyCompressedFiles))
        folder.NotifyPropertyChanged(NameOf(folder.WikiPoorlyCompressedFilesCount))
        folder.NotifyPropertyChanged(NameOf(folder.IsGettingEstimate))

    End Function
    Public Sub CancelEstimation(folder As CompressableFolder)
        If folderTokens.ContainsKey(folder) AndAlso Not folderTokens(folder).IsCancellationRequested Then
            folderTokens(folder).Cancel()
        End If
    End Sub


    Public Shared Function GetThreadCount(folder As CompressableFolder) As Integer
        Dim threadCount As Integer = Application.GetService(Of ISettingsService).AppSettings.MaxCompressionThreads
        If Application.GetService(Of ISettingsService).AppSettings.LockHDDsToOneThread Then
            Dim HDDType As DiskDetector.Models.HardwareType = GetDiskType(folder)
            If HDDType = DiskDetector.Models.HardwareType.Hdd Then
                threadCount = 1
            End If
        End If
        Return threadCount
    End Function

    Public Shared Function GetDiskType(folder As CompressableFolder) As DiskDetector.Models.HardwareType
        If folder.FolderName Is Nothing Then Return DiskDetector.Models.HardwareType.Unknown
        Try
            Return DiskDetector.Detector.DetectDrive(folder.FolderName.First, DiskDetector.Models.QueryType.RotationRate).HardwareType
        Catch ex As Exception
            Return DiskDetector.Models.HardwareType.Unknown
        End Try
    End Function

    Public Shared Function IsHDD(folder As CompressableFolder) As Boolean
        Dim HDDType As DiskDetector.Models.HardwareType = GetDiskType(folder)
        Return HDDType = DiskDetector.Models.HardwareType.Hdd
    End Function

    Private Function GetSkipList(folder As CompressableFolder) As String()
        Dim exclist As String() = Array.Empty(Of String)()

        If folder.CompressionOptions.SkipPoorlyCompressedFileTypes AndAlso Application.GetService(Of ISettingsService).AppSettings.NonCompressableList.Count <> 0 Then
            'Debug.WriteLine("Adding non-compressable list to exclusion list")
            exclist = exclist.Union(Application.GetService(Of ISettingsService).AppSettings.NonCompressableList).ToArray
        End If
        If folder.CompressionOptions.SkipUserSubmittedFiletypes AndAlso folder.WikiPoorlyCompressedFiles?.Count <> 0 Then
            'Debug.WriteLine("Adding estimator poorly compressed list to exclusion list")
            exclist = exclist.Union(folder.WikiPoorlyCompressedFiles).ToArray
        End If

        Return exclist
    End Function


End Class


================================================
FILE: CompactGUI/Services/CustomSnackBarService.vb
================================================
Imports CommunityToolkit.Mvvm.Input

Imports CompactGUI.Core.SharedMethods
Imports CompactGUI.Logging

Imports Microsoft.Extensions.Logging

Imports Wpf.Ui.Controls

Public Class CustomSnackBarService
    Inherits Wpf.Ui.SnackbarService

    Private ReadOnly logger As ILogger(Of CustomSnackBarService)
    Public _snackbar As Snackbar

    Public Sub New(logger As ILogger(Of CustomSnackBarService))
        MyBase.New()
        Me.logger = logger
    End Sub

    Public Sub ShowCustom(message As UIElement, title As String, appearance As ControlAppearance, Optional icon As IconElement = Nothing, Optional timeout As TimeSpan = Nothing)

        If GetSnackbarPresenter() Is Nothing Then Throw New InvalidOperationException("The SnackbarPresenter was never set")
        If _snackbar Is Nothing Then _snackbar = New Snackbar(GetSnackbarPresenter())

        _snackbar.SetCurrentValue(Snackbar.TitleProperty, title)
        _snackbar.SetCurrentValue(ContentControl.ContentProperty, message)
        _snackbar.SetCurrentValue(Snackbar.AppearanceProperty, appearance)
        _snackbar.SetCurrentValue(Snackbar.IconProperty, icon)
        _snackbar.SetCurrentValue(Snackbar.TimeoutProperty, If(timeout = Nothing, DefaultTimeOut, timeout))

        _snackbar.Show(True)
    End Sub



    Public Sub ShowInvalidFoldersMessage(InvalidFolders As List(Of String), InvalidMessages As List(Of FolderVerificationResult))

        Dim messageString = ""
        For i = 0 To InvalidFolders.Count - 1
            SnackbarServiceLog.ShowInvalidFoldersMessage(logger, InvalidFolders(i), GetFolderVerificationMessage(InvalidMessages(i)))
            If InvalidFolders.Count = 1 AndAlso InvalidMessages(i) = FolderVerificationResult.InsufficientPermission Then
                ShowInsufficientPermission(InvalidFolders(i))
                Return
            End If
            messageString &= $"{InvalidFolders(i)}: {GetFolderVerificationMessage(InvalidMessages(i))}" & vbCrLf
        Next

        Show("Invalid Folders", messageString, Wpf.Ui.Controls.ControlAppearance.Danger, Nothing, TimeSpan.FromSeconds(10))

    End Sub

    Public Sub ShowInsufficientPermission(folderName As String)
        Dim button = New Button With {
            .Content = "Restart as Admin",
            .Command = New RelayCommand(Sub() RunAsAdmin(folderName)),
            .Margin = New Thickness(-3, 10, 0, 0)
        }
        ShowCustom(button, "Insufficient permission to access this folder.", ControlAppearance.Danger, timeout:=TimeSpan.FromSeconds(60))
    End Sub

    Public Sub ShowUpdateAvailable(newVersion As String, isPreRelease As Boolean)
        Dim textBlock = New TextBlock
        textBlock.Text = "Click to download"

        ' Show the custom snackbar
        SnackbarServiceLog.ShowUpdateAvailable(logger, newVersion, isPreRelease)
        ShowCustom(textBlock, $"Update Available ▸ Version {newVersion}", If(isPreRelease, ControlAppearance.Info, ControlAppearance.Success), timeout:=TimeSpan.FromSeconds(10))

        Dim handler As MouseButtonEventHandler = Nothing
        Dim closedHandler As TypedEventHandler(Of Snackbar, RoutedEventArgs) = Nothing

        handler = Sub(sender, e)
                      Process.Start(New ProcessStartInfo("https://github.com/IridiumIO/CompactGUI/releases/") With {.UseShellExecute = True})
                      RemoveHandler Me.GetSnackbarPresenter.MouseDown, handler
                      RemoveHandler Me._snackbar.Closed, closedHandler
                  End Sub

        closedHandler = Sub(sender, e)
                            RemoveHandler Me.GetSnackbarPresenter.MouseDown, handler
                            RemoveHandler Me._snackbar.Closed, closedHandler
                        End Sub

        AddHandler Me.GetSnackbarPresenter.MouseDown, handler
        AddHandler Me._snackbar.Closed, closedHandler
    End Sub

    Public Sub ShowFailedToSubmitToWiki()
        Show("Failed to submit to wiki", "Please check your internet connection and try again", Wpf.Ui.Controls.ControlAppearance.Danger, Nothing, TimeSpan.FromSeconds(5))
        SnackbarServiceLog.ShowFailedToSubmitToWiki(logger)
    End Sub

    Public Sub ShowSubmittedToWiki(steamsubmitdata As SteamSubmissionData, compressionMode As Integer)
        Show("Submitted to wiki", $"UID: {steamsubmitdata.UID}{vbCrLf}Game: {steamsubmitdata.GameName}{vbCrLf}SteamID: {steamsubmitdata.SteamID}{vbCrLf}Compression: {[Enum].GetName(GetType(Core.WOFCompressionAlgorithm), Core.WOFHelper.WOFConvertCompressionLevel(compressionMode))}", Wpf.Ui.Controls.ControlAppearance.Success, Nothing, TimeSpan.FromSeconds(10))
        SnackbarServiceLog.ShowSubmittedToWiki(logger, steamsubmitdata.UID, steamsubmitdata.GameName, steamsubmitdata.SteamID, steamsubmitdata.CompressionMode)
    End Sub


    Public Sub ShowAppliedToAllFolders()
        Show("Applied to all folders", "Compression options have been applied to all folders", Wpf.Ui.Controls.ControlAppearance.Success, Nothing, TimeSpan.FromSeconds(5))
        SnackbarServiceLog.ShowAppliedToAllFolders(logger)
    End Sub

    Public Sub ShowCannotRemoveFolder()
        Show("Cannot remove folder", "Please wait until the current operation is finished", Wpf.Ui.Controls.ControlAppearance.Caution, Nothing, TimeSpan.FromSeconds(5))
        SnackbarServiceLog.ShowCannotRemoveFolder(logger)
    End Sub

    Public Sub ShowAddedToQueue()
        Show("Success", "Added to Queue", Wpf.Ui.Controls.ControlAppearance.Success, Nothing, TimeSpan.FromSeconds(5))
        SnackbarServiceLog.ShowAddedToQueue(logger)
    End Sub

    Public Sub ShowDirectStorageWarning(displayName As String)
        Show(displayName,
            "This game uses DirectStorage technology. If you are using this feature, you should not compress this game.",
            Wpf.Ui.Controls.ControlAppearance.Info,
            Nothing,
            TimeSpan.FromSeconds(20))
        SnackbarServiceLog.ShowDirectStorageWarning(logger, displayName)
    End Sub
End Class

================================================
FILE: CompactGUI/Services/SchedulerService.vb
================================================
Imports CompactGUI.Core.Settings
Imports CompactGUI.Logging
Imports CompactGUI.Watcher

Imports Coravel.Scheduling.Schedule
Imports Coravel.Scheduling.Schedule.Interfaces

Imports Microsoft.Extensions.Logging

Public Class SchedulerService
    Private ReadOnly settings As Settings
    Private ReadOnly idleDetector As IdleDetector
    Private ReadOnly logger As ILogger(Of SchedulerService)

    Public Sub New(settingsService As ISettingsService, idleDetector As IdleDetector, logger As ILogger(Of SchedulerService))
        Me.settings = settingsService.AppSettings
        Me.idleDetector = idleDetector
        Me.logger = logger

    End Sub

    Friend Sub RegenerateSchedule()
        Dim scheduler = CType(Application.GetService(Of IScheduler), Scheduler)
        If Not scheduler.TryUnschedule(NameOf(Watcher)) Then Return

        scheduler.ScheduleAsync(Async Function() Await RunScheduledTask()).
                                Cron($"{settings.ScheduledBackgroundMinute} {settings.ScheduledBackgroundHour} * * *").Zoned(TimeZoneInfo.Local).
                                When(Function() Task.FromResult(IsSchedulerRunnable)).
                                PreventOverlapping(NameOf(Watcher))

    End Sub


    Public Async Function RunScheduledTask() As Task(Of Boolean)

        Dim trayService = Application.GetService(Of TrayNotifierService)
        trayService.Notify_BackgroundSchedulerRunning()
        Dim task = Await Application.GetService(Of Watcher.Watcher).RunWatcher()

        If task Then
            trayService.Notify_BackgroundSchedulerCompleted()
            Return True
        End If
        Return False

    End Function



    Public Function IsSchedulerRunnable() As Boolean

        SchedulerServiceLog.CheckingSchedulerRunnable(logger)

        If Not settings.EnableBackgroundWatcher Then
            SchedulerServiceLog.SchedulerDisabled(logger)
            Return False
        End If

        If settings.NextScheduledBackgroundRun.Date > Date.Now.Date Then
            SchedulerServiceLog.SchedulerNextRunInFuture(logger, settings.NextScheduledBackgroundRun)
            Return False
        End If

        Select Case settings.BackgroundModeSelection
            Case BackgroundMode.ScheduledAndIdle
                If idleDetector.State = IdleState.Idle Then
                    SchedulerServiceLog.SchedulerRunningIdle(logger)
                    Return True
                Else
                    SchedulerServiceLog.SchedulerNotIdle(logger)
                    Return False
                End If
            Case BackgroundMode.Scheduled
                SchedulerServiceLog.SchedulerRunningScheduled(logger)
                Return True
            Case Else
                SchedulerServiceLog.SchedulerModeDisabled(logger)
                Return False
        End Select

    End Function

End Class


================================================
FILE: CompactGUI/Services/SettingsService.vb
================================================
Imports System.IO
Imports System.Text.Json

Imports CompactGUI.Core.Settings
Imports CompactGUI.Logging

Imports Microsoft.Extensions.Logging

Public Class SettingsService : Implements ISettingsService

    Public ReadOnly Property DataFolder As DirectoryInfo Implements ISettingsService.DataFolder
    Public ReadOnly Property SettingsJSONFile As FileInfo Implements ISettingsService.SettingsJSONFile
    Public ReadOnly Property SettingsVersion As Decimal Implements ISettingsService.SettingsVersion
    Public Property AppSettings As Settings Implements ISettingsService.AppSettings

    Public Sub New()

        DataFolder = New IO.DirectoryInfo(IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "IridiumIO", "CompactGUI"))
        SettingsJSONFile = New IO.FileInfo(IO.Path.Combine(DataFolder.FullName, "settings.json"))

        SettingsVersion = 1.2

    End Sub

    Public Sub LoadSettings() Implements ISettingsService.LoadSettings
        If Not DataFolder.Exists Then DataFolder.Create()
        If Not SettingsJSONFile.Exists Then SettingsJSONFile.Create().Dispose()

        AppSettings = DeserializeAndValidateJSON(SettingsJSONFile)

        If AppSettings.SettingsVersion = 0 OrElse SettingsVersion > AppSettings.SettingsVersion Then

            Dim skipList = AppSettings.NonCompressableList

            AppSettings = New Settings With {.SettingsVersion = SettingsVersion, .NonCompressableList = skipList}

            Dim msgError As New Wpf.Ui.Controls.ContentDialog With {.Title = $"New Settings Version {SettingsVersion} Detected", .Content = "Your settings have been reset to their default to accommodate the update", .CloseButtonText = "OK"}
            msgError.ShowAsync()

        End If

        Dim output = JsonSerializer.Serialize(AppSettings, Jsonoptions)
        IO.File.WriteAllText(SettingsJSONFile.FullName, output)
    End Sub

    Public Sub SaveSettings() Implements ISettingsService.SaveSettings
        ScheduleSettingsSave()
    End Sub

    Public Sub ScheduleSettingsSave() Implements ISettingsService.ScheduleSettingsSave
        SyncLock timerLock
            If debounceTimer Is Nothing Then
                debounceTimer = New System.Timers.Timer(debounceDelay.TotalMilliseconds)
                debounceTimer.AutoReset = False
                AddHandler debounceTimer.Elapsed, Async Sub(__, ___)
                                                      Await WriteToFileAsync()
                                                  End Sub
            Else
                debounceTimer.Stop()
            End If
            debounceTimer.Start()
        End SyncLock
    End Sub


    Private ReadOnly Jsonoptions As New JsonSerializerOptions With {.IncludeFields = True, .WriteIndented = True}

    Private Function DeserializeAndValidateJSON(inputjsonFile As IO.FileInfo) As Settings
        Dim SettingsJSON = IO.File.ReadAllText(inputjsonFile.FullName)
        If SettingsJSON = "" Then SettingsJSON = "{}"

        Dim validatedSettings As Settings

        Try

            validatedSettings = JsonSerializer.Deserialize(Of Settings)(SettingsJSON, Jsonoptions)
        Catch ex As Exception
            validatedSettings = New Settings With {.SettingsVersion = SettingsVersion}

            Dim msgError As New Wpf.Ui.Controls.ContentDialog With {.Title = $"Corrupted Settings File Detected", .Content = "Your settings have been reset to their default.", .CloseButtonText = "OK"}
            msgError.ShowAsync()


        End Try

        Return validatedSettings

    End Function



    Private ReadOnly debounceDelay As TimeSpan = TimeSpan.FromMilliseconds(1000)
    Private ReadOnly timerLock As New Object()
    Private debounceTimer As System.Timers.Timer


    Private Async Function WriteToFileAsync() As Task
        Try
            Dim output = JsonSerializer.Serialize(AppSettings, Jsonoptions)
            Await IO.File.WriteAllTextAsync(SettingsJSONFile.FullName, output)
            SettingsLog.SettingsSaved(Application.GetService(Of ILogger(Of Settings)))
        Catch ex As Exception
            ' Log or handle exception
        End Try
    End Function

End Class


================================================
FILE: CompactGUI/Services/SteamACFParser.vb
================================================


Imports Gameloop.Vdf
Imports Gameloop.Vdf.Linq

Imports Microsoft.Extensions.Caching.Memory

Public Class SteamACFParser


    Private Shared ReadOnly SteamLibraryCache As New MemoryCache(New MemoryCacheOptions())

    Public Shared Function GetSteamNameAndIDFromFolder(SteamFolder As IO.DirectoryInfo) As SteamACFResult?
        Dim steamAppsFolder = SteamFolder.Parent.Parent

        Dim cachedResult = TryGetCachedGame(steamAppsFolder, SteamFolder)
        If cachedResult IsNot Nothing Then
            Return If(Not cachedResult.Equals(SteamACFResult.NoResult), cachedResult, Nothing)
        End If

        Dim allGames = LookupAllSteamGames(steamAppsFolder)
        CacheLibrary(steamAppsFolder, allGames)

        If allGames.ContainsKey(SteamFolder.Name) Then Return allGames(SteamFolder.Name)
        Return Nothing

    End Function

    Private Shared Sub CacheLibrary(steamAppsFolder As IO.DirectoryInfo, allGames As Dictionary(Of String, SteamACFResult?))
        Dim policy As New MemoryCacheEntryOptions With {
            .AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2)
        }

        SteamLibraryCache.Set(steamAppsFolder.FullName, allGames, policy)
    End Sub

    Private Shared Function TryGetCachedGame(steamAppsFolder As IO.DirectoryInfo, SteamFolder As IO.DirectoryInfo) As SteamACFResult?

        Dim libraryCache = GetCachedLibrary(steamAppsFolder)
        If libraryCache Is Nothing Then Return Nothing

        If libraryCache.ContainsKey(SteamFolder.Name) Then Return libraryCache(SteamFolder.Name)
        Return SteamACFResult.NoResult

    End Function


    Private Shared Function GetCachedLibrary(steamAppsFolder As IO.DirectoryInfo) As Dictionary(Of String, SteamACFResult?)
        Return TryCast(SteamLibraryCache.Get(steamAppsFolder.FullName), Dictionary(Of String, SteamACFResult?))
    End Function

    Private Shared Function LookupAllSteamGames(steamAppsFolder As IO.DirectoryInfo) As Dictionary(Of String, SteamACFResult?)
        Dim allGames As New Dictionary(Of String, SteamACFResult?)

        For Each fl In steamAppsFolder.EnumerateFiles("*.acf").Where(Function(f) f.Length > 0)
            Try
                Dim ACFFile = VdfConvert.Deserialize(IO.File.ReadAllText(fl.FullName))
                If ACFFile IsNot Nothing Then
                    Dim game = ParseACFFile(ACFFile)
                    allGames(game.InstallDirectory) = game
                End If
            Catch
                Debug.WriteLine($"ACF file unsupported: {fl.FullName}")
            End Try
        Next

        Return allGames
    End Function


    Private Shared Function ParseACFFile(ACFFile As VProperty) As SteamACFResult
        Dim appID = CInt(ACFFile.Value.Item("appid").ToString)
        Dim sName = ACFFile.Value.Item("name").ToString
        Dim sInstallDir = ACFFile.Value.Item("installdir").ToString
        Return New SteamACFResult With {.AppID = appID, .GameName = sName, .InstallDirectory = sInstallDir}
    End Function


End Class


================================================
FILE: CompactGUI/Services/TrayNotifierService.vb
================================================
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Interop

Imports CompactGUI.Core.Settings

Public Class TrayNotifierService
    Private Const NIM_ADD As Integer = &H0
    Private Const NIM_MODIFY As Integer = &H1
    Private Const NIM_DELETE As Integer = &H2

    Private Const NIF_MESSAGE As Integer = &H1
    Private Const NIF_ICON As Integer = &H2
    Private Const NIF_TIP As Integer = &H4
    Private Const NIF_INFO As Integer = &H10

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
    Private Structure NOTIFYICONDATA
        Public cbSize As Integer
        Public hWnd As IntPtr
        Public uID As Integer
        Public uFlags As Integer
        Public uCallbackMessage As Integer
        Public hIcon As IntPtr
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
        Public szTip As String
        Public dwState As Integer
        Public dwStateMask As Integer
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)>
        Public szInfo As String
        Public uTimeoutOrVersion As Integer
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=64)>
        Public szInfoTitle As String
        Public dwInfoFlags As Integer
        Public guidItem As Guid
        Public hBalloonIcon As IntPtr
    End Structure

    <DllImport("shell32.dll", CharSet:=CharSet.Unicode)>
    Private Shared Function Shell_NotifyIcon(dwMessage As Integer, ByRef lpdata As NOTIFYICONDATA) As Boolean
    End Function

    Private m_data As NOTIFYICONDATA
    Private m_icon As Icon

    Public Sub New(window As Window, icon As Icon, tooltip As String)

        Application.Current.Dispatcher.Invoke(
            Sub()

                Dim helper As New WindowInteropHelper(window)
                m_icon = icon
                m_data = New NOTIFYICONDATA()
                m_data.cbSize = Marshal.SizeOf(m_data)
                m_data.hWnd = helper.Handle
                m_data.uID = 1
                m_data.uFlags = NIF_MESSAGE Or NIF_ICON Or NIF_TIP
                m_data.uCallbackMessage = 0
                m_data.hIcon = icon.Handle
                m_data.szTip = tooltip
                m_data.szInfo = ""
                m_data.szInfoTitle = ""
                m_data.dwInfoFlags = 0
                m_data.guidItem = Guid.Empty
            End Sub)


    End Sub

    Public Sub ShowBalloon(title As String, message As String, Optional infoFlags As Integer = 0)

        m_data.uFlags = NIF_INFO Or NIF_ICON Or NIF_TIP
        m_data.szInfoTitle = title
        m_data.szInfo = message
        m_data.dwInfoFlags = infoFlags ' 1=Info, 2=Warning, 3=Error

        Shell_NotifyIcon(NIM_ADD, m_data)
        Shell_NotifyIcon(NIM_DELETE, m_data)
    End Sub

    Public Sub Dispose()
        Shell_NotifyIcon(NIM_DELETE, m_data)
        If m_icon IsNot Nothing Then
            m_icon.Dispose()
        End If
    End Sub

    Public Sub Notify_Compressed(DisplayName As String, BytesSaved As Long, CompressionRatio As Decimal)

        Dim title = $"{DisplayName}"
        Dim readableSaved = $"{New BytesToReadableConverter().Convert(BytesSaved, GetType(Long), Nothing, Globalization.CultureInfo.CurrentCulture)} saved"
        Dim percentCompressed = $"{100 - CInt(CompressionRatio * 100)}% smaller"
        ShowBalloon(title, $"▸ {readableSaved}{Environment.NewLine}▸ {percentCompressed}")

    End Sub

    Public Sub Notify_BackgroundSchedulerRunning()
        Dim title = "Scheduled Compression Running"
        Dim message = "CompactGUI is running a scheduled task and will compress monitored folders in the background"
        ShowBalloon(title, message)
    End Sub

    Public Sub Notify_BackgroundSchedulerCompleted()
        Dim title = "Scheduled Compression Completed"
        Dim message = $"Next scheduled task is on {Application.GetService(Of ISettingsService).AppSettings.NextScheduledBackgroundRun}"
        ShowBalloon(title, message)
    End Sub

End Class


================================================
FILE: CompactGUI/Services/UpdaterService.vb
================================================
Imports System.Net.Http
Imports System.Text.Json

Imports Wpf.Ui.Controls

Public Interface IUpdaterService
    Function CheckForUpdate(includePrerelease As Boolean) As Task
End Interface

Public Class UpdaterService : Implements IUpdaterService

    Public Shared ReadOnly UpdateURL As String = "https://raw.githubusercontent.com/IridiumIO/CompactGUI/database/version.json"
    Public Shared ReadOnly httpClient As New HttpClient()


    Public Async Function CheckForUpdate(includePrerelease As Boolean) As Task Implements IUpdaterService.CheckForUpdate
        Try
            Dim ret = Await httpClient.GetStringAsync(UpdateURL)
            Dim jVer = JsonSerializer.Deserialize(Of Dictionary(Of String, SemVersion))(ret)
            Dim newVersion As SemVersion = If(includePrerelease, jVer("Latest"), jVer("LatestNonPreRelease"))
            If newVersion > Application.AppVersion Then
                Application.GetService(Of CustomSnackBarService).ShowUpdateAvailable(newVersion.Friendly, newVersion.IsPreRelease)
            End If
        Catch ex As Exception
            Debug.WriteLine(ex.Message)
        End Try
    End Function


End Class


================================================
FILE: CompactGUI/Services/WikiService.vb
================================================
Imports System.Net.Http
Imports System.Text.Json

Imports CompactGUI.Core.Settings

Public Interface IWikiService
    Function GetUpdatedJSONAsync() As Task
    Function ParseData(appid As Integer) As Task(Of (estimatedRatio As Decimal, confidence As Integer, poorlyCompressedList As Dictionary(Of String, Integer), compressionResults As List(Of CompressionResult)))
    Function GetAllDatabaseCompressionResultsAsync() As Task(Of List(Of DatabaseCompressionResult))
    Function SubmitToWiki(folderpath As String, analysisResults As List(Of Core.AnalysedFileDetails), poorlyCompressedFiles As List(Of Core.ExtensionResult), compressionMode As Integer) As Task(Of Boolean)
    Function SubmitURLForm(url As String, submissionstring As String) As Task(Of Boolean)
End Interface

Public Class WikiService : Implements IWikiService

    Private ReadOnly filePath As String
    Private ReadOnly dlPath As String

    Private ReadOnly _settingsService As ISettingsService

    Public Sub New(settingsService As ISettingsService)
        _settingsService = settingsService
        filePath = IO.Path.Combine(_settingsService.DataFolder.FullName, "databasev2.json")

        dlPath = "https://raw.githubusercontent.com/IridiumIO/CompactGUI/database/database.json"

    End Sub

    Async Function GetUpdatedJSONAsync() As Task Implements IWikiService.GetUpdatedJSONAsync
        Debug.WriteLine("Updating JSON file")
        Dim JSONFile As New IO.FileInfo(filePath)

        If JSONFile.Exists AndAlso _settingsService.AppSettings.ResultsDBLastUpdated.AddHours(6) >= DateTime.Now Then Return

        Dim httpClient As New HttpClient

        Try

            Dim res = Await httpClient.GetStreamAsync(dlPath)

            Using fs As New IO.FileStream(JSONFile.FullName, IO.FileMode.Create)
                Await res.CopyToAsync(fs)
            End Using

        Catch ex As TaskCanceledException
            Debug.WriteLine("HTTP request timed out.")
            Return

        Catch ex As IO.IOException
            Debug.WriteLine("Could not update JSON file: file is in use.")
            Return
        Catch ex As HttpRequestException
            Debug.WriteLine($"Unable to reach endpoint. Likely no internet connection")
            Return
        Finally
            httpClient.Dispose()
        End Try


        _settingsService.AppSettings.ResultsDBLastUpdated = DateTime.Now
        _settingsService.SaveSettings()
        Debug.WriteLine("Updated JSON file")

    End Function

    Private ReadOnly JsonDefaultSettings As New JsonSerializerOptions With {.IncludeFields = True}

    Async Function ParseData(appid As Integer) As Task(Of (estimatedRatio As Decimal, confidence As Integer, poorlyCompressedList As Dictionary(Of String, Integer), compressionResults As List(Of CompressionResult))) Implements IWikiService.ParseData
        Dim JSONFile As New IO.FileInfo(filePath)
        If Not JSONFile.Exists Then Return Nothing

        Dim jStream As IO.FileStream = JSONFile.OpenRead
        Dim parsedSteamWikiResults = Await JsonSerializer.DeserializeAsync(Of List(Of SteamResultsData))(jStream, JsonDefaultSettings).ConfigureAwait(False)
        Dim workingGame = parsedSteamWikiResults.Find(Function(game) game.SteamID = appid)

        If workingGame Is Nothing Then Return Nothing
        Dim estimatedRatio As Decimal
        Dim totaldataPoints As Integer = workingGame.CompressionResults.Sum(Function(x) x.TotalResults)

        For Each compressionResult In workingGame.CompressionResults
            Dim ratio = compressionResult.AfterBytes / compressionResult.BeforeBytes
            estimatedRatio += ratio * compressionResult.TotalResults
        Next

        workingGame.CompressionResults.Sort(Function(x, y) x.CompType.CompareTo(y.CompType))

        'TODO: Adjust this return to account for selected level of aggressiveness in settings
        'Dim poorlyCompressedExt = workingGame.PoorlyCompressedExtensions.Where(Function(k) k.Value > 100).Select(Function(k) k.Key)

        estimatedRatio /= totaldataPoints
        Return (estimatedRatio, workingGame.Confidence, workingGame.PoorlyCompressedExtensions, workingGame.CompressionResults)

    End Function


    Public Async Function GetAllDatabaseCompressionResultsAsync() As Task(Of List(Of DatabaseCompressionResult)) Implements IWikiService.GetAllDatabaseCompressionResultsAsync
        Dim JSONFile As New IO.FileInfo(filePath)
        If Not JSONFile.Exists Then Return New List(Of DatabaseCompressionResult)()

        Using jStream As IO.FileStream = JSONFile.OpenRead()
            ' Deserialize the JSON into a list of SteamResultsData (or your source model)
            Dim parsedResults = Await JsonSerializer.DeserializeAsync(Of List(Of SteamResultsData))(jStream, JsonDefaultSettings).ConfigureAwait(False)
            If parsedResults Is Nothing Then Return New List(Of DatabaseCompressionResult)()

            ' Map each SteamResultsData to DatabaseCompressionResult
            Dim results As New List(Of DatabaseCompressionResult)
            For Each item In parsedResults
                Dim dbResult As New DatabaseCompressionResult With {
                .GameName = item.GameName,
                .SteamID = item.SteamID,
                .Confidence = CType(item.Confidence, DBResultConfidence),
                .Result_X4K = item.CompressionResults.FirstOrDefault(Function(r) r.CompType = 0),
                .Result_X8K = item.CompressionResults.FirstOrDefault(Function(r) r.CompType = 1),
                .Result_X16K = item.CompressionResults.FirstOrDefault(Function(r) r.CompType = 2),
                .Result_LZX = item.CompressionResults.FirstOrDefault(Function(r) r.CompType = 3),
                .PoorlyCompressedExtensions = item.PoorlyCompressedExtensions?.Select(Function(kvp) New DBPoorlyCompressedExtension With {.Extension = kvp.Key, .Count = kvp.Value}).ToList()
            }
                results.Add(dbResult)
            Next

            Return results
        End Using
    End Function





    Async Function SubmitToWiki(folderpath As String, analysisResults As List(Of Core.AnalysedFileDetails), poorlyCompressedFiles As List(Of Core.ExtensionResult), compressionMode As Integer) As Task(Of Boolean) Implements IWikiService.SubmitToWiki
        Dim wikiSubmitURI = "https://docs.google.com/forms/d/e/1FAIpQLSdQyMwHIfldsuKKdDYBE9DNEyro8bidBDInq8EafGogFu382A/formResponse?entry.1019946248=%3CCompactGUI3%3E"

        Dim ret = Await Task.Run(Function() GetSteamNameAndIDFromFolder(folderpath))

        Dim before = analysisResults.Sum(Function(res) res.UncompressedSize)
        Dim after = analysisResults.Sum(Function(res) res.CompressedSize)

        Dim steamsubmitdata As New SteamSubmissionData With {
            .UID = getUID(),
            .SteamID = ret.appID,
            .GameName = ret.gameName,
            .FolderName = ret.installDir,
            .BeforeBytes = before,
            .AfterBytes = after,
            .CompressionMode = compressionMode,
            .PoorlyCompressedExt = poorlyCompressedFiles}

        Dim jstring = JsonSerializer.Serialize(steamsubmitdata)
        Dim response = Await SubmitURLForm(wikiSubmitURI, jstring)

        Dim snackbar = Application.GetService(Of CustomSnackBarService)()
        If Not response Then
            snackbar.ShowFailedToSubmitToWiki()
            Return False
        End If

        snackbar.ShowSubmittedToWiki(steamsubmitdata, compressionMode)
        Return True

    End Function


    Async Function SubmitURLForm(url As String, submissionstring As String) As Task(Of Boolean) Implements IWikiService.SubmitURLForm
        Try

            Dim httpC As New HttpClient
            Dim resp = Await httpC.GetAsync(New Uri(url & submissionstring))
            Return resp.StatusCode
        Catch ex As Exception
            Return 0
        End Try

    End Function

End Class


================================================
FILE: CompactGUI/Services/WindowService.vb
================================================
Public Interface IWindowService
    Sub ShowMainWindow()
    Sub MinimizeMainWindow()
    Sub HideMainWindow()
    Function ShowMessageBox(title As String, content As String) As Task(Of Boolean)
End Interface


Public Class WindowService
    Implements IWindowService

    Public Sub ShowMainWindow() Implements IWindowService.ShowMainWindow
        Dim mainWindow = Application.GetService(Of MainWindow)()
        mainWindow.Show()
        mainWindow.WindowState = WindowState.Normal
        mainWindow.Topmost = True
        mainWindow.Activate()
        mainWindow.Topmost = False
    End Sub

    Public Sub MinimizeMainWindow() Implements IWindowService.MinimizeMainWindow
        Dim mainWindow = Application.GetService(Of MainWindow)()
        mainWindow.WindowState = WindowState.Minimized
    End Sub

    Public Sub HideMainWindow() Implements IWindowService.HideMainWindow
        Dim mainWindow = Application.GetService(Of MainWindow)()
        mainWindow.Hide()
    End Sub

    Public Async Function ShowMessageBox(title As String, content As String) As Task(Of Boolean) Implements IWindowService.ShowMessageBox
        Dim msgBox = New Wpf.Ui.Controls.MessageBox With {
               .Title = title,
               .Content = content,
               .IsPrimaryButtonEnabled = True,
               .PrimaryButtonText = "Yes",
               .CloseButtonText = "Cancel"
           }
        Dim result = Await msgBox.ShowDialogAsync()
        Return result = Wpf.Ui.Controls.MessageBoxResult.Primary
    End Function
End Class


================================================
FILE: CompactGUI/Themes/Generic.xaml
================================================
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CompactGUI">


    <Style TargetType="{x:Type local:TokenizedTextBox}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TokenizedTextBox}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>


================================================
FILE: CompactGUI/ViewModels/DatabaseViewModel.vb
================================================
Imports System.Collections.ObjectModel
Imports System.ComponentModel

Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input

Imports CompactGUI.Core.Settings

Public Class DatabaseViewModel : Inherits ObservableObject

    <ObservableProperty>
    Private _DatabaseResults As ObservableCollection(Of DatabaseCompressionResult)

    <ObservableProperty>
    Private _searchText As String

    Public ReadOnly Property FilteredResults As ICollectionView

    Public ReadOnly Property DatabaseGamesCount As Integer
        Get
            Return DatabaseResults.Count
        End Get
    End Property

    Public ReadOnly Property DatabaseSubmissionsCount As Integer
        Get
            Return DatabaseResults.Sum(Function(result) _
                    (If(result.Result_X4K?.TotalResults, 0)) +
                    (If(result.Result_X8K?.TotalResults, 0)) +
                    (If(result.Result_X16K?.TotalResults, 0)) +
                    (If(result.Result_LZX?.TotalResults, 0))
                    )
        End Get
    End Property

    Public ReadOnly Property LastUpdatedDatabase As DateTime
        Get
            Return _SettingsService.AppSettings.ResultsDBLastUpdated
        End Get
    End Property


    Private ReadOnly _SettingsService As ISettingsService

    Public Sub New(settingsService As ISettingsService, wikiService As IWikiService)

        _SettingsService = settingsService
        DatabaseResults = New ObservableCollection(Of DatabaseCompressionResult)(wikiService.GetAllDatabaseCompressionResultsAsync().GetAwaiter.GetResult)
        FilteredResults = CollectionViewSource.GetDefaultView(DatabaseResults)
        FilteredResults.Filter = AddressOf FilterResults
    End Sub


    Private Sub OnSearchTextChanged(value As String)
        FilteredResults.Refresh()
    End Sub

    Private Function NormalizeString(input As String) As String
        If String.IsNullOrEmpty(input) Then Return String.Empty
        Return New String(input.Where(Function(c) Char.IsLetterOrDigit(c) OrElse Char.IsWhiteSpace(c)).ToArray()).ToLowerInvariant()
    End Function

    Private Function FilterResults(obj As Object) As Boolean
        If String.IsNullOrWhiteSpace(SearchText) Then Return True
        Dim item = TryCast(obj, DatabaseCompressionResult)
        If item Is Nothing OrElse item.GameName Is Nothing Then Return False

        ' Normalize  GameName for punctuation-insensitive search
        Dim normalizedGameName = NormalizeString(item.GameName)

        Return (item.GameName.IndexOf(SearchText, StringComparison.OrdinalIgnoreCase) >= 0) OrElse
           (normalizedGameName.Contains(SearchText)) OrElse
           (item.SteamID.ToString().Contains(SearchText))
    End Function


    <RelayCommand>
    Private Sub SortResults(param As Object)
        Dim sortOption = param?.ToString()
        FilteredResults.SortDescriptions.Clear()

        Select Case sortOption
            Case "GameNameAsc"
                FilteredResults.SortDescriptions.Add(New SortDescription("GameName", ListSortDirection.Ascending))
            Case "GameNameDesc"
                FilteredResults.SortDescriptions.Add(New SortDescription("GameName", ListSortDirection.Descending))
            Case "SteamIDAsc"
                FilteredResults.SortDescriptions.Add(New SortDescription("SteamID", ListSortDirection.Ascending))
            Case "SteamIDDesc"
                FilteredResults.SortDescriptions.Add(New SortDescription("SteamID", ListSortDirection.Descending))
            Case "MaxSavingsAsc"
                FilteredResults.SortDescriptions.Add(New SortDescription("MaxSavings", ListSortDirection.Ascending))
            Case "MaxSavingsDesc"
                FilteredResults.SortDescriptions.Add(New SortDescription("MaxSavings", ListSortDirection.Descending))
        End Select
    End Sub


End Class


================================================
FILE: CompactGUI/ViewModels/FolderViewModel.vb
================================================

Imports System.ComponentModel

Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input

Imports CompactGUI.Core.Settings

Imports Wpf.Ui.Controls


Public NotInheritable Class FolderViewModel : Inherits ObservableObject : Implements IDisposable

    <ObservableProperty>
    Private _Folder As CompressableFolder

    <ObservableProperty>
    Private _CompressionProgress As Integer

    <ObservableProperty>
    Private _CompressionProgressFile As String

    <ObservableProperty>
    Private _AlwaysShowDetailsCompressionMode As Boolean = False

    Private ReadOnly _watcher As Watcher.Watcher
    Private ReadOnly _snackbarService As CustomSnackBarService
    Private ReadOnly _compressableFolderService As CompressableFolderService

    Public Sub New(folder As CompressableFolder, watcher As Watcher.Watcher, snackbarService As CustomSnackBarService, compressableFolderService As CompressableFolderService)
        Me.Folder = folder
        _watcher = watcher
        _snackbarService = snackbarService
        _compressableFolderService = compressableFolderService
        AddHandler folder.PropertyChanged, AddressOf OnFolderPropertyChanged
        AddHandler folder.CompressionOptions.PropertyChanged, AddressOf OnFolderCompressionOptionsPropertyChanged
        AddHandler Application.GetService(Of Core.Settings.ISettingsService).AppSettings.PropertyChanged, AddressOf OnAppSettingsPropertyChanged
    End Sub

    Private Sub OnAppSettingsPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        If e.PropertyName Is NameOf(Core.Settings.Settings.AlwaysShowDetailedCompressionMode) Then
            AlwaysShowDetailsCompressionMode = Application.GetService(Of Core.Settings.ISettingsService).AppSettings.AlwaysShowDetailedCompressionMode
        End If
    End Sub

    Public ReadOnly Property IsAnalysing As Boolean
        Get
            Return Folder?.FolderActionState = ActionState.Analysing
        End Get
    End Property

    Public ReadOnly Property IsNotResultsOrAnalysing As Boolean
        Get
            Return Folder?.FolderActionState <> ActionState.Results AndAlso Not IsAnalysing
        End Get
    End Property

    Public ReadOnly Property CompressionDisplayLevel As String
        Get
            If Folder.AnalysisResults Is Nothing OrElse
                Not Folder.AnalysisResults.Any(Function(x) x.CompressionMode <> Core.WOFCompressionAlgorithm.NO_COMPRESSION) Then
                Return "Not Compressed"
            End If
            Return "Compressed"
        End Get
    End Property

    Public ReadOnly Property TotalCompressedFiles As Integer
        Get
            Return Folder.AnalysisResults.Where(Function(x) x.CompressionMode <> Core.WOFCompressionAlgorithm.NO_COMPRESSION).Count()
        End Get
    End Property

    Public ReadOnly Property TotalFiles As Integer
        Get
            Return Folder.AnalysisResults.Count
        End Get
    End Property

    Public ReadOnly Property DominantCompressionMode As Core.WOFCompressionAlgorithm
        Get
            Return Folder?.AnalysisResults _
            .Where(Function(x) x.CompressionMode <> Core.WOFCompressionAlgorithm.NO_COMPRESSION) _
            .GroupBy(Function(x) x.CompressionMode) _
            .OrderByDescending(Function(g) g.Count()) _
            .Select(Function(g) g.Key) _
            .FirstOrDefault()
        End Get
    End Property

    Public ReadOnly Property DisplayedFolderAfterSize As Long
        Get
            If TypeOf (Folder) Is SteamFolder AndAlso (Folder.FolderActionState = ActionState.Idle OrElse Folder.FolderActionState = ActionState.Working) Then
                Dim working = CType(Folder, SteamFolder)
                If working.WikiCompressionResults Is Nothing Then Return Folder.CompressedBytes
                Select Case working.CompressionOptions.SelectedCompressionMode
                    Case Core.CompressionMode.XPRESS4K
                        Return CLng(working.WikiCompressionResults.XPress4K?.CompressionPercent / 100 * working.UncompressedBytes)
                    Case Core.CompressionMode.XPRESS8K
                        Return CLng(working.WikiCompressionResults.XPress8K?.CompressionPercent / 100 * working.UncompressedBytes)
                    Case Core.CompressionMode.XPRESS16K
                        Return CLng(working.WikiCompressionResults.XPress16K?.CompressionPercent / 100 * working.UncompressedBytes)
                    Case Core.CompressionMode.LZX
                        Return CLng(working.WikiCompressionResults.LZX?.CompressionPercent / 100 * working.UncompressedBytes)
                End Select


            End If
            Return Folder.CompressedBytes
        End Get
    End Property


    Public ReadOnly Property IsSteamIDVisible
        Get
            Return TypeOf Folder Is SteamFolder
        End Get
    End Property

    Private Sub OnFolderCompressionOptionsPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        Dim compressionOptions = CType(sender, CompressionOptions)
        If e.PropertyName = NameOf(compressionOptions.SelectedCompressionMode) Then
            OnPropertyChanged(NameOf(DisplayedFolderAfterSize))
        End If
    End Sub

    Private Sub OnFolderPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        If e.PropertyName = NameOf(Folder.FolderActionState) Then
            OnPropertyChanged(NameOf(IsAnalysing))
            OnPropertyChanged(NameOf(IsNotResultsOrAnalysing))
            OnPropertyChanged(NameOf(CompressionDisplayLevel))
            OnPropertyChanged(NameOf(DisplayedFolderAfterSize))
            OnPropertyChanged(NameOf(TotalFiles))

        ElseIf e.PropertyName = NameOf(Folder.CompressionProgress) Then
            CompressionProgress = Folder.CompressionProgress.ProgressPercent
            CompressionProgressFile = Folder.CompressionProgress.FileName.Replace(Folder.FolderName, "")

        End If
    End Sub


    <RelayCommand>
    Private Sub CompressAgain()
        Folder.FolderActionState = ActionState.Idle
    End Sub

    <RelayCommand>
    Private Async Function Uncompress() As Task
        Await _compressableFolderService.UncompressFolder(Folder)
        _watcher.UpdateWatched(Folder.FolderName, Folder.Analyser, False)
    End Function

    <RelayCommand>
    Private Sub ApplyToAll()
        Dim allFolders = Application.GetService(Of HomeViewModel)().Folders

        For Each fl In allFolders.Where(Function(f) f.FolderActionState <> ActionState.Analysing AndAlso f.FolderActionState <> ActionState.Working AndAlso f.FolderActionState <> ActionState.Paused)
            If fl IsNot Folder Then
                fl.CompressionOptions = Folder.CompressionOptions.Clone
                fl.FolderActionState = ActionState.Idle
            End If
        Next

        _snackbarService.ShowAppliedToAllFolders()
    End Sub

    <RelayCommand>
    Private Sub Pause()

        If Folder.FolderActionState = ActionState.Working Then
            Folder.Compressor?.Pause()
            Folder.FolderActionState = ActionState.Paused
        Else
            Folder.Compressor?.Resume()
            Folder.FolderActionState = ActionState.Working

        End If
    End Sub

    <RelayCommand>
    Private Sub Cancel()
        Folder.Compressor?.Cancel()
    End Sub

    <RelayCommand>
    Private Async Function SubmitToWiki() As Task

        SubmitToWikiCommand.NotifyCanExecuteChanged()

        Dim result = Await Application.GetService(Of IWikiService).SubmitToWiki(Folder.FolderName, Folder.AnalysisResults.ToList, Folder.PoorlyCompressedFiles, Folder.CompressionOptions.SelectedCompressionMode)

        Folder.IsFreshlyCompressed = False
        SubmitToWikiCommand.NotifyCanExecuteChanged()
    End Function
    Private Function CanSubmitToWiki() As Boolean
        Return TypeOf (Folder) _
            Is SteamFolder AndAlso
            Folder.IsFreshlyCompressed AndAlso
            Not Folder.CompressionOptions.SkipPoorlyCompressedFileTypes AndAlso
            Not Folder.CompressionOptions.SkipUserSubmittedFiletypes
    End Function






    Public Sub Dispose() Implements IDisposable.Dispose
        RemoveHandler Folder.PropertyChanged, AddressOf OnFolderPropertyChanged
        RemoveHandler Folder.CompressionOptions.PropertyChanged, AddressOf OnFolderCompressionOptionsPropertyChanged
    End Sub



End Class



================================================
FILE: CompactGUI/ViewModels/HomeViewModel.vb
================================================
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.ComponentModel

Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input
Imports CommunityToolkit.Mvvm.Messaging

Imports CompactGUI.Core.Settings

Imports CompactGUI.Core.SharedMethods
Imports CompactGUI.Logging

Imports Microsoft.Extensions.Logging

Partial Public NotInheritable Class HomeViewModel : Inherits ObservableRecipient : Implements IRecipient(Of WatcherAddedFolderToQueueMessage)

    Private ReadOnly _folderViewModels As New Dictionary(Of CompressableFolder, FolderViewModel)

    <ObservableProperty>
    Private _Folders As ObservableCollection(Of CompressableFolder) = New ObservableCollection(Of CompressableFolder)

    <ObservableProperty>
    <NotifyPropertyChangedFor(NameOf(SelectedFolderViewModel))>
    <NotifyPropertyChangedRecipients>
    Private _SelectedFolder As CompressableFolder

    Public ReadOnly Property SelectedFolderViewModel As FolderViewModel
        Get
            If SelectedFolder Is Nothing Then Return Nothing

            Dim value As FolderViewModel = Nothing
            Return If(_folderViewModels.TryGetValue(SelectedFolder, value), value, Nothing)

        End Get
    End Property

    Public ReadOnly Property HomeViewIsFresh As Boolean
        Get
            Return Not Folders.Any()
        End Get
    End Property

    Public ReadOnly Property DisplayVersion As String
        Get
            Return Application.AppVersion.Friendly
        End Get
    End Property

    Public ReadOnly Property IsAdmin As Boolean
        Get
            Dim principal = New Security.Principal.WindowsPrincipal(Security.Principal.WindowsIdentity.GetCurrent())
            Return principal.IsInRole(Security.Principal.WindowsBuiltInRole.Administrator)
        End Get
    End Property



    Private ReadOnly _watcher As Watcher.Watcher
    Private ReadOnly _snackbarService As CustomSnackB
Download .txt
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
Download .txt
SYMBOL INDEX (175 symbols across 20 files)

FILE: CompactGUI.Core/Analyser.cs
  class Analyser (line 9) | public sealed class Analyser : IDisposable
    method Analyser (line 19) | public Analyser(string folder, ILogger<Analyser> logger)
    method GetTotalCompressedBytes (line 34) | private static long GetTotalCompressedBytes(List<AnalysedFileDetails> ...
    method GetTotalUncompressedBytes (line 38) | private static long GetTotalUncompressedBytes(List<AnalysedFileDetails...
    method GetContainsCompressedFiles (line 42) | private static bool GetContainsCompressedFiles(List<AnalysedFileDetail...
    method GetAnalysedFilesAsync (line 50) | public async ValueTask<List<AnalysedFileDetails>?> GetAnalysedFilesAsy...
    method AnalyseFolder (line 64) | private async Task<List<AnalysedFileDetails>?> AnalyseFolder(Cancellat...
    method AnalyseFile (line 103) | private AnalysedFileDetails? AnalyseFile(string file)
    method GetPoorlyCompressedExtensions (line 126) | public List<ExtensionResult> GetPoorlyCompressedExtensions()
    method Dispose (line 148) | public void Dispose()

FILE: CompactGUI.Core/Compactor.cs
  class Compactor (line 14) | public sealed class Compactor : ICompressor, IDisposable
    method Compactor (line 33) | public Compactor(string folderPath, WOFCompressionAlgorithm compressio...
    method InitializeCompressionInfoPointer (line 44) | private void InitializeCompressionInfoPointer()
    method RunAsync (line 53) | public async Task<bool> RunAsync(List<string> filesList, IProgress<Com...
    method PauseAndProcessFile (line 95) | private async Task PauseAndProcessFile(FileDetails file, long totalFil...
    method WOFCompressFile (line 108) | private unsafe int? WOFCompressFile(string filePath)
    method BuildWorkingFilesList (line 124) | public async Task<IEnumerable<FileDetails>> BuildWorkingFilesList()
    method Pause (line 146) | public void Pause()
    method Resume (line 153) | public void Resume()
    method Cancel (line 160) | public void Cancel()
    method Dispose (line 167) | public void Dispose()
    type FileDetails (line 179) | public readonly record struct FileDetails(string FileName, long Uncomp...

FILE: CompactGUI.Core/Estimator.cs
  class Estimator (line 11) | public class Estimator
    type FileDetails (line 22) | private record FileDetails(AnalysedFileDetails AnalysedFile, float Com...
    method EstimateCompression (line 24) | public List<(AnalysedFileDetails AnalysedFile, float CompressionRatio)...
    method EstimateCompressabilityLZ4 (line 57) | private float EstimateCompressabilityLZ4(string fileName, long uncompr...
    method EstimateCompressability (line 79) | private float EstimateCompressability(FileStream input, long fileSize,...
    method EstimateCompressabilityHDD (line 127) | private float EstimateCompressabilityHDD(FileStream input, long fileSi...
    method GetFirstLcn (line 160) | unsafe long GetFirstLcn(string fileName)

FILE: CompactGUI.Core/FolderChangeMonitor.cs
  class FolderChangeMonitor (line 10) | public sealed class FolderChangeMonitor : IDisposable
    method FolderChangeMonitor (line 39) | public FolderChangeMonitor(string folderPath, int debounceMilliseconds...
    method OnChanged (line 57) | private void OnChanged(object sender, FileSystemEventArgs e)
    method DebounceCallback (line 62) | private void DebounceCallback(object? state)
    method OnError (line 69) | private void OnError(object sender, ErrorEventArgs e)
    method Reset (line 77) | public void Reset()
    method Dispose (line 82) | public void Dispose()

FILE: CompactGUI.Core/ICompressor.cs
  type ICompressor (line 3) | public interface ICompressor : IDisposable
    method RunAsync (line 5) | Task<bool> RunAsync(List<String> filesList,
    method Pause (line 9) | void Pause();
    method Resume (line 10) | void Resume();
    method Cancel (line 11) | void Cancel();

FILE: CompactGUI.Core/NTFSInterop.cs
  class NTFSInterop (line 5) | internal static class NTFSInterop
    type STARTING_VCN_INPUT_BUFFER (line 15) | [StructLayout(LayoutKind.Sequential)]
    type RETRIEVAL_POINTERS_BUFFER (line 22) | [StructLayout(LayoutKind.Sequential)]
    type LCN_EXTENT (line 31) | [StructLayout(LayoutKind.Sequential)]

FILE: CompactGUI.Core/Settings/ISettingsService.cs
  type ISettingsService (line 9) | public interface ISettingsService
    method LoadSettings (line 19) | void LoadSettings();
    method SaveSettings (line 20) | void SaveSettings();
    method ScheduleSettingsSave (line 21) | void ScheduleSettingsSave();

FILE: CompactGUI.Core/Settings/Settings.cs
  class Settings (line 6) | [NotifyPropertyChangedRecipients]
    method OnScheduledBackgroundIntervalChanged (line 50) | partial void OnScheduledBackgroundIntervalChanged(int value) => Update...
    method OnScheduledBackgroundHourChanged (line 52) | partial void OnScheduledBackgroundHourChanged(int value) => UpdateNext...
    method OnScheduledBackgroundMinuteChanged (line 54) | partial void OnScheduledBackgroundMinuteChanged(int value) => UpdateNe...
    method OnScheduledBackgroundLastRanChanged (line 56) | partial void OnScheduledBackgroundLastRanChanged(DateTime value) => Up...
    method UpdateNextScheduledBackgroundRun (line 58) | private void UpdateNextScheduledBackgroundRun()
  type WindowState (line 78) | public enum WindowState
  type BackgroundMode (line 85) | public enum BackgroundMode

FILE: CompactGUI.Core/SharedMethods.cs
  class SharedMethods (line 10) | public static class SharedMethods
    type FolderVerificationResult (line 14) | public enum FolderVerificationResult
    method VerifyFolder (line 27) | public static FolderVerificationResult VerifyFolder(string folder)
    method GetFolderVerificationMessage (line 49) | public static string GetFolderVerificationMessage(FolderVerificationRe...
    method IsDirectoryEmptySafe (line 67) | static bool IsDirectoryEmptySafe(string folderPath)
    method IsOneDriveFolder (line 80) | static bool IsOneDriveFolder(string folderPath)
    method PreventSleep (line 98) | public static void PreventSleep()
    method RestoreSleep (line 107) | public static void RestoreSleep()
    method AsShortPathNames (line 123) | public static IEnumerable<string> AsShortPathNames(this IEnumerable<st...
    method GetShortPath (line 129) | private static string GetShortPath(string filePath)
    method GetClusterSize (line 152) | public static unsafe uint GetClusterSize(string folderPath)
    method GetFileSizeOnDisk (line 170) | public static unsafe long GetFileSizeOnDisk(string file)
    method HasDirectoryWritePermission (line 179) | public static bool HasDirectoryWritePermission(string folderName)
    method IsFolderLZNT1Compressed (line 218) | public static bool IsFolderLZNT1Compressed(string folderPath)
    method IsDirectStorageGameFolder (line 224) | public static bool IsDirectStorageGameFolder(string folderPath)

FILE: CompactGUI.Core/SharedObjects.cs
  class AnalysedFileDetails (line 4) | public sealed class AnalysedFileDetails
  class ExtensionResult (line 14) | public sealed class ExtensionResult
  type CompressionProgress (line 27) | public struct CompressionProgress
    method CompressionProgress (line 32) | public CompressionProgress(int progressPercent, string fileName)
  type CompressionMode (line 40) | public enum CompressionMode: int
  type WOFCompressionAlgorithm (line 50) | public enum WOFCompressionAlgorithm: int

FILE: CompactGUI.Core/Uncompactor.cs
  class Uncompactor (line 12) | public sealed class Uncompactor : ICompressor, IDisposable
    method Uncompactor (line 21) | public Uncompactor(ILogger<Uncompactor>? logger = null)
    method RunAsync (line 26) | public async Task<bool> RunAsync(List<string> filesList, IProgress<Com...
    method PauseAndProcessFile (line 55) | private async Task PauseAndProcessFile(string file, int totalFiles, IP...
    method WOFDecompressFile (line 75) | private unsafe bool? WOFDecompressFile(string file)
    method Pause (line 91) | public void Pause()
    method Resume (line 98) | public void Resume()
    method Cancel (line 105) | public void Cancel()
    method Dispose (line 112) | public void Dispose()

FILE: CompactGUI.Core/WOFHelper.cs
  class WOFHelper (line 6) | public static class WOFHelper
    method WOFConvertCompressionLevel (line 13) | public static WOFCompressionAlgorithm WOFConvertCompressionLevel(int l...
    method CompressionModeFromWOFMode (line 25) | public static CompressionMode CompressionModeFromWOFMode(WOFCompressio...
    method WOFConvertCompressionLevel (line 38) | public static WOFCompressionAlgorithm WOFConvertCompressionLevel(Compr...
    type WOF_FILE_COMPRESSION_INFO_V1 (line 50) | [StructLayout(LayoutKind.Sequential)]
    method DetectCompression (line 58) | public static unsafe WOFCompressionAlgorithm DetectCompression(FileInf...

FILE: CompactGUI.Logging/Core/AnalyserLog.cs
  class AnalyserLog (line 10) | public static partial class AnalyserLog
    method StartingAnalysis (line 13) | [LoggerMessage(Level = LogLevel.Debug, Message = "Starting analysis of...
    method ProcessingFile (line 16) | [LoggerMessage(Level = LogLevel.Trace, Message = "Processing file: {Fi...
    method ProcessingFileFailed (line 19) | [LoggerMessage(Level = LogLevel.Warning, Message = "Processing file fa...
    method ProcessingFolder (line 23) | [LoggerMessage(Level = LogLevel.Trace, Message = "Processing folder: {...
    method AnalysisFailed (line 26) | [LoggerMessage(Level = LogLevel.Warning, Message = "Analysis failed fo...
    method AnalysisCompleted (line 29) | [LoggerMessage(Level = LogLevel.Information, Message = "Analysis compl...

FILE: CompactGUI.Logging/Core/CompactorLog.cs
  class CompactorLog (line 10) | public static partial class CompactorLog
    method StartingCompression (line 13) | [LoggerMessage(Level = LogLevel.Information, Message = "Starting compr...
    method BuildingWorkingFilesList (line 16) | [LoggerMessage(Level = LogLevel.Debug, Message = "Building working fil...
    method ProcessingFile (line 19) | [LoggerMessage(Level = LogLevel.Trace, Message = "Processing file: {Fi...
    method FileCompressionFailed (line 22) | [LoggerMessage(Level = LogLevel.Warning, Message = "File compression f...
    method CompressionPaused (line 25) | [LoggerMessage(Level = LogLevel.Information, Message = "Compression pa...
    method CompressionResumed (line 28) | [LoggerMessage(Level = LogLevel.Information, Message = "Compression re...
    method CompressionCanceled (line 31) | [LoggerMessage(Level = LogLevel.Information, Message = "Compression ca...
    method CompressionCompleted (line 34) | [LoggerMessage(Level = LogLevel.Information, Message = "Compression co...
    method CompressionFailed (line 37) | [LoggerMessage(Level = LogLevel.Error, Message = "Compression failed w...

FILE: CompactGUI.Logging/Core/UncompactorLog.cs
  class UncompactorLog (line 10) | public static partial class UncompactorLog
    method StartingDecompression (line 12) | [LoggerMessage(Level = LogLevel.Information, Message = "Starting decom...
    method ProcessingFile (line 15) | [LoggerMessage(Level = LogLevel.Trace, Message = "Processing file: {Fi...
    method FileDecompressionFailed (line 18) | [LoggerMessage(Level = LogLevel.Warning, Message = "File decompression...
    method DecompressionPaused (line 21) | [LoggerMessage(Level = LogLevel.Information, Message = "Decompression ...
    method DecompressionResumed (line 24) | [LoggerMessage(Level = LogLevel.Information, Message = "Decompression ...
    method DecompressionCanceled (line 27) | [LoggerMessage(Level = LogLevel.Information, Message = "Decompression ...
    method DecompressionCompleted (line 30) | [LoggerMessage(Level = LogLevel.Information, Message = "Decompression ...

FILE: CompactGUI.Logging/HomeViewModelLog.cs
  class HomeViewModelLog (line 5) | public static partial class HomeViewModelLog
    method AddingFolders (line 8) | [LoggerMessage(Level = LogLevel.Information,Message = "Loading folders...
    method InvalidFolders (line 11) | [LoggerMessage(Level = LogLevel.Warning, Message = "Invalid folders fo...
    method GettingEstimatedCompression (line 15) | [LoggerMessage(Level = LogLevel.Information, Message = "Getting estima...
    method CompressingFolder (line 20) | [LoggerMessage(Level = LogLevel.Debug, Message = "Compressing folder {...
    method AddingFolderToWatcher (line 24) | [LoggerMessage(Level = LogLevel.Debug, Message = "Adding folder {folde...
    method StartingBatchCompression (line 27) | [LoggerMessage(Level = LogLevel.Information, Message = "Starting batch...

FILE: CompactGUI.Logging/SchedulerServiceLog.cs
  class SchedulerServiceLog (line 10) | public static partial class SchedulerServiceLog
    method CheckingSchedulerRunnable (line 13) | [LoggerMessage(Level = LogLevel.Debug, Message = "Checking if schedule...
    method SchedulerDisabled (line 16) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler not ...
    method SchedulerNextRunInFuture (line 19) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler not ...
    method SchedulerRunningIdle (line 22) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler runn...
    method SchedulerNotIdle (line 25) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler not ...
    method SchedulerRunningScheduled (line 28) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler runn...
    method SchedulerModeDisabled (line 31) | [LoggerMessage(Level = LogLevel.Information, Message = "Scheduler not ...
    method SchedulerError (line 34) | [LoggerMessage(Level = LogLevel.Error, Message = "Scheduler error: {Ex...

FILE: CompactGUI.Logging/SettingsLog.cs
  class SettingsLog (line 10) | public static partial class SettingsLog
    method AddingToContextMenus (line 13) | [LoggerMessage(Level = LogLevel.Debug, Message = "Adding to Context Me...
    method AddingToContextMenusSuccess (line 16) | [LoggerMessage(Level = LogLevel.Debug, Message = "Adding to Context Me...
    method AddingToContextMenusFailed (line 19) | [LoggerMessage(Level = LogLevel.Warning, Message = "Adding to Context ...
    method RemovingFromContextMenus (line 22) | [LoggerMessage(Level = LogLevel.Debug, Message = "Removing from Contex...
    method RemovingFromContextMenusSuccess (line 25) | [LoggerMessage(Level = LogLevel.Debug, Message = "Removing from Contex...
    method RemovingFromContextMenusFailed (line 28) | [LoggerMessage(Level = LogLevel.Warning, Message = "Removing from Cont...
    method AddingStartMenuShortcut (line 31) | [LoggerMessage(Level = LogLevel.Debug, Message = "Adding Start Menu Sh...
    method RemovingStartMenuShortcut (line 34) | [LoggerMessage(Level = LogLevel.Debug, Message = "Removing Start Menu ...
    method SettingsSaved (line 37) | [LoggerMessage(Level = LogLevel.Debug, Message = "Settings Saved")]
    method SettingEnvironmentVariables (line 40) | [LoggerMessage(Level = LogLevel.Debug, Message = "Setting Environment ...

FILE: CompactGUI.Logging/SnackbarServiceLog.cs
  class SnackbarServiceLog (line 5) | public static partial class SnackbarServiceLog
    method ShowInvalidFoldersMessage (line 8) | [LoggerMessage(Level = LogLevel.Warning, Message = "Invalid Folder {Fo...
    method ShowInsufficientPermission (line 11) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing insuff...
    method ShowUpdateAvailable (line 14) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing update...
    method ShowFailedToSubmitToWiki (line 17) | [LoggerMessage(Level = LogLevel.Warning, Message = "Showing failed to ...
    method ShowSubmittedToWiki (line 20) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing submit...
    method ShowAppliedToAllFolders (line 23) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing applie...
    method ShowCannotRemoveFolder (line 26) | [LoggerMessage(Level = LogLevel.Warning, Message = "Showing cannot rem...
    method ShowAddedToQueue (line 29) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing added ...
    method ShowDirectStorageWarning (line 32) | [LoggerMessage(Level = LogLevel.Information, Message = "Showing Direct...

FILE: CompactGUI.Logging/Watcher/WatcherLog.cs
  class WatcherLog (line 10) | public static partial class WatcherLog
    method WatcherStarted (line 13) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher starte...
    method BackgroundingDisabled (line 16) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Back...
    method BackgroundingEnabled (line 19) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Back...
    method InitializingWatchedFolders (line 22) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Init...
    method ParsingWatchers (line 25) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Pars...
    method ParsingSingleWatcher (line 28) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Pars...
    method RemovingNonexistentFolders (line 31) | [LoggerMessage(Level = LogLevel.Debug, Message = "Watcher - Removing {...
    method DeserializeWatcherJsonFailed (line 34) | [LoggerMessage(Level = LogLevel.Warning, Message = "Watcher - Failed t...
    method SystemIdleDetected (line 37) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Syst...
    method SystemNotIdle (line 40) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Syst...
    method FolderChanged (line 43) | [LoggerMessage(Level = LogLevel.Debug, Message = "Watcher - Folder {Fo...
    method BackgroundCompactingStarted (line 55) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Back...
    method BackgroundCompactingFinished (line 58) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Back...
    method CompactingFolder (line 61) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Watc...
    method SkippingRecentlyModifiedFolder (line 64) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Skip...
    method FinishedCompactingFolder (line 67) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Fini...
    method PausingBackgroundCompactor (line 70) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Watc...
    method ResumingBackgroundCompactor (line 73) | [LoggerMessage(Level = LogLevel.Information, Message = "Watcher - Resu...
    method BackgroundCompactingError (line 76) | [LoggerMessage(Level = LogLevel.Error, Message = "Watcher - Error duri...
Condensed preview — 113 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (589K chars).
[
  {
    "path": ".gitattributes",
    "chars": 2518,
    "preview": "###############################################################################\n# Set default behavior to automatically "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 626,
    "preview": "# These are supported funding model platforms\n\ngithub: IridiumIO\npatreon: # Replace with a single Patreon username\nopen_"
  },
  {
    "path": ".gitignore",
    "chars": 6334,
    "preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
  },
  {
    "path": "CompactGUI/Application.xaml",
    "chars": 19303,
    "preview": "<Application x:Class=\"Application\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n     "
  },
  {
    "path": "CompactGUI/Application.xaml.vb",
    "chars": 14298,
    "preview": "Imports System.IO\nImports System.IO.Pipes\nImports System.Threading\nImports System.Windows.Threading\nImports Wpf.Ui\nImpo"
  },
  {
    "path": "CompactGUI/AssemblyInfo.vb",
    "chars": 553,
    "preview": "Imports System.Windows\n\n'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries ca"
  },
  {
    "path": "CompactGUI/CompactGUI.vbproj",
    "chars": 3936,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    \n    <OutputType>WinExe</OutputType>\n    <TargetFramework>net9"
  },
  {
    "path": "CompactGUI/Components/Behaviors/FocusOnMouseOverBehavior.vb",
    "chars": 541,
    "preview": "Imports Microsoft.Xaml.Behaviors\n\nPublic Class FocusOnMouseOverBehavior : Inherits Behavior(Of ComboBox)\n\n    Protected"
  },
  {
    "path": "CompactGUI/Components/Converters/Converters.xaml",
    "chars": 2954,
    "preview": "<ResourceDictionary xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n                    xmlns:x=\"http"
  },
  {
    "path": "CompactGUI/Components/Converters/IValueConverters.vb",
    "chars": 21327,
    "preview": "Imports System.Globalization\n\nPublic Class DecimalToPercentageConverter : Implements IValueConverter\n    Public Functio"
  },
  {
    "path": "CompactGUI/Components/Converters/MyConverters.vb",
    "chars": 489,
    "preview": "Imports FunctionalConverters\n\nPublic Class MyConverters : Inherits ExtensibleConverter\n\n    Public Sub New(ConverterNam"
  },
  {
    "path": "CompactGUI/Components/Custom/ImageControl.vb",
    "chars": 4014,
    "preview": "Imports System.Windows.Media.Animation\n\nPublic Class ImageControl : Inherits Image\n\n    Public Shared ReadOnly SourceCh"
  },
  {
    "path": "CompactGUI/Components/Custom/TokenizedTextBox.vb",
    "chars": 2952,
    "preview": "'Credit: https://blog.pixelingene.com/2010/10/tokenizing-control-convert-text-to-tokens\n\nPublic Class TokenizedTextBox "
  },
  {
    "path": "CompactGUI/Components/FolderActionStateTemplateSelector.vb",
    "chars": 2085,
    "preview": "Public Class FolderActionStateTemplateSelector\n    Inherits DataTemplateSelector\n\n    Public Property IdleTemplate As D"
  },
  {
    "path": "CompactGUI/Components/Settings/Settings_skiplistflyout.xaml",
    "chars": 3954,
    "preview": "<ui:FluentWindow x:Class=\"Settings_skiplistflyout\"\n                 xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml"
  },
  {
    "path": "CompactGUI/Components/Settings/Settings_skiplistflyout.xaml.vb",
    "chars": 1642,
    "preview": "Imports CompactGUI.Core.Settings\n\nPublic Class Settings_skiplistflyout\n\n    Private _settingsService As ISettingsServic"
  },
  {
    "path": "CompactGUI/Helper.vb",
    "chars": 7247,
    "preview": "Imports System.IO\nImports System.Net.NetworkInformation\nImports System.Runtime.InteropServices\nImports System.Text\n\nImp"
  },
  {
    "path": "CompactGUI/MainWindow.xaml",
    "chars": 14035,
    "preview": "<ui:FluentWindow x:Class=\"MainWindow\"\n                 xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  },
  {
    "path": "CompactGUI/MainWindow.xaml.vb",
    "chars": 4526,
    "preview": "Imports System.ComponentModel\n\nImports CompactGUI.Core.Settings\n\nImports Wpf.Ui\nImports Wpf.Ui.Abstractions\nImports Wpf"
  },
  {
    "path": "CompactGUI/Messages/WatcherAddedFolderToQueueMessage.vb",
    "chars": 224,
    "preview": "Imports CommunityToolkit.Mvvm.Messaging.Messages\n\nPublic Class WatcherAddedFolderToQueueMessage : Inherits ValueChanged"
  },
  {
    "path": "CompactGUI/Models/CompressableFolders/CompressableFolder.vb",
    "chars": 3401,
    "preview": "Imports System.Collections.ObjectModel\nImports System.IO\nImports System.Threading\n\nImports CommunityToolkit.Mvvm.Compon"
  },
  {
    "path": "CompactGUI/Models/CompressableFolders/CompressableFolderFactory.vb",
    "chars": 1105,
    "preview": "Public Class CompressableFolderFactory\n    Public Shared Async Function CreateCompressableFolder(path As String) As Tas"
  },
  {
    "path": "CompactGUI/Models/CompressableFolders/StandardFolder.vb",
    "chars": 285,
    "preview": "\n\nPublic NotInheritable Class StandardFolder : Inherits CompressableFolder\n\n    Public Sub New(path As String)\n        "
  },
  {
    "path": "CompactGUI/Models/CompressableFolders/SteamFolder.vb",
    "chars": 4563,
    "preview": "\nImports System.IO\nImports System.Net.Http\n\nImports CommunityToolkit.Mvvm.ComponentModel\n\nImports CompactGUI.Core.Setti"
  },
  {
    "path": "CompactGUI/Models/CompressionResult.vb",
    "chars": 1416,
    "preview": "\nImports CommunityToolkit.Mvvm.ComponentModel\n\nPublic Class CompressionResult : Inherits ObservableObject\n\n    <Observa"
  },
  {
    "path": "CompactGUI/Models/NewModels/CompressionOptions.vb",
    "chars": 865,
    "preview": "Imports CommunityToolkit.Mvvm.ComponentModel\n\nPublic Class CompressionOptions : Inherits ObservableObject\n    <Observab"
  },
  {
    "path": "CompactGUI/Models/NewModels/DatabaseCompressionResult.vb",
    "chars": 1418,
    "preview": "Imports CommunityToolkit.Mvvm.ComponentModel\n\nPublic Class DatabaseCompressionResult : Inherits ObservableObject\n\n    <"
  },
  {
    "path": "CompactGUI/Models/NewModels/WikiCompressionResults.vb",
    "chars": 1130,
    "preview": "Imports CommunityToolkit.Mvvm.ComponentModel\n\nPublic Class WikiCompressionResults : Inherits ObservableObject\n    <Obse"
  },
  {
    "path": "CompactGUI/Models/SemVersion.vb",
    "chars": 2394,
    "preview": "Public Class SemVersion : Implements IComparable(Of SemVersion)\n    Property Major As Integer\n    Property Minor As Int"
  },
  {
    "path": "CompactGUI/Models/SteamACFResult.vb",
    "chars": 353,
    "preview": "Public Structure SteamACFResult\n    Public AppID As Integer\n    Public GameName As String\n    Public InstallDirectory A"
  },
  {
    "path": "CompactGUI/Models/SteamResultsData.vb",
    "chars": 381,
    "preview": "\n' Object to get results from existing wiki file\nPublic Class SteamResultsData\n\n    Public SteamID As Integer\n    Publi"
  },
  {
    "path": "CompactGUI/Models/SteamSubmissionData.vb",
    "chars": 471,
    "preview": "\n' Object used to build submission data to send online after compression\nPublic Class SteamSubmissionData\n    Public Pr"
  },
  {
    "path": "CompactGUI/My Project/Application.myapp",
    "chars": 456,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<MyApplicationData xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs"
  },
  {
    "path": "CompactGUI/My Project/app.manifest",
    "chars": 3319,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <ass"
  },
  {
    "path": "CompactGUI/Services/ApplicationHostService.vb",
    "chars": 3002,
    "preview": "Imports CompactGUI.Core.Settings\n\nImports Microsoft.Extensions.DependencyInjection\nImports Microsoft.Extensions.Hosting"
  },
  {
    "path": "CompactGUI/Services/CompressableFolderService.vb",
    "chars": 9223,
    "preview": "Imports System.Collections.ObjectModel\nImports System.Threading\n\nImports CompactGUI.Core\nImports CompactGUI.Core.Settin"
  },
  {
    "path": "CompactGUI/Services/CustomSnackBarService.vb",
    "chars": 5991,
    "preview": "Imports CommunityToolkit.Mvvm.Input\n\nImports CompactGUI.Core.SharedMethods\nImports CompactGUI.Logging\n\nImports Microsof"
  },
  {
    "path": "CompactGUI/Services/SchedulerService.vb",
    "chars": 2863,
    "preview": "Imports CompactGUI.Core.Settings\nImports CompactGUI.Logging\nImports CompactGUI.Watcher\n\nImports Coravel.Scheduling.Sche"
  },
  {
    "path": "CompactGUI/Services/SettingsService.vb",
    "chars": 4178,
    "preview": "Imports System.IO\nImports System.Text.Json\n\nImports CompactGUI.Core.Settings\nImports CompactGUI.Logging\n\nImports Micros"
  },
  {
    "path": "CompactGUI/Services/SteamACFParser.vb",
    "chars": 3005,
    "preview": "\n\nImports Gameloop.Vdf\nImports Gameloop.Vdf.Linq\n\nImports Microsoft.Extensions.Caching.Memory\n\nPublic Class SteamACFPar"
  },
  {
    "path": "CompactGUI/Services/TrayNotifierService.vb",
    "chars": 3962,
    "preview": "Imports System.Drawing\nImports System.Runtime.InteropServices\nImports System.Windows.Interop\n\nImports CompactGUI.Core.S"
  },
  {
    "path": "CompactGUI/Services/UpdaterService.vb",
    "chars": 1158,
    "preview": "Imports System.Net.Http\nImports System.Text.Json\n\nImports Wpf.Ui.Controls\n\nPublic Interface IUpdaterService\n    Functio"
  },
  {
    "path": "CompactGUI/Services/WikiService.vb",
    "chars": 7893,
    "preview": "Imports System.Net.Http\nImports System.Text.Json\n\nImports CompactGUI.Core.Settings\n\nPublic Interface IWikiService\n    F"
  },
  {
    "path": "CompactGUI/Services/WindowService.vb",
    "chars": 1542,
    "preview": "Public Interface IWindowService\n    Sub ShowMainWindow()\n    Sub MinimizeMainWindow()\n    Sub HideMainWindow()\n    Func"
  },
  {
    "path": "CompactGUI/Themes/Generic.xaml",
    "chars": 764,
    "preview": "<ResourceDictionary\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.mi"
  },
  {
    "path": "CompactGUI/ViewModels/DatabaseViewModel.vb",
    "chars": 3865,
    "preview": "Imports System.Collections.ObjectModel\nImports System.ComponentModel\n\nImports CommunityToolkit.Mvvm.ComponentModel\nImpo"
  },
  {
    "path": "CompactGUI/ViewModels/FolderViewModel.vb",
    "chars": 8396,
    "preview": "\nImports System.ComponentModel\n\nImports CommunityToolkit.Mvvm.ComponentModel\nImports CommunityToolkit.Mvvm.Input\n\nImpor"
  },
  {
    "path": "CompactGUI/ViewModels/HomeViewModel.vb",
    "chars": 12324,
    "preview": "Imports System.Collections.ObjectModel\nImports System.Collections.Specialized\nImports System.ComponentModel\n\nImports Co"
  },
  {
    "path": "CompactGUI/ViewModels/MainWindowViewModel.vb",
    "chars": 2835,
    "preview": "\nImports CommunityToolkit.Mvvm.ComponentModel\nImports CommunityToolkit.Mvvm.Input\nImports CommunityToolkit.Mvvm.Messagi"
  },
  {
    "path": "CompactGUI/ViewModels/SettingsViewModel.vb",
    "chars": 7496,
    "preview": "\nImports System.ComponentModel\nImports System.Xml\n\nImports CommunityToolkit.Mvvm.ComponentModel\nImports CommunityToolki"
  },
  {
    "path": "CompactGUI/ViewModels/WatcherViewModel.vb",
    "chars": 3694,
    "preview": "Imports System.Threading\n\nImports CommunityToolkit.Mvvm.ComponentModel\nImports CommunityToolkit.Mvvm.Input\nImports Comm"
  },
  {
    "path": "CompactGUI/Views/Components/CompressionMode_Radio.xaml",
    "chars": 18823,
    "preview": "<RadioButton x:Class=\"CompressionMode_Radio\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentat"
  },
  {
    "path": "CompactGUI/Views/Components/CompressionMode_Radio.xaml.vb",
    "chars": 7953,
    "preview": "Public Class CompressionMode_Radio\n    Inherits RadioButton\n\n\n    Public Shared ReadOnly CompressionModeProperty As Dep"
  },
  {
    "path": "CompactGUI/Views/Components/FolderWatcherCard.xaml",
    "chars": 20947,
    "preview": "<UserControl x:Class=\"FolderWatcherCard\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\""
  },
  {
    "path": "CompactGUI/Views/Components/FolderWatcherCard.xaml.vb",
    "chars": 4964,
    "preview": "Imports System.Windows.Media.Animation\n\nPublic Class FolderWatcherCard : Inherits UserControl\n    Private currentlyExpa"
  },
  {
    "path": "CompactGUI/Views/Pages/DatabasePage.xaml",
    "chars": 17310,
    "preview": "<UserControl x:Class=\"DatabasePage\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    "
  },
  {
    "path": "CompactGUI/Views/Pages/DatabasePage.xaml.vb",
    "chars": 271,
    "preview": "Public Class DatabasePage\n    Public Property viewModel As DatabaseViewModel\n    Sub New(VM As DatabaseViewModel)\n\n    "
  },
  {
    "path": "CompactGUI/Views/Pages/FolderView.xaml",
    "chars": 11555,
    "preview": "<UserControl x:Class=\"FolderView\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      "
  },
  {
    "path": "CompactGUI/Views/Pages/FolderView.xaml.vb",
    "chars": 36,
    "preview": "Public Class FolderView\n\nEnd Class\n"
  },
  {
    "path": "CompactGUI/Views/Pages/HomePage.xaml",
    "chars": 19382,
    "preview": "<Page x:Class=\"HomePage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://"
  },
  {
    "path": "CompactGUI/Views/Pages/HomePage.xaml.vb",
    "chars": 1828,
    "preview": "Class HomePage\n\n    Private _viewModel As HomeViewModel\n\n\n    Sub New(viewmodel As HomeViewModel)\n\n        ' This call "
  },
  {
    "path": "CompactGUI/Views/Pages/PendingCompression.xaml",
    "chars": 12293,
    "preview": "<UserControl x:Class=\"PendingCompression\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  },
  {
    "path": "CompactGUI/Views/Pages/PendingCompression.xaml.vb",
    "chars": 2101,
    "preview": "Imports CompactGUI.Core.Settings\n\nPublic Class PendingCompression\n\n    Private ReadOnly _settingsService As ISettingsSe"
  },
  {
    "path": "CompactGUI/Views/Pages/ResultsTemplate.xaml",
    "chars": 7200,
    "preview": "<UserControl x:Class=\"ResultsTemplate\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n "
  },
  {
    "path": "CompactGUI/Views/Pages/ResultsTemplate.xaml.vb",
    "chars": 41,
    "preview": "Public Class ResultsTemplate\n\nEnd Class\n"
  },
  {
    "path": "CompactGUI/Views/Pages/WatcherPage.xaml",
    "chars": 595,
    "preview": "<Page x:Class=\"WatcherPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http"
  },
  {
    "path": "CompactGUI/Views/Pages/WatcherPage.xaml.vb",
    "chars": 262,
    "preview": "Class WatcherPage\n\n    Public Property viewModel As WatcherViewModel\n    Sub New(VM As WatcherViewModel)\n\n        Initi"
  },
  {
    "path": "CompactGUI/Views/SettingsPage.xaml",
    "chars": 40356,
    "preview": "<Page x:Class=\"SettingsPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"htt"
  },
  {
    "path": "CompactGUI/Views/SettingsPage.xaml.vb",
    "chars": 234,
    "preview": "Public Class SettingsPage\n\n    Sub New(settingsviewmodel As SettingsViewModel)\n\n        InitializeComponent()\n\n\n       "
  },
  {
    "path": "CompactGUI.Core/Analyser.cs",
    "chars": 5716,
    "preview": "using Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing System.Collections.Concurren"
  },
  {
    "path": "CompactGUI.Core/CompactGUI.Core.csproj",
    "chars": 893,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net9.0-windows</TargetFramework>\n    <Nullabl"
  },
  {
    "path": "CompactGUI.Core/Compactor.cs",
    "chars": 6145,
    "preview": "\nusing CompactGUI.Logging.Core;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nus"
  },
  {
    "path": "CompactGUI.Core/Estimator.cs",
    "chars": 6475,
    "preview": "using K4os.Compression.LZ4;\nusing K4os.Compression.LZ4.Streams;\nusing Microsoft.Win32.SafeHandles;\nusing System.Collect"
  },
  {
    "path": "CompactGUI.Core/FolderChangeMonitor.cs",
    "chars": 2646,
    "preview": "using System;\nusing System.IO;\nusing System.Threading;\n\nnamespace CompactGUI.Core;\n\n/// <summary>\n/// Monitors a folder"
  },
  {
    "path": "CompactGUI.Core/ICompressor.cs",
    "chars": 278,
    "preview": "namespace CompactGUI.Core;\n\npublic interface ICompressor : IDisposable\n{\n    Task<bool> RunAsync(List<String> filesList"
  },
  {
    "path": "CompactGUI.Core/NTFSInterop.cs",
    "chars": 1016,
    "preview": "using System.Runtime.InteropServices;\n\nnamespace CompactGUI.Core;\n\ninternal static class NTFSInterop\n{\n\n    internal co"
  },
  {
    "path": "CompactGUI.Core/NativeMethods.txt",
    "chars": 138,
    "preview": "WofIsExternalFile\nWofSetFileDataLocation\nDeviceIoControl\nGetDiskFreeSpace\nSetThreadExecutionState\nGetShortPathName\nGetC"
  },
  {
    "path": "CompactGUI.Core/Settings/ISettingsService.cs",
    "chars": 479,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "CompactGUI.Core/Settings/Settings.cs",
    "chars": 4247,
    "preview": "using CommunityToolkit.Mvvm.ComponentModel;\nusing Microsoft.Extensions.Logging;\n\nnamespace CompactGUI.Core.Settings;\n\n["
  },
  {
    "path": "CompactGUI.Core/SharedMethods.cs",
    "chars": 8717,
    "preview": "using System.Runtime.InteropServices;\nusing System.Security.AccessControl;\nusing System.Security.Principal;\nusing Windo"
  },
  {
    "path": "CompactGUI.Core/SharedObjects.cs",
    "chars": 1178,
    "preview": "namespace CompactGUI.Core;\n\n\npublic sealed class AnalysedFileDetails\n{\n    public required string FileName { get; set; "
  },
  {
    "path": "CompactGUI.Core/Uncompactor.cs",
    "chars": 3668,
    "preview": "\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing Microsoft.Win32.SafeHandles"
  },
  {
    "path": "CompactGUI.Core/WOFHelper.cs",
    "chars": 2435,
    "preview": "using System.Runtime.InteropServices;\nusing Windows.Win32;\n\nnamespace CompactGUI.Core;\n\npublic static class WOFHelper\n{"
  },
  {
    "path": "CompactGUI.CoreVB/Analyser.vb",
    "chars": 7683,
    "preview": "Imports System.IO\nImports System.Security.AccessControl\nImports System.Security.Principal\nImports System.Threading\n\nPubl"
  },
  {
    "path": "CompactGUI.CoreVB/CompactGUI.CoreVB.vbproj",
    "chars": 865,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <RootNamespace>CompactGUI.CoreVB</RootNamespace>\n    <TargetFr"
  },
  {
    "path": "CompactGUI.CoreVB/Compactor.vb",
    "chars": 5967,
    "preview": "Imports System.IO\nImports System.Runtime.InteropServices\nImports System.Threading\n\nImports Microsoft.Win32.SafeHandles\n"
  },
  {
    "path": "CompactGUI.CoreVB/Estimator.vb",
    "chars": 9439,
    "preview": "Imports System.IO\nImports System.IO.Compression\nImports System.Runtime.InteropServices\n\nImports K4os.Compression.LZ4\n\nI"
  },
  {
    "path": "CompactGUI.CoreVB/FodyWeavers.xml",
    "chars": 187,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Weavers xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSc"
  },
  {
    "path": "CompactGUI.CoreVB/ICompressor.vb",
    "chars": 305,
    "preview": "Public Interface ICompressor : Inherits IDisposable\n    Function RunAsync(filesList As List(Of String), Optional progre"
  },
  {
    "path": "CompactGUI.CoreVB/NtfsInterop.vb",
    "chars": 1483,
    "preview": "Imports System.Runtime.InteropServices\n\nImports Microsoft.Win32.SafeHandles\n\nFriend Module NtfsInterop\n    Public Const"
  },
  {
    "path": "CompactGUI.CoreVB/SharedMethods.vb",
    "chars": 7552,
    "preview": "Imports System.IO\nImports System.Runtime.CompilerServices\nImports System.Runtime.InteropServices\nImports System.Text\nPu"
  },
  {
    "path": "CompactGUI.CoreVB/SharedObjects.vb",
    "chars": 1251,
    "preview": "Public Class AnalysedFileDetails\n\n    Public Property FileName As String\n    Public Property UncompressedSize As Long\n "
  },
  {
    "path": "CompactGUI.CoreVB/Uncompactor.vb",
    "chars": 3074,
    "preview": "Imports System.IO\nImports System.Threading\n\nImports Microsoft.Win32.SafeHandles\n\nPublic Class Uncompactor : Implements "
  },
  {
    "path": "CompactGUI.CoreVB/WOFHelper.vb",
    "chars": 2923,
    "preview": "Imports System.Runtime.InteropServices\n\nImports Microsoft.Win32.SafeHandles\n\nPublic Module WOFHelper\n\n    Public Const "
  },
  {
    "path": "CompactGUI.Logging/CompactGUI.Logging.csproj",
    "chars": 449,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net9.0</TargetFramework>\n    <ImplicitUsings>"
  },
  {
    "path": "CompactGUI.Logging/Core/AnalyserLog.cs",
    "chars": 1626,
    "preview": "using Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
  },
  {
    "path": "CompactGUI.Logging/Core/CompactorLog.cs",
    "chars": 2039,
    "preview": "using Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
  },
  {
    "path": "CompactGUI.Logging/Core/UncompactorLog.cs",
    "chars": 1559,
    "preview": "using Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
  },
  {
    "path": "CompactGUI.Logging/HomeViewModelLog.cs",
    "chars": 1391,
    "preview": "using Microsoft.Extensions.Logging;\n\nnamespace CompactGUI.Logging;\n\npublic static partial class HomeViewModelLog\n{\n\n   "
  },
  {
    "path": "CompactGUI.Logging/SchedulerServiceLog.cs",
    "chars": 1812,
    "preview": "using Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
  },
  {
    "path": "CompactGUI.Logging/SettingsLog.cs",
    "chars": 1871,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusi"
  },
  {
    "path": "CompactGUI.Logging/SnackbarServiceLog.cs",
    "chars": 2024,
    "preview": "using Microsoft.Extensions.Logging;\n\nnamespace CompactGUI.Logging;\n\npublic static partial class SnackbarServiceLog\n{\n\n "
  },
  {
    "path": "CompactGUI.Logging/Watcher/WatcherLog.cs",
    "chars": 3864,
    "preview": "using Microsoft.Extensions.Logging;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Tex"
  },
  {
    "path": "CompactGUI.Watcher/BackgroundCompactor.vb",
    "chars": 5085,
    "preview": "Imports System.Collections.ObjectModel\nImports System.Threading\n\nImports CompactGUI.Logging.Watcher\n\nImports Microsoft."
  },
  {
    "path": "CompactGUI.Watcher/CompactGUI.Watcher.vbproj",
    "chars": 785,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <RootNamespace>CompactGUI.Watcher</RootNamespace>\n    <TargetFr"
  },
  {
    "path": "CompactGUI.Watcher/IdleDetector.vb",
    "chars": 2738,
    "preview": "Imports System.Runtime.InteropServices\nImports System.Threading\n\nPublic Enum IdleState\n    Idle\n    NotIdle\nEnd Enum\n\nP"
  },
  {
    "path": "CompactGUI.Watcher/IdleSettings.vb",
    "chars": 572,
    "preview": "Public Class IdleSettings\n    Public Property IdleCheckIntervalSeconds As Integer = 5 ' How often to check for idle sta"
  },
  {
    "path": "CompactGUI.Watcher/WatchedFolder.vb",
    "chars": 4600,
    "preview": "Imports System.IO\nImports System.Text.Json.Serialization\nImports System.Threading\n\nImports CommunityToolkit.Mvvm.Compon"
  },
  {
    "path": "CompactGUI.Watcher/Watcher.vb",
    "chars": 18938,
    "preview": "Imports System.Collections.ObjectModel\nImports System.Collections.Specialized\nImports System.Runtime\nImports System.Text"
  },
  {
    "path": "CompactGUI.slnx",
    "chars": 953,
    "preview": "<Solution>\n  <Configurations>\n    <Platform Name=\"Any CPU\" />\n    <Platform Name=\"ARM\" />\n    <Platform Name=\"ARM64\" />\n"
  },
  {
    "path": "LICENSE",
    "chars": 36873,
    "preview": "(c) Iridium IO 2024\n\n                     GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Co"
  },
  {
    "path": "README.md",
    "chars": 6652,
    "preview": "<p align=\"center\"><img src=\"https://github.com/IridiumIO/CompactGUI/assets/1491536/64f66b5d-0710-4f66-8b88-6a69f7eb9b63\""
  },
  {
    "path": "Version.xml",
    "chars": 375,
    "preview": "<Info>\n\t<VersionMajor>3.0</VersionMajor>\n\t<VersionMinor>0</VersionMinor>\n\t<IsPrerelease>True</IsPrerelease>\n\t<VersionStr"
  }
]

About this extraction

This page contains the full source code of the IridiumIO/CompactGUI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 113 files (549.2 KB), approximately 122.8k tokens, and a symbol index with 175 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!