modmanager.backend.Modification.java Source code

Java tutorial

Introduction

Here is the source code for modmanager.backend.Modification.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.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;
import modmanager.ModificationManager;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import util.Util;

/**
 *
 * @author ruman
 */
public final class Modification {

    private static final Logger logger = Logger.getLogger(Modification.class.getName());
    private static final IOFileFilter README_FILENAME_FILTER = new IOFileFilter() {

        @Override
        public boolean accept(File file, String filename) {
            filename = filename.toLowerCase();

            if (FilenameUtils.wildcardMatch(filename, "*.txt") && !filename.equals("wizard.txt")) {
                return true;
            }

            return false;
        }

        @Override
        public boolean accept(File file) {
            return accept(file, file.getName());
        }
    };
    private static final IOFileFilter IMAGE_FILENAME_FILTER = new IOFileFilter() {

        @Override
        public boolean accept(File file, String filename) {
            if (FilenameUtils.wildcardMatch(filename, "*.png") || FilenameUtils.wildcardMatch(filename, "*.jpg")
                    || FilenameUtils.wildcardMatch(filename, "*.bmp")
                    || FilenameUtils.wildcardMatch(filename, "*.jpeg")) {
                return true;
            }

            return false;
        }

        @Override
        public boolean accept(File file) {
            return accept(file, file.getName());
        }
    };

    /**
     * The mod directory
     */
    private File directory;

    /**
     * Determines if the mod could be loaded
     */
    private boolean loaded;
    /**
     * The name, usually extracted from directory name
     */
    private String name;

    /**
     * The modification type
     */
    private Type type;

    /**
     * Contains all modification options
     */
    private HashMap<String, ModificationOption> options;

    /**
     * Contains information if this modification is activated
     */
    private ModificationStatus status;

    /**
     * Event listeners
     */
    private ArrayList<ModificationListener> listeners;

    /**
     * Instance to mod manager
     */
    private ModificationManager manager;

    public Modification(ModificationManager manager, File path) {
        this.manager = manager;
        options = new HashMap<>();
        status = new ModificationStatus(this);
        listeners = new ArrayList<>();

        load(path);
        updateStatus();
    }

    private void load(File path) {
        logger.log(Level.INFO, "Loading modification from {0}", path.getAbsolutePath());

        if (!path.isDirectory()) {
            return;
        }

        /**
         * Precheck: dir empty? (failed extraction)
         */
        if (path.list().length == 0) {
            path.delete();
            return;
        }

        /**
         * Precheck: archive ghost dir?
         */
        {
            File[] entries = path.listFiles();

            if (entries.length == 1) {
                File one = entries[0];

                if (one.isDirectory()) {
                    if (!one.getName().equalsIgnoreCase("data")) {
                        try {
                            logger.log(Level.INFO,
                                    "Moving directory, because someone decided it's good to pack all data into a sub directory ...");

                            /**
                             * It's a directory. Change filesystem by
                             * reparenting
                             */
                            File tmp = new File(path.getAbsolutePath() + ".tmp");

                            FileUtils.moveDirectory(one, tmp);
                            FileUtils.deleteDirectory(path);
                            FileUtils.moveDirectory(tmp, path);
                        } catch (IOException ex) {
                            logger.log(Level.SEVERE, ex.getMessage());

                            return;
                        }
                    }
                }
            }
        }

        this.directory = path;

        /**
         * Read name
         */
        this.name = path.getName();

        /**
         * Wizard indicates
         */
        if ((new File(path, "Wizard.txt")).exists()) {
            this.type = Type.MULTIOPTION;
        } else {
            this.type = Type.MULTIOPTION;

            for (File sub : path.listFiles()) {
                if (FilenameUtils.wildcardMatch(sub.getName(), "*.bsa", IOCase.INSENSITIVE)) {
                    this.type = Type.SIMPLE;
                    break;
                } else if (FilenameUtils.wildcardMatch(sub.getName(), "*.esp", IOCase.INSENSITIVE)) {
                    this.type = Type.SIMPLE;
                    break;
                } else if (FilenameUtils.wildcardMatch(sub.getName(), "Meshes", IOCase.INSENSITIVE)) {
                    this.type = Type.SIMPLE;
                    break;
                } else if (FilenameUtils.wildcardMatch(sub.getName(), "Textures", IOCase.INSENSITIVE)) {
                    this.type = Type.SIMPLE;
                    break;
                } else if (FilenameUtils.wildcardMatch(sub.getName(), "data", IOCase.INSENSITIVE)) {
                    this.type = Type.SIMPLE;
                    break;
                }
            }
        }

        logger.log(Level.INFO, "... mod type detected: {0}", this.type);

        /**
         * OK, load the mod types from given type
         */
        switch (this.type) {
        /**
         * This is intended!
         */
        case MULTIOPTION: {
            for (File sub : path.listFiles()) {
                if (sub.isDirectory() && !sub.getName().equalsIgnoreCase("fomod")
                        && !sub.getName().toLowerCase().contains("readme")
                        && !sub.getName().toLowerCase().contains("manual")) {
                    ModificationOption opt = new ModificationOption(this, sub);

                    if (opt.getFileCount() != 0) {
                        options.put(opt.getName(), opt);
                    }
                }
            }
        }
            break;
        case SIMPLE:

        {
            //Create new option based on modification path
            ModificationOption opt = new ModificationOption(this, path);

            if (opt.getFileCount() != 0) {
                options.put(opt.getName(), opt);
            }
        }

            break;
        }

        /**
         * Load screenshots
         */
        for (File file : getScreenshotImages()) {
            status.addScreenshotImage(file);
        }

        if (!options.isEmpty()) {
            loaded = true;
        } else {
            logger.log(Level.WARNING, "Ignoring {0}: Empty!", name);
        }

    }

    /**
     * Tries to find read me files and returns the text
     *
     * @return
     */
    public String getReadMe() {
        StringBuilder readme = new StringBuilder();

        for (File file : FileUtils.listFiles(directory, README_FILENAME_FILTER, TrueFileFilter.INSTANCE)) {
            try {
                readme.append(FileUtils.readFileToString(file));
                readme.append("\n---------\n");
            } catch (IOException ex) {

            }
        }

        if (readme.length() == 0) {
            readme.append("No readme found.");
        }

        return readme.toString();
    }

    /**
     * Tries to find some images (screenshots, ...)
     *
     * @return
     */
    public ArrayList<File> getScreenshotImages() {
        ArrayList<File> files = new ArrayList<>();

        files.addAll(FileUtils.listFiles(directory, IMAGE_FILENAME_FILTER, TrueFileFilter.INSTANCE));

        return files;
    }

    /**
     * @return the loaded
     */
    public boolean isLoaded() {
        return loaded;
    }

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

    /**
     * @return the type
     */
    public Type getType() {
        return type;
    }

    public void updateStatus() {
        for (ModificationOption opt : getOptions()) {
            opt.updateStatus(getStatus());
        }
    }

    /**
     * Checks if all files are installed This won't work for FOMOD or WIZARD
     *
     * @return
     */
    public boolean isCompletelyInstalled() {
        switch (type) {
        /**
         * Cannot decide if we used a wizard
         */
        case MULTIOPTION:
            return isInstalled();
        case SIMPLE:
            return getStatus().getOptionCount() == getStatus().getInstalledOptions();
        }

        throw new IllegalStateException();
    }

    /**
     * Checks if some files are installed
     *
     * @return
     */
    public boolean isInstalled() {
        return getStatus().getInstalledOptions() != 0;
    }

    /**
     * @return the status
     */
    public ModificationStatus getStatus() {
        return status;
    }

    /**
     * @return the options
     */
    public Collection<ModificationOption> getOptions() {
        return options.values();
    }

    /**
     * Installs this modifications with given options
     *
     * @param options
     */
    public void install(ModificationOption... options) {
        if (isInstalled()) {
            uninstall();
        }

        for (ModificationOption opt : options) {
            if (!this.options.values().contains(opt)) {
                throw new IllegalStateException("Option does not belong to this modification.");
            }

            opt.install();
        }

        updateStatus();

        for (ModificationListener lis : listeners) {
            lis.modificationInstalled(this, options);
        }
    }

    /**
     * Uninstalls this modification.
     */
    public void uninstall() {
        for (ModificationOption opt : options.values()) {
            opt.uninstall();
        }

        updateStatus();

        for (ModificationListener lis : listeners) {
            lis.modificationUninstalled(this);
        }
    }

    public void addListener(ModificationListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removeListener(ModificationListener listener) {
        listeners.remove(listener);
    }

    public ModificationOption getOption(String name) {
        return options.get(name);
    }

    public ModificationOption getFirstOption() {
        for (ModificationOption opt : options.values()) {
            return opt;
        }

        return null;
    }

    /**
     * Returns all options, which conflicts with current selection of
     * to-be-installed options.
     *
     * @param modifications
     * @param to_be_added
     * @return
     */
    public static LinkedList<ModificationOption> findConflicts(Collection<Modification> modifications,
            Collection<ModificationOption> to_be_added) {
        LinkedList<ModificationOption> conflicts = new LinkedList<>();

        for (Modification mod : modifications) {
            for (ModificationOption opt : mod.getOptions()) {
                /**
                 * option should be installed OR in the list of to-be-added
                 */
                if (mod.getStatus().isInstalled(opt) || to_be_added.contains(opt)) {
                    /**
                     * Check all mods in collection
                     */
                    for (ModificationOption wannabe : to_be_added) {
                        /**
                         * Prevent self-checking
                         */
                        if (wannabe == opt) {
                            continue;
                        }

                        /**
                         * Only care about uninstalled selected packages
                         */
                        if (!wannabe.getParent().getStatus().isInstalled(wannabe)) {
                            if (opt.conflicts(wannabe)) {
                                conflicts.add(opt);
                            }
                        }
                    }
                }
            }
        }

        return conflicts;
    }

    /**
     * @return the manager
     */
    public ModificationManager getModificationManager() {
        return manager;
    }

    /**
     * Returns directory of this modification
     *
     * @return
     */
    public File getDirectory() {
        return directory;
    }

    public static enum Type {

        /**
         * Just some files
         */
        SIMPLE,
        /**
         * Contains multiple options
         */
        MULTIOPTION
    }

    /**
     *
     * @author ruman
     */
    public static class ModificationUninstaller extends SwingWorker<Modification, String> {

        private Modification modification;

        public ModificationUninstaller(Modification mod) {
            this.modification = mod;
        }

        @Override
        protected Modification doInBackground() throws Exception {
            /**
             * Calculate size
             */
            float count = 0;
            float copied = 0;

            for (ModificationOption option : modification.getOptions()) {
                count += option.getFileCount();
            }

            for (ModificationOption option : modification.getOptions()) {
                publish(option.getName());

                for (File file : option.getFiles()) {
                    if (copied % 5 == 0) {
                        setProgress((int) ((float) copied / (float) count * 100f));
                    }

                    option.uninstall(file);

                    copied++;
                }

                option.enforceFullStatusUpdate();
            }

            modification.updateStatus();

            return modification;
        }

        @Override
        protected void done() {
            for (ModificationListener lis : modification.listeners) {
                lis.modificationUninstalled(modification);
            }
        }
    }

    /**
     *
     * @author ruman
     */
    public static class ModificationInstaller extends SwingWorker<Modification, String> {

        private Modification modification;
        private ModificationOption[] options;

        public ModificationInstaller(Modification mod, ModificationOption... options) {
            this.modification = mod;
            this.options = options;
        }

        @Override
        protected Modification doInBackground() throws Exception {
            /**
             * Pre: uninstall
             */
            publish("Uninstalling existing files ...");

            modification.uninstall();

            /**
             * Calculate size
             */
            float count = 0;
            float copied = 0;

            for (ModificationOption option : options) {
                count += option.getFileCount();
            }

            for (ModificationOption option : options) {
                if (!modification.options.values().contains(option)) {
                    throw new IllegalArgumentException();
                }

                publish(option.getName());

                for (File file : option.getFiles()) {
                    if (copied % 5 == 0) {
                        setProgress((int) ((float) copied / (float) count * 100f));
                    }

                    option.install(file);

                    copied++;
                }

                option.enforceFullStatusUpdate();
            }

            modification.updateStatus();

            return modification;
        }

        @Override
        protected void done() {
            for (ModificationListener lis : modification.listeners) {
                lis.modificationInstalled(modification, options);
            }
        }

    }

    /**
     * Gets the actual destination file in Skyrim directory for a source file
     *
     * @param absolute source
     * @return
     */
    public String getRelativePathInModification(File absolute) {
        return Util.relativePath(directory, absolute);
    }
}