From 44a92b6923c1e893d6d3f67b7df7823142d3733c Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Fri, 4 Dec 2020 10:25:58 +0100
Subject: [PATCH] Create FileTypeInspector which determine file type based on
 magic value.

---
 .../java/cz/it4i/qcmp/DataCompressor.java     | 106 ++++++++++++------
 .../it4i/qcmp/fileformat/QCMPFileHeader.java  |   2 +-
 .../cz/it4i/qcmp/io/FileTypeInspector.java    |  64 +++++++++++
 .../cz/it4i/qcmp/utilities/ColorConsole.java  |   4 +-
 4 files changed, 139 insertions(+), 37 deletions(-)
 create mode 100644 src/main/java/cz/it4i/qcmp/io/FileTypeInspector.java

diff --git a/src/main/java/cz/it4i/qcmp/DataCompressor.java b/src/main/java/cz/it4i/qcmp/DataCompressor.java
index 0c0de09..b87be17 100644
--- a/src/main/java/cz/it4i/qcmp/DataCompressor.java
+++ b/src/main/java/cz/it4i/qcmp/DataCompressor.java
@@ -7,10 +7,11 @@ import cz.it4i.qcmp.cli.CustomFunctionBase;
 import cz.it4i.qcmp.cli.functions.DebugFunction;
 import cz.it4i.qcmp.compression.ImageCompressor;
 import cz.it4i.qcmp.compression.ImageDecompressor;
-import cz.it4i.qcmp.fileformat.FileExtensions;
 import cz.it4i.qcmp.fileformat.IQvcFile;
+import cz.it4i.qcmp.io.FileTypeInspector;
 import cz.it4i.qcmp.io.QuantizationCacheManager;
 import cz.it4i.qcmp.io.QvcFileReader;
+import cz.it4i.qcmp.utilities.ColorConsole;
 import org.apache.commons.cli.*;
 
 import java.io.IOException;
@@ -108,47 +109,69 @@ public class DataCompressor {
             }
             break;
             case InspectFile: {
-                if (parsedOptions.getInputDataInfo().getFilePath().endsWith(FileExtensions.CACHE_FILE_EXT)) {
-                    QuantizationCacheManager.inspectCacheFile(parsedOptions.getInputDataInfo().getFilePath(),
-                                                              parsedOptions.isVerbose());
-                } else {
-                    final ImageDecompressor decompressor = new ImageDecompressor(parsedOptions);
-                    try {
-                        System.out.println(decompressor.inspectCompressedFile());
-                    } catch (final IOException e) {
-                        System.err.println("Errors occurred during inspecting file.");
-                        System.err.println(e.getMessage());
-                        e.printStackTrace();
+                final FileTypeInspector.FileType fileType = FileTypeInspector.inspectFile(parsedOptions.getInputDataInfo().getFilePath());
+                switch (fileType) {
+                    case Qcmp: {
+                        final ImageDecompressor decompressor = new ImageDecompressor(parsedOptions);
+                        try {
+                            System.out.println(decompressor.inspectCompressedFile());
+                        } catch (final IOException e) {
+                            System.err.println("Errors occurred during inspecting file.");
+                            System.err.println(e.getMessage());
+                            e.printStackTrace();
+                        }
                     }
+                    break;
+                    case Qvc: {
+                        QuantizationCacheManager.inspectCacheFile(parsedOptions.getInputDataInfo().getFilePath(),
+                                                                  parsedOptions.isVerbose());
+                    }
+                    break;
+                    case InvalidPath:
+                        exitWithInvalidFilePath();
+                        break;
+                    case Unknown:
+                        exitWithUnknownFile();
+                        break;
                 }
             }
             break;
             case Convert: {
-                final boolean inPlace = parsedOptions.getOutputFilePath() == null;
-                // TODO(Moravec): Maybe replace with generic reader which can determine file type based on magic value and not an extension.
-                if (parsedOptions.getInputDataInfo().getFilePath().endsWith(FileExtensions.CACHE_FILE_EXT)) {
-                    IQvcFile cacheFile = null;
-                    try {
-                        cacheFile = QvcFileReader.readCacheFile(parsedOptions.getInputDataInfo().getFilePath());
-                    } catch (final IOException e) {
-                        System.err.println("Unable to read QVC file. Error: " + e.getMessage());
-                        exitApplication(1);
-                    }
-                    try {
-                        assert (cacheFile != null);
-                        cacheFile.convertToNewerVersion(inPlace, parsedOptions.getInputDataInfo().getFilePath(),
-                                                        parsedOptions.getOutputFilePath());
-                    } catch (final IOException e) {
-                        System.err.println("Unable to convert specified QVC file. Error: " + e.getMessage());
-                        exitApplication(1);
+                final FileTypeInspector.FileType fileType = FileTypeInspector.inspectFile(parsedOptions.getInputDataInfo().getFilePath());
+                switch (fileType) {
+                    case Qcmp: {
+                        System.err.println("Qcmp file conversion isn't supported yet");
                     }
+                    break;
+                    case Qvc: {
+                        IQvcFile cacheFile = null;
+                        try {
+                            cacheFile = QvcFileReader.readCacheFile(parsedOptions.getInputDataInfo().getFilePath());
+                        } catch (final IOException e) {
+                            System.err.println("Unable to read QVC file. Error: " + e.getMessage());
+                            exitApplication(1);
+                        }
+                        final boolean inPlace = parsedOptions.getOutputFilePath() == null;
+                        try {
+                            assert (cacheFile != null);
+                            cacheFile.convertToNewerVersion(inPlace, parsedOptions.getInputDataInfo().getFilePath(),
+                                                            parsedOptions.getOutputFilePath());
+                        } catch (final IOException e) {
+                            System.err.println("Unable to convert specified QVC file. Error: " + e.getMessage());
+                            exitApplication(1);
+                        }
 
-                    if (parsedOptions.isVerbose()) {
-                        System.err.println("Qvc file is converted.");
+                        if (parsedOptions.isVerbose()) {
+                            System.err.println("Qvc file is converted.");
+                        }
                     }
-
-                } else {
-                    System.err.println("Qcmp file conversion isn't supported yet");
+                    break;
+                    case InvalidPath:
+                        exitWithInvalidFilePath();
+                        break;
+                    case Unknown:
+                        exitWithUnknownFile();
+                        break;
                 }
             }
             break;
@@ -156,6 +179,21 @@ public class DataCompressor {
         ScifioWrapper.dispose();
     }
 
+    private static void exitWithUnknownFile() {
+        ColorConsole.fprintf(ColorConsole.Target.stderr,
+                             ColorConsole.Color.Red,
+                             "Provided file is of unknown type. Only QCMP and QVC files are supported.\n");
+
+        exitApplication(1);
+    }
+
+    private static void exitWithInvalidFilePath() {
+        ColorConsole.fprintf(ColorConsole.Target.stderr,
+                             ColorConsole.Color.Red,
+                             "File specified by provided path doesn't exist.\n");
+        exitApplication(1);
+    }
+
     private static void exitApplication(final int exitCode) {
         ScifioWrapper.dispose();
         System.exit(exitCode);
diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeader.java b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeader.java
index e9522b4..e720b0d 100644
--- a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeader.java
+++ b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeader.java
@@ -15,7 +15,7 @@ public class QCMPFileHeader implements IFileHeader, Cloneable {
     //region Constants
     private static final int VERSION = 1;
     private static final int BASE_QCMP_HEADER_SIZE = 23;
-    private static final String MAGIC_VALUE = "QCMPFILE";
+    public static final String MAGIC_VALUE = "QCMPFILE";
     //endregion
 
     //region Header fields
diff --git a/src/main/java/cz/it4i/qcmp/io/FileTypeInspector.java b/src/main/java/cz/it4i/qcmp/io/FileTypeInspector.java
new file mode 100644
index 0000000..14a99bd
--- /dev/null
+++ b/src/main/java/cz/it4i/qcmp/io/FileTypeInspector.java
@@ -0,0 +1,64 @@
+package cz.it4i.qcmp.io;
+
+import cz.it4i.qcmp.fileformat.QCMPFileHeader;
+import cz.it4i.qcmp.fileformat.QvcHeaderV1;
+import cz.it4i.qcmp.fileformat.QvcHeaderV2;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class FileTypeInspector {
+    public enum FileType {
+        /**
+         * Compressed image hyperstack.
+         */
+        Qcmp,
+        /**
+         * Codebook cache file.
+         */
+        Qvc,
+        /**
+         * Invalid file path.
+         */
+        InvalidPath,
+        /**
+         * No known magic value was matched.
+         */
+        Unknown
+    }
+
+    /**
+     * Inspect file specified by path and return its type.
+     *
+     * @param filePath Path to the file.
+     * @return File type.
+     */
+    public static FileType inspectFile(final String filePath) {
+        // QCMPFileHeader.MAGIC_VALUE   // 8 bytes
+        // QvcHeaderV1.MAGIC_VALUE      // 9 bytes
+        // QvcHeaderV2.MAGIC_VALUE      // 9 bytes
+        try (final FileInputStream stream = new FileInputStream(filePath)) {
+            final byte[] buf1 = new byte[QCMPFileHeader.MAGIC_VALUE.length()];
+            RawDataIO.readFullBuffer(stream, buf1);
+            if (new String(buf1).equals(QCMPFileHeader.MAGIC_VALUE)) {
+                return FileType.Qcmp;
+            }
+
+            final byte[] buf2 = new byte[QvcHeaderV1.MAGIC_VALUE.length()];
+            System.arraycopy(buf1, 0, buf2, 0, buf1.length);
+            final int read = stream.read(buf2, buf1.length, 1);
+            if (read != 1)
+                return FileType.Unknown;
+
+            final String magicValue = new String(buf2);
+            if (magicValue.equals(QvcHeaderV1.MAGIC_VALUE) || magicValue.equals(QvcHeaderV2.MAGIC_VALUE))
+                return FileType.Qvc;
+        } catch (final FileNotFoundException e) {
+            return FileType.InvalidPath;
+        } catch (final IOException e) {
+            return FileType.Unknown;
+        }
+        return FileType.Unknown;
+    }
+}
diff --git a/src/main/java/cz/it4i/qcmp/utilities/ColorConsole.java b/src/main/java/cz/it4i/qcmp/utilities/ColorConsole.java
index d41b659..a3625f1 100644
--- a/src/main/java/cz/it4i/qcmp/utilities/ColorConsole.java
+++ b/src/main/java/cz/it4i/qcmp/utilities/ColorConsole.java
@@ -48,10 +48,10 @@ public final class ColorConsole {
 
         switch (target) {
             case stdout:
-                System.out.println(getColor(color) + string + ANSI_RESET);
+                System.out.print(getColor(color) + string + ANSI_RESET);
                 break;
             case stderr:
-                System.err.println(getColor(color) + string + ANSI_RESET);
+                System.err.print(getColor(color) + string + ANSI_RESET);
                 break;
         }
     }
-- 
GitLab