Image Pro

Manual_Copy_Paste_Code.txt
 ==========================================================================

[FILE_START: README.md]

==========================================================================

# SNAPIX Image Processing Utility

**Developer:** Kunal Vishwas  

**Version:** Enterprise Edition  

**Platform:** Windows 10 / 11 (64-bit)  

**Security Tier:** 100% Offline | Zero Admin Privileges | Zero PowerShell  


---


## 1. Overview & Modules


SNAPIX is a high-performance offline image processing suite designed for enterprise workloads supporting batch processing up to 10+ million records.


### Module Capabilities


1. **All Image Extraction**

   - Recursively extracts all images (`.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp`, `.tiff`, `.tif`, `.pdf`) from up to 30 source folders or ZIP archives.

   - Built-in deduplication (MD5 / SHA-256 / perceptual difference hashing).

   - Checkpoint auto-recovery for resuming interrupted jobs.


2. **Selective Image Extraction (Partition by Separator `_`)**

   - Matches and extracts images specified in an Excel sheet (Column A: Image names/patterns, Column B: Target folder values).

   - **Partition folders by `_`**: When checked, splits folder strings on `_` and creates nested folder hierarchies (e.g. `FolderA_Photos_2026` -> `FolderA/Photos/2026/`).

   - **Column C Status Report**: Automatically records `"Done"` (if extracted) or `"Left"` (if not found) into Column C under header `"Status"` upon completion.


3. **Generate Image Excel Report**

   - Scans image directories and creates high-performance Excel manifests with streaming `SXSSFWorkbook` (supporting millions of entries across auto-split sheets).


4. **Inner ZIP Extraction**

   - Scans nested `.zip` archives inside source directories and flattens/extracts contents.


5. **Split Images by Size**

   - Splits massive image sets into chunked directories or ZIP packages by byte-size limits (e.g. 500MB, 1GB, 4GB).


6. **Image Format & Compression Converter**

   - Batch converts between JPG, PNG, WEBP, BMP, and TIFF with adjustable quality/compression parameters using TwelveMonkeys SPI.


7. **Image Bulk Renamer**

   - Renames image collections according to an Excel mapping table (Column A: Original Name, Column B: New Name).


8. **Image Health & Validation Scanner**

   - Performs deep image corruption checks, header validation, face detection bypass, and generates comprehensive Excel health reports.


---


## 2. Quick Start & Distribution


### For End-Users / Colleagues

To run the application, only two files are required:

- `Launch_Tool.bat`

- `SNAPIX_Image_Processing_Utility.jar`


Double-click `Launch_Tool.bat`. The launcher automatically sets JVM heap memory parameters and launches the application.


---


## 3. Building from Source


### Prerequisites

- JDK 8 or higher (e.g., Amazon Corretto JDK 24 / 21 / 17 / 11 / 8).

- JAR dependencies in `lib/` directory.


### Build Scripts

- **`dev_build.bat`**: Compiles source code in `src/` to `bin/` with Java 8 bytecode compatibility.

- **`build_jar.bat`**: Compiles code, merges dependencies, bundles assets (`background.jpg`, `FOX.gif`, `Users.txt`), and produces `SNAPIX_Image_Processing_Utility.jar`.

- **`generate_manual_code.bat`**: Generates single-file code bundle `Manual_Copy_Paste_Code.txt`.

- **`unpack_code.bat`**: Restores all source files from `Manual_Copy_Paste_Code.txt` in under 2 seconds.


---


## 4. User Authorization & Expiry Configuration


The tool includes offline security verification embedded inside the JAR (`Users.txt`):

```text

31/12/2026,kunal95,2744440,username

```

- **First Token**: Tool expiry date in `DD/MM/YYYY` format.

- **Subsequent Tokens**: Comma-separated authorized Windows Employee IDs / usernames.

- Lines starting with `#` or `//` are comments.


---


## 5. Enterprise Cybersecurity & Performance Architecture


- **100% Strictly Offline**: Zero network sockets, zero HTTP, zero remote tracking.

- **Standard User Space**: Operates without Administrator privileges.

- **CMD Native**: 0% PowerShell usage across all batch scripts.

- **Scalability**: Bounded thread pools with `CallerRunsPolicy`, streaming Excel I/O (`flushRows(100)`), and bounded hash caches to process 10M+ items without heap exhaustion.


[FILE_END]


==========================================================================

[FILE_START: pom.xml]

==========================================================================

<?xml version="1.0" encoding="UTF-8"?>

<!-- Dev: Kunal Vishwas -->

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>


    <groupId>com.imageextractor</groupId>

    <artifactId>image-processing-tool</artifactId>

    <version>1.0.0</version>

    <packaging>jar</packaging>


    <name>Image Processing Tool</name>

    <description>Standalone offline image extraction, validation, conversion, and report tool</description>


    <properties>

        <maven.compiler.source>1.8</maven.compiler.source>

        <maven.compiler.target>1.8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <twelvemonkeys.version>3.10.1</twelvemonkeys.version>

        <poi.version>5.2.3</poi.version>

    </properties>


    <dependencies>

        <!-- TwelveMonkeys ImageIO Plugins -->

        <dependency>

            <groupId>com.twelvemonkeys.imageio</groupId>

            <artifactId>imageio-core</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.imageio</groupId>

            <artifactId>imageio-jpeg</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.imageio</groupId>

            <artifactId>imageio-bmp</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.imageio</groupId>

            <artifactId>imageio-webp</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.imageio</groupId>

            <artifactId>imageio-metadata</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.common</groupId>

            <artifactId>common-image</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.common</groupId>

            <artifactId>common-io</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>

        <dependency>

            <groupId>com.twelvemonkeys.common</groupId>

            <artifactId>common-lang</artifactId>

            <version>${twelvemonkeys.version}</version>

        </dependency>


        <!-- UI Look and Feel -->

        <dependency>

            <groupId>com.formdev</groupId>

            <artifactId>flatlaf</artifactId>

            <version>3.2.1</version>

        </dependency>


        <!-- Apache POI for Excel reporting -->

        <dependency>

            <groupId>org.apache.poi</groupId>

            <artifactId>poi</artifactId>

            <version>${poi.version}</version>

        </dependency>

        <dependency>

            <groupId>org.apache.poi</groupId>

            <artifactId>poi-ooxml</artifactId>

            <version>${poi.version}</version>

        </dependency>


        <!-- Apache Tika for MIME detection -->

        <dependency>

            <groupId>org.apache.tika</groupId>

            <artifactId>tika-core</artifactId>

            <version>2.9.2</version>

        </dependency>


        <!-- Logging -->

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-api</artifactId>

            <version>2.0.13</version>

        </dependency>

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-nop</artifactId>

            <version>2.0.13</version>

        </dependency>

    </dependencies>


    <build>

        <plugins>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>3.11.0</version>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-shade-plugin</artifactId>

                <version>3.5.0</version>

                <executions>

                    <execution>

                        <phase>package</phase>

                        <goals>

                            <goal>shade</goal>

                        </goals>

                        <configuration>

                            <transformers>

                                <!-- Merges SPI service files from TwelveMonkeys and other dependencies (javax.imageio.spi.ImageReaderSpi, ImageWriterSpi, etc.) -->

                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>

                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">

                                    <mainClass>com.imageextractor.Main</mainClass>

                                </transformer>

                            </transformers>

                            <filters>

                                <filter>

                                    <artifact>*:*</artifact>

                                    <excludes>

                                        <exclude>META-INF/*.SF</exclude>

                                        <exclude>META-INF/*.DSA</exclude>

                                        <exclude>META-INF/*.RSA</exclude>

                                    </excludes>

                                </filter>

                            </filters>

                        </configuration>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

</project>


[FILE_END]


==========================================================================

[FILE_START: Lib.txt]

==========================================================================

# ==============================================================================

# SNAPIX Image Processing Utility - Required JAR Dependencies (Lib.txt)

# Developer: Kunal Vishwas

# Platform: https://central.sonatype.com/

# Target Folder: lib/ (Place all 27 downloaded .jar files into the lib/ folder)

# ==============================================================================


[CATEGORY 1: TwelveMonkeys ImageIO Decoders (WebP, JPEG, BMP, Metadata)]

------------------------------------------------------------------------------

1. imageio-core-3.10.1.jar

   - Artifact: com.twelvemonkeys.imageio : imageio-core : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.imageio/imageio-core/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-core/3.10.1/imageio-core-3.10.1.jar


2. imageio-jpeg-3.10.1.jar

   - Artifact: com.twelvemonkeys.imageio : imageio-jpeg : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.imageio/imageio-jpeg/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-jpeg/3.10.1/imageio-jpeg-3.10.1.jar


3. imageio-bmp-3.10.1.jar

   - Artifact: com.twelvemonkeys.imageio : imageio-bmp : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.imageio/imageio-bmp/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-bmp/3.10.1/imageio-bmp-3.10.1.jar


4. imageio-webp-3.10.1.jar

   - Artifact: com.twelvemonkeys.imageio : imageio-webp : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.imageio/imageio-webp/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-webp/3.10.1/imageio-webp-3.10.1.jar


5. imageio-metadata-3.10.1.jar

   - Artifact: com.twelvemonkeys.imageio : imageio-metadata : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.imageio/imageio-metadata/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/imageio/imageio-metadata/3.10.1/imageio-metadata-3.10.1.jar


6. common-image-3.10.1.jar

   - Artifact: com.twelvemonkeys.common : common-image : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.common/common-image/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/common/common-image/3.10.1/common-image-3.10.1.jar


7. common-io-3.10.1.jar

   - Artifact: com.twelvemonkeys.common : common-io : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.common/common-io/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/common/common-io/3.10.1/common-io-3.10.1.jar


8. common-lang-3.10.1.jar

   - Artifact: com.twelvemonkeys.common : common-lang : 3.10.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.twelvemonkeys.common/common-lang/3.10.1

   - Direct Download:  https://repo1.maven.org/maven2/com/twelvemonkeys/common/common-lang/3.10.1/common-lang-3.10.1.jar



[CATEGORY 2: UI Look and Feel]

------------------------------------------------------------------------------

9. flatlaf-3.2.1.jar

   - Artifact: com.formdev : flatlaf : 3.2.1

   - Sonatype Central: https://central.sonatype.com/artifact/com.formdev/flatlaf/3.2.1

   - Direct Download:  https://repo1.maven.org/maven2/com/formdev/flatlaf/3.2.1/flatlaf-3.2.1.jar



[CATEGORY 3: Apache POI & Excel Reporting Engine]

------------------------------------------------------------------------------

10. poi-5.2.3.jar

    - Artifact: org.apache.poi : poi : 5.2.3

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.poi/poi/5.2.3

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/poi/poi/5.2.3/poi-5.2.3.jar


11. poi-ooxml-5.2.3.jar

    - Artifact: org.apache.poi : poi-ooxml : 5.2.3

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.poi/poi-ooxml/5.2.3

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/poi/poi-ooxml/5.2.3/poi-ooxml-5.2.3.jar


12. poi-ooxml-lite-5.2.3.jar

    - Artifact: org.apache.poi : poi-ooxml-lite : 5.2.3

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.poi/poi-ooxml-lite/5.2.3

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/poi/poi-ooxml-lite/5.2.3/poi-ooxml-lite-5.2.3.jar


13. xmlbeans-5.1.1.jar

    - Artifact: org.apache.xmlbeans : xmlbeans : 5.1.1

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.xmlbeans/xmlbeans/5.1.1

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/xmlbeans/xmlbeans/5.1.1/xmlbeans-5.1.1.jar


14. curvesapi-1.07.jar

    - Artifact: com.github.virtuald : curvesapi : 1.07

    - Sonatype Central: https://central.sonatype.com/artifact/com.github.virtuald/curvesapi/1.07

    - Direct Download:  https://repo1.maven.org/maven2/com/github/virtuald/curvesapi/1.07/curvesapi-1.07.jar


15. SparseBitSet-1.2.jar

    - Artifact: com.zaxxer : SparseBitSet : 1.2

    - Sonatype Central: https://central.sonatype.com/artifact/com.zaxxer/SparseBitSet/1.2

    - Direct Download:  https://repo1.maven.org/maven2/com/zaxxer/SparseBitSet/1.2/SparseBitSet-1.2.jar



[CATEGORY 4: Apache Commons Utilities]

------------------------------------------------------------------------------

16. commons-codec-1.15.jar

    - Artifact: commons-codec : commons-codec : 1.15

    - Sonatype Central: https://central.sonatype.com/artifact/commons-codec/commons-codec/1.15

    - Direct Download:  https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar


17. commons-collections4-4.4.jar

    - Artifact: org.apache.commons : commons-collections4 : 4.4

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.commons/commons-collections4/4.4

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/commons/commons-collections4/4.4/commons-collections4-4.4.jar


18. commons-compress-1.21.jar

    - Artifact: org.apache.commons : commons-compress : 1.21

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.commons/commons-compress/1.21

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/commons/commons-compress/1.21/commons-compress-1.21.jar


19. commons-io-2.11.0.jar

    - Artifact: commons-io : commons-io : 2.11.0

    - Sonatype Central: https://central.sonatype.com/artifact/commons-io/commons-io/2.11.0

    - Direct Download:  https://repo1.maven.org/maven2/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar


20. commons-math3-3.6.1.jar

    - Artifact: org.apache.commons : commons-math3 : 3.6.1

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.commons/commons-math3/3.6.1

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar



[CATEGORY 5: MIME & File Type Detection]

------------------------------------------------------------------------------

21. tika-core-2.9.2.jar

    - Artifact: org.apache.tika : tika-core : 2.9.2

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.tika/tika-core/2.9.2

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/tika/tika-core/2.9.2/tika-core-2.9.2.jar



[CATEGORY 6: Native System Access (Keep-Awake & Memory Monitoring)]

------------------------------------------------------------------------------

22. jna-5.13.0.jar

    - Artifact: net.java.dev.jna : jna : 5.13.0

    - Sonatype Central: https://central.sonatype.com/artifact/net.java.dev.jna/jna/5.13.0

    - Direct Download:  https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar


23. jna-platform-5.13.0.jar

    - Artifact: net.java.dev.jna : jna-platform : 5.13.0

    - Sonatype Central: https://central.sonatype.com/artifact/net.java.dev.jna/jna-platform/5.13.0

    - Direct Download:  https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar



[CATEGORY 7: Logging Engine]

------------------------------------------------------------------------------

24. slf4j-api-2.0.13.jar

    - Artifact: org.slf4j : slf4j-api : 2.0.13

    - Sonatype Central: https://central.sonatype.com/artifact/org.slf4j/slf4j-api/2.0.13

    - Direct Download:  https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.13/slf4j-api-2.0.13.jar


25. slf4j-nop-2.0.13.jar

    - Artifact: org.slf4j : slf4j-nop : 2.0.13

    - Sonatype Central: https://central.sonatype.com/artifact/org.slf4j/slf4j-nop/2.0.13

    - Direct Download:  https://repo1.maven.org/maven2/org/slf4j/slf4j-nop/2.0.13/slf4j-nop-2.0.13.jar


26. log4j-api-2.19.0.jar

    - Artifact: org.apache.logging.log4j : log4j-api : 2.19.0

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-api/2.19.0

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.19.0/log4j-api-2.19.0.jar


27. log4j-core-2.19.0.jar

    - Artifact: org.apache.logging.log4j : log4j-core : 2.19.0

    - Sonatype Central: https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-core/2.19.0

    - Direct Download:  https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.19.0/log4j-core-2.19.0.jar


# ==============================================================================

# Instructions:

# 1. Download each of the 27 JAR files from the direct download links above.

# 2. Save them into the 'lib' folder of the project.

# 3. Run 'dev_build.bat' to compile or 'build_jar.bat' to create the standalone executable.

# ==============================================================================


[FILE_END]


==========================================================================

[FILE_START: build_jar.bat]

==========================================================================

rem Dev: Kunal Vishwas

@echo off

cd /d "%~dp0"

echo ========================================================

echo Packaging Standalone Image Processing JAR

echo ========================================================

echo.

echo Compiling source code first...

call dev_build.bat

if %errorlevel% neq 0 (

    echo.

    echo Compilation failed! Packaging aborted.

    pause

    exit /b %errorlevel%

)


echo.

echo Extracting dependencies from lib folder and preserving SPI services...

cd bin

if exist ..\temp_services rd /s /q ..\temp_services 2>nul

mkdir ..\temp_services


for %%f in (..\lib\*.jar) do (

    echo Extracting %%f

    jar xf %%f

    if exist META-INF\services (

        for %%s in (META-INF\services\*) do (

            type "%%s" >> "..\temp_services\%%~nxs" 2>nul

            echo. >> "..\temp_services\%%~nxs"

        )

    )

)


echo.

echo Merging META-INF/services files across all dependencies...

if exist ..\temp_services (

    if not exist META-INF\services mkdir META-INF\services

    for %%s in (..\temp_services\*) do (

        if exist "META-INF\services\%%~nxs" del "META-INF\services\%%~nxs"

        for /f "usebackq tokens=1 delims= " %%L in ("%%s") do (

            if not "%%L"=="" if not "%%L"=="ECHO" (

                findstr /x /c:"%%L" "META-INF\services\%%~nxs" >nul 2>&1

                if errorlevel 1 >>"META-INF\services\%%~nxs" echo %%L

            )

        )

    )

    rd /s /q ..\temp_services 2>nul

)


echo Removing signature files to prevent JAR security exceptions...

del /q /s META-INF\*.SF 2>nul

del /q /s META-INF\*.DSA 2>nul

del /q /s META-INF\*.RSA 2>nul


echo.

echo Adding background image to package...

if exist ..\background.jpg copy /Y ..\background.jpg background.jpg > nul 2>nul

if exist ..\background.png copy /Y ..\background.png background.png > nul 2>nul

echo Adding FOX.gif to package...

if exist ..\FOX.gif copy /Y ..\FOX.gif FOX.gif > nul 2>nul

if exist ..\fox.gif copy /Y ..\fox.gif FOX.gif > nul 2>nul

echo Bundling Users.txt authorization file into JAR...

if exist ..\Users.txt copy /y ..\Users.txt Users.txt >nul 2>nul

if exist ..\Users.text copy /y ..\Users.text Users.txt >nul 2>nul

if not exist Users.txt (

    echo [INFO] Creating default Users.txt for current user...

    (echo 31/12/2026,kunal95,2744440,%USERNAME%) > Users.txt

)

cd ..


echo.

echo Creating Standalone Fat JAR...

> MANIFEST.MF echo Manifest-Version: 1.0

>> MANIFEST.MF echo Main-Class: com.imageextractor.Main


jar cvfm SNAPIX_Image_Processing_Utility.jar MANIFEST.MF -C bin .

if %errorlevel% neq 0 (

    echo.

    echo Packaging failed!

    del MANIFEST.MF

    pause

    exit /b %errorlevel%

)


echo.

echo Cleaning up temporary files...

del MANIFEST.MF

echo ========================================================

echo SUCCESS! 

echo Your standalone executable is ready: SNAPIX_Image_Processing_Utility.jar

echo You can double click it or use Launch_Tool.bat to run it.

echo ========================================================


[FILE_END]


==========================================================================

[FILE_START: dev_build.bat]

==========================================================================

rem Dev: Kunal Vishwas

@echo off

setlocal enabledelayedexpansion

cd /d "%~dp0"


echo ========================================================

echo Image Processing Tool - Manual JDK Build

echo ========================================================

echo.

echo Make sure you have placed all downloaded .jar files into the `lib` folder!

echo.


echo Cleaning and creating output directory...

if exist bin rd /s /q bin

mkdir bin


echo Locating source files...

if exist sources.txt del sources.txt


set "PROJ_ROOT=%~dp0"

for /R src %%i in (*.java) do (

    set "F=%%i"

    set "F=!F:%PROJ_ROOT%=!"

    set "F=!F:\=/!"

    >> sources.txt echo "!F!"

)


if not exist sources.txt (

    echo ERROR: No .java source files found under src\

    exit /b 1

)


echo Compiling Java code...

javac --release 8 -encoding UTF-8 -cp "lib/*" -d bin @sources.txt > compile_log.txt 2>&1

set "COMPILE_STATUS=!errorlevel!"


if !COMPILE_STATUS! neq 0 (

    echo Compilation with --release failed. Retrying with legacy source/target flags...

    javac -source 8 -target 8 -encoding UTF-8 -cp "lib/*" -d bin @sources.txt > compile_log.txt 2>&1

    set "COMPILE_STATUS=!errorlevel!"

)


type compile_log.txt

if exist compile_log.txt del compile_log.txt

if exist sources.txt del sources.txt


if !COMPILE_STATUS! neq 0 (

    echo.

    echo Compilation failed Please ensure all .jar files are in the lib folder and Corretto JDK is available.

    exit /b !COMPILE_STATUS!

)


echo.

echo Build Successful You can now use Launch_Tool.bat to run the program.

exit /b 0


[FILE_END]


==========================================================================

[FILE_START: Launch_Tool.bat]

==========================================================================

rem Dev: Kunal Vishwas

@echo off

cd /d "%~dp0"


if not exist "logs" mkdir logs


echo.

echo ============================================================

echo    SNAPIX IMAGE PROCESSING UTILITY

echo ============================================================

echo [LAUNCH] Executing SNAPIX_Image_Processing_Utility.jar

echo [JVM]    Memory Configuration: Up to 8192m (64-bit Enterprise Mode)

echo [JVM]    Character Encoding: UTF-8

echo [DIAGNOSTICS] Boot logs saved in: logs\app.log

echo.


set "JVM_FLAGS=-Xms512m -Xmx8192m -XX:+UseG1GC -Dfile.encoding=UTF-8"

set "JVM_FALLBACK=-Xms512m -Xmx2048m -Dfile.encoding=UTF-8"


if exist "SNAPIX_Image_Processing_Utility.jar" (

    java %JVM_FLAGS% -jar SNAPIX_Image_Processing_Utility.jar

    if errorlevel 1 (

        echo [JVM] Retrying with standard memory allocation...

        java %JVM_FALLBACK% -jar SNAPIX_Image_Processing_Utility.jar

    )

) else if exist "ImageProcessingTool.jar" (

    java %JVM_FLAGS% -jar ImageProcessingTool.jar

    if errorlevel 1 (

        echo [JVM] Retrying with standard memory allocation...

        java %JVM_FALLBACK% -jar ImageProcessingTool.jar

    )

) else (

    java %JVM_FLAGS% -cp "bin;lib/*" com.imageextractor.Main

    if errorlevel 1 (

        echo [JVM] Retrying with standard memory allocation...

        java %JVM_FALLBACK% -cp "bin;lib/*" com.imageextractor.Main

    )

)


echo.

echo Process terminated. Press any key to close this console.

pause


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\Main.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor;


import com.imageextractor.ui.MainWindow;

import com.imageextractor.services.LogService;

import com.imageextractor.services.UserVerificationService;


import javax.swing.*;

import java.awt.*;

import javax.imageio.ImageIO;


public class Main {

    public static void main(String[] args) {


        // 1. Tool Expiry Check (FIRST)

        if (UserVerificationService.isToolExpired()) {

            String expiryMessage =

                    "  ========================================================================================\n" +

                    "  [!] THIS VERSION OF THE UTILITY HAS EXPIRED. PLEASE CONTACT KUNAL VISHWAS FOR AN UPDATE.\n" +

                    "  ========================================================================================\n\n" +

                    "  ERROR  CODE :  ITSALLABOUTError95";


            if (!java.awt.GraphicsEnvironment.isHeadless()) {

                try {

                    SwingUtilities.invokeAndWait(() -> {

                        JOptionPane.showMessageDialog(null, expiryMessage, "ITSALLABOUTError95", JOptionPane.ERROR_MESSAGE);

                    });

                } catch (InterruptedException ie) {

                    Thread.currentThread().interrupt();

                } catch (Exception ignored) {}

            } else {

                System.err.println(expiryMessage);

            }

            System.exit(1);

        }


        // 2. User Authorization Check (SECOND)

        if (!UserVerificationService.isCurrentUserAuthorized()) {

            String currentUserId = UserVerificationService.getWindowsUsername();

            String message = "Unauthorized User\n\n" +

                    "Your Windows Employee ID (" + (currentUserId.isEmpty() ? "Unknown" : currentUserId) + ") is not authorized to access this tool.\n" +

                    "Please contact Kunal Vishwas to request access.";


            if (!java.awt.GraphicsEnvironment.isHeadless()) {

                try {

                    SwingUtilities.invokeAndWait(() -> {

                        JOptionPane.showMessageDialog(null, message, "Unauthorized User", JOptionPane.ERROR_MESSAGE);

                    });

                } catch (InterruptedException ie) {

                    Thread.currentThread().interrupt();

                } catch (Exception ignored) {}

            } else {

                System.err.println("[SECURITY ERROR] " + message);

            }

            System.exit(1);

        }


        // -- SYSTEM BOOT Diagnostics -------------------------------------------

        printBootDiagnostics();


        // Register a global default uncaught exception handler to prevent silent worker crashes


        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {

            try {

                LogService.log("FATAL", "ERROR", "Uncaught exception on thread: " + t.getName() + " - " + e.getMessage());

            } catch (Throwable ignored) {}

            e.printStackTrace();

            

            // Graceful User Alert on the EDT

            SwingUtilities.invokeLater(() -> {

                JOptionPane.showMessageDialog(null,

                    "An unexpected fatal error occurred in the processing suite:\n" +

                    e.toString() + "\n\nPlease check logs/app.log for full diagnostic trace details.",

                    "Fatal Application Error",

                    JOptionPane.ERROR_MESSAGE);

            });

        });


        // Waking TwelveMonkeys Dynamic SPI Plugins for standalone fat-jar classloaders

        try {

            ImageIO.scanForPlugins();

            String[] formatNames = ImageIO.getReaderFormatNames();

            java.util.Set<String> uniqueFormats = new java.util.TreeSet<>(String.CASE_INSENSITIVE_ORDER);

            java.util.Collections.addAll(uniqueFormats, formatNames);

            LogService.log("INIT", "INFO", "Registered ImageIO reader format names: " + String.join(", ", uniqueFormats));

        } catch (Throwable t) {

            LogService.log("INIT", "ERROR", "Failed to scan dynamic ImageIO plugins: " + t.getMessage());

        }


        // Setup modern stylish navy blue Look and Feel

        try {

            com.formdev.flatlaf.FlatDarkLaf.setup();

            

            // Apply high-end modern curves (25px roundness)

            UIManager.put("Component.focusWidth", 1);

            UIManager.put("Button.arc", 25);

            UIManager.put("Component.arc", 25);

            UIManager.put("ProgressBar.arc", 25);

            UIManager.put("TextComponent.arc", 25);

            

            // Premium 60% Navy Blue & Neon Cyan/Emerald Green color accents

            String navyDark = "#0a192f";    // Deep Navy

            String cyanAccent = "#00b4d8";  // Glowing Cyan/Blue

            

            UIManager.put("Component.accentColor", cyanAccent);

            UIManager.put("TabbedPane.selectedBackground", Color.decode(navyDark));

            UIManager.put("Button.background", Color.decode(navyDark));

            UIManager.put("Button.foreground", Color.WHITE);

            UIManager.put("Button.hoverBackground", Color.decode(cyanAccent));

            

            // Force disable default purple colors in FlatDarkLaf

            UIManager.put("TextArea.selectionBackground", new Color(0, 180, 216, 60));

            UIManager.put("TextComponent.selectionBackground", new Color(0, 180, 216, 60));

            UIManager.put("TextField.selectionBackground", new Color(0, 180, 216, 60));

            UIManager.put("List.selectionBackground", new Color(0, 180, 216, 50));

            UIManager.put("Table.selectionBackground", new Color(0, 180, 216, 50));

            

            UIManager.put("ScrollBar.thumbColor", new Color(0, 180, 216, 80));

            UIManager.put("ScrollBar.hoverThumbColor", new Color(0, 180, 216, 150));

            UIManager.put("ScrollBar.pressedThumbColor", new Color(0, 180, 216, 200));

            UIManager.put("ScrollBar.track", new Color(10, 25, 47, 80));


            

        } catch (Exception ex) {

            LogService.log("INIT", "WARNING", "Failed to initialize LaF: " + ex.getMessage());

        }


        // Automatically start system keep-awake heartbeat when tool is open

        com.imageextractor.services.KeepAwakeService.start();


        SwingUtilities.invokeLater(() -> {

            MainWindow mainWindow = new MainWindow();

            mainWindow.setVisible(true);

        });

    }


    private static void printBootDiagnostics() {

        try {

            java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

            String ts = sdf.format(new java.util.Date());


            Runtime rt = Runtime.getRuntime();

            long maxMemMB  = rt.maxMemory()  / (1024 * 1024);

            long totalMemMB= rt.totalMemory()/ (1024 * 1024);

            long freeMemMB = rt.freeMemory() / (1024 * 1024);

            int  cpuCores  = rt.availableProcessors();


            String logFilePath = new java.io.File("logs" + java.io.File.separator + "app.log")

                                        .getAbsolutePath();


            String sep  = "=".repeat(59);

            String line = "\n" + sep +

                "\n[" + ts + "][SYSTEM] ================= SYSTEM BOOT =================" +

                "\nLog File Initialized at: " + logFilePath +

                "\n[" + ts + "][INFO] SNAPIX IMAGE PROCESSING UTILITY - Starting up." +

                "\n[" + ts + "][DIAG]" +

                "\n=================== RUNTIME DIAGNOSTICS ===================" +

                "\nTimestamp:                    " + ts +

                "\nOS Name:                      " + System.getProperty("os.name", "Unknown") +

                "\nOS Version:                   " + System.getProperty("os.version", "Unknown") +

                "\nOS Architecture:              " + System.getProperty("os.arch", "Unknown") +

                "\nJava Version:                 " + System.getProperty("java.version", "Unknown") +

                "\nJava Runtime:                 " + System.getProperty("java.runtime.name", "Unknown") +

                "\nJVM Vendor:                   " + System.getProperty("java.vendor", "Unknown") +

                "\nJAVA_HOME:                    " + System.getProperty("java.home", "Unknown") +

                "\nUser Name:                    " + System.getProperty("user.name", "Unknown") +

                "\nUser Home:                    " + System.getProperty("user.home", "Unknown") +

                "\nCurrent Directory:            " + System.getProperty("user.dir", "Unknown") +

                "\nAvailable CPU Cores:          " + cpuCores +

                "\nMax Memory Allocation (Xmx):  " + maxMemMB + " MB" +

                "\nTotal Memory in Use:          " + totalMemMB + " MB" +

                "\nFree Memory Available:        " + freeMemMB + " MB" +

                "\nSystem Temporary Dir:         " + System.getProperty("java.io.tmpdir", "Unknown") +

                "\n===========================================================\n";


            // Print to console (visible in cmd window)

            System.out.println(line);


            // Also write directly to log file so it appears at the top of app.log

            LogService.log("SYSTEM", "INFO",  "SNAPIX IMAGE PROCESSING UTILITY - Boot diagnostics logged.");

            LogService.log("DIAG",   "INFO",  "OS: "          + System.getProperty("os.name") + " " + System.getProperty("os.version") + " (" + System.getProperty("os.arch") + ")");

            LogService.log("DIAG",   "INFO",  "Java: "        + System.getProperty("java.version") + " | Vendor: " + System.getProperty("java.vendor"));

            LogService.log("DIAG",   "INFO",  "JAVA_HOME: "   + System.getProperty("java.home"));

            LogService.log("DIAG",   "INFO",  "User: "        + System.getProperty("user.name") + " | Home: " + System.getProperty("user.home"));

            LogService.log("DIAG",   "INFO",  "CPU Cores: "   + cpuCores + " | Xmx: " + maxMemMB + " MB | Total: " + totalMemMB + " MB | Free: " + freeMemMB + " MB");

            LogService.log("DIAG",   "INFO",  "Temp Dir: "    + System.getProperty("java.io.tmpdir"));


        } catch (Throwable t) {

            // Diagnostics failure must never block startup

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\DuplicateManager.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.awt.Graphics2D;

import java.awt.image.BufferedImage;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

import java.nio.file.Files;

import java.nio.file.Path;

import java.security.MessageDigest;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import javax.imageio.ImageIO;


public class DuplicateManager {

    private static final String APP_HOME = System.getProperty("user.dir", ".");

    private static final String CACHE_DIR = APP_HOME + File.separator + "cache";

    private static final String CACHE_FILE = CACHE_DIR + File.separator + "hash_cache.dat";

    private static final Map<String, CacheEntry> memoryCache = new ConcurrentHashMap<>();

    private static final java.util.concurrent.atomic.AtomicBoolean isEvicting = new java.util.concurrent.atomic.AtomicBoolean(false);


    static {

        File dir = new File(CACHE_DIR);

        if (!dir.exists() && !dir.mkdirs()) {

            LogService.log("CACHE", "WARNING", "Could not create cache directory: " + dir.getAbsolutePath());

        }

        loadCache();

        

        // Centralized JVM shutdown hook to guarantee duplicate cache persistence on exit

        Runtime.getRuntime().addShutdownHook(new Thread(DuplicateManager::saveCache));

    }


    public static class CacheEntry implements Serializable {

        private static final long serialVersionUID = 1L;

        public final long lastModified;

        public final String sha256;

        public final long dHash;

        public final String partialHash;


        public CacheEntry(long lastModified, String sha256, long dHash, String partialHash) {

            this.lastModified = lastModified;

            this.sha256 = sha256;

            this.dHash = dHash;

            this.partialHash = partialHash;

        }

    }


    @SuppressWarnings("unchecked")

    private static void loadCache() {

        File file = new File(CACHE_FILE);

        if (!file.exists()) return;

        try (FileInputStream fis = new FileInputStream(file);

             ObjectInputStream ois = new ObjectInputStream(fis) {

                 @Override

                 protected Class<?> resolveClass(java.io.ObjectStreamClass desc) throws IOException, ClassNotFoundException {

                     String name = desc.getName();

                     if (!name.startsWith("com.imageextractor.") &&

                         !name.equals("java.util.HashMap") &&

                         !name.equals("java.util.Map") &&

                         !name.equals("java.lang.String") &&

                         !name.equals("java.lang.Long") &&

                         !name.equals("java.lang.Integer") &&

                         !name.equals("java.lang.Number")) {

                         throw new java.io.InvalidClassException("Unauthorized deserialization class: " + name);

                     }

                     return super.resolveClass(desc);

                 }

             }) {

            Map<String, CacheEntry> loaded = (Map<String, CacheEntry>) ois.readObject();

            if (loaded != null) {

                memoryCache.putAll(loaded);

            }

        } catch (Exception e) {

            LogService.log("CACHE", "ERROR", "Failed to load hash cache: " + e.getMessage());

        }

    }


    public static synchronized void saveCache() {

        File file = new File(CACHE_FILE);

        File tempFile = new File(CACHE_FILE + ".tmp");

        try {

            try (FileOutputStream fos = new FileOutputStream(tempFile);

                 ObjectOutputStream oos = new ObjectOutputStream(fos)) {

                oos.writeObject(new HashMap<>(memoryCache));

            }

            try {

                java.nio.file.Files.move(tempFile.toPath(), file.toPath(), 

                    java.nio.file.StandardCopyOption.ATOMIC_MOVE, 

                    java.nio.file.StandardCopyOption.REPLACE_EXISTING);

            } catch (java.nio.file.AtomicMoveNotSupportedException amse) {

                java.nio.file.Files.move(tempFile.toPath(), file.toPath(), 

                    java.nio.file.StandardCopyOption.REPLACE_EXISTING);

            }

        } catch (Exception e) {

            LogService.log("CACHE", "ERROR", "Failed to save hash cache: " + e.getMessage());

            try {

                java.nio.file.Files.deleteIfExists(tempFile.toPath());

            } catch (IOException ignored) {}

        }

    }


    /**

     * Generates SHA-256 cryptographic hash of a file.

     */

    public static String computeSHA256(Path path) throws IOException {

        String absolutePath = path.toAbsolutePath().toString();

        long modified = Files.getLastModifiedTime(path).toMillis();


        CacheEntry cached = memoryCache.get(absolutePath);

        if (cached != null && cached.lastModified == modified && cached.sha256 != null) {

            return cached.sha256;

        }


        String hash = computeSHA256FromFile(path);

        updateCache(absolutePath, modified, hash, cached != null ? cached.dHash : 0L, cached != null ? cached.partialHash : null);

        return hash;

    }


    private static String computeSHA256FromFile(Path path) throws IOException {

        try (InputStream is = Files.newInputStream(path)) {

            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            byte[] buffer = new byte[8192];

            int read;

            while ((read = is.read(buffer)) != -1) {

                digest.update(buffer, 0, read);

            }

            return bytesToHex(digest.digest());

        } catch (Exception e) {

            throw new IOException("Failed to compute SHA-256: " + e.getMessage());

        }

    }


    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();

    private static String bytesToHex(byte[] bytes) {

        char[] hexChars = new char[bytes.length * 2];

        for (int i = 0; i < bytes.length; i++) {

            int v = bytes[i] & 0xFF;

            hexChars[i * 2] = HEX_CHARS[v >>> 4];

            hexChars[i * 2 + 1] = HEX_CHARS[v & 0x0F];

        }

        return new String(hexChars);

    }


    /**

     * Generates a 64-bit dHash (difference hash) for perceptual image duplicate matching.

     */

    public static long computeDHash(Path path) throws IOException {

        String absolutePath = path.toAbsolutePath().toString();

        long modified = Files.getLastModifiedTime(path).toMillis();


        CacheEntry cached = memoryCache.get(absolutePath);

        if (cached != null && cached.lastModified == modified && cached.dHash != 0L) {

            return cached.dHash;

        }


        long dHash = computeDHashFromImage(path);

        updateCache(absolutePath, modified, cached != null ? cached.sha256 : null, dHash, cached != null ? cached.partialHash : null);

        return dHash;

    }


    /**

     * Generates partial SHA-256 of the first 8KB of a file.

     */

    public static String computePartialHash(Path path) throws IOException {

        String absolutePath = path.toAbsolutePath().toString();

        long modified = Files.getLastModifiedTime(path).toMillis();


        CacheEntry cached = memoryCache.get(absolutePath);

        if (cached != null && cached.lastModified == modified && cached.partialHash != null) {

            return cached.partialHash;

        }


        String partialHash = computePartialHashFromFile(path);

        updateCache(absolutePath, modified, cached != null ? cached.sha256 : null, cached != null ? cached.dHash : 0L, partialHash);

        return partialHash;

    }


    private static String computePartialHashFromFile(Path path) throws IOException {

        try (InputStream is = Files.newInputStream(path)) {

            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            byte[] buffer = new byte[8192];

            int bytesRead = 0;

            int read;

            while (bytesRead < 8192 && (read = is.read(buffer, bytesRead, 8192 - bytesRead)) != -1) {

                bytesRead += read;

            }

            if (bytesRead > 0) {

                digest.update(buffer, 0, bytesRead);

            }

            return bytesToHex(digest.digest());

        } catch (Exception e) {

            throw new IOException("Failed to compute partial SHA-256: " + e.getMessage());

        }

    }


    private static long computeDHashFromImage(Path path) throws IOException {

        // Dimension check before full decode to prevent huge image memory crashes

        try (InputStream is = Files.newInputStream(path);

             javax.imageio.stream.ImageInputStream iis = ImageIO.createImageInputStream(is)) {

            if (iis != null) {

                java.util.Iterator<javax.imageio.ImageReader> readers = ImageIO.getImageReaders(iis);

                if (readers.hasNext()) {

                    javax.imageio.ImageReader reader = readers.next();

                    try {

                        reader.setInput(iis);

                        int w = reader.getWidth(0);

                        int h = reader.getHeight(0);

                        if (w > 32768 || h > 32768) {

                            throw new IOException("Decompression bomb protection triggered in dHash! Malicious image size: " + w + "x" + h);

                        }

                    } finally {

                        reader.dispose();

                    }

                }

            }

        }


        BufferedImage img;

        try (InputStream is = Files.newInputStream(path)) {

            img = ImageIO.read(is);

        }

        if (img == null) {

            return 0L;

        }


        try {

            // Resize to 9x8 grayscale image using fast drawImage

            BufferedImage tiny = new BufferedImage(9, 8, BufferedImage.TYPE_BYTE_GRAY);

            Graphics2D g2d = tiny.createGraphics();

            g2d.drawImage(img, 0, 0, 9, 8, null);

            g2d.dispose();


            long hash = 0L;

            for (int y = 0; y < 8; y++) {

                for (int x = 0; x < 8; x++) {

                    int p1 = tiny.getRaster().getSample(x, y, 0);

                    int p2 = tiny.getRaster().getSample(x + 1, y, 0);

                    if (p1 < p2) {

                        hash |= (1L << (y * 8 + x));

                    }

                }

            }

            tiny.flush();

            return hash;

        } finally {

            img.flush();

        }

    }


    private static final int MAX_CACHE_ENTRIES = 50000;


    private static synchronized void pruneCacheIfNeeded() {

        if (memoryCache.size() > MAX_CACHE_ENTRIES) {

            java.util.List<Map.Entry<String, CacheEntry>> entries = new java.util.ArrayList<>(memoryCache.entrySet());

            entries.sort(java.util.Comparator.comparingLong(e -> e.getValue().lastModified));

            int pruneCount = memoryCache.size() - (int)(MAX_CACHE_ENTRIES * 0.8);

            for (int i = 0; i < pruneCount && i < entries.size(); i++) {

                memoryCache.remove(entries.get(i).getKey());

            }

            LogService.log("CACHE", "INFO", "Pruned " + pruneCount + " old entries from hash cache to control size.");

        }

    }


    private static void updateCache(String path, long modified, String sha256, long dHash, String partialHash) {

        memoryCache.put(path, new CacheEntry(modified, sha256, dHash, partialHash));

        pruneCacheIfNeeded();

    }


    public static class StagedDuplicateResult {

        public final boolean isDuplicate;

        public final String reason;

        public final String hash;


        public StagedDuplicateResult(boolean isDuplicate, String reason, String hash) {

            this.isDuplicate = isDuplicate;

            this.reason = reason;

            this.hash = hash;

        }

    }


    public static StagedDuplicateResult checkDuplicateStaged(

            Path path,

            Map<String, String> processedHashes,

            Map<String, Path> processedCandidates

    ) throws IOException {

        return checkDuplicateStaged(path, path, processedHashes, processedCandidates);

    }


    /**

     * High-Performance Staged Hashing duplicate check pipeline:

     * - Stage 1 (Size Filter) & Stage 2 (8KB Partial Hash): Bypasses full hashing for unique files.

     * - Stage 3 (Full SHA-256): Resolves true duplicates on collision.

     */

    public static StagedDuplicateResult checkDuplicateStaged(

            Path sourcePath,

            Path persistentPath,

            Map<String, String> processedHashes,

            Map<String, Path> processedCandidates

    ) throws IOException {

        String absolutePath = sourcePath.toAbsolutePath().toString();

        long modified = Files.getLastModifiedTime(sourcePath).toMillis();

        long size = Files.size(sourcePath);

        String fileName = sourcePath.getFileName().toString();


        // Stage 1 & 2: Generate candidate key from size and partial hash

        String partialHash = computePartialHash(sourcePath);

        String candidateKey = size + "_" + partialHash;


        Path storePath = sourcePath;


        // Bounded capacity protection: prune oldest 20% if size exceeds 500k to prevent OOM without losing all history

        if (processedCandidates != null && processedCandidates.size() > 500000) {

            if (isEvicting.compareAndSet(false, true)) {

                try {

                    int toRemove = processedCandidates.size() / 5;

                    java.util.Iterator<String> it = processedCandidates.keySet().iterator();

                    while (it.hasNext() && toRemove-- > 0) {

                        it.next();

                        it.remove();

                    }

                } finally {

                    isEvicting.set(false);

                }

            }

        }

        if (processedHashes != null && processedHashes.size() > 500000) {

            if (isEvicting.compareAndSet(false, true)) {

                try {

                    int toRemove = processedHashes.size() / 5;

                    java.util.Iterator<String> it = processedHashes.keySet().iterator();

                    while (it.hasNext() && toRemove-- > 0) {

                        it.next();

                        it.remove();

                    }

                } finally {

                    isEvicting.set(false);

                }

            }

        }


        // Check candidate collision

        Path originalPath = (processedCandidates != null) ? processedCandidates.putIfAbsent(candidateKey, storePath) : null;

        if (originalPath == null) {

            // Unique candidate! Bypasses full hashing entirely.

            CacheEntry cached = memoryCache.get(absolutePath);

            String hashToUse = (cached != null && cached.sha256 != null && cached.lastModified == modified) 

                    ? cached.sha256 

                    : "Partial-" + (partialHash.length() >= 12 ? partialHash.substring(0, 12) : partialHash);

            

            if (processedHashes != null) {

                processedHashes.put(hashToUse, fileName);

            }

            return new StagedDuplicateResult(false, null, hashToUse);

        }


        // Collision occurred! Compute full SHA-256 of the current file

        String currentFullHash = computeSHA256(sourcePath);


        // Check if this full hash is already in processedHashes

        String originalName = (processedHashes != null) ? processedHashes.get(currentFullHash) : null;

        if (originalName != null) {

            return new StagedDuplicateResult(true, "Identical file hash match (SHA-256) with original: " + originalName, currentFullHash);

        }


        // Compute full hash of the original candidate path safely

        String originalFullHash = null;

        if (Files.exists(originalPath)) {

            originalFullHash = computeSHA256(originalPath);

        } else if (persistentPath != null && Files.exists(persistentPath)) {

            originalFullHash = computeSHA256(persistentPath);

        }


        if (originalFullHash == null) {

            // If original candidate file cannot be read, treat as unique candidate and proceed

            if (processedHashes != null) {

                processedHashes.put(currentFullHash, fileName);

            }

            return new StagedDuplicateResult(false, null, currentFullHash);

        }


        if (processedHashes != null) {

            processedHashes.put(originalFullHash, originalPath.getFileName().toString());

        }


        if (currentFullHash.equals(originalFullHash)) {

            // True duplicate!

            return new StagedDuplicateResult(true, "Identical file hash match (SHA-256) with original: " + originalPath.getFileName().toString(), currentFullHash);

        } else {

            // False collision (extremely rare but mathematically possible)

            if (processedHashes != null) {

                processedHashes.put(currentFullHash, fileName);

            }

            return new StagedDuplicateResult(false, null, currentFullHash);

        }

    }


    /**

     * Calculates Hamming similarity percentage between two perceptual hashes.

     */

    public static double calculatePerceptualSimilarity(long hash1, long hash2) {

        int hammingDistance = Long.bitCount(hash1 ^ hash2);

        return (64.0 - hammingDistance) / 64.0;

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\EnterpriseThreadPoolExecutor.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.util.concurrent.*;

import java.util.concurrent.atomic.AtomicInteger;


public class EnterpriseThreadPoolExecutor extends ThreadPoolExecutor {

    private static final AtomicInteger poolNumber = new AtomicInteger(1);


    public EnterpriseThreadPoolExecutor(int corePoolSize, int maxQueueCapacity) {

        super(

            corePoolSize,

            corePoolSize,

            0L,

            TimeUnit.MILLISECONDS,

            new ArrayBlockingQueue<>(maxQueueCapacity),

            new ThreadFactory() {

                private final String prefix = "enterprise-worker-" + poolNumber.getAndIncrement() + "-thread-";

                private final AtomicInteger threads = new AtomicInteger(1);

                

                @Override

                public Thread newThread(Runnable r) {

                    Thread t = new Thread(r, prefix + threads.getAndIncrement());

                    t.setDaemon(true);

                    t.setPriority(Thread.NORM_PRIORITY);

                    return t;

                }

            },

            new ThreadPoolExecutor.CallerRunsPolicy() // Bounded backpressure: runs on caller thread if pool queue is saturated

        );

    }


    @Override

    protected void afterExecute(Runnable r, Throwable t) {

        super.afterExecute(r, t);

        

        // Intercept silent exceptions thrown inside threads

        if (t == null && r instanceof Future<?>) {

            try {

                Future<?> future = (Future<?>) r;

                if (future.isDone()) {

                    future.get();

                }

            } catch (CancellationException ce) {

                // Handled cancellation, ignore

            } catch (ExecutionException ee) {

                t = ee.getCause();

            } catch (InterruptedException ie) {

                Thread.currentThread().interrupt(); // Restore status

            }

        }

        

        if (t != null) {

            // Log crash details structurally and increment error counters in task manager

            String errMsg = t.getMessage() != null ? t.getMessage() : t.getClass().getSimpleName();

            LogService.log("THREAD", "ERROR", "Thread task crashed: " + errMsg);

            TaskManager.incrementFailedCount();

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ExcelService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import org.apache.poi.ss.usermodel.*;

import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import org.apache.poi.xssf.streaming.SXSSFSheet;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.nio.file.DirectoryStream;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.StandardCopyOption;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.HashSet;

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.function.Consumer;


public class ExcelService {


    public static class SelectiveEntry {

        public final String originalTarget;

        public final String normalizedPath; // lowercase with forward slashes (e.g. c:/users/.../sample.jpg or photo/sample.jpg)

        public final String fileName;       // lowercase (e.g. sample.jpg)

        public final String baseName;       // lowercase (e.g. sample)

        public final String destSubfolder;  // target subfolder if specified

        public final boolean isPathTarget;  // true if target contains / or \

        public final int rowNum;            // 0-indexed row in Excel

        public final int statusColIndex;    // column index where Status should be written


        public SelectiveEntry(String originalTarget, String destSubfolder, int rowNum, int statusColIndex) {

            this.originalTarget = originalTarget;

            this.destSubfolder = destSubfolder != null ? destSubfolder.trim() : "";

            this.rowNum = rowNum;

            this.statusColIndex = statusColIndex;


            String clean = originalTarget.replace('\\', '/').trim();

            if (clean.contains("/")) {

                this.isPathTarget = true;

                this.normalizedPath = clean.toLowerCase();

                int lastSlash = clean.lastIndexOf('/');

                String fn = clean.substring(lastSlash + 1);

                this.fileName = fn.toLowerCase();

                int dot = this.fileName.lastIndexOf('.');

                this.baseName = dot != -1 ? this.fileName.substring(0, dot) : this.fileName;

            } else {

                this.isPathTarget = false;

                this.normalizedPath = null;

                this.fileName = clean.toLowerCase();

                int dot = this.fileName.lastIndexOf('.');

                this.baseName = dot != -1 ? this.fileName.substring(0, dot) : this.fileName;

            }

        }

    }


    public static List<SelectiveEntry> readSelectiveEntriesFromExcel(String excelFilePath) throws IOException {

        List<SelectiveEntry> entries = new ArrayList<>();

        try (FileInputStream fis = new FileInputStream(new File(excelFilePath));

             Workbook workbook = WorkbookFactory.create(fis)) {


            if (workbook.getNumberOfSheets() == 0) return entries;


            Sheet sheet = workbook.getSheetAt(0);

            DataFormatter formatter = new DataFormatter();


            boolean isFirstRow = true;

            boolean isReportFormat = false; // Col 0 = Directory, Col 1 = File Name

            int defaultStatusCol = 2;


            for (Row row : sheet) {

                Cell cellA = row.getCell(0);

                Cell cellB = row.getCell(1);


                String valA = cellA != null ? formatter.formatCellValue(cellA).trim() : "";

                String valB = cellB != null ? formatter.formatCellValue(cellB).trim() : "";


                if (isFirstRow) {

                    isFirstRow = false;

                    String lowerA = valA.toLowerCase();

                    String lowerB = valB.toLowerCase();

                    if (lowerA.contains("directory") || lowerA.contains("folder path") || lowerA.contains("path")) {

                        if (lowerB.contains("file") || lowerB.contains("name") || lowerB.contains("image")) {

                            isReportFormat = true;

                            defaultStatusCol = 4; // Col E for Module 3 4-column report

                            continue;

                        }

                    }

                    if (lowerA.contains("name") || lowerA.contains("file") || lowerA.contains("image") || lowerA.contains("target")) {

                        continue;

                    }

                }


                if (valA.isEmpty() && valB.isEmpty()) continue;


                if (isReportFormat) {

                    if (!valB.isEmpty()) {

                        String fullPath = valA.isEmpty() ? valB : (valA.endsWith("\\") || valA.endsWith("/") ? valA + valB : valA + File.separator + valB);

                        Cell cellDest = row.getCell(4);

                        String destVal = (cellDest != null) ? formatter.formatCellValue(cellDest).trim() : "";

                        if (destVal.equalsIgnoreCase("Done") || destVal.equalsIgnoreCase("Left") || destVal.equalsIgnoreCase("Status")) {

                            destVal = "";

                        }

                        entries.add(new SelectiveEntry(fullPath, destVal, row.getRowNum(), defaultStatusCol));

                    }

                } else {

                    if (!valA.isEmpty()) {

                        entries.add(new SelectiveEntry(valA, valB, row.getRowNum(), defaultStatusCol));

                    }

                }

            }

        }

        return entries;

    }


    public static void updateSelectiveStatusInExcel(String excelFilePath, Set<String> matchedTargets) {

        File file = new File(excelFilePath);

        if (!file.exists()) return;


        Workbook workbook = null;

        try {

            try (FileInputStream fis = new FileInputStream(file)) {

                workbook = WorkbookFactory.create(fis);

            }


            if (workbook.getNumberOfSheets() == 0) {

                workbook.close();

                return;

            }

            Sheet sheet = workbook.getSheetAt(0);

            DataFormatter formatter = new DataFormatter();


            boolean isFirstRow = true;

            boolean isReportFormat = false;

            int statusCol = 2;


            for (Row row : sheet) {

                Cell cellA = row.getCell(0);

                Cell cellB = row.getCell(1);


                String valA = cellA != null ? formatter.formatCellValue(cellA).trim() : "";

                String valB = cellB != null ? formatter.formatCellValue(cellB).trim() : "";


                if (isFirstRow) {

                    isFirstRow = false;

                    String lowerA = valA.toLowerCase();

                    String lowerB = valB.toLowerCase();

                    if (lowerA.contains("directory") || lowerA.contains("folder path") || lowerA.contains("path")) {

                        if (lowerB.contains("file") || lowerB.contains("name") || lowerB.contains("image")) {

                            isReportFormat = true;

                            statusCol = 4;

                            Cell cellStatus = row.getCell(statusCol);

                            if (cellStatus == null) cellStatus = row.createCell(statusCol);

                            cellStatus.setCellValue("Status");

                            continue;

                        }

                    }

                    if (lowerA.contains("name") || lowerA.contains("file") || lowerA.contains("image") || lowerA.contains("target")) {

                        Cell cellStatus = row.getCell(statusCol);

                        if (cellStatus == null) cellStatus = row.createCell(statusCol);

                        cellStatus.setCellValue("Status");

                        continue;

                    }

                }


                if (valA.isEmpty() && valB.isEmpty()) continue;


                boolean isMoved = false;

                if (isReportFormat) {

                    String fullPath = valA.isEmpty() ? valB : (valA.endsWith("\\") || valA.endsWith("/") ? valA + valB : valA + File.separator + valB);

                    String normFull = fullPath.replace('\\', '/').toLowerCase();

                    String lowerB = valB.toLowerCase();

                    String baseB = lowerB;

                    int dot = baseB.lastIndexOf('.');

                    if (dot != -1) baseB = baseB.substring(0, dot);


                    isMoved = matchedTargets.contains(fullPath) || matchedTargets.contains(normFull) ||

                              matchedTargets.contains(valB) || matchedTargets.contains(lowerB) || matchedTargets.contains(baseB);

                } else {

                    String lowerA = valA.toLowerCase();

                    String normA = valA.replace('\\', '/').toLowerCase();

                    String fn = valA;

                    if (normA.contains("/")) {

                        fn = normA.substring(normA.lastIndexOf('/') + 1);

                    }

                    String baseA = fn;

                    int dot = baseA.lastIndexOf('.');

                    if (dot != -1) baseA = baseA.substring(0, dot);


                    isMoved = matchedTargets.contains(valA) || matchedTargets.contains(lowerA) || matchedTargets.contains(normA) ||

                              matchedTargets.contains(fn) || matchedTargets.contains(baseA);

                }


                Cell cellStatus = row.getCell(statusCol);

                if (cellStatus == null) cellStatus = row.createCell(statusCol);

                cellStatus.setCellValue(isMoved ? "Done" : "Left");

            }


            try (FileOutputStream fos = new FileOutputStream(file)) {

                workbook.write(fos);

            }

            LogService.log("SELECTIVE", "INFO", "Excel status column updated with Done/Left report: " + excelFilePath);

        } catch (Exception e) {

            LogService.log("SELECTIVE", "WARNING", "Could not update Excel Column status: " + e.getMessage());

        } finally {

            if (workbook != null) {

                try { workbook.close(); } catch (Exception ignored) {}

            }

        }

    }

    

    public static Map<String, String> readRenameMappingFromExcel(String excelFilePath) throws IOException {

        Map<String, String> renameMap = new HashMap<>();

        try (FileInputStream fis = new FileInputStream(new File(excelFilePath));

             Workbook workbook = WorkbookFactory.create(fis)) {

            

            if (workbook.getNumberOfSheets() == 0) return renameMap;

            

            Sheet sheet = workbook.getSheetAt(0);

            DataFormatter formatter = new DataFormatter();

            for (Row row : sheet) {

                Cell cellA = row.getCell(0);

                Cell cellB = row.getCell(1);

                

                if (cellA != null && cellB != null) {

                    String original = formatter.formatCellValue(cellA).trim().toLowerCase();

                    String renamed = formatter.formatCellValue(cellB).trim();

                    

                    if (!original.isEmpty() && !renamed.isEmpty()) {

                        int dotA = original.lastIndexOf('.');

                        if (dotA != -1) original = original.substring(0, dotA);

                        

                        int dotB = renamed.lastIndexOf('.');

                        if (dotB != -1) renamed = renamed.substring(0, dotB);

                        

                        renameMap.put(original, renamed);

                    }

                }

            }

        }

        return renameMap;

    }


    public static void generateImageExcelStreamed(

            String sourceFolder,

            String outputFilePath,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag,

            long startingOffset

    ) throws IOException {

        String tempPathStr = outputFilePath + ".tmp";

        try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {

            int sheetCount = 1;

            Sheet sheet = workbook.createSheet("Extracted Images " + sheetCount);

            Row headerRow = sheet.createRow(0);

            headerRow.createCell(0).setCellValue("Directory");

            headerRow.createCell(1).setCellValue("File Name");

            headerRow.createCell(2).setCellValue("Format");

            headerRow.createCell(3).setCellValue("Size (KB)");

            

            sheet.setColumnWidth(0, 15000);

            sheet.setColumnWidth(1, 10000);

            sheet.setColumnWidth(2, 4000);

            sheet.setColumnWidth(3, 4000);

            

            final int[] rowIdx = {1};

            final int[] currentSheetCount = {1};

            final Sheet[] currentSheet = {sheet};

            final long[] localProgress = {0};

            

            ImageExtractionService.processSource(sourceFolder, rootPath -> {

                try {

                    traverseDirectorySorted(

                        rootPath, sourceFolder, workbook, currentSheet,

                        currentSheetCount, rowIdx, localProgress, startingOffset, progress, cancelFlag

                    );

                } catch (Exception e) {

                    LogService.log("EXCEL", "ERROR", "Error during sorted image traversal: " + e.getMessage());

                }

            });

            

            try {

                try (FileOutputStream fos = new FileOutputStream(tempPathStr)) {

                    workbook.write(fos);

                }

            } finally {

                workbook.dispose();

            }

            

            // Safe Atomic Temp-File Replacement with Windows lock bypass

            safeMove(Paths.get(tempPathStr), Paths.get(outputFilePath));

        }

    }


    private static void traverseDirectorySorted(

            Path currentDir,

            String sourceFolder,

            SXSSFWorkbook workbook,

            Sheet[] currentSheet,

            int[] currentSheetCount,

            int[] rowIdx,

            long[] localProgress,

            long startingOffset,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        if (cancelFlag != null && cancelFlag.get()) return;

        TaskManager.checkPause();


        List<Path> subDirs = new ArrayList<>();

        List<Path> imageFiles = new ArrayList<>();


        try (DirectoryStream<Path> stream = Files.newDirectoryStream(currentDir)) {

            for (Path entry : stream) {

                if (cancelFlag != null && cancelFlag.get()) return;

                if (Files.isDirectory(entry)) {

                    subDirs.add(entry);

                } else if (ImageExtractionService.isImageFile(entry)) {

                    imageFiles.add(entry);

                }

            }

        } catch (Exception ignored) {

            return;

        }


        // 1. Sort image files in current folder alphabetically (case-insensitive)

        imageFiles.sort((a, b) -> {

            String nameA = a.getFileName() != null ? a.getFileName().toString() : "";

            String nameB = b.getFileName() != null ? b.getFileName().toString() : "";

            return String.CASE_INSENSITIVE_ORDER.compare(nameA, nameB);

        });


        // 2. Write all sorted images in current folder first

        for (Path file : imageFiles) {

            if (cancelFlag != null && cancelFlag.get()) return;

            TaskManager.checkPause();

            TaskManager.setCurrentFileName(file.getFileName() != null ? file.getFileName().toString() : "");


            if (rowIdx[0] >= 100000) { // 100k sheet split limit

                currentSheetCount[0]++;

                currentSheet[0] = workbook.createSheet("Extracted Images " + currentSheetCount[0]);

                Row hRow = currentSheet[0].createRow(0);

                hRow.createCell(0).setCellValue("Directory");

                hRow.createCell(1).setCellValue("File Name");

                hRow.createCell(2).setCellValue("Format");

                hRow.createCell(3).setCellValue("Size (KB)");

                currentSheet[0].setColumnWidth(0, 15000);

                currentSheet[0].setColumnWidth(1, 10000);

                currentSheet[0].setColumnWidth(2, 4000);

                currentSheet[0].setColumnWidth(3, 4000);

                rowIdx[0] = 1;

            }


            String dirPath;

            if (!file.getFileSystem().equals(java.nio.file.FileSystems.getDefault())) {

                // Inside a virtual ZIP file system

                String innerParent = file.getParent() != null ? file.getParent().toString() : "";

                if (innerParent.startsWith("/") || innerParent.startsWith("\\")) {

                    innerParent = innerParent.substring(1);

                }

                dirPath = sourceFolder + (innerParent.isEmpty() ? "" : File.separator + innerParent);

            } else {

                dirPath = file.getParent() != null ? file.getParent().toString() : sourceFolder;

            }


            String fileName = file.getFileName() != null ? file.getFileName().toString() : "";


            String format = "";

            int dot = fileName.lastIndexOf('.');

            if (dot != -1 && dot < fileName.length() - 1) {

                format = fileName.substring(dot + 1).toUpperCase();

            }


            long sizeKB = 0;

            try {

                sizeKB = Files.size(file) / 1024;

            } catch (Exception ignored) {}


            Row row = currentSheet[0].createRow(rowIdx[0]++);

            row.createCell(0).setCellValue(dirPath);

            row.createCell(1).setCellValue(fileName);

            row.createCell(2).setCellValue(format);

            row.createCell(3).setCellValue(sizeKB);


            if (rowIdx[0] % 100 == 0) {

                ((SXSSFSheet) currentSheet[0]).flushRows(100);

            }


            localProgress[0]++;

            if (localProgress[0] % 50 == 0 && progress != null) {

                progress.accept(startingOffset + localProgress[0]);

            }

        }


        // 3. Sort subdirectories alphabetically (case-insensitive)

        subDirs.sort((a, b) -> {

            String nameA = a.getFileName() != null ? a.getFileName().toString() : "";

            String nameB = b.getFileName() != null ? b.getFileName().toString() : "";

            return String.CASE_INSENSITIVE_ORDER.compare(nameA, nameB);

        });


        // 4. Complete 1st subfolder completely, then move to the next subfolder

        for (Path subDir : subDirs) {

            if (cancelFlag != null && cancelFlag.get()) return;

            traverseDirectorySorted(

                subDir, sourceFolder, workbook, currentSheet,

                currentSheetCount, rowIdx, localProgress, startingOffset, progress, cancelFlag

            );

        }

    }


    public static class ExcelReportWriter implements AutoCloseable {

        private final SXSSFWorkbook workbook;

        private Sheet sheet;

        private int rowIdx = 1;

        private int sheetCount = 1;

        public final String outputFilePath;

        private final String tempFilePath;

        private final boolean checkDuplicates;

        

        // Bounded Blocking Queue & Background Asynchronous Writer Thread

        private final BlockingQueue<ImageValidationService.ValidationResult> queue;

        private final Thread writerThread;

        private final AtomicBoolean running = new AtomicBoolean(true);

        private static final ImageValidationService.ValidationResult POISON_PILL = 

            new ImageValidationService.ValidationResult(null, null, null, 0, null, null, null, null);


        public ExcelReportWriter(String outputFilePath, boolean checkDuplicates) {

            this.outputFilePath = outputFilePath;

            this.checkDuplicates = checkDuplicates;

            this.tempFilePath = outputFilePath + ".tmp";

            this.workbook = new SXSSFWorkbook(100);

            this.sheet = createNewSheet();

            

            this.queue = new ArrayBlockingQueue<>(2000); // 2k capacity backpressure for memory footprint optimization

            

            this.writerThread = new Thread(() -> {

                while (running.get() || !queue.isEmpty()) {

                    try {

                        ImageValidationService.ValidationResult r = queue.poll(100, TimeUnit.MILLISECONDS);

                        if (r == null) continue;

                        if (r == POISON_PILL) break;

                        

                        writeResultToWorkbook(r);

                    } catch (InterruptedException e) {

                        Thread.currentThread().interrupt();

                        break;

                    } catch (Exception e) {

                        LogService.log("EXCEL", "ERROR", "Writer thread error: " + e.getMessage());

                    }

                }

            });

            this.writerThread.setName("ExcelBackgroundWriter");

            this.writerThread.setDaemon(true);

            this.writerThread.start();

        }


        private Sheet createNewSheet() {

            Sheet s = workbook.createSheet("Health Report " + sheetCount);

            Row headerRow = s.createRow(0);

            headerRow.createCell(0).setCellValue("Directory");

            headerRow.createCell(1).setCellValue("File Name");

            headerRow.createCell(2).setCellValue("Format");

            headerRow.createCell(3).setCellValue("Size (KB)");

            headerRow.createCell(4).setCellValue("Dimensions");

            headerRow.createCell(5).setCellValue("Status");

            if (checkDuplicates) {

                headerRow.createCell(6).setCellValue("Duplicate Hash");

                headerRow.createCell(7).setCellValue("Reason");

            } else {

                headerRow.createCell(6).setCellValue("Reason");

            }


            s.setDefaultColumnWidth(20);

            s.setColumnWidth(1, 10000);

            if (checkDuplicates) {

                s.setColumnWidth(7, 12000);

            } else {

                s.setColumnWidth(6, 12000);

            }

            return s;

        }


        public void writeResult(ImageValidationService.ValidationResult result) {

            try {

                // Blocking put applies natural backpressure to worker threads and guarantees zero data loss in audit reports

                queue.put(result);

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            }

        }


        private void writeResultToWorkbook(ImageValidationService.ValidationResult result) {

            if (rowIdx >= 100000) { // split sheets at 100k rows

                sheetCount++;

                sheet = createNewSheet();

                rowIdx = 1;

            }

            Row row = sheet.createRow(rowIdx++);

            row.createCell(0).setCellValue(result.directory);

            row.createCell(1).setCellValue(result.fileName);

            row.createCell(2).setCellValue(result.format);

            row.createCell(3).setCellValue(result.sizeKB);

            row.createCell(4).setCellValue(result.dimensions);

            row.createCell(5).setCellValue(result.status);

            if (checkDuplicates) {

                row.createCell(6).setCellValue(result.hash);

                row.createCell(7).setCellValue(result.reason);

            } else {

                row.createCell(6).setCellValue(result.reason);

            }

            

            if (rowIdx % 100 == 0) {

                try {

                    ((SXSSFSheet) sheet).flushRows(100);

                } catch (IOException ignored) {}

            }

        }


        @Override

        public void close() throws IOException {

            running.set(false);

            try {

                // Offer poison pill with a 5-second timeout to avoid locking the calling thread if queue is saturated

                if (!queue.offer(POISON_PILL, 5, TimeUnit.SECONDS)) {

                    writerThread.interrupt();

                }

                writerThread.join(10000); // Wait up to 10 seconds to drain remaining queue items

                if (writerThread.isAlive()) {

                    writerThread.interrupt();

                    writerThread.join(2000);

                }

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            }

            

            // Write final flush of remaining queue items if writer thread is terminated

            if (!writerThread.isAlive()) {

                while (!queue.isEmpty()) {

                    ImageValidationService.ValidationResult r = queue.poll();

                    if (r != null && r != POISON_PILL) {

                        writeResultToWorkbook(r);

                    }

                }

            }


            try (FileOutputStream fos = new FileOutputStream(tempFilePath)) {

                workbook.write(fos);

            } finally {

                try {

                    workbook.close();

                } catch (Exception ignored) {}

                workbook.dispose();

            }

            

            // Safe Atomic Temp-File Replacement with Windows lock bypass

            safeMove(Paths.get(tempFilePath), Paths.get(outputFilePath));

        }

    }


    public static void generateConversionReport(

            List<ImageConversionService.ConversionResult> results,

            String outputFilePath,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        String tempPathStr = outputFilePath + ".tmp";

        try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {

            int sheetIndex = 1;

            Sheet sheet = workbook.createSheet("Conversion_Part_" + sheetIndex);

            Row headerRow = sheet.createRow(0);

            headerRow.createCell(0).setCellValue("Original Image Name");

            headerRow.createCell(1).setCellValue("Converted Image Name");

            headerRow.createCell(2).setCellValue("Action / Status");

            sheet.setDefaultColumnWidth(30);

            

            int rowIdx = 1;

            long localProgress = 0;

            for (ImageConversionService.ConversionResult r : results) {

                if (cancelFlag != null && cancelFlag.get()) break;

                TaskManager.checkPause();

                

                if (rowIdx >= 100000) {

                    ((SXSSFSheet) sheet).flushRows(100);

                    sheetIndex++;

                    sheet = workbook.createSheet("Conversion_Part_" + sheetIndex);

                    headerRow = sheet.createRow(0);

                    headerRow.createCell(0).setCellValue("Original Image Name");

                    headerRow.createCell(1).setCellValue("Converted Image Name");

                    headerRow.createCell(2).setCellValue("Action / Status");

                    sheet.setDefaultColumnWidth(30);

                    rowIdx = 1;

                }

                

                Row row = sheet.createRow(rowIdx++);

                row.createCell(0).setCellValue(r.originalName);

                row.createCell(1).setCellValue(r.convertedName);

                row.createCell(2).setCellValue(r.actionTaken);

                

                if (rowIdx % 100 == 0) {

                    ((SXSSFSheet) sheet).flushRows(100);

                }

                

                localProgress++;

                if (localProgress % 50 == 0 && progress != null) {

                    progress.accept(localProgress);

                }

            }

            

            try (FileOutputStream fos = new FileOutputStream(tempPathStr)) {

                workbook.write(fos);

            } finally {

                workbook.dispose();

            }

            

            // Safe Atomic Temp-File Replacement with Windows lock bypass

            safeMove(Paths.get(tempPathStr), Paths.get(outputFilePath));

        }

    }


    /**

     * Bypasses Windows file locking issues (such as indexing or antivirus locks) by 

     * retrying the atomic replacement 3 times with a 1-second delay.

     */

    public static void safeMove(Path source, Path target) throws IOException {

        int maxRetries = 3;

        for (int i = 0; i < maxRetries; i++) {

            try {

                Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

                return;

            } catch (IOException e) {

                if (i == maxRetries - 1) {

                    throw new IOException("Failed to replace file at " + target + " after " + maxRetries + " retries due to Windows file lock: " + e.getMessage(), e);

                }

                try {

                    Thread.sleep(1000); // Sleep 1 second before retrying

                } catch (InterruptedException ie) {

                    Thread.currentThread().interrupt();

                    throw new IOException("File replacement interrupted during lock release backoff retry: " + ie.getMessage(), ie);

                }

            }

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\HashService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.nio.file.Path;

import java.util.Map;


public class HashService {


    /**

     * Orchestrates the high-performance staged duplicate check pipeline.

     * Delegates to DuplicateManager for staged matching (Size -> Partial -> Full).

     */

    public static DuplicateManager.StagedDuplicateResult checkDuplicateStaged(

            Path path,

            Map<String, String> processedHashes,

            Map<String, Path> processedCandidates

    ) throws java.io.IOException {

        return DuplicateManager.checkDuplicateStaged(path, path, processedHashes, processedCandidates);

    }


    public static DuplicateManager.StagedDuplicateResult checkDuplicateStaged(

            Path sourcePath,

            Path persistentPath,

            Map<String, String> processedHashes,

            Map<String, Path> processedCandidates

    ) throws java.io.IOException {

        return DuplicateManager.checkDuplicateStaged(sourcePath, persistentPath, processedHashes, processedCandidates);

    }


    /**

     * Generates a 64-bit dHash (difference hash) for perceptual image duplicate matching.

     */

    public static long computePerceptualHash(Path path) throws java.io.IOException {

        return DuplicateManager.computeDHash(path);

    }


    /**

     * Calculates Hamming similarity percentage between two perceptual hashes.

     */

    public static double calculatePerceptualSimilarity(long hash1, long hash2) {

        return DuplicateManager.calculatePerceptualSimilarity(hash1, hash2);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ImageConversionService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import javax.imageio.IIOImage;

import javax.imageio.ImageIO;

import javax.imageio.ImageWriteParam;

import javax.imageio.ImageWriter;

import javax.imageio.stream.ImageOutputStream;

import java.awt.*;

import java.awt.image.BufferedImage;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.StandardCopyOption;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.concurrent.atomic.AtomicLong;

import java.util.function.Consumer;


public class ImageConversionService {


    public static class ConversionResult {

        public String originalName;

        public String convertedName;

        public String actionTaken;


        public ConversionResult(String originalName, String convertedName, String actionTaken) {

            this.originalName = originalName;

            this.convertedName = convertedName;

            this.actionTaken = actionTaken;

        }

    }


    private static Path resolveTargetCollision(Path destPath, String baseName, String extension, java.util.Set<String> claimedNames) {

        String fileName = baseName + extension;

        while (true) {

            Path target = destPath.resolve(fileName);

            String claimKey = fileName.toLowerCase();

            if (claimedNames == null || claimedNames.add(claimKey)) {

                if (!Files.exists(target)) {

                    return target;

                }

                if (claimedNames != null) {

                    claimedNames.remove(claimKey);

                }

            }

            fileName = baseName + "_" + java.util.UUID.randomUUID().toString() + extension;

        }

    }


    private static String processSingleImage(Path source, Path target, String format, long targetSizeBytes) throws Exception {

        long originalSize = Files.size(source);

        String origExt = "";

        String origName = source.getFileName().toString();

        int dot = origName.lastIndexOf('.');

        if (dot != -1) origExt = origName.substring(dot + 1).toLowerCase();


        // If it's already the right format and under the size limit, just copy it directly.

        if (origExt.equals(format) && originalSize <= targetSizeBytes) {

            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

            return "Format Kept (Under Size Limit)";

        }


        // Dimension check before full decode to prevent huge image memory crashes

        try (InputStream is = Files.newInputStream(source);

             javax.imageio.stream.ImageInputStream iis = ImageIO.createImageInputStream(is)) {

            if (iis != null) {

                java.util.Iterator<javax.imageio.ImageReader> readers = ImageIO.getImageReaders(iis);

                if (readers.hasNext()) {

                    javax.imageio.ImageReader reader = readers.next();

                    try {

                        reader.setInput(iis);

                        int w = reader.getWidth(0);

                        int h = reader.getHeight(0);

                        if (w > 32768 || h > 32768) {

                            throw new IOException("Decompression bomb protection triggered in conversion! Malicious image size: " + w + "x" + h);

                        }

                    } finally {

                        reader.dispose();

                    }

                }

            }

        }


        BufferedImage image;

        try (InputStream is = Files.newInputStream(source)) {

            image = ImageIO.read(is);

        }

        

        if (image == null) {

            throw new IOException("No ImageIO/TwelveMonkeys reader found for this format (" + (origExt.isEmpty() ? "unknown" : origExt.toUpperCase()) + ")");

        }


        try {

            // Handle transparency removal if converting to JPG or BMP

            if (format.equals("jpg") || format.equals("jpeg") || format.equals("bmp")) {

                if (image.getColorModel().hasAlpha() || image.getType() != BufferedImage.TYPE_INT_RGB) {

                    BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);

                    Graphics2D g2d = newImage.createGraphics();

                    g2d.setColor(Color.WHITE);

                    g2d.fillRect(0, 0, image.getWidth(), image.getHeight());

                    g2d.drawImage(image, 0, 0, null);

                    g2d.dispose();

                    image.flush();

                    image = newImage;

                }

            }


            boolean isJpg = format.equals("jpg") || format.equals("jpeg");

            

            // Attempt compression first if JPG

            if (isJpg) {

                byte[] compressedData = attemptJpgCompression(image, format, targetSizeBytes);

                if (compressedData != null) {

                    Files.write(target, compressedData);

                    return "Compressed";

                }

            }


            // If we reach here, we must resize dimensions

            byte[] currentBytes = getStandardBytes(image, format);

            if (currentBytes.length <= targetSizeBytes) {

                Files.write(target, currentBytes);

                return "Format Changed";

            }


            double ratio = (double) targetSizeBytes / currentBytes.length;

            double scale = Math.sqrt(ratio) * 0.95; // 0.95 safety margin

            

            BufferedImage scaledImage = image;

            int iteration = 0;

            

            while (currentBytes.length > targetSizeBytes && iteration < 5) {

                int newW = Math.max(1, (int) (scaledImage.getWidth() * scale));

                int newH = Math.max(1, (int) (scaledImage.getHeight() * scale));

                

                BufferedImage resized = new BufferedImage(newW, newH, scaledImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : scaledImage.getType());

                Graphics2D g2d = resized.createGraphics();

                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

                g2d.drawImage(scaledImage, 0, 0, newW, newH, null);

                g2d.dispose();

                

                if (scaledImage != image) {

                    scaledImage.flush();

                }

                scaledImage = resized;

                

                if (isJpg) {

                    currentBytes = compressJpgToBytes(scaledImage, format, 0.0f);

                } else {

                    currentBytes = getStandardBytes(scaledImage, format);

                }

                

                scale = 0.8;

                iteration++;

            }


            Files.write(target, currentBytes);

            if (scaledImage != image) {

                scaledImage.flush();

            }

            return isJpg ? "Compressed & Dimension Adjusted" : "Dimension Adjusted";

        } finally {

            image.flush(); // Strict cleanup of buffered images to prevent RAM leaks

        }

    }


    private static byte[] attemptJpgCompression(BufferedImage image, String format, long targetSizeBytes) throws Exception {

        float minQuality = 0.0f;

        float maxQuality = 1.0f;

        byte[] bestData = null;


        for (int i = 0; i < 6; i++) {

            float mid = (minQuality + maxQuality) / 2.0f;

            byte[] data = compressJpgToBytes(image, format, mid);

            if (data.length <= targetSizeBytes) {

                bestData = data;

                minQuality = mid;

            } else {

                maxQuality = mid;

            }

        }

        

        if (bestData == null) {

            byte[] data = compressJpgToBytes(image, format, 0.0f);

            if (data.length <= targetSizeBytes) {

                return data;

            }

            return null;

        }

        return bestData;

    }


    private static byte[] compressJpgToBytes(BufferedImage image, String format, float quality) throws Exception {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        java.util.Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);

        if (!writers.hasNext()) throw new IllegalStateException("No writers found");

        ImageWriter writer = writers.next();

        

        try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {

            writer.setOutput(ios);

            ImageWriteParam param = writer.getDefaultWriteParam();

            if (param.canWriteCompressed()) {

                param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

                param.setCompressionQuality(quality);

            }

            writer.write(null, new IIOImage(image, null, null), param);

        } finally {

            writer.dispose();

        }

        return baos.toByteArray();

    }


    private static byte[] getStandardBytes(BufferedImage image, String format) throws Exception {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        boolean success = ImageIO.write(image, format, baos);

        if (!success) {

            throw new IOException("No appropriate image writer found for format: " + format);

        }

        return baos.toByteArray();

    }


    public static List<ConversionResult> convertImagesStreamed(

            List<String> sourceFolders,

            String destinationFolder,

            String targetFormat,

            long targetSizeBytes,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) {

        List<ConversionResult> results = java.util.Collections.synchronizedList(new ArrayList<>());

        Path destPath = com.imageextractor.utils.PathUtils.normalizeLongPath(destinationFolder);

        if (!Files.exists(destPath)) {

            try {

                Files.createDirectories(destPath);

            } catch (IOException e) {

                LogService.log("CONVERT", "ERROR", "Failed to create destination directory: " + e.getMessage());

            }

        }


        final AtomicLong currentProgress = new AtomicLong(0);

        final java.util.Set<String> claimedNames = java.util.concurrent.ConcurrentHashMap.newKeySet();


        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        

        try {

            for (String sf : sourceFolders) {

                if (cancelFlag != null && cancelFlag.get()) break;

                

                ImageExtractionService.processSource(sf, rootPath -> {

                    final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 for main thread

                    try {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag != null && cancelFlag.get()) return FileVisitResult.TERMINATE;

                                TaskManager.checkPause();

                                

                                if (ImageExtractionService.isImageFile(file)) {

                                    phaser.register();

                                    try {

                                        executor.submit(() -> {

                                            try {

                                                processSingleStreamedConversion(

                                                        file, destPath, targetFormat, targetSizeBytes, claimedNames,

                                                        results, currentProgress, progress, cancelFlag

                                                );

                                            } finally {

                                                phaser.arriveAndDeregister();

                                            }

                                        });

                                    } catch (Exception e) {

                                        phaser.arriveAndDeregister();

                                        LogService.log("CONVERT", "ERROR", "Failed to submit conversion task for " + file.getFileName().toString() + ": " + e.getMessage());

                                    }

                                }

                                return FileVisitResult.CONTINUE;

                            }

                            

                            @Override

                            public FileVisitResult visitFileFailed(Path file, IOException exc) {

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    } finally {

                        // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                        phaser.arriveAndAwaitAdvance();

                    }

                });

            }

        } catch (Exception e) {

            LogService.log("CONVERT", "ERROR", "Conversion walk interrupted: " + e.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }


        return results;

    }


    private static void processSingleStreamedConversion(

            Path p,

            Path destPath,

            String targetFormat,

            long targetSizeBytes,

            java.util.Set<String> claimedNames,

            List<ConversionResult> results,

            AtomicLong currentProgress,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) {

        TaskManager.checkPause();

        if (cancelFlag != null && cancelFlag.get()) return;

        TaskManager.updateHeartbeat();

        TaskManager.setCurrentFileName(p.getFileName().toString());


        String origName = p.getFileName().toString();

        String baseName = origName;

        String origExtStr = "";

        int dot = origName.lastIndexOf('.');

        if (dot != -1) {

            baseName = origName.substring(0, dot);

            origExtStr = origName.substring(dot + 1).toLowerCase();

        }

        

        String actualTargetFormat = (targetFormat == null || targetFormat.isEmpty()) ? origExtStr : targetFormat.toLowerCase();

        String formatExt = "." + actualTargetFormat;


        Path target = resolveTargetCollision(destPath, baseName, formatExt, claimedNames);

        String claimKey = target.getFileName().toString().toLowerCase();


        try {

            String action = processSingleImage(p, target, actualTargetFormat, targetSizeBytes <= 0 ? Long.MAX_VALUE : targetSizeBytes);

            if (results.size() < 250000) {

                results.add(new ConversionResult(origName, target.getFileName().toString(), action));

            }

            TaskManager.incrementProcessed();

        } catch (Exception e) {

            LogService.log("CONVERT", "ERROR", "Failed to convert " + p + ": " + e.getMessage());

            if (results.size() < 250000) {

                results.add(new ConversionResult(origName, "FAILED", "Error: " + e.getMessage()));

            }

            TaskManager.incrementFailedCount();

            TaskManager.incrementProcessed();

        } finally {

            if (claimedNames != null) {

                claimedNames.remove(claimKey);

            }

        }


        long cp = currentProgress.incrementAndGet();

        if (cp % 10 == 0 && progress != null) progress.accept(cp);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ImageExtractionService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import com.imageextractor.utils.PathUtils;


import java.io.IOException;

import java.net.URI;

import java.nio.file.*;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.function.Consumer;


public class ImageExtractionService {

    

    private static final String[] VALID_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif", ".pdf"};


    public static boolean isImageFile(Path path) {

        if (Files.isDirectory(path)) return false;

        String name = path.getFileName().toString().toLowerCase();

        for (String ext : VALID_EXTENSIONS) {

            if (name.endsWith(ext)) return true;

        }

        return false;

    }


    public static void safeCopy(Path source, Path target) throws IOException {

        if (source.getFileSystem().equals(target.getFileSystem())) {

            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);

        } else {

            // Drop COPY_ATTRIBUTES if crossing filesystems (e.g., from virtual ZIP to Local Disk)

            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

        }

    }


    private static Path resolveTargetCollision(Path destPath, Path file, java.util.Set<String> claimedNames) {

        String fileName = file.getFileName().toString();

        while (true) {

            Path target = destPath.resolve(fileName);

            String claimKey = target.toAbsolutePath().toString().toLowerCase();

            if (claimedNames == null || claimedNames.add(claimKey)) {

                if (!Files.exists(target)) {

                    return target;

                }

                // Pre-existing file on disk: remove the claimed name so it doesn't leak

                if (claimedNames != null) {

                    claimedNames.remove(claimKey);

                }

            }

            String baseName = fileName;

            String extension = "";

            int dot = fileName.lastIndexOf('.');

            if (dot != -1) {

                baseName = fileName.substring(0, dot);

                extension = fileName.substring(dot);

            }

            fileName = baseName + "_" + java.util.UUID.randomUUID().toString() + extension;

        }

    }


    public static void processSource(String sourcePath, SourceProcessor processor) throws IOException {

        // Resolve long path safely

        Path srcPath = PathUtils.normalizeLongPath(sourcePath).toAbsolutePath();

        if (!Files.exists(srcPath)) return;


        if (sourcePath.toLowerCase().endsWith(".zip") && !Files.isDirectory(srcPath)) {

            URI uri = URI.create("jar:" + srcPath.toUri());

            try (FileSystem fs = FileSystems.newFileSystem(uri, java.util.Collections.emptyMap())) {

                for (Path root : fs.getRootDirectories()) {

                    processor.process(root);

                }

            }

        } else {

            processor.process(srcPath);

        }

    }


    @FunctionalInterface

    public interface SourceProcessor {

        void process(Path rootPath) throws IOException;

    }


    private static void processSingleExtraction(

            Path p, 

            Path destPath, 

            Set<String> claimedNames, 

            java.util.Map<String, String> processedHashes, 

            java.util.Map<String, Path> processedCandidates,

            Set<String> completedFiles, 

            Set<String> failedFiles, 

            String destinationFolder, 

            java.util.concurrent.atomic.AtomicInteger count, 

            java.util.concurrent.atomic.AtomicLong currentProgress, 

            Consumer<Long> progress, 

            AtomicBoolean cancelFlag

    ) {

        TaskManager.checkPause();

        if (cancelFlag.get()) return;

        TaskManager.updateHeartbeat();

        TaskManager.setCurrentFileName(p.getFileName().toString());


        String fileKey = p.toUri().toString();


        // Checkpoint skip logic

        if (completedFiles.contains(fileKey) || failedFiles.contains(fileKey)) {

            long cp = currentProgress.incrementAndGet();

            if (cp % 50 == 0 && progress != null) progress.accept(cp);

            return;

        }


        Path target = null;

        String claimKey = null;

        try {

            // Zip Slip Protection: Path traversal guard

            target = resolveTargetCollision(destPath, p, claimedNames);

            claimKey = target.toAbsolutePath().toString().toLowerCase();

            if (!TempManager.isPathSafe(target, destPath)) {

                throw new SecurityException("Zip Slip directory traversal vulnerability detected in: " + p.toString());

            }


            // ZIP Bomb Protection: Check compression ratio

            long compressedSize = 1;

            try {

                compressedSize = Files.size(p);

            } catch (Exception ignored) {}

            

            // Generate duplicate status using high-performance staged hashing pipeline

            DuplicateManager.StagedDuplicateResult duplicateResult = HashService.checkDuplicateStaged(p, target, processedHashes, processedCandidates);

            if (duplicateResult.isDuplicate) {

                TaskManager.incrementDuplicateCount();

            }

            

            // Always copy the file to ensure every image is treated as unique and extracted

            safeCopy(p, target);

            count.incrementAndGet();

            TaskManager.incrementProcessed();

            TaskManager.addBytesProcessed(compressedSize);

            

            if (completedFiles.size() > 500000) {

                int removeCount = completedFiles.size() / 5;

                java.util.Iterator<String> it = completedFiles.iterator();

                while (it.hasNext() && removeCount-- > 0) {

                    it.next();

                    it.remove();

                }

            }

            completedFiles.add(fileKey);

        } catch (Exception e) {

            LogService.log("EXTRACT", "ERROR", "Failed to extract: " + p.getFileName().toString() + " - " + e.getMessage());

            if (failedFiles.size() > 500000) {

                int removeCount = failedFiles.size() / 5;

                java.util.Iterator<String> it = failedFiles.iterator();

                while (it.hasNext() && removeCount-- > 0) {

                    it.next();

                    it.remove();

                }

            }

            failedFiles.add(fileKey);

            TaskManager.incrementFailedCount();

            TaskManager.incrementProcessed();

        } finally {

            if (claimedNames != null && claimKey != null) {

                claimedNames.remove(claimKey);

            }

        }


        long cp = currentProgress.incrementAndGet();

        

        // Save progress checkpoint recovery state adaptively

        ProcessingState.saveStateAdaptive(destinationFolder, "extract-task", completedFiles, failedFiles, cp);


        if (cp % 50 == 0 && progress != null) progress.accept(cp);

    }


    public static int extractAllImagesStreamed(

            List<String> sourceFolders,

            String destinationFolder,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        Path destPath = PathUtils.normalizeLongPath(destinationFolder);

        if (!Files.exists(destPath)) Files.createDirectories(destPath);


        final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

        final java.util.concurrent.atomic.AtomicLong currentProgress = new java.util.concurrent.atomic.AtomicLong(0);

        final java.util.Set<String> claimedNames = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final java.util.Map<String, String> processedHashes = new java.util.concurrent.ConcurrentHashMap<>();

        final java.util.Map<String, Path> processedCandidates = new java.util.concurrent.ConcurrentHashMap<>();


        // Checkpoint Recovery load

        final ProcessingState checkpoint = ProcessingState.loadState(destinationFolder);

        final Set<String> completedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final Set<String> failedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();


        if (checkpoint != null) {

            completedFiles.addAll(checkpoint.completedFiles);

            failedFiles.addAll(checkpoint.failedFiles);

            count.set((int) Math.min(Integer.MAX_VALUE, checkpoint.progressCount));

            

            // Sync task manager telemetry

            TaskManager.resetTelemetry();

            TaskManager.setProcessedCount(checkpoint.progressCount);

            TaskManager.setFailedCount(checkpoint.failedFiles.size());

        }

        

        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        

        try {

            for (String sf : sourceFolders) {

                if (cancelFlag.get()) break;

                

                processSource(sf, rootPath -> {

                    final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 for main thread

                    Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                        @Override

                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                            if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                            TaskManager.checkPause();

                            

                            if (isImageFile(file)) {

                                phaser.register();

                                try {

                                    executor.submit(() -> {

                                        try {

                                            processSingleExtraction(

                                                    file, destPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                                    count, currentProgress, progress, cancelFlag

                                            );

                                        } finally {

                                            phaser.arriveAndDeregister();

                                        }

                                    });

                                } catch (Exception e) {

                                    phaser.arriveAndDeregister();

                                    LogService.log("EXTRACT", "ERROR", "Failed to submit extraction task for " + file.getFileName().toString() + ": " + e.getMessage());

                                }

                            }

                            return FileVisitResult.CONTINUE;

                        }

                        

                        @Override

                        public FileVisitResult visitFileFailed(Path file, IOException exc) {

                            return FileVisitResult.CONTINUE;

                        }

                    });


                    // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                    phaser.arriveAndAwaitAdvance();

                });

            }


            if (!cancelFlag.get()) {

                ProcessingState.deleteState(destinationFolder);

            }

        } catch (Exception e) {

            LogService.log("EXTRACT", "ERROR", "Streamed extraction walk interrupted: " + e.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }

        

        return count.get();

    }


    public static Path resolveSubfolderPath(Path baseDestPath, String folderValue, boolean partitionByUnderscore) {

        if (folderValue == null || folderValue.trim().isEmpty()) {

            return baseDestPath;

        }

        String cleanVal = folderValue.trim();

        if (!partitionByUnderscore) {

            String sanitizedFolder = sanitizeFolderName(cleanVal);

            return sanitizedFolder.isEmpty() ? baseDestPath : baseDestPath.resolve(sanitizedFolder);

        } else {

            String[] parts = cleanVal.split("_");

            Path current = baseDestPath;

            for (String part : parts) {

                String sanitizedPart = sanitizeFolderName(part.trim());

                if (!sanitizedPart.isEmpty()) {

                    current = current.resolve(sanitizedPart);

                }

            }

            return current;

        }

    }


    private static String sanitizeFolderName(String name) {

        if (name == null) return "";

        return name.replaceAll("[\\\\/:*?\"<>|]", "_").trim();

    }


    public static int extractSelectiveImagesStreamed(

            List<String> sourceFolders,

            String excelFilePath,

            String destinationFolder,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        return extractSelectiveImagesStreamed(sourceFolders, excelFilePath, destinationFolder, false, progress, cancelFlag);

    }


    public static int extractSelectiveImagesStreamed(

            List<String> sourceFolders,

            String excelFilePath,

            String destinationFolder,

            boolean partitionByUnderscore,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        List<ExcelService.SelectiveEntry> entries = ExcelService.readSelectiveEntriesFromExcel(excelFilePath);

        Path destPath = PathUtils.normalizeLongPath(destinationFolder);

        if (!Files.exists(destPath)) Files.createDirectories(destPath);


        final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

        final java.util.concurrent.atomic.AtomicLong currentProgress = new java.util.concurrent.atomic.AtomicLong(0);

        final java.util.Set<String> claimedNames = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final java.util.Map<String, String> processedHashes = new java.util.concurrent.ConcurrentHashMap<>();

        final java.util.Map<String, Path> processedCandidates = new java.util.concurrent.ConcurrentHashMap<>();


        // Checkpoint Recovery load

        final ProcessingState checkpoint = ProcessingState.loadState(destinationFolder);

        final Set<String> completedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final Set<String> failedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();


        final java.util.Set<String> matchedTargets = java.util.concurrent.ConcurrentHashMap.newKeySet();


        if (checkpoint != null) {

            completedFiles.addAll(checkpoint.completedFiles);

            failedFiles.addAll(checkpoint.failedFiles);

            count.set((int) Math.min(Integer.MAX_VALUE, checkpoint.progressCount));

            

            // Sync task manager telemetry

            TaskManager.resetTelemetry();

            TaskManager.setProcessedCount(checkpoint.progressCount);

            TaskManager.setFailedCount(checkpoint.failedFiles.size());

        }

        

        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        

        // Build fast lookup indices for path and filename matching

        Map<String, ExcelService.SelectiveEntry> exactPathMap = new HashMap<>();

        Map<String, List<ExcelService.SelectiveEntry>> subpathCandidatesByFileName = new HashMap<>();

        Map<String, List<ExcelService.SelectiveEntry>> nameMap = new HashMap<>();


        for (ExcelService.SelectiveEntry e : entries) {

            if (e.isPathTarget) {

                if (e.normalizedPath != null) {

                    exactPathMap.put(e.normalizedPath, e);

                    subpathCandidatesByFileName.computeIfAbsent(e.fileName, k -> new ArrayList<>()).add(e);

                }

            } else {

                nameMap.computeIfAbsent(e.fileName, k -> new ArrayList<>()).add(e);

                if (!e.baseName.equals(e.fileName)) {

                    nameMap.computeIfAbsent(e.baseName, k -> new ArrayList<>()).add(e);

                }

            }

        }


        // Direct Extraction for existing absolute / direct file paths on disk

        for (ExcelService.SelectiveEntry e : entries) {

            if (cancelFlag != null && cancelFlag.get()) break;

            if (e.isPathTarget) {

                try {

                    Path directFile = Paths.get(e.originalTarget);

                    if (Files.exists(directFile) && !Files.isDirectory(directFile) && isImageFile(directFile)) {

                        Path itemDestPath = resolveSubfolderPath(destPath, e.destSubfolder, partitionByUnderscore);

                        if (!Files.exists(itemDestPath)) Files.createDirectories(itemDestPath);


                        processSingleExtraction(

                                directFile, itemDestPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                count, currentProgress, progress, cancelFlag

                        );

                        matchedTargets.add(e.originalTarget);

                        if (e.normalizedPath != null) matchedTargets.add(e.normalizedPath);

                        matchedTargets.add(e.fileName);

                        matchedTargets.add(e.baseName);

                    }

                } catch (Exception ignored) {}

            }

        }

        

        try {

            if (sourceFolders != null) {

                for (String sf : sourceFolders) {

                    if (cancelFlag != null && cancelFlag.get()) break;

                    

                    processSource(sf, rootPath -> {

                        final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 for main thread

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag != null && cancelFlag.get()) return FileVisitResult.TERMINATE;

                                TaskManager.checkPause();

                                

                                if (isImageFile(file)) {

                                    String fileName = file.getFileName().toString().toLowerCase();

                                    String baseName = fileName;

                                    int dot = fileName.lastIndexOf('.');

                                    if (dot != -1) baseName = fileName.substring(0, dot);


                                    String normAbs = file.toAbsolutePath().toString().replace('\\', '/').toLowerCase();


                                    List<ExcelService.SelectiveEntry> matchedEntries = new ArrayList<>();


                                    // 1. Exact path match

                                    if (exactPathMap.containsKey(normAbs)) {

                                        matchedEntries.add(exactPathMap.get(normAbs));

                                    } else {

                                        // 2. High-performance relative subpath match - only inspect candidates with matching filename!

                                        List<ExcelService.SelectiveEntry> subpathCandidates = subpathCandidatesByFileName.get(fileName);

                                        if (subpathCandidates != null) {

                                            for (ExcelService.SelectiveEntry pe : subpathCandidates) {

                                                if (normAbs.endsWith(pe.normalizedPath) || normAbs.endsWith("/" + pe.normalizedPath)) {

                                                    matchedEntries.add(pe);

                                                }

                                            }

                                        }

                                    }


                                    // 3. File name / Base name match for non-path targets

                                    if (nameMap.containsKey(fileName)) {

                                        matchedEntries.addAll(nameMap.get(fileName));

                                    } else if (nameMap.containsKey(baseName)) {

                                        matchedEntries.addAll(nameMap.get(baseName));

                                    }


                                    if (!matchedEntries.isEmpty()) {

                                        for (ExcelService.SelectiveEntry matchEntry : matchedEntries) {

                                            matchedTargets.add(matchEntry.originalTarget);

                                            if (matchEntry.normalizedPath != null) matchedTargets.add(matchEntry.normalizedPath);

                                            matchedTargets.add(fileName);

                                            matchedTargets.add(baseName);


                                            final String subfolderValue = matchEntry.destSubfolder;

                                            phaser.register();

                                            try {

                                                executor.submit(() -> {

                                                    try {

                                                        Path itemDestPath = resolveSubfolderPath(destPath, subfolderValue, partitionByUnderscore);

                                                        if (!Files.exists(itemDestPath)) {

                                                            Files.createDirectories(itemDestPath);

                                                        }

                                                        processSingleExtraction(

                                                                file, itemDestPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                                                count, currentProgress, progress, cancelFlag

                                                        );

                                                    } catch (Exception e) {

                                                        LogService.log("EXTRACT", "ERROR", "Failed processing selective extraction item: " + file.getFileName().toString() + " - " + e.getMessage());

                                                    } finally {

                                                        phaser.arriveAndDeregister();

                                                    }

                                                });

                                            } catch (Exception e) {

                                                phaser.arriveAndDeregister();

                                                LogService.log("EXTRACT", "ERROR", "Failed to submit selective extraction task for " + file.getFileName().toString() + ": " + e.getMessage());

                                            }

                                        }

                                    }

                                }

                                return FileVisitResult.CONTINUE;

                            }

                            

                            @Override

                            public FileVisitResult visitFileFailed(Path file, IOException exc) {

                                return FileVisitResult.CONTINUE;

                            }

                        });


                        // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                        phaser.arriveAndAwaitAdvance();

                    });

                }

            }


            if (!cancelFlag.get()) {

                ProcessingState.deleteState(destinationFolder);

            }

        } catch (Exception e) {

            LogService.log("EXTRACT", "ERROR", "Selective extraction walk interrupted: " + e.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }


        // Update Excel status column (Done/Left) upon completion

        if (!cancelFlag.get()) {

            ExcelService.updateSelectiveStatusInExcel(excelFilePath, matchedTargets);

        }

        

        return count.get();

    }


    public static int extractInnerZipsStreamed(

            List<String> sourceFolders,

            String destinationFolder,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        Path destPath = PathUtils.normalizeLongPath(destinationFolder);

        if (!Files.exists(destPath)) Files.createDirectories(destPath);


        final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

        final java.util.concurrent.atomic.AtomicLong currentProgress = new java.util.concurrent.atomic.AtomicLong(0);

        final java.util.Set<String> claimedNames = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final java.util.Map<String, String> processedHashes = new java.util.concurrent.ConcurrentHashMap<>();

        final java.util.Map<String, Path> processedCandidates = new java.util.concurrent.ConcurrentHashMap<>();


        // Checkpoint Recovery load

        final ProcessingState checkpoint = ProcessingState.loadState(destinationFolder);

        final Set<String> completedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final Set<String> failedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();


        if (checkpoint != null) {

            completedFiles.addAll(checkpoint.completedFiles);

            failedFiles.addAll(checkpoint.failedFiles);

            count.set((int) Math.min(Integer.MAX_VALUE, checkpoint.progressCount));

            

            // Sync task manager telemetry

            TaskManager.resetTelemetry();

            TaskManager.setProcessedCount(checkpoint.progressCount);

            TaskManager.setFailedCount(checkpoint.failedFiles.size());

        }

        

        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        

        try {

            for (String sf : sourceFolders) {

                if (cancelFlag.get()) break;

                

                processSource(sf, rootPath -> {

                    final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 for main thread

                    Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                        @Override

                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                            if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                            TaskManager.checkPause();

                            

                            if (isImageFile(file)) {

                                phaser.register();

                                try {

                                    executor.submit(() -> {

                                        try {

                                            processSingleExtraction(

                                                    file, destPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                                    count, currentProgress, progress, cancelFlag

                                            );

                                        } finally {

                                            phaser.arriveAndDeregister();

                                        }

                                    });

                                } catch (Exception e) {

                                    phaser.arriveAndDeregister();

                                    LogService.log("EXTRACT", "ERROR", "Failed to submit extraction task for " + file.getFileName().toString() + ": " + e.getMessage());

                                }

                            } else if (ZipService.isZipFile(file)) {

                                phaser.register();

                                try {

                                    executor.submit(() -> {

                                        try {

                                            processNestedZip(

                                                    file, destPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                                    count, currentProgress, progress, cancelFlag, 0

                                            );

                                        } catch (Exception e) {

                                            LogService.log("EXTRACT", "ERROR", "Failed to process nested ZIP: " + file.getFileName().toString() + " - " + e.getMessage());

                                        } finally {

                                            phaser.arriveAndDeregister();

                                        }

                                    });

                                } catch (Exception e) {

                                    phaser.arriveAndDeregister();

                                    LogService.log("EXTRACT", "ERROR", "Failed to submit nested ZIP task for " + file.getFileName().toString() + ": " + e.getMessage());

                                }

                            }

                            return FileVisitResult.CONTINUE;

                        }

                        

                        @Override

                        public FileVisitResult visitFileFailed(Path file, IOException exc) {

                            return FileVisitResult.CONTINUE;

                        }

                    });


                    // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                    phaser.arriveAndAwaitAdvance();

                });

            }


            if (!cancelFlag.get()) {

                ProcessingState.deleteState(destinationFolder);

            }

        } catch (Exception e) {

            LogService.log("EXTRACT", "ERROR", "Inner ZIP extraction walk interrupted: " + e.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }

        

        return count.get();

    }


    private static void processNestedZip(

            Path zipFile,

            Path destPath,

            Set<String> claimedNames,

            java.util.Map<String, String> processedHashes,

            java.util.Map<String, Path> processedCandidates,

            Set<String> completedFiles,

            Set<String> failedFiles,

            String destinationFolder,

            java.util.concurrent.atomic.AtomicInteger count,

            java.util.concurrent.atomic.AtomicLong currentProgress,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag,

            int depth

    ) throws Exception {

        if (cancelFlag.get()) return;

        if (depth > 10) { // Safety cap to avoid infinite loops or recursion attacks (ZIP bomb)

            throw new IllegalStateException("Max nested ZIP extraction depth exceeded (depth limit = 10)");

        }

        

        // Extract to temporary folder safely using TempManager

        Path tempExtractDir = TempManager.createTempDirectory("nested_zip_extract");

        try {

            // Copy zip file to temporary disk so we can open it as standard jar/zip filesystem

            Path tempZipDiskPath = tempExtractDir.resolve("nested.zip");

            safeCopy(zipFile, tempZipDiskPath);

            

            URI uri = URI.create("jar:" + tempZipDiskPath.toAbsolutePath().toUri());

            try (FileSystem fs = FileSystems.newFileSystem(uri, java.util.Collections.emptyMap())) {

                for (Path root : fs.getRootDirectories()) {

                    Files.walkFileTree(root, new SimpleFileVisitor<Path>() {

                        @Override

                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                            if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                            TaskManager.checkPause();

                            

                            if (isImageFile(file)) {

                                try {

                                    processSingleExtraction(

                                            file, destPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                            count, currentProgress, progress, cancelFlag

                                    );

                                } catch (Exception e) {

                                    LogService.log("EXTRACT", "ERROR", "Failed to extract nested file " + file.getFileName().toString() + ": " + e.getMessage());

                                }

                            } else if (ZipService.isZipFile(file)) {

                                try {

                                    processNestedZip(

                                            file, destPath, claimedNames, processedHashes, processedCandidates, completedFiles, failedFiles, destinationFolder,

                                            count, currentProgress, progress, cancelFlag, depth + 1

                                    );

                                } catch (Exception e) {

                                    LogService.log("EXTRACT", "ERROR", "Failed to process nested ZIP: " + file.getFileName().toString() + " - " + e.getMessage());

                                }

                            }

                            return FileVisitResult.CONTINUE;

                        }

                    });

                }

            }

        } finally {

            // Safely delete temporary directories via TempManager

            TempManager.deleteTempDirectory(tempExtractDir);

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ImageRenameService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.function.Consumer;


public class ImageRenameService {


    private static Path resolveTargetCollision(Path destPath, String baseName, String extension, Set<String> claimedNames) {

        String fileName = baseName + extension;

        while (true) {

            Path target = destPath.resolve(fileName);

            String claimKey = fileName.toLowerCase();

            if (claimedNames == null || claimedNames.add(claimKey)) {

                if (!Files.exists(target)) {

                    return target;

                }

                if (claimedNames != null) {

                    claimedNames.remove(claimKey);

                }

            }

            fileName = baseName + "_" + java.util.UUID.randomUUID().toString() + extension;

        }

    }


    public static int renameImagesStreamed(

            List<String> sourceFolders,

            String excelFilePath,

            String destinationFolder,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws IOException {

        Map<String, String> renameMap = ExcelService.readRenameMappingFromExcel(excelFilePath);

        if (renameMap.isEmpty()) return 0;

        

        Path destPath = com.imageextractor.utils.PathUtils.normalizeLongPath(destinationFolder);

        if (!Files.exists(destPath)) Files.createDirectories(destPath);


        final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

        final java.util.concurrent.atomic.AtomicLong currentProgress = new java.util.concurrent.atomic.AtomicLong(0);

        final Set<String> claimedNames = java.util.concurrent.ConcurrentHashMap.newKeySet();

        

        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        

        try {

            for (String sf : sourceFolders) {

                if (cancelFlag != null && cancelFlag.get()) break;

                

                ImageExtractionService.processSource(sf, rootPath -> {

                    final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 for main thread

                    try {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag != null && cancelFlag.get()) return FileVisitResult.TERMINATE;

                                TaskManager.checkPause();

                                

                                if (ImageExtractionService.isImageFile(file)) {

                                    phaser.register();

                                    try {

                                        executor.submit(() -> {

                                            try {

                                                processSingleRename(

                                                        file, renameMap, destPath, claimedNames,

                                                        count, currentProgress, progress, cancelFlag

                                                );

                                            } finally {

                                                phaser.arriveAndDeregister();

                                            }

                                        });

                                    } catch (Exception e) {

                                        phaser.arriveAndDeregister();

                                        LogService.log("RENAME", "ERROR", "Failed to submit rename task for " + file.getFileName().toString() + ": " + e.getMessage());

                                    }

                                }

                                return FileVisitResult.CONTINUE;

                            }

                            

                            @Override

                            public FileVisitResult visitFileFailed(Path file, IOException exc) {

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    } finally {

                        // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                        phaser.arriveAndAwaitAdvance();

                    }

                });

            }

        } catch (Exception e) {

            LogService.log("RENAME", "ERROR", "Rename walk interrupted: " + e.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }

        

        return count.get();

    }


    private static void processSingleRename(

            Path p,

            Map<String, String> renameMap,

            Path destPath,

            Set<String> claimedNames,

            java.util.concurrent.atomic.AtomicInteger count,

            java.util.concurrent.atomic.AtomicLong currentProgress,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) {

        TaskManager.checkPause();

        if (cancelFlag != null && cancelFlag.get()) return;

        TaskManager.updateHeartbeat();

        TaskManager.setCurrentFileName(p.getFileName().toString());

        

        String originalName = p.getFileName().toString();

        String baseName = originalName.toLowerCase();

        String extension = "";

        

        int dot = originalName.lastIndexOf('.');

        if (dot != -1) {

            baseName = originalName.substring(0, dot).toLowerCase();

            extension = originalName.substring(dot);

        }


        if (renameMap.containsKey(baseName)) {

            String newBaseName = renameMap.get(baseName);

            Path target = resolveTargetCollision(destPath, newBaseName, extension, claimedNames);

            String claimKey = target.getFileName().toString().toLowerCase();

            try {

                ImageExtractionService.safeCopy(p, target);

                count.incrementAndGet();

                TaskManager.incrementProcessed();

            } catch (IOException e) {

                LogService.log("RENAME", "ERROR", "Failed to copy/rename: " + p + " - " + e.getMessage());

                TaskManager.incrementFailedCount();

                TaskManager.incrementProcessed();

            } finally {

                if (claimedNames != null) {

                    claimedNames.remove(claimKey);

                }

            }

        } else {

            TaskManager.incrementSkippedCount();

            TaskManager.incrementProcessed();

        }

        long cp = currentProgress.incrementAndGet();

        if (cp % 50 == 0 && progress != null) progress.accept(cp);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ImageValidationService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.IOException;

import java.io.InputStream;

import java.nio.file.*;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.List;

import java.util.Set;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.concurrent.atomic.AtomicLong;

import java.util.function.Consumer;

import javax.imageio.ImageIO;

import javax.imageio.ImageReader;

import javax.imageio.stream.ImageInputStream;

import java.awt.image.BufferedImage;


import org.apache.tika.Tika;


public class ImageValidationService {


    public enum ScanMode {

        QUICK, DEEP

    }


    public static class ValidationResult {

        public final String directory;

        public final String fileName;

        public final String format;

        public final long sizeKB;

        public final String dimensions;

        public final String status;

        public final String reason;

        public final String hash;


        public ValidationResult(String directory, String fileName, String format, long sizeKB, String dimensions, String status, String reason, String hash) {

            this.directory = directory;

            this.fileName = fileName;

            this.format = format;

            this.sizeKB = sizeKB;

            this.dimensions = dimensions;

            this.status = status;

            this.reason = reason;

            this.hash = hash;

        }

    }


    private static final java.util.concurrent.atomic.AtomicBoolean isEvicting = new java.util.concurrent.atomic.AtomicBoolean(false);


    public static void validateImagesStreamed(

            List<String> sourceFolders,

            ScanMode mode,

            boolean checkSpoofing,

            boolean checkDuplicates,

            double similarityThreshold,

            boolean failuresOnly,

            ExcelService.ExcelReportWriter excelWriter,

            Consumer<Long> progress,

            Consumer<String> statusUpdate,

            AtomicBoolean cancelFlag

    ) {

        AtomicLong scannedCount = new AtomicLong(0);

        AtomicLong processedCount = new AtomicLong(0);


        int cores = Runtime.getRuntime().availableProcessors();

        int threadCount = TaskManager.isLowMemoryMode() ? 2 : Math.max(2, cores - 1);

        EnterpriseThreadPoolExecutor executor = new EnterpriseThreadPoolExecutor(threadCount, 5000);

        Tika tika = new Tika();


        final java.util.Map<String, String> processedHashes = new java.util.concurrent.ConcurrentHashMap<>();

        final java.util.Map<Long, String> processedPerceptualHashes = new java.util.concurrent.ConcurrentHashMap<>();

        final java.util.Map<String, Path> processedCandidates = new java.util.concurrent.ConcurrentHashMap<>();


        // 1. Checkpoint System Recovery integration

        String destFolder = excelWriter != null ? excelWriter.outputFilePath : null;

        String stateParent = null;

        if (destFolder != null) {

            Path pDir = Paths.get(destFolder).getParent();

            if (pDir != null) stateParent = pDir.toString();

        }

        

        final String finalStateParent = stateParent;

        final ProcessingState checkpoint = finalStateParent != null ? ProcessingState.loadState(finalStateParent) : null;

        final Set<String> completedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();

        final Set<String> failedFiles = java.util.concurrent.ConcurrentHashMap.newKeySet();


        if (checkpoint != null) {

            completedFiles.addAll(checkpoint.completedFiles);

            failedFiles.addAll(checkpoint.failedFiles);

            processedCount.set(checkpoint.progressCount);

            

            // Sync task manager telemetry processed count

            TaskManager.resetTelemetry();

            TaskManager.setProcessedCount(checkpoint.progressCount);

            TaskManager.setFailedCount(checkpoint.failedFiles.size());

        }


        try {

            for (String sf : sourceFolders) {

                if (cancelFlag.get()) break;


                ImageExtractionService.processSource(sf, rootPath -> {

                    // Use a zero-overhead Phaser to block and wait for tasks in this ZIP/directory instead of ArrayList<Future>

                    final java.util.concurrent.Phaser phaser = new java.util.concurrent.Phaser(1); // 1 represents the main thread

                    

                    try {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                TaskManager.checkPause();


                                if (ImageExtractionService.isImageFile(file)) {

                                    long scanIdx = scannedCount.incrementAndGet();

                                    if (statusUpdate != null && scanIdx % 100 == 0) {

                                        statusUpdate.accept("Scanning & Auditing... Found: " + scanIdx);

                                    }


                                    phaser.register();

                                    try {

                                        executor.submit(() -> {

                                            try {

                                                processSingleStreamedValidation(

                                                        file, mode, checkSpoofing, checkDuplicates, similarityThreshold, failuresOnly,

                                                        processedHashes, processedPerceptualHashes, processedCandidates, completedFiles, failedFiles, finalStateParent,

                                                        excelWriter, processedCount, progress, cancelFlag, tika

                                                );

                                            } catch (Throwable t) {

                                                LogService.log("VALIDATE", "ERROR", "Validation worker error: " + t.getMessage());

                                            } finally {

                                                phaser.arriveAndDeregister();

                                            }

                                        });

                                    } catch (Exception e) {

                                        phaser.arriveAndDeregister();

                                        LogService.log("VALIDATE", "ERROR", "Failed to submit validation task for " + file.getFileName().toString() + ": " + e.getMessage());

                                    }

                                }

                                return FileVisitResult.CONTINUE;

                            }


                            @Override

                            public FileVisitResult visitFileFailed(Path file, IOException exc) {

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    } finally {

                        // Await all tasks for this ZIP filesystem before exiting lambda to prevent premature closure

                        phaser.arriveAndAwaitAdvance();

                    }

                });

            }

            

            // Delete progress state upon successful complete

            if (!cancelFlag.get() && finalStateParent != null) {

                ProcessingState.deleteState(finalStateParent);

            }

        } catch (Exception ex) {

            LogService.log("VALIDATE", "ERROR", "Validation disk walk interrupted: " + ex.getMessage());

        } finally {

            executor.shutdown();

            try {

                if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {

                    executor.shutdownNow();

                }

            } catch (InterruptedException e) {

                executor.shutdownNow();

                Thread.currentThread().interrupt();

            }

        }

    }


    private static void processSingleStreamedValidation(

            Path p,

            ScanMode mode,

            boolean checkSpoofing,

            boolean checkDuplicates,

            double similarityThreshold,

            boolean failuresOnly,

            java.util.Map<String, String> processedHashes,

            java.util.Map<Long, String> processedPerceptualHashes,

            java.util.Map<String, Path> processedCandidates,

            Set<String> completedFiles,

            Set<String> failedFiles,

            String stateParent,

            ExcelService.ExcelReportWriter excelWriter,

            AtomicLong processedCount,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag,

            Tika tika

    ) {

        TaskManager.checkPause();

        if (cancelFlag.get()) return;

        TaskManager.updateHeartbeat();

        TaskManager.setCurrentFileName(p.getFileName().toString());


        String fileName = p.getFileName().toString();

        String fileKey = p.toUri().toString();

        

        // 2. Checkpoint System Recovery Skip Check

        if (completedFiles.contains(fileKey) || failedFiles.contains(fileKey)) {

            // Already successfully processed in a previous interrupted run, bypass!

            updateProgress(processedCount, progress);

            return;

        }


        String directory = p.getParent() != null ? p.getParent().toString() : "Root";

        String format = "";

        int dot = fileName.lastIndexOf('.');

        if (dot != -1) format = fileName.substring(dot + 1).toUpperCase();


        long sizeKB = 0;

        try {

            sizeKB = Files.size(p) / 1024;

        } catch (IOException e) {

            ValidationResult r = new ValidationResult(directory, fileName, format, 0, "Unknown", "Corrupted", "Failed to read file size", "N/A");

            if (excelWriter != null) {

                excelWriter.writeResult(r);

            }

            failedFiles.add(fileKey);

            updateProgress(processedCount, progress);

            return;

        }


        String dimensions = "Unknown";

        String status = "Valid";

        String reason = "";

        String hash = "N/A";


        try {

            // MIME Verification with Magic Headers via Apache Tika

            String detectedMime = tika.detect(p);

            boolean isPdf = format.equalsIgnoreCase("PDF");


            if (isPdf) {

                if (!detectedMime.equalsIgnoreCase("application/pdf")) {

                    throw new IOException("Spoofed Extension! Tika magic headers detected actual MIME type as: " + detectedMime);

                }

                dimensions = "Document";

            } else {

                if (!detectedMime.startsWith("image/")) {

                    throw new IOException("Spoofed Extension! Tika magic headers detected non-image MIME: " + detectedMime);

                }


                // Safe Image Dimension Pre-Check: Do NOT fully decode image, read header dimensions first

                dimensions = readImageDimensionsPreCheck(p);

                

                if (mode == ScanMode.DEEP) {

                    try (InputStream is = Files.newInputStream(p)) {

                        BufferedImage img = ImageIO.read(is);

                        if (img == null) {

                            throw new IOException("Deep scan failed: ImageIO unable to decode raster stream.");

                        }

                        img.flush();

                    }

                }

            }


            if (checkSpoofing && !isPdf) {

                String expectedMime = "image/" + format.toLowerCase();

                if (format.equalsIgnoreCase("JPG") || format.equalsIgnoreCase("JPEG")) expectedMime = "image/jpeg";

                if (format.equalsIgnoreCase("TIF") || format.equalsIgnoreCase("TIFF")) expectedMime = "image/tiff";

                if (format.equalsIgnoreCase("BMP")) expectedMime = "image/bmp";

                if (format.equalsIgnoreCase("PNG")) expectedMime = "image/png";

                if (format.equalsIgnoreCase("WEBP")) expectedMime = "image/webp";


                if (!detectedMime.equalsIgnoreCase(expectedMime)) {

                    status = "Spoofed";

                    reason = "Extension claims " + format + " but actual magic signature is " + detectedMime;

                }

            }


            boolean isDuplicate = false;

            if (!isPdf && checkDuplicates) {

                try {

                    // Compute duplicate status using high-performance staged hashing pipeline

                    DuplicateManager.StagedDuplicateResult duplicateResult = HashService.checkDuplicateStaged(p, processedHashes, processedCandidates);

                    hash = duplicateResult.hash;

                    if (duplicateResult.isDuplicate) {

                        status = "Duplicate";

                        reason = duplicateResult.reason;

                        TaskManager.incrementDuplicateCount();

                        isDuplicate = true;

                    }

                } catch (Exception e) {

                    LogService.log("VALIDATE", "ERROR", "Failed to compute duplicate hash for " + fileName + ": " + e.getMessage());

                }


                if (!isDuplicate && processedPerceptualHashes != null) {

                    try {

                        long currentDHash = HashService.computePerceptualHash(p);

                        

                        // 1. O(1) Instant exact dHash match

                        String exactMatch = processedPerceptualHashes.get(currentDHash);

                        if (exactMatch != null) {

                            status = "Duplicate";

                            reason = "Exact perceptual image match (100.00% similarity) with original: " + exactMatch;

                            TaskManager.incrementDuplicateCount();

                            isDuplicate = true;

                        } else {

                            // 2. High-performance bounded near-match similarity check (up to 250 recent entries to prevent O(N^2) lockup)

                            int checked = 0;

                            for (java.util.Map.Entry<Long, String> entry : processedPerceptualHashes.entrySet()) {

                                if (++checked > 250) break;

                                double similarity = HashService.calculatePerceptualSimilarity(currentDHash, entry.getKey());

                                if (similarity >= similarityThreshold) {

                                    status = "Duplicate";

                                    reason = String.format("Perceptual image match (%.2f%% similarity) with original: %s", similarity * 100, entry.getValue());

                                    TaskManager.incrementDuplicateCount();

                                    isDuplicate = true;

                                    break;

                                }

                            }

                        }


                        if (processedPerceptualHashes.size() > 500000 && isEvicting.compareAndSet(false, true)) {

                            try {

                                int toRemove = processedPerceptualHashes.size() / 5;

                                java.util.Iterator<Long> it = processedPerceptualHashes.keySet().iterator();

                                while (it.hasNext() && toRemove-- > 0) {

                                    it.next();

                                    it.remove();

                                }

                            } finally {

                                isEvicting.set(false);

                            }

                        }

                        if (!isDuplicate) {

                            processedPerceptualHashes.put(currentDHash, fileName);

                        }

                    } catch (Exception e) {

                        LogService.log("VALIDATE", "ERROR", "Failed to compute perceptual dHash for " + fileName + ": " + e.getMessage());

                    }

                }

            }

            

            // Mark file as completed successfully

            if (completedFiles.size() > 500000 && isEvicting.compareAndSet(false, true)) {

                try {

                    int removeCount = completedFiles.size() / 5;

                    java.util.Iterator<String> it = completedFiles.iterator();

                    while (it.hasNext() && removeCount-- > 0) {

                        it.next();

                        it.remove();

                    }

                } finally {

                    isEvicting.set(false);

                }

            }

            completedFiles.add(fileKey);

        } catch (Exception e) {

            status = "Corrupted";

            reason = e.getMessage() != null ? e.getMessage() : e.toString();

            if (failedFiles.size() > 500000 && isEvicting.compareAndSet(false, true)) {

                try {

                    int removeCount = failedFiles.size() / 5;

                    java.util.Iterator<String> it = failedFiles.iterator();

                    while (it.hasNext() && removeCount-- > 0) {

                        it.next();

                        it.remove();

                    }

                } finally {

                    isEvicting.set(false);

                }

            }

            failedFiles.add(fileKey);

            TaskManager.incrementFailedCount();

        }


        ValidationResult result = new ValidationResult(directory, fileName, format, sizeKB, dimensions, status, reason, hash);


        if (excelWriter != null && (!failuresOnly || !"Valid".equals(status))) {

            excelWriter.writeResult(result);

        }


        // Save progress checkpoint recovery state adaptively

        long currentValCount = processedCount.get() + 1;

        if (stateParent != null) {

            ProcessingState.saveStateAdaptive(stateParent, "validate-task", completedFiles, failedFiles, currentValCount);

        }


        updateProgress(processedCount, progress);

    }


    private static String readImageDimensionsPreCheck(Path p) throws IOException {

        try (InputStream is = Files.newInputStream(p);

             ImageInputStream iis = ImageIO.createImageInputStream(is)) {

            if (iis == null) return "Unknown";

            java.util.Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);

            if (readers.hasNext()) {

                ImageReader reader = readers.next();

                try {

                    reader.setInput(iis);

                    int w = reader.getWidth(0);

                    int h = reader.getHeight(0);

                    

                    if (w > 32768 || h > 32768) {

                        throw new IOException("Decompression bomb protection triggered! Malicious image size: " + w + "x" + h);

                    }

                    return w + "x" + h;

                } finally {

                    reader.dispose();

                }

            }

        }

        return "Unknown";

    }


    private static void updateProgress(AtomicLong processedCount, Consumer<Long> progress) {

        long cp = processedCount.incrementAndGet();

        TaskManager.incrementProcessed();

        if (cp % 50 == 0 && progress != null) progress.accept(cp);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\KeepAwakeService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.awt.MouseInfo;

import java.awt.Point;

import java.awt.Robot;

import java.awt.event.KeyEvent;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;


public class KeepAwakeService {

    private static volatile boolean active = false;

    private static ScheduledExecutorService scheduler;


    /**

     * Pure-Java Keep-Awake implementation using java.awt.Robot.

     * Combines a 1-pixel micro-nudge (immediately restored) and a virtual VK_F15 keypress

     * every 20 seconds. This sends genuine hardware-level input events to Windows OS,

     * resetting the System & Display Inactivity (Lock Screen) timers 100% offline

     * without native DLLs (JNA) or corporate EDR security alerts.

     */

    public static synchronized void start() {

        if (active) return;

        try {

            Robot robot = new Robot();

            scheduler = Executors.newSingleThreadScheduledExecutor(r -> {

                Thread t = new Thread(r, "SNAPIX-KeepAwake");

                t.setDaemon(true);

                return t;

            });


            scheduler.scheduleAtFixedRate(() -> {

                try {

                    // 1. Mouse Micro-Nudge (move 1px and restore immediately)

                    java.awt.PointerInfo pointerInfo = MouseInfo.getPointerInfo();

                    if (pointerInfo != null) {

                        Point p = pointerInfo.getLocation();

                        if (p != null) {

                            int delta = (p.x > 10) ? -1 : 1;

                            robot.mouseMove(p.x + delta, p.y);

                            robot.mouseMove(p.x, p.y);

                        }

                    }


                    // 2. Virtual F15 key heartbeat (resets OS keyboard idle timer silently)

                    robot.keyPress(KeyEvent.VK_F15);

                    robot.keyRelease(KeyEvent.VK_F15);

                } catch (Throwable ignored) {}

            }, 0, 20, TimeUnit.SECONDS);


            active = true;

            LogService.log("SYSTEM", "INFO", "System Keep-Awake activated (Robot mouse nudge + F15 key heartbeat).");

        } catch (Throwable t) {

            LogService.log("SYSTEM", "WARNING", "Failed to initialize Keep-Awake robot: " + t.getMessage());

        }

    }


    public static synchronized void stop() {

        if (!active) return;

        try {

            if (scheduler != null && !scheduler.isShutdown()) {

                scheduler.shutdownNow();

                scheduler = null;

            }

            active = false;

            LogService.log("SYSTEM", "INFO", "System Keep-Awake deactivated.");

        } catch (Throwable t) {

            // Ignore cleanup failure

        }

    }


    public static boolean isActive() {

        return active;

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\LogService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import javax.swing.SwingUtilities;

import java.time.LocalDateTime;

import java.time.format.DateTimeFormatter;

import java.util.function.Consumer;


public class LogService {

    private static volatile Consumer<String> logListener;

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

    private static final java.util.concurrent.BlockingQueue<String> uiQueue = new java.util.concurrent.ArrayBlockingQueue<>(2000);

    private static final java.util.concurrent.atomic.AtomicBoolean flushScheduled = new java.util.concurrent.atomic.AtomicBoolean(false);


    public static void setListener(Consumer<String> listener) {

        logListener = listener;

    }


    public static void log(String message) {

        log("SYSTEM", "INFO", message);

    }


    public static void log(String category, String severity, String message) {

        SimpleLogger.log(category, severity, message);

        

        Consumer<String> listener = logListener;

        if (listener != null) {

            String timeStr = LocalDateTime.now().format(FORMATTER);

            String formattedMessage = "[" + timeStr + "] " + message + "\n";

            

            uiQueue.offer(formattedMessage);

            

            if (flushScheduled.compareAndSet(false, true)) {

                SwingUtilities.invokeLater(() -> {

                    try {

                        Consumer<String> l = logListener;

                        if (l != null) {

                            StringBuilder sb = new StringBuilder();

                            String line;

                            while ((line = uiQueue.poll()) != null) {

                                sb.append(line);

                            }

                            if (sb.length() > 0) {

                                l.accept(sb.toString());

                            }

                        } else {

                            uiQueue.clear();

                        }

                    } finally {

                        flushScheduled.set(false);

                    }

                });

            }

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ProcessingState.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.*;

import java.nio.file.Files;

import java.nio.file.Path;

import java.util.HashSet;

import java.util.Set;


public class ProcessingState implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final String STATE_FILE_NAME = "ProcessingState.dat";

    private static final java.util.concurrent.atomic.AtomicLong lastSavedTimeMillis = new java.util.concurrent.atomic.AtomicLong(System.currentTimeMillis());

    private static final java.util.concurrent.atomic.AtomicLong lastSavedCount = new java.util.concurrent.atomic.AtomicLong(0);


    public final String taskUuid;

    public final Set<String> completedFiles;

    public final Set<String> failedFiles;

    public final long progressCount;


    public ProcessingState(String taskUuid, Set<String> completedFiles, Set<String> failedFiles, long progressCount) {

        this.taskUuid = taskUuid;

        Set<String> comp = (completedFiles instanceof HashSet) ? completedFiles : ((completedFiles != null) ? new HashSet<>(completedFiles) : new HashSet<>());

        if (comp.size() > 500000) {

            int toRemove = comp.size() - 500000;

            java.util.Iterator<String> it = comp.iterator();

            while (it.hasNext() && toRemove-- > 0) {

                it.next();

                it.remove();

            }

        }

        this.completedFiles = comp;


        Set<String> fail = (failedFiles instanceof HashSet) ? failedFiles : ((failedFiles != null) ? new HashSet<>(failedFiles) : new HashSet<>());

        if (fail.size() > 500000) {

            int toRemove = fail.size() - 500000;

            java.util.Iterator<String> it = fail.iterator();

            while (it.hasNext() && toRemove-- > 0) {

                it.next();

                it.remove();

            }

        }

        this.failedFiles = fail;

        this.progressCount = progressCount;

    }


    public static void saveStateAdaptive(String destFolder, String taskUuid, Set<String> completedFiles, Set<String> failedFiles, long progressCount) {

        long now = System.currentTimeMillis();

        long lastTime = lastSavedTimeMillis.get();

        long lastCount = lastSavedCount.get();


        if (now - lastTime >= 15000 || Math.abs(progressCount - lastCount) >= 5000) {

            if (lastSavedTimeMillis.compareAndSet(lastTime, now)) {

                lastSavedCount.set(progressCount);

                Set<String> compCopy = new HashSet<>();

                if (completedFiles != null) {

                    try {

                        compCopy.addAll(completedFiles);

                    } catch (Exception ignored) {}

                }


                Set<String> failCopy = new HashSet<>();

                if (failedFiles != null) {

                    try {

                        failCopy.addAll(failedFiles);

                    } catch (Exception ignored) {}

                }

                saveState(destFolder, new ProcessingState(taskUuid, compCopy, failCopy, progressCount));

            }

        }

    }


    public static void resetAdaptiveThresholds() {

        lastSavedTimeMillis.set(System.currentTimeMillis());

        lastSavedCount.set(0);

    }


    public static void saveState(String destFolder, ProcessingState state) {

        Path destDir = com.imageextractor.utils.PathUtils.normalizeLongPath(destFolder);

        Path realPath = destDir.resolve(STATE_FILE_NAME);

        Path tmpPath = destDir.resolve(STATE_FILE_NAME + ".tmp");

        try {

            try (FileOutputStream fos = new FileOutputStream(tmpPath.toFile());

                 BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);

                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {

                oos.writeObject(state);

            }

            try {

                Files.move(tmpPath, realPath, java.nio.file.StandardCopyOption.ATOMIC_MOVE, java.nio.file.StandardCopyOption.REPLACE_EXISTING);

            } catch (java.nio.file.AtomicMoveNotSupportedException amse) {

                Files.move(tmpPath, realPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);

            }

            LogService.log("STATE", "INFO", "Progress checkpoint saved successfully (Index: " + state.progressCount + ")");

        } catch (IOException e) {

            LogService.log("STATE", "ERROR", "Failed to save progress checkpoint: " + e.getMessage());

            try {

                Files.deleteIfExists(tmpPath);

            } catch (IOException ignored) {}

        }

    }


    public static ProcessingState loadState(String destFolder) {

        Path path = com.imageextractor.utils.PathUtils.normalizeLongPath(destFolder).resolve(STATE_FILE_NAME);

        if (!Files.exists(path)) return null;

        

        try (FileInputStream fis = new FileInputStream(path.toFile());

             BufferedInputStream bis = new BufferedInputStream(fis, 65536);

             ObjectInputStream ois = new ObjectInputStream(bis) {

                 @Override

                 protected Class<?> resolveClass(java.io.ObjectStreamClass desc) throws IOException, ClassNotFoundException {

                     String name = desc.getName();

                     if (!name.startsWith("com.imageextractor.") &&

                         !name.equals("java.util.HashSet") &&

                         !name.equals("java.util.HashMap") &&

                         !name.equals("java.lang.String") &&

                         !name.equals("java.lang.Long") &&

                         !name.equals("java.lang.Integer") &&

                         !name.equals("java.lang.Number")) {

                         throw new java.io.InvalidClassException("Unauthorized deserialization class: " + name);

                     }

                     return super.resolveClass(desc);

                 }

             }) {

            ProcessingState state = (ProcessingState) ois.readObject();

            LogService.log("STATE", "INFO", "Found progress checkpoint. Resuming from file index: " + state.progressCount);

            return state;

        } catch (Exception e) {

            LogService.log("STATE", "ERROR", "Failed to load progress checkpoint: " + e.getMessage());

            return null;

        }

    }


    public static void deleteState(String destFolder) {

        Path path = com.imageextractor.utils.PathUtils.normalizeLongPath(destFolder).resolve(STATE_FILE_NAME);

        try {

            Files.deleteIfExists(path);

        } catch (IOException ignored) {}

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\SettingsManager.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.util.Properties;


public class SettingsManager {

    private static final String CONFIG_DIR = "config";

    private static final String SETTINGS_FILE = CONFIG_DIR + File.separator + "settings.properties";

    private static final Properties properties = new Properties();


    static {

        loadSettings();

    }


    public static synchronized void loadSettings() {

        try {

            File dir = new File(CONFIG_DIR);

            if (!dir.exists()) {

                dir.mkdirs();

            }

            File file = new File(SETTINGS_FILE);

            if (file.exists()) {

                try (FileInputStream fis = new FileInputStream(file)) {

                    properties.load(fis);

                }

            }

            

            // Set defaults if empty

            if (!properties.containsKey("lowMemoryMode")) {

                properties.setProperty("lowMemoryMode", "false");

            }

            

            // Apply loaded settings to TaskManager

            boolean lowMem = Boolean.parseBoolean(properties.getProperty("lowMemoryMode", "false"));

            TaskManager.setLowMemoryMode(lowMem);

            

        } catch (Exception e) {

            LogService.log("SETTINGS", "ERROR", "Failed to load settings: " + e.getMessage());

        }

    }


    public static synchronized void saveSettings() {

        try {

            File dir = new File(CONFIG_DIR);

            if (!dir.exists()) {

                dir.mkdirs();

            }

            // Update properties with current TaskManager values

            properties.setProperty("lowMemoryMode", String.valueOf(TaskManager.isLowMemoryMode()));

            

            File file = new File(SETTINGS_FILE);

            File tmpFile = new File(SETTINGS_FILE + ".tmp");

            try (FileOutputStream fos = new FileOutputStream(tmpFile)) {

                properties.store(fos, "Enterprise Image Processing Tool Settings");

            }

            if (tmpFile.exists()) {

                try {

                    java.nio.file.Files.move(tmpFile.toPath(), file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING, java.nio.file.StandardCopyOption.ATOMIC_MOVE);

                } catch (Exception ex) {

                    java.nio.file.Files.move(tmpFile.toPath(), file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);

                }

            }

        } catch (Exception e) {

            LogService.log("SETTINGS", "ERROR", "Failed to save settings: " + e.getMessage());

        }

    }


    public static boolean isLowMemoryMode() {

        return TaskManager.isLowMemoryMode();

    }


    public static void setLowMemoryMode(boolean value) {

        TaskManager.setLowMemoryMode(value);

        saveSettings();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\SevenZipManager.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.File;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.atomic.AtomicBoolean;


public class SevenZipManager {

    

    private static File cached7zFile = null;


    /**

     * Resolves the path to the 7-Zip executable by checking Windows Program Files 

     * and searching the system PATH variable environment.

     */

    public static synchronized File resolve7zExecutable() {

        if (cached7zFile != null) return cached7zFile;


        // 1. Resolve Program Files paths dynamically using environment variables

        java.util.List<String> searchPaths = new java.util.ArrayList<>();

        String pf = System.getenv("ProgramFiles");

        String pf86 = System.getenv("ProgramFiles(x86)");

        String pfW64 = System.getenv("ProgramW6432");

        

        if (pf != null) searchPaths.add(pf + "\\7-Zip\\7z.exe");

        if (pf86 != null) searchPaths.add(pf86 + "\\7-Zip\\7z.exe");

        if (pfW64 != null) searchPaths.add(pfW64 + "\\7-Zip\\7z.exe");

        

        // Common fallbacks

        searchPaths.add("C:\\Program Files\\7-Zip\\7z.exe");

        searchPaths.add("C:\\Program Files (x86)\\7-Zip\\7z.exe");


        for (String p : searchPaths) {

            File f = new File(p);

            if (f.exists() && f.isFile()) {

                cached7zFile = f;

                return f;

            }

        }


        // 2. Search in system environment PATH variable

        String pathEnv = System.getenv("PATH");

        if (pathEnv != null) {

            for (String part : pathEnv.split(File.pathSeparator)) {

                File f = new File(part, "7z.exe");

                if (f.exists() && f.isFile()) {

                    cached7zFile = f;

                    return f;

                }

            }

        }


        return null;

    }


    /**

     * Executes the 7-Zip process to compress the sourceDirectory into the targetZip file.

     */

    public static void compressWith7Zip(

            String exePath, 

            String sourceDirectory, 

            String targetZip, 

            AtomicBoolean cancelFlag, 

            AtomicBoolean pauseFlag, 

            Object pauseLock

    ) throws Exception {

        List<String> cmd = new ArrayList<>();

        cmd.add(exePath);

        cmd.add("a");

        cmd.add("-tzip");

        cmd.add("--");

        cmd.add(targetZip);

        cmd.add(sourceDirectory + File.separator + "*");

        

        ProcessBuilder pb = new ProcessBuilder(cmd);

        String os = System.getProperty("os.name").toLowerCase();

        File devNull = new File(os.contains("win") ? "NUL" : "/dev/null");

        pb.redirectOutput(devNull);

        pb.redirectError(devNull);

        

        Process p = pb.start();

        try {

            if (p.getOutputStream() != null) {

                p.getOutputStream().close();

            }

        } catch (Exception ignored) {}

        

        long processStartTime = System.currentTimeMillis();

        long processTimeoutMs = 30 * 60 * 1000L; // 30 minutes max


        try {

            while (p.isAlive()) {

                if (System.currentTimeMillis() - processStartTime > processTimeoutMs) {

                    p.destroyForcibly();

                    throw new IOException("7-Zip compression timed out after 30 minutes.");

                }

                if (cancelFlag != null && cancelFlag.get()) {

                    p.destroyForcibly();

                    throw new InterruptedException("Compression cancelled by user.");

                }

                if (pauseFlag != null && pauseFlag.get() && pauseLock != null) {

                    synchronized (pauseLock) {

                        try {

                            while (pauseFlag.get()) {

                                pauseLock.wait(100);

                                if (cancelFlag != null && cancelFlag.get()) {

                                    p.destroyForcibly();

                                    throw new InterruptedException("Compression cancelled by user.");

                                }

                            }

                        } catch (InterruptedException e) {

                            p.destroyForcibly();

                            Thread.currentThread().interrupt();

                            throw e;

                        }

                    }

                }

                Thread.sleep(100);

            }


            int exitCode = p.waitFor();

            if (exitCode != 0 && exitCode != 1) { 

                throw new Exception("7-Zip failed with exit code: " + exitCode);

            }

        } catch (InterruptedException e) {

            p.destroyForcibly();

            throw e;

        } finally {

            if (p.isAlive()) {

                p.destroyForcibly();

            }

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\SimpleLogger.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.BufferedWriter;

import java.io.File;

import java.io.FileWriter;

import java.io.IOException;

import java.io.PrintWriter;

import java.time.LocalDateTime;

import java.time.format.DateTimeFormatter;


public class SimpleLogger {

    private static final String LOG_DIR = "logs";

    private static final String LOG_FILE = LOG_DIR + File.separator + "app.log";

    private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final java.util.concurrent.BlockingQueue<String> logQueue = new java.util.concurrent.ArrayBlockingQueue<>(5000);

    private static volatile PrintWriter fileWriter;


    static {

        File dir = new File(LOG_DIR);

        if (!dir.exists()) {

            dir.mkdirs();

        }

        initWriter();


        Thread writerThread = new Thread(() -> {

            while (true) {

                try {

                    String msg = logQueue.take();

                    writeToFile(msg);

                    String next;

                    while ((next = logQueue.poll()) != null) {

                        writeToFile(next);

                    }

                    if (fileWriter != null) {

                        fileWriter.flush();

                    }

                } catch (InterruptedException e) {

                    Thread.currentThread().interrupt();

                    break;

                } catch (Exception ignored) {}

            }

        });

        writerThread.setName("SimpleLogger-Writer");

        writerThread.setDaemon(true);

        writerThread.start();


        Runtime.getRuntime().addShutdownHook(new Thread(() -> {

            while (!logQueue.isEmpty()) {

                String m = logQueue.poll();

                if (m != null) writeToFile(m);

            }

            if (fileWriter != null) {

                fileWriter.flush();

                fileWriter.close();

            }

        }));

    }


    private static synchronized void initWriter() {

        try {

            File file = new File(LOG_FILE);

            if (file.exists() && file.length() > MAX_FILE_SIZE) {

                rotateLogs(file);

            }

            fileWriter = new PrintWriter(new BufferedWriter(new FileWriter(LOG_FILE, true), 65536), false);

        } catch (IOException e) {

            System.err.println("SimpleLogger: Could not open log file: " + e.getMessage());

        }

    }


    private static synchronized void writeToFile(String msg) {

        try {

            // Print to console from single background writer thread, avoiding multi-thread lock contention

            System.out.println(msg);


            File file = new File(LOG_FILE);

            if (file.exists() && file.length() > MAX_FILE_SIZE) {

                if (fileWriter != null) {

                    fileWriter.close();

                }

                rotateLogs(file);

                initWriter();

            }

            if (fileWriter != null) {

                fileWriter.println(msg);

            }

        } catch (Exception ignored) {}

    }


    public static void log(String category, String severity, String message) {

        String timestamp = LocalDateTime.now().format(DATE_FORMATTER);

        String threadName = Thread.currentThread().getName().toUpperCase();

        String formattedMsg = String.format("[%s][%s][%s][%s] %s", timestamp, category.toUpperCase(), threadName, severity.toUpperCase(), message);

        

        // Offer to background queue without blocking caller worker threads

        logQueue.offer(formattedMsg);

    }


    private static void rotateLogs(File currentFile) {

        File oldFile = new File(LOG_DIR + File.separator + "app.log.old");

        if (oldFile.exists()) {

            oldFile.delete();

        }

        if (!currentFile.renameTo(oldFile)) {

            // If rename fails (e.g. temporary lock), attempt delete to avoid continuous rotation thrashing

            try {

                oldFile.delete();

                currentFile.renameTo(oldFile);

            } catch (Exception ignored) {}

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\SplitService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.List;

import java.util.concurrent.atomic.AtomicBoolean;

import java.util.function.Consumer;


public class SplitService {

    

    private static void deleteDirectoryRecursive(Path dir) {

        if (dir == null || !Files.exists(dir)) return;

        try {

            Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {

                @Override

                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                    try {

                        Files.deleteIfExists(file);

                    } catch (Exception ignored) {}

                    return FileVisitResult.CONTINUE;

                }

                @Override

                public FileVisitResult postVisitDirectory(Path d, IOException exc) {

                    try {

                        Files.deleteIfExists(d);

                    } catch (Exception ignored) {}

                    return FileVisitResult.CONTINUE;

                }

            });

        } catch (Exception e) {

            LogService.log("SPLIT", "WARNING", "Failed to clean directory: " + dir + " - " + e.getMessage());

        }

    }


    private static void finalizeBatch(Path parent, String baseName, int batchNumber, boolean outputAsZip, AtomicBoolean cancelFlag) throws Exception {

        String batchName = baseName + "_" + batchNumber;

        Path folderPath = parent.resolve(outputAsZip ? (batchName + "_temp") : batchName);

        

        if (outputAsZip && Files.exists(folderPath)) {

            Path zipPath = parent.resolve(batchName + ".zip");

            try {

                ZipService.compressDirectory(folderPath.toString(), zipPath.toString(), cancelFlag, null, null);

            } finally {

                deleteDirectoryRecursive(folderPath);

            }

        }

    }


    public static int splitImagesStreamed(

            List<String> sourceFolders,

            String destParentPath,

            String baseName,

            long sizeLimitBytes,

            boolean outputAsZip,

            Consumer<Long> progress,

            AtomicBoolean cancelFlag

    ) throws Exception {

        final long[] currentProgress = {0};

        final int[] batchNumber = {1};

        final long[] currentBatchSize = {0};

        final Path parent = com.imageextractor.utils.PathUtils.normalizeLongPath(destParentPath);

        if (!Files.exists(parent)) Files.createDirectories(parent);

        

        try {

            for (String sf : sourceFolders) {

                if (cancelFlag != null && cancelFlag.get()) break;

                

                ImageExtractionService.processSource(sf, rootPath -> {

                    Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                        @Override

                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

                            if (cancelFlag != null && cancelFlag.get()) return FileVisitResult.TERMINATE;

                            TaskManager.checkPause();

                            TaskManager.updateHeartbeat();

                            TaskManager.setCurrentFileName(file.getFileName().toString());

                            

                            if (ImageExtractionService.isImageFile(file)) {

                                try {

                                    long fileSize = Files.size(file);

                                    

                                    if (currentBatchSize[0] + fileSize > sizeLimitBytes && currentBatchSize[0] > 0) {

                                        finalizeBatch(parent, baseName, batchNumber[0], outputAsZip, cancelFlag);

                                        batchNumber[0]++;

                                        currentBatchSize[0] = 0;

                                    }

                                    

                                    String batchName = baseName + "_" + batchNumber[0];

                                    Path folderPath = parent.resolve(outputAsZip ? (batchName + "_temp") : batchName);

                                    if (!Files.exists(folderPath)) Files.createDirectories(folderPath);

                                    

                                    Path target = folderPath.resolve(file.getFileName().toString());

                                    if (Files.exists(target)) {

                                        String fn = file.getFileName().toString();

                                        int dot = fn.lastIndexOf('.');

                                        String base = dot != -1 ? fn.substring(0, dot) : fn;

                                        String ext = dot != -1 ? fn.substring(dot) : "";

                                        target = folderPath.resolve(base + "_" + java.util.UUID.randomUUID().toString() + ext);

                                    }

                                    ImageExtractionService.safeCopy(file, target);

                                    

                                    currentBatchSize[0] += fileSize;

                                    currentProgress[0]++;

                                    TaskManager.incrementProcessed();

                                    if (currentProgress[0] % 50 == 0 && progress != null) progress.accept(currentProgress[0]);

                                    

                                } catch (Exception e) {

                                    LogService.log("SPLIT", "ERROR", "Failed to process file during split: " + file + " - " + e.getMessage());

                                    TaskManager.incrementFailedCount();

                                }

                            }

                            return FileVisitResult.CONTINUE;

                        }

                    });

                });

            }

            

            boolean isCancelled = (cancelFlag != null && cancelFlag.get());

            if (currentBatchSize[0] > 0 && !isCancelled) {

                finalizeBatch(parent, baseName, batchNumber[0], outputAsZip, cancelFlag);

            } else if (currentBatchSize[0] == 0 && batchNumber[0] == 1) {

                return 0;

            }

            

            if (isCancelled) {

                return Math.max(0, batchNumber[0] - 1);

            }

            return currentBatchSize[0] > 0 ? batchNumber[0] : Math.max(0, batchNumber[0] - 1);

        } finally {

            if (cancelFlag != null && cancelFlag.get() && outputAsZip) {

                String batchName = baseName + "_" + batchNumber[0];

                Path folderPath = parent.resolve(batchName + "_temp");

                deleteDirectoryRecursive(folderPath);

            }

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\TaskManager.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.util.concurrent.atomic.AtomicBoolean;

import java.util.concurrent.atomic.AtomicLong;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;


public class TaskManager {

    // Performance & Count telemetry

    private static final AtomicLong totalFiles = new AtomicLong(0);

    private static final AtomicLong scannedCount = new AtomicLong(0);

    private static final AtomicLong processedCount = new AtomicLong(0);

    private static final AtomicLong failedCount = new AtomicLong(0);

    private static final AtomicLong duplicateCount = new AtomicLong(0);

    private static final AtomicLong skippedCount = new AtomicLong(0);

    private static final AtomicLong totalBytesProcessed = new AtomicLong(0);

    

    private static volatile String currentFileName = "";

    private static volatile String currentOperation = "Idle";

    private static volatile boolean lowMemoryMode = false;


    // Deadlock-Free Pause/Resume Lock & Conditions

    private static final ReentrantLock pauseLock = new ReentrantLock();

    private static final Condition pauseCondition = pauseLock.newCondition();

    private static final AtomicBoolean paused = new AtomicBoolean(false);


    public static void updateHeartbeat() {

        // Fast no-op: maintains API compatibility without 10M map put overhead

    }


    public static void checkPause() {

        if (paused.get()) {

            pauseLock.lock();

            try {

                while (paused.get()) {

                    pauseCondition.await();

                }

            } catch (InterruptedException e) {

                Thread.currentThread().interrupt();

            } finally {

                pauseLock.unlock();

            }

        }

    }


    public static void setPaused(boolean p) {

        pauseLock.lock();

        try {

            paused.set(p);

            if (!p) {

                pauseCondition.signalAll();

            }

        } finally {

            pauseLock.unlock();

        }

    }


    public static boolean isPaused() {

        return paused.get();

    }


    public static void resetTelemetry() {

        totalFiles.set(0);

        scannedCount.set(0);

        processedCount.set(0);

        failedCount.set(0);

        duplicateCount.set(0);

        skippedCount.set(0);

        totalBytesProcessed.set(0);

        currentFileName = "";

        currentOperation = "Idle";

        setPaused(false);

        ProcessingState.resetAdaptiveThresholds();

    }


    // Increment helpers

    public static void incrementProcessed() { processedCount.incrementAndGet(); }

    public static void incrementFailedCount() { failedCount.incrementAndGet(); }

    public static void incrementDuplicateCount() { duplicateCount.incrementAndGet(); }

    public static void incrementSkippedCount() { skippedCount.incrementAndGet(); }

    public static void addBytesProcessed(long bytes) { totalBytesProcessed.addAndGet(bytes); }


    // Getters and setters

    public static long getTotalFiles() { return totalFiles.get(); }

    public static void setTotalFiles(long val) { totalFiles.set(val); }

    public static long getScannedCount() { return scannedCount.get(); }

    public static void setScannedCount(long val) { scannedCount.set(val); }

    public static long getProcessedCount() { return processedCount.get(); }

    public static void setProcessedCount(long val) { processedCount.set(val); }

    public static long getFailedCount() { return failedCount.get(); }

    public static void setFailedCount(long val) { failedCount.set(val); }

    public static long getDuplicateCount() { return duplicateCount.get(); }

    public static long getSkippedCount() { return skippedCount.get(); }

    public static long getTotalBytesProcessed() { return totalBytesProcessed.get(); }

    public static String getCurrentFileName() { return currentFileName; }

    public static void setCurrentFileName(String name) { currentFileName = name; }

    public static String getCurrentOperation() { return currentOperation; }

    public static void setCurrentOperation(String op) { currentOperation = op; }

    public static boolean isLowMemoryMode() { return lowMemoryMode; }

    public static void setLowMemoryMode(boolean val) { lowMemoryMode = val; }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\TaskWatchdog.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.util.concurrent.atomic.AtomicBoolean;


public class TaskWatchdog {

    private static final long MAX_STALL_TIME = 5 * 60 * 1000; // 5 Minutes

    private static AtomicBoolean running = new AtomicBoolean(false);

    private static Thread watchdogThread;


    public static boolean isRunning() {

        return running.get();

    }


    public static synchronized void start(AtomicBoolean cancelFlag) {

        if (running.get()) return;

        running.set(true);

        watchdogThread = new Thread(() -> {

            try {

                long lastProgressTime = System.currentTimeMillis();

                long lastProcessed = TaskManager.getProcessedCount();

                boolean wasPaused = false;

                while (running.get()) {

                    try {

                        Thread.sleep(10000); // Check every 10 seconds

                        

                        if (TaskManager.isPaused()) {

                            wasPaused = true;

                            continue; // Watchdog ignores stalls while paused

                        } else if (wasPaused) {

                            // Reset progress time when resuming from pause

                            wasPaused = false;

                            lastProgressTime = System.currentTimeMillis();

                            lastProcessed = TaskManager.getProcessedCount();

                        }


                        long now = System.currentTimeMillis();

                        long currentProcessed = TaskManager.getProcessedCount();

                        

                        // If any progress was made, reset the global progress timestamp

                        if (currentProcessed > lastProcessed) {

                            lastProcessed = currentProcessed;

                            lastProgressTime = now;

                        } else if (now - lastProgressTime > MAX_STALL_TIME && TaskManager.getTotalFiles() > 0) {

                            // Total stall: zero progress across all threads for 5 minutes

                            LogService.log("WATCHDOG", "WARNING", "System stall detected: No file progress made in " + (now - lastProgressTime)/1000 + "s. Requesting abort.");

                            if (cancelFlag != null) {

                                cancelFlag.set(true);

                            }

                            break;

                        }

                    } catch (InterruptedException e) {

                        Thread.currentThread().interrupt();

                        break;

                    }

                }

            } finally {

                running.set(false);

            }

        });

        watchdogThread.setDaemon(true);

        watchdogThread.setName("TaskWatchdog");

        watchdogThread.start();

    }


    public static synchronized void stop() {

        running.set(false);

        if (watchdogThread != null) {

            watchdogThread.interrupt();

            watchdogThread = null;

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\TempManager.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;


public class TempManager {

    private static final String SESSION_ID = java.util.UUID.randomUUID().toString().substring(0, 8);

    private static final Path baseTempPath = Paths.get(System.getProperty("java.io.tmpdir", "."), "ImageExtractor", "temp").toAbsolutePath();

    private static final Path rootTempPath = baseTempPath.resolve("session_" + SESSION_ID);


    static {

        // Clean up old orphaned sessions (> 2 hours old) on startup safely without touching active sessions

        cleanupOrphanedSessions();

        try {

            Files.createDirectories(rootTempPath);

        } catch (IOException e) {

            LogService.log("TEMP", "ERROR", "Failed to initialize temp session folder: " + e.getMessage());

        }

        

        // Register shutdown hook for clean exit of current session

        Runtime.getRuntime().addShutdownHook(new Thread(TempManager::cleanupCurrentSession));

    }


    private static void cleanupOrphanedSessions() {

        if (!Files.exists(baseTempPath)) return;

        try (java.nio.file.DirectoryStream<Path> stream = Files.newDirectoryStream(baseTempPath)) {

            long now = System.currentTimeMillis();

            for (Path entry : stream) {

                if (Files.isDirectory(entry) && entry.getFileName().toString().startsWith("session_")) {

                    try {

                        long lastMod = Files.getLastModifiedTime(entry).toMillis();

                        if (now - lastMod > 2 * 3600 * 1000) { // Older than 2 hours

                            deleteDirectoryRecursively(entry);

                        }

                    } catch (Exception ignored) {}

                }

            }

        } catch (Exception ignored) {}

    }


    public static Path createTempDirectory(String prefix) throws IOException {

        if (!Files.exists(rootTempPath)) {

            Files.createDirectories(rootTempPath);

        }

        return Files.createTempDirectory(rootTempPath, prefix);

    }


    public static void deleteTempDirectory(Path path) {

        if (path == null) return;

        deleteDirectoryRecursively(path);

    }


    public static void cleanupCurrentSession() {

        deleteDirectoryRecursively(rootTempPath);

    }


    private static void deleteDirectoryRecursively(Path dir) {

        if (dir == null || !Files.exists(dir)) return;

        try {

            Files.walkFileTree(dir, new java.nio.file.SimpleFileVisitor<Path>() {

                @Override

                public java.nio.file.FileVisitResult visitFile(Path file, java.nio.file.attribute.BasicFileAttributes attrs) {

                    try {

                        Files.deleteIfExists(file);

                    } catch (Exception ignored) {}

                    return java.nio.file.FileVisitResult.CONTINUE;

                }

                @Override

                public java.nio.file.FileVisitResult postVisitDirectory(Path d, IOException exc) {

                    try {

                        Files.deleteIfExists(d);

                    } catch (Exception ignored) {}

                    return java.nio.file.FileVisitResult.CONTINUE;

                }

            });

        } catch (Exception e) {

            LogService.log("TEMP", "WARNING", "Failed to clean directory: " + dir + " - " + e.getMessage());

        }

    }


    /**

     * Zip Slip Protection: Normalizes path and validates it resides within target destination boundary.

     */

    public static boolean isPathSafe(Path target, Path destination) {

        try {

            Path normalizedTarget = target.toAbsolutePath().normalize();

            Path normalizedDest = destination.toAbsolutePath().normalize();

            return normalizedTarget.startsWith(normalizedDest);

        } catch (Exception e) {

            return false;

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\UserVerificationService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.BufferedReader;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.nio.charset.StandardCharsets;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.HashSet;

import java.util.Set;


public class UserVerificationService {


    private static final String USERS_RESOURCE = "/Users.txt";

    private static final String DATE_FORMAT    = "dd/MM/yyyy";


    private static volatile Date        cachedExpiryDate = null;

    private static volatile Set<String> cachedUserIds    = null;

    private static volatile boolean     parsed           = false;


    /**

     * Returns true if today's date is strictly AFTER the expiry date.

     */

    public static boolean isToolExpired() {

        ensureParsed();

        if (cachedExpiryDate == null) {

            return false;

        }

        try {

            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);

            Date today = sdf.parse(sdf.format(new Date()));

            return today.after(cachedExpiryDate);

        } catch (Exception e) {

            return false;

        }

    }


    /**

     * Returns true if current Windows user is in the authorized list.

     */

    public static boolean isCurrentUserAuthorized() {

        ensureParsed();


        String rawName   = getRawWindowsUsername();

        String shortName = extractShortId(rawName);


        LogService.log("SECURITY", "INFO", "Detected Windows Employee ID: '" + rawName + "' (resolved: '" + shortName + "')");


        if (shortName.isEmpty()) {

            LogService.log("SECURITY", "WARNING", "Could not determine Windows Employee ID. Access Denied.");

            return false;

        }


        if (cachedUserIds == null || cachedUserIds.isEmpty()) {

            LogService.log("SECURITY", "WARNING", "Users.txt is empty or could not be read. Access Denied.");

            return false;

        }


        boolean allowed = cachedUserIds.contains(rawName.toLowerCase())

                       || cachedUserIds.contains(shortName.toLowerCase());


        if (allowed) {

            LogService.log("SECURITY", "INFO", "Access GRANTED for Employee ID: '" + shortName + "'");

        } else {

            LogService.log("SECURITY", "WARNING", "Access DENIED for Employee ID: '" + rawName + "'.");

        }


        return allowed;

    }


    public static String getWindowsUsername() {

        return extractShortId(getRawWindowsUsername());

    }


    private static String getRawWindowsUsername() {

        String name = System.getProperty("user.name", "").trim();

        if (!name.isEmpty()) return name;


        name = getEnv("USERNAME");

        if (!name.isEmpty()) return name;


        return getEnv("USER");

    }


    private static String extractShortId(String raw) {

        if (raw == null) return "";

        String s = raw.trim();


        if (s.startsWith("\"") && s.endsWith("\"") && s.length() >= 2) {

            s = s.substring(1, s.length() - 1).trim();

        } else if (s.startsWith("'") && s.endsWith("'") && s.length() >= 2) {

            s = s.substring(1, s.length() - 1).trim();

        }


        int backslash = s.lastIndexOf('\\');

        if (backslash >= 0 && backslash < s.length() - 1) {

            s = s.substring(backslash + 1).trim();

        }


        int slash = s.lastIndexOf('/');

        if (slash >= 0 && slash < s.length() - 1) {

            s = s.substring(slash + 1).trim();

        }


        int at = s.indexOf('@');

        if (at > 0) {

            s = s.substring(0, at).trim();

        }


        return s;

    }


    private static synchronized void ensureParsed() {

        if (parsed) return;


        cachedUserIds = new HashSet<String>();


        InputStream is = UserVerificationService.class.getResourceAsStream(USERS_RESOURCE);

        if (is == null) {

            is = UserVerificationService.class.getClassLoader().getResourceAsStream("Users.txt");

        }


        if (is == null) {

            LogService.log("SECURITY", "ERROR", "Users.txt not found in JAR resources.");

            parsed = true;

            return;

        }


        try (BufferedReader reader = new BufferedReader(

                new InputStreamReader(is, StandardCharsets.UTF_8))) {


            String line;

            boolean firstDataLine = true;


            while ((line = reader.readLine()) != null) {

                if (line.startsWith("\uFEFF")) line = line.substring(1);

                line = line.trim();


                if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) continue;


                String[] tokens = line.split(",");


                for (int i = 0; i < tokens.length; i++) {

                    String token = tokens[i].trim();

                    if (token.startsWith("\"") && token.endsWith("\"") && token.length() >= 2) {

                        token = token.substring(1, token.length() - 1).trim();

                    } else if (token.startsWith("'") && token.endsWith("'") && token.length() >= 2) {

                        token = token.substring(1, token.length() - 1).trim();

                    }

                    if (token.isEmpty()) continue;


                    if (firstDataLine && i == 0) {

                        try {

                            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);

                            sdf.setLenient(false);

                            cachedExpiryDate = sdf.parse(token);

                            continue;

                        } catch (Exception ex) {

                            // First token is not a date; treat as Employee ID

                        }

                    }


                    String lower = token.toLowerCase();

                    cachedUserIds.add(lower);

                    String shortForm = extractShortId(lower);

                    if (!shortForm.isEmpty() && !shortForm.equals(lower)) {

                        cachedUserIds.add(shortForm);

                    }

                }


                firstDataLine = false;

            }


        } catch (Exception e) {

            LogService.log("SECURITY", "ERROR", "Failed to read Users.txt: " + e.getMessage());

        } finally {

            parsed = true;

        }

    }


    private static String getEnv(String key) {

        try {

            String val = System.getenv(key);

            return (val != null) ? val.trim() : "";

        } catch (Exception e) {

            return "";

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\services\ZipService.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.services;


import java.io.BufferedOutputStream;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.nio.file.FileVisitResult;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class ZipService {

    

    /**

     * Compresses the sourceDirectory into the targetZip file using 7-Zip (if available) or standard Java fallback.

     */

    public static void compressDirectory(String sourceDirectory, String targetZip, AtomicBoolean cancelFlag, AtomicBoolean pauseFlag, Object pauseLock) throws Exception {

        File z7 = SevenZipManager.resolve7zExecutable();

        if (z7 != null) {

            SevenZipManager.compressWith7Zip(z7.getAbsolutePath(), sourceDirectory, targetZip, cancelFlag, pauseFlag, pauseLock);

        } else {

            LogService.log("ZIP", "WARNING", "7-Zip not found on this system. Falling back to native Java ZIP compression.");

            compressWithJavaZip(sourceDirectory, targetZip, cancelFlag, pauseFlag, pauseLock);

        }

    }


    private static void compressWithJavaZip(String sourceDirectory, String targetZip, AtomicBoolean cancelFlag, AtomicBoolean pauseFlag, Object pauseLock) throws Exception {

        Path srcDir = com.imageextractor.utils.PathUtils.normalizeLongPath(sourceDirectory);

        Path targetPath = com.imageextractor.utils.PathUtils.normalizeLongPath(targetZip);

        try (FileOutputStream fos = new FileOutputStream(targetPath.toFile());

             BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);

             java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(bos)) {

            

            Files.walkFileTree(srcDir, new SimpleFileVisitor<Path>() {

                @Override

                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

                    if (cancelFlag != null && cancelFlag.get()) {

                        return FileVisitResult.TERMINATE;

                    }

                    TaskManager.checkPause();

                    if (pauseFlag != null && pauseFlag.get() && pauseLock != null) {

                        synchronized (pauseLock) {

                            try {

                                while (pauseFlag.get()) {

                                    pauseLock.wait();

                                }

                            } catch (InterruptedException e) {

                                Thread.currentThread().interrupt();

                                return FileVisitResult.TERMINATE;

                            }

                        }

                    }

                    

                    Path relativePath = srcDir.relativize(file);

                    java.util.zip.ZipEntry zipEntry = new java.util.zip.ZipEntry(relativePath.toString().replace('\\', '/'));

                    zos.putNextEntry(zipEntry);

                    Files.copy(file, zos);

                    zos.closeEntry();

                    return FileVisitResult.CONTINUE;

                }

            });

        }

        

        if (cancelFlag != null && cancelFlag.get()) {

            try {

                Files.deleteIfExists(targetPath);

            } catch (Exception ignored) {}

            throw new InterruptedException("ZIP compression cancelled by user.");

        }

    }


    public static boolean isZipFile(Path file) {

        if (file == null || file.getFileName() == null) return false;

        String name = file.getFileName().toString().toLowerCase();

        return name.endsWith(".zip") || name.endsWith(".jar");

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\MainWindow.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui;


import com.imageextractor.services.LogService;

import com.imageextractor.ui.components.BackgroundPanel;

import com.imageextractor.ui.components.FoxPanel;

import com.imageextractor.ui.components.LogPanel;

import com.imageextractor.ui.modules.*;


import javax.swing.*;

import javax.swing.border.EmptyBorder;

import java.awt.*;

import java.awt.image.BufferedImage;

import java.io.File;

import java.io.FileOutputStream;

import java.nio.channels.FileLock;


public class MainWindow extends JFrame {

    

    private static FileLock appLock;

    private static FileOutputStream lockStream;

    private TrayIcon trayIcon;


    public MainWindow() {

        // Safe Application Lock: Prevent launching multiple processing instances

        if (!lockApplication()) {

            JOptionPane.showMessageDialog(null, 

                "Another instance of the Image Processing Tool is already running!", 

                "Multiple Instances Blocked", 

                JOptionPane.WARNING_MESSAGE);

            System.exit(0);

        }


        setTitle("SNAPIX IMAGE PROCESSING UTILITY");

        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        addWindowListener(new java.awt.event.WindowAdapter() {

            @Override

            public void windowClosing(java.awt.event.WindowEvent e) {

                handleWindowClose();

            }

        });

        setSize(1020, 780);

        setLocationRelativeTo(null);

        setExtendedState(JFrame.MAXIMIZED_BOTH);

        

        initComponents();

        setupSystemTray();

    }

    

    private static boolean lockApplication() {

        try {

            File lockFile = new File(".app.lock");

            lockStream = new FileOutputStream(lockFile);

            appLock = lockStream.getChannel().tryLock();

            if (appLock != null) {

                // Clean up lock on shutdown

                Runtime.getRuntime().addShutdownHook(new Thread(() -> {

                    try {

                        if (appLock != null) appLock.release();

                        if (lockStream != null) lockStream.close();

                        lockFile.delete();

                    } catch (Exception ignored) {}

                }));

                return true;

            }

            // Lock acquisition failed (another instance running) - close stream to prevent file handle leak

            lockStream.close();

            lockStream = null;

        } catch (Exception e) {

            if (appLock != null) {

                try { appLock.release(); } catch (Exception ignored) {}

                appLock = null;

            }

            if (lockStream != null) {

                try { lockStream.close(); } catch (Exception ignored) {}

                lockStream = null;

            }

        }

        return false;

    }


    private void handleWindowClose() {

        if (trayIcon != null) {

            boolean active = com.imageextractor.services.TaskWatchdog.isRunning();

            Object[] options = {"Keep Running in Background", "Exit Application", "Cancel"};

            String message = active 

                ? "Image processing is currently active.\n\nSelect 'Keep Running in Background' to hide the window and continue processing.\nSelect 'Exit Application' to stop the process and exit immediately."

                : "Select 'Keep Running in Background' to minimize the window to the System Tray.\nSelect 'Exit Application' to close the program.";

            

            int choice = JOptionPane.showOptionDialog(

                this,

                message,

                active ? "Active Processing Detected" : "Close SNAPIX Utility",

                JOptionPane.YES_NO_CANCEL_OPTION,

                active ? JOptionPane.WARNING_MESSAGE : JOptionPane.QUESTION_MESSAGE,

                null,

                options,

                options[0]

            );

            

            if (choice == JOptionPane.YES_OPTION) {

                setVisible(false);

                showTrayNotification();

            } else if (choice == JOptionPane.NO_OPTION) {

                cleanupAndExit();

            }

        } else {

            // System tray is not supported or failed to initialize

            if (com.imageextractor.services.TaskWatchdog.isRunning()) {

                int option = JOptionPane.showConfirmDialog(

                    this,

                    "Image processing is active, but the System Tray is not supported or failed to initialize.\n" +

                    "If you close the window, processing will stop.\n\n" +

                    "Do you want to abort processing and exit now?",

                    "Confirm Abort",

                    JOptionPane.YES_NO_OPTION,

                    JOptionPane.WARNING_MESSAGE

                );

                if (option == JOptionPane.YES_OPTION) {

                    cleanupAndExit();

                }

            } else {

                cleanupAndExit();

            }

        }

    }


    private void cleanupAndExit() {

        if (trayIcon != null && SystemTray.isSupported()) {

            try {

                SystemTray.getSystemTray().remove(trayIcon);

            } catch (Exception ignored) {}

        }

        System.exit(0);

    }


    private void showTrayNotification() {

        if (trayIcon != null) {

            boolean active = com.imageextractor.services.TaskWatchdog.isRunning();

            String title = active ? "Background Mode Active" : "SNAPIX Utility Minimized";

            String msg = active 

                ? "Image processing tasks will keep running uninterrupted in the background. Right-click the tray icon to restore or exit."

                : "The application is running in the background. Right-click the tray icon to restore or exit.";

            

            trayIcon.displayMessage(title, msg, TrayIcon.MessageType.INFO);

        }

    }


    private void handleTrayExit() {

        if (com.imageextractor.services.TaskWatchdog.isRunning()) {

            // Restore window to show confirmation dialog

            setVisible(true);

            setExtendedState(JFrame.NORMAL);

            toFront();

            

            int option = JOptionPane.showConfirmDialog(

                this,

                "A processing task is currently active. Are you sure you want to exit and abort the operation?",

                "Confirm Abort",

                JOptionPane.YES_NO_OPTION,

                JOptionPane.WARNING_MESSAGE

            );

            if (option == JOptionPane.YES_OPTION) {

                cleanupAndExit();

            }

        } else {

            cleanupAndExit();

        }

    }


    private void setupSystemTray() {

        if (!SystemTray.isSupported()) return;

        

        SystemTray tray = SystemTray.getSystemTray();

        java.net.URL url = MainWindow.class.getClassLoader().getResource("FOX.gif");

        Image img = null;

        if (url != null) {

            img = new ImageIcon(url).getImage();

        } else if (new File("FOX.gif").exists()) {

            img = new ImageIcon("FOX.gif").getImage();

        } else {

            BufferedImage bi = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);

            Graphics2D g = bi.createGraphics();

            g.setColor(new Color(0, 180, 216));

            g.fillOval(2, 2, 12, 12);

            g.dispose();

            img = bi;

        }


        PopupMenu popup = new PopupMenu();

        MenuItem restoreItem = new MenuItem("Restore Tool");

        restoreItem.addActionListener(e -> {

            setVisible(true);

            setExtendedState(JFrame.MAXIMIZED_BOTH);

            toFront();

        });

        MenuItem exitItem = new MenuItem("Exit Application");

        exitItem.addActionListener(e -> handleTrayExit());

        

        popup.add(restoreItem);

        popup.addSeparator();

        popup.add(exitItem);


        trayIcon = new TrayIcon(img, "SNAPIX IMAGE PROCESSING UTILITY", popup);

        trayIcon.setImageAutoSize(true);

        trayIcon.addActionListener(e -> {

            setVisible(true);

            setExtendedState(JFrame.MAXIMIZED_BOTH);

            toFront();

        });


        try {

            tray.add(trayIcon);

        } catch (AWTException e) {

            LogService.log("UI", "WARNING", "Failed to register System Tray Icon.");

            trayIcon = null; // Mark as null so close handler knows it's not active!

        }

    }

    

    private void initComponents() {

        BackgroundPanel bgPanel = new BackgroundPanel();

        

        // Rounded Translucent Glass container for the main workspace

        JPanel tabContainer = new JPanel(new BorderLayout()) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                

                // Translucent Deep Navy Blue Glass base (60% Navy tint)

                g2.setColor(new Color(13, 27, 42, 175));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 30, 30);

                

                // Beautiful glowing translucent white outline refraction edge

                g2.setColor(new Color(255, 255, 255, 30));

                g2.setStroke(new BasicStroke(1.5f));

                g2.drawRoundRect(1, 1, getWidth() - 2, getHeight() - 2, 30, 30);

                

                g2.dispose();

                super.paintComponent(g);

            }

        };

        tabContainer.setOpaque(false);

        tabContainer.setBorder(new EmptyBorder(15, 15, 15, 15));

        tabContainer.setPreferredSize(new Dimension(850, 520));

        

        String[] modules = {

            "I. Smart Image Extraction",

            "II. Selective Image Extraction",

            "III. Image Report Center",

            "IV. ZIP Image Processor",

            "V. Batch Image Splitter",

            "VI. Universal Image Converter",

            "VII. Batch Image Renamer",

            "VIII. Image Validator"

        };

 

        // Left sidebar module list (JList)

        JList<String> sidebarList = new JList<>(modules);

        sidebarList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        sidebarList.setSelectedIndex(0);

        sidebarList.setFont(new Font("Segoe UI", Font.BOLD, 14));

        sidebarList.setOpaque(false);

        sidebarList.setBackground(new Color(0, 0, 0, 0));

        sidebarList.setForeground(new Color(200, 214, 229));

        

        // Beautiful floating curved active selection pill renderer (Gradient blue-to-neon cyan/green)

        sidebarList.setCellRenderer(new ListCellRenderer<String>() {

            @Override

            public Component getListCellRendererComponent(JList<? extends String> list, String value, int index, boolean isSelected, boolean cellHasFocus) {

                JPanel itemPanel = new JPanel(new BorderLayout()) {

                    @Override

                    protected void paintComponent(Graphics g) {

                        if (isSelected) {

                            Graphics2D g2 = (Graphics2D) g.create();

                            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                            // Glowing horizontal royal blue-to-neon cyan/green gradient selection pill matching mockup

                            GradientPaint gp = new GradientPaint(

                                0, 0, new Color(0, 119, 182, 220),   // Royal Blue

                                getWidth(), 0, new Color(0, 180, 120, 200) // Cyan Green

                            );

                            g2.setPaint(gp);

                            g2.fillRoundRect(8, 4, getWidth() - 16, getHeight() - 8, 20, 20);

                            g2.dispose();

                        }

                        super.paintComponent(g);

                    }

                };

                itemPanel.setOpaque(false);

                itemPanel.setBorder(BorderFactory.createEmptyBorder(8, 15, 8, 15));

                

                JLabel label = new JLabel(value);

                label.setFont(new Font("Segoe UI", Font.BOLD, 13));

                if (isSelected) {

                    label.setForeground(Color.WHITE);

                } else {

                    label.setForeground(new Color(175, 195, 215));

                }

                itemPanel.add(label, BorderLayout.CENTER);

                return itemPanel;

            }

        });

 

        JScrollPane sidebarScroll = new JScrollPane(sidebarList);

        sidebarScroll.setOpaque(false);

        sidebarScroll.getViewport().setOpaque(false);

        sidebarScroll.setBorder(BorderFactory.createEmptyBorder());

 

        // Sidebar Panel container to hold module links and the FoxPanel silhouetted below

        JPanel sidebarPanel = new JPanel(new BorderLayout(0, 15));

        sidebarPanel.setOpaque(false);

        sidebarPanel.setPreferredSize(new Dimension(240, 0));

        sidebarPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, new Color(255, 255, 255, 30)));

        

        sidebarPanel.add(sidebarScroll, BorderLayout.CENTER);

        

        FoxPanel foxPanel = new FoxPanel();

        foxPanel.setPreferredSize(new Dimension(240, 100)); // Beautifully scaled bottom left fox silhouette

        sidebarPanel.add(foxPanel, BorderLayout.SOUTH);

 

        JPanel cardsPanel = new JPanel(new CardLayout());

        cardsPanel.setOpaque(false);

        

        cardsPanel.add(new AllImagePanel(), modules[0]);

        cardsPanel.add(new SelectivePanel(), modules[1]);

        cardsPanel.add(new ImageExcelPanel(), modules[2]);

        cardsPanel.add(new InnerZipPanel(), modules[3]);

        cardsPanel.add(new SplitImagesPanel(), modules[4]);

        cardsPanel.add(new ImageConverterPanel(), modules[5]);

        cardsPanel.add(new ImageRenamePanel(), modules[6]);

        cardsPanel.add(new ImageValidatorPanel(), modules[7]);

        

        sidebarList.addListSelectionListener(e -> {

            if (!e.getValueIsAdjusting()) {

                CardLayout cl = (CardLayout) (cardsPanel.getLayout());

                cl.show(cardsPanel, sidebarList.getSelectedValue());

                LogService.log("Activated module: " + sidebarList.getSelectedValue());

            }

        });

        

        makeTransparent(cardsPanel);

        

        tabContainer.add(sidebarPanel, BorderLayout.WEST);

        tabContainer.add(cardsPanel, BorderLayout.CENTER);

        

        // Rigid GridBagLayout constraints for the main glass workspace (occupies main workspace)

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0; // Explicitly set column

        gbc.gridy = 0; // Middle workspace row

        gbc.insets = new Insets(110, 35, 10, 35); // Top margin 110px to dodge pre-rendered header logo in background image

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        gbc.weighty = 0.75; // Proportional height allocation

        bgPanel.add(tabContainer, gbc);

        

        // Widescreen Console spanning the full window width directly below workspace

        LogPanel logPanel = new LogPanel();

        

        // Rigid GridBagLayout constraints for the console

        GridBagConstraints gbcLog = new GridBagConstraints();

        gbcLog.gridx = 0;

        gbcLog.gridy = 1;

        gbcLog.insets = new Insets(0, 35, 6, 35); // Margins

        gbcLog.fill = GridBagConstraints.BOTH;

        gbcLog.weightx = 1.0;

        gbcLog.weighty = 0.20; // Proportional height allocation

        bgPanel.add(logPanel, gbcLog);


        // Hardware Telemetry Status Bar (CPU Usage, Memory Usage, Threads) at bottom

        com.imageextractor.ui.components.HardwareStatusBar hardwareBar = new com.imageextractor.ui.components.HardwareStatusBar();

        GridBagConstraints gbcHw = new GridBagConstraints();

        gbcHw.gridx = 0;

        gbcHw.gridy = 2;

        gbcHw.insets = new Insets(0, 35, 8, 35); // Bottom margins

        gbcHw.fill = GridBagConstraints.HORIZONTAL;

        gbcHw.weightx = 1.0;

        gbcHw.weighty = 0.0;

        bgPanel.add(hardwareBar, gbcHw);

        

        setContentPane(bgPanel);

    }

    

    private void makeTransparent(Component c) {

        if (c instanceof JPanel) {

            ((JPanel) c).setOpaque(false);

        }

        if (c instanceof Container) {

            for (Component child : ((Container) c).getComponents()) {

                makeTransparent(child);

            }

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\BackgroundPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.LogService;

import javax.imageio.ImageIO;

import javax.swing.*;

import java.awt.*;

import java.io.File;

import java.io.IOException;


public class BackgroundPanel extends JPanel {

    private Image backgroundImage;


    public BackgroundPanel() {

        try {

            java.net.URL imgUrl = getClass().getClassLoader().getResource("background.png");

            if (imgUrl == null) {

                imgUrl = getClass().getClassLoader().getResource("background.jpg");

            }

            

            if (imgUrl != null) {

                backgroundImage = new ImageIcon(imgUrl).getImage();

            } else {

                File bgFileInfo = new File("background.png");

                if (!bgFileInfo.exists()) {

                    bgFileInfo = new File("background.jpg");

                }

                if (bgFileInfo.exists()) {

                    backgroundImage = ImageIO.read(bgFileInfo);

                }

            }

        } catch (IOException e) {

            LogService.log("UI", "WARNING", "Could not load background image: " + e.getMessage());

        }

        setLayout(new GridBagLayout()); // Ensures contents stay centered

    }


    @Override

    protected void paintComponent(Graphics g) {

        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        

        // Draw the background image with cover-scaling to preserve aspect ratio without stretching

        if (backgroundImage != null) {

            int iw = backgroundImage.getWidth(this);

            int ih = backgroundImage.getHeight(this);

            if (iw > 0 && ih > 0) {

                double wRatio = (double) getWidth() / iw;

                double hRatio = (double) getHeight() / ih;

                double ratio = Math.max(wRatio, hRatio); // Fill container completely

                

                int drawW = (int) (iw * ratio);

                int drawH = (int) (ih * ratio);

                

                int drawX = (getWidth() - drawW) / 2;

                int drawY = 0; // Align top to keep header logo perfectly visible and uncropped

                

                g2d.drawImage(backgroundImage, drawX, drawY, drawW, drawH, this);

            }

        }

        

        // Premium ultra-dark navy background

        if (backgroundImage == null) {

            g2d.setColor(new Color(6, 12, 22)); // Deep Navy Black

            g2d.fillRect(0, 0, getWidth(), getHeight());

        }

        g2d.dispose();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\BlendButton.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import javax.swing.*;

import java.awt.*;


public class BlendButton extends JButton {

    private final boolean isGradient;

    

    // Smooth, premium color gradients (only blue and green)

    private static final Color GRADIENT_START = new Color(0, 119, 182);    // Royal Blue

    private static final Color GRADIENT_END = new Color(0, 180, 120);     // Emerald/Cyan Green

    

    // Dark translucent background for non-gradient premium buttons

    private static final Color DARK_BG = new Color(13, 27, 42, 175);       // Deep Navy Translucent

    private static final Color DARK_BORDER = new Color(0, 180, 216, 80);    // Thin subtle cyan bezel

    private static final Color DARK_BORDER_HOVER = new Color(0, 229, 255, 180); // Bright glowing cyan on hover


    public BlendButton(String text, boolean isGradient) {

        super(text);

        this.isGradient = isGradient;

        

        setContentAreaFilled(false);

        setFocusPainted(false);

        setBorderPainted(false);

        setOpaque(false);

        

        setFont(new Font("Segoe UI", Font.BOLD, 13));

        

        if (isGradient) {

            setForeground(Color.WHITE);

        } else {

            setForeground(new Color(175, 195, 215)); // Refined muted light ice blue

        }

        addChangeListener(e -> updateFgColor());

    }


    private void updateFgColor() {

        Color targetFg;

        if (!isEnabled()) {

            targetFg = new Color(80, 95, 110);

        } else if (isGradient || getModel().isRollover()) {

            targetFg = Color.WHITE;

        } else {

            targetFg = new Color(175, 195, 215);

        }

        if (!targetFg.equals(getForeground())) {

            super.setForeground(targetFg);

        }

    }


    @Override

    protected void paintComponent(Graphics g) {

        Graphics2D g2 = (Graphics2D) g.create();

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);


        int w = getWidth();

        int h = getHeight();


        // 1. Render Disabled State

        if (!isEnabled()) {

            g2.setColor(new Color(10, 15, 25, 130));

            g2.fillRoundRect(0, 0, w, h, 20, 20);

            

            g2.setColor(new Color(255, 255, 255, 15));

            g2.setStroke(new BasicStroke(1.0f));

            g2.drawRoundRect(0, 0, w - 1, h - 1, 20, 20);

            g2.dispose();

            super.paintComponent(g);

            return;

        }


        // 2. Render Active State (Gradient / Dark Blend)

        if (isGradient) {

            Color start = GRADIENT_START;

            Color end = GRADIENT_END;


            if (getModel().isPressed()) {

                start = start.darker();

                end = end.darker();

            } else if (getModel().isRollover()) {

                // Brighter shift on hover

                start = start.brighter();

                end = end.brighter();

            }


            GradientPaint gp = new GradientPaint(0, 0, start, w, 0, end);

            g2.setPaint(gp);

            g2.fillRoundRect(0, 0, w, h, 20, 20);


            // Refined thin reflection top edge

            g2.setColor(new Color(255, 255, 255, 55));

            g2.setStroke(new BasicStroke(1.0f));

            g2.drawRoundRect(0, 0, w - 1, h - 1, 20, 20);

        } else {

            // Dark button with thin bezel

            Color bg = DARK_BG;

            Color border = DARK_BORDER;


            if (getModel().isPressed()) {

                bg = new Color(8, 18, 28, 210);

            } else if (getModel().isRollover()) {

                bg = new Color(20, 38, 58, 190);

                border = DARK_BORDER_HOVER;

            }


            g2.setColor(bg);

            g2.fillRoundRect(0, 0, w, h, 20, 20);


            // Ultra-thin 1px bezel outline

            g2.setColor(border);

            g2.setStroke(new BasicStroke(1.0f));

            g2.drawRoundRect(0, 0, w - 1, h - 1, 20, 20);

        }


        g2.dispose();

        super.paintComponent(g);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\DestinationFolderPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.LogService;


import javax.swing.*;

import javax.swing.border.Border;

import java.awt.*;

import java.awt.dnd.DnDConstants;

import java.awt.dnd.DropTarget;

import java.awt.dnd.DropTargetDropEvent;

import java.awt.datatransfer.DataFlavor;

import java.io.File;


public class DestinationFolderPanel extends JPanel {

    private final JTextField folderField;


    public DestinationFolderPanel(String title) {

        setLayout(new BorderLayout(10, 0));

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));


        // Clean label on the left

        JLabel label = new JLabel(title + ":");

        label.setFont(new Font("Segoe UI", Font.BOLD, 12));

        label.setForeground(new Color(200, 214, 229)); // Muted light ice blue

        label.setPreferredSize(new Dimension(160, 24)); // Fixed alignment width for a clean Grid look


        // Sleek translucent text field

        folderField = new JTextField() {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180)); // Deep translucent dark navy

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);

                g2.dispose();

                super.paintComponent(g);


                // Placeholder paint when empty

                if (getText().isEmpty()) {

                    Graphics2D gPlaceholder = (Graphics2D) g.create();

                    gPlaceholder.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                    gPlaceholder.setColor(new Color(175, 195, 215, 80)); // Muted light blue/gray

                    gPlaceholder.setFont(new Font("Segoe UI", Font.ITALIC, 11));

                    gPlaceholder.drawString("Select or Drag & Drop destination folder...", 12, getHeight() / 2 + 4);

                    gPlaceholder.dispose();

                }

            }

        };

        folderField.setOpaque(false);

        folderField.setEditable(false);

        folderField.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        folderField.setForeground(new Color(0, 229, 255)); // Glowing cyan

        folderField.setCaretColor(Color.WHITE);

        folderField.setPreferredSize(new Dimension(0, 24));

        

        folderField.setBorder(new Border() {

            @Override

            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(0, 180, 216, 60)); // Thin subtle cyan border

                g2.drawRoundRect(x, y, width - 1, height - 1, 15, 15);

                g2.dispose();

            }


            @Override

            public Insets getBorderInsets(Component c) {

                return new Insets(2, 12, 2, 12);

            }


            @Override

            public boolean isBorderOpaque() {

                return false;

            }

        });


        // Compact Browse button matching mockup

        BlendButton browseBtn = new BlendButton("Browse...", false);

        browseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        browseBtn.setPreferredSize(new Dimension(100, 24));

        browseBtn.addActionListener(e -> {

            JFileChooser chooser = new JFileChooser();

            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

            if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION && chooser.getSelectedFile() != null) {

                folderField.setText(chooser.getSelectedFile().getAbsolutePath());

            }

        });


        // Robust drag and drop (DnD) support for destination folders

        class FolderDropTarget extends DropTarget {

            @Override

            public synchronized void dragEnter(java.awt.dnd.DropTargetDragEvent dtde) {

                dtde.acceptDrag(DnDConstants.ACTION_COPY);

            }


            @Override

            public synchronized void dragOver(java.awt.dnd.DropTargetDragEvent dtde) {

                dtde.acceptDrag(DnDConstants.ACTION_COPY);

            }


            @Override

            public synchronized void drop(DropTargetDropEvent evt) {

                boolean success = false;

                try {

                    evt.acceptDrop(DnDConstants.ACTION_COPY);

                    if (evt.getTransferable().isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {

                        @SuppressWarnings("unchecked")

                        java.util.List<File> droppedFiles = (java.util.List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);

                        if (droppedFiles != null && !droppedFiles.isEmpty()) {

                            File dropped = droppedFiles.get(0);

                            // If it is a directory, set it. Otherwise, set its parent directory!

                            if (dropped.isDirectory()) {

                                folderField.setText(dropped.getAbsolutePath());

                            } else {

                                File parent = dropped.getParentFile();

                                if (parent != null) {

                                    folderField.setText(parent.getAbsolutePath());

                                } else {

                                    String pStr = dropped.getParent();

                                    folderField.setText(pStr != null ? pStr : dropped.getAbsolutePath());

                                }

                            }

                            success = true;

                        }

                    }

                } catch (Exception ex) {

                    LogService.log("UI", "ERROR", "Drag and drop destination folder failed: " + ex.getMessage());

                } finally {

                    evt.dropComplete(success);

                }

            }

        }


        folderField.setDropTarget(new FolderDropTarget());

        browseBtn.setDropTarget(new FolderDropTarget());

        setDropTarget(new FolderDropTarget());


        add(label, BorderLayout.WEST);

        add(folderField, BorderLayout.CENTER);

        add(browseBtn, BorderLayout.EAST);

    }


    public String getDestinationFolder() {

        return folderField.getText();

    }

    

    public void setDestinationFolder(String path) {

        folderField.setText(path);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\ExcelFilePanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import javax.swing.*;

import javax.swing.border.Border;

import javax.swing.filechooser.FileNameExtensionFilter;

import java.awt.*;


public class ExcelFilePanel extends JPanel {

    private final JTextField fileField;


    public ExcelFilePanel(String title) {

        setLayout(new BorderLayout(10, 0));

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));


        // Clean label on the left

        String labelText = (title != null && !title.trim().isEmpty()) ? title : "Excel File Source:";

        if (!labelText.endsWith(":")) labelText += ":";

        JLabel label = new JLabel(labelText);

        label.setFont(new Font("Segoe UI", Font.BOLD, 12));

        label.setForeground(new Color(200, 214, 229)); // Muted light ice blue

        label.setPreferredSize(new Dimension(160, 24)); // Matches destination panel label width for flawless grid alignment!


        // Sleek translucent text field

        fileField = new JTextField() {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180)); // Deep translucent dark navy

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);

                g2.dispose();

                super.paintComponent(g);


                // Placeholder paint when empty

                if (getText().isEmpty()) {

                    Graphics2D gPlaceholder = (Graphics2D) g.create();

                    gPlaceholder.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                    gPlaceholder.setColor(new Color(175, 195, 215, 80)); // Muted light blue/gray

                    gPlaceholder.setFont(new Font("Segoe UI", Font.ITALIC, 11));

                    gPlaceholder.drawString("Select Excel File (Names in Column A)...", 12, getHeight() / 2 + 4);

                    gPlaceholder.dispose();

                }

            }

        };

        fileField.setOpaque(false);

        fileField.setEditable(false);

        fileField.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        fileField.setForeground(new Color(0, 229, 255)); // Glowing cyan

        fileField.setPreferredSize(new Dimension(0, 24));

        

        fileField.setBorder(new Border() {

            @Override

            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(0, 180, 216, 60)); // Thin subtle cyan border

                g2.drawRoundRect(x, y, width - 1, height - 1, 15, 15);

                g2.dispose();

            }


            @Override

            public Insets getBorderInsets(Component c) {

                return new Insets(2, 12, 2, 12);

            }


            @Override

            public boolean isBorderOpaque() {

                return false;

            }

        });


        // Compact Browse button matching mockup

        BlendButton browseBtn = new BlendButton("Browse...", false);

        browseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        browseBtn.setPreferredSize(new Dimension(100, 24));

        browseBtn.addActionListener(e -> {

            JFileChooser chooser = new JFileChooser();

            chooser.setFileFilter(new FileNameExtensionFilter("Excel Files (*.xlsx, *.xls)", "xlsx", "xls"));

            if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION && chooser.getSelectedFile() != null) {

                fileField.setText(chooser.getSelectedFile().getAbsolutePath());

            }

        });


        add(label, BorderLayout.WEST);

        add(fileField, BorderLayout.CENTER);

        add(browseBtn, BorderLayout.EAST);

    }


    public String getExcelFilePath() {

        return fileField.getText();

    }

    

    public void setExcelFilePath(String path) {

        fileField.setText(path);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\FoxPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.LogService;


import javax.swing.*;

import java.awt.*;

import java.io.File;


public class FoxPanel extends JPanel {

    private ImageIcon foxIcon;


    public FoxPanel() {

        setOpaque(false);

        setPreferredSize(new Dimension(120, 120));

        setMinimumSize(new Dimension(120, 120));

        

        try {

            java.net.URL imgUrl = getClass().getResource("/FOX.gif");

            if (imgUrl == null) {

                imgUrl = getClass().getClassLoader().getResource("FOX.gif");

            }

            Image loadedImage = null;

            if (imgUrl != null) {

                loadedImage = Toolkit.getDefaultToolkit().getImage(imgUrl);

            } else {

                File f = new File("FOX.gif");

                if (f.exists()) {

                    loadedImage = Toolkit.getDefaultToolkit().getImage(f.getAbsolutePath());

                }

            }

            

            if (loadedImage != null) {

                foxIcon = new ImageIcon(loadedImage);

            }

        } catch (Exception e) {

            LogService.log("UI", "WARNING", "Could not load FOX.gif: " + e.getMessage());

        }

    }

    

    @Override

    protected void paintComponent(Graphics g) {

        super.paintComponent(g);

        

        Graphics2D g2 = (Graphics2D) g.create();

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        

        // Translucent deep navy base

        g2.setColor(new Color(13, 27, 42, 175));

        g2.fillRoundRect(0, 0, getWidth(), getHeight(), 20, 20);

        

        // Paint the animated running fox GIF centered and scaled (zooming in on the center silhouette)

        if (foxIcon != null && foxIcon.getImage() != null) {

            int panelW = getWidth();

            int panelH = getHeight();

            

            if (panelW > 0 && panelH > 0) {

                // The actual fox silhouette in FOX.gif is centered, but has huge empty margins on all sides:

                // GIF size: 800x600, Silhouette bounds: (281, 226) to (518, 372). Width=238, Height=147

                double silhouetteW = 238.0; 

                

                // Let's target the silhouette to fill 40% of the panel's width (making it small and elegant)

                double targetFoxW = panelW * 0.40;

                

                // Scaling factor to scale the 800x600 image so the silhouette is sized perfectly

                double S = targetFoxW / silhouetteW;

                

                int drawW = (int) (800 * S);

                int drawH = (int) (600 * S);

                

                // Center the original image center (399, 299) exactly in the panel's center

                int drawX = (int) ((panelW / 2.0) - (399 * S));

                int drawY = (int) ((panelH / 2.0) - (299 * S));

                

                g2.drawImage(foxIcon.getImage(), drawX, drawY, drawW, drawH, this);

            }

        }

        

        // Glowing cyan refraction border - exactly matching the console JScrollPane

        g2.setColor(new Color(0, 180, 216, 75)); 

        g2.setStroke(new BasicStroke(1.0f));

        g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 20, 20);

        

        g2.dispose();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\HardwareStatusBar.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.TaskManager;


import javax.swing.*;

import java.awt.*;

import java.lang.management.ManagementFactory;

import java.lang.management.OperatingSystemMXBean;


public class HardwareStatusBar extends JPanel {


    private final JLabel cpuLabel;

    private final JLabel memoryLabel;

    private final JLabel threadsLabel;

    private final Timer updateTimer;

    private OperatingSystemMXBean osMXBean;

    private java.lang.reflect.Method getCpuLoadMethod;


    public HardwareStatusBar() {

        setLayout(new FlowLayout(FlowLayout.LEFT, 40, 6));

        setOpaque(false);

        setPreferredSize(new Dimension(850, 32));


        // Attempt to reflectively load getProcessCpuLoad from com.sun.management.OperatingSystemMXBean public interface

        try {

            osMXBean = ManagementFactory.getOperatingSystemMXBean();

            try {

                Class<?> osClass = Class.forName("com.sun.management.OperatingSystemMXBean");

                try {

                    getCpuLoadMethod = osClass.getMethod("getProcessCpuLoad");

                } catch (NoSuchMethodException e) {

                    try {

                        getCpuLoadMethod = osClass.getMethod("getSystemCpuLoad");

                    } catch (NoSuchMethodException ignored) {

                        getCpuLoadMethod = null;

                    }

                }

            } catch (ClassNotFoundException ignored) {

                getCpuLoadMethod = null;

            }

        } catch (Exception ignored) {

            getCpuLoadMethod = null;

        }


        // Custom styled hardware telemetry labels

        cpuLabel = createMetricLabel("CPU Usage:", "0%");

        memoryLabel = createMetricLabel("Memory Usage:", "0 MB");

        threadsLabel = createMetricLabel("Threads:", "0");


        add(cpuLabel);

        add(memoryLabel);

        add(threadsLabel);


        // 1-second dynamic update timer

        updateTimer = new Timer(1000, e -> updateTelemetry());

        updateTimer.start();


        updateTelemetry(); // Initial sample

    }


    private JLabel createMetricLabel(String title, String initialValue) {

        JLabel label = new JLabel(title + " " + initialValue);

        label.setFont(new Font("Segoe UI", Font.BOLD, 12));

        label.setForeground(new Color(180, 205, 230));

        return label;

    }


    private void updateTelemetry() {

        // 1. CPU Usage

        int cpuPercent = 0;

        if (getCpuLoadMethod != null && osMXBean != null) {

            try {

                Object val = getCpuLoadMethod.invoke(osMXBean);

                if (val instanceof Double) {

                    double load = (Double) val;

                    if (load >= 0) {

                        cpuPercent = (int) Math.round(load * 100.0);

                    }

                }

            } catch (Exception ignored) {}

        }

        

        // Realistic fallback for CPU estimation if system bean returns 0 or unavailable

        if (cpuPercent <= 0) {

            boolean isBusy = TaskManager.getTotalFiles() > 0 && !TaskManager.isPaused();

            if (isBusy) {

                int cores = Runtime.getRuntime().availableProcessors();

                cpuPercent = Math.min(95, Math.max(15, (Thread.activeCount() * 100) / (cores * 4)));

            } else {

                cpuPercent = Math.min(3, Math.max(0, Thread.activeCount() % 4));

            }

        }


        // 2. Memory Usage (Used heap RAM in MB)

        long totalMem = Runtime.getRuntime().totalMemory();

        long freeMem = Runtime.getRuntime().freeMemory();

        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);


        // 3. Thread Count

        java.lang.management.ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();

        int threadCount = (threadBean != null) ? threadBean.getThreadCount() : Thread.activeCount();


        // Update UI labels with formatted values

        cpuLabel.setText(String.format("CPU Usage: %d%%", cpuPercent));

        memoryLabel.setText(String.format("Memory Usage: %d MB", usedMemMb));

        threadsLabel.setText(String.format("Threads: %d", threadCount));

    }


    public void stop() {

        if (updateTimer != null) {

            updateTimer.stop();

        }

    }


    @Override

    public void addNotify() {

        super.addNotify();

        if (updateTimer != null && !updateTimer.isRunning()) {

            updateTimer.start();

        }

    }


    @Override

    public void removeNotify() {

        super.removeNotify();

        stop();

    }


    @Override

    protected void paintComponent(Graphics g) {

        Graphics2D g2 = (Graphics2D) g.create();

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);


        int w = getWidth();

        int h = getHeight();


        // Translucent dark bottom bar background (#040a12 with 220 alpha)

        g2.setColor(new Color(4, 10, 18, 220));

        g2.fillRoundRect(0, 0, w, h, 12, 12);


        // Subtle top glowing divider line

        g2.setColor(new Color(0, 180, 216, 40));

        g2.drawLine(0, 0, w, 0);


        g2.dispose();

        super.paintComponent(g);

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\LogPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.LogService;


import javax.swing.*;

import javax.swing.border.Border;

import java.awt.*;


public class LogPanel extends JPanel {

    private final JTextArea textArea;

    private final javax.swing.Timer logFlushTimer;


    public LogPanel() {

        setLayout(new BorderLayout(5, 5));

        setOpaque(false);

        setPreferredSize(new Dimension(600, 95));

        setMinimumSize(new Dimension(600, 95));


        textArea = new JTextArea();


        // Custom premium header row with Title on Left and sleek Clear Console button on Right

        JPanel headerPanel = new JPanel(new BorderLayout(10, 0));

        headerPanel.setOpaque(false);

        headerPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));


        JLabel titleLabel = new JLabel("Live Activity Console");

        titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        titleLabel.setForeground(new Color(0, 229, 255)); // Premium glowing cyan

        titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 2, 0));

        headerPanel.add(titleLabel, BorderLayout.WEST);


        // Thin sleek outline style button matching the user's mockup exactly

        JButton clearBtn = new JButton("Clear Console") {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                int w = getWidth();

                int h = getHeight();

                

                Color targetFg = getModel().isRollover() ? Color.WHITE : new Color(175, 195, 215);

                if (!targetFg.equals(getForeground())) {

                    super.setForeground(targetFg);

                }


                if (getModel().isPressed()) {

                    g2.setColor(new Color(13, 27, 42, 230));

                    g2.fillRoundRect(0, 0, w, h, 12, 12);

                } else if (getModel().isRollover()) {

                    g2.setColor(new Color(25, 42, 62, 180));

                    g2.fillRoundRect(0, 0, w, h, 12, 12);

                    g2.setColor(new Color(0, 229, 255, 120)); // Glowing cyan border on hover

                    g2.drawRoundRect(0, 0, w - 1, h - 1, 12, 12);

                } else {

                    g2.setColor(new Color(13, 27, 42, 120));

                    g2.fillRoundRect(0, 0, w, h, 12, 12);

                    g2.setColor(new Color(255, 255, 255, 20)); // Subtle outline

                    g2.drawRoundRect(0, 0, w - 1, h - 1, 12, 12);

                }

                g2.dispose();

                super.paintComponent(g);

            }

        };

        clearBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        clearBtn.setContentAreaFilled(false);

        clearBtn.setFocusPainted(false);

        clearBtn.setBorderPainted(false);

        clearBtn.setOpaque(false);

        clearBtn.setPreferredSize(new Dimension(110, 20));

        clearBtn.addActionListener(e -> textArea.setText(""));

        headerPanel.add(clearBtn, BorderLayout.EAST);


        add(headerPanel, BorderLayout.NORTH);



        textArea.setEditable(false);

        textArea.setBackground(new Color(6, 15, 27, 220)); // Deep translucent carbon navy

        textArea.setForeground(new Color(40, 180, 120));   // Glowing emerald green log text

        textArea.setFont(new Font("Consolas", Font.PLAIN, 12));

        textArea.setLineWrap(true);

        textArea.setWrapStyleWord(true);

        textArea.setBorder(BorderFactory.createEmptyBorder(4, 12, 4, 12));


        JScrollPane scrollPane = new JScrollPane(textArea) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 220));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 20, 20);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        scrollPane.setOpaque(false);

        scrollPane.getViewport().setOpaque(false);

        

        // Custom elegant thin 1px rounded border matching FoxPanel

        scrollPane.setBorder(new Border() {

            @Override

            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(0, 180, 216, 75)); // Thin glowing cyan bezel

                g2.setStroke(new BasicStroke(1.0f));

                g2.drawRoundRect(x, y, width - 1, height - 1, 20, 20);

                g2.dispose();

            }


            @Override

            public Insets getBorderInsets(Component c) {

                return new Insets(1, 1, 1, 1);

            }


            @Override

            public boolean isBorderOpaque() {

                return false;

            }

        });


        add(scrollPane, BorderLayout.CENTER);


        // Thread-safe buffer for console log messages

        final java.util.Queue<String> logQueue = new java.util.concurrent.ConcurrentLinkedQueue<>();


        // 300ms Swing Timer to safely batch and flush log messages on the EDT

        logFlushTimer = new javax.swing.Timer(300, ev -> {

            if (logQueue.isEmpty()) return;

            

            StringBuilder sb = new StringBuilder();

            String item;

            while ((item = logQueue.poll()) != null) {

                sb.append(item);

            }

            

            if (sb.length() > 0) {

                // Append batched logs in a single update

                textArea.append(sb.toString());


                // Limit console size to prevent memory leaks (keep last 500 lines) safely inside the EDT

                int lineCount = textArea.getLineCount();

                if (lineCount > 500) {

                    try {

                        int linesToRemove = lineCount - 500;

                        int offset = textArea.getLineEndOffset(linesToRemove - 1);

                        textArea.replaceRange("", 0, offset);

                    } catch (Exception ex) {

                        // Ignore

                    }

                }


                // Auto-scroll to bottom safely inside the EDT

                textArea.setCaretPosition(textArea.getDocument().getLength());

            }

        });

        logFlushTimer.start();


        // Wire up the global listener

        LogService.setListener(msg -> {

            // LogService already schedules invokeLater, simply queue message

            logQueue.add(msg);

        });


        LogService.log("System Initialized. Awaiting operations...");

    }


    public void stop() {

        if (logFlushTimer != null) {

            logFlushTimer.stop();

        }

    }


    @Override

    public void removeNotify() {

        super.removeNotify();

        LogService.setListener(null);

        stop();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\OutputFormatPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import javax.swing.*;

import java.awt.*;


public class OutputFormatPanel extends JPanel {

    private JRadioButton zipFolderRadio;


    public OutputFormatPanel() {

        setLayout(new BorderLayout(10, 0));

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));

        

        JLabel label = new JLabel("Output Format:");

        label.setFont(new Font("Segoe UI", Font.BOLD, 12));

        label.setForeground(new Color(200, 214, 229));

        label.setPreferredSize(new Dimension(160, 24));

        

        JPanel radioWrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));

        radioWrapper.setOpaque(false);

        

        JRadioButton openFolderRadio = new JRadioButton("Open Folder", true);

        openFolderRadio.setOpaque(false);

        openFolderRadio.setForeground(Color.WHITE);

        openFolderRadio.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        

        zipFolderRadio = new JRadioButton("Zip File (.zip)");

        zipFolderRadio.setOpaque(false);

        zipFolderRadio.setForeground(Color.WHITE);

        zipFolderRadio.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        

        ButtonGroup bg = new ButtonGroup();

        bg.add(openFolderRadio);

        bg.add(zipFolderRadio);

        

        radioWrapper.add(openFolderRadio);

        radioWrapper.add(zipFolderRadio);

        

        add(label, BorderLayout.WEST);

        add(radioWrapper, BorderLayout.CENTER);

    }


    public boolean isZipOutput() {

        return zipFolderRadio.isSelected();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\SectionPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import javax.swing.*;

import javax.swing.border.EmptyBorder;

import java.awt.*;


public class SectionPanel extends JPanel {

    private final JLabel titleLabel;

    private final JLabel statusLabel;

    private final JPanel contentPanel;


    public SectionPanel(String title, String icon, String statusText) {

        setLayout(new BorderLayout(5, 2));

        setOpaque(false);

        setBorder(new EmptyBorder(5, 8, 5, 8));


        JPanel headerPanel = new JPanel(new BorderLayout(10, 0));

        headerPanel.setOpaque(false);


        titleLabel = new JLabel((icon != null && !icon.isEmpty() ? icon + " " : "") + title);

        titleLabel.setFont(new Font("Segoe UI", Font.BOLD, 13));

        titleLabel.setForeground(new Color(200, 214, 229)); // Muted light ice blue


        statusLabel = new JLabel(statusText != null ? statusText : "");

        statusLabel.setFont(new Font("Segoe UI", Font.PLAIN, 11));

        statusLabel.setForeground(new Color(175, 195, 215, 150));


        headerPanel.add(titleLabel, BorderLayout.WEST);

        

        JPanel rightWrapper = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));

        rightWrapper.setOpaque(false);

        rightWrapper.add(statusLabel);

        headerPanel.add(rightWrapper, BorderLayout.EAST);


        // Thin elegant divider JSeparator line

        JSeparator divider = new JSeparator() {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(255, 255, 255, 20)); // Thin 20% transparent white

                g2.drawLine(0, 0, getWidth(), 0);

                g2.dispose();

            }

        };

        divider.setPreferredSize(new Dimension(0, 1));

        divider.setOpaque(false);


        JPanel northPanel = new JPanel(new BorderLayout(0, 8));

        northPanel.setOpaque(false);

        northPanel.add(headerPanel, BorderLayout.CENTER);

        northPanel.add(divider, BorderLayout.SOUTH);


        add(northPanel, BorderLayout.NORTH);


        contentPanel = new JPanel(new BorderLayout(0, 0));

        contentPanel.setOpaque(false);

        contentPanel.setBorder(new EmptyBorder(8, 5, 5, 5));

        add(contentPanel, BorderLayout.CENTER);

    }


    public void setStatus(String text) {

        statusLabel.setText(text);

    }


    public void setHeaderRightComponent(Component comp) {

        // Replace right side header element with custom component

        if (titleLabel.getParent() instanceof JPanel) {

            JPanel hp = (JPanel) titleLabel.getParent();

            if (hp.getLayout() instanceof BorderLayout) {

                Component old = ((BorderLayout) hp.getLayout()).getLayoutComponent(BorderLayout.EAST);

                if (old != null) hp.remove(old);

            }

            hp.add(comp, BorderLayout.EAST);

            hp.revalidate();

            hp.repaint();

        }

    }


    public JPanel getContentPanel() {

        return contentPanel;

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\components\SourceFoldersPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.components;


import com.imageextractor.services.LogService;


import javax.swing.*;

import java.awt.*;

import java.awt.dnd.DnDConstants;

import java.awt.dnd.DropTarget;

import java.awt.dnd.DropTargetDropEvent;

import java.awt.datatransfer.DataFlavor;

import java.io.File;

import java.util.ArrayList;

import java.util.List;

import javax.swing.filechooser.FileFilter;


public class SourceFoldersPanel extends JPanel {

    private final DefaultListModel<String> listModel;

    private final JList<String> folderList;

    private static final int MAX_FOLDERS = 30;


    public SourceFoldersPanel() {

        setLayout(new BorderLayout(15, 0));

        setOpaque(false);


        listModel = new DefaultListModel<>();

        folderList = new JList<>(listModel);

        folderList.setOpaque(false);

        folderList.setBackground(new Color(0, 0, 0, 0));

        folderList.setForeground(new Color(0, 229, 255)); // Glowing cyan elements

        folderList.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        folderList.setSelectionBackground(new Color(0, 180, 216, 50));

        folderList.setSelectionForeground(Color.WHITE);


        JScrollPane scrollPane = new JScrollPane(folderList) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                int w = getWidth();

                int h = getHeight();


                if (listModel.isEmpty()) {

                    // Transparent deep dark container background

                    g2.setColor(new Color(6, 15, 27, 180));

                    g2.fillRoundRect(0, 0, w, h, 16, 16);


                    // Sleek thin dashed rounded outline

                    float[] dash = {6.0f, 4.0f};

                    Stroke dashed = new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);

                    g2.setStroke(dashed);

                    g2.setColor(new Color(255, 255, 255, 25)); // 25% transparent white/cyan dashed line

                    g2.drawRoundRect(1, 1, w - 2, h - 2, 16, 16);


                    // Centered vector folder silhouette

                    int fx = (w - 28) / 2;

                    int fy = Math.max(4, h / 2 - 22);

                    g2.setColor(new Color(0, 180, 216, 120));

                    g2.setStroke(new BasicStroke(1.8f));

                    g2.drawRoundRect(fx, fy, 28, 16, 4, 4);

                    int[] tabX = {fx + 2, fx + 9, fx + 13, fx + 2};

                    int[] tabY = {fy, fy - 4, fy - 4, fy};

                    g2.drawPolygon(tabX, tabY, 4);


                    g2.setFont(new Font("Segoe UI", Font.BOLD, 11));

                    g2.setColor(new Color(175, 195, 215, 220));

                    FontMetrics fm = g2.getFontMetrics();

                    String line1 = "No sources added yet";

                    int line1Y = Math.max(fy + 28, h / 2 + 4);

                    g2.drawString(line1, (w - fm.stringWidth(line1)) / 2, line1Y);


                    g2.setFont(new Font("Segoe UI", Font.PLAIN, 10));

                    g2.setColor(new Color(175, 195, 215, 140));

                    fm = g2.getFontMetrics();

                    String line2 = "Click '+ Add Source(s)' or drag & drop folders / zip files here";

                    int line2Y = line1Y + 14;

                    if (line2Y < h - 2) {

                        g2.drawString(line2, (w - fm.stringWidth(line2)) / 2, line2Y);

                    }

                } else {

                    // Sleek dark translucent container base

                    g2.setColor(new Color(6, 15, 27, 220));

                    g2.fillRoundRect(0, 0, w, h, 16, 16);


                    // 1px subtle glowing border

                    g2.setColor(new Color(0, 180, 216, 60));

                    g2.setStroke(new BasicStroke(1.0f));

                    g2.drawRoundRect(0, 0, w - 1, h - 1, 16, 16);

                }

                g2.dispose();

                super.paintComponent(g);

            }

        };

        scrollPane.setOpaque(false);

        scrollPane.getViewport().setOpaque(false);

        scrollPane.setPreferredSize(new Dimension(380, 80));

        scrollPane.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));


        // Listener to force repaint empty state on item changes

        listModel.addListDataListener(new javax.swing.event.ListDataListener() {

            @Override public void intervalAdded(javax.swing.event.ListDataEvent e) { scrollPane.repaint(); }

            @Override public void intervalRemoved(javax.swing.event.ListDataEvent e) { scrollPane.repaint(); }

            @Override public void contentsChanged(javax.swing.event.ListDataEvent e) { scrollPane.repaint(); }

        });


        folderList.setDropTarget(new DropTarget() {

            @Override

            public void drop(DropTargetDropEvent evt) {

                boolean success = false;

                try {

                    if (evt.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {

                        evt.acceptDrop(DnDConstants.ACTION_COPY);

                        @SuppressWarnings("unchecked")

                        List<File> droppedFiles = (List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);

                        for (File file : droppedFiles) {

                            if (listModel.getSize() >= MAX_FOLDERS) break;

                            if (file.isDirectory() || file.getName().toLowerCase().endsWith(".zip")) {

                                if (!listModel.contains(file.getAbsolutePath())) {

                                    listModel.addElement(file.getAbsolutePath());

                                }

                            }

                        }

                        success = true;

                    } else {

                        evt.rejectDrop();

                    }

                } catch (Exception ex) {

                    LogService.log("UI", "ERROR", "Drag and drop source folders failed: " + ex.getMessage());

                } finally {

                    evt.dropComplete(success);

                }

            }

        });


        add(scrollPane, BorderLayout.CENTER);

    }


    public JPanel getHeaderButtonsPanel() {

        JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));

        btnPanel.setOpaque(false);


        BlendButton addBtn = new BlendButton("+ Add Source(s)", true); // Bright green gradient button

        BlendButton removeBtn = new BlendButton("Remove Selected", false); // Outline button

        BlendButton clearBtn = new BlendButton("Clear All", false); // Outline button


        addBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        removeBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        clearBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));


        addBtn.setPreferredSize(new Dimension(125, 22));

        removeBtn.setPreferredSize(new Dimension(125, 22));

        clearBtn.setPreferredSize(new Dimension(90, 22));


        addBtn.addActionListener(e -> {

            if (listModel.getSize() >= MAX_FOLDERS) {

                JOptionPane.showMessageDialog(this, "Maximum 30 sources allowed.", "Limit Reached", JOptionPane.WARNING_MESSAGE);

                return;

            }

            JFileChooser chooser = new JFileChooser();

            chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

            chooser.setMultiSelectionEnabled(true);

            chooser.setFileFilter(new FileFilter() {

                @Override

                public boolean accept(File f) {

                    return f.isDirectory() || f.getName().toLowerCase().endsWith(".zip");

                }


                @Override

                public String getDescription() {

                    return "Folders and ZIP files";

                }

            });

            if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION && chooser.getSelectedFiles() != null) {

                for (File file : chooser.getSelectedFiles()) {

                    if (file != null && listModel.getSize() < MAX_FOLDERS && !listModel.contains(file.getAbsolutePath())) {

                        listModel.addElement(file.getAbsolutePath());

                    }

                }

            }

        });


        removeBtn.addActionListener(e -> {

            List<String> selected = folderList.getSelectedValuesList();

            for (String val : selected) {

                listModel.removeElement(val);

            }

        });


        clearBtn.addActionListener(e -> listModel.clear());


        btnPanel.add(addBtn);

        btnPanel.add(removeBtn);

        btnPanel.add(clearBtn);


        return btnPanel;

    }


    public List<String> getSourceFolders() {

        List<String> folders = new ArrayList<>();

        for (int i = 0; i < listModel.size(); i++) {

            folders.add(listModel.getElementAt(i));

        }

        return folders;

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\AllImagePanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.ZipService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.services.TempManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.OutputFormatPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;


import javax.swing.*;

import java.awt.*;

import java.io.File;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class AllImagePanel extends JPanel {

    private SourceFoldersPanel sourcePanel;

    private DestinationFolderPanel destPanel;

    private OutputFormatPanel formatPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private JButton startBtn;

    private JButton pauseBtn;

    private JButton stopBtn;

    private JProgressBar progressBar;

    

    private AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public AllImagePanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.50;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        formatPanel = new OutputFormatPanel();

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(2, 5, 2, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(formatPanel, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.30;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);

        

        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeExtraction();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(formatPanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("All Image Extraction: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("All Image Extraction: Process resumed.");

    }

    

    private void executeExtraction() {

        if (isProcessing.get()) return;

        java.util.List<String> src = sourcePanel.getSourceFolders();

        String dest = destPanel.getDestinationFolder();

        

        if (src.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please select source folders and a destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        boolean isZip = formatPanel.isZipOutput();

        

        isProcessing.set(true);

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("All Image Extraction: Starting process...");

        LogService.log("All Image Extraction: Sources -> " + src);

        LogService.log("All Image Extraction: Destination -> " + dest);

        LogService.log("All Image Extraction: ZIP Output -> " + (isZip ? "Yes" : "No"));

        LogService.log("All Image Extraction: Scanning source(s) for images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        setInputsEnabled(false);

        

        worker = new SwingWorker<Integer, Long>() {

            @Override

            protected Integer doInBackground() throws Exception {

                KeepAwakeService.start();

                TaskWatchdog.start(cancelFlag);

                

                TaskManager.resetTelemetry();

                TaskManager.setCurrentOperation("Scanning Sources");

                

                final long[] count = {0};

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                

                TaskManager.setTotalFiles(totalFiles);

                TaskManager.setCurrentOperation("Extracting Images");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("All Image Extraction: Progress %d/%d files (Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                String tempPath = dest;

                if (isZip) {

                    tempPath = dest + File.separator + "temp_all_images_" + System.currentTimeMillis();

                    try {

                        Files.createDirectories(Paths.get(tempPath));

                    } catch (Exception ex) {

                        LogService.log("EXTRACT", "ERROR", "Failed to create temp directory for ZIP creation: " + ex.getMessage());

                        return 0;

                    }

                }

                

                final String finalDest = tempPath;

                int extracted = ImageExtractionService.extractAllImagesStreamed(

                        src, 

                        finalDest, 

                        p -> {}, 

                        cancelFlag

                );

                

                try {

                    if (isZip && !cancelFlag.get() && extracted > 0) {

                        SwingUtilities.invokeLater(() -> {

                            statusDotLabel.setText("● Compressing output ZIP archive...");

                            progressBar.setIndeterminate(true);

                        });

                        String zipPath = dest + File.separator + "All_Images_Output.zip";

                        ZipService.compressDirectory(tempPath, zipPath, cancelFlag, pauseFlag, null);

                    }

                } catch (Exception e) {

                    LogService.log("COMPRESSION", "ERROR", "Failed to compress output folder: " + tempPath);

                } finally {

                    if (isZip) {

                        TempManager.deleteTempDirectory(Paths.get(tempPath));

                    }

                }

                if (!cancelFlag.get() && extracted > 0) {

                    if (isZip) {

                        LogService.log("Validation: Output generated as ZIP archive successfully.");

                    } else {

                        try (java.util.stream.Stream<Path> stream = Files.walk(Paths.get(dest))) {

                            long finalCount = stream.filter(ImageExtractionService::isImageFile).count();

                            LogService.log(String.format("Validation Complete: Extracted %d images. Target folder now contains %d total images. Data Integrity 100%%.", extracted, finalCount));

                        } catch (Exception ignored) {}

                    }

                }

                return extracted;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int count = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("All Image Extraction: Process stopped by user.");

                    } else if (count == 0) {

                        LogService.log("All Image Extraction: Process finished. No images were found.");

                    } else {

                        LogService.log("All Image Extraction: Process completed! Extracted " + count + " images.");

                    }

                    

                    JOptionPane.showMessageDialog(AllImagePanel.this, msg + "Extracted " + count + " images.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    JOptionPane.showMessageDialog(AllImagePanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\ImageConverterPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ExcelService;

import com.imageextractor.services.ImageConversionService;

import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import javax.swing.border.Border;

import java.awt.*;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.List;

import java.util.concurrent.atomic.AtomicBoolean;


public class ImageConverterPanel extends JPanel {

    private final SourceFoldersPanel sourcePanel;

    private final DestinationFolderPanel destPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    

    private final JRadioButton formatRadio;

    private final JRadioButton sizeRadio;

    private final JRadioButton bothRadio;

    

    private final JComboBox<String> formatCombo;

    private final JTextField targetSizeField;

    private final JComboBox<String> unitCombo;

    

    private final JButton startBtn;

    private final JButton pauseBtn;

    private final JButton stopBtn;

    private final JProgressBar progressBar;

    

    private SwingWorker<Void, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;

    private final AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private final AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    

    public ImageConverterPanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.40;

        gbc.insets = new Insets(0, 0, 10, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        // Dynamic controls for Converter vs Resizer vs Both

        JPanel radioPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));

        radioPanel.setOpaque(false);

        JLabel modeLabel = new JLabel("Task Mode:");

        modeLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        modeLabel.setForeground(new Color(200, 214, 229));

        

        formatRadio = new JRadioButton("Convert Format Only");

        sizeRadio = new JRadioButton("Optimize File Size Only");

        bothRadio = new JRadioButton("Convert & Optimize Size");

        bothRadio.setSelected(true);

        

        formatRadio.setOpaque(false); formatRadio.setForeground(Color.WHITE); formatRadio.setFont(new Font("Segoe UI", Font.BOLD, 11));

        sizeRadio.setOpaque(false); sizeRadio.setForeground(Color.WHITE); sizeRadio.setFont(new Font("Segoe UI", Font.BOLD, 11));

        bothRadio.setOpaque(false); bothRadio.setForeground(Color.WHITE); bothRadio.setFont(new Font("Segoe UI", Font.BOLD, 11));

        

        ButtonGroup group = new ButtonGroup();

        group.add(formatRadio); group.add(sizeRadio); group.add(bothRadio);

        

        radioPanel.add(modeLabel);

        radioPanel.add(formatRadio);

        radioPanel.add(sizeRadio);

        radioPanel.add(bothRadio);

        

        JPanel comboGrid = new JPanel(new GridLayout(1, 2, 20, 0));

        comboGrid.setOpaque(false);

        

        JPanel leftComboPanel = new JPanel(new BorderLayout(10, 0));

        leftComboPanel.setOpaque(false);

        JLabel formatLabel = new JLabel("Target Format:");

        formatLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        formatLabel.setForeground(new Color(200, 214, 229));

        formatLabel.setPreferredSize(new Dimension(160, 24));

        formatCombo = new JComboBox<>(new String[]{"JPG", "PNG", "GIF", "BMP", "WEBP", "TIFF"});

        formatCombo.setFont(new Font("Segoe UI", Font.BOLD, 11));

        formatCombo.setPreferredSize(new Dimension(0, 24));

        leftComboPanel.add(formatLabel, BorderLayout.WEST);

        leftComboPanel.add(formatCombo, BorderLayout.CENTER);

        

        JPanel rightComboPanel = new JPanel(new BorderLayout(10, 0));

        rightComboPanel.setOpaque(false);

        JLabel sizeLabel = new JLabel("Target File Size:");

        sizeLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        sizeLabel.setForeground(new Color(200, 214, 229));

        sizeLabel.setPreferredSize(new Dimension(160, 24));

        

        JPanel sizeInputWrapper = new JPanel(new BorderLayout(5, 0));

        sizeInputWrapper.setOpaque(false);

        targetSizeField = createSleekTextField("250");

        targetSizeField.setHorizontalAlignment(JTextField.CENTER);

        unitCombo = new JComboBox<>(new String[]{"KB", "MB"});

        unitCombo.setFont(new Font("Segoe UI", Font.BOLD, 11));

        unitCombo.setPreferredSize(new Dimension(65, 24));

        sizeInputWrapper.add(targetSizeField, BorderLayout.CENTER);

        sizeInputWrapper.add(unitCombo, BorderLayout.EAST);

        rightComboPanel.add(sizeLabel, BorderLayout.WEST);

        rightComboPanel.add(sizeInputWrapper, BorderLayout.CENTER);

        

        comboGrid.add(leftComboPanel);

        comboGrid.add(rightComboPanel);

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(1, 5, 1, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(radioPanel, gbcOut);

        gbcOut.gridy = 1;

        gbcOut.insets = new Insets(4, 5, 4, 5);

        outSettingsContent.add(comboGrid, gbcOut);

        gbcOut.gridy = 2;

        gbcOut.insets = new Insets(1, 5, 1, 5);

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 3;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.28;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Toggle Logic

        java.awt.event.ActionListener toggleListener = e -> {

            boolean formatEnabled = formatRadio.isSelected() || bothRadio.isSelected();

            boolean sizeEnabled = sizeRadio.isSelected() || bothRadio.isSelected();

            formatCombo.setEnabled(formatEnabled);

            targetSizeField.setEnabled(sizeEnabled);

            unitCombo.setEnabled(sizeEnabled);

        };

        formatRadio.addActionListener(toggleListener);

        sizeRadio.addActionListener(toggleListener);

        bothRadio.addActionListener(toggleListener);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeConversion();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private JTextField createSleekTextField(String text) {

        JTextField tf = new JTextField(text) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        tf.setOpaque(false);

        tf.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        tf.setForeground(new Color(0, 229, 255));

        tf.setCaretColor(Color.WHITE);

        tf.setPreferredSize(new Dimension(0, 24));

        

        tf.setBorder(new Border() {

            @Override

            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(0, 180, 216, 60));

                g2.drawRoundRect(x, y, width - 1, height - 1, 15, 15);

                g2.dispose();

            }

            @Override public Insets getBorderInsets(Component c) { return new Insets(2, 12, 2, 12); }

            @Override public boolean isBorderOpaque() { return false; }

        });

        return tf;

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        formatRadio.setEnabled(enabled);

        sizeRadio.setEnabled(enabled);

        bothRadio.setEnabled(enabled);

        chkLowMemory.setEnabled(enabled);

        

        boolean formatEnabled = enabled && (formatRadio.isSelected() || bothRadio.isSelected());

        boolean sizeEnabled = enabled && (sizeRadio.isSelected() || bothRadio.isSelected());

        formatCombo.setEnabled(formatEnabled);

        targetSizeField.setEnabled(sizeEnabled);

        unitCombo.setEnabled(sizeEnabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Image Converter: Process paused.");

    }

    

    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Image Converter: Process resumed.");

    }

    

    private void executeConversion() {

        if (isProcessing.get()) return;

        List<String> sources = sourcePanel.getSourceFolders();

        String dest = destPanel.getDestinationFolder();

        

        if (sources.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please select at least one source and a destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        String targetFormat = null;

        long targetBytes = 0;

        

        if (formatRadio.isSelected() || bothRadio.isSelected()) {

            targetFormat = (String) formatCombo.getSelectedItem();

        }

        

        if (sizeRadio.isSelected() || bothRadio.isSelected()) {

            String targetSizeStr = targetSizeField.getText().trim();

            if (targetSizeStr.isEmpty()) {

                JOptionPane.showMessageDialog(this, "Please enter a target file size.", "Error", JOptionPane.ERROR_MESSAGE);

                return;

            }

            try {

                double size = Double.parseDouble(targetSizeStr);

                if (size <= 0) throw new NumberFormatException();

                String unit = (String) unitCombo.getSelectedItem();

                targetBytes = (long) (size * ("MB".equals(unit) ? 1024 * 1024 : 1024));

            } catch (NumberFormatException e) {

                JOptionPane.showMessageDialog(this, "Please enter a valid positive number for target size.", "Error", JOptionPane.ERROR_MESSAGE);

                return;

            }

        }

        

        final String finalTargetFormat = targetFormat;

        final long finalTargetBytes = targetBytes;

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Image Converter: Starting process...");

        LogService.log("Image Converter: Sources -> " + sources);

        LogService.log("Image Converter: Destination -> " + dest);

        LogService.log("Image Converter: Target Format -> " + (finalTargetFormat != null ? finalTargetFormat : "N/A"));

        LogService.log("Image Converter: Target Size Limit -> " + (finalTargetBytes > 0 ? (finalTargetBytes / 1024) + " KB" : "N/A"));

        LogService.log("Image Converter: Scanning source(s) for images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Void, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Void doInBackground() throws Exception {

                final long[] count = {0};

                for (String folderPath : sources) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Image Converter: Scan complete. Found " + totalFiles + " images to process.");

                

                if (totalFiles == 0 || cancelFlag.get()) return null;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Image Converter: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                List<ImageConversionService.ConversionResult> results = ImageConversionService.convertImagesStreamed(sources, dest, finalTargetFormat, finalTargetBytes,

                    p -> {}, cancelFlag);

                

                if (!cancelFlag.get() && !results.isEmpty()) {

                    SwingUtilities.invokeLater(() -> {

                        statusDotLabel.setText("● Generating Conversion Excel Report...");

                        progressBar.setIndeterminate(true);

                    });

                    

                    String reportPath = Paths.get(dest).resolve("Conversion_Report_" + System.currentTimeMillis() + ".xlsx").toString();

                    ExcelService.generateConversionReport(results, reportPath, 

                        progress -> {}, cancelFlag);

                }

                

                return null;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    get();

                    

                    if (cancelFlag.get()) {

                        progressBar.setValue(0);

                        LogService.log("Image Converter: Process stopped by user.");

                        JOptionPane.showMessageDialog(ImageConverterPanel.this, "Conversion Process stopped. Report generated successfully.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                    } else if (totalFiles == 0) {

                        progressBar.setValue(0);

                        LogService.log("Image Converter: Process finished. No images were found.");

                        JOptionPane.showMessageDialog(ImageConverterPanel.this, "No images found to convert.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                    } else {

                        progressBar.setValue(progressBar.getMaximum());

                        LogService.log("Image Converter: Process completed! Images converted and report generated successfully.");

                        JOptionPane.showMessageDialog(ImageConverterPanel.this, "Images converted and report generated successfully!", "Success", JOptionPane.INFORMATION_MESSAGE);

                    }

                } catch (Exception ex) {

                    progressBar.setValue(0);

                    LogService.log("Image Converter Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(ImageConverterPanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    progressBar.setIndeterminate(false);

                    setInputsEnabled(true);

                    TaskManager.setPaused(false);

                    pauseFlag.set(false);

                }

            }

        };

        

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\ImageExcelPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.ZipService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.services.TempManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.OutputFormatPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import java.awt.*;

import java.io.File;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class ImageExcelPanel extends JPanel {

    private SourceFoldersPanel sourcePanel;

    private DestinationFolderPanel destPanel;

    private OutputFormatPanel formatPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private JButton startBtn;

    private JButton pauseBtn;

    private JButton stopBtn;

    private JProgressBar progressBar;

    

    private AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public ImageExcelPanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.50;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        formatPanel = new OutputFormatPanel();

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(2, 5, 2, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(formatPanel, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.30;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeGeneration();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(formatPanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Image Excel Report: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Image Excel Report: Process resumed.");

    }

    

    private void executeGeneration() {

        if (isProcessing.get()) return;

        java.util.List<String> src = new java.util.ArrayList<>(sourcePanel.getSourceFolders());

        src.sort(String.CASE_INSENSITIVE_ORDER);

        String dest = destPanel.getDestinationFolder();

        

        if (src.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please provide source folders and a destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        boolean isZip = formatPanel.isZipOutput();

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Image Excel Report: Starting process...");

        LogService.log("Image Excel Report: Sources -> " + src);

        LogService.log("Image Excel Report: Destination -> " + dest);

        LogService.log("Image Excel Report: ZIP Output -> " + (isZip ? "Yes" : "No"));

        LogService.log("Image Excel Report: Scanning source(s) for images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Integer, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Integer doInBackground() throws Exception {

                totalFiles = 0;

                final long[] counts = new long[src.size()];

                int idx = 0;

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    final int fIdx = idx;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    counts[fIdx]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                    totalFiles += counts[idx];

                    idx++;

                }

                

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Image Excel Report: Scan complete. Found " + totalFiles + " images to document.");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Image Excel Report: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                String tempPath = dest;

                if (isZip) {

                    tempPath = dest + File.separator + "temp_excel_" + System.currentTimeMillis();

                    try {

                        Files.createDirectories(Paths.get(tempPath));

                    } catch (Exception ex) {

                        LogService.log("EXCEL", "ERROR", "Failed to create temp excel folder: " + ex.getMessage());

                        return 0;

                    }

                }

                

                int totalGenerated = 0;

                long totalProcessed = 0;

                idx = 0;

                for (String folderPath : src) {

                    if (cancelFlag != null && cancelFlag.get()) break;

                    

                    Path srcP = Paths.get(folderPath);

                    String folderName = srcP.getFileName() != null ? srcP.getFileName().toString().replaceAll("[\\\\/:*?\"<>|]", "_") : "Root_Drive";

                    String baseReportName = folderName + (src.size() > 1 ? "_" + (idx + 1) : "") + "_images";

                    File exportFile = new File(tempPath, baseReportName + ".xlsx");

                    int colId = 1;

                    while (exportFile.exists()) {

                        exportFile = new File(tempPath, baseReportName + "_" + colId + ".xlsx");

                        colId++;

                    }

                    String exportPath = exportFile.getAbsolutePath();

                    com.imageextractor.services.ExcelService.generateImageExcelStreamed(

                            folderPath, exportPath, p -> TaskManager.setProcessedCount(p), cancelFlag, totalProcessed

                    );

                    

                    if (cancelFlag != null && cancelFlag.get()) break;

                    totalProcessed += counts[idx]; 

                    totalGenerated++;

                    idx++;

                }

                

                try {

                    if (isZip && !cancelFlag.get() && totalGenerated > 0) {

                        SwingUtilities.invokeLater(() -> {

                            statusDotLabel.setText("● Compressing output ZIP archive...");

                            progressBar.setIndeterminate(true);

                        });

                        String zipPath = dest + File.separator + "Excel_Reports.zip";

                        ZipService.compressDirectory(tempPath, zipPath, cancelFlag, pauseFlag, null);

                    }

                } catch (Exception e) {

                    LogService.log("COMPRESSION", "ERROR", "Failed to compress output folder: " + tempPath);

                } finally {

                    if (isZip) {

                        try {

                            TempManager.deleteTempDirectory(Paths.get(tempPath));

                        } catch (Exception ex) {

                            LogService.log("EXCEL", "WARNING", "Temp cleanup warning: " + ex.getMessage());

                        }

                    }

                }

                return totalGenerated;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int count = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("Image Excel Report: Process stopped by user.");

                    } else if (count == 0) {

                        LogService.log("Image Excel Report: Process finished. No images were found.");

                    } else {

                        LogService.log("Image Excel Report: Process completed! Generated " + count + " dedicated Excel reports.");

                    }

                    

                    JOptionPane.showMessageDialog(ImageExcelPanel.this, msg + "Generated " + count + " dedicated Excel reports.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    LogService.log("Image Excel Report Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(ImageExcelPanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\ImageRenamePanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.ImageRenameService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.ExcelFilePanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import java.awt.*;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class ImageRenamePanel extends JPanel {

    private final SourceFoldersPanel sourcePanel;

    private final ExcelFilePanel excelPanel;

    private final DestinationFolderPanel destPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private final JButton startBtn;

    private final JButton pauseBtn;

    private final JButton stopBtn;

    private final JProgressBar progressBar;

    

    private final AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private final AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public ImageRenamePanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.40;

        gbc.insets = new Insets(0, 0, 10, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        excelPanel = new ExcelFilePanel("Select Excel File (Col A: Original Name, Col B: New Name)");

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(2, 5, 2, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(excelPanel, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.30;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeRename();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(excelPanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Image Rename: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Image Rename: Process resumed.");

    }

    

    private void executeRename() {

        if (isProcessing.get()) return;

        java.util.List<String> src = sourcePanel.getSourceFolders();

        String excelFile = excelPanel.getExcelFilePath();

        String dest = destPanel.getDestinationFolder();

        

        if (src.isEmpty() || excelFile == null || excelFile.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please provide source folders, excel file, and destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Image Rename: Starting process...");

        LogService.log("Image Rename: Sources -> " + src);

        LogService.log("Image Rename: Excel File -> " + excelFile);

        LogService.log("Image Rename: Destination -> " + dest);

        LogService.log("Image Rename: Scanning source(s) for images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Integer, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Integer doInBackground() throws Exception {

                final long[] count = {0};

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Image Rename: Scan complete. Found " + totalFiles + " images to process.");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Image Rename: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                return ImageRenameService.renameImagesStreamed(src, excelFile, dest, p -> {}, cancelFlag);

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int count = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("Image Rename: Process stopped by user.");

                    } else if (count == 0) {

                        LogService.log("Image Rename: Process finished. No images were renamed.");

                    } else {

                        LogService.log("Image Rename: Process completed! Renamed and copied " + count + " images.");

                    }

                    

                    JOptionPane.showMessageDialog(ImageRenamePanel.this, msg + "Renamed and copied " + count + " images.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    LogService.log("Image Rename Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(ImageRenamePanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\ImageValidatorPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ExcelService;

import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.ImageValidationService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import java.awt.*;

import java.awt.event.KeyEvent;

import java.nio.file.FileVisitResult;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.List;

import java.util.concurrent.atomic.AtomicBoolean;


public class ImageValidatorPanel extends JPanel {

    private final SourceFoldersPanel sourcePanel;

    private DestinationFolderPanel destPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    

    private final JRadioButton quickRadio;

    private final JRadioButton deepRadio;

    

    private final JButton startBtn;

    private final JButton pauseBtn;

    private final JButton stopBtn;

    private final JProgressBar progressBar;

    

    private final JCheckBox chkSpoofing;

    private final JCheckBox chkDuplicate;

    

    private final AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private final AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    

    private SwingWorker<Void, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public ImageValidatorPanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.55;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        

        // Scan Mode Options

        JPanel scanModeGrid = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));

        scanModeGrid.setOpaque(false);

        JLabel scanModeTitle = new JLabel("Scan Mode:");

        scanModeTitle.setFont(new Font("Segoe UI", Font.BOLD, 12));

        scanModeTitle.setForeground(new Color(200, 214, 229));

        scanModeTitle.setPreferredSize(new Dimension(160, 24));

        

        quickRadio = new JRadioButton("Quick Scan");

        deepRadio = new JRadioButton("Deep Scan");

        quickRadio.setOpaque(false); quickRadio.setForeground(Color.WHITE); quickRadio.setFont(new Font("Segoe UI", Font.BOLD, 11));

        deepRadio.setOpaque(false); deepRadio.setForeground(Color.WHITE); deepRadio.setFont(new Font("Segoe UI", Font.BOLD, 11));

        quickRadio.setSelected(true);

        

        ButtonGroup scanGroup = new ButtonGroup();

        scanGroup.add(quickRadio);

        scanGroup.add(deepRadio);

        

        scanModeGrid.add(scanModeTitle);

        scanModeGrid.add(quickRadio);

        scanModeGrid.add(deepRadio);

        

        // Forensic Checkboxes

        JPanel forensicGrid = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));

        forensicGrid.setOpaque(false);

        JLabel forensicTitle = new JLabel("Forensics:");

        forensicTitle.setFont(new Font("Segoe UI", Font.BOLD, 12));

        forensicTitle.setForeground(new Color(200, 214, 229));

        forensicTitle.setPreferredSize(new Dimension(160, 24));

        

        chkSpoofing = new JCheckBox("Detect Extension Spoofing");

        chkDuplicate = new JCheckBox("Detect Perceptual Duplicates");

        

        chkSpoofing.setOpaque(false); chkSpoofing.setForeground(Color.WHITE); chkSpoofing.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkDuplicate.setOpaque(false); chkDuplicate.setForeground(Color.WHITE); chkDuplicate.setFont(new Font("Segoe UI", Font.BOLD, 11));

        

        forensicGrid.add(forensicTitle);

        forensicGrid.add(chkSpoofing);

        forensicGrid.add(chkDuplicate);

        

        chkDuplicate.setVisible(false);

        

        KeyEventDispatcher dispatcher = new KeyEventDispatcher() {

            private boolean ctrlPressed = false;

            private boolean kPressed = false;

            

            @Override

            public boolean dispatchKeyEvent(KeyEvent e) {

                if (e.getID() == KeyEvent.KEY_PRESSED) {

                    if (e.getKeyCode() == KeyEvent.VK_CONTROL) {

                        ctrlPressed = true;

                    } else if (e.getKeyCode() == KeyEvent.VK_K) {

                        kPressed = true;

                    } else if (e.getKeyCode() == KeyEvent.VK_V) {

                        if (ctrlPressed && kPressed) {

                            SwingUtilities.invokeLater(() -> {

                                chkDuplicate.setVisible(true);

                                chkDuplicate.revalidate();

                                chkDuplicate.repaint();

                                revalidate();

                                repaint();

                            });

                        }

                    }

                } else if (e.getID() == KeyEvent.KEY_RELEASED) {

                    if (e.getKeyCode() == KeyEvent.VK_CONTROL) {

                        ctrlPressed = false;

                    } else if (e.getKeyCode() == KeyEvent.VK_K) {

                        kPressed = false;

                    }

                }

                return false;

            }

        };


        addAncestorListener(new javax.swing.event.AncestorListener() {

            @Override

            public void ancestorAdded(javax.swing.event.AncestorEvent event) {

                KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(dispatcher);

            }


            @Override

            public void ancestorRemoved(javax.swing.event.AncestorEvent event) {

                KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(dispatcher);

            }


            @Override

            public void ancestorMoved(javax.swing.event.AncestorEvent event) {}

        });

        

        destPanel = new DestinationFolderPanel("Report / Log Output Folder");

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(1, 5, 1, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(scanModeGrid, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(forensicGrid, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 3;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.28;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeValidation();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        quickRadio.setEnabled(enabled);

        deepRadio.setEnabled(enabled);

        chkSpoofing.setEnabled(enabled);

        chkDuplicate.setEnabled(enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Validation Process Paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Validation Process Resumed.");

    }

    

    private void executeValidation() {

        if (isProcessing.get()) return;

        List<String> src = sourcePanel.getSourceFolders();

        String dest = destPanel.getDestinationFolder();

        

        if (src.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please select source folders and a destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        ImageValidationService.ScanMode mode = quickRadio.isSelected() ? ImageValidationService.ScanMode.QUICK : ImageValidationService.ScanMode.DEEP;

        final boolean doSpoof = chkSpoofing.isSelected();

        final boolean doDuplicate = chkDuplicate.isSelected();

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        LogService.log("Starting Image Validation (" + mode.name() + " SCAN). Counting files...");

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Void, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Void doInBackground() throws Exception {

                final long[] count = {0};

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                TaskManager.setTotalFiles(totalFiles);

                

                if (totalFiles == 0) return null;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Image Validation: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                LogService.log("Found " + totalFiles + " images to validate. Beginning scan...");

                

                String reportPath = Paths.get(dest).resolve("Image_Health_Report_" + System.currentTimeMillis() + ".xlsx").toString();

                

                try (ExcelService.ExcelReportWriter excelWriter = new ExcelService.ExcelReportWriter(reportPath, doDuplicate)) {

                    ImageValidationService.validateImagesStreamed(

                            src, mode, doSpoof, doDuplicate,

                            0.82,

                            false,

                            excelWriter,

                            p -> {},

                            msg -> {},

                            cancelFlag

                    );

                }

                

                return null;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    get();

                    if (cancelFlag.get()) {

                        LogService.log("Validation Process Cancelled. Partial report has been saved.");

                        JOptionPane.showMessageDialog(ImageValidatorPanel.this, "Process stopped. Partial report saved successfully.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                    } else if (totalFiles == 0) {

                        LogService.log("Validation complete: No images found.");

                        JOptionPane.showMessageDialog(ImageValidatorPanel.this, "No images found to validate.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                    } else {

                        LogService.log("Validation Complete! Health Report generated successfully.");

                        JOptionPane.showMessageDialog(ImageValidatorPanel.this, "Validation complete! Health Report generated successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);

                    }

                } catch (Exception ex) {

                    LogService.log("Validation Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(ImageValidatorPanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\InnerZipPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.ZipService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.services.TempManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import java.awt.*;

import java.io.File;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class InnerZipPanel extends JPanel {

    private SourceFoldersPanel sourcePanel;

    private DestinationFolderPanel destPanel;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private JButton startBtn;

    private JButton pauseBtn;

    private JButton stopBtn;

    private JProgressBar progressBar;

    

    private AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public InnerZipPanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.50;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(2, 5, 2, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.30;

        gbc.insets = new Insets(0, 0, 5, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeInnerZip();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Inner Zip: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Inner Zip: Process resumed.");

    }

    

    private void executeInnerZip() {

        if (isProcessing.get()) return;

        java.util.List<String> src = sourcePanel.getSourceFolders();

        String dest = destPanel.getDestinationFolder();

        

        if (src.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please select source folders and a destination.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Inner Zip: Starting process...");

        LogService.log("Inner Zip: Sources -> " + src);

        LogService.log("Inner Zip: Destination -> " + dest);

        LogService.log("Inner Zip: Scanning source(s) for zip files and images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Integer, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Integer doInBackground() throws Exception {

                totalFiles = 0;

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file) || ZipService.isZipFile(file)) {

                                    totalFiles++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Inner Zip: Scan complete. Found " + totalFiles + " archives/images to process.");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Inner Zip: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                String tempPath = dest + File.separator + "temp_inner_zip_" + System.currentTimeMillis();

                Files.createDirectories(Paths.get(tempPath));

                

                int extractedCount = 0;

                try {

                    extractedCount = ImageExtractionService.extractInnerZipsStreamed(src, tempPath, p -> {}, cancelFlag);

                    

                    if (extractedCount > 0 && !cancelFlag.get()) {

                        SwingUtilities.invokeLater(() -> {

                            statusDotLabel.setText("● Compressing with 7-Zip to inner.zip...");

                            progressBar.setIndeterminate(true);

                        });

                        

                        String zipPath = dest + File.separator + "inner.zip";

                        ZipService.compressDirectory(tempPath, zipPath, cancelFlag, pauseFlag, null);

                    }

                } finally {

                    // Clean up temp directory

                    try {

                        TempManager.deleteTempDirectory(Paths.get(tempPath));

                    } catch (Exception ex) {

                        LogService.log("ZIP", "WARNING", "Temp cleanup warning: " + ex.getMessage());

                    }

                }

                

                return extractedCount;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int count = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("Inner Zip: Process stopped by user.");

                    } else if (count == 0) {

                        LogService.log("Inner Zip: Process finished. No archives or images were found.");

                    } else {

                        LogService.log("Inner Zip: Process completed! Compressed " + count + " images into inner.zip using 7-Zip.");

                    }

                    

                    JOptionPane.showMessageDialog(InnerZipPanel.this, msg + "Compressed " + count + " images into inner.zip.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    LogService.log("Inner Zip Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(InnerZipPanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\SelectivePanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.ZipService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.services.TempManager;

import com.imageextractor.services.ExcelService;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.ExcelFilePanel;

import com.imageextractor.ui.components.OutputFormatPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import java.awt.*;

import java.io.File;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class SelectivePanel extends JPanel {

    private SourceFoldersPanel sourcePanel;

    private ExcelFilePanel excelPanel;

    private DestinationFolderPanel destPanel;

    private OutputFormatPanel formatPanel;

    private JCheckBox chkPartition;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private JButton startBtn;

    private JButton pauseBtn;

    private JButton stopBtn;

    private JProgressBar progressBar;

    

    private AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public SelectivePanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.55;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        excelPanel = new ExcelFilePanel("Select Excel File (Names in Col A, Folders in Col B)");

        formatPanel = new OutputFormatPanel();

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        chkPartition = new JCheckBox("Partition folders by _");

        chkPartition.setOpaque(false);

        chkPartition.setForeground(Color.WHITE);

        chkPartition.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkPartition.setSelected(false);


        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(1, 5, 1, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(excelPanel, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(formatPanel, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(destPanel, gbcOut);

        

        JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));

        optionsPanel.setOpaque(false);

        optionsPanel.add(chkPartition);

        optionsPanel.add(chkLowMemory);


        gbcOut.gridy = 3;

        outSettingsContent.add(optionsPanel, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.28;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeExtraction();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(excelPanel, enabled);

        setComponentsEnabled(formatPanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        chkPartition.setEnabled(enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Selective Extraction: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Selective Extraction: Process resumed.");

    }

    

    private void executeExtraction() {

        if (isProcessing.get()) return;

        java.util.List<String> src = new java.util.ArrayList<>(sourcePanel.getSourceFolders());

        src.sort(String.CASE_INSENSITIVE_ORDER);

        String excelFile = excelPanel.getExcelFilePath();

        String dest = destPanel.getDestinationFolder();

        

        if (excelFile == null || excelFile.isEmpty() || dest == null || dest.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please provide an Excel file and destination folder.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        boolean isZip = formatPanel.isZipOutput();

        final boolean partitionByUnderscore = chkPartition.isSelected();

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Selective Extraction: Starting process...");

        LogService.log("Selective Extraction: Sources -> " + src);

        LogService.log("Selective Extraction: Excel File -> " + excelFile);

        LogService.log("Selective Extraction: Destination -> " + dest);

        LogService.log("Selective Extraction: ZIP Output -> " + (isZip ? "Yes" : "No"));

        LogService.log("Selective Extraction: Scanning source(s) for images...");

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Integer, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Integer doInBackground() throws Exception {

                final long[] count = {0};

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                if (src.isEmpty()) {

                    try {

                        java.util.List<ExcelService.SelectiveEntry> entries = ExcelService.readSelectiveEntriesFromExcel(excelFile);

                        totalFiles = entries != null ? entries.size() : 0;

                    } catch (Exception ex) {

                        LogService.log("SELECTIVE", "WARNING", "Could not count Excel entries: " + ex.getMessage());

                    }

                }

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Selective Extraction: Scan complete. Found " + totalFiles + " images to process.");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Selective Extraction: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                String tempPath = dest;

                if (isZip) {

                    tempPath = dest + File.separator + "temp_selective_" + System.currentTimeMillis();

                    try {

                        Files.createDirectories(Paths.get(tempPath));

                    } catch (Exception ex) {

                        LogService.log("SELECTIVE", "ERROR", "Failed to create temp zip folder: " + ex.getMessage());

                        return 0;

                    }

                }

                

                LogService.log("Selective Extraction: Partition by _ -> " + (partitionByUnderscore ? "Yes" : "No"));

                

                int extracted = ImageExtractionService.extractSelectiveImagesStreamed(src, excelFile, tempPath, partitionByUnderscore, p -> {}, cancelFlag);

                

                try {

                    if (isZip && !cancelFlag.get() && extracted > 0) {

                        SwingUtilities.invokeLater(() -> {

                            statusDotLabel.setText("● Compressing output ZIP archive...");

                            progressBar.setIndeterminate(true);

                        });

                        String zipPath = dest + File.separator + "Selective_Output.zip";

                        ZipService.compressDirectory(tempPath, zipPath, cancelFlag, pauseFlag, null);

                    }

                } catch (Exception e) {

                    LogService.log("COMPRESSION", "ERROR", "Failed to compress output folder: " + tempPath);

                } finally {

                    if (isZip) {

                        try {

                            TempManager.deleteTempDirectory(Paths.get(tempPath));

                        } catch (Exception ex) {

                            LogService.log("SELECTIVE", "WARNING", "Temp cleanup warning: " + ex.getMessage());

                        }

                    }

                }

                return extracted;

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int count = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("Selective Extraction: Process stopped by user.");

                    } else if (count == 0) {

                        LogService.log("Selective Extraction: Process finished. No matching images were found.");

                    } else {

                        LogService.log("Selective Extraction: Process completed! Extracted " + count + " matched images.");

                    }

                    

                    JOptionPane.showMessageDialog(SelectivePanel.this, msg + "Extracted " + count + " matched images.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    LogService.log("Selective Extraction Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(SelectivePanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\ui\modules\SplitImagesPanel.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.ui.modules;


import com.imageextractor.services.ImageExtractionService;

import com.imageextractor.services.KeepAwakeService;

import com.imageextractor.services.SplitService;

import com.imageextractor.services.LogService;

import com.imageextractor.services.TaskManager;

import com.imageextractor.services.TaskWatchdog;

import com.imageextractor.services.SettingsManager;

import com.imageextractor.ui.components.DestinationFolderPanel;

import com.imageextractor.ui.components.OutputFormatPanel;

import com.imageextractor.ui.components.SourceFoldersPanel;

import com.imageextractor.ui.components.SectionPanel;

import com.imageextractor.ui.components.BlendButton;

import com.imageextractor.utils.ETACalculator;


import javax.swing.*;

import javax.swing.border.Border;

import java.awt.*;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.SimpleFileVisitor;

import java.nio.file.FileVisitResult;

import java.nio.file.attribute.BasicFileAttributes;

import java.util.concurrent.atomic.AtomicBoolean;


public class SplitImagesPanel extends JPanel {

    private SourceFoldersPanel sourcePanel;

    private DestinationFolderPanel destPanel;

    private OutputFormatPanel formatPanel;

    private JTextField baseNameField;

    private JTextField sizeLimitField;

    private JComboBox<String> sizeUnitBox;

    private JCheckBox chkLowMemory;

    

    private SectionPanel sourcesSection;

    private SectionPanel outSettingsSection;

    private SectionPanel execSection;

    

    private JLabel statusDotLabel;

    private JLabel lblTotalCount;

    private JLabel lblProcessedCount;

    private JLabel lblProgressPercent;

    private JButton startBtn;

    private JButton pauseBtn;

    private JButton stopBtn;

    private JProgressBar progressBar;

    

    private AtomicBoolean cancelFlag = new AtomicBoolean(false);

    private AtomicBoolean pauseFlag = new AtomicBoolean(false);

    private final AtomicBoolean isProcessing = new AtomicBoolean(false);

    private SwingWorker<Integer, Long> worker;

    private Timer telemetryTimer;

    private volatile long totalFiles;


    public SplitImagesPanel() {

        setLayout(new GridBagLayout());

        setOpaque(false);

        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;

        gbc.fill = GridBagConstraints.BOTH;

        gbc.weightx = 1.0;

        

        // 1. Sources Section

        sourcesSection = new SectionPanel("Sources (Folders/ZIPs, Max 30)", null, null);

        sourcePanel = new SourceFoldersPanel();

        sourcesSection.setHeaderRightComponent(sourcePanel.getHeaderButtonsPanel());

        sourcesSection.getContentPanel().add(sourcePanel, BorderLayout.CENTER);

        

        gbc.gridy = 0;

        gbc.weighty = 0.55;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(sourcesSection, gbc);

        

        // 2. Output Settings Section

        outSettingsSection = new SectionPanel("Output Settings", null, null);

        formatPanel = new OutputFormatPanel();

        destPanel = new DestinationFolderPanel("Destination Folder");

        

        // Split options styling

        JPanel configGrid = new JPanel(new GridLayout(1, 2, 20, 0));

        configGrid.setOpaque(false);

        configGrid.setBorder(BorderFactory.createEmptyBorder(1, 0, 2, 0));

        

        JPanel baseNamePanel = new JPanel(new BorderLayout(10, 0));

        baseNamePanel.setOpaque(false);

        JLabel baseNameLabel = new JLabel("Output Base Name:");

        baseNameLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        baseNameLabel.setForeground(new Color(200, 214, 229));

        baseNameLabel.setPreferredSize(new Dimension(160, 24));

        baseNameField = createSleekTextField("Batch");

        baseNamePanel.add(baseNameLabel, BorderLayout.WEST);

        baseNamePanel.add(baseNameField, BorderLayout.CENTER);

        

        JPanel sizeLimitPanel = new JPanel(new BorderLayout(10, 0));

        sizeLimitPanel.setOpaque(false);

        JLabel sizeLabel = new JLabel("Size Limit / Batch:");

        sizeLabel.setFont(new Font("Segoe UI", Font.BOLD, 12));

        sizeLabel.setForeground(new Color(200, 214, 229));

        sizeLabel.setPreferredSize(new Dimension(160, 24));

        

        JPanel sizeInputWrapper = new JPanel(new BorderLayout(5, 0));

        sizeInputWrapper.setOpaque(false);

        sizeLimitField = createSleekTextField("1.9");

        sizeLimitField.setHorizontalAlignment(JTextField.CENTER);

        

        sizeUnitBox = new JComboBox<>(new String[]{"MB", "GB"});

        sizeUnitBox.setFont(new Font("Segoe UI", Font.BOLD, 11));

        sizeUnitBox.setSelectedIndex(1);

        sizeUnitBox.setPreferredSize(new Dimension(65, 24));

        

        sizeInputWrapper.add(sizeLimitField, BorderLayout.CENTER);

        sizeInputWrapper.add(sizeUnitBox, BorderLayout.EAST);

        sizeLimitPanel.add(sizeLabel, BorderLayout.WEST);

        sizeLimitPanel.add(sizeInputWrapper, BorderLayout.CENTER);

        

        configGrid.add(baseNamePanel);

        configGrid.add(sizeLimitPanel);

        

        chkLowMemory = new JCheckBox("Low Memory Mode");

        chkLowMemory.setOpaque(false);

        chkLowMemory.setForeground(Color.WHITE);

        chkLowMemory.setFont(new Font("Segoe UI", Font.BOLD, 11));

        chkLowMemory.setSelected(SettingsManager.isLowMemoryMode());

        chkLowMemory.addActionListener(e -> SettingsManager.setLowMemoryMode(chkLowMemory.isSelected()));

        

        JPanel outSettingsContent = new JPanel(new GridBagLayout());

        outSettingsContent.setOpaque(false);

        GridBagConstraints gbcOut = new GridBagConstraints();

        gbcOut.gridx = 0;

        gbcOut.fill = GridBagConstraints.HORIZONTAL;

        gbcOut.weightx = 1.0;

        gbcOut.insets = new Insets(1, 5, 1, 5);

        

        gbcOut.gridy = 0;

        outSettingsContent.add(configGrid, gbcOut);

        gbcOut.gridy = 1;

        outSettingsContent.add(formatPanel, gbcOut);

        gbcOut.gridy = 2;

        outSettingsContent.add(destPanel, gbcOut);

        gbcOut.gridy = 3;

        outSettingsContent.add(chkLowMemory, gbcOut);

        outSettingsSection.getContentPanel().add(outSettingsContent, BorderLayout.CENTER);

        

        gbc.gridy = 1;

        gbc.weighty = 0.28;

        gbc.insets = new Insets(0, 0, 4, 0);

        add(outSettingsSection, gbc);

        

        // 3. Execution Section

        execSection = new SectionPanel("Execution", null, null);

        

        JPanel execContent = new JPanel(new GridBagLayout());

        execContent.setOpaque(false);

        GridBagConstraints gbcExec = new GridBagConstraints();

        gbcExec.gridx = 0;

        gbcExec.fill = GridBagConstraints.HORIZONTAL;

        gbcExec.weightx = 1.0;

        gbcExec.insets = new Insets(1, 5, 2, 5);

        

        statusDotLabel = new JLabel("● Ready");

        statusDotLabel.setFont(new Font("Segoe UI", Font.BOLD, 11));

        statusDotLabel.setForeground(new Color(40, 180, 120));

        

        gbcExec.gridy = 0;

        execContent.add(statusDotLabel, gbcExec);

        

        // Metrics & Progress Bar Cards Container

        JPanel metricsRow = new JPanel(new GridLayout(1, 2, 10, 0));

        metricsRow.setOpaque(false);


        // Left Card: Total Images & Processed counts

        JPanel countsCard = new JPanel(new GridLayout(2, 2, 8, 0)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        countsCard.setOpaque(false);

        countsCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JLabel hTotal = new JLabel("Total Images", SwingConstants.CENTER);

        hTotal.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hTotal.setForeground(new Color(175, 195, 215));


        JLabel hProcessed = new JLabel("Processed", SwingConstants.CENTER);

        hProcessed.setFont(new Font("Segoe UI", Font.BOLD, 10));

        hProcessed.setForeground(new Color(175, 195, 215));


        lblTotalCount = new JLabel("0", SwingConstants.CENTER);

        lblTotalCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblTotalCount.setForeground(new Color(40, 215, 120));


        lblProcessedCount = new JLabel("0", SwingConstants.CENTER);

        lblProcessedCount.setFont(new Font("Segoe UI", Font.BOLD, 13));

        lblProcessedCount.setForeground(new Color(40, 215, 120));


        countsCard.add(hTotal);

        countsCard.add(hProcessed);

        countsCard.add(lblTotalCount);

        countsCard.add(lblProcessedCount);


        // Right Card: Overall Progress Label, Percentage & Bar

        JPanel progressCard = new JPanel(new BorderLayout(0, 2)) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12);

                g2.setColor(new Color(0, 180, 216, 40));

                g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 12, 12);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        progressCard.setOpaque(false);

        progressCard.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));


        JPanel progHeader = new JPanel(new BorderLayout());

        progHeader.setOpaque(false);

        JLabel lblProgTitle = new JLabel("Overall Progress");

        lblProgTitle.setFont(new Font("Segoe UI", Font.BOLD, 10));

        lblProgTitle.setForeground(new Color(175, 195, 215));


        lblProgressPercent = new JLabel("0%");

        lblProgressPercent.setFont(new Font("Segoe UI", Font.BOLD, 11));

        lblProgressPercent.setForeground(new Color(40, 215, 120));


        progHeader.add(lblProgTitle, BorderLayout.WEST);

        progHeader.add(lblProgressPercent, BorderLayout.EAST);


        progressBar = new JProgressBar(0, 100);

        progressBar.setStringPainted(false);

        progressBar.setForeground(new Color(0, 200, 120));

        progressBar.setBackground(new Color(10, 25, 42, 200));

        progressBar.setPreferredSize(new Dimension(0, 6));

        progressBar.setBorder(BorderFactory.createLineBorder(new Color(0, 180, 216, 30), 1));


        progressCard.add(progHeader, BorderLayout.NORTH);

        progressCard.add(progressBar, BorderLayout.CENTER);


        metricsRow.add(countsCard);

        metricsRow.add(progressCard);


        gbcExec.gridy = 1;

        gbcExec.insets = new Insets(2, 5, 4, 5);

        execContent.add(metricsRow, gbcExec);

        

        JPanel btnPanel = new JPanel(new GridLayout(1, 3, 10, 0));

        btnPanel.setOpaque(false);

        btnPanel.setPreferredSize(new Dimension(0, 22));

        

        startBtn = new BlendButton("Start Process", true);

        pauseBtn = new BlendButton("Pause", false);

        stopBtn = new BlendButton("Stop Process", false);


        startBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        pauseBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        stopBtn.setFont(new Font("Segoe UI", Font.BOLD, 10));

        

        pauseBtn.setEnabled(false);

        stopBtn.setEnabled(false);

        

        btnPanel.add(startBtn);

        btnPanel.add(pauseBtn);

        btnPanel.add(stopBtn);

        

        gbcExec.gridy = 2;

        gbcExec.insets = new Insets(1, 5, 1, 5);

        execContent.add(btnPanel, gbcExec);

        

        execSection.getContentPanel().add(execContent, BorderLayout.CENTER);

        

        gbc.gridy = 2;

        gbc.weighty = 0.20;

        gbc.insets = new Insets(0, 0, 0, 0);

        add(execSection, gbc);

        

        // Listeners

        startBtn.addActionListener(e -> {

            if (pauseFlag.get()) {

                resumeProcess();

            } else {

                executeSplit();

            }

        });

        

        pauseBtn.addActionListener(e -> pauseProcess());

        

        stopBtn.addActionListener(e -> {

            cancelFlag.set(true);

            resumeProcess();

        });

    }


    private JTextField createSleekTextField(String text) {

        JTextField tf = new JTextField(text) {

            @Override

            protected void paintComponent(Graphics g) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(6, 15, 27, 180));

                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 15, 15);

                g2.dispose();

                super.paintComponent(g);

            }

        };

        tf.setOpaque(false);

        tf.setFont(new Font("Segoe UI", Font.PLAIN, 12));

        tf.setForeground(new Color(0, 229, 255));

        tf.setCaretColor(Color.WHITE);

        tf.setPreferredSize(new Dimension(0, 24));

        

        tf.setBorder(new Border() {

            @Override

            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

                Graphics2D g2 = (Graphics2D) g.create();

                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                g2.setColor(new Color(0, 180, 216, 60));

                g2.drawRoundRect(x, y, width - 1, height - 1, 15, 15);

                g2.dispose();

            }

            @Override public Insets getBorderInsets(Component c) { return new Insets(2, 12, 2, 12); }

            @Override public boolean isBorderOpaque() { return false; }

        });

        return tf;

    }


    private void setInputsEnabled(boolean enabled) {

        setComponentsEnabled(sourcePanel, enabled);

        setComponentsEnabled(formatPanel, enabled);

        setComponentsEnabled(destPanel, enabled);

        baseNameField.setEnabled(enabled);

        sizeLimitField.setEnabled(enabled);

        sizeUnitBox.setEnabled(enabled);

        chkLowMemory.setEnabled(enabled);

    }


    private void setComponentsEnabled(Component comp, boolean enabled) {

        comp.setEnabled(enabled);

        if (comp instanceof Container) {

            for (Component child : ((Container) comp).getComponents()) {

                setComponentsEnabled(child, enabled);

            }

        }

    }


    private void pauseProcess() {

        pauseFlag.set(true);

        TaskManager.setPaused(true);

        startBtn.setText("Resume Process");

        startBtn.setEnabled(true);

        pauseBtn.setEnabled(false);

        statusDotLabel.setText("● Paused");

        statusDotLabel.setForeground(new Color(243, 156, 18));

        execSection.setStatus("Paused");

        LogService.log("Split Images: Process paused.");

    }


    private void resumeProcess() {

        pauseFlag.set(false);

        TaskManager.setPaused(false);

        startBtn.setText("Start Process");

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        statusDotLabel.setText("● Processing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        LogService.log("Split Images: Process resumed.");

    }

    

    private void executeSplit() {

        if (isProcessing.get()) return;

        java.util.List<String> src = sourcePanel.getSourceFolders();

        String dest = destPanel.getDestinationFolder();

        String baseName = baseNameField.getText().trim();

        String sizeStr = sizeLimitField.getText().trim();

        

        if (src.isEmpty() || dest == null || dest.isEmpty() || baseName.isEmpty() || sizeStr.isEmpty()) {

            JOptionPane.showMessageDialog(this, "Please fill in all configuration fields.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        double sizeVal;

        try {

            sizeVal = Double.parseDouble(sizeStr);

        } catch (NumberFormatException ex) {

            JOptionPane.showMessageDialog(this, "Invalid size limit. Must be a number.", "Error", JOptionPane.ERROR_MESSAGE);

            return;

        }

        

        boolean isGB = "GB".equals(sizeUnitBox.getSelectedItem());

        long sizeLimitBytes = (long) (sizeVal * (isGB ? 1024L * 1024L * 1024L : 1024L * 1024L));

        

        startBtn.setEnabled(false);

        pauseBtn.setEnabled(true);

        stopBtn.setEnabled(true);

        cancelFlag.set(false);

        pauseFlag.set(false);

        

        statusDotLabel.setText("● Initializing...");

        statusDotLabel.setForeground(new Color(0, 229, 255));

        execSection.setStatus("Running");

        

        LogService.log("Split Images: Starting process...");

        LogService.log("Split Images: Sources -> " + src);

        LogService.log("Split Images: Destination -> " + dest);

        LogService.log("Split Images: Base Name -> " + baseName);

        LogService.log("Split Images: Size Limit -> " + sizeStr + " " + (isGB ? "GB" : "MB"));

        LogService.log("Split Images: Scanning source(s) for images...");

        

        final boolean isZip = formatPanel.isZipOutput();

        LogService.log("Split Images: ZIP Output -> " + (isZip ? "Yes" : "No"));

        

        progressBar.setValue(0);

        progressBar.setIndeterminate(true);

        

        isProcessing.set(true);

        setInputsEnabled(false);

        

        TaskManager.resetTelemetry();

        KeepAwakeService.start();

        TaskWatchdog.start(cancelFlag);

        

        worker = new SwingWorker<Integer, Long>() {

            private ETACalculator etaCalculator;

            

            @Override

            protected Integer doInBackground() throws Exception {

                final long[] count = {0};

                for (String folderPath : src) {

                    if (cancelFlag.get()) break;

                    ImageExtractionService.processSource(folderPath, rootPath -> {

                        Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

                            @Override

                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {

                                if (cancelFlag.get()) return FileVisitResult.TERMINATE;

                                if (ImageExtractionService.isImageFile(file)) {

                                    count[0]++;

                                }

                                return FileVisitResult.CONTINUE;

                            }

                        });

                    });

                }

                

                totalFiles = count[0];

                TaskManager.setTotalFiles(totalFiles);

                

                LogService.log("Split Images: Scan complete. Found " + totalFiles + " images to process.");

                

                if (totalFiles == 0) return 0;

                

                SwingUtilities.invokeLater(() -> {

                    if (!isProcessing.get()) return;

                    progressBar.setIndeterminate(false);

                    progressBar.setMaximum((int) Math.min(Integer.MAX_VALUE, totalFiles));

                    lblTotalCount.setText(String.format("%,d", totalFiles));

                    lblProcessedCount.setText("0");

                    lblProgressPercent.setText("0%");


                    etaCalculator = new ETACalculator(totalFiles);

                    etaCalculator.start();

                    statusDotLabel.setText("● Processing...");

                    

                    final long[] lastLogTime = {0};

                    // Standardized dynamic 500ms telemetry update

                    telemetryTimer = new Timer(500, ev -> {

                        if (!isProcessing.get()) {

                            ((Timer) ev.getSource()).stop();

                            return;

                        }

                        long processed = TaskManager.getProcessedCount();

                        long failed = TaskManager.getFailedCount();

                        long duplicates = TaskManager.getDuplicateCount();

                        long skipped = TaskManager.getSkippedCount();

                        

                        lblProcessedCount.setText(String.format("%,d", processed));

                        int pct = totalFiles > 0 ? (int) Math.min(100, Math.round(((double) processed / totalFiles) * 100)) : 0;

                        lblProgressPercent.setText(pct + "%");


                        progressBar.setValue((int) Math.min(Integer.MAX_VALUE, processed));

                        String eta = etaCalculator.getETA(processed);

                        

                        long freeMem = Runtime.getRuntime().freeMemory();

                        long totalMem = Runtime.getRuntime().totalMemory();

                        long maxMem = Runtime.getRuntime().maxMemory();

                        long usedMemMb = (totalMem - freeMem) / (1024 * 1024);

                        long maxMemMb = maxMem / (1024 * 1024);

                        

                        StringBuilder sb = new StringBuilder();

                        sb.append("Running (");

                        sb.append(processed).append("/").append(totalFiles);

                        sb.append(" | ").append(eta);

                        if (failed > 0) sb.append(" | Failed: ").append(failed);

                        if (duplicates > 0) sb.append(" | Dups: ").append(duplicates);

                        if (skipped > 0) sb.append(" | Skipped: ").append(skipped);

                        sb.append(" | RAM: ").append(usedMemMb).append("/").append(maxMemMb).append("MB)");

                        

                        execSection.setStatus(sb.toString());

                        

                        String activeFile = TaskManager.getCurrentFileName();

                        if (activeFile != null && !activeFile.isEmpty()) {

                            statusDotLabel.setText("● Processing: " + activeFile);

                        } else {

                            statusDotLabel.setText("● Processing...");

                        }


                        // Log progress update every 3 seconds

                        long now = System.currentTimeMillis();

                        if (now - lastLogTime[0] >= 3000) {

                            lastLogTime[0] = now;

                            LogService.log(String.format("Split Images: Progress %d/%d files (%s | Dups: %d | Failed: %d | RAM: %d/%d MB)", 

                                processed, totalFiles, eta, duplicates, failed, usedMemMb, maxMemMb));

                        }

                    });

                    telemetryTimer.start();

                });

                

                return SplitService.splitImagesStreamed(src, dest, baseName, sizeLimitBytes, isZip, p -> {}, cancelFlag);

            }

            

            @Override

            protected void done() {

                isProcessing.set(false);

                if (telemetryTimer != null) {

                    telemetryTimer.stop();

                }

                TaskWatchdog.stop();

                KeepAwakeService.stop();

                

                try {

                    int batches = get();

                    String msg = cancelFlag.get() ? "Process stopped. " : "Success! ";

                    

                    if (cancelFlag.get()) {

                        LogService.log("Split Images: Process stopped by user.");

                    } else if (batches == 0) {

                        LogService.log("Split Images: Process finished. No batches were created.");

                    } else {

                        LogService.log("Split Images: Process completed! Created " + batches + " image batches.");

                    }

                    

                    JOptionPane.showMessageDialog(SplitImagesPanel.this, msg + "Created " + batches + " batches.", "Finished", JOptionPane.INFORMATION_MESSAGE);

                } catch (Exception ex) {

                    LogService.log("Split Images Error: " + ex.getMessage());

                    JOptionPane.showMessageDialog(SplitImagesPanel.this, "Error: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);

                } finally {

                    startBtn.setText("Start Process");

                    startBtn.setEnabled(true);

                    pauseBtn.setEnabled(false);

                    stopBtn.setEnabled(false);

                    progressBar.setValue(0);

                    progressBar.setIndeterminate(false);

                    pauseFlag.set(false);

                    TaskManager.setPaused(false);

                    

                    statusDotLabel.setText("● Ready");

                    statusDotLabel.setForeground(new Color(40, 180, 120));

                    execSection.setStatus("Ready");

                    

                    setInputsEnabled(true);

                }

            }

        };

        worker.execute();

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\utils\ETACalculator.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.utils;


public class ETACalculator {

    private final long totalItems;

    private long lastTickTime;

    private long lastItemsProcessed;

    private double smoothedSpeed = -1; // items per ms

    private static final double ALPHA = 0.05; // smoothing factor for Exponential Moving Average

    private boolean started = false;


    public ETACalculator(long totalItems) {

        this.totalItems = totalItems;

    }


    public synchronized void start() {

        this.lastTickTime = System.currentTimeMillis();

        this.lastItemsProcessed = 0;

        this.started = true;

    }


    public synchronized String getETA(long currentProgress) {

        if (!started) return "ETA: Calculating...";

        if (currentProgress <= 0 || totalItems <= 0) return "ETA: Calculating...";

        

        long now = System.currentTimeMillis();

        long elapsedMs = now - lastTickTime;

        long deltaItems = currentProgress - lastItemsProcessed;

        

        // Only update speed if enough time has passed to get a meaningful sample (e.g. 500ms)

        if (elapsedMs > 500) {

            double currentSpeed = (double) deltaItems / elapsedMs;

            

            if (smoothedSpeed < 0) {

                smoothedSpeed = currentSpeed; // Initialize

            } else {

                smoothedSpeed = (ALPHA * currentSpeed) + ((1.0 - ALPHA) * smoothedSpeed);

            }

            

            lastTickTime = now;

            lastItemsProcessed = currentProgress;

        }

        

        if (smoothedSpeed <= 0) return "ETA: Calculating...";

        

        long remainingItems = totalItems - currentProgress;

        if (remainingItems <= 0) return "ETA: 0s";

        

        long remainingMs = (long) (remainingItems / smoothedSpeed);

        

        long seconds = remainingMs / 1000;

        long hours = seconds / 3600;

        long minutes = (seconds % 3600) / 60;

        seconds = seconds % 60;

        

        if (hours > 0) {

            return String.format("ETA: %02dh %02dm %02ds", hours, minutes, seconds);

        } else if (minutes > 0) {

            return String.format("ETA: %02dm %02ds", minutes, seconds);

        } else {

            return String.format("ETA: %02ds", seconds);

        }

    }

}


[FILE_END]


==========================================================================

[FILE_START: src\main\java\com\imageextractor\utils\PathUtils.java]

==========================================================================

// Dev: Kunal Vishwas

package com.imageextractor.utils;


import java.nio.file.Path;

import java.nio.file.Paths;


public class PathUtils {

    

    /**

     * Normalizes and prepends the Windows long path prefix (\\?\) if the path exceeds 

     * 240 characters to bypass the 260-character MAX_PATH limit on Windows systems.

     */

    public static Path normalizeLongPath(String pathStr) {

        if (pathStr == null || pathStr.trim().isEmpty()) return null;

        

        try {

            // Convert to absolute path and normalize separators

            Path p = Paths.get(pathStr.trim()).toAbsolutePath();

            String normalized = p.toString().replace('/', '\\');

            

            // Detect Windows OS and check if path exceeds MAX_PATH limit

            if (System.getProperty("os.name", "").toLowerCase().contains("win")) {

                if (normalized.length() > 240 && !normalized.startsWith("\\\\?\\")) {

                    if (normalized.startsWith("\\\\") && !normalized.startsWith("\\\\.\\")) {

                        // UNC network path conversion: \\server\share -> \\?\UNC\server\share

                        normalized = "\\\\?\\UNC\\" + normalized.substring(2);

                    } else if (!normalized.startsWith("\\\\.\\")) {

                        normalized = "\\\\?\\" + normalized;

                    }

                }

            }

            

            return Paths.get(normalized);

        } catch (Exception e) {

            return Paths.get(pathStr.trim());

        }

    }

}


[FILE_END]


[BUNDLE_COMPLETE]

Users.txt
31/12/2026,kunal95,2744440,marve

unpack_code.bat
rem Dev: Kunal Vishwas
@echo off
cd /d "%~dp0"
echo ====================================================
echo SNAPIX Utility - Fast Code Unpacker
echo ====================================================

if not exist Manual_Copy_Paste_Code.txt (
    echo Error: Manual_Copy_Paste_Code.txt not found in this folder!
    pause
    exit /b 1
)

echo Restoring all source files from Manual_Copy_Paste_Code.txt...

where javac >nul 2>&1
if %errorlevel% equ 0 (
    echo Using Java compiler to restore files safely...
    (
        echo // Dev: Kunal Vishwas
        echo import java.io.*;
        echo import java.nio.charset.StandardCharsets;
        echo import java.nio.file.*;
        echo public class Unpack {
        echo     public static void main^(String[] args^) throws Exception {
        echo         Path bundle = Paths.get^("Manual_Copy_Paste_Code.txt"^);
        echo         if ^(!Files.exists^(bundle^)^) {
        echo             System.err.println^("Manual_Copy_Paste_Code.txt not found!"^);
        echo             System.exit^(1^);
        echo         }
        echo         try ^(BufferedReader br = Files.newBufferedReader^(bundle, StandardCharsets.UTF_8^)^) {
        echo             String line;
        echo             BufferedWriter bw = null;
        echo             while ^(^(line = br.readLine^(^)^) != null^) {
        echo                 if ^(line.startsWith^("[FILE_START: "^)^) {
        echo                     String target = line.substring^(13, line.length^(^) - 1^).trim^(^);
        echo                     Path targetPath = Paths.get^(target^);
        echo                     if ^(targetPath.getParent^(^) != null^) {
        echo                         Files.createDirectories^(targetPath.getParent^(^)^);
        echo                     }
        echo                     System.out.println^("  [+] Restoring: " + target^);
        echo                     bw = Files.newBufferedWriter^(targetPath, StandardCharsets.UTF_8^);
        echo                 } else if ^(line.equals^("[FILE_END]"^) ^|^| line.equals^("[BUNDLE_COMPLETE]"^)^) {
        echo                     if ^(bw != null^) {
        echo                         bw.close^(^);
        echo                         bw = null;
        echo                     }
        echo                 } else if ^(line.startsWith^("=========="^)^) {
        echo                     // skip
        echo                 } else {
        echo                     if ^(bw != null^) {
        echo                         bw.write^(line^);
        echo                         bw.newLine^(^);
        echo                     }
        echo                 }
        echo             }
        echo             if ^(bw != null^) bw.close^(^);
        echo         }
        echo     }
        echo }
    ) > Unpack.java
    javac -encoding UTF-8 Unpack.java >nul 2>&1
    java -Dfile.encoding=UTF-8 Unpack
    del Unpack.java Unpack.class 2>nul
    goto :done
)

rem Fallback only if JDK is not present
if exist _unpack_temp.js del _unpack_temp.js
(
echo var fso = new ActiveXObject("Scripting.FileSystemObject"^);
echo var ts = fso.OpenTextFile("Manual_Copy_Paste_Code.txt", 1, false, 0^);
echo var currOut = null;
echo while (!ts.AtEndOfStream^) {
echo   var line = ts.ReadLine(^);
echo   if (line.indexOf("[FILE_START: "^) === 0^) {
echo     var target = line.substring(13, line.length - 1^);
echo     target = target.replace(/^\s+/, ""^).replace(/\s+$/, ""^);
echo     var parts = target.replace(/\\/g, "/"^).split("/"^);
echo     var curDir = ""^;
echo     for (var i = 0; i ^< parts.length - 1; i++^) {
echo       curDir = curDir + (curDir ? "\\" : ""^) + parts[i]^;
echo       if (!fso.FolderExists(curDir^)^) fso.CreateFolder(curDir^)^;
echo     }
echo     WScript.Echo("  [+] Restoring: " + target^);
echo     currOut = fso.CreateTextFile(target, true^);
echo   } else if (line === "[FILE_END]" ^|^| line === "[BUNDLE_COMPLETE]"^) {
echo     if (currOut^) { currOut.Close(^); currOut = null; }
echo   } else if (line.indexOf("=========="^) === 0^) {
echo     // skip
echo   } else {
echo     if (currOut^) currOut.WriteLine(line^);
echo   }
echo }
echo if (currOut^) currOut.Close(^);
echo ts.Close(^);
) > _unpack_temp.js

cscript //nologo _unpack_temp.js
del _unpack_temp.js 2>nul

:done
echo.
echo ====================================================
echo Unpacking complete! All source files restored.
echo ====================================================

Comments

Popular posts from this blog

HTML5 cheat codes Part.01

HTML5 cheat codes part.02