org.virtualAsylum.spriggan.data.Addon.java Source code

Java tutorial

Introduction

Here is the source code for org.virtualAsylum.spriggan.data.Addon.java

Source

package org.virtualAsylum.spriggan.data;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.transport.FetchResult;
import org.virtualAsylum.spriggan.Database;
import org.virtualAsylum.spriggan.Main;
import org.virtualAsylum.spriggan.interfaces.popups.DependencyPopup;
import org.virtualAsylum.spriggan.interfaces.MainInterface;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;

import static com.sun.javafx.application.PlatformImpl.runLater;
import static java.nio.file.Files.copy;
import static org.virtualAsylum.spriggan.util.SprigganHelper.*;

/**
 * Created by Morgan on 27/03/2014.
 */
public class Addon {

    Git git;
    Exception lastException;

    private void handleException(Exception ex) {
        Main.handleException(lastException = ex);
    }

    public void download(EventObject event) {
        download();
    }

    public void download() {
        daemon(this::doDownload);
    }

    public State doDownload() {
        log("Downloading %s (%s)", getDisplayName(), getID());
        setState(State.DOWNLOADING);
        State result = State.IDLE;
        setStateProgress(-1);
        Database.getCurrent().getRepository().add(this);
        File repositoryDirectory = getRepositoryDirectory(this);
        if (repositoryDirectory.exists()) {
            recursiveDelete(repositoryDirectory);
        }
        try {
            git = Git.cloneRepository().setURI(getRemoteRepository()).setBranch(getBranch())
                    .setDirectory(repositoryDirectory).setProgressMonitor(monitor).call();
            Database.save();
        } catch (Exception ex) {
            handleException(ex);
            result = State.ERROR;
        }

        log("Downloading %s (%s): %s", getDisplayName(), getID(), result);
        setState(result);
        setStateProgress(0);
        return result;
    }

    public void downloadAndInstall(EventObject event) {
        downloadAndInstall();
    }

    public void downloadAndInstall() {
        daemon(this::doDownloadAndInstall);
    }

    public State doDownloadAndInstall() {
        if (doDownload().equals(State.IDLE)) {
            return doInstall();
        }
        return State.ERROR;
    }

    public void install(EventObject event) {
        install();
    }

    public void install() {
        daemon(this::doInstall);
    }

    static final List<String> DEFAULT_INSTALL_IGNORE = Arrays.asList(".git", ".gitignore", ".spriggan", "README.md",
            "LICENSE.md", "readme", "readme.md", "license", "license.md", "LICENSE", "README");

    private ArrayList<File> getRepositoryFiles() {
        ArrayList<File> result = new ArrayList();
        File repositoryDirectory = getRepositoryDirectory(this);
        Collection<String> ignore = getInstallIgnore();
        for (File file : repositoryDirectory.listFiles()) {
            getRepositoryFiles_Sub(file, repositoryDirectory, result, ignore);
        }
        return result;
    }

    private void getRepositoryFiles_Sub(File file, File repositoryDirectory, ArrayList<File> result,
            Collection<String> ignore) {
        Path path = repositoryDirectory.toPath().relativize(file.toPath());
        if (ignore.contains(path.toString()) || DEFAULT_INSTALL_IGNORE.contains(path.toString())) {
            return;
        }
        result.add(file);
        if (file.isDirectory()) {
            for (File sub : file.listFiles()) {
                getRepositoryFiles_Sub(sub, repositoryDirectory, result, ignore);
            }
        }
    }

    public State doInstall() {
        log("Installing %s (%s)", getDisplayName(), getID());
        State result = State.IDLE;
        State error = State.ERROR;
        setState(State.INSTALLING);
        setStateProgress(-1);
        boolean didError = false;
        try {
            //<editor-fold desc="Dependencies">
            Collection<String> dependencyIDs = getDependencies();
            ArrayList<Addon> dependencies = new ArrayList();
            if (dependencyIDs.size() > 0) {
                setState(State.INSTALLING_DEPENDENCIES);
                for (String dependencyIDString : dependencyIDs) {
                    Addon dependency = Database.find_ID(dependencyIDString);
                    if (dependency == null) {
                        throw new Exception(String.format("Dependency %s was missing for %s(%s)",
                                dependencyIDString, getDisplayName(), getID()));
                    }
                    if (!dependency.getInstalled()) {
                        dependencies.add(dependency);
                    }
                }
                if (dependencies.size() > 0) {
                    SimpleObjectProperty<Boolean> popupResult = new SimpleObjectProperty(null);
                    runLater(() -> {
                        DependencyPopup popup = new DependencyPopup(this, dependencies, popupResult);
                        Stage stage = popup.popup(StageStyle.UTILITY, Modality.WINDOW_MODAL,
                                MainInterface.current.getWindow(), getDisplayName());
                        stage.setOnCloseRequest(e -> popupResult.set(false));
                        stage.show();
                    });

                    while (popupResult.get() == null) {
                        sleep(500);
                    }
                    if (!popupResult.get()) {
                        error = State.IDLE;
                        throw new Exception(String.format("Did not install dependencies for %s (%s)",
                                getDisplayName(), getID()));
                    }
                    double perDep = 1.0 / dependencies.size();
                    setStateProgress(0.0);
                    for (Addon dependency : dependencies) {
                        boolean downloaded = Database.getCurrent().getRepository().contains(dependency);
                        State depResult = State.IDLE;
                        if (!downloaded) {
                            depResult = dependency.doDownloadAndInstall();
                        } else if (!dependency.getInstalled()) {
                            depResult = dependency.doInstall();
                        }
                        if (depResult != State.IDLE) {
                            throw new Exception(
                                    String.format("Dependency %s (%s) failed to download and/or install",
                                            dependency.getDisplayName(), dependency.getID()));
                        }
                        incrementStateProgress(perDep);
                    }
                    setState(State.INSTALLING);
                }

            }
            //</editor-fold>

            error = State.ERROR;

            final File repositoryDirectory = getRepositoryDirectory(this);
            final File installDirectory = getInstallDirectory(this);

            ArrayList<File> files = getRepositoryFiles();

            double perFile = 1.0 / files.size();
            setStateProgress(0.0);
            for (File inputFile : files) {
                File outputFile = new File(installDirectory,
                        repositoryDirectory.toPath().relativize(inputFile.toPath()).toString());
                outputFile.getParentFile().mkdirs();
                Files.copy(inputFile.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                incrementStateProgress(perFile);
            }

        } catch (Exception ex) {
            if (error == State.ERROR) {
                handleException(ex);
            }
            result = error;
            didError = true;
        }

        if (!didError) {
            setInstalled(true);
        }
        log("Installing %s (%s): %s", getDisplayName(), getID(), result);
        setStateProgress(0);
        setState(result);
        return result;
    }

    public void uninstall(EventObject event) {
        uninstall();
    }

    public void uninstall() {
        daemon(this::doUninstall);
    }

    public State doUninstall() {
        log("Uninstalling %s (%s)", getDisplayName(), getID());
        State result = State.IDLE;
        setState(State.UNINSTALLING);
        setStateProgress(-1);

        final File repositoryDirectory = getRepositoryDirectory(this);
        final File installDirectory = getInstallDirectory(this);
        ArrayList<File> files = getRepositoryFiles();

        double perFile = 1.0 / files.size();
        setStateProgress(0.0);
        for (File inputFile : files) {
            File outputFile = new File(installDirectory,
                    repositoryDirectory.toPath().relativize(inputFile.toPath()).toString());
            if (outputFile.exists()) {
                outputFile.delete();
                if (outputFile.getParentFile().listFiles().length == 0) {
                    outputFile.getParentFile().delete();
                }
                incrementStateProgress(perFile);
            }
        }

        if (result == State.IDLE) {
            setInstalled(false);
        }
        log("Uninstalling %s (%s): %s", getDisplayName(), getID(), result);
        setStateProgress(0);
        setState(result);
        return result;
    }

    private static void changeListener(Object a, Object b, Object c) {
        MainInterface.current.updateFilters();
    }

    public static File getRepositoryDirectory(Addon addon) {
        return new File(Database.PATH_REPOSITORY, addon.getInstallName() + "/" + addon.getBranch());
    }

    public boolean hasTag(String tag) {
        return tags.get().contains(tag);
    }

    public void update(ActionEvent e) {
        update();
    }

    public void update() {
        daemon(this::doUpdate);
    }

    static Git getGit(Addon addon) {
        try {
            FileRepository repository = new FileRepository(new File(getRepositoryDirectory(addon), ".git"));
            return new Git(repository);
        } catch (Exception ex) {
            addon.handleException(ex);
            return null;
        }
    }

    public State doUpdate() {
        State result = State.IDLE;
        setStateProgress(-1);
        setState(State.UPDATING);

        try {
            FetchResult fetchResult = getGit(this).fetch().setDryRun(true).setProgressMonitor(monitor).call();
            if (fetchResult.getTrackingRefUpdates().isEmpty()) {
                setState(State.IDLE);
            } else {
                setState(State.UPDATING);
                getGit(this).pull().setProgressMonitor(monitor).call();
                if (getInstalled()) {
                    doInstall();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        setState(result);
        setStateProgress(0.0);
        return result;
    }

    public static enum State {
        IDLE("Idle"), DOWNLOADING("Downloading"), ERROR("Error"), INSTALLING("Installing"), UNINSTALLING(
                "Uninstalling"), INSTALLING_DEPENDENCIES("Installing Dependencies"), UPDATING("Updating");

        final String text;

        State(String text) {
            this.text = text;
        }

        @Override
        public String toString() {
            return text;
        }
    }

    private static class AddonProgressMonitor implements ProgressMonitor {

        final Addon addon;

        public AddonProgressMonitor(Addon addon) {
            this.addon = addon;
        }

        double done = 0, perItem;
        String title;

        @Override
        public void start(int totalTasks) {
            //System.out.printf("START: %s tasks\n", totalTasks);
        }

        @Override
        public void beginTask(String title, int totalWork) {
            addon.setStateProgress(done = 0);
            perItem = 1.0 / (totalWork + 1);
            //System.out.printf("BEGIN: %s, %s\n", this.title = title, totalWork);
        }

        @Override
        public void update(int completed) {
            addon.setStateProgress(done += perItem);
            //System.out.printf("UPDATE: %s, %s (%s)\n", title, completed, done);
        }

        @Override
        public void endTask() {
            addon.setStateProgress(1);
            //System.out.printf("END: %s\n", title);
        }

        @Override
        public boolean isCancelled() {
            return false;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Addon) {
            Addon objA = (Addon) obj;
            return objA.getID().equals(getID());
        }
        return false;
    }

    public static File getInstallDirectory(Addon addon) {
        return new File(Database.PATH_INSTALL, addon.getInstallName());
    }

    SimpleObjectProperty<UUID> id = new SimpleObjectProperty();

    @JsonProperty("id")
    public String getID() {
        return id.get().toString();
    }

    @JsonIgnore
    public SimpleObjectProperty<UUID> idProperty() {
        return id;
    }

    @JsonProperty("id")
    public void setID(String id) {
        this.id.set(UUID.fromString(id));
    }

    //<editor-fold desc="Dependencies">
    SimpleListProperty<String> dependencies = new SimpleListProperty(FXCollections.observableArrayList());

    public Collection<String> getDependencies() {
        return dependencies.get();
    }

    @JsonIgnore
    public SimpleListProperty<String> dependenciesProperty() {
        return dependencies;
    }

    public void setDependencies(String[] dependencies) {
        if (dependencies == null || dependencies.length == 0) {
            return;
        }
        this.dependencies.set(FXCollections.observableArrayList(dependencies));
    }
    //</editor-fold>

    //<editor-fold desc="Installed">
    SimpleBooleanProperty installed = new SimpleBooleanProperty();

    public boolean getInstalled() {
        return installed.get();
    }

    @JsonIgnore
    public SimpleBooleanProperty installedProperty() {
        return installed;
    }

    public void setInstalled(boolean installed) {
        runLater(() -> {
            this.installed.set(installed);
            Database.save();
        });
    }
    //</editor-fold>

    //<editor-fold desc="State">
    SimpleObjectProperty<State> state = new SimpleObjectProperty(State.IDLE);

    @JsonIgnore
    public State getState() {
        return state.get();
    }

    @JsonIgnore
    public SimpleObjectProperty<State> stateProperty() {
        return state;
    }

    @JsonIgnore
    public void setState(State state) {
        runLater(() -> this.state.set(state));
    }
    //</editor-fold>

    //<editor-fold desc="StateProgress">
    SimpleDoubleProperty stateProgress = new SimpleDoubleProperty(0);

    @JsonIgnore
    public SimpleDoubleProperty stateProgressProperty() {
        return stateProgress;
    }

    @JsonIgnore
    public void setStateProgress(double stateProgress) {
        runLater(() -> this.stateProgress.set(stateProgress));
    }

    public void incrementStateProgress(double value) {
        setStateProgress(stateProgress.get() + value);
    }
    //</editor-fold>

    //<editor-fold desc="RemoteRepository">
    SimpleStringProperty remoteRepository = new SimpleStringProperty();

    public String getRemoteRepository() {
        return remoteRepository.get();
    }

    @JsonIgnore
    public SimpleStringProperty remoteRepositoryProperty() {
        return remoteRepository;
    }

    public void setRemoteRepository(String remoteRepository) {
        this.remoteRepository.set(remoteRepository);
    }
    //</editor-fold>

    @JsonAnySetter
    public void metaSet(String name, Object value) {

    }

    //<editor-fold desc="Readme">
    SimpleObjectProperty<URL> readme = new SimpleObjectProperty();

    public URL getReadme() {
        return readme.get();
    }

    @JsonIgnore
    public SimpleObjectProperty<URL> readmeProperty() {
        return readme;
    }

    public void setReadme(URL root) {
        this.readme.set(root);
    }
    //</editor-fold>

    //<editor-fold desc="DisplayName">
    SimpleStringProperty displayName = new SimpleStringProperty();

    public String getDisplayName() {
        return displayName.get();
    }

    @JsonIgnore
    public SimpleStringProperty displayNameProperty() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName.set(displayName);
    }
    //</editor-fold>

    //<editor-fold desc="Author">
    SimpleStringProperty author = new SimpleStringProperty();

    public String getAuthor() {
        return author.get();
    }

    @JsonIgnore
    public SimpleStringProperty authorProperty() {
        return author;
    }

    public void setAuthor(String author) {
        this.author.set(author);
    }
    //</editor-fold>

    //<editor-fold desc="WikiRoot">
    SimpleStringProperty wikiRoot = new SimpleStringProperty();

    public String getWikiRoot() {
        return wikiRoot.get();
    }

    @JsonIgnore
    public SimpleStringProperty wikiRootProperty() {
        return wikiRoot;
    }

    public void setWikiRoot(String wikiRoot) {
        this.wikiRoot.set(wikiRoot);
    }
    //</editor-fold>

    //<editor-fold desc="InstallName">
    SimpleStringProperty installName = new SimpleStringProperty();

    public String getInstallName() {
        return installName.get();
    }

    @JsonIgnore
    public SimpleStringProperty installNameProperty() {
        return installName;
    }

    public void setInstallName(String installName) {
        this.installName.set(installName);
    }
    //</editor-fold>

    //<editor-fold desc="Tags">
    SimpleListProperty<String> tags = new SimpleListProperty(FXCollections.observableArrayList());

    public Collection<String> getTags() {
        return tags.get();
    }

    @JsonIgnore
    public SimpleListProperty<String> tagsProperty() {
        return tags;
    }

    public void setTags(String[] tags) {
        this.tags.set(FXCollections.observableArrayList(tags));
    }
    //</editor-fold>

    //<editor-fold desc="InstallIgnore">
    SimpleListProperty<String> installIgnore = new SimpleListProperty(FXCollections.observableArrayList());

    public Collection<String> getInstallIgnore() {
        return installIgnore.get();
    }

    @JsonIgnore
    public SimpleListProperty<String> installIgnoreProperty() {
        return installIgnore;
    }

    public void setInstallIgnore(String[] installIgnore) {
        if (installIgnore == null || installIgnore.length == 0) {
            return;
        }
        this.installIgnore.set(FXCollections.observableArrayList(installIgnore));
    }
    //</editor-fold>

    //<editor-fold desc="Branch">
    SimpleStringProperty branch = new SimpleStringProperty("master");

    public String getBranch() {
        return branch.get();
    }

    @JsonIgnore
    public SimpleStringProperty branchProperty() {
        return branch;
    }

    public void setBranch(String branch) {
        this.branch.set(branch);
    }
    //</editor-fold>

    private final AddonProgressMonitor monitor;

    public Addon() {
        monitor = new AddonProgressMonitor(this);
        state.addListener(Addon::changeListener);
    }
}