Package ch.njol.skript.variables

Source Code of ch.njol.skript.variables.VariablesStorage

/*
*   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.variables;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.log.ParseLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.FileUtils;
import ch.njol.skript.util.Task;
import ch.njol.skript.util.Timespan;
import ch.njol.skript.variables.SerializedVariable.Value;
import ch.njol.util.Closeable;

/**
* @author Peter Güttinger
*/
public abstract class VariablesStorage implements Closeable {
 
  private final static int QUEUE_SIZE = 1000, FIRST_WARNING = 300;
 
  final LinkedBlockingQueue<SerializedVariable> changesQueue = new LinkedBlockingQueue<SerializedVariable>(QUEUE_SIZE);
 
  protected volatile boolean closed = false;
 
  protected final String databaseName;
 
  @Nullable
  protected File file;
 
  /**
   * null for '.*' or '.+'
   */
  @Nullable
  private Pattern variablePattern;
 
  // started in load()
  private final Thread writeThread;
 
  protected VariablesStorage(final String name) {
    databaseName = name;
    writeThread = Skript.newThread(new Runnable() {
      @Override
      public void run() {
        while (!closed) {
          try {
            final SerializedVariable var = changesQueue.take();
            final Value d = var.value;
            if (d != null)
              save(var.name, d.type, d.data);
            else
              save(var.name, null, null);
          } catch (final InterruptedException e) {}
        }
      }
    }, "Skript variable save thread for database '" + name + "'");
  }
 
  @Nullable
  protected String getValue(final SectionNode n, final String key) {
    return getValue(n, key, String.class);
  }
 
  @Nullable
  protected <T> T getValue(final SectionNode n, final String key, final Class<T> type) {
    final String v = n.getValue(key);
    if (v == null) {
      Skript.error("The config is missing the entry for '" + key + "' in the database '" + databaseName + "'");
      return null;
    }
    final ParseLogHandler log = SkriptLogger.startParseLogHandler();
    try {
      final T r = Classes.parse(v, type, ParseContext.CONFIG);
      if (r == null)
        log.printError("The entry for '" + key + "' in the database '" + databaseName + "' must be " + Classes.getSuperClassInfo(type).getName().withIndefiniteArticle());
      else
        log.printLog();
      return r;
    } finally {
      log.stop();
    }
  }
 
  public final boolean load(final SectionNode n) {
    final String pattern = getValue(n, "pattern");
    if (pattern == null)
      return false;
    try {
      variablePattern = pattern.equals(".*") || pattern.equals(".+") ? null : Pattern.compile(pattern);
    } catch (final PatternSyntaxException e) {
      Skript.error("Invalid pattern '" + pattern + "': " + e.getLocalizedMessage());
      return false;
    }
   
    if (requiresFile()) {
      final String f = getValue(n, "file");
      if (f == null)
        return false;
      final File file = getFile(f).getAbsoluteFile();
      this.file = file;
      if (file.exists() && !file.isFile()) {
        Skript.error("The database file '" + file.getName() + "' must be an actual file, not a directory.");
        return false;
      } else {
        try {
          file.createNewFile();
        } catch (final IOException e) {
          Skript.error("Cannot create the database file '" + file.getName() + "': " + e.getLocalizedMessage());
          return false;
        }
      }
      if (!file.canWrite()) {
        Skript.error("Cannot write to the database file '" + file.getName() + "'!");
        return false;
      }
      if (!file.canRead()) {
        Skript.error("Cannot read from the database file '" + file.getName() + "'!");
//        Skript.error("This means that no variables will be available and can also prevent new variables from being saved!");
//        try {
//          final File backup = FileUtils.backup(file);
//          Skript.error("A backup of your variables.csv was created as " + backup.getName());
//        } catch (final IOException e) {
//          Skript.error("Failed to create a backup of your variables.csv: " + e.getLocalizedMessage());
//          loadError = true;
//        }
        return false;
      }
     
      if (!"0".equals(getValue(n, "backup interval"))) {
        final Timespan backupInterval = getValue(n, "backup interval", Timespan.class);
        if (backupInterval != null)
          startBackupTask(backupInterval);
      }
    }
   
    if (!load_i(n))
      return false;
   
    writeThread.start();
    Skript.closeOnDisable(this);
   
    return true;
  }
 
  /**
   * Loads variables stored here.
   *
   * @return Whether the database could be loaded successfully, i.e. whether the config is correct and all variables could be loaded
   */
  protected abstract boolean load_i(SectionNode n);
 
  protected abstract boolean requiresFile();
 
  protected abstract File getFile(String file);
 
  /**
   * Must be locked after {@link Variables#getReadLock()} (if that lock is used at all)
   */
  protected final Object fileLock = new Object();
 
  /**
   * (Re)connects to the database (not called on the first connect - do this in {@link #load_i(SectionNode)}).
   *
   * @return Whether the connection could be re-established. An error should be printed by this method prior to returning false.
   */
  protected abstract boolean connect();
 
  /**
   * Disconnects from the database.
   */
  protected abstract void disconnect();
 
  @Nullable
  protected Task backupTask = null;
 
  public void startBackupTask(final Timespan t) {
    final File file = this.file;
    if (file == null || t.getTicks() == 0)
      return;
    backupTask = new Task(Skript.getInstance(), t.getTicks(), t.getTicks(), true) {
      @Override
      public void run() {
        synchronized (fileLock) {
          disconnect();
          try {
            FileUtils.backup(file);
          } catch (final IOException e) {
            Skript.error("Automatic variables backup failed: " + e.getLocalizedMessage());
          } finally {
            connect();
            fileLock.notifyAll();
          }
        }
      }
    };
  }
 
  final boolean accept(final @Nullable String var) {
    if (var == null)
      return false;
    return variablePattern != null ? variablePattern.matcher(var).matches() : true;
  }
 
  private long lastWarning = Long.MIN_VALUE;
  private final static int WARNING_INTERVAL = 10;
  private long lastError = Long.MIN_VALUE;
  private final static int ERROR_INTERVAL = 10;
 
  /**
   * May be called from a different thread than Bukkit's main thread.
   */
  final void save(final SerializedVariable var) {
    if (changesQueue.size() > FIRST_WARNING && lastWarning < System.currentTimeMillis() - WARNING_INTERVAL * 1000) {
      Skript.warning("Cannot write variables to the database '" + databaseName + "' at sufficient speed; server performance may suffer and many variables will be lost if the server crashes. (this warning will be repeated at most once every " + WARNING_INTERVAL + " seconds)");
      lastWarning = System.currentTimeMillis();
    }
    if (!changesQueue.offer(var)) {
      if (lastError < System.currentTimeMillis() - ERROR_INTERVAL * 1000) {
        Skript.error("Skript cannot save any variables to the database '" + databaseName + "'. The server will hang and may crash if no more variables can be saved.");
        lastError = System.currentTimeMillis();
      }
      while (true) {
        try {
          // REMIND add repetitive error and/or stop saving variables altogether?
          changesQueue.put(var);
          break;
        } catch (final InterruptedException e) {}
      }
    }
  }
 
  @Override
  public void close() {
    while (changesQueue.size() > 0) {
      try {
        Thread.sleep(10);
      } catch (final InterruptedException e) {}
    }
    closed = true;
    writeThread.interrupt();
  }
 
  /**
   * Clears the queue of unsaved variables. Only used if all variables are saved immediately after calling this method.
   */
  protected void clearChangesQueue() {
    changesQueue.clear();
  }
 
  /**
   * Saves a variable.
   *
   * @param name
   * @param type
   * @param value
   * @return Whether the variable was saved
   */
  protected abstract boolean save(String name, @Nullable String type, @Nullable byte[] value);
 
}
TOP

Related Classes of ch.njol.skript.variables.VariablesStorage

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.