Java tutorial
/* This file is part of NWN Server Updater. NWN Server Updater is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. NWN Server Updater is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NWN Server Updater. If not, see <http://www.gnu.org/licenses/>. */ package com.nwn; import java.io.BufferedInputStream; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; /** * Created by Sam on 10/10/2015. * TODO://add version check in json */ public class NwnUpdater implements Runnable { private Path nwnRootPath; private Path serverFileJson; private ArrayList<ServerFile> serverFileList; private ArrayList<String> affectedFolders; private NwnUpdaterHomeView currentGui; private boolean[] error; /** * Create NwnUpdater object * Also creates compressed folder if it does not exist * @param newNwnRootPath * @param newServerFileJson */ public NwnUpdater(Path newNwnRootPath, Path newServerFileJson, NwnUpdaterHomeView gui) { serverFileList = new ArrayList<ServerFile>(); affectedFolders = new ArrayList<String>(); nwnRootPath = newNwnRootPath; serverFileJson = newServerFileJson; currentGui = gui; error = new boolean[] { false }; File tmpFolder = new File(nwnRootPath.toString() + File.separator + FolderByExt.COMPRESSED.toString()); if (!tmpFolder.exists()) { tmpFolder.mkdir(); } else { deleteDirWithMessage(tmpFolder, "\nRemoving old files..."); tmpFolder.mkdir(); } } /** * Start updater process */ @Override public void run() { error[0] = false; currentGui.setUpdateBtnText("Stop"); if (Thread.currentThread().isInterrupted()) { cleanup(); printExitStatus(1); return; } if (!parseServerFileJson()) { cleanup(); printExitStatus(2); return; } currentGui.setOverallProgressBarValue(5); if (Thread.currentThread().isInterrupted()) { cleanup(); printExitStatus(1); return; } ArrayList<ServerFile> filesToDownload = determineFilesToDownload(); currentGui.setOverallProgressBarValue(10); if (Thread.currentThread().isInterrupted()) { cleanup(); printExitStatus(1); return; } if (filesToDownload.size() > 0) { downloadFilesFromList(filesToDownload); } else { cleanup(); printExitStatus(3); return; } currentGui.setOverallProgressBarValue(90); if (Thread.currentThread().isInterrupted()) { cleanup(); printExitStatus(1); return; } cleanup(); printExitStatus(0); } /** * Output why the update process ended * Available statuses: * 0 = Complete * 1 = Canceled * 2 = Failed * 3 = No update required * default = Unknown reason * @param status exit status integer * //todo: ENUMS */ private void printExitStatus(int status) { String exitStatus; if (error[0]) { status = 4; } switch (status) { case 0: exitStatus = "Update Process Complete"; break; case 1: exitStatus = "Update canceled by user"; break; case 2: exitStatus = "Update failed"; break; case 3: exitStatus = "All Files up to date"; break; case 4: exitStatus = "Update completed with errors"; break; default: exitStatus = "Update failed for unknown reason"; break; } currentGui.appendOutputText("\n" + exitStatus); } /** * Make sure everything is cleaned up * This currently only wipes the compressed file directory */ private void cleanup() { currentGui.setTaskProgressBarValue(50); deleteDirWithMessage(new File(nwnRootPath + File.separator + FolderByExt.COMPRESSED.toString()), "Cleaning up temporary files..."); currentGui.setTaskProgressBarValue(100); currentGui.setOverallProgressBarValue(100); currentGui.setUpdateBtnText("Update"); } /** * Notification wrapper for deleteDir * @param file directory or file to delete * @param message message to give user about delete */ private void deleteDirWithMessage(File file, String message) { currentGui.appendOutputText("\n" + message); NwnFileHandler.deleteDir(file); currentGui.appendOutputText("done"); } /** * For debugging file deletes * @param file * @return String of why file can't be deleted */ private String getReasonForFileDeletionFailureInPlainEnglish(File file) { try { if (!file.exists()) return file.getName() + " It doesn't exist in the first place."; else if (file.isDirectory() && file.list().length > 0) return file.getName() + " It's a directory and it's not empty.\n"; else return file.getName() + " Somebody else has it open, we don't have write permissions, or somebody stole my disk."; } catch (SecurityException e) { return file.getName() + " We're sandboxed and don't have filesystem access."; } } /** * Downloads file from given url * Gracefully handle thread interrupt * @param fileUrl String of url to download * @param dest Location on system where file should be downloaded * @return True if download success, False if download failed */ public boolean interruptableDownloadFile(String fileUrl, String dest) { boolean success = true; currentGui.appendOutputText("\nDownloading " + fileUrl + " to " + dest + "..."); try { URL url = new URL(fileUrl); BufferedInputStream bis = new BufferedInputStream(url.openStream()); FileOutputStream fis = new FileOutputStream(dest); String fileSizeString = url.openConnection().getHeaderField("Content-Length"); double fileSize = Double.parseDouble(fileSizeString); byte[] buffer = new byte[1024]; int count; double bytesDownloaded = 0.0; currentGui.setTaskProgressBarValue(0); while ((count = bis.read(buffer, 0, 1024)) != -1 && !Thread.currentThread().isInterrupted()) { bytesDownloaded += count; fis.write(buffer, 0, count); if (fileSize > 0) { int downloadStatus = (int) ((bytesDownloaded / fileSize) * 100); currentGui.setTaskProgressBarValue(downloadStatus); // System.out.println("Downloading " + fileUrl + " to " + dest + " " + downloadStatus + "%"); } } fis.close(); bis.close(); if (Thread.currentThread().isInterrupted()) {//cleanup from interrupt File thisFile = new File(dest); thisFile.delete(); success = false; } } catch (MalformedURLException ex) { currentGui.appendOutputText("\nERROR: URL Invalid"); error[0] = true; // ex.printStackTrace(); success = false; } catch (FileNotFoundException ex) { currentGui.appendOutputText("\nERROR: File not found"); error[0] = true; // ex.printStackTrace(); success = false; } catch (IOException ex) { currentGui.appendOutputText("\nERROR: Cannot save file"); error[0] = true; // ex.printStackTrace(); success = false; } if (success) { currentGui.appendOutputText("done"); } return success; } /** * Reads through every file in directory and determines correct action for each file based off the file extension * Files will either be moved to the correct directory or extracted and processed. * @param uncompressedFolder Path of directory to process */ private void processFilesInDirectory(Path uncompressedFolder) { ArrayList<String> fileNames; try { fileNames = NwnFileHandler.getFileNamesInDirectory(uncompressedFolder); } catch (NoSuchFileException nsfe) { currentGui.appendOutputText( "\nERROR: Folder " + uncompressedFolder.getFileName().toString() + " does not exist!"); error[0] = true; return; } catch (IOException ex) { currentGui.appendOutputText("\nERROR: Cannot parse local directory!"); error[0] = true; return; } for (String fileName : fileNames) { Path srcFile = Paths.get(uncompressedFolder.toString() + File.separator + fileName); if (srcFile.toFile().isDirectory()) { processFilesInDirectory(srcFile); } else { String folderName = ServerFile.getFolderByExtension(fileName); Path desiredFolder = Paths.get(nwnRootPath.toString() + File.separator + folderName); Path desiredPath = Paths .get(nwnRootPath.toString() + File.separator + folderName + File.separator + fileName); //make sure no file with the same name exists before we move it there if (!desiredPath.toFile().exists() && desiredFolder.toFile().exists()) { NwnFileHandler.moveFile(srcFile, desiredPath); currentGui.appendOutputText( "\nMoving " + srcFile.getFileName().toString() + " to " + desiredFolder.toString()); if (folderName.equals(FolderByExt.COMPRESSED.toString())) { uncompressFile(fileName, folderName); } //we don't want to create folders for the user, so just alert them and move on } else if (!desiredFolder.toFile().exists()) { currentGui.appendOutputText("\nERROR: Folder " + folderName + " does not exist!"); error[0] = true; } } } } /** * Extract contents of archive to current directory * Supports zip and rar * @param fileName archive file name * @param parentFolder folder containing archive file */ private void uncompressFile(String fileName, String parentFolder) { currentGui.appendOutputText("\nExtracting " + fileName + "..."); String fileLoc = nwnRootPath + File.separator + parentFolder + File.separator + fileName; String baseName = fileLoc; String fileExt = NwnFileHandler.getFileExtension(fileLoc); if (fileExt.length() > 0) { baseName = fileLoc.substring(0, fileLoc.lastIndexOf('.')); } if (fileExt.equals("zip") || fileExt.equals("rar")) { File extractFolder = new File(baseName); if (!extractFolder.exists()) { extractFolder.mkdir(); } NwnFileHandler.extractFile(Paths.get(fileLoc), Paths.get(baseName)); currentGui.appendOutputText("done"); processFilesInDirectory(Paths.get(baseName)); } else { currentGui.appendOutputText("\nERROR: compression not supported"); error[0] = true; } } /** * Download files from given list * If they are archives, extract them * @param filesToDownload List of ServerFile objects to download */ private void downloadFilesFromList(ArrayList<ServerFile> filesToDownload) { //todo: don't hardcode progress numbers int overallProgress = 10; int overallInterval = 80; if (filesToDownload.size() > 0) { overallInterval = 80 / filesToDownload.size(); } boolean downloadSuccess; for (ServerFile serverFile : filesToDownload) { downloadSuccess = interruptableDownloadFile(serverFile.getUrl().toString(), nwnRootPath + File.separator + serverFile.getFolder() + File.separator + serverFile.getName()); if (!downloadSuccess) { currentGui.appendOutputText("\nError downloading file: " + serverFile.getName()); } else { if (serverFile.getFolder().equals(FolderByExt.COMPRESSED.toString())) { uncompressFile(serverFile.getName(), serverFile.getFolder()); } } overallProgress = overallProgress + overallInterval; currentGui.setOverallProgressBarValue(overallProgress); } } /** * Scan local folders which should contain the files needed for the selected server * If any files are missing, add them to the list of files to download * @return */ private ArrayList<ServerFile> determineFilesToDownload() { currentGui.setTaskProgressBarValue(0); int currentProgress = 0; int progressIncrement; if (affectedFolders.size() > 0) { progressIncrement = 100 / affectedFolders.size(); } else { progressIncrement = 100; } currentGui.appendOutputText("\nChecking local files"); ArrayList<ServerFile> filesToDownload = new ArrayList<ServerFile>(); for (String folder : affectedFolders) { Path folderPath = Paths.get(nwnRootPath.toString() + File.separator + folder); ArrayList<String> localFiles; try { localFiles = NwnFileHandler.getFileNamesInDirectory(folderPath); } catch (NoSuchFileException nsfe) { currentGui.appendOutputText( "\nERROR: Folder " + folderPath.getFileName().toString() + " does not exist!"); error[0] = true; return new ArrayList<ServerFile>(); } catch (IOException ex) { currentGui.appendOutputText("\nERROR: Cannot parse local directory!"); error[0] = true; return new ArrayList<ServerFile>(); } for (ServerFile serverFile : serverFileList) { currentGui.appendOutputText("."); if (serverFile.getFileList() == null && serverFile.getFolder().equals(folder) && !localFiles.contains(serverFile.getName())) { filesToDownload.add(serverFile); } else if (serverFile.getFileList() != null) { for (String file : serverFile.getFileList()) { if (!localFiles.contains(file) && NwnFileHandler.getFileExtension(file).equalsIgnoreCase(folder) && !filesToDownload.contains(serverFile)) { filesToDownload.add(serverFile); break; } } } } currentProgress = currentProgress + progressIncrement; currentGui.setTaskProgressBarValue(currentProgress); } currentGui.appendOutputText("done"); return filesToDownload; } /** * Parse json file containing files required for server * Convert those files into ServerFile objects and store them */ private boolean parseServerFileJson() { boolean success = true; String compressedFileName; currentGui.setTaskProgressBarValue(0); int currentProgress = 0; int statusIncrement; currentGui.appendOutputText("\n\nReading file list"); try { Thread.sleep(500); FileReader reader = new FileReader(serverFileJson.toString()); JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = (JSONObject) jsonParser.parse(reader); Set<String> folders = jsonObject.keySet(); if (folders.size() > 0) { statusIncrement = 100 / folders.size(); } else { statusIncrement = 100; } for (String folderName : folders) { if (!folderName.contains("..") && !folderName.contains(":")) { affectedFolders.add(folderName); JSONArray filesByFolder = (JSONArray) jsonObject.get(folderName); Iterator fileItr = filesByFolder.iterator(); while (fileItr.hasNext()) { currentGui.appendOutputText("."); JSONObject fileJson = (JSONObject) fileItr.next(); URL fileUrl = new URL(fileJson.get("url").toString()); if (folderName.equalsIgnoreCase(FolderByExt.COMPRESSED.toString())) { ArrayList<String> compressedFileList = new ArrayList<>(); JSONArray compressedFileArray = (JSONArray) fileJson.get("files"); Iterator cfItr = compressedFileArray.iterator(); while (cfItr.hasNext()) { compressedFileName = cfItr.next().toString(); affectedFolders.add(NwnFileHandler.getFileExtension(compressedFileName)); compressedFileList.add(compressedFileName); } serverFileList.add(new ServerFile(fileJson.get("name").toString(), fileUrl, folderName, compressedFileList)); } else { serverFileList .add(new ServerFile(fileJson.get("name").toString(), fileUrl, folderName)); } } } else { currentGui.appendOutputText("An unusual folder path was detected: " + folderName + "\nServer owner may be attempting to place files outside of NWN." + "\nThis folder has been excluded from the update."); } currentProgress = currentProgress + statusIncrement; currentGui.setTaskProgressBarValue(currentProgress); } currentGui.appendOutputText("done"); reader.close(); } catch (IOException ex) { // ex.printStackTrace(); currentGui.appendOutputText("...failed\nERROR: Cannot read server file list."); error[0] = true; success = false; } catch (ParseException ex) { // ex.printStackTrace(); currentGui.appendOutputText("...failed\nERROR: Cannot parse server file list."); error[0] = true; success = false; } catch (InterruptedException ex) { currentGui.appendOutputText("...canceled"); success = false; } return success; } /** * @return ArrayList of files required by the selected server */ public ArrayList<ServerFile> getServerFileList() { return serverFileList; } /** * @return User specified root path of nwn */ public Path getNwnRootPath() { return nwnRootPath; } /** * @return path to json file containing files required by selected server */ public Path getServerFileJson() { return serverFileJson; } /** * Set path of directory containing nwmain.exe * @param newNwnRootPath root path for nwn */ public void setNwnRootPath(Path newNwnRootPath) { nwnRootPath = newNwnRootPath; } /** * @param newServerFileJson path to json file containing required server files */ public void setServerFileJson(Path newServerFileJson) { serverFileJson = newServerFileJson; } }