Java tutorial
/* * 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; } }