Java tutorial
/* * Copyright (C) 2013 Spencer Alderman * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package com.codelanx.playtime.update; import com.codelanx.playtime.Playtime; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.logging.Level; import org.bukkit.plugin.Plugin; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; /** * Handles the update process for {@link Playtime} * * @since 1.4.5 * @author 1Rogue * @version 1.4.5 */ public class UpdateHandler { protected final Playtime plugin; protected final Choice choice; protected Result result = Result.INCOMPLETE; protected final String file; protected final int id; protected byte debug = 0; /** * Constructor for {@link UpdateHandler} * * @since 1.4.5 * @version 1.4.5 * * @param plugin The main {@link Playtime} instance * @param choice The downloading option to use * @param id The project id * @param file The name of the current plugin file */ public UpdateHandler(Playtime plugin, Choice choice, int id, String file) { this.plugin = plugin; this.choice = choice; this.id = id; this.file = file; } /** * Runs an update check. Should only be called once (have not made a good work-around yet) * * @since 1.4.5 * @version 1.4.5 */ public void runCheck() { UpdateRunnable ur = new UpdateRunnable(this.plugin, choice, this.id, this.file); ur.setDebug(this.debug); this.plugin.getServer().getScheduler().runTaskLater(this.plugin, ur, 10L); } /** * Handles the appropriate response to an update {@link Result} * * @since 1.4.5 * @version 1.4.5 * * @param result The update {@link Result} */ protected final void handleUpdate(Result result) { this.result = result; if (result == Result.UPDATE_AVAILABLE) { this.registerNewNotifier(); } else { result.handleUpdate(this.plugin.getLogger()); } } /** * Registers a new {@link UpdateListener} about an available update * * @since 1.4.5 * @version 1.4.5 */ protected final void registerNewNotifier() { this.plugin.getListenerManager().registerListener("update", new UpdateListener(this.plugin.getCipher().getString("listener.update"))); } /** * Returns the {@link Result} from the update check * * @since 1.4.5 * @version 1.4.5 * * @return The update {@link Result} */ public final Result getUpdateStatus() { return this.result; } /** * Sets the debug level of this update check * * @since 1.4.5 * @version 1.4.5 * * @param debug The debug level to set */ public final void setDebug(byte debug) { this.debug = debug; } } /** * Runs an update check * * @since 1.4.5 * @author 1Rogue * @version 1.4.5 */ class UpdateRunnable extends UpdateHandler implements Runnable { private final String VERSION_URL; private final String DL_URL = "downloadUrl"; private final String DL_FILE = "fileName"; private final String DL_NAME = "name"; private boolean majorVersionChange = false; private JSONObject latest; /** * Constructor for {@link UpdateRunnable} * * @since 1.4.5 * @version 1.4.5 * * @param plugin The {@link Playtime} instance * @param choice The {@link Choice} for downloading * @param id The project id * @param file The name of the plugin file */ public UpdateRunnable(Playtime plugin, Choice choice, int id, String file) { super(plugin, choice, id, file); this.result = Result.NO_UPDATE; this.VERSION_URL = "https://api.curseforge.com/servermods/files?projectIds=" + id; } /** * Runs the update process * * @since 1.4.5 * @version 1.5.0 */ public void run() { boolean current = false; if (!this.choice.equals(Choice.NO_UPDATE)) { this.getJSON(); if (this.latest != null) { if (this.choice.doCheck()) { this.result = this.checkVersion(); } if (this.choice.doDownload() && this.result == Result.UPDATE_AVAILABLE && !this.majorVersionChange) { this.result = this.download(); } } } this.handleUpdate(this.result); } /** * Downloads the latest jarfile for the {@link Plugin} * * @since 1.4.5 * @version 1.4.5 * * @TODO Add zip file support * @return The download result */ public Result download() { Result back = Result.UPDATED; File updateLoc = this.plugin.getServer().getUpdateFolderFile(); updateLoc.mkdirs(); String url = (String) this.latest.get(this.DL_URL); File location = new File(updateLoc, this.file); ReadableByteChannel rbc = null; FileOutputStream fos = null; try { URL call = new URL(url); rbc = Channels.newChannel(call.openStream()); fos = new FileOutputStream(location); fos.getChannel().transferFrom(rbc, 0, 1 << 24); } catch (MalformedURLException ex) { this.plugin.getLogger().log(Level.SEVERE, "Error finding plugin update to download!", ex); back = Result.ERROR_FILENOTFOUND; } catch (IOException ex) { this.plugin.getLogger().log(Level.SEVERE, "Error transferring plugin data!", ex); back = Result.ERROR_DOWNLOAD_FAILED; } finally { try { if (fos != null) { fos.close(); } if (rbc != null) { rbc.close(); } } catch (IOException ex) { this.plugin.getLogger().log(Level.SEVERE, "Error closing streams/channels for download!", ex); } } return back; } /** * Checks the current {@link Plugin} version against the latest live version * * @since 1.4.5 * @version 1.4.5 * * @return The {@link Result} of the version check */ private Result checkVersion() { Result back = Result.NO_UPDATE; String curVersion = this.plugin.getDescription().getVersion(); String file = (String) this.latest.get(this.DL_NAME); String last = file.substring(file.lastIndexOf("-") + 1, file.length()); if (newVersion(curVersion, last)) { back = Result.UPDATE_AVAILABLE; } return back; } /** * Compares two string versions to determine which is newer. Keep in mind * that conventions of different lengths are considered. The default approach * is that if both numbers are the same up until the extraneous number, the * longer version will be considered "newer". For example: * <ul> * <li> 1.2.3 </li> * <li> 1.2.3.1 </li> * </ul> * * <p>The second option would be considered "newer". Keep in mind if the * second example was "1.2.3.0", it would still be considered newer.</p> * * @since 1.4.5 * @version 1.5.0 * * @param v1 The original version * @param v2 The new version to compare * @return True if v2 is newer, false otherwise */ private boolean newVersion(String v1, String v2) { String[] v1tot = v1.split("\\."); String[] v2tot = v2.split("\\."); for (int i = 0; i < v1tot.length && i < v2tot.length; i++) { this.plugin.getLogger().log(Level.INFO, "Comparing {0} to {1}", new String[] { v1tot[i], v2tot[i] }); if (this.getInt(v1tot[i]) < this.getInt(v2tot[i])) { if (i == 0) { this.majorVersionChange = true; } return true; } } this.plugin.getLogger().log(Level.INFO, "check loop complete, no different found. Checking lengths..."); if (v1tot.length != v2tot.length) { return v1tot.length < v2tot.length; } return false; } /** * Gets an integer from a string, or returns 0 if it is not a number * * @since 1.4.5 * @version 1.4.5 * * @param s The string to convert * @return The numeric value, or 0 if there is no comprehensible value */ private int getInt(String s) { try { return Integer.parseInt(s); } catch (NumberFormatException ex) { this.plugin.getLogger().log(Level.SEVERE, "Error parsing input '{0}'!", s); return 0; } } /** * Gets the {@link JSONObject} from the CurseAPI of the newest project version. * * @since 1.4.5 * @version 1.4.5 */ private void getJSON() { InputStream stream = null; InputStreamReader isr = null; BufferedReader reader = null; String json = null; try { URL call = new URL(this.VERSION_URL); stream = call.openStream(); isr = new InputStreamReader(stream); reader = new BufferedReader(isr); json = reader.readLine(); } catch (MalformedURLException ex) { plugin.getLogger().log(Level.SEVERE, "Error checking for an update", this.debug >= 3 ? ex : ""); this.result = Result.ERROR_BADID; this.latest = null; } catch (IOException ex) { plugin.getLogger().log(Level.SEVERE, "Error checking for an update", this.debug >= 3 ? ex : ""); } finally { try { if (stream != null) { stream.close(); } if (isr != null) { isr.close(); } if (reader != null) { reader.close(); } } catch (IOException ex) { this.plugin.getLogger().log(Level.SEVERE, "Error closing updater streams!", this.debug >= 3 ? ex : ""); } } if (json != null) { JSONArray arr = (JSONArray) JSONValue.parse(json); this.latest = (JSONObject) arr.get(arr.size() - 1); } else { this.latest = null; } } }