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
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
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.