├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── githubactionsbuilds.yml ├── .gitignore ├── CreateChocoPackage ├── .gitignore ├── CreatePackage.ps1 ├── PowerShellHelpers.psm1 ├── PushPackage.ps1 ├── ReadMe.md ├── TestInstall.cmd ├── _TODO.txt ├── deveimageoptimizerwpf.nuspec ├── templates │ ├── VERIFICATION.txt │ ├── chocolateybeforemodify.ps1 │ ├── chocolateyinstall.ps1 │ └── chocolateyuninstall.ps1 └── tools │ └── LICENSE.txt ├── DeveImageOptimizerWPF.Tests ├── Converters │ └── KbConverterTests.cs ├── DeveImageOptimizerWPF.Tests.csproj └── ViewModel │ └── ObservableData │ └── LoggerExtractinatorTests.cs ├── DeveImageOptimizerWPF.sln ├── DeveImageOptimizerWPF ├── App.xaml ├── App.xaml.cs ├── Converters │ ├── EnumToBooleanConverter.cs │ ├── InverseBooleanConverter.cs │ ├── KbConverter.cs │ ├── ObservableCollectionOptimizedFileResultToSizeReducedConverter.cs │ ├── ObservableCollectionOptimizedFileResultToTotalSizeAfterConverter.cs │ ├── ObservableCollectionOptimizedFileResultToTotalSizeBeforeConverter.cs │ ├── OptimizationResultToColorConverter.cs │ ├── OptimizedFileResulToErrorStringConverter.cs │ ├── OptimizedFileResultToSizeConverter.cs │ ├── StringToImageSourceConverter.cs │ └── SwitchBindingExtension.cs ├── DeveImageOptimizerWPF.csproj ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Helpers │ ├── AutoFilteringObservableCollection.cs │ ├── ComboBoxKeyValue.cs │ ├── InitialDirFinder.cs │ ├── ScrollViewerExtensions.cs │ └── ScrollViewerExtensions2.cs ├── ILLink.Descriptors.xml ├── Icon.ico ├── LogViewer.xaml ├── LogViewer.xaml.cs ├── LogViewerData │ ├── CollapsibleLogEntry.cs │ ├── LogEntry.cs │ └── PropertyChangedBase.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── State │ ├── IChangable.cs │ ├── MainWindowState │ │ └── WindowState.cs │ ├── ProcessingState │ │ ├── FileProgressState.cs │ │ └── OptimizableFileUI.cs │ ├── StateManager.cs │ ├── StaticState.cs │ └── UserSettings │ │ ├── RemembererSettings.cs │ │ └── UserSettingsData.cs └── ViewModel │ ├── ConsoleViewModel.cs │ ├── MainViewModel.cs │ ├── ObservableData │ └── LoggerExtractinator.cs │ ├── SettingsViewModel.cs │ └── ViewModelLocator.cs ├── Icon.png ├── LICENSE ├── NuGet.config ├── README.md ├── ScreenshotConfig.png ├── ScreenshotMain.png └── Scripts ├── GoRepack.ps1 └── set-version.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/githubactionsbuilds.yml: -------------------------------------------------------------------------------- 1 | name: GitHubActionsBuilds 2 | 3 | on: push 4 | 5 | jobs: 6 | generate_version_number: 7 | permissions: 8 | contents: write 9 | runs-on: ubuntu-latest 10 | outputs: 11 | build_number: ${{ steps.buildnumber.outputs.build_number }} 12 | steps: 13 | - name: Generate build number 14 | id: buildnumber 15 | uses: onyxmueller/build-tag-number@v1 16 | with: 17 | token: ${{secrets.github_token}} 18 | 19 | build_windows: 20 | needs: generate_version_number 21 | runs-on: windows-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Setup .NET Core @ Latest 25 | uses: actions/setup-dotnet@v3 26 | with: 27 | dotnet-version: 9.0.x 28 | - uses: actions/setup-java@v3 29 | with: 30 | distribution: 'zulu' 31 | java-version: '19' # The JDK version to make available on the path. 32 | java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk 33 | architecture: x64 # (x64 or x86) - defaults to x64 34 | - name: Install tools 35 | run: | 36 | dotnet tool install --global dotnet-sonarscanner 37 | dotnet tool install --global dotnet-reportgenerator-globaltool 38 | - name: Install dependencies 39 | run: dotnet restore DeveImageOptimizerWPF.sln 40 | - name: SonarQube begin 41 | if: github.actor != 'dependabot[bot]' 42 | run: dotnet-sonarscanner begin /k:"DeveImageOptimizerWPF" /o:"devedse-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login=${{secrets.SONARQUBETOKEN}} /d:sonar.cs.opencover.reportsPaths=".\DeveImageOptimizerWPF.Tests\TestResults\Coverage\coverage.opencover.xml" /d:sonar.coverage.exclusions="DeveImageOptimizerWPF.Tests/**/*.cs" 43 | - name: Build 44 | run: dotnet build DeveImageOptimizerWPF.sln -c Release --no-restore /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 45 | - name: Test 46 | run: dotnet test DeveImageOptimizerWPF.sln --verbosity normal --no-build -c Release --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura,opencover 47 | - name: SonarQube end 48 | if: github.actor != 'dependabot[bot]' 49 | run: dotnet-sonarscanner end /d:sonar.login=${{secrets.SONARQUBETOKEN}} 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | - name: Publish 53 | run: dotnet publish .\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj -r win-x64 -c Release /p:PublishSingleFile=true /p:SelfContained=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true /p:TrimMode=partial /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 54 | - name: Publish (Without Trim) 55 | run: dotnet publish .\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj -r win-x64 -c Release -o "DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishuntrimmed\" /p:PublishSingleFile=true /p:SelfContained=true /p:IncludeNativeLibrariesForSelfExtract=true /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 56 | - name: Publish 57 | run: dotnet publish .\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj -r win-x64 -c Release -o "DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishtrimmedlink\" /p:PublishSingleFile=true /p:SelfContained=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true /p:TrimMode=Link /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 58 | - name: Publish 59 | run: dotnet publish .\DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj -r win-x64 -c Release -o "DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishtrimmedcopyused\" /p:PublishSingleFile=true /p:SelfContained=true /p:IncludeNativeLibrariesForSelfExtract=true /p:PublishTrimmed=true /p:TrimMode=CopyUsed /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 60 | # - name: Pack 61 | # run: dotnet pack DeveImageOptimizer\DeveImageOptimizer.csproj --verbosity normal --no-build -c Release /p:Version=1.0.${{needs.generate_version_number.outputs.build_number}} 62 | - name: Run CodeCov 63 | uses: codecov/codecov-action@v5 64 | with: 65 | directory: ./DeveImageOptimizerWPF.Tests/TestResults 66 | fail_ci_if_error: true # optional (default = false) 67 | verbose: true # optional (default = false) 68 | env: 69 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 70 | 71 | - uses: actions/upload-artifact@v4 72 | with: 73 | name: DeveImageOptimizerWPF_win-x64 74 | path: DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publish\DeveImageOptimizerWPF.exe 75 | - uses: actions/upload-artifact@v4 76 | with: 77 | name: DeveImageOptimizerWPF_win-x64_untrimmed 78 | path: DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishuntrimmed\DeveImageOptimizerWPF.exe 79 | - uses: actions/upload-artifact@v4 80 | with: 81 | name: DeveImageOptimizerWPF_win-x64_trimlink 82 | path: DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishtrimmedlink\DeveImageOptimizerWPF.exe 83 | - uses: actions/upload-artifact@v4 84 | with: 85 | name: DeveImageOptimizerWPF_win-x64_trimcopyused 86 | path: DeveImageOptimizerWPF\bin\Release\net9.0-windows\win-x64\publishtrimmedcopyused\DeveImageOptimizerWPF.exe 87 | 88 | release_github: 89 | needs: [generate_version_number, build_windows] 90 | runs-on: ubuntu-latest 91 | if: github.ref == 'refs/heads/master' 92 | steps: 93 | - name: Download all artifacts 94 | uses: actions/download-artifact@v4 95 | with: 96 | path: ./artifacts/ 97 | 98 | - name: Display structure of downloaded files 99 | run: | 100 | cd ./artifacts/ 101 | ls -R 102 | - name: Create GitHub Release 103 | uses: softprops/action-gh-release@v2 104 | with: 105 | tag_name: 1.0.${{needs.generate_version_number.outputs.build_number}} 106 | name: 1.0.${{needs.generate_version_number.outputs.build_number}} 107 | body: | 108 | ${{ github.event.head_commit.message }} 109 | files: | 110 | ./artifacts/DeveImageOptimizerWPF_win-x64/DeveImageOptimizerWPF.exe 111 | fail_on_unmatched_files: true 112 | make_latest: true 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | 116 | release_chocolatey: 117 | needs: [build_windows, release_github] 118 | runs-on: windows-latest 119 | if: github.ref == 'refs/heads/master' 120 | steps: 121 | - uses: actions/checkout@v3 122 | - name: Download all artifacts 123 | uses: actions/download-artifact@v4 124 | with: 125 | path: ./artifacts/ 126 | - name: Run CreatePackage.ps1 script 127 | run: .\CreateChocoPackage\CreatePackage.ps1 128 | shell: powershell 129 | - name: Run PushPackage.ps1 script 130 | run: .\CreateChocoPackage\PushPackage.ps1 -chocolateykey ${{secrets.CHOCOLATEYTOKEN}} 131 | shell: powershell 132 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | .vscode/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # Benchmark Results 47 | BenchmarkDotNet.Artifacts/ 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # Visual Studio Trace Files 101 | *.e2e 102 | 103 | # TFS 2012 Local Workspace 104 | $tf/ 105 | 106 | # Guidance Automation Toolkit 107 | *.gpState 108 | 109 | # ReSharper is a .NET coding add-in 110 | _ReSharper*/ 111 | *.[Rr]e[Ss]harper 112 | *.DotSettings.user 113 | 114 | # JustCode is a .NET coding add-in 115 | .JustCode 116 | 117 | # TeamCity is a build add-in 118 | _TeamCity* 119 | 120 | # DotCover is a Code Coverage Tool 121 | *.dotCover 122 | 123 | # AxoCover is a Code Coverage Tool 124 | .axoCover/* 125 | !.axoCover/settings.json 126 | 127 | # Visual Studio code coverage results 128 | *.coverage 129 | *.coveragexml 130 | *.Coverage.xml 131 | 132 | # NCrunch 133 | _NCrunch_* 134 | .*crunch*.local.xml 135 | nCrunchTemp_* 136 | 137 | # MightyMoose 138 | *.mm.* 139 | AutoTest.Net/ 140 | 141 | # Web workbench (sass) 142 | .sass-cache/ 143 | 144 | # Installshield output folder 145 | [Ee]xpress/ 146 | 147 | # DocProject is a documentation generator add-in 148 | DocProject/buildhelp/ 149 | DocProject/Help/*.HxT 150 | DocProject/Help/*.HxC 151 | DocProject/Help/*.hhc 152 | DocProject/Help/*.hhk 153 | DocProject/Help/*.hhp 154 | DocProject/Help/Html2 155 | DocProject/Help/html 156 | 157 | # Click-Once directory 158 | publish/ 159 | 160 | # Publish Web Output 161 | *.[Pp]ublish.xml 162 | *.azurePubxml 163 | # Note: Comment the next line if you want to checkin your web deploy settings, 164 | # but database connection strings (with potential passwords) will be unencrypted 165 | *.pubxml 166 | *.publishproj 167 | 168 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 169 | # checkin your Azure Web App publish settings, but sensitive information contained 170 | # in these scripts will be unencrypted 171 | PublishScripts/ 172 | 173 | # NuGet Packages 174 | *.nupkg 175 | # The packages folder can be ignored because of Package Restore 176 | **/[Pp]ackages/* 177 | # except build/, which is used as an MSBuild target. 178 | !**/[Pp]ackages/build/ 179 | # Uncomment if necessary however generally it will be regenerated when needed 180 | #!**/[Pp]ackages/repositories.config 181 | # NuGet v3's project.json files produces more ignorable files 182 | *.nuget.props 183 | *.nuget.targets 184 | 185 | # Microsoft Azure Build Output 186 | csx/ 187 | *.build.csdef 188 | 189 | # Microsoft Azure Emulator 190 | ecf/ 191 | rcf/ 192 | 193 | # Windows Store app package directories and files 194 | AppPackages/ 195 | BundleArtifacts/ 196 | Package.StoreAssociation.xml 197 | _pkginfo.txt 198 | *.appx 199 | 200 | # Visual Studio cache files 201 | # files ending in .cache can be ignored 202 | *.[Cc]ache 203 | # but keep track of directories ending in .cache 204 | !*.[Cc]ache/ 205 | 206 | # Others 207 | ClientBin/ 208 | ~$* 209 | *~ 210 | *.dbmdl 211 | *.dbproj.schemaview 212 | *.jfm 213 | *.pfx 214 | *.publishsettings 215 | orleans.codegen.cs 216 | 217 | # Since there are multiple workflows, uncomment next line to ignore bower_components 218 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 219 | #bower_components/ 220 | 221 | # RIA/Silverlight projects 222 | Generated_Code/ 223 | 224 | # Backup & report files from converting an old project file 225 | # to a newer Visual Studio version. Backup files are not needed, 226 | # because we have git ;-) 227 | _UpgradeReport_Files/ 228 | Backup*/ 229 | UpgradeLog*.XML 230 | UpgradeLog*.htm 231 | 232 | # SQL Server files 233 | *.mdf 234 | *.ldf 235 | *.ndf 236 | 237 | # Business Intelligence projects 238 | *.rdl.data 239 | *.bim.layout 240 | *.bim_*.settings 241 | 242 | # Microsoft Fakes 243 | FakesAssemblies/ 244 | 245 | # GhostDoc plugin setting file 246 | *.GhostDoc.xml 247 | 248 | # Node.js Tools for Visual Studio 249 | .ntvs_analysis.dat 250 | node_modules/ 251 | 252 | # Typescript v1 declaration files 253 | typings/ 254 | 255 | # Visual Studio 6 build log 256 | *.plg 257 | 258 | # Visual Studio 6 workspace options file 259 | *.opt 260 | 261 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 262 | *.vbw 263 | 264 | # Visual Studio LightSwitch build output 265 | **/*.HTMLClient/GeneratedArtifacts 266 | **/*.DesktopClient/GeneratedArtifacts 267 | **/*.DesktopClient/ModelManifest.xml 268 | **/*.Server/GeneratedArtifacts 269 | **/*.Server/ModelManifest.xml 270 | _Pvt_Extensions 271 | 272 | # Paket dependency manager 273 | .paket/paket.exe 274 | paket-files/ 275 | 276 | # FAKE - F# Make 277 | .fake/ 278 | 279 | # JetBrains Rider 280 | .idea/ 281 | *.sln.iml 282 | 283 | # CodeRush 284 | .cr/ 285 | 286 | # Python Tools for Visual Studio (PTVS) 287 | __pycache__/ 288 | *.pyc 289 | 290 | # Cake - Uncomment if you are using it 291 | # tools/** 292 | # !tools/packages.config 293 | 294 | # Tabs Studio 295 | *.tss 296 | 297 | # Telerik's JustMock configuration file 298 | *.jmconfig 299 | 300 | # BizTalk build output 301 | *.btp.cs 302 | *.btm.cs 303 | *.odx.cs 304 | *.xsd.cs 305 | 306 | # OpenCover UI analysis results 307 | OpenCover/ 308 | 309 | # Ignored FileOptimizerFiles just incase 310 | /Scripts/FileOptimizerFull.7z.exe 311 | /Scripts/FileOptimizer 312 | /Scripts/Output -------------------------------------------------------------------------------- /CreateChocoPackage/.gitignore: -------------------------------------------------------------------------------- 1 | *.nupkg 2 | tools/*.exe 3 | tools/*.ps1 4 | tools/*.7z 5 | tools/VERIFICATION.txt -------------------------------------------------------------------------------- /CreateChocoPackage/CreatePackage.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $fileToPackage = "artifacts\DeveImageOptimizerWPF_win-x64\DeveImageOptimizerWPF.exe" 4 | 5 | #Install-Module -Name FormatPowerShellCode -Scope CurrentUser -Confirm:$False 6 | 7 | $invocation = (Get-Variable MyInvocation).Value 8 | $directorypath = Split-Path $invocation.MyCommand.Path 9 | $SolutionRoot = Split-Path -Path $directorypath -Parent 10 | 11 | $powerShellHelpersModule = Join-Path $directorypath "PowerShellHelpers" 12 | Import-Module -Name $powerShellHelpersModule 13 | 14 | $fullPathFileToPackage = Join-Path $SolutionRoot $fileToPackage -Resolve 15 | $toolsDir = Join-Path $directorypath "tools" 16 | $templateDir = Join-Path $directorypath "templates" 17 | 18 | $destinationFileName = [System.IO.Path]::GetFileName($fullPathFileToPackage) 19 | $destinationFileNameNoExtension = [System.IO.Path]::GetFileNameWithoutExtension($fullPathFileToPackage) 20 | $destinationFilePath = Join-Path $toolsDir $destinationFileName 21 | $destinationFilePath7z = Join-path $toolsDir "$($destinationFileNameNoExtension).7z" 22 | $chocoinstallpsfilepath = Join-path $toolsDir "chocolateyinstall.ps1" 23 | $chocouninstallpsfilepath = Join-path $toolsDir "chocolateyuninstall.ps1" 24 | $chocobeforemodifypsfilepath = Join-path $toolsDir "chocolateybeforemodify.ps1" 25 | $verificationfilepath = Join-Path $toolsDir "VERIFICATION.txt" 26 | 27 | $chocoinstallpsfiletemplatepath = Join-path $templateDir "chocolateyinstall.ps1" 28 | $chocouninstallpsfiletemplatepath = Join-path $templateDir "chocolateyuninstall.ps1" 29 | $chocobeforemodifypsfiletemplatepath = Join-path $templateDir "chocolateybeforemodify.ps1" 30 | $verificationfiletemplatepath = Join-Path $templateDir "VERIFICATION.txt" 31 | 32 | if (Test-Path $destinationFilePath) 33 | { 34 | Remove-Item $destinationFilePath 35 | } 36 | if (Test-Path $destinationFilePath7z) 37 | { 38 | Remove-Item $destinationFilePath7z 39 | } 40 | 41 | 7z a -t7z -m0=LZMA2 -mmt=on -mx9 -md=1536m -mfb=273 -ms=on -mqs=on -sccUTF-8 "$destinationFilePath7z" "$fullPathFileToPackage" 42 | #Copy-Item $fullPathFileToPackage $destinationFilePath 43 | 44 | Write-Host "Copying templates..." 45 | 46 | Copy-Item $chocoinstallpsfiletemplatepath $chocoinstallpsfilepath 47 | #Copy-Item $chocouninstallpsfiletemplatepath $chocouninstallpsfilepath 48 | #Copy-Item $chocobeforemodifypsfiletemplatepath $chocobeforemodifypsfilepath 49 | Copy-Item $verificationfiletemplatepath $verificationfilepath 50 | 51 | Write-Host "Removing comments..." 52 | Remove-Comments($chocoinstallpsfilepath) 53 | #Remove-Comments($chocouninstallpsfilepath) 54 | #Remove-Comments($chocobeforemodifypsfilepath) 55 | 56 | 57 | $ReleaseVersionNumberFull = (Get-Item $fullPathFileToPackage).VersionInfo.FileVersion 58 | $ReleaseVersionNumberShort = $ReleaseVersionNumberFull.Substring(0, $ReleaseVersionNumberFull.LastIndexOf('.')) 59 | $checksum = (Get-FileHash $fullPathFileToPackage -Algorithm SHA256).Hash 60 | #$checksum = checksum -t sha256 -f $fullPathFileToPackage 61 | #$checksum = checksum -t sha256 -f $destinationFilePath7z 62 | 63 | $nuspecFile = (Get-ChildItem "$($directorypath)\*" -include *.nuspec).FullName 64 | 65 | 66 | $re = [regex]"(?<=).*(?=<\/version>)" 67 | $reChecksum = [regex]"{checksumtoreplace}" 68 | 69 | Write-Host "Setting version $ReleaseVersionNumberShort in $nuspecFile by using Regex: $re" 70 | 71 | $re.Replace([string]::Join("`n", (Get-Content -Path $nuspecFile)), "$ReleaseVersionNumberShort", 1) | Set-Content -Path $nuspecFile -Encoding UTF8 72 | 73 | Write-Host "Setting checksum $checksum in $verificationfilepath by using Regex: $reChecksum" 74 | 75 | $reChecksum.Replace([string]::Join("`n", (Get-Content -Path $verificationfilepath)), "$checksum", 1) | Set-Content -Path $verificationfilepath -Encoding UTF8 76 | 77 | Push-Location -Path $directorypath 78 | choco pack 79 | Pop-Location 80 | -------------------------------------------------------------------------------- /CreateChocoPackage/PowerShellHelpers.psm1: -------------------------------------------------------------------------------- 1 | Function Remove-Comments($Path) 2 | { 3 | Get-ChildItem $Path -ErrorAction Stop | Out-Null 4 | $ScriptBlockString = [IO.File]::ReadAllText((Resolve-Path $Path)) 5 | $ScriptBlock = [ScriptBlock]::Create($ScriptBlockString) 6 | 7 | 8 | # Tokenize the scriptblock and return all tokens except for comments 9 | $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptBlock, [Ref] $Null) | Where-Object { $_.Type -ne 'Comment' } 10 | 11 | $StringBuilder = New-Object Text.StringBuilder 12 | 13 | # The majority of the remaining code comes from Lee Holmes' Show-ColorizedContent script. 14 | $CurrentColumn = 1 15 | $NewlineCount = 0 16 | foreach($CurrentToken in $Tokens) 17 | { 18 | # Now output the token 19 | if(($CurrentToken.Type -eq 'NewLine') -or ($CurrentToken.Type -eq 'LineContinuation')) 20 | { 21 | $CurrentColumn = 1 22 | # Only insert a single newline. Sequential newlines are ignored in order to save space. 23 | if ($NewlineCount -eq 0) 24 | { 25 | $StringBuilder.AppendLine() | Out-Null 26 | } 27 | $NewlineCount++ 28 | } 29 | else 30 | { 31 | $NewlineCount = 0 32 | 33 | # Do any indenting 34 | if($CurrentColumn -lt $CurrentToken.StartColumn) 35 | { 36 | # Insert a single space in between tokens on the same line. Extraneous whiltespace is ignored. 37 | if ($CurrentColumn -ne 1) 38 | { 39 | $StringBuilder.Append(' ') | Out-Null 40 | } 41 | } 42 | 43 | # See where the token ends 44 | $CurrentTokenEnd = $CurrentToken.Start + $CurrentToken.Length - 1 45 | 46 | # Handle the line numbering for multi-line strings 47 | if(($CurrentToken.Type -eq 'String') -and ($CurrentToken.EndLine -gt $CurrentToken.StartLine)) 48 | { 49 | $LineCounter = $CurrentToken.StartLine 50 | $StringLines = $(-join $ScriptBlockString[$CurrentToken.Start..$CurrentTokenEnd] -split '`r`n') 51 | 52 | foreach($StringLine in $StringLines) 53 | { 54 | $StringBuilder.Append($StringLine) | Out-Null 55 | $LineCounter++ 56 | } 57 | } 58 | # Write out a regular token 59 | else 60 | { 61 | $StringBuilder.Append((-join $ScriptBlockString[$CurrentToken.Start..$CurrentTokenEnd])) | Out-Null 62 | } 63 | 64 | # Update our position in the column 65 | $CurrentColumn = $CurrentToken.EndColumn 66 | } 67 | } 68 | 69 | $StringBuilder.ToString() | Set-Content -Path $Path 70 | #Write-Output ([ScriptBlock]::Create($StringBuilder.ToString())) 71 | } -------------------------------------------------------------------------------- /CreateChocoPackage/PushPackage.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory=$true)] 3 | [string]$chocolateykey 4 | ) 5 | 6 | $ErrorActionPreference = "Stop" 7 | 8 | Write-Host "Pushing to Chocolatey..." 9 | 10 | $invocation = (Get-Variable MyInvocation).Value 11 | $directorypath = Split-Path $invocation.MyCommand.Path 12 | 13 | $nupkgFile = (Get-ChildItem "$($directorypath)\*" -include *.nupkg).FullName 14 | choco push $nupkgFile -s "https://push.chocolatey.org/" -k $chocolateykey -------------------------------------------------------------------------------- /CreateChocoPackage/ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | How do I create packages? See https://chocolatey.org/docs/create-packages 3 | 4 | If you are submitting packages to the community feed (https://chocolatey.org) 5 | always try to ensure you have read, understood and adhere to the create 6 | packages wiki link above. 7 | 8 | ## Automatic Packaging Updates? 9 | Consider making this package an automatic package, for the best 10 | maintainability over time. Read up at https://chocolatey.org/docs/automatic-packages 11 | 12 | ## Shim Generation 13 | Any executables you include in the package or download (but don't call 14 | install against using the built-in functions) will be automatically shimmed. 15 | 16 | This means those executables will automatically be included on the path. 17 | Shim generation runs whether the package is self-contained or uses automation 18 | scripts. 19 | 20 | By default, these are considered console applications. 21 | 22 | If the application is a GUI, you should create an empty file next to the exe 23 | named 'name.exe.gui' e.g. 'bob.exe' would need a file named 'bob.exe.gui'. 24 | See https://chocolatey.org/docs/create-packages#how-do-i-set-up-shims-for-applications-that-have-a-gui 25 | 26 | If you want to ignore the executable, create an empty file next to the exe 27 | named 'name.exe.ignore' e.g. 'bob.exe' would need a file named 28 | 'bob.exe.ignore'. 29 | See https://chocolatey.org/docs/create-packages#how-do-i-exclude-executables-from-getting-shims 30 | 31 | ## Self-Contained? 32 | If you have a self-contained package, you can remove the automation scripts 33 | entirely and just include the executables, they will automatically get shimmed, 34 | which puts them on the path. Ensure you have the legal right to distribute 35 | the application though. See https://chocolatey.org/docs/legal. 36 | 37 | You should read up on the Shim Generation section to familiarize yourself 38 | on what to do with GUI applications and/or ignoring shims. 39 | 40 | ## Automation Scripts 41 | You have a powerful use of Chocolatey, as you are using PowerShell. So you 42 | can do just about anything you need. Choco has some very handy built-in 43 | functions that you can use, these are sometimes called the helpers. 44 | 45 | ### Built-In Functions 46 | https://chocolatey.org/docs/helpers-reference 47 | 48 | A note about a couple: 49 | * Get-BinRoot - this is a horribly named function that doesn't do what new folks think it does. It gets you the 'tools' root, which by default is set to 'c:\tools', not the chocolateyInstall bin folder - see https://chocolatey.org/docs/helpers-get-tools-location 50 | * Install-BinFile - used for non-exe files - executables are automatically shimmed... - see https://chocolatey.org/docs/helpers-install-bin-file 51 | * Uninstall-BinFile - used for non-exe files - executables are automatically shimmed - see https://chocolatey.org/docs/helpers-uninstall-bin-file 52 | 53 | ### Getting package specific information 54 | Use the package parameters pattern - see https://chocolatey.org/docs/how-to-parse-package-parameters-argument 55 | 56 | ### Need to mount an ISO? 57 | https://chocolatey.org/docs/how-to-mount-an-iso-in-chocolatey-package 58 | 59 | ### Environment Variables 60 | Chocolatey makes a number of environment variables available (You can access any of these with $env:TheVariableNameBelow): 61 | 62 | * TEMP/TMP - Overridden to the CacheLocation, but may be the same as the original TEMP folder 63 | * ChocolateyInstall - Top level folder where Chocolatey is installed 64 | * ChocolateyPackageName - The name of the package, equivalent to the `` field in the nuspec (0.9.9+) 65 | * ChocolateyPackageTitle - The title of the package, equivalent to the `` field in the nuspec (0.10.1+) 66 | * ChocolateyPackageVersion - The version of the package, equivalent to the `<version />` field in the nuspec (0.9.9+) 67 | * ChocolateyPackageFolder - The top level location of the package folder - the folder where Chocolatey has downloaded and extracted the NuGet package, typically `C:\ProgramData\chocolatey\lib\packageName`. 68 | 69 | #### Advanced Environment Variables 70 | The following are more advanced settings: 71 | 72 | * ChocolateyPackageParameters - Parameters to use with packaging, not the same as install arguments (which are passed directly to the native installer). Based on `--package-parameters`. (0.9.8.22+) 73 | * CHOCOLATEY_VERSION - The version of Choco you normally see. Use if you are 'lighting' things up based on choco version. (0.9.9+) - Otherwise take a dependency on the specific version you need. 74 | * ChocolateyForceX86 = If available and set to 'true', then user has requested 32bit version. (0.9.9+) - Automatically handled in built in Choco functions. 75 | * OS_PLATFORM - Like Windows, OSX, Linux. (0.9.9+) 76 | * OS_VERSION - The version of OS, like 6.1 something something for Windows. (0.9.9+) 77 | * OS_NAME - The reported name of the OS. (0.9.9+) 78 | * USER_NAME = The user name (0.10.6+) 79 | * USER_DOMAIN = The user domain name (could also be local computer name) (0.10.6+) 80 | * IS_PROCESSELEVATED = Is the process elevated? (0.9.9+) 81 | * IS_SYSTEM = Is the user the system account? (0.10.6+) 82 | * IS_REMOTEDESKTOP = Is the user in a terminal services session? (0.10.6+) 83 | * ChocolateyToolsLocation - formerly 'ChocolateyBinRoot' ('ChocolateyBinRoot' will be removed with Chocolatey v2.0.0), this is where tools being installed outside of Chocolatey packaging will go. (0.9.10+) 84 | 85 | #### Set By Options and Configuration 86 | Some environment variables are set based on options that are passed, configuration and/or features that are turned on: 87 | 88 | * ChocolateyEnvironmentDebug - Was `--debug` passed? If using the built-in PowerShell host, this is always true (but only logs debug messages to console if `--debug` was passed) (0.9.10+) 89 | * ChocolateyEnvironmentVerbose - Was `--verbose` passed? If using the built-in PowerShell host, this is always true (but only logs verbose messages to console if `--verbose` was passed). (0.9.10+) 90 | * ChocolateyForce - Was `--force` passed? (0.9.10+) 91 | * ChocolateyForceX86 - Was `-x86` passed? (CHECK) 92 | * ChocolateyRequestTimeout - How long before a web request will time out. Set by config `webRequestTimeoutSeconds` (CHECK) 93 | * ChocolateyResponseTimeout - How long to wait for a download to complete? Set by config `commandExecutionTimeoutSeconds` (CHECK) 94 | * ChocolateyPowerShellHost - Are we using the built-in PowerShell host? Set by `--use-system-powershell` or the feature `powershellHost` (0.9.10+) 95 | 96 | #### Business Edition Variables 97 | 98 | * ChocolateyInstallArgumentsSensitive - Encrypted arguments passed from command line `--install-arguments-sensitive` that are not logged anywhere. (0.10.1+ and licensed editions 1.6.0+) 99 | * ChocolateyPackageParametersSensitive - Package parameters passed from command line `--package-parameters-senstivite` that are not logged anywhere. (0.10.1+ and licensed editions 1.6.0+) 100 | * ChocolateyLicensedVersion - What version is the licensed edition on? 101 | * ChocolateyLicenseType - What edition / type of the licensed edition is installed? 102 | * USER_CONTEXT - The original user context - different when self-service is used (Licensed v1.10.0+) 103 | 104 | #### Experimental Environment Variables 105 | The following are experimental or use not recommended: 106 | 107 | * OS_IS64BIT = This may not return correctly - it may depend on the process the app is running under (0.9.9+) 108 | * CHOCOLATEY_VERSION_PRODUCT = the version of Choco that may match CHOCOLATEY_VERSION but may be different (0.9.9+) - based on git describe 109 | * IS_ADMIN = Is the user an administrator? But doesn't tell you if the process is elevated. (0.9.9+) 110 | * IS_REMOTE = Is the user in a remote session? (0.10.6+) 111 | 112 | #### Not Useful Or Anti-Pattern If Used 113 | 114 | * ChocolateyInstallOverride = Not for use in package automation scripts. Based on `--override-arguments` being passed. (0.9.9+) 115 | * ChocolateyInstallArguments = The installer arguments meant for the native installer. You should use chocolateyPackageParameters intead. Based on `--install-arguments` being passed. (0.9.9+) 116 | * ChocolateyIgnoreChecksums - Was `--ignore-checksums` passed or the feature `checksumFiles` turned off? (0.9.9.9+) 117 | * ChocolateyAllowEmptyChecksums - Was `--allow-empty-checksums` passed or the feature `allowEmptyChecksums` turned on? (0.10.0+) 118 | * ChocolateyAllowEmptyChecksumsSecure - Was `--allow-empty-checksums-secure` passed or the feature `allowEmptyChecksumsSecure` turned on? (0.10.0+) 119 | * ChocolateyCheckLastExitCode - Should Chocolatey check LASTEXITCODE? Is the feature `scriptsCheckLastExitCode` turned on? (0.10.3+) 120 | * ChocolateyChecksum32 - Was `--download-checksum` passed? (0.10.0+) 121 | * ChocolateyChecksumType32 - Was `--download-checksum-type` passed? (0.10.0+) 122 | * ChocolateyChecksum64 - Was `--download-checksum-x64` passed? (0.10.0)+ 123 | * ChocolateyChecksumType64 - Was `--download-checksum-type-x64` passed? (0.10.0)+ 124 | * ChocolateyPackageExitCode - The exit code of the script that just ran - usually set by `Set-PowerShellExitCode` (CHECK) 125 | * ChocolateyLastPathUpdate - Set by Chocolatey as part of install, but not used for anything in particular in packaging. 126 | * ChocolateyProxyLocation - The explicit proxy location as set in the configuration `proxy` (0.9.9.9+) 127 | * ChocolateyDownloadCache - Use available download cache? Set by `--skip-download-cache`, `--use-download-cache`, or feature `downloadCache` (0.9.10+ and licensed editions 1.1.0+) 128 | * ChocolateyProxyBypassList - Explicitly set locations to ignore in configuration `proxyBypassList` (0.10.4+) 129 | * ChocolateyProxyBypassOnLocal - Should the proxy bypass on local connections? Set based on configuration `proxyBypassOnLocal` (0.10.4+) 130 | * http_proxy - Set by original `http_proxy` passthrough, or same as `ChocolateyProxyLocation` if explicitly set. (0.10.4+) 131 | * https_proxy - Set by original `https_proxy` passthrough, or same as `ChocolateyProxyLocation` if explicitly set. (0.10.4+) 132 | * no_proxy- Set by original `no_proxy` passthrough, or same as `ChocolateyProxyBypassList` if explicitly set. (0.10.4+) 133 | 134 | -------------------------------------------------------------------------------- /CreateChocoPackage/TestInstall.cmd: -------------------------------------------------------------------------------- 1 | cd /d %~dp0 2 | echo Make sure to run this as administrator 3 | pause 4 | choco install deveimageoptimizerwpf -dv -s . 5 | pause -------------------------------------------------------------------------------- /CreateChocoPackage/_TODO.txt: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | 1. Determine Package Use: 4 | 5 | Organization? Internal Use? - You are not subject to distribution 6 | rights when you keep everything internal. Put the binaries directly 7 | into the tools directory (as long as total nupkg size is under 1GB). 8 | When bigger, look to use from a share or download binaries from an 9 | internal location. Embedded binaries makes for the most reliable use 10 | of Chocolatey. Use `$fileLocation` (`$file`/`$file64`) and 11 | `Install-ChocolateyInstallPackage`/`Get-ChocolateyUnzip` in 12 | tools\chocolateyInstall.ps1. 13 | 14 | You can also choose to download from internal urls, see the next 15 | section, but ignore whether you have distribution rights or not, it 16 | doesn't apply. Under no circumstances should download from the 17 | internet, it is completely unreliable. See 18 | https://chocolatey.org/docs/community-packages-disclaimer#organizations 19 | to understand the limitations of a publicly available repository. 20 | 21 | Community Repository? 22 | Have Distribution Rights? 23 | If you are the software vendor OR the software EXPLICITLY allows 24 | redistribution and the total nupkg size will be under 200MB, you 25 | have the option to embed the binaries directly into the package to 26 | provide the most reliable install experience. Put the binaries 27 | directly into the tools folder, use `$fileLocation` (`$file`/ 28 | `$file64`) and `Install-ChocolateyInstallPackage`/ 29 | `Get-ChocolateyUnzip` in tools\chocolateyInstall.ps1. Additionally, 30 | fill out the LICENSE and VERIFICATION file (see 3 below and those 31 | files for specifics). 32 | 33 | NOTE: You can choose to download binaries at runtime, but be sure 34 | the download location will remain stable. See the next section. 35 | 36 | Do Not Have Distribution Rights? 37 | - Note: Packages built this way cannot be 100% reliable, but it's a 38 | constraint of publicly available packages and there is little 39 | that can be done to change that. See 40 | https://chocolatey.org/docs/community-packages-disclaimer#organizations 41 | to better understand the limitations of a publicly available 42 | repository. 43 | Download Location is Publicly Available? 44 | You will need to download the runtime files from their official 45 | location at runtime. Use `$url`/`$url64` and 46 | `Install-ChocolateyPackage`/`Install-ChocolateyZipPackage` in 47 | tools\chocolateyInstall.ps1. 48 | Download Location is Not Publicly Available? 49 | Stop here, you can't push this to the community repository. You 50 | can ask the vendor for permission to embed, then include a PDF of 51 | that signed permission directly in the package. Otherwise you 52 | will need to seek alternate locations to non-publicly host the 53 | package. 54 | Download Location Is Same For All Versions? 55 | You still need to point to those urls, but you may wish to set up 56 | something like Automatic Updater (AU) so that when a new version 57 | of the software becomes available, the new package version 58 | automatically gets pushed up to the community repository. See 59 | https://chocolatey.org/docs/automatic-packages#automatic-updater-au 60 | 61 | 2. Determine Package Type: 62 | 63 | - Installer Package - contains an installer (everything in template is 64 | geared towards this type of package) 65 | - Zip Package - downloads or embeds and unpacks archives, may unpack 66 | and run an installer using `Install-ChocolateyInstallPackage` as a 67 | secondary step. 68 | - Portable Package - Contains runtime binaries (or unpacks them as a 69 | zip package) - cannot require administrative permissions to install 70 | or use 71 | - Config Package - sets config like files, registry keys, etc 72 | - Extension Package - Packages that add PowerShell functions to 73 | Chocolatey - https://chocolatey.org/docs/how-to-create-extensions 74 | - Template Package - Packages that add templates like this for `choco 75 | new -t=name` - https://chocolatey.org/docs/how-to-create-custom-package-templates 76 | - Other - there are other types of packages as well, these are the main 77 | package types seen in the wild 78 | 79 | 3. Fill out the package contents: 80 | 81 | - tools\chocolateyBeforeModify.ps1 - remove if you have no processes 82 | or services to shut down before upgrade/uninstall 83 | - tools\LICENSE.txt / tools\VERIFICATION.txt - Remove if you are not 84 | embedding binaries. Keep and fill out if you are embedding binaries 85 | in the package AND pushing to the community repository, even if you 86 | are the author of software. The file becomes easier to fill out 87 | (does not require changes each version) if you are the software 88 | vendor. If you are building packages for internal use (organization, 89 | etc), you don't need these files as you are not subject to 90 | distribution rights internally. 91 | - tools\chocolateyUninstall.ps1 - remove if autouninstaller can 92 | automatically uninstall and you have nothing additional to do during 93 | uninstall 94 | - Readme.txt - delete this file once you have read over and used 95 | anything you've needed from here 96 | - nuspec - fill this out, then clean out all the comments (you may wish 97 | to leave the headers for the package vs software metadata) 98 | - tools\chocolateyInstall.ps1 - instructions in next section. 99 | 100 | 4. ChocolateyInstall.ps1: 101 | 102 | - For embedded binaries - use `$fileLocation` (`$file`/`$file64`) and 103 | `Install-ChocolateyInstallPackage`/ `Get-ChocolateyUnzip`. 104 | - Downloading binaries at runtime - use `$url`/`$url64` and 105 | `Install-ChocolateyPackage` / `Install-ChocolateyZipPackage`. 106 | - Other needs (creating files, setting registry keys), use regular 107 | PowerShell to do so or see if there is a function already defined: 108 | https://chocolatey.org/docs/helpers-reference 109 | - There may also be functions available in extension packages, see 110 | https://chocolatey.org/packages?q=id%3A.extension for examples and 111 | availability. 112 | - Clean out the comments and sections you are not using. 113 | 114 | 5. Test the package to ensure install/uninstall work appropriately. 115 | There is a test environment you can use for this - 116 | https://github.com/chocolatey/chocolatey-test-environment 117 | 118 | 6. Learn more about Chocolatey packaging - go through the workshop at 119 | https://github.com/ferventcoder/chocolatey-workshop 120 | You will learn about 121 | - General packaging 122 | - Customizing package behavior at runtime (package parameters) 123 | - Extension packages 124 | - Custom packaging templates 125 | - Setting up an internal Chocolatey.Server repository 126 | - Adding and using internal repositories 127 | - Reporting 128 | - Advanced packaging techniques when installers are not friendly to 129 | automation 130 | 131 | 7. Delete this file. 132 | -------------------------------------------------------------------------------- /CreateChocoPackage/deveimageoptimizerwpf.nuspec: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"> 3 | <metadata> 4 | <id>deveimageoptimizerwpf</id> 5 | <version>1.0.101</version> 6 | <title>DeveImageOptimizerWPF (Install) 7 | Devedse 8 | https://github.com/devedse/DeveImageOptimizerWPF 9 | https://rawcdn.githack.com/devedse/DeveImageOptimizerWPF/41e170e1313466cc768e6fe09428b186dd829a6d/Icon.png 10 | https://github.com/devedse/DeveImageOptimizerWPF/blob/master/LICENSE 11 | true 12 | deveimageoptimizerwpf image optimizer compression fileoptimizerfull fileoptimizer file 13 | Uses FileOptimizerFull to compress/otpimize Images 14 | This is a tool written around FileOptimizerFull. It optimizes images in parallel and then verifies if the source exactly matches the output. 15 | https://github.com/devedse/DeveImageOptimizerWPF/releases 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CreateChocoPackage/templates/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | I am the software vendor, this package is automatically created by an AppVeyor build. 6 | https://github.com/devedse/DeveImageOptimizerWPF 7 | 8 | The Checksum of this tool is: 9 | {checksumtoreplace} 10 | This checksum can be verified by using the checksum tool (choco install checksum): 11 | checksum -t sha256 -f DeveImageOptimizerWPF.exe -------------------------------------------------------------------------------- /CreateChocoPackage/templates/chocolateybeforemodify.ps1: -------------------------------------------------------------------------------- 1 | # This runs in 0.9.10+ before upgrade and uninstall. 2 | # Use this file to do things like stop services prior to upgrade or uninstall. 3 | # NOTE: It is an anti-pattern to call chocolateyUninstall.ps1 from here. If you 4 | # need to uninstall an MSI prior to upgrade, put the functionality in this 5 | # file without calling the uninstall script. Make it idempotent in the 6 | # uninstall script so that it doesn't fail when it is already uninstalled. 7 | # NOTE: For upgrades - like the uninstall script, this script always runs from 8 | # the currently installed version, not from the new upgraded package version. 9 | 10 | -------------------------------------------------------------------------------- /CreateChocoPackage/templates/chocolateyinstall.ps1: -------------------------------------------------------------------------------- 1 | # IMPORTANT: Before releasing this package, copy/paste the next 2 lines into PowerShell to remove all comments from this file: 2 | # $f='c:\path\to\thisFile.ps1' 3 | # gc $f | ? {$_ -notmatch "^\s*#"} | % {$_ -replace '(^.*?)\s*?[^``]#.*','$1'} | Out-File $f+".~" -en utf8; mv -fo $f+".~" $f 4 | 5 | # 1. See the _TODO.md that is generated top level and read through that 6 | # 2. Follow the documentation below to learn how to create a package for the package type you are creating. 7 | # 3. In Chocolatey scripts, ALWAYS use absolute paths - $toolsDir gets you to the package's tools directory. 8 | $ErrorActionPreference = 'Stop'; # stop on all errors 9 | $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 10 | # Internal packages (organizations) or software that has redistribution rights (community repo) 11 | # - Use `Install-ChocolateyInstallPackage` instead of `Install-ChocolateyPackage` 12 | # and put the binaries directly into the tools folder (we call it embedding) 13 | $fileLocation = Join-Path $toolsDir 'DeveImageOptimizerWPF.7z' 14 | # If embedding binaries increase total nupkg size to over 1GB, use share location or download from urls 15 | #$fileLocation = '\\SHARE_LOCATION\to\INSTALLER_FILE' 16 | # Community Repo: Use official urls for non-redist binaries or redist where total package size is over 200MB 17 | # Internal/Organization: Download from internal location (internet sources are unreliable) 18 | # $url = '' # download url, HTTPS preferred 19 | # $url64 = '' # 64bit URL here (HTTPS preferred) or remove - if installer contains both (very rare), use $url 20 | 21 | # $checksumOfFile = "{checksumtoreplace}" 22 | 23 | # $packageArgs = @{ 24 | # packageName = $env:ChocolateyPackageName 25 | # unzipLocation = $toolsDir 26 | # fileType = 'exe' #only one of these: exe, msi, msu 27 | # #url = $url 28 | # #url64bit = $url64 29 | # file = $fileLocation 30 | 31 | # softwareName = 'DeveImageOptimizerWPF*' #part or all of the Display Name as you see it in Programs and Features. It should be enough to be unique 32 | 33 | # # Checksums are now required as of 0.10.0. 34 | # # To determine checksums, you can get that from the original site if provided. 35 | # # You can also use checksum.exe (choco install checksum) and use it 36 | # # e.g. checksum -t sha256 -f path\to\file 37 | # checksum = $checksumOfFile 38 | # checksumType = 'sha256' #default is md5, can also be sha1, sha256 or sha512 39 | # checksum64 = $checksumOfFile 40 | # checksumType64= 'sha256' #default is checksumType 41 | 42 | # # MSI 43 | # silentArgs = "" # ALLUSERS=1 DISABLEDESKTOPSHORTCUT=1 ADDDESKTOPICON=0 ADDSTARTMENU=0 44 | # validExitCodes= @(0, 3010, 1641) 45 | # # OTHERS 46 | # # Uncomment matching EXE type (sorted by most to least common) 47 | # #silentArgs = '/S' # NSIS 48 | # #silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' # Inno Setup 49 | # #silentArgs = '/s' # InstallShield 50 | # #silentArgs = '/s /v"/qn"' # InstallShield with MSI 51 | # #silentArgs = '/s' # Wise InstallMaster 52 | # #silentArgs = '-s' # Squirrel 53 | # #silentArgs = '-q' # Install4j 54 | # #silentArgs = '-s' # Ghost 55 | # # Note that some installers, in addition to the silentArgs above, may also need assistance of AHK to achieve silence. 56 | # #silentArgs = '' # none; make silent with input macro script like AutoHotKey (AHK) 57 | # # https://chocolatey.org/packages/autohotkey.portable 58 | # #validExitCodes= @(0) #please insert other valid exit codes here 59 | # } 60 | 61 | Get-ChocolateyUnzip -FileFullPath $fileLocation -Destination $toolsDir -PackageName $env:ChocolateyPackageName 62 | #Install-ChocolateyZipPackage @packageArgs # https://chocolatey.org/docs/helpers-install-chocolatey-package 63 | #Install-ChocolateyZipPackage @packageArgs # https://chocolatey.org/docs/helpers-install-chocolatey-zip-package 64 | ## If you are making your own internal packages (organizations), you can embed the installer or 65 | ## put on internal file share and use the following instead (you'll need to add $file to the above) 66 | #Install-ChocolateyInstallPackage @packageArgs # https://chocolatey.org/docs/helpers-install-chocolatey-install-package 67 | 68 | ## Main helper functions - these have error handling tucked into them already 69 | ## see https://chocolatey.org/docs/helpers-reference 70 | 71 | ## Install an application, will assert administrative rights 72 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-package 73 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-install-package 74 | ## add additional optional arguments as necessary 75 | ##Install-ChocolateyPackage $packageName $fileType $silentArgs $url [$url64 -validExitCodes $validExitCodes -checksum $checksum -checksumType $checksumType -checksum64 $checksum64 -checksumType64 $checksumType64] 76 | 77 | ## Download and unpack a zip file - https://chocolatey.org/docs/helpers-install-chocolatey-zip-package 78 | ##Install-ChocolateyZipPackage $packageName $url $toolsDir [$url64 -checksum $checksum -checksumType $checksumType -checksum64 $checksum64 -checksumType64 $checksumType64] 79 | 80 | ## Install Visual Studio Package - https://chocolatey.org/docs/helpers-install-chocolatey-vsix-package 81 | #Install-ChocolateyVsixPackage $packageName $url [$vsVersion] [-checksum $checksum -checksumType $checksumType] 82 | #Install-ChocolateyVsixPackage @packageArgs 83 | 84 | ## see the full list at https://chocolatey.org/docs/helpers-reference 85 | 86 | ## downloader that the main helpers use to download items 87 | ## if removing $url64, please remove from here 88 | ## - https://chocolatey.org/docs/helpers-get-chocolatey-web-file 89 | #Get-ChocolateyWebFile $packageName 'DOWNLOAD_TO_FILE_FULL_PATH' $url $url64 90 | 91 | ## Installer, will assert administrative rights - used by Install-ChocolateyPackage 92 | ## use this for embedding installers in the package when not going to community feed or when you have distribution rights 93 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-install-package 94 | #Install-ChocolateyInstallPackage $packageName $fileType $silentArgs '_FULLFILEPATH_' -validExitCodes $validExitCodes 95 | 96 | ## Unzips a file to the specified location - auto overwrites existing content 97 | ## - https://chocolatey.org/docs/helpers-get-chocolatey-unzip 98 | #Get-ChocolateyUnzip "FULL_LOCATION_TO_ZIP.zip" $toolsDir 99 | 100 | ## Runs processes asserting UAC, will assert administrative rights - used by Install-ChocolateyInstallPackage 101 | ## - https://chocolatey.org/docs/helpers-start-chocolatey-process-as-admin 102 | #Start-ChocolateyProcessAsAdmin 'STATEMENTS_TO_RUN' 'Optional_Application_If_Not_PowerShell' -validExitCodes $validExitCodes 103 | 104 | ## To avoid quoting issues, you can also assemble your -Statements in another variable and pass it in 105 | #$appPath = "$env:ProgramFiles\appname" 106 | ##Will resolve to C:\Program Files\appname 107 | #$statementsToRun = "/C `"$appPath\bin\installservice.bat`"" 108 | #Start-ChocolateyProcessAsAdmin $statementsToRun cmd -validExitCodes $validExitCodes 109 | 110 | ## add specific folders to the path - any executables found in the chocolatey package 111 | ## folder will already be on the path. This is used in addition to that or for cases 112 | ## when a native installer doesn't add things to the path. 113 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-path 114 | #Install-ChocolateyPath 'LOCATION_TO_ADD_TO_PATH' 'User_OR_Machine' # Machine will assert administrative rights 115 | 116 | ## Add specific files as shortcuts to the desktop 117 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-shortcut 118 | #$target = Join-Path $toolsDir "$($packageName).exe" 119 | # Install-ChocolateyShortcut -shortcutFilePath "" -targetPath "" [-workDirectory "C:\" -arguments "C:\test.txt" -iconLocation "C:\test.ico" -description "This is the description"] 120 | 121 | ## Outputs the bitness of the OS (either "32" or "64") 122 | ## - https://chocolatey.org/docs/helpers-get-o-s-architecture-width 123 | #$osBitness = Get-ProcessorBits 124 | 125 | ## Set persistent Environment variables 126 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-environment-variable 127 | #Install-ChocolateyEnvironmentVariable -variableName "SOMEVAR" -variableValue "value" [-variableType = 'Machine' #Defaults to 'User'] 128 | 129 | ## Set up a file association 130 | ## - https://chocolatey.org/docs/helpers-install-chocolatey-file-association 131 | #Install-ChocolateyFileAssociation 132 | 133 | ## Adding a shim when not automatically found - Cocolatey automatically shims exe files found in package directory. 134 | ## - https://chocolatey.org/docs/helpers-install-bin-file 135 | ## - https://chocolatey.org/docs/create-packages#how-do-i-exclude-executables-from-getting-shims 136 | #Install-BinFile 137 | 138 | #PORTABLE EXAMPLE 139 | #$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 140 | # despite the name "Install-ChocolateyZipPackage" this also works with 7z archives 141 | #Install-ChocolateyZipPackage $packageName $url $toolsDir $url64 142 | # END PORTABLE EXAMPLE 143 | 144 | ## [DEPRECATING] PORTABLE EXAMPLE 145 | #$binRoot = Get-BinRoot 146 | #$installDir = Join-Path $binRoot "$packageName" 147 | #Write-Host "Adding `'$installDir`' to the path and the current shell path" 148 | #Install-ChocolateyPath "$installDir" 149 | #$env:Path = "$($env:Path);$installDir" 150 | 151 | # if removing $url64, please remove from here 152 | # despite the name "Install-ChocolateyZipPackage" this also works with 7z archives 153 | #Install-ChocolateyZipPackage "$packageName" "$url" "$installDir" "$url64" 154 | ## END PORTABLE EXAMPLE 155 | -------------------------------------------------------------------------------- /CreateChocoPackage/templates/chocolateyuninstall.ps1: -------------------------------------------------------------------------------- 1 | # IMPORTANT: Before releasing this package, copy/paste the next 2 lines into PowerShell to remove all comments from this file: 2 | # $f='c:\path\to\thisFile.ps1' 3 | # gc $f | ? {$_ -notmatch "^\s*#"} | % {$_ -replace '(^.*?)\s*?[^``]#.*','$1'} | Out-File $f+".~" -en utf8; mv -fo $f+".~" $f 4 | 5 | ## NOTE: In 80-90% of the cases (95% with licensed versions due to Package Synchronizer and other enhancements), 6 | ## AutoUninstaller should be able to detect and handle registry uninstalls without a chocolateyUninstall.ps1. 7 | ## See https://chocolatey.org/docs/commands-uninstall 8 | ## and https://chocolatey.org/docs/helpers-uninstall-chocolatey-package 9 | 10 | ## If this is an MSI, ensure 'softwareName' is appropriate, then clean up comments and you are done. 11 | ## If this is an exe, change fileType, silentArgs, and validExitCodes 12 | 13 | $ErrorActionPreference = 'Stop'; # stop on all errors 14 | $packageArgs = @{ 15 | packageName = $env:ChocolateyPackageName 16 | softwareName = 'DeveImageOptimizerWPF*' #part or all of the Display Name as you see it in Programs and Features. It should be enough to be unique 17 | fileType = 'EXE_MSI_OR_MSU' #only one of these: MSI or EXE (ignore MSU for now) 18 | # MSI 19 | silentArgs = "/qn /norestart" 20 | validExitCodes= @(0, 3010, 1605, 1614, 1641) # https://msdn.microsoft.com/en-us/library/aa376931(v=vs.85).aspx 21 | # OTHERS 22 | # Uncomment matching EXE type (sorted by most to least common) 23 | #silentArgs = '/S' # NSIS 24 | #silentArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-' # Inno Setup 25 | #silentArgs = '/s' # InstallShield 26 | #silentArgs = '/s /v"/qn"' # InstallShield with MSI 27 | #silentArgs = '/s' # Wise InstallMaster 28 | #silentArgs = '-s' # Squirrel 29 | #silentArgs = '-q' # Install4j 30 | #silentArgs = '-s -u' # Ghost 31 | # Note that some installers, in addition to the silentArgs above, may also need assistance of AHK to achieve silence. 32 | #silentArgs = '' # none; make silent with input macro script like AutoHotKey (AHK) 33 | # https://chocolatey.org/packages/autohotkey.portable 34 | #validExitCodes= @(0) #please insert other valid exit codes here 35 | } 36 | 37 | $uninstalled = $false 38 | # Get-UninstallRegistryKey is new to 0.9.10, if supporting 0.9.9.x and below, 39 | # take a dependency on "chocolatey-core.extension" in your nuspec file. 40 | # This is only a fuzzy search if $softwareName includes '*'. Otherwise it is 41 | # exact. In the case of versions in key names, we recommend removing the version 42 | # and using '*'. 43 | [array]$key = Get-UninstallRegistryKey -SoftwareName $packageArgs['softwareName'] 44 | 45 | if ($key.Count -eq 1) { 46 | $key | % { 47 | $packageArgs['file'] = "$($_.UninstallString)" #NOTE: You may need to split this if it contains spaces, see below 48 | 49 | if ($packageArgs['fileType'] -eq 'MSI') { 50 | # The Product Code GUID is all that should be passed for MSI, and very 51 | # FIRST, because it comes directly after /x, which is already set in the 52 | # Uninstall-ChocolateyPackage msiargs (facepalm). 53 | $packageArgs['silentArgs'] = "$($_.PSChildName) $($packageArgs['silentArgs'])" 54 | 55 | # Don't pass anything for file, it is ignored for msi (facepalm number 2) 56 | # Alternatively if you need to pass a path to an msi, determine that and 57 | # use it instead of the above in silentArgs, still very first 58 | $packageArgs['file'] = '' 59 | } else { 60 | # NOTES: 61 | # - You probably will need to sanitize $packageArgs['file'] as it comes from the registry and could be in a variety of fun but unusable formats 62 | # - Split args from exe in $packageArgs['file'] and pass those args through $packageArgs['silentArgs'] or ignore them 63 | # - Ensure you don't pass double quotes in $file (aka $packageArgs['file']) - otherwise you will get "Illegal characters in path when you attempt to run this" 64 | # - Review the code for auto-uninstaller for all of the fun things it does in sanitizing - https://github.com/chocolatey/choco/blob/bfe351b7d10c798014efe4bfbb100b171db25099/src/chocolatey/infrastructure.app/services/AutomaticUninstallerService.cs#L142-L192 65 | } 66 | 67 | Uninstall-ChocolateyPackage @packageArgs 68 | } 69 | } elseif ($key.Count -eq 0) { 70 | Write-Warning "$packageName has already been uninstalled by other means." 71 | } elseif ($key.Count -gt 1) { 72 | Write-Warning "$($key.Count) matches found!" 73 | Write-Warning "To prevent accidental data loss, no programs will be uninstalled." 74 | Write-Warning "Please alert package maintainer the following keys were matched:" 75 | $key | % {Write-Warning "- $($_.DisplayName)"} 76 | } 77 | 78 | ## OTHER POWERSHELL FUNCTIONS 79 | ## https://chocolatey.org/docs/helpers-reference 80 | #Uninstall-ChocolateyZipPackage $packageName # Only necessary if you did not unpack to package directory - see https://chocolatey.org/docs/helpers-uninstall-chocolatey-zip-package 81 | #Uninstall-ChocolateyEnvironmentVariable # 0.9.10+ - https://chocolatey.org/docs/helpers-uninstall-chocolatey-environment-variable 82 | #Uninstall-BinFile # Only needed if you used Install-BinFile - see https://chocolatey.org/docs/helpers-uninstall-bin-file 83 | ## Remove any shortcuts you added in the install script. 84 | -------------------------------------------------------------------------------- /CreateChocoPackage/tools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | From: https://github.com/devedse/DeveImageOptimizerWPF/blob/master/LICENSE 2 | 3 | LICENSE 4 | 5 | MIT License 6 | 7 | Copyright (c) 2017 Devedse 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. -------------------------------------------------------------------------------- /DeveImageOptimizerWPF.Tests/Converters/KbConverterTests.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.Converters; 2 | using System.Globalization; 3 | using Xunit; 4 | 5 | namespace DeveImageOptimizerWPF.Tests.Converters 6 | { 7 | public class KbConverterTests 8 | { 9 | [Fact] 10 | public void KbToString() 11 | { 12 | //Arrange 13 | var kbConverter = new KbConverter(); 14 | 15 | //Act 16 | var result = kbConverter.Convert(123456789L, null, null, CultureInfo.InvariantCulture); 17 | 18 | //Assert 19 | Assert.Equal("117.7MB", result.ToString()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF.Tests/DeveImageOptimizerWPF.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0-windows 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF.Tests/ViewModel/ObservableData/LoggerExtractinatorTests.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.ViewModel.ObservableData; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace DeveImageOptimizerWPF.Tests.ViewModel.ObservableData 11 | { 12 | public class LoggerExtractinatorTests 13 | { 14 | [Fact] 15 | public void LogsCorrectlyAndStopsLoggingLater() 16 | { 17 | Console.WriteLine("Before"); 18 | var instance = LoggerExtractinator.CreateLoggerExtractinatorAndSetupConsoleRedirection(); 19 | Console.WriteLine("During"); 20 | int waitedXTimes = 0; 21 | while(!instance.LogLines.Any()) 22 | { 23 | waitedXTimes++; 24 | Thread.Sleep(10); 25 | } 26 | instance.DestroyAndRevertConsoleOut(); 27 | Console.WriteLine($"After: {waitedXTimes}"); 28 | 29 | Assert.Single(instance.LogLines); 30 | Assert.Single(instance.LogLinesEntry); 31 | 32 | var theLine = instance.LogLines.Single(); 33 | var theEntry = instance.LogLinesEntry.Single(); 34 | 35 | Assert.Contains("During", theLine); 36 | Assert.Equal("During", theEntry.Message); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeveImageOptimizerWPF", "DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj", "{01AB1C40-C596-4D29-AFB8-B6DD31586DFC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeveImageOptimizerWPF.Tests", "DeveImageOptimizerWPF.Tests\DeveImageOptimizerWPF.Tests.csproj", "{883E92F9-F416-4BE8-99EA-7B755418E6B1}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {01AB1C40-C596-4D29-AFB8-B6DD31586DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {01AB1C40-C596-4D29-AFB8-B6DD31586DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {01AB1C40-C596-4D29-AFB8-B6DD31586DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {01AB1C40-C596-4D29-AFB8-B6DD31586DFC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {883E92F9-F416-4BE8-99EA-7B755418E6B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {883E92F9-F416-4BE8-99EA-7B755418E6B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {883E92F9-F416-4BE8-99EA-7B755418E6B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {883E92F9-F416-4BE8-99EA-7B755418E6B1}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {DEA46556-432B-433E-B6BB-2B1C85451DC3} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.ViewModel; 2 | using DeveImageOptimizerWPF.ViewModel.ObservableData; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Windows; 6 | 7 | namespace DeveImageOptimizerWPF 8 | { 9 | /// 10 | /// Interaction logic for App.xaml 11 | /// 12 | public partial class App : Application 13 | { 14 | public App() 15 | { 16 | Services = ConfigureServices(); 17 | 18 | this.InitializeComponent(); 19 | 20 | Console.WriteLine("DeveImageOptimizerWPF started"); 21 | } 22 | 23 | /// 24 | /// Gets the current instance in use 25 | /// 26 | public new static App Current => (App)Application.Current; 27 | 28 | /// 29 | /// Gets the instance to resolve application services. 30 | /// 31 | public IServiceProvider Services { get; } 32 | 33 | /// 34 | /// Configures the services for the application. 35 | /// 36 | private static IServiceProvider ConfigureServices() 37 | { 38 | var services = new ServiceCollection(); 39 | 40 | var loggerExtractinator = LoggerExtractinator.CreateLoggerExtractinatorAndSetupConsoleRedirection(); 41 | services.AddSingleton(loggerExtractinator); 42 | 43 | // Viewmodels 44 | services.AddTransient(); 45 | services.AddTransient(); 46 | services.AddTransient(); 47 | 48 | return services.BuildServiceProvider(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/EnumToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace DeveImageOptimizerWPF.Converters 6 | { 7 | public class EnumToBooleanConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 10 | { 11 | if (value == null && parameter == null) 12 | { 13 | return true; 14 | } 15 | else if (value == null) 16 | { 17 | return false; 18 | } 19 | return value.Equals(parameter); 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return value.Equals(true) ? parameter : Binding.DoNothing; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | 4 | namespace DeveImageOptimizerWPF.Converters 5 | { 6 | [ValueConversion(typeof(bool), typeof(bool))] 7 | public class InverseBooleanConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 10 | { 11 | if (targetType != typeof(bool)) 12 | { 13 | throw new InvalidOperationException("The target must be a boolean"); 14 | } 15 | return !(bool)value; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 19 | { 20 | throw new NotSupportedException(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/KbConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Conversion; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | 6 | namespace DeveImageOptimizerWPF.Converters 7 | { 8 | [ValueConversion(typeof(long), typeof(string))] 9 | public sealed class KbConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return ValuesToStringHelper.BytesToString((long)value, culture); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/ObservableCollectionOptimizedFileResultToSizeReducedConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Conversion; 2 | using DeveImageOptimizerWPF.State.ProcessingState; 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Windows.Data; 8 | 9 | namespace DeveImageOptimizerWPF.Converters 10 | { 11 | [ValueConversion(typeof(ObservableCollection), typeof(string))] 12 | public sealed class ObservableCollectionOptimizedFileResultToSizeReducedConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value == null) 17 | { 18 | return string.Empty; 19 | } 20 | var ofr = (ObservableCollection)value; 21 | var totalOptimizedSize = ofr.Sum(t => t.OriginalSize - t.OptimizedSize); 22 | return ValuesToStringHelper.BytesToString(totalOptimizedSize, culture); 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/ObservableCollectionOptimizedFileResultToTotalSizeAfterConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Conversion; 2 | using DeveImageOptimizerWPF.State.ProcessingState; 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Windows.Data; 8 | 9 | namespace DeveImageOptimizerWPF.Converters 10 | { 11 | [ValueConversion(typeof(ObservableCollection), typeof(string))] 12 | public sealed class ObservableCollectionOptimizedFileResultToTotalSizeAfterConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value == null) 17 | { 18 | return string.Empty; 19 | } 20 | var ofr = (ObservableCollection)value; 21 | var totalOptimizedSize = ofr.Sum(t => t.OptimizedSize); 22 | return ValuesToStringHelper.BytesToString(totalOptimizedSize, culture); 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/ObservableCollectionOptimizedFileResultToTotalSizeBeforeConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Conversion; 2 | using DeveImageOptimizerWPF.State.ProcessingState; 3 | using System; 4 | using System.Collections.ObjectModel; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Windows.Data; 8 | 9 | namespace DeveImageOptimizerWPF.Converters 10 | { 11 | [ValueConversion(typeof(ObservableCollection), typeof(string))] 12 | public sealed class ObservableCollectionOptimizedFileResultToTotalSizeBeforeConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value == null) 17 | { 18 | return string.Empty; 19 | } 20 | var ofr = (ObservableCollection)value; 21 | var totalOptimizedSize = ofr.Sum(t => t.OriginalSize); 22 | return ValuesToStringHelper.BytesToString(totalOptimizedSize, culture); 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/OptimizationResultToColorConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.State; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace DeveImageOptimizerWPF.Converters 8 | { 9 | [ValueConversion(typeof(bool), typeof(string))] 10 | public sealed class OptimizationResultToColorConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value == null) 15 | { 16 | return Brushes.Red; 17 | } 18 | var ofr = (OptimizationResult)value; 19 | switch (ofr) 20 | { 21 | case OptimizationResult.Success: 22 | return Brushes.LightGreen; 23 | case OptimizationResult.Skipped: 24 | return Brushes.Yellow; 25 | case OptimizationResult.InProgress: 26 | return Brushes.Blue; 27 | case OptimizationResult.Failed: 28 | default: 29 | return Brushes.Red; 30 | } 31 | } 32 | 33 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/OptimizedFileResulToErrorStringConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.State.ProcessingState; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | 6 | namespace DeveImageOptimizerWPF.Converters 7 | { 8 | [ValueConversion(typeof(OptimizableFileUI), typeof(string))] 9 | public sealed class OptimizedFileResulToErrorStringConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value == null) 14 | { 15 | return string.Empty; 16 | } 17 | var ofr = (OptimizableFileUI)value; 18 | return string.Join(Environment.NewLine, ofr.Errors); 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/OptimizedFileResultToSizeConverter.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Conversion; 2 | using DeveImageOptimizerWPF.State.ProcessingState; 3 | using System; 4 | using System.Globalization; 5 | using System.Windows.Data; 6 | 7 | namespace DeveImageOptimizerWPF.Converters 8 | { 9 | [ValueConversion(typeof(OptimizableFileUI), typeof(string))] 10 | public sealed class OptimizedFileResultToSizeConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value == null) 15 | { 16 | return string.Empty; 17 | } 18 | var ofr = (OptimizableFileUI)value; 19 | return ValuesToStringHelper.BytesToString(ofr.OriginalSize - ofr.OptimizedSize, culture); 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/StringToImageSourceConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | using System.Windows.Media.Imaging; 6 | 7 | namespace DeveImageOptimizerWPF.Converters 8 | { 9 | [ValueConversion(typeof(string), typeof(ImageSource))] 10 | public class StringToImageSourceConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (!(value is string valueString)) 15 | { 16 | return null; 17 | } 18 | try 19 | { 20 | //BitmapImage image = new BitmapImage(); 21 | //image.BeginInit(); 22 | //image.CacheOption = BitmapCacheOption.OnLoad; 23 | //image.UriSource = new Uri(valueString); 24 | //image.EndInit(); 25 | //return image; 26 | //imgThumbnail.Source = image; 27 | ImageSource image = BitmapFrame.Create(new Uri(valueString), BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad); 28 | return image; 29 | } 30 | catch { return null; } 31 | } 32 | 33 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Converters/SwitchBindingExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | using System.Windows.Markup; 5 | 6 | namespace DeveImageOptimizerWPF.Converters 7 | { 8 | public class SwitchBindingExtension : Binding 9 | { 10 | public SwitchBindingExtension() 11 | { 12 | Initialize(); 13 | } 14 | 15 | public SwitchBindingExtension(string path) 16 | : base(path) 17 | { 18 | Initialize(); 19 | } 20 | 21 | public SwitchBindingExtension(string path, object valueIfTrue, object valueIfFalse) 22 | : base(path) 23 | { 24 | Initialize(); 25 | this.ValueIfTrue = valueIfTrue; 26 | this.ValueIfFalse = valueIfFalse; 27 | } 28 | 29 | private void Initialize() 30 | { 31 | this.ValueIfTrue = Binding.DoNothing; 32 | this.ValueIfFalse = Binding.DoNothing; 33 | this.Converter = new SwitchConverter(this); 34 | } 35 | 36 | [ConstructorArgument("valueIfTrue")] 37 | public object ValueIfTrue { get; set; } 38 | 39 | [ConstructorArgument("valueIfFalse")] 40 | public object ValueIfFalse { get; set; } 41 | 42 | private class SwitchConverter : IValueConverter 43 | { 44 | public SwitchConverter(SwitchBindingExtension switchExtension) 45 | { 46 | _switch = switchExtension; 47 | } 48 | 49 | private SwitchBindingExtension _switch; 50 | 51 | #region IValueConverter Members 52 | 53 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 54 | { 55 | try 56 | { 57 | bool b = System.Convert.ToBoolean(value); 58 | return b ? _switch.ValueIfTrue : _switch.ValueIfFalse; 59 | } 60 | catch 61 | { 62 | return DependencyProperty.UnsetValue; 63 | } 64 | } 65 | 66 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 67 | { 68 | return Binding.DoNothing; 69 | } 70 | 71 | #endregion 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/DeveImageOptimizerWPF.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows 6 | 7 | Icon.ico 8 | 1.0.0.0 9 | enable 10 | true 11 | 12 | <_SuppressWpfTrimError>true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ILLink.Descriptors.xml 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to control if the Dependent properties feature is enabled. 17 | 18 | 19 | 20 | 21 | Used to control if the IsChanged property feature is enabled. 22 | 23 | 24 | 25 | 26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 32 | 33 | 34 | 35 | 36 | Used to control if equality checks should use the Equals method resolved from the base class. 37 | 38 | 39 | 40 | 41 | Used to control if equality checks should use the static Equals method resolved from the base class. 42 | 43 | 44 | 45 | 46 | Used to turn off build warnings from this weaver. 47 | 48 | 49 | 50 | 51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 60 | 61 | 62 | 63 | 64 | A comma-separated list of error codes that can be safely ignored in assembly verification. 65 | 66 | 67 | 68 | 69 | 'false' to turn off automatic generation of the XML Schema file. 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Helpers/AutoFilteringObservableCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Collections.Specialized; 4 | 5 | namespace DeveImageOptimizerWPF.Helpers 6 | { 7 | public class AutoFilteringObservableCollection : ObservableCollection 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | public AutoFilteringObservableCollection() 13 | { 14 | } 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// 20 | /// The source. 21 | /// 22 | public AutoFilteringObservableCollection(IEnumerable source) 23 | : base(source) 24 | { 25 | } 26 | 27 | /// 28 | /// Custom Raise collection changed 29 | /// 30 | /// 31 | /// The notification action 32 | /// 33 | public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) 34 | { 35 | OnCollectionChanged(e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Helpers/ComboBoxKeyValue.cs: -------------------------------------------------------------------------------- 1 | namespace DeveImageOptimizerWPF.Helpers 2 | { 3 | public class ComboBoxKeyValue 4 | { 5 | public string Key { get; set; } 6 | public T Value { get; set; } 7 | 8 | public ComboBoxKeyValue(string key, T value) 9 | { 10 | Key = key; 11 | Value = value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Helpers/InitialDirFinder.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace DeveImageOptimizerWPF.Helpers 4 | { 5 | public static class InitialDirFinder 6 | { 7 | public static string FindStartingDirectoryBasedOnInput(string inputDir) 8 | { 9 | var startDir = inputDir; 10 | 11 | while (!Directory.Exists(startDir) && !string.IsNullOrWhiteSpace(startDir)) 12 | { 13 | startDir = Path.GetDirectoryName(startDir); 14 | } 15 | 16 | return startDir; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Helpers/ScrollViewerExtensions.cs: -------------------------------------------------------------------------------- 1 | using PropertyChanged; 2 | using System; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace DeveImageOptimizerWPF.Helpers 7 | { 8 | public class ScrollViewerExtensions 9 | { 10 | public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached( 11 | "AlwaysScrollToEnd", 12 | typeof(bool), 13 | typeof(ScrollViewerExtensions), 14 | new PropertyMetadata(false, AlwaysScrollToEndChanged)); 15 | private static bool _autoScroll; 16 | 17 | private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e) 18 | { 19 | if (sender is ScrollViewer scroll) 20 | { 21 | bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue; 22 | if (alwaysScrollToEnd) 23 | { 24 | scroll.ScrollToEnd(); 25 | scroll.ScrollChanged += ScrollChanged; 26 | } 27 | else 28 | { 29 | scroll.ScrollChanged -= ScrollChanged; 30 | } 31 | } 32 | else 33 | { 34 | throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); 35 | } 36 | } 37 | 38 | public static bool GetAlwaysScrollToEnd(ScrollViewer scroll) 39 | { 40 | if (scroll == null) { throw new ArgumentNullException("scroll"); } 41 | return (bool)scroll.GetValue(AlwaysScrollToEndProperty); 42 | } 43 | 44 | public static void SetAlwaysScrollToEnd(ScrollViewer scroll, bool alwaysScrollToEnd) 45 | { 46 | if (scroll == null) { throw new ArgumentNullException("scroll"); } 47 | scroll.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd); 48 | } 49 | 50 | private static void ScrollChanged(object sender, ScrollChangedEventArgs e) 51 | { 52 | if (sender is ScrollViewer scroll) 53 | { 54 | // User scroll event : set or unset autoscroll mode 55 | if (e.ExtentHeightChange == 0) 56 | { 57 | _autoScroll = scroll.VerticalOffset == scroll.ScrollableHeight; 58 | scroll.SetValue(AlwaysScrollToEndProperty, _autoScroll); 59 | } 60 | 61 | // Content scroll event : autoscroll eventually 62 | if (_autoScroll && e.ExtentHeightChange != 0) 63 | { 64 | scroll.ScrollToVerticalOffset(scroll.ExtentHeight); 65 | } 66 | } 67 | else 68 | { 69 | throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Helpers/ScrollViewerExtensions2.cs: -------------------------------------------------------------------------------- 1 | using PropertyChanged; 2 | using System; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace DeveImageOptimizerWPF.Helpers 7 | { 8 | [AddINotifyPropertyChangedInterface] 9 | public class ScrollViewerExtensionConfig : Freezable 10 | { 11 | public bool AlwaysScrollToEnd { get; set; } 12 | 13 | protected override Freezable CreateInstanceCore() 14 | { 15 | return new ScrollViewerExtensionConfig(); 16 | } 17 | } 18 | 19 | public class ScrollViewerExtensions2 20 | { 21 | public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached( 22 | "AlwaysScrollToEnd", 23 | typeof(ScrollViewerExtensionConfig), 24 | typeof(ScrollViewerExtensions), 25 | new PropertyMetadata(default(ScrollViewerExtensionConfig), AlwaysScrollToEndChanged)); 26 | private static bool _autoScroll; 27 | 28 | private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e) 29 | { 30 | if (sender is ScrollViewer scroll) 31 | { 32 | //bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue; 33 | //scroll.ScrollToEnd(); 34 | if (e.NewValue is ScrollViewerExtensionConfig config) 35 | { 36 | scroll.ScrollChanged += ScrollChanged; 37 | } 38 | } 39 | else 40 | { 41 | throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); 42 | } 43 | } 44 | 45 | public static bool GetAlwaysScrollToEnd(ScrollViewer scroll) 46 | { 47 | if (scroll == null) { throw new ArgumentNullException("scroll"); } 48 | return (bool)scroll.GetValue(AlwaysScrollToEndProperty); 49 | } 50 | 51 | public static void SetAlwaysScrollToEnd(ScrollViewer scroll, bool alwaysScrollToEnd) 52 | { 53 | if (scroll == null) { throw new ArgumentNullException("scroll"); } 54 | scroll.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd); 55 | } 56 | 57 | private static void ScrollChanged(object sender, ScrollChangedEventArgs e) 58 | { 59 | if (sender is ScrollViewer scroll) 60 | { 61 | // User scroll event : set or unset autoscroll mode 62 | if (e.ExtentHeightChange == 0) 63 | { 64 | _autoScroll = scroll.VerticalOffset == scroll.ScrollableHeight; 65 | scroll.SetValue(AlwaysScrollToEndProperty, _autoScroll); 66 | } 67 | 68 | // Content scroll event : autoscroll eventually 69 | if (_autoScroll && e.ExtentHeightChange != 0) 70 | { 71 | scroll.ScrollToVerticalOffset(scroll.ExtentHeight); 72 | } 73 | } 74 | else 75 | { 76 | throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ILLink.Descriptors.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedse/DeveImageOptimizerWPF/6b60e3a27b4e6f4390d884d859185acde7cbc197/DeveImageOptimizerWPF/Icon.ico -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/LogViewer.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/LogViewer.xaml.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.LogViewerData; 2 | using IX.Observable; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | 8 | namespace DeveImageOptimizerWPF 9 | { 10 | /// 11 | /// Interaction logic for LogViewer.xaml 12 | /// 13 | public partial class LogViewer : UserControl 14 | { 15 | public static readonly DependencyProperty LogLinesProperty = DependencyProperty.Register( 16 | "LogLines", 17 | typeof(ObservableQueue), 18 | typeof(LogViewer), 19 | new PropertyMetadata(new ObservableQueue(new List() { new LogEntry() { DateTime = DateTime.Now, Index = 0, Message = "Test" } }))); 20 | 21 | public ObservableQueue LogLines 22 | { 23 | get 24 | { 25 | return (ObservableQueue)GetValue(LogLinesProperty); 26 | } 27 | set 28 | { 29 | SetValue(LogLinesProperty, value); 30 | } 31 | } 32 | 33 | public static readonly DependencyProperty LogViewerFontSizeProperty = DependencyProperty.Register( 34 | "LogViewerFontSize", 35 | typeof(int), 36 | typeof(LogViewer), 37 | new PropertyMetadata(12)); 38 | 39 | public int LogViewerFontSize 40 | { 41 | get 42 | { 43 | return (int)GetValue(LogViewerFontSizeProperty); 44 | } 45 | set 46 | { 47 | SetValue(LogViewerFontSizeProperty, value); 48 | } 49 | } 50 | 51 | public LogViewer() 52 | { 53 | InitializeComponent(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/LogViewerData/CollapsibleLogEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DeveImageOptimizerWPF.LogViewerData 4 | { 5 | public class CollapsibleLogEntry : LogEntry 6 | { 7 | public List Contents { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/LogViewerData/LogEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeveImageOptimizerWPF.LogViewerData 4 | { 5 | public class LogEntry : PropertyChangedBase 6 | { 7 | public DateTime DateTime { get; set; } 8 | 9 | public int Index { get; set; } 10 | 11 | public string Message { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/LogViewerData/PropertyChangedBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows; 3 | 4 | namespace DeveImageOptimizerWPF.LogViewerData 5 | { 6 | public class PropertyChangedBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected virtual void OnPropertyChanged(string propertyName) 11 | { 12 | Application.Current?.Dispatcher?.BeginInvoke(() => 13 | { 14 | PropertyChangedEventHandler handler = PropertyChanged; 15 | if (handler != null) 16 | handler(this, new PropertyChangedEventArgs(propertyName)); 17 | }); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows; 3 | 4 | namespace DeveImageOptimizerWPF 5 | { 6 | /// 7 | /// Interaction logic for MainWindow.xaml 8 | /// 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | 15 | Title = $"DeveImageOptimizer {Assembly.GetEntryAssembly().GetName().Version}"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/IChangable.cs: -------------------------------------------------------------------------------- 1 | namespace DeveImageOptimizerWPF.State 2 | { 3 | public interface IChangable 4 | { 5 | bool IsChanged { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/MainWindowState/WindowState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace DeveImageOptimizerWPF.State.MainWindowState 5 | { 6 | [Serializable] 7 | public class WindowState : INotifyPropertyChanged 8 | { 9 | public string ProcessingDirectory { get; set; } = string.Empty; 10 | public event PropertyChangedEventHandler PropertyChanged; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/ProcessingState/FileProgressState.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.Helpers; 2 | using DeveImageOptimizer.State; 3 | using DeveImageOptimizerWPF.Helpers; 4 | using DeveImageOptimizerWPF.State.ProcessingState; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Specialized; 8 | using System.ComponentModel; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | using System.Windows.Data; 13 | 14 | namespace DeveImageOptimizerWPF.State.MainWindowState 15 | { 16 | public class FileProgressState : INotifyPropertyChanged, IProgressReporter 17 | { 18 | private readonly string _logPath; 19 | 20 | public AutoFilteringObservableCollection ProcessedFiles { get; set; } = new AutoFilteringObservableCollection(); 21 | public OptimizableFileUI SelectedProcessedFile { get; set; } 22 | 23 | public event PropertyChangedEventHandler PropertyChanged; 24 | 25 | private readonly object _logfilelockject = new object(); 26 | 27 | public OptimizationResult? _filter { get; set; } 28 | 29 | public OptimizationResult? Filter 30 | { 31 | get => _filter; 32 | set 33 | { 34 | _filter = value; 35 | ProcessedFilesView.Refresh(); 36 | } 37 | } 38 | 39 | 40 | internal CollectionViewSource ProcessedFilesViewSource { get; set; } = new CollectionViewSource(); 41 | public ICollectionView ProcessedFilesView => ProcessedFilesViewSource.View; 42 | 43 | 44 | public FileProgressState() 45 | { 46 | ProcessedFilesViewSource.Source = ProcessedFiles; 47 | ProcessedFilesViewSource.Filter += ApplyFilter; 48 | 49 | _logPath = Path.Combine(FolderHelperMethods.ConfigFolder, "Log.txt"); 50 | } 51 | 52 | private void ApplyFilter(object sender, FilterEventArgs e) 53 | { 54 | OptimizableFileUI fileUI = (OptimizableFileUI)e.Item; 55 | 56 | if (_filter == null || fileUI.OptimizationResult == OptimizationResult.InProgress || fileUI.OptimizationResult == _filter.Value) 57 | { 58 | e.Accepted = true; 59 | } 60 | else 61 | { 62 | e.Accepted = false; 63 | } 64 | } 65 | 66 | // Create the OnPropertyChanged method to raise the event 67 | public virtual void OnPropertyChanged(string name) 68 | { 69 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 70 | } 71 | 72 | public Task OptimizableFileProgressUpdated(OptimizableFile optimizableFile) 73 | { 74 | if (optimizableFile.OptimizationResult != OptimizationResult.InProgress) 75 | { 76 | //No need for lock because this happens on the UI thread 77 | File.AppendAllText(_logPath, $"{optimizableFile.OptimizationResult}|{optimizableFile.Path}{Environment.NewLine}"); 78 | } 79 | 80 | var foundFile = ProcessedFiles.FirstOrDefault(t => t.Path == optimizableFile.Path); 81 | if (foundFile == null) 82 | { 83 | ProcessedFiles.Add(new OptimizableFileUI(optimizableFile)); 84 | } 85 | else 86 | { 87 | foundFile.Set(optimizableFile); 88 | 89 | //This code is required to have an item that updates from Processing to Skipped be re-filtered 90 | var indexOf = ProcessedFiles.IndexOf(foundFile); 91 | 92 | var eventArgs = new NotifyCollectionChangedEventArgs( 93 | NotifyCollectionChangedAction.Replace, 94 | new List { foundFile }, 95 | new List { foundFile }, indexOf); 96 | 97 | ProcessedFiles.RaiseCollectionChanged(eventArgs); 98 | } 99 | 100 | OnPropertyChanged(nameof(ProcessedFiles)); 101 | 102 | return Task.CompletedTask; 103 | } 104 | 105 | public Task TotalFileCountDiscovered(int count) 106 | { 107 | //We don't need this yet 108 | return Task.CompletedTask; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/ProcessingState/OptimizableFileUI.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Collections; 2 | using DeveImageOptimizer.State; 3 | using PropertyChanged; 4 | using System; 5 | using System.Collections.ObjectModel; 6 | 7 | namespace DeveImageOptimizerWPF.State.ProcessingState 8 | { 9 | [AddINotifyPropertyChangedInterface] 10 | public class OptimizableFileUI 11 | { 12 | public string Path { get; set; } 13 | public string RelativePath { get; set; } 14 | 15 | public OptimizationResult OptimizationResult { get; set; } = OptimizationResult.InProgress; 16 | 17 | public long OriginalSize { get; set; } 18 | public long OptimizedSize { get; set; } 19 | 20 | public TimeSpan Duration { get; set; } = TimeSpan.Zero; 21 | 22 | public ObservableCollection Errors { get; } = new ObservableCollection(); 23 | 24 | public OptimizableFileUI(OptimizableFile optimizableFile) 25 | { 26 | Set(optimizableFile); 27 | } 28 | 29 | public void Set(OptimizableFile optimizableFile) 30 | { 31 | Path = optimizableFile.Path; 32 | RelativePath = optimizableFile.RelativePath; 33 | 34 | OptimizationResult = optimizableFile.OptimizationResult; 35 | 36 | OriginalSize = optimizableFile.OriginalSize; 37 | OptimizedSize = optimizableFile.OptimizedSize; 38 | 39 | Duration = optimizableFile.Duration; 40 | 41 | ListSynchronizerV2.SynchronizeLists(optimizableFile.Errors, Errors); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/StateManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Serialization; 3 | 4 | namespace DeveImageOptimizerWPF.State 5 | { 6 | public class StateManager where T : class, new() 7 | { 8 | private readonly string _stateFileName; 9 | private T _currentUserSettings; 10 | private readonly object _lockject = new object(); 11 | 12 | public StateManager(string stateFileName) 13 | { 14 | _stateFileName = stateFileName; 15 | } 16 | 17 | public T State => _currentUserSettings ?? (_currentUserSettings = LoadFromFile(_stateFileName)); 18 | 19 | public void Save() 20 | { 21 | SaveToFile(_stateFileName); 22 | } 23 | 24 | private T LoadFromFile(string filename) 25 | { 26 | if (File.Exists(filename)) 27 | { 28 | lock (_lockject) 29 | { 30 | using (var sw = new StreamReader(filename)) 31 | { 32 | var xmls = new XmlSerializer(typeof(T)); 33 | var retval = xmls.Deserialize(sw) as T; 34 | if (retval != null) 35 | { 36 | var changable = retval as IChangable; 37 | if (changable != null) 38 | { 39 | changable.IsChanged = false; 40 | } 41 | return retval; 42 | } 43 | } 44 | } 45 | } 46 | return new T(); 47 | } 48 | 49 | private void SaveToFile(string filename) 50 | { 51 | var curState = State; 52 | lock (_lockject) 53 | { 54 | using (var sw = new StreamWriter(filename)) 55 | { 56 | var xmls = new XmlSerializer(typeof(T)); 57 | xmls.Serialize(sw, State); 58 | } 59 | 60 | var changable = curState as IChangable; 61 | if (changable != null) 62 | { 63 | changable.IsChanged = false; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/StaticState.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.Helpers; 2 | using DeveImageOptimizerWPF.State.MainWindowState; 3 | using DeveImageOptimizerWPF.State.UserSettings; 4 | using System.IO; 5 | 6 | namespace DeveImageOptimizerWPF.State 7 | { 8 | public static class StaticState 9 | { 10 | public static StateManager UserSettingsManager { get; } = new StateManager(Path.Combine(FolderHelperMethods.ConfigFolder, "UserSettings.xml")); 11 | public static StateManager WindowStateManager { get; } = new StateManager(Path.Combine(FolderHelperMethods.ConfigFolder, "WindowState.xml")); 12 | 13 | //Commented out because we don't need to save all processed files (this is only UI stuff) 14 | //public static StateManager FilesProcessingStateManager { get; } = new StateManager(Path.Combine(FolderHelperMethods.ConfigFolder, "FilesProcessingState.xml")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/UserSettings/RemembererSettings.cs: -------------------------------------------------------------------------------- 1 | namespace DeveImageOptimizerWPF.State.UserSettings 2 | { 3 | public enum RemembererSettings 4 | { 5 | OptimizeAlways = 0, 6 | StorePerFile = 1, 7 | StorePerDirectory = 2, 8 | StorePerFileAndDirectory = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/State/UserSettings/UserSettingsData.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.FileProcessing; 2 | using DeveImageOptimizer.ImageOptimization; 3 | using PropertyChanged; 4 | using System; 5 | using System.ComponentModel; 6 | using System.Xml.Serialization; 7 | 8 | namespace DeveImageOptimizerWPF.State.UserSettings 9 | { 10 | [Serializable] 11 | [AddINotifyPropertyChangedInterface] 12 | public class UserSettingsData : IChangable, INotifyPropertyChanged 13 | { 14 | public string FileOptimizerPath { get; set; } 15 | 16 | public string TempDirectory { get; set; } 17 | 18 | public bool HideOptimizerWindow { get; set; } 19 | 20 | public RemembererSettings RemembererSettings { get; set; } 21 | 22 | public bool SaveFailedFiles { get; set; } 23 | public bool KeepFileAttributes { get; set; } 24 | 25 | public bool ExecuteImageOptimizationParallel { get; set; } 26 | public int MaxDegreeOfParallelism { get; set; } 27 | 28 | public bool DirectlyCallOptimizers { get; set; } 29 | 30 | public ImageOptimizationLevel ImageOptimizationLevel { get; set; } 31 | 32 | public bool OptimizeJpg { get; set; } 33 | public bool OptimizePng { get; set; } 34 | public bool OptimizeGif { get; set; } 35 | public bool OptimizeBmp { get; set; } 36 | 37 | public int LogLevel { get; set; } 38 | 39 | 40 | public event PropertyChangedEventHandler PropertyChanged; 41 | 42 | public UserSettingsData() 43 | { 44 | ResetToDefaults(); 45 | } 46 | 47 | public void LoadFromConfiguration(DeveImageOptimizerConfiguration config) 48 | { 49 | FileOptimizerPath = config.FileOptimizerPath; 50 | TempDirectory = config.TempDirectory; 51 | HideOptimizerWindow = config.HideOptimizerWindow; 52 | SaveFailedFiles = config.SaveFailedFiles; 53 | KeepFileAttributes = config.KeepFileAttributes; 54 | 55 | ExecuteImageOptimizationParallel = config.ExecuteImageOptimizationParallel; 56 | MaxDegreeOfParallelism = config.MaxDegreeOfParallelism; 57 | 58 | DirectlyCallOptimizers = config.CallOptimizationToolsDirectlyInsteadOfThroughFileOptimizer; 59 | 60 | ImageOptimizationLevel = config.ImageOptimizationLevel; 61 | 62 | OptimizeJpg = config.OptimizeJpg; 63 | OptimizePng = config.OptimizePng; 64 | OptimizeGif = config.OptimizeGif; 65 | OptimizeBmp = config.OptimizeBmp; 66 | 67 | LogLevel = config.LogLevel; 68 | } 69 | 70 | public DeveImageOptimizerConfiguration ToDeveImageOptimizerConfiguration() 71 | { 72 | var config = new DeveImageOptimizerConfiguration() 73 | { 74 | ExecuteImageOptimizationParallel = ExecuteImageOptimizationParallel, 75 | FileOptimizerPath = FileOptimizerPath, 76 | HideOptimizerWindow = HideOptimizerWindow, 77 | LogLevel = LogLevel, 78 | MaxDegreeOfParallelism = MaxDegreeOfParallelism, 79 | SaveFailedFiles = SaveFailedFiles, 80 | KeepFileAttributes = KeepFileAttributes, 81 | TempDirectory = TempDirectory, 82 | CallOptimizationToolsDirectlyInsteadOfThroughFileOptimizer = DirectlyCallOptimizers, 83 | ImageOptimizationLevel = ImageOptimizationLevel, 84 | OptimizeJpg = OptimizeJpg, 85 | OptimizePng = OptimizePng, 86 | OptimizeGif = OptimizeGif, 87 | OptimizeBmp = OptimizeBmp 88 | }; 89 | return config; 90 | } 91 | 92 | public void ResetToDefaults() 93 | { 94 | RemembererSettings = RemembererSettings.StorePerFile; 95 | LoadFromConfiguration(new DeveImageOptimizerConfiguration()); 96 | } 97 | 98 | [XmlIgnore] 99 | public bool IsChanged { get; set; } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ViewModel/ConsoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizerWPF.Helpers; 2 | using DeveImageOptimizerWPF.ViewModel.ObservableData; 3 | using Microsoft.Toolkit.Mvvm.ComponentModel; 4 | using Microsoft.Toolkit.Mvvm.Input; 5 | using PropertyChanged; 6 | using System; 7 | using System.Windows.Input; 8 | 9 | namespace DeveImageOptimizerWPF.ViewModel 10 | { 11 | [AddINotifyPropertyChangedInterface] 12 | public class ConsoleViewModel : ObservableRecipient 13 | { 14 | public LoggerExtractinator LoggerExtractinator { get; set; } 15 | 16 | public int ConsoleFontSize { get; set; } = 12; 17 | public ICommand IncreaseFontSizeCommand { get; } 18 | public ICommand DecreaseFontSizeCommand { get; } 19 | 20 | public ScrollViewerExtensionConfig ScrollConfig { get; set; } = new ScrollViewerExtensionConfig() { AlwaysScrollToEnd = true }; 21 | 22 | public ConsoleViewModel(LoggerExtractinator loggerExtractinator) 23 | { 24 | LoggerExtractinator = loggerExtractinator; 25 | 26 | IncreaseFontSizeCommand = new RelayCommand(() => ConsoleFontSize = Math.Clamp(ConsoleFontSize + 2, 12, 30)); 27 | DecreaseFontSizeCommand = new RelayCommand(() => ConsoleFontSize = Math.Clamp(ConsoleFontSize - 2, 12, 30)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ViewModel/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.Exceptions; 2 | using DeveImageOptimizer.FileProcessing; 3 | using DeveImageOptimizer.State; 4 | using DeveImageOptimizer.State.StoringProcessedDirectories; 5 | using DeveImageOptimizerWPF.Helpers; 6 | using DeveImageOptimizerWPF.State; 7 | using DeveImageOptimizerWPF.State.MainWindowState; 8 | using DeveImageOptimizerWPF.State.UserSettings; 9 | using Microsoft.Toolkit.Mvvm.ComponentModel; 10 | using Microsoft.Toolkit.Mvvm.Input; 11 | using Ookii.Dialogs.Wpf; 12 | using System; 13 | using System.IO; 14 | using System.Linq; 15 | using System.Threading.Tasks; 16 | using System.Windows.Input; 17 | 18 | namespace DeveImageOptimizerWPF.ViewModel 19 | { 20 | /// 21 | /// This class contains properties that the main View can data bind to. 22 | /// 23 | /// Use the mvvminpc snippet to add bindable properties to this ViewModel. 24 | /// 25 | /// 26 | /// You can also use Blend to data bind with the tool's support. 27 | /// 28 | /// 29 | /// See http://www.galasoft.ch/mvvm 30 | /// 31 | /// 32 | public class MainViewModel : ObservableRecipient 33 | { 34 | public WindowState WindowState { get; set; } 35 | public FileProgressState FilesProcessingState { get; set; } 36 | 37 | public bool PreviewEnabled { get; set; } 38 | public bool IsOptimizing { get; set; } 39 | 40 | private readonly FileProcessedStateRememberer _fileRememberer; 41 | private readonly DirProcessedStateRememberer _dirRememberer; 42 | 43 | public MainViewModel() 44 | { 45 | WindowState = StaticState.WindowStateManager.State; 46 | FilesProcessingState = new FileProgressState(); 47 | 48 | WindowState.PropertyChanged += ProcessingStateData_PropertyChanged; 49 | FilesProcessingState.PropertyChanged += FilesProcessingState_PropertyChanged; 50 | 51 | GoCommand = new AsyncRelayCommand(GoCommandImp); 52 | BrowseCommand = new RelayCommand(BrowseCommandImp); 53 | 54 | var optimize = GetRemembererSettings(); 55 | 56 | _fileRememberer = new FileProcessedStateRememberer(optimize.fileOptimize); 57 | _dirRememberer = new DirProcessedStateRememberer(optimize.dirOptimize); 58 | 59 | StaticState.UserSettingsManager.State.PropertyChanged += State_PropertyChanged; 60 | } 61 | 62 | private (bool fileOptimize, bool dirOptimize) GetRemembererSettings() 63 | { 64 | var state = StaticState.UserSettingsManager.State; 65 | 66 | var fileOptimize = state.RemembererSettings == RemembererSettings.OptimizeAlways || state.RemembererSettings == RemembererSettings.StorePerDirectory; 67 | var dirOptimize = state.RemembererSettings == RemembererSettings.OptimizeAlways || state.RemembererSettings == RemembererSettings.StorePerFile; 68 | 69 | return (fileOptimize, dirOptimize); 70 | } 71 | 72 | private void State_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) 73 | { 74 | var optimize = GetRemembererSettings(); 75 | 76 | _fileRememberer.ShouldAlwaysOptimize = optimize.fileOptimize; 77 | _dirRememberer.ShouldAlwaysOptimize = optimize.dirOptimize; 78 | } 79 | 80 | private void FilesProcessingState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) 81 | { 82 | } 83 | 84 | private void ProcessingStateData_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) 85 | { 86 | StaticState.WindowStateManager.Save(); 87 | } 88 | 89 | public ICommand GoCommand { get; private set; } 90 | private async Task GoCommandImp() 91 | { 92 | IsOptimizing = true; 93 | try 94 | { 95 | var state = StaticState.UserSettingsManager.State; 96 | 97 | var config = state.ToDeveImageOptimizerConfiguration(); 98 | 99 | var fileProcessor = new DeveImageOptimizerProcessor(config, FilesProcessingState, _fileRememberer, _dirRememberer); 100 | 101 | await fileProcessor.ProcessDirectory(WindowState.ProcessingDirectory); 102 | } 103 | catch (FileOptimizerNotFoundException ex) 104 | { 105 | ShowFileOptimizerNotFoundError(ex.Message); 106 | } 107 | catch (AggregateException ex) when (ex.InnerExceptions?.OfType()?.Any() == true) 108 | { 109 | var message = ex.InnerExceptions?.OfType().FirstOrDefault()?.Message; 110 | ShowFileOptimizerNotFoundError(message); 111 | } 112 | finally 113 | { 114 | IsOptimizing = false; 115 | } 116 | } 117 | 118 | private static void ShowFileOptimizerNotFoundError(string? message) 119 | { 120 | System.Windows.MessageBox.Show(message, "Could not find FileOptimizer.exe", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); 121 | } 122 | 123 | public ICommand BrowseCommand { get; private set; } 124 | 125 | private void BrowseCommandImp() 126 | { 127 | var folderDialog = new VistaFolderBrowserDialog(); 128 | 129 | string startDir = InitialDirFinder.FindStartingDirectoryBasedOnInput(WindowState.ProcessingDirectory); 130 | if (Directory.Exists(startDir)) 131 | { 132 | folderDialog.SelectedPath = startDir; 133 | } 134 | 135 | if (folderDialog.ShowDialog() == true) 136 | { 137 | WindowState.ProcessingDirectory = folderDialog.SelectedPath; 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ViewModel/ObservableData/LoggerExtractinator.cs: -------------------------------------------------------------------------------- 1 | using DeveCoolLib.Streams; 2 | using DeveImageOptimizerWPF.LogViewerData; 3 | using IX.Observable; 4 | using PropertyChanged; 5 | using System; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Threading; 11 | 12 | namespace DeveImageOptimizerWPF.ViewModel.ObservableData 13 | { 14 | [AddINotifyPropertyChangedInterface] 15 | public class LoggerExtractinator 16 | { 17 | private static object _Lockject = new object(); 18 | private static bool _HasInstance = false; 19 | private static TextWriter? _OriginalOutTextWriter = null; 20 | 21 | public static LoggerExtractinator CreateLoggerExtractinatorAndSetupConsoleRedirection() 22 | { 23 | lock (_Lockject) 24 | { 25 | if (_HasInstance) 26 | { 27 | throw new InvalidOperationException($"Cannot create multiple instances of the {nameof(LoggerExtractinator)}"); 28 | } 29 | _HasInstance = true; 30 | _OriginalOutTextWriter = Console.Out; 31 | var originalOut = Console.OpenStandardOutput(); 32 | var consoleOutputStream = new MovingMemoryStream(); 33 | 34 | var multiOut = new MultiStream(originalOut, consoleOutputStream); 35 | var writer = new StreamWriter(multiOut) 36 | { 37 | AutoFlush = true 38 | }; 39 | 40 | Console.SetOut(writer); 41 | 42 | var extractinator = new LoggerExtractinator(consoleOutputStream); 43 | return extractinator; 44 | } 45 | } 46 | 47 | private readonly TextReader _reader; 48 | 49 | private bool _isRunning = false; 50 | private Task _runningTask; 51 | 52 | public ObservableQueue LogLines { get; set; } = new ObservableQueue(); 53 | public ObservableQueue LogLinesEntry { get; set; } = new ObservableQueue(); 54 | private int lineCount = 0; 55 | 56 | private LoggerExtractinator(MovingMemoryStream movingMemoryStream) 57 | { 58 | _reader = new StreamReader(movingMemoryStream); 59 | _runningTask = Task.Run(Runner); 60 | } 61 | 62 | public void DestroyAndRevertConsoleOut() 63 | { 64 | lock (_Lockject) 65 | { 66 | _isRunning = false; 67 | _runningTask.Wait(); 68 | if (_OriginalOutTextWriter != null) 69 | { 70 | Console.SetOut(_OriginalOutTextWriter); 71 | } 72 | _HasInstance = false; 73 | } 74 | } 75 | 76 | private void Runner() 77 | { 78 | _isRunning = true; 79 | var dispatcher = Application.Current?.Dispatcher; 80 | 81 | while (_isRunning) 82 | { 83 | var logLine = _reader.ReadLine(); 84 | if (logLine != null) 85 | { 86 | try 87 | { 88 | var toInvoke = new Action(() => 89 | { 90 | try 91 | { 92 | var lineToAdd = $"{lineCount,4}: {logLine}"; 93 | LogLines.Enqueue(lineToAdd); 94 | LogLinesEntry.Enqueue(new LogEntry() { DateTime = DateTime.Now, Index = lineCount, Message = logLine }); 95 | lineCount++; 96 | 97 | while (LogLines.Count > 1000) 98 | { 99 | LogLines.Dequeue(); 100 | } 101 | while (LogLinesEntry.Count > 1000) 102 | { 103 | LogLinesEntry.Dequeue(); 104 | } 105 | } 106 | catch (Exception ex) 107 | { 108 | //Swallow exception as this usually only happens when you kill the application 109 | } 110 | }); 111 | if (dispatcher != null) 112 | { 113 | dispatcher.BeginInvoke(toInvoke); 114 | } 115 | else 116 | { 117 | toInvoke(); 118 | } 119 | } 120 | catch (Exception ex) 121 | { 122 | //Swallow exception as this usually only happens when you kill the application 123 | } 124 | } 125 | else 126 | { 127 | Thread.Sleep(1); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ViewModel/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using DeveImageOptimizer.ImageOptimization; 2 | using DeveImageOptimizerWPF.Helpers; 3 | using DeveImageOptimizerWPF.State; 4 | using DeveImageOptimizerWPF.State.UserSettings; 5 | using Microsoft.Toolkit.Mvvm.ComponentModel; 6 | using Microsoft.Toolkit.Mvvm.Input; 7 | using Microsoft.Win32; 8 | using Ookii.Dialogs.Wpf; 9 | using PropertyChanged; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Windows.Input; 15 | 16 | namespace DeveImageOptimizerWPF.ViewModel 17 | { 18 | [AddINotifyPropertyChangedInterface] 19 | public class SettingsViewModel : ObservableRecipient 20 | { 21 | public UserSettingsData UserSettingsData { get; } 22 | 23 | public IEnumerable AvailableImageOptimizationLevels { get; } 24 | public IEnumerable MaxParallelismChoices { get; } 25 | public IEnumerable AvailableLogLevels { get; } 26 | 27 | public IEnumerable AvailableStorageModes { get; } 28 | 29 | public SettingsViewModel() 30 | { 31 | UserSettingsData = StaticState.UserSettingsManager.State; 32 | BrowseCommandFileOptimizer = new RelayCommand(BrowseCommandFileOptimizerImp); 33 | BrowseCommandTempDir = new RelayCommand(BrowseCommandTempDirImp); 34 | 35 | SaveCommand = new RelayCommand(SaveCommandImp); 36 | ResetToDefaultsCommand = new RelayCommand(ResetToDefaultsCommandImpl); 37 | 38 | AvailableImageOptimizationLevels = Enum.GetValues(); 39 | MaxParallelismChoices = Enumerable.Range(1, Environment.ProcessorCount).ToList(); 40 | AvailableLogLevels = new List() { 0, 1, 2, 3, 4 }; 41 | 42 | AvailableStorageModes = Enum.GetValues(typeof(RemembererSettings)).Cast().ToList(); 43 | } 44 | 45 | public ICommand SaveCommand { get; } 46 | private void SaveCommandImp() 47 | { 48 | StaticState.UserSettingsManager.Save(); 49 | } 50 | 51 | public ICommand ResetToDefaultsCommand { get; } 52 | private void ResetToDefaultsCommandImpl() 53 | { 54 | StaticState.UserSettingsManager.State.ResetToDefaults(); 55 | } 56 | 57 | public ICommand BrowseCommandFileOptimizer { get; private set; } 58 | private void BrowseCommandFileOptimizerImp() 59 | { 60 | var fileDialog = new OpenFileDialog() 61 | { 62 | Filter = "FileOptimizer (FileOptimizer.exe,FileOptimizer64.exe)|FileOptimizer.exe;FileOptimizer64.exe|All files (*.*)|*.*" 63 | }; 64 | 65 | string startDir = InitialDirFinder.FindStartingDirectoryBasedOnInput(UserSettingsData.FileOptimizerPath); 66 | if (Directory.Exists(startDir)) 67 | { 68 | fileDialog.InitialDirectory = startDir; 69 | } 70 | 71 | if (fileDialog.ShowDialog() == true) 72 | { 73 | UserSettingsData.FileOptimizerPath = fileDialog.FileName; 74 | } 75 | } 76 | 77 | public ICommand BrowseCommandTempDir { get; private set; } 78 | 79 | private void BrowseCommandTempDirImp() 80 | { 81 | var folderDialog = new VistaFolderBrowserDialog(); 82 | 83 | string startDir = InitialDirFinder.FindStartingDirectoryBasedOnInput(UserSettingsData.TempDirectory); 84 | if (Directory.Exists(startDir)) 85 | { 86 | folderDialog.SelectedPath = startDir; 87 | } 88 | 89 | if (folderDialog.ShowDialog() == true) 90 | { 91 | UserSettingsData.TempDirectory = folderDialog.SelectedPath; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /DeveImageOptimizerWPF/ViewModel/ViewModelLocator.cs: -------------------------------------------------------------------------------- 1 | /* 2 | In App.xaml: 3 | 4 | 6 | 7 | 8 | In the View: 9 | DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}" 10 | 11 | You can also use Blend to do all this with the tool's support. 12 | See http://www.galasoft.ch/mvvm 13 | */ 14 | 15 | using Microsoft.Extensions.DependencyInjection; 16 | using System; 17 | 18 | namespace DeveImageOptimizerWPF.ViewModel 19 | { 20 | /// 21 | /// This class contains static references to all the view models in the 22 | /// application and provides an entry point for the bindings. 23 | /// 24 | public class ViewModelLocator 25 | { 26 | /// 27 | /// Initializes a new instance of the ViewModelLocator class. 28 | /// 29 | public ViewModelLocator() 30 | { 31 | } 32 | 33 | public MainViewModel? Main => ResolveViewModelOrThrow(); 34 | public SettingsViewModel? Settings => ResolveViewModelOrThrow(); 35 | public ConsoleViewModel? Console => ResolveViewModelOrThrow(); 36 | 37 | private T ResolveViewModelOrThrow() 38 | { 39 | var viewModel = App.Current.Services.GetService(); 40 | if (viewModel == null) 41 | { 42 | throw new InvalidOperationException($"Could not resolve ViewModel {typeof(T).FullName}. Ensure it's configued in 'App.xaml.cs'"); 43 | } 44 | return viewModel; 45 | } 46 | 47 | public static void Cleanup() 48 | { 49 | // TODO Clear the ViewModels 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedse/DeveImageOptimizerWPF/6b60e3a27b4e6f4390d884d859185acde7cbc197/Icon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Devedse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeveImageOptimizerWPF 2 | This tool uses FileOptimizer to recompress images: http://nikkhokkho.sourceforge.net/static.php?page=FileOptimizer 3 | 4 | The library used to do the Optimization and pixel by pixel comparing the images is named 5 | [DeveImageOptimizer](https://github.com/devedse/DeveImageOptimizer) 6 | 7 | DeveImageOptimizerWPF has 2 main advantages over using Nikkho's FileOptimizer directly: 8 | 1. DeveImageOptimizerWPF kicks of optimization in parallel, thus being way quicker 9 | 1. DeveImageOptimizerWPF verifies images on a Pixel by Pixel basis to see if the result matches the input. (It happens sometimes that one of the 16 tools makes a mistake which could corrupt an image) 10 | 11 | Also checkout Papa's Best Optimizer as an alternative to my tool, it also doesn't contain the Image verification part though: 12 | 13 | https://papas-best.com/ 14 | 15 | Some other advantages of my application are the use of a modern programming language (debatable I know :smile:) C#. And having fully automated builds and releases using AppVeyor and Chocolatey: 16 | 17 | https://chocolatey.org/packages/DeveImageOptimizerWPF 18 | 19 | ## Build status 20 | 21 | | GitHubActions Builds | 22 | |:--------------------:| 23 | | [![GitHubActions Builds](https://github.com/devedse/DeveImageOptimizerWPF/workflows/GitHubActionsBuilds/badge.svg)](https://github.com/devedse/DeveImageOptimizerWPF/actions/workflows/githubactionsbuilds.yml) | 24 | 25 | ## Code Coverage status 26 | 27 | | CodeCov | 28 | |:-------:| 29 | | [![codecov](https://codecov.io/gh/devedse/DeveImageOptimizerWPF/branch/master/graph/badge.svg)](https://codecov.io/gh/devedse/DeveImageOptimizerWPF) | 30 | 31 | (Reason why Coverage is quite low is because a lot of tests can't run on the Build Server) 32 | 33 | ## Code Quality Status 34 | 35 | | SonarQube | 36 | |:---------:| 37 | | [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=DeveImageOptimizerWPF&metric=alert_status)](https://sonarcloud.io/dashboard?id=DeveImageOptimizerWPF) | 38 | 39 | ## Chocolatey 40 | 41 | | Chocolatey | 42 | |:-----:| 43 | | [![NuGet](https://img.shields.io/chocolatey/v/deveimageoptimizerwpf)](https://community.chocolatey.org/packages/deveimageoptimizerwpf) | 44 | 45 | ## More information 46 | 47 | This tool has some advantages/differences over the GUI for FileOptimizer: 48 | 49 | | Number | Description | Implementation progress | 50 | | --- | --- | --- | 51 | | 1 | A basic UI using MVVM Light and some cool features from C# and WPF | 100% | 52 | | 2 | It will remember all processed files, so it won't reprocess them again if they have already been optimized. | 100% | 53 | | 3 | It will do a pixel for pixel comparison between the input/output and only replace the original if it matches 100% (this is just to be sure the image did not get corrupted) | 100% | 54 | | 4 | If you select a folder it will only take PNG's, GIF's, JPEG's and BMP's from that folder to optimize, no other formats will be included | 100% | 55 | | 5 | Automated builds / release | AppVeyor: 100% | 56 | | 6 | Packing the whole build into one usable exe file (As of version 1.0.46) (Using ILMerge as ILRepack does not work at this moment) | 100% | 57 | 58 | Basically as of version 1.0.46 this tool is fully usable. You can download the exe file from the releases page which can be ran if FileOptimizer is also installed. 59 | 60 | The reason the .exe file is quite big is because it includes the full LibVips library to do JPEG conversion to do accurate comparison. 61 | 62 | ## Screenshots 63 | 64 | ![ScreenshotMain](ScreenshotMain.png) 65 | 66 | ![ScreenshotConfig](ScreenshotConfig.png) 67 | -------------------------------------------------------------------------------- /ScreenshotConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedse/DeveImageOptimizerWPF/6b60e3a27b4e6f4390d884d859185acde7cbc197/ScreenshotConfig.png -------------------------------------------------------------------------------- /ScreenshotMain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devedse/DeveImageOptimizerWPF/6b60e3a27b4e6f4390d884d859185acde7cbc197/ScreenshotMain.png -------------------------------------------------------------------------------- /Scripts/GoRepack.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $relativePathToReleaseFolder = '..\DeveImageOptimizerWPF\bin\Release' 4 | $relativePathToILRepackExe = '..\packages\ILMerge.3.0.29\tools\net452\ILMerge.exe' 5 | $fileNameOfPrimaryExe = 'DeveImageOptimizerWPF.exe' 6 | $relativePathToOutputFolder = 'Output' 7 | 8 | $invocation = (Get-Variable MyInvocation).Value 9 | $directorypath = Split-Path $invocation.MyCommand.Path 10 | $ilrepackexe = Join-Path $directorypath $relativePathToILRepackExe -Resolve 11 | 12 | $releaseFolder = Join-Path $directorypath $relativePathToReleaseFolder -Resolve 13 | $deveimageoptimizerexe = Join-Path $releaseFolder $fileNameOfPrimaryExe -Resolve 14 | $outputfolder = Join-Path $directorypath $relativePathToOutputFolder 15 | $outputexe = Join-Path $outputfolder $fileNameOfPrimaryExe 16 | 17 | If(!(test-path $outputfolder)) 18 | { 19 | New-Item -ItemType Directory -Force -Path $outputfolder 20 | } 21 | 22 | Write-Host $directorypath; 23 | Write-Host $ilrepackexe; 24 | 25 | $arguments = @(); 26 | 27 | $arguments += "/lib:""C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7"""; 28 | $arguments += "/log"; 29 | $arguments += "/out:""$($outputexe)"""; 30 | $arguments += """$($deveimageoptimizerexe)"""; 31 | 32 | Get-ChildItem $releaseFolder -Filter *.dll | 33 | Foreach-Object { 34 | $path = """$($_.FullName)""" 35 | $arguments += $path 36 | Write-Host "Found dll for merging: $path" 37 | } 38 | 39 | Write-Host $arguments 40 | 41 | & $ilrepackexe $arguments 42 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } -------------------------------------------------------------------------------- /Scripts/set-version.ps1: -------------------------------------------------------------------------------- 1 | $ReleaseVersionNumber = $env:APPVEYOR_BUILD_VERSION 2 | $PreReleaseName = '' 3 | 4 | If($env:APPVEYOR_PULL_REQUEST_NUMBER -ne $null) { 5 | $PreReleaseName = '-PR-' + $env:APPVEYOR_PULL_REQUEST_NUMBER 6 | } ElseIf($env:APPVEYOR_REPO_BRANCH -ne 'master' -and -not $env:APPVEYOR_REPO_BRANCH.StartsWith('release')) { 7 | $PreReleaseName = '-' + $env:APPVEYOR_REPO_BRANCH 8 | } Else { 9 | $PreReleaseName = '' # This was previously: '-CI' 10 | } 11 | 12 | $totalVersion = "$ReleaseVersionNumber$PreReleaseName" 13 | 14 | $PSScriptFilePath = (Get-Item $MyInvocation.MyCommand.Path).FullName 15 | $ScriptDir = Split-Path -Path $PSScriptFilePath -Parent 16 | $SolutionRoot = Split-Path -Path $ScriptDir -Parent 17 | 18 | $csprojPath = Join-Path -Path $SolutionRoot -ChildPath "DeveImageOptimizerWPF\DeveImageOptimizerWPF.csproj" 19 | $re = [regex]"(?<=).*(?=<\/Version>)" 20 | 21 | Write-Host "Applying version $totalVersion to $csprojPath using regex $re" 22 | 23 | $re.Replace([string]::Join("`n", (Get-Content -Path $csprojPath)), "$totalVersion", 1) | Set-Content -Path $csprojPath -Encoding UTF8 --------------------------------------------------------------------------------