Package ch.njol.skript

Source Code of ch.njol.skript.Updater

/*
*   This file is part of Skript.
*
*  Skript 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.
*
*  Skript 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 Skript.  If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/

package ch.njol.skript;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.lang.StringEscapeUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.eclipse.jdt.annotation.Nullable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import ch.njol.skript.localization.FormattedMessage;
import ch.njol.skript.localization.Message;
import ch.njol.skript.util.Date;
import ch.njol.skript.util.ExceptionUtils;
import ch.njol.skript.util.FileUtils;
import ch.njol.skript.util.Task;
import ch.njol.skript.util.Timespan;
import ch.njol.skript.util.Version;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* @author Peter Güttinger
*/
public final class Updater {
 
  @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS")
  public final static class VersionInfo implements Comparable<VersionInfo> {
    final String name; // exact name, e.g. "2.0 (jar only)"
    final Version version;
    final String downloadURL;
    @Nullable
    @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "used in SkriptCommand")
    String changelog;
    @Nullable
    @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "used in SkriptCommand")
    Date date;
   
    VersionInfo(final String name, final Version version, final String downloadURL) {
      this.name = name;
      this.version = version;
      this.downloadURL = downloadURL;
    }
   
    @Override
    public String toString() {
      return version.toString();
    }
   
    @Override
    public int compareTo(final VersionInfo o) {
      return version.compareTo(o.version);
    }
  }
 
  /**
   * Used to check for updates & get the file download links as required by the Bukkit guidelines
   */
  private final static String filesURL = "https://api.curseforge.com/servermods/files?projectIds=32084";
 
  /**
   * Used to get the changelogs and release dates of the newest files
   */
  private final static String RSSURL = "http://dev.bukkit.org/server-mods/skript/files.rss";
 
  private final static DateFormat RFC2822 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
 
  public static enum UpdateState {
    NOT_STARTED, CHECK_IN_PROGRESS, CHECK_ERROR, CHECKED_FOR_UPDATE, DOWNLOAD_IN_PROGRESS, DOWNLOAD_ERROR, DOWNLOADED;
  }
 
  public final static ReentrantReadWriteLock stateLock = new ReentrantReadWriteLock();
  /**
   * must be synchronised with {@link #stateLock}
   */
  public static volatile UpdateState state = UpdateState.NOT_STARTED;
  final static AtomicReference<String> error = new AtomicReference<String>();
 
  public final static List<VersionInfo> infos = new ArrayList<VersionInfo>();
  public final static AtomicReference<VersionInfo> latest = new AtomicReference<VersionInfo>();
 
  // must be down here as they reference 'error' and 'latest' which are defined above
  public final static Message m_not_started = new Message("updater.not started");
  public final static Message m_checking = new Message("updater.checking");
  public final static Message m_check_in_progress = new Message("updater.check in progress");
  public final static FormattedMessage m_check_error = new FormattedMessage("updater.check error", error);
  public final static Message m_running_latest_version = new Message("updater.running latest version");
  public final static Message m_running_latest_version_beta = new Message("updater.running latest version (beta)");
  public final static FormattedMessage m_update_available = new FormattedMessage("updater.update available", latest, Skript.getVersion());
  public final static FormattedMessage m_downloading = new FormattedMessage("updater.downloading", latest);
  public final static Message m_download_in_progress = new Message("updater.download in progress");
  public final static FormattedMessage m_download_error = new FormattedMessage("updater.download error", error);
  public final static FormattedMessage m_downloaded = new FormattedMessage("updater.downloaded", latest);
  public final static Message m_internal_error = new Message("updater.internal error");
 
  @Nullable
  static Task checkerTask = null;
 
  static void start() {
    checkerTask = new Task(Skript.getInstance(), 0, true) {
      @SuppressWarnings("null")
      @Override
      public void run() {
        if (!SkriptConfig.checkForNewVersion.value())
          return;
        check(Bukkit.getConsoleSender(), SkriptConfig.automaticallyDownloadNewVersion.value(), true);
        final Timespan t = SkriptConfig.updateCheckInterval.value();
        if (t.getTicks() != 0)
          setNextExecution(t.getTicks());
      }
    };
  }
 
  /**
   * @param sender Sender to receive messages
   * @param download Whether to directly download the newest version if one is found
   * @param isAutomatic
   */
  static void check(final CommandSender sender, final boolean download, final boolean isAutomatic) {
    stateLock.writeLock().lock();
    try {
      if (state == UpdateState.CHECK_IN_PROGRESS || state == UpdateState.DOWNLOAD_IN_PROGRESS)
        return;
      state = UpdateState.CHECK_IN_PROGRESS;
    } finally {
      stateLock.writeLock().unlock();
    }
    if (!isAutomatic || Skript.logNormal())
      Skript.info(sender, "" + m_checking);
    Skript.newThread(new Runnable() {
      @Override
      public void run() {
        infos.clear();
       
        InputStream in = null;
        try {
          final URLConnection conn = new URL(filesURL).openConnection();
          conn.setRequestProperty("User-Agent", "Skript/v" + Skript.getVersion() + " (by Njol)");
          in = conn.getInputStream();
          final BufferedReader reader = new BufferedReader(new InputStreamReader(in, conn.getContentEncoding() == null ? "UTF-8" : conn.getContentEncoding()));
          try {
            final String line = reader.readLine();
            if (line != null) {
              final JSONArray a = (JSONArray) JSONValue.parse(line);
              for (final Object o : a) {
                final Object name = ((JSONObject) o).get("name");
                if (!(name instanceof String) || !((String) name).matches("\\d+\\.\\d+(\\.\\d+)?( \\(jar( only)?\\))?"))// not the default version pattern to not match beta/etc. versions
                  continue;
                final Object url = ((JSONObject) o).get("downloadUrl");
                if (!(url instanceof String))
                  continue;
               
                final Version version = new Version(((String) name).contains(" ") ? "" + ((String) name).substring(0, ((String) name).indexOf(' ')) : ((String) name));
                if (version.compareTo(Skript.getVersion()) > 0) {
                  infos.add(new VersionInfo((String) name, version, (String) url));
                }
              }
            }
          } finally {
            reader.close();
          }
         
          if (!infos.isEmpty()) {
            Collections.sort(infos);
            latest.set(infos.get(0));
          } else {
            latest.set(null);
          }
         
          getChangelogs(sender);
         
          final String message = infos.isEmpty() ? (Skript.getVersion().isStable() ? "" + m_running_latest_version : "" + m_running_latest_version_beta) : "" + m_update_available;
          if (isAutomatic && !infos.isEmpty()) {
            Skript.adminBroadcast(message);
          } else {
            Skript.info(sender, message);
          }
         
          if (download && !infos.isEmpty()) {
            stateLock.writeLock().lock();
            try {
              state = UpdateState.DOWNLOAD_IN_PROGRESS;
            } finally {
              stateLock.writeLock().unlock();
            }
            download_i(sender, isAutomatic);
          } else {
            stateLock.writeLock().lock();
            try {
              state = UpdateState.CHECKED_FOR_UPDATE;
            } finally {
              stateLock.writeLock().unlock();
            }
          }
        } catch (final IOException e) {
          stateLock.writeLock().lock();
          try {
            state = UpdateState.CHECK_ERROR;
            error.set(ExceptionUtils.toString(e));
            if (sender != null)
              Skript.error(sender, m_check_error.toString());
          } finally {
            stateLock.writeLock().unlock();
          }
        } catch (final Exception e) {
          if (sender != null)
            Skript.error(sender, m_internal_error.toString());
          Skript.exception(e, "Unexpected error while checking for a new version of Skript");
          stateLock.writeLock().lock();
          try {
            state = UpdateState.CHECK_ERROR;
            error.set(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage());
          } finally {
            stateLock.writeLock().unlock();
          }
        } finally {
          if (in != null) {
            try {
              in.close();
            } catch (final IOException e) {}
          }
        }
      }
    }, "Skript update thread").start();
  }
 
  /**
   * Gets the changelogs and release dates of the newest versions
   *
   * @param sender
   */
  final static void getChangelogs(final CommandSender sender) {
    InputStream in = null;
    InputStreamReader r = null;
    try {
      final URLConnection conn = new URL(RSSURL).openConnection();
      conn.setRequestProperty("User-Agent", "Skript/v" + Skript.getVersion() + " (by Njol)"); // Bukkit returns a 403 (forbidden) if no user agent is set
      in = conn.getInputStream();
      r = new InputStreamReader(in, conn.getContentEncoding() == null ? "UTF-8" : conn.getContentEncoding());
      final XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(r);
     
      infos.clear();
      VersionInfo current = null;
     
      outer: while (reader.hasNext()) {
        XMLEvent e = reader.nextEvent();
        if (e.isStartElement()) {
          final String element = e.asStartElement().getName().getLocalPart();
          if (element.equalsIgnoreCase("title")) {
            final String name = reader.nextEvent().asCharacters().getData().trim();
            for (final VersionInfo i : infos) {
              if (name.equals(i.name)) {
                current = i;
                continue outer;
              }
            }
            current = null;
          } else if (element.equalsIgnoreCase("description")) {
            if (current == null)
              continue;
            final StringBuilder cl = new StringBuilder();
            while ((e = reader.nextEvent()).isCharacters())
              cl.append(e.asCharacters().getData());
            current.changelog = "- " + StringEscapeUtils.unescapeHtml("" + cl).replace("<br>", "").replace("<p>", "").replace("</p>", "").replaceAll("\n(?!\n)", "\n- ");
          } else if (element.equalsIgnoreCase("pubDate")) {
            if (current == null)
              continue;
            synchronized (RFC2822) { // to make FindBugs shut up
              current.date = new Date(RFC2822.parse(reader.nextEvent().asCharacters().getData()).getTime());
            }
          }
        }
      }
    } catch (final IOException e) {
      stateLock.writeLock().lock();
      try {
        state = UpdateState.CHECK_ERROR;
        error.set(ExceptionUtils.toString(e));
        Skript.error(sender, m_check_error.toString());
      } finally {
        stateLock.writeLock().unlock();
      }
    } catch (final Exception e) {
      Skript.error(sender, m_internal_error.toString());
      Skript.exception(e, "Unexpected error while checking for a new version of Skript");
      stateLock.writeLock().lock();
      try {
        state = UpdateState.CHECK_ERROR;
        error.set(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage());
      } finally {
        stateLock.writeLock().unlock();
      }
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (final IOException e) {}
      }
      if (r != null) {
        try {
          r.close();
        } catch (final IOException e) {}
      }
    }
  }
 
  /**
   * Must set {@link #state} to {@link UpdateState#DOWNLOAD_IN_PROGRESS} prior to calling this
   *
   * @param sender
   * @param isAutomatic
   */
  static void download_i(final CommandSender sender, final boolean isAutomatic) {
    assert sender != null;
    stateLock.readLock().lock();
    try {
      if (state != UpdateState.DOWNLOAD_IN_PROGRESS)
        throw new IllegalStateException();
    } finally {
      stateLock.readLock().unlock();
    }
    Skript.info(sender, "" + m_downloading);
//    boolean hasJar = false;
//    ZipInputStream zip = null;
    InputStream in = null;
    try {
      final URLConnection conn = new URL(latest.get().downloadURL).openConnection();
      in = conn.getInputStream();
      assert in != null;
      FileUtils.save(in, new File(Bukkit.getUpdateFolderFile(), "Skript.jar"));
//      zip = new ZipInputStream(conn.getInputStream());
//      ZipEntry entry;
////      boolean hasAliases = false;
//      while ((entry = zip.getNextEntry()) != null) {
//        if (entry.getName().endsWith("Skript.jar")) {
//          assert !hasJar;
//          save(zip, new File(Bukkit.getUpdateFolderFile(), "Skript.jar"));
//          hasJar = true;
//        }// else if (entry.getName().endsWith("aliases.sk")) {
////          assert !hasAliases;
////          saveZipEntry(zip, new File(Skript.getInstance().getDataFolder(), "aliases-" + latest.get().version + ".sk"));
////          hasAliases = true;
////        }
//        zip.closeEntry();
//        if (hasJar)// && hasAliases)
//          break;
//      }
      if (isAutomatic)
        Skript.adminBroadcast("" + m_downloaded);
      else
        Skript.info(sender, "" + m_downloaded);
      stateLock.writeLock().lock();
      try {
        state = UpdateState.DOWNLOADED;
      } finally {
        stateLock.writeLock().unlock();
      }
    } catch (final IOException e) {
      stateLock.writeLock().lock();
      try {
        state = UpdateState.DOWNLOAD_ERROR;
        error.set(ExceptionUtils.toString(e));
        Skript.error(sender, m_download_error.toString());
      } finally {
        stateLock.writeLock().unlock();
      }
    } catch (final Exception e) {
      Skript.exception(e, "Error while downloading the latest version of Skript");
      stateLock.writeLock().lock();
      try {
        state = UpdateState.DOWNLOAD_ERROR;
        error.set(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage());
      } finally {
        stateLock.writeLock().unlock();
      }
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (final IOException e) {}
      }
    }
  }
 
  /**
   * Must only be called if {@link #state} == {@link UpdateState#CHECKED_FOR_UPDATE} or {@link UpdateState#DOWNLOAD_ERROR}
   *
   * @param sender
   */
  public static void download(final CommandSender sender, final boolean isAutomatic) {
    assert sender != null;
    stateLock.writeLock().lock();
    try {
      if (state != UpdateState.CHECKED_FOR_UPDATE && state != UpdateState.DOWNLOAD_ERROR)
        throw new IllegalStateException("Must check for an update first");
      state = UpdateState.DOWNLOAD_IN_PROGRESS;
    } finally {
      stateLock.writeLock().unlock();
    }
    Skript.newThread(new Runnable() {
      @Override
      public void run() {
        download_i(sender, isAutomatic);
      }
    }, "Skript download thread").start();
  }
 
}
TOP

Related Classes of ch.njol.skript.Updater

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.