modmanager.backend.ModificationOption.java Source code

Java tutorial

Introduction

Here is the source code for modmanager.backend.ModificationOption.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package modmanager.backend;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.TrueFileFilter;
import util.Util;

/**
 * Handles the options of a modification
 *
 * @author ruman
 */
public class ModificationOption {

    private static final Logger logger = Logger.getLogger(ModificationOption.class.getName());

    private Modification parent;

    /**
     * Content dir
     */
    private File directory;

    /**
     * The name of this option, loaded from directory
     */
    private String name;
    /**
     * All files in this option
     */
    private HashSet<File> files;
    /**
     * The root directory where to put the files
     *
     * If the mod contains 'data' folder, it will be put to skyrim root
     * Otherwiise to SKYRIM/data
     */
    private String rootDirectory;

    public ModificationOption(Modification parent, File path) {
        this.parent = parent;
        directory = path;
        files = new HashSet<>();

        load(path);
    }

    private void load(File path) {
        logger.log(Level.FINE, "Modification option requested for {0}", path.getAbsolutePath());

        /**
         * Set name
         */
        name = path.getName();

        /**
         * Make compatible
         */
        makeUnixCompatible(path);

        /**
         * Declare if data rooted or main dir rooted
         */
        if ((new File(path, "Data")).exists()) {
            rootDirectory = "";
        } else {
            rootDirectory = "Data";
        }

        /**
         * Look in files
         */
        for (File file : FileUtils.listFiles(path, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)) {
            if (!file.isDirectory()) {
                if (file.getParentFile().getName().equalsIgnoreCase("fomod")) {
                    continue;
                } else if (FilenameUtils.wildcardMatch(file.getName(), "*.txt", IOCase.INSENSITIVE)) {
                    continue;
                } else if (FilenameUtils.wildcardMatch(file.getName(), "*.rtf", IOCase.INSENSITIVE)) {
                    continue;
                } else if (FilenameUtils.wildcardMatch(file.getName(), "*.pdf", IOCase.INSENSITIVE)) {
                    continue;
                } else if (FilenameUtils.wildcardMatch(file.getName(), "*.jskmm_status", IOCase.INSENSITIVE)) {
                    continue;
                }

                getFiles().add(file);

                logger.log(Level.FINE, "... added {0} to option", Util.relativePath(path, file));
            }
        }
    }

    /**
     * Renames folders to be compatible to unix file systems
     */
    private void makeUnixCompatible(File path) {
        logger.log(Level.FINER, "Making modification compatible to UNIX filesystems (Mac, Linux, ...)");

        /**
         * Check if the data folder is named wrong
         */
        if (FileUtils.getFile(path, "data").exists()) {
            FileUtils.getFile(path, "data").renameTo(FileUtils.getFile(path, "Data"));

            /**
             * Go to data directory
             */
            path = FileUtils.getFile(path, "Data");

            logger.log(Level.FINEST, "unix: Renamed data to Data");
        } else if (FileUtils.getFile(directory, "Data").exists()) {
            /**
             * Go to data directory
             */
            path = FileUtils.getFile(path, "Data");
        }

        /**
         * All top folders should be renamed to uppercase letter first
         */
        for (File dir : path.listFiles()) {
            if (dir.isDirectory()) {
                if (Character.isLowerCase(dir.getName().charAt(0))) {
                    /**
                     * Silently assuming that a folder has more than one letter
                     */
                    String new_name = Character.toUpperCase(dir.getName().charAt(0)) + dir.getName().substring(1);

                    dir.renameTo(FileUtils.getFile(dir.getParentFile(), new_name));
                    logger.log(Level.FINEST, "unix: Renamed {0} to {1}",
                            new Object[] { dir.getAbsolutePath(), new_name });
                }
            }
        }
    }

    /**
     * Gets all files in this option
     *
     * @return
     */
    public HashSet<File> getFiles() {
        return files;
    }

    /**
     * updates the status of contained files in this modification
     *
     * @param status
     */
    public void updateStatus(ModificationStatus status) {
        logger.log(Level.INFO, "Updating status of option ...");

        LinkedList<File> installed = getOrLoadInstalledFiles();
        status.set(this, isInstalled(installed), installed.size());

        for (File file : files) {
            /**
             * ! O(n)
             */
            status.set(file, installed.contains(file));
        }
    }

    /**
     * Getd the file where to put a list of all installed files
     *
     * @return
     */
    public File getStatusFile() {
        return new File(directory, "installed_files.jskmm_status");
    }

    /**
     * Deletes the status file and enforces a full status update @ next
     * updateStatus
     */
    public void enforceFullStatusUpdate() {
        getStatusFile().delete();
    }

    /**
     * Uninstalls this option
     */
    protected void uninstall() {
        logger.log(Level.INFO, "Uninstalling mod option ...");

        for (File file : getFiles()) {
            uninstall(file);
        }

        enforceFullStatusUpdate();
    }

    /**
     * Uninstalls given source file
     *
     * @param file
     */
    protected void uninstall(File file) {
        logger.log(Level.INFO, "Uninstalling {0} ...", file.getAbsolutePath());

        File destination = getDestinationFile(file);

        try {
            Files.deleteIfExists(destination.toPath());
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Could not uninstall because of: {0}", ex.getMessage());
        }
    }

    /**
     * Installs this option
     */
    protected void install() {
        logger.log(Level.INFO, "Installing mod option ...");

        for (File file : getFiles()) {
            install(file);
        }

        enforceFullStatusUpdate();
    }

    /**
     * Installs given source file
     *
     * @param file
     */
    protected void install(File file) {
        logger.log(Level.INFO, "Installing {0} ...", file.getAbsolutePath());

        File destination = getDestinationFile(file);

        /**
         * Make necessary directories
         */
        destination.getParentFile().mkdirs();

        try {
            /**
             * Copy files
             */
            Files.copy(file.toPath(), destination.toPath());
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Could not copy to {0}", destination.getAbsolutePath());
        }
    }

    public int getFileCount() {
        return files.size();
    }

    public LinkedList<File> getOrLoadInstalledFiles() {
        LinkedList<File> file;

        if (getStatusFile().exists()) {
            file = new LinkedList<>();

            try (BufferedReader reader = new BufferedReader(new FileReader(getStatusFile()))) {
                String line;

                while ((line = reader.readLine()) != null) {
                    if (!line.isEmpty()) {
                        if (!line.startsWith(directory.getAbsolutePath())) {
                            throw new IOException("Incompatible data!");
                        }

                        file.add(new File(line));
                    }
                }
            } catch (IOException ex) {
                logger.log(Level.SEVERE, null, ex);

                /**
                 * Delete status file and create it next time May cause loop,
                 * but #yolo
                 */
                enforceFullStatusUpdate();
                return getOrLoadInstalledFiles();
            }
        } else {
            file = getInstalledFiles();

            /**
             * Write data to status file
             */
            try (PrintWriter writer = new PrintWriter(getStatusFile())) {
                for (File f : file) {
                    writer.println(f.getAbsolutePath());
                }
            } catch (IOException ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }

        return file;
    }

    public LinkedList<File> getInstalledFiles() {
        LinkedList<File> installed = new LinkedList<>();

        for (File file : files) {
            if (isInstalled(file)) {
                installed.add(file);
            }
        }

        return installed;
    }

    public int getInstalledFileCount() {
        return getInstalledFiles().size();
    }

    public boolean isInstalled() {
        return isInstalled(getInstalledFiles());
    }

    private boolean isInstalled(LinkedList<File> installed) {
        return installed.size() != 0;
    }

    public boolean isCompletelyInstalled() {
        return isCompletelyInstalled(getInstalledFiles());
    }

    private boolean isCompletelyInstalled(LinkedList<File> installed) {
        return installed.size() == getFileCount();
    }

    /**
     * Returns if given source file is installed
     *
     * @param file
     * @return
     */
    private boolean isInstalled(File file) {
        File destination = getDestinationFile(file);

        if (!destination.exists()) {
            return false;
        }
        if (destination.isDirectory() != file.isDirectory()) {
            return false;
        }

        if (file.length() <= 268435456L) {
            try {
                if (FileUtils.checksumCRC32(destination) != FileUtils.checksumCRC32(file)) {
                    return false;
                }
            } catch (IOException ex) {
                Logger.getLogger(ModificationOption.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            logger.log(Level.INFO, "Skipping check file {0}: too large", file.getAbsolutePath());
        }

        //        try
        //        {
        //            if (!FileUtils.contentEquals(file, destination))
        //            {
        //                return false;
        //            }
        //        }
        //        catch (IOException ex)
        //        {
        //            Logger.getLogger(ModificationOption.class.getName()).log(Level.SEVERE, null, ex);
        //        }
        /**
         * more checks, hash, equal?
         */
        return true;
    }

    /**
     * Gets the actual destination file in Skyrim directory for a source file
     *
     * @param source
     * @return
     */
    public File getDestinationFile(File source) {
        return new File(new File(parent.getModificationManager().getSkyrimDirectory(), rootDirectory),
                Util.relativePath(directory, source));
    }

    /**
     * Returns true if this option conflicts with another option
     *
     * O(n^2)
     *
     * @param option
     * @return
     */
    public boolean conflicts(ModificationOption option) {
        for (File here : files) {
            for (File there : option.files) {
                if (getDestinationFile(here).equals(option.getDestinationFile(there))) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @return the parent
     */
    public Modification getParent() {
        return parent;
    }
}