mirror of
https://github.com/EpicEricEE/ShopChest.git
synced 2024-11-25 03:55:22 +01:00
Use CompletedFuture as callbacks
This commit is contained in:
parent
ae38519f83
commit
0fe6583485
@ -2,6 +2,7 @@ package de.epiceric.shopchest.api;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.bukkit.Location;
|
||||
@ -77,12 +78,11 @@ public interface ShopManager {
|
||||
* Can be either chest if it's on a double chest
|
||||
* @param buyPrice the price a player can buy the product for.
|
||||
* @param sellPrice the price a player can sell the product for.
|
||||
* @param callback the callback returning the created shop on success
|
||||
* @param errorCallback the callback returning the error if one occurred
|
||||
* @return a completable future returning the new shop
|
||||
* @since 1.13
|
||||
* @see ShopManager#addAdminShop(ShopProduct, Location, double, double, Consumer, Consumer)
|
||||
*/
|
||||
void addShop(OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice, Consumer<Shop> callback, Consumer<Throwable> errorCallback);
|
||||
CompletableFuture<Shop> addShop(OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice);
|
||||
|
||||
/**
|
||||
* Creates an admin shop and adds it to the database
|
||||
@ -96,32 +96,29 @@ public interface ShopManager {
|
||||
* Can be either chest if it's on a double chest
|
||||
* @param buyPrice the price a player can buy the product for.
|
||||
* @param sellPrice the price a player can sell the product for.
|
||||
* @param callback the callback returning the created shop on success
|
||||
* @param errorCallback the callback returning the error if one occurred
|
||||
* @return a completable future returning the new shop
|
||||
* @since 1.13
|
||||
* @see ShopManager#addShop(OfflinePlayer, ShopProduct, Location, double, double, Consumer, Consumer)
|
||||
*/
|
||||
void addAdminShop(ShopProduct product, Location location, double buyPrice, double sellPrice, Consumer<Shop> callback, Consumer<Throwable> errorCallback);
|
||||
CompletableFuture<Shop> addAdminShop(ShopProduct product, Location location, double buyPrice, double sellPrice);
|
||||
|
||||
/**
|
||||
* Removes a shop from the database
|
||||
*
|
||||
* @param shop the shop to remove
|
||||
* @param callback the callback returning nothing on success
|
||||
* @param errorCallback the callback returning the error if one occurred
|
||||
* @return a completable future returning nothing
|
||||
* @since 1.13
|
||||
*/
|
||||
void removeShop(Shop shop, Runnable callback, Consumer<Throwable> errorCallback);
|
||||
CompletableFuture<Void> removeShop(Shop shop);
|
||||
|
||||
/**
|
||||
* Removes all shops and reloads the shops in currently loaded chunks
|
||||
* <p>
|
||||
* This does not trigger the {@link ShopReloadEvent}.
|
||||
*
|
||||
* @param callback the callback returning the amount of shops on success
|
||||
* @param errorCallback the callback returning the error if one occurred
|
||||
* @return a completable future returning the loaded shops
|
||||
* @since 1.13
|
||||
*/
|
||||
void reloadShops(Consumer<Integer> callback, Consumer<Throwable> errorCallback);
|
||||
CompletableFuture<Collection<Shop>> reloadShops();
|
||||
|
||||
}
|
@ -6,15 +6,12 @@ import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
@ -26,7 +23,6 @@ import de.epiceric.shopchest.api.ShopManager;
|
||||
import de.epiceric.shopchest.api.command.ShopCommand;
|
||||
import de.epiceric.shopchest.api.config.Config;
|
||||
import de.epiceric.shopchest.api.database.DatabaseType;
|
||||
import de.epiceric.shopchest.api.event.ShopLoadedEvent;
|
||||
import de.epiceric.shopchest.api.player.ShopPlayer;
|
||||
import de.epiceric.shopchest.command.ShopCommandImpl;
|
||||
import de.epiceric.shopchest.config.ConfigManager;
|
||||
@ -182,31 +178,21 @@ public class ShopChestImpl extends ShopChest {
|
||||
database = new MySQL(this);
|
||||
}
|
||||
|
||||
((ShopManagerImpl) getShopManager()).loadShopAmounts(
|
||||
shopAmounts -> {
|
||||
Logger.info("Loaded shop amounts from the database");
|
||||
},
|
||||
error -> {
|
||||
Logger.severe("Failed to load shops amounts from the database");
|
||||
Logger.severe("Shop limits will not be working correctly");
|
||||
Logger.severe(error);
|
||||
}
|
||||
);
|
||||
|
||||
Chunk[] chunks = getServer().getWorlds().stream().map(World::getLoadedChunks)
|
||||
.flatMap(Stream::of).toArray(Chunk[]::new);
|
||||
|
||||
((ShopManagerImpl) getShopManager()).loadShops(chunks,
|
||||
shops -> {
|
||||
getServer().getPluginManager().callEvent(new ShopLoadedEvent(shops));
|
||||
Logger.info("Loaded {0} shops from the database", shops.size());
|
||||
},
|
||||
error -> {
|
||||
Logger.severe("Failed to load shops from the database");
|
||||
Logger.severe(error);
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
);
|
||||
getDatabase().connect()
|
||||
.thenCompose(amount -> ((ShopManagerImpl) getShopManager()).loadShopAmounts())
|
||||
.thenCompose(shopAmounts -> {
|
||||
Logger.info("Loaded shop amounts from the database");
|
||||
return getShopManager().reloadShops();
|
||||
})
|
||||
.thenAccept(shops -> {
|
||||
Logger.info("Loaded {0} shops from the database", shops.size());
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
Logger.severe("Failed to load shops from the database");
|
||||
Logger.severe(ex);
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void unloadDatabase() {
|
||||
|
@ -9,7 +9,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -69,9 +69,11 @@ public class ShopManagerImpl implements ShopManager {
|
||||
* Destoys all shops and clears the cache
|
||||
*/
|
||||
public void clearShops() {
|
||||
shopsInWorld.values().stream().flatMap(map -> map.values().stream())
|
||||
.forEach(shop -> ((ShopImpl) shop).destroy());
|
||||
shopsInWorld.clear();
|
||||
synchronized (shopsInWorld) {
|
||||
shopsInWorld.values().stream().flatMap(map -> map.values().stream())
|
||||
.forEach(shop -> ((ShopImpl) shop).destroy());
|
||||
shopsInWorld.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,56 +81,52 @@ public class ShopManagerImpl implements ShopManager {
|
||||
* <p>
|
||||
* This will fire a {@link ShopLoadedEvent}.
|
||||
*
|
||||
* @param chunks a collection
|
||||
* @param chunks the chunks whose shops will be loaded
|
||||
* @return a completable future returning the loaded shops
|
||||
*/
|
||||
public void loadShops(Chunk[] chunks, Consumer<Collection<Shop>> callback, Consumer<Throwable> errorCallback) {
|
||||
plugin.getDatabase().connect(
|
||||
amount -> {
|
||||
plugin.getDatabase().getShops(chunks,
|
||||
shops -> {
|
||||
for (Iterator<Shop> it = shops.iterator(); it.hasNext();) {
|
||||
Shop shop = it.next();
|
||||
public CompletableFuture<Collection<Shop>> loadShops(Chunk[] chunks) {
|
||||
return plugin.getDatabase().getShops(chunks).thenApply(shops -> {
|
||||
for (Iterator<Shop> it = shops.iterator(); it.hasNext();) {
|
||||
Shop shop = it.next();
|
||||
|
||||
if (getShop(shop.getLocation()).isPresent()) {
|
||||
// A shop is already loaded at the location, which should be the same.
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
if (getShop(shop.getLocation()).isPresent()) {
|
||||
// A shop is already loaded at the location, which should be the same.
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
String worldName = shop.getWorld().getName();
|
||||
if (!shopsInWorld.containsKey(worldName)) {
|
||||
shopsInWorld.put(worldName, new HashMap<>());
|
||||
}
|
||||
synchronized (shopsInWorld) {
|
||||
String worldName = shop.getWorld().getName();
|
||||
if (!shopsInWorld.containsKey(worldName)) {
|
||||
shopsInWorld.put(worldName, new HashMap<>());
|
||||
}
|
||||
|
||||
((ShopImpl) shop).create();
|
||||
((ShopImpl) shop).create();
|
||||
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(shop.getLocation()), shop);
|
||||
((ShopImpl) shop).getOtherLocation().ifPresent(otherLoc ->
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(otherLoc), shop));
|
||||
}
|
||||
|
||||
callback.accept(Collections.unmodifiableCollection(shops));
|
||||
plugin.getServer().getPluginManager().callEvent(new ShopLoadedEvent(shops));
|
||||
},
|
||||
errorCallback
|
||||
);
|
||||
},
|
||||
errorCallback
|
||||
);
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(shop.getLocation()), shop);
|
||||
((ShopImpl) shop).getOtherLocation().ifPresent(otherLoc ->
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(otherLoc), shop));
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Shop> unmodifiableShops = Collections.unmodifiableCollection(shops);
|
||||
plugin.getServer().getPluginManager().callEvent(new ShopLoadedEvent(unmodifiableShops));
|
||||
return unmodifiableShops;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all players' shop amounts from the database
|
||||
*
|
||||
* @return a completable future returning nothing
|
||||
*/
|
||||
public void loadShopAmounts(Consumer<Map<UUID, Integer>> callback, Consumer<Throwable> errorCallback) {
|
||||
plugin.getDatabase().getShopAmounts(
|
||||
shopAmounts -> {
|
||||
public CompletableFuture<Void> loadShopAmounts() {
|
||||
return plugin.getDatabase().getShopAmounts().thenAccept(shopAmounts -> {
|
||||
synchronized (shopAmounts) {
|
||||
this.shopAmounts.clear();
|
||||
shopAmounts.forEach((uuid, amount) -> this.shopAmounts.put(uuid, new Counter(amount)));
|
||||
callback.accept(shopAmounts);
|
||||
},
|
||||
errorCallback
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,15 +137,19 @@ public class ShopManagerImpl implements ShopManager {
|
||||
* @see ShopPlayer#getShopAmount()
|
||||
*/
|
||||
public int getShopAmount(OfflinePlayer player) {
|
||||
return shopAmounts.getOrDefault(player.getUniqueId(), new Counter()).get();
|
||||
synchronized (shopAmounts) {
|
||||
return shopAmounts.getOrDefault(player.getUniqueId(), new Counter()).get();
|
||||
}
|
||||
}
|
||||
|
||||
/* API Implementation */
|
||||
|
||||
@Override
|
||||
public Collection<Shop> getShops() {
|
||||
return shopsInWorld.values().stream().flatMap((map) -> map.values().stream()).distinct()
|
||||
.collect(Collectors.toList());
|
||||
synchronized (shopsInWorld) {
|
||||
return shopsInWorld.values().stream().flatMap((map) -> map.values().stream()).distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,8 +163,10 @@ public class ShopManagerImpl implements ShopManager {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(shopsInWorld.get(location.getWorld().getName()))
|
||||
.map(map -> map.get(toBlockLocation(location)));
|
||||
synchronized (shopsInWorld) {
|
||||
return Optional.ofNullable(shopsInWorld.get(location.getWorld().getName()))
|
||||
.map(map -> map.get(toBlockLocation(location)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -174,72 +178,79 @@ public class ShopManagerImpl implements ShopManager {
|
||||
|
||||
@Override
|
||||
public Collection<Shop> getShops(World world) {
|
||||
if (!shopsInWorld.containsKey(world.getName())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
synchronized (shopsInWorld) {
|
||||
if (!shopsInWorld.containsKey(world.getName())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return shopsInWorld.get(world.getName()).values().stream().distinct().collect(Collectors.toList());
|
||||
return shopsInWorld.get(world.getName()).values().stream().distinct().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addShop(OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice, Consumer<Shop> callback, Consumer<Throwable> errorCallback) {
|
||||
Shop shop = new ShopImpl(vendor, product, location, buyPrice, sellPrice);
|
||||
plugin.getDatabase().addShop(shop,
|
||||
id -> {
|
||||
((ShopImpl) shop).create();
|
||||
public CompletableFuture<Shop> addShop(OfflinePlayer vendor, ShopProduct product, Location location, double buyPrice, double sellPrice) {
|
||||
ShopImpl shop = new ShopImpl(vendor, product, location, buyPrice, sellPrice);
|
||||
return plugin.getDatabase().addShop(shop).thenApply(id -> {
|
||||
shop.create();
|
||||
shop.setId(id);
|
||||
|
||||
synchronized (shopsInWorld) {
|
||||
String worldName = location.getWorld().getName();
|
||||
if (!shopsInWorld.containsKey(worldName)) {
|
||||
shopsInWorld.put(worldName, new HashMap<>());
|
||||
}
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(location), shop);
|
||||
|
||||
((ShopImpl) shop).getOtherLocation().ifPresent(otherLoc ->
|
||||
shop.getOtherLocation().ifPresent(otherLoc ->
|
||||
shopsInWorld.get(worldName).put(toBlockLocation(otherLoc), shop));
|
||||
}
|
||||
|
||||
if (vendor != null) {
|
||||
if (vendor != null) {
|
||||
synchronized (shopAmounts) {
|
||||
shopAmounts.compute(vendor.getUniqueId(), (uuid, counter) -> {
|
||||
return counter == null ? new Counter(1) : counter.increment();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
callback.accept(shop);
|
||||
},
|
||||
errorCallback
|
||||
);
|
||||
return shop;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdminShop(ShopProduct product, Location location, double buyPrice, double sellPrice, Consumer<Shop> callback, Consumer<Throwable> errorCallback) {
|
||||
addShop(null, product, location, buyPrice, sellPrice, callback, errorCallback);
|
||||
public CompletableFuture<Shop> addAdminShop(ShopProduct product, Location location, double buyPrice, double sellPrice) {
|
||||
return addShop(null, product, location, buyPrice, sellPrice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeShop(Shop shop, Runnable callback, Consumer<Throwable> errorCallback) {
|
||||
((ShopImpl) shop).destroy();
|
||||
shopsInWorld.get(shop.getWorld().getName()).remove(shop.getLocation());
|
||||
|
||||
getRemainingShopLocation(shop.getId(), shop.getWorld()).ifPresent(otherLoc ->
|
||||
shopsInWorld.get(shop.getWorld().getName()).remove(otherLoc));
|
||||
public CompletableFuture<Void> removeShop(Shop shop) {
|
||||
return plugin.getDatabase().removeShop(shop).thenRun(() -> {
|
||||
((ShopImpl) shop).destroy();
|
||||
|
||||
synchronized (shopsInWorld) {
|
||||
shopsInWorld.get(shop.getWorld().getName()).remove(shop.getLocation());
|
||||
|
||||
shop.getVendor().ifPresent(vendor -> {
|
||||
shopAmounts.compute(vendor.getUniqueId(), (uuid, counter) -> {
|
||||
return counter == null ? new Counter() : counter.decrement();
|
||||
getRemainingShopLocation(shop.getId(), shop.getWorld()).ifPresent(otherLoc ->
|
||||
shopsInWorld.get(shop.getWorld().getName()).remove(otherLoc));
|
||||
}
|
||||
|
||||
shop.getVendor().ifPresent(vendor -> {
|
||||
synchronized (shopAmounts) {
|
||||
shopAmounts.compute(vendor.getUniqueId(), (uuid, counter) -> {
|
||||
return counter == null ? new Counter() : counter.decrement();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
plugin.getDatabase().removeShop(shop, callback, errorCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadShops(Consumer<Integer> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Collection<Shop>> reloadShops() {
|
||||
clearShops();
|
||||
|
||||
Chunk[] chunks = plugin.getServer().getWorlds().stream().map(World::getLoadedChunks)
|
||||
.flatMap(Stream::of).toArray(Chunk[]::new);
|
||||
|
||||
loadShops(chunks,
|
||||
shops -> callback.accept(shops.size()),
|
||||
errorCallback
|
||||
);
|
||||
return loadShops(chunks);
|
||||
}
|
||||
}
|
@ -29,14 +29,13 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
@ -58,36 +57,6 @@ public abstract class Database {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
protected <T> void callSyncResult(Runnable callback) {
|
||||
if (callback == null) return;
|
||||
|
||||
if (plugin.getServer().isPrimaryThread()) {
|
||||
callback.run();
|
||||
} else {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> callback.run());
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> void callSyncResult(Consumer<T> callback, T result) {
|
||||
if (callback == null) return;
|
||||
|
||||
if (plugin.getServer().isPrimaryThread()) {
|
||||
callback.accept(result);
|
||||
} else {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> callback.accept(result));
|
||||
}
|
||||
}
|
||||
|
||||
protected void callSyncError(Consumer<Throwable> callback, Throwable error) {
|
||||
if (callback == null) return;
|
||||
|
||||
if (plugin.getServer().isPrimaryThread()) {
|
||||
callback.accept(error);
|
||||
} else {
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> callback.accept(error));
|
||||
}
|
||||
}
|
||||
|
||||
abstract HikariDataSource getDataSource();
|
||||
|
||||
abstract String getQueryCreateTableShops();
|
||||
@ -284,15 +253,16 @@ public abstract class Database {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>(Re-)Connects to the the database and initializes it.</p>
|
||||
* Connects or reconnects to the the database and initializes it
|
||||
* <p>
|
||||
* All tables are created if necessary. If the database structure has to be updated,
|
||||
* that is done as well.
|
||||
*
|
||||
* All tables are created if necessary and if the database
|
||||
* structure has to be updated, that is done as well.
|
||||
*
|
||||
* @param callback Callback that - if succeeded - returns the amount of shops
|
||||
* that were found (as {@code int})
|
||||
* @return a completable future that returns the amount of shops found in the database
|
||||
*/
|
||||
public void connect(Consumer<Integer> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Integer> connect() {
|
||||
CompletableFuture<Integer> result = new CompletableFuture<>();
|
||||
|
||||
if (!Config.DATABASE_TABLE_PREFIX.get().matches("^([a-zA-Z0-9\\-\\_]+)?$")) {
|
||||
// Only letters, numbers dashes and underscores are allowed
|
||||
Logger.severe("Database table prefix contains illegal letters, using 'shopchest_' prefix.");
|
||||
@ -312,14 +282,14 @@ public abstract class Database {
|
||||
try {
|
||||
dataSource = getDataSource();
|
||||
} catch (Exception e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataSource == null) {
|
||||
Exception e = new IllegalStateException("Data source is null");
|
||||
Logger.severe("Failed to get data source: {0}", e.getMessage());
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -360,76 +330,83 @@ public abstract class Database {
|
||||
if (rs.next()) {
|
||||
int count = rs.getInt(1);
|
||||
initialized = true;
|
||||
callSyncResult(callback, count);
|
||||
result.complete(count);
|
||||
} else {
|
||||
throw new SQLException("Count result set has no entries");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to initialize or connect to database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a shop from the database
|
||||
* Removes the given shop from the database
|
||||
*
|
||||
* @param shop Shop to remove
|
||||
* @param callback Callback that - if succeeded - returns {@code null}
|
||||
* @param shop the shop to remove
|
||||
* @return a completable future returning nothing
|
||||
*/
|
||||
public void removeShop(Shop shop, Runnable callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Void> removeShop(Shop shop) {
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement("DELETE FROM " + tableShops + " WHERE id = ?")) {
|
||||
ps.setInt(1, shop.getId());
|
||||
ps.executeUpdate();
|
||||
callSyncResult(callback);
|
||||
result.complete(null);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to remove shop from database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shop amounts for each player
|
||||
* Gets shop amounts for each player
|
||||
*
|
||||
* @param callback Callback that returns a map of each player's shop amount
|
||||
* @return a completable future returning a map of each player's shop amounts
|
||||
*/
|
||||
public void getShopAmounts(Consumer<Map<UUID, Integer>> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Map<UUID, Integer>> getShopAmounts() {
|
||||
CompletableFuture<Map<UUID, Integer>> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
Statement s = con.createStatement()) {
|
||||
ResultSet rs = s.executeQuery("SELECT vendor, COUNT(*) AS count FROM " + tableShops + " WHERE shoptype = 'NORMAL' GROUP BY vendor");
|
||||
|
||||
Map<UUID, Integer> result = new HashMap<>();
|
||||
Map<UUID, Integer> shopAmounts = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
UUID uuid = UUID.fromString(rs.getString("vendor"));
|
||||
result.put(uuid, rs.getInt("count"));
|
||||
shopAmounts.put(uuid, rs.getInt("count"));
|
||||
}
|
||||
|
||||
callSyncResult(callback, result);
|
||||
result.complete(shopAmounts);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to get shop amounts from database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all shops from the database
|
||||
* Gets the shops in the given chunks from the database
|
||||
*
|
||||
* @param showConsoleMessages Whether console messages (errors or warnings)
|
||||
* should be shown
|
||||
* @param callback Callback that - if succeeded - returns a read-only
|
||||
* collection of all shops (as
|
||||
* {@code Collection<Shop>})
|
||||
* @param chunks the chunks whose shops will be retrieved
|
||||
* @return a completable future returning the shops
|
||||
*/
|
||||
public void getShops(Chunk[] chunks, Consumer<Collection<Shop>> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Collection<Shop>> getShops(Chunk[] chunks) {
|
||||
// Split chunks into packages containing each {splitSize} chunks at max
|
||||
int splitSize = 80;
|
||||
int parts = (int) Math.ceil(chunks.length / (double) splitSize);
|
||||
@ -441,6 +418,8 @@ public abstract class Database {
|
||||
splitChunks[i] = tmp;
|
||||
}
|
||||
|
||||
CompletableFuture<Collection<Shop>> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
List<Shop> shops = new ArrayList<>();
|
||||
|
||||
@ -517,7 +496,7 @@ public abstract class Database {
|
||||
shops.add(new ShopImpl(id, vendor, product, location, buyPrice, sellPrice));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to get shops from database");
|
||||
Logger.severe(e);
|
||||
|
||||
@ -525,21 +504,24 @@ public abstract class Database {
|
||||
}
|
||||
}
|
||||
|
||||
callSyncResult(callback, Collections.unmodifiableCollection(shops));
|
||||
result.complete(shops);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a shop to the database
|
||||
* Adds the given shop to the database
|
||||
*
|
||||
* @param shop Shop to add
|
||||
* @param callback Callback that - if succeeded - returns the ID the shop was
|
||||
* given (as {@code int})
|
||||
* @param shop the shop to add
|
||||
* @return a completable future returning the added shop's ID
|
||||
*/
|
||||
public void addShop(Shop shop, Consumer<Integer> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Integer> addShop(Shop shop) {
|
||||
final String queryNoId = "REPLACE INTO " + tableShops + " (vendor,product,amount,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?)";
|
||||
final String queryWithId = "REPLACE INTO " + tableShops + " (id,vendor,product,amount,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
|
||||
|
||||
CompletableFuture<Integer> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
String query = shop.getId() != -1 ? queryWithId : queryNoId;
|
||||
|
||||
@ -572,24 +554,26 @@ public abstract class Database {
|
||||
((ShopImpl) shop).setId(shopId);
|
||||
}
|
||||
|
||||
callSyncResult(callback, shop.getId());
|
||||
result.complete(shop.getId());
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to add shop to database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a shop in the database
|
||||
* Updates the given shop in the database
|
||||
*
|
||||
* @param shop shop to update
|
||||
* @param callback callback that runs on success
|
||||
* @param errorCallback callback that returns an error if one occurred
|
||||
* @return a completable future that returns nothing
|
||||
*/
|
||||
public void updateShop(Shop shop, Runnable callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Void> updateShop(Shop shop) {
|
||||
final String query = "REPLACE INTO " + tableShops + " (id,vendor,product,amount,world,x,y,z,buyprice,sellprice,shoptype) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
@ -607,34 +591,37 @@ public abstract class Database {
|
||||
ps.setString(11, shop.isAdminShop() ? "ADMIN" : "NORMAL");
|
||||
ps.executeUpdate();
|
||||
|
||||
callSyncResult(callback);
|
||||
result.complete(null);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to update shop in database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an economy transaction to the database
|
||||
* Logs an economy transaction to the database
|
||||
*
|
||||
* @param executor Player who bought/sold something
|
||||
* @param shop The {@link Shop} the player bought from or sold to
|
||||
* @param product The {@link ItemStack} that was bought/sold
|
||||
* @param price The price the product was bought or sold for
|
||||
* @param type Whether the executor bought or sold
|
||||
* @param callback Callback that - if succeeded - returns {@code null}
|
||||
* @param executor the player who bought/sold something
|
||||
* @param shop the shop the player bought from or sold to
|
||||
* @param product the item that was bought/sold
|
||||
* @param price the price the product was bought or sold for
|
||||
* @param type whether the executor bought or sold
|
||||
* @return a completable future returning nothing
|
||||
*/
|
||||
public void logEconomy(Player executor, Shop shop, ShopProduct product, double price, Type type, Runnable callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Void> logEconomy(Player executor, Shop shop, ShopProduct product, double price, Type type) {
|
||||
if (!Config.ECONOMY_LOG_ENABLE.get()) {
|
||||
callSyncResult(callback);
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
final String query = "INSERT INTO " + tableLogs + " (shop_id,timestamp,time,player_name,player_uuid,product_name,product,amount,"
|
||||
+ "vendor_name,vendor_uuid,admin,world,x,y,z,price,type) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
||||
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(query)) {
|
||||
@ -660,23 +647,29 @@ public abstract class Database {
|
||||
ps.setString(17, type.toString());
|
||||
ps.executeUpdate();
|
||||
|
||||
callSyncResult(callback);
|
||||
result.complete(null);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to log economy transaction to database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the economy log to reduce file size
|
||||
*
|
||||
* @return a completable future returning nothing
|
||||
*/
|
||||
public void cleanUpEconomy() {
|
||||
public CompletableFuture<Void> cleanUpEconomy() {
|
||||
if (!Config.ECONOMY_LOG_CLEANUP.get()) {
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int cleanupDays = Config.ECONOMY_LOG_CLEANUP_DAYS.get();
|
||||
long time = System.currentTimeMillis() - cleanupDays * 86400000L;
|
||||
@ -689,22 +682,27 @@ public abstract class Database {
|
||||
s.executeUpdate(queryCleanUpLog);
|
||||
s2.executeUpdate(queryCleanUpPlayers);
|
||||
Logger.info("Cleaned up economy log entries older than {0} days", cleanupDays);
|
||||
result.complete(null);
|
||||
} catch (SQLException e) {
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to clean up economy log");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the revenue a player got while he was offline
|
||||
* Gets the revenue a player got while he was offline
|
||||
*
|
||||
* @param player Player whose revenue to get
|
||||
* @param logoutTime Time in milliseconds when he logged out the last time
|
||||
* @param callback Callback that - if succeeded - returns the revenue the
|
||||
* player made while offline (as {@code double})
|
||||
* @param player the player whose revenue to get
|
||||
* @param logoutTime the system time in milliseconds when he logged out the last time
|
||||
* @return a completable future returning the revenue
|
||||
*/
|
||||
public void getRevenue(Player player, long logoutTime, Consumer<Double> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Double> getRevenue(Player player, long logoutTime) {
|
||||
CompletableFuture<Double> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
double revenue = 0;
|
||||
|
||||
@ -727,58 +725,66 @@ public abstract class Database {
|
||||
}
|
||||
}
|
||||
|
||||
callSyncResult(callback, revenue);
|
||||
result.complete(revenue);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.complete(null);
|
||||
Logger.severe("Failed to get revenue of {0} from database", player.getName());
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a logout to the database
|
||||
* Logs the given player's logout to the database
|
||||
*
|
||||
* @param player Player who logged out
|
||||
* @param callback Callback that - if succeeded - returns {@code null}
|
||||
* @param player the player who logged out
|
||||
* @return a completable future returning nothing
|
||||
*/
|
||||
public void logLogout(Player player, Runnable callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Void> logLogout(Player player) {
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement("REPLACE INTO " + tableLogouts + " (player,time) VALUES(?,?)")) {
|
||||
ps.setString(1, player.getUniqueId().toString());
|
||||
ps.setLong(2, System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
callSyncResult(callback);
|
||||
result.complete(null);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to log last logout to database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last logout of a player
|
||||
* Gets the system time in milliseconds when the given player logged out last
|
||||
*
|
||||
* @param player Player who logged out
|
||||
* @param callback Callback that - if succeeded - returns the time in
|
||||
* milliseconds the player logged out (as {@code long})
|
||||
* or {@code -1} if the player has not logged out yet.
|
||||
* @param player the player who logged out
|
||||
* @return a completable future returning the time or -1 if no recent logout has been found
|
||||
*/
|
||||
public void getLastLogout(Player player, Consumer<Long> callback, Consumer<Throwable> errorCallback) {
|
||||
public CompletableFuture<Long> getLastLogout(Player player) {
|
||||
CompletableFuture<Long> result = new CompletableFuture<>();
|
||||
|
||||
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + tableLogouts + " WHERE player = ?")) {
|
||||
ps.setString(1, player.getUniqueId().toString());
|
||||
ResultSet rs = ps.executeQuery();
|
||||
callSyncResult(callback, rs.next() ? rs.getLong("time") : -1L);
|
||||
result.complete(rs.next() ? rs.getLong("time") : -1L);
|
||||
} catch (SQLException e) {
|
||||
callSyncError(errorCallback, e);
|
||||
result.completeExceptionally(e);
|
||||
Logger.severe("Failed to get last logout from database");
|
||||
Logger.severe(e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ public class MySQL extends Database {
|
||||
s.execute("/* ping */ SELECT 1");
|
||||
} catch (SQLException ex) {
|
||||
Logger.severe("Failed to ping MySQL server. Trying to reconnect...");
|
||||
connect(null, null);
|
||||
connect();
|
||||
}
|
||||
}
|
||||
}.runTaskAsynchronously(plugin);
|
||||
|
@ -35,13 +35,14 @@ public class ChunkLoadListener implements Listener {
|
||||
if (newLoadedChunks.isEmpty()) {
|
||||
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||
int chunkCount = newLoadedChunks.size();
|
||||
((ShopManagerImpl) plugin.getShopManager()).loadShops(newLoadedChunks.toArray(new Chunk[chunkCount]),
|
||||
shops -> {},
|
||||
error -> {
|
||||
Logger.severe("Failed to load shops in newly loaded chunks");
|
||||
Logger.severe(error);
|
||||
}
|
||||
);
|
||||
|
||||
((ShopManagerImpl) plugin.getShopManager()).loadShops(newLoadedChunks.toArray(new Chunk[chunkCount]))
|
||||
.exceptionally(ex -> {
|
||||
Logger.severe("Failed to load shops in newly loaded chunks");
|
||||
Logger.severe(ex);
|
||||
return null;
|
||||
});
|
||||
|
||||
newLoadedChunks.clear();
|
||||
}, 10L);
|
||||
}
|
||||
|
@ -46,15 +46,23 @@ public class ShopInteractMonitorListener implements Listener {
|
||||
public void onShopCreate(ShopCreateEvent e) {
|
||||
Shop shop = e.getShop();
|
||||
if (shop.isAdminShop()) {
|
||||
plugin.getShopManager().addAdminShop(shop.getProduct(), shop.getLocation(), shop.getBuyPrice(), shop.getSellPrice(),
|
||||
newShop -> e.getPlayer().sendMessage("§aAdmin shop has been added with ID {0}.", newShop.getId()), // TODO: i18n
|
||||
error -> e.getPlayer().sendMessage("§cFailed to add admin shop: {0}", error.getMessage())
|
||||
);
|
||||
plugin.getShopManager().addAdminShop(shop.getProduct(), shop.getLocation(), shop.getBuyPrice(), shop.getSellPrice())
|
||||
.thenAccept(newShop -> e.getPlayer().sendMessage("§aAdmin shop has been added with ID {0}.", newShop.getId()))
|
||||
.exceptionally(ex -> { // TODO: i18n
|
||||
Logger.severe("Failed to add admin shop");
|
||||
Logger.severe(ex);
|
||||
e.getPlayer().sendMessage("§cFailed to add admin shop: {0}", ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
plugin.getShopManager().addShop(shop.getVendor().get(), shop.getProduct(), shop.getLocation(), shop.getBuyPrice(), shop.getSellPrice(),
|
||||
newShop -> e.getPlayer().sendMessage("§aShop has been added with ID {0}.", newShop.getId()), // TODO: i18n
|
||||
error -> e.getPlayer().sendMessage("§cFailed to add shop: {0}", error.getMessage())
|
||||
);
|
||||
plugin.getShopManager().addShop(shop.getVendor().get(), shop.getProduct(), shop.getLocation(), shop.getBuyPrice(), shop.getSellPrice())
|
||||
.thenAccept(newShop -> e.getPlayer().sendMessage("§aShop has been added with ID {0}.", newShop.getId()))
|
||||
.exceptionally(ex -> { // TODO: i18n
|
||||
Logger.severe("Failed to add shop");
|
||||
Logger.severe(ex);
|
||||
e.getPlayer().sendMessage("§cFailed to add shop: {0}", ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,18 +73,26 @@ public class ShopInteractMonitorListener implements Listener {
|
||||
shop.setSellPrice(e.getSellPrice());
|
||||
shop.setProduct(new ShopProductImpl(e.getItemStack(), e.getAmount()));
|
||||
|
||||
((ShopChestImpl) plugin).getDatabase().updateShop(shop,
|
||||
() -> e.getPlayer().sendMessage("§aShop has been edited."), // TODO: i18n
|
||||
error -> e.getPlayer().sendMessage("§cFailed to save edit: {0}", error.getMessage())
|
||||
);
|
||||
((ShopChestImpl) plugin).getDatabase().updateShop(shop)
|
||||
.thenRun(() -> e.getPlayer().sendMessage("§aShop has been edited.")) // TODO: i18n
|
||||
.exceptionally(ex -> {
|
||||
Logger.severe("Failed to save shop edit");
|
||||
Logger.severe(ex);
|
||||
e.getPlayer().sendMessage("§cFailed to save edit: {0}", ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onShopRemove(ShopRemoveEvent e) {
|
||||
plugin.getShopManager().removeShop(e.getShop(),
|
||||
() -> e.getPlayer().sendMessage("§aShop has been removed."), // TODO: i18n
|
||||
error -> e.getPlayer().sendMessage("§cFailed to remove shop: {0}", error.getMessage())
|
||||
);
|
||||
((ShopChestImpl) plugin).getDatabase().removeShop(e.getShop())
|
||||
.thenRun(() -> e.getPlayer().sendMessage("§aShop has been removed.")) // TODO: i18n
|
||||
.exceptionally(ex -> {
|
||||
Logger.severe("Failed to remove shop");
|
||||
Logger.severe(ex);
|
||||
e.getPlayer().sendMessage("§cFailed to remove shop: {0}", ex.getMessage());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private String getProductJson(ShopProduct product) {
|
||||
|
Loading…
Reference in New Issue
Block a user