public static void getPersistentMeta(UUID uuid, String key, RunnableVal<byte[]> result) {
   PlotPlayer player = UUIDHandler.getPlayer(uuid);
   if (player != null) {
     result.run(player.getPersistentMeta(key));
   } else {
     DBFunc.getPersistentMeta(
         uuid,
         new RunnableVal<Map<String, byte[]>>() {
           @Override
           public void run(Map<String, byte[]> value) {
             result.run(value.get(key));
           }
         });
   }
 }
  /**
   * Format a string with plot information.
   *
   * @param info
   * @param plot
   * @param player
   * @param full
   * @param whenDone
   */
  public static void format(
      String info, Plot plot, PlotPlayer player, boolean full, RunnableVal<String> whenDone) {
    int num = plot.getConnectedPlots().size();
    String alias = !plot.getAlias().isEmpty() ? plot.getAlias() : C.NONE.s();
    Location bot = plot.getCorners()[0];
    String biome = WorldUtil.IMP.getBiome(plot.getArea().worldname, bot.getX(), bot.getZ());
    String trusted = getPlayerList(plot.getTrusted());
    String members = getPlayerList(plot.getMembers());
    String denied = getPlayerList(plot.getDenied());
    String seen;
    if (Settings.Enabled_Components.PLOT_EXPIRY) {
      if (plot.isOnline()) {
        seen = C.NOW.s();
      } else {
        int time = (int) (ExpireManager.IMP.getAge(plot) / 1000);
        seen = time != 0 ? secToTime(time) : C.UNKNOWN.s();
      }
    } else {
      seen = C.NEVER.s();
    }
    Optional<String> descriptionFlag = plot.getFlag(Flags.DESCRIPTION);
    String description =
        descriptionFlag.isPresent()
            ? Flags.DESCRIPTION.valueToString(descriptionFlag.get())
            : C.NONE.s();

    StringBuilder flags = new StringBuilder();
    HashMap<Flag<?>, Object> flagMap =
        FlagManager.getPlotFlags(plot.getArea(), plot.getSettings(), true);
    if (flagMap.isEmpty()) {
      flags.append(C.NONE.s());
    } else {
      String prefix = "";
      for (Map.Entry<Flag<?>, Object> entry : flagMap.entrySet()) {
        flags.append(prefix).append(C.PLOT_FLAG_LIST.f(entry.getKey().getName(), entry.getValue()));
        prefix = ", ";
      }
    }
    boolean build = plot.isAdded(player.getUUID());
    String owner = plot.getOwners().isEmpty() ? "unowned" : getPlayerList(plot.getOwners());
    info = info.replace("%id%", plot.getId().toString());
    info = info.replace("%alias%", alias);
    info = info.replace("%num%", String.valueOf(num));
    info = info.replace("%desc%", description);
    info = info.replace("%biome%", biome);
    info = info.replace("%owner%", owner);
    info = info.replace("%members%", members);
    info = info.replace("%player%", player.getName());
    info = info.replace("%trusted%", trusted);
    info = info.replace("%helpers%", members);
    info = info.replace("%denied%", denied);
    info = info.replace("%seen%", seen);
    info = info.replace("%flags%", flags);
    info = info.replace("%build%", String.valueOf(build));
    info = info.replace("%desc%", "No description set.");
    if (info.contains("%rating%")) {
      String newInfo = info;
      TaskManager.runTaskAsync(
          () -> {
            int max = 10;
            if (Settings.Ratings.CATEGORIES != null && !Settings.Ratings.CATEGORIES.isEmpty()) {
              max = 8;
            }
            String info1;
            if (full
                && Settings.Ratings.CATEGORIES != null
                && Settings.Ratings.CATEGORIES.size() > 1) {
              double[] ratings = getAverageRatings(plot);
              String rating = "";
              String prefix = "";
              for (int i = 0; i < ratings.length; i++) {
                rating +=
                    prefix
                        + Settings.Ratings.CATEGORIES.get(i)
                        + '='
                        + String.format("%.1f", ratings[i]);
                prefix = ",";
              }
              info1 = newInfo.replaceAll("%rating%", rating);
            } else {
              info1 =
                  newInfo.replaceAll(
                      "%rating%", String.format("%.1f", plot.getAverageRating()) + '/' + max);
            }
            whenDone.run(info1);
          });
      return;
    }
    whenDone.run(info);
  }
 public static void upload(
     UUID uuid,
     String file,
     String extension,
     RunnableVal<OutputStream> writeTask,
     RunnableVal<URL> whenDone) {
   if (writeTask == null) {
     PS.debug("&cWrite task cannot be null");
     TaskManager.runTask(whenDone);
     return;
   }
   String filename;
   String website;
   if (uuid == null) {
     uuid = UUID.randomUUID();
     website = Settings.Web.URL + "upload.php?" + uuid;
     filename = "plot." + extension;
   } else {
     website = Settings.Web.URL + "save.php?" + uuid;
     filename = file + '.' + extension;
   }
   URL url;
   try {
     url =
         new URL(
             Settings.Web.URL
                 + "?key="
                 + uuid
                 + "&ip="
                 + Settings.Web.SERVER_IP
                 + "&type="
                 + extension);
   } catch (MalformedURLException e) {
     e.printStackTrace();
     whenDone.run();
     return;
   }
   TaskManager.runTaskAsync(
       () -> {
         try {
           String boundary = Long.toHexString(System.currentTimeMillis());
           URLConnection con = new URL(website).openConnection();
           con.setDoOutput(true);
           con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
           try (OutputStream output = con.getOutputStream();
               PrintWriter writer =
                   new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true)) {
             String CRLF = "\r\n";
             writer.append("--").append(boundary).append(CRLF);
             writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
             writer
                 .append("Content-Type: text/plain; charset=")
                 .append(StandardCharsets.UTF_8.displayName())
                 .append(CRLF);
             String param = "value";
             writer.append(CRLF).append(param).append(CRLF).flush();
             writer.append("--").append(boundary).append(CRLF);
             writer
                 .append("Content-Disposition: form-data; name=\"schematicFile\"; filename=\"")
                 .append(filename)
                 .append('"')
                 .append(CRLF);
             writer
                 .append("Content-Type: ")
                 .append(URLConnection.guessContentTypeFromName(filename))
                 .append(CRLF);
             writer.append("Content-Transfer-Encoding: binary").append(CRLF);
             writer.append(CRLF).flush();
             writeTask.value = output;
             writeTask.run();
             output.flush();
             writer.append(CRLF).flush();
             writer.append("--").append(boundary).append("--").append(CRLF).flush();
           }
           //                    try (Reader response = new InputStreamReader(con.getInputStream(),
           // StandardCharsets.UTF_8)) {
           //                        final char[] buffer = new char[256];
           //                        final StringBuilder result = new StringBuilder();
           //                        while (true) {
           //                            final int r = response.read(buffer);
           //                            if (r < 0) {
           //                                break;
           //                            }
           //                            result.append(buffer, 0, r);
           //                        }
           //                        if (!result.toString().startsWith("Success")) {
           //                            PS.debug(result);
           //                        }
           //                    } catch (IOException e) {
           //                        e.printStackTrace();
           //                    }
           int responseCode = ((HttpURLConnection) con).getResponseCode();
           if (responseCode == 200) {
             whenDone.value = url;
           }
           TaskManager.runTask(whenDone);
         } catch (IOException e) {
           e.printStackTrace();
           TaskManager.runTask(whenDone);
         }
       });
 }