Use CompletedFuture as callbacks

This commit is contained in:
Eric 2020-03-21 15:34:31 +01:00
parent ae38519f83
commit 0fe6583485
7 changed files with 277 additions and 260 deletions

View File

@ -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();
}

View File

@ -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 -> {
getDatabase().connect()
.thenCompose(amount -> ((ShopManagerImpl) getShopManager()).loadShopAmounts())
.thenCompose(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));
return getShopManager().reloadShops();
})
.thenAccept(shops -> {
Logger.info("Loaded {0} shops from the database", shops.size());
},
error -> {
})
.exceptionally(ex -> {
Logger.severe("Failed to load shops from the database");
Logger.severe(error);
Logger.severe(ex);
getServer().getPluginManager().disablePlugin(this);
}
);
return null;
});
}
private void unloadDatabase() {

View File

@ -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,23 +69,23 @@ public class ShopManagerImpl implements ShopManager {
* Destoys all shops and clears the cache
*/
public void clearShops() {
synchronized (shopsInWorld) {
shopsInWorld.values().stream().flatMap(map -> map.values().stream())
.forEach(shop -> ((ShopImpl) shop).destroy());
shopsInWorld.clear();
}
}
/**
* Loads shops in the given chunks from the database
* <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 -> {
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();
@ -95,6 +95,7 @@ public class ShopManagerImpl implements ShopManager {
continue;
}
synchronized (shopsInWorld) {
String worldName = shop.getWorld().getName();
if (!shopsInWorld.containsKey(worldName)) {
shopsInWorld.put(worldName, new HashMap<>());
@ -106,29 +107,26 @@ public class ShopManagerImpl implements ShopManager {
((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
);
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,16 +137,20 @@ public class ShopManagerImpl implements ShopManager {
* @see ShopPlayer#getShopAmount()
*/
public int getShopAmount(OfflinePlayer player) {
synchronized (shopAmounts) {
return shopAmounts.getOrDefault(player.getUniqueId(), new Counter()).get();
}
}
/* API Implementation */
@Override
public Collection<Shop> getShops() {
synchronized (shopsInWorld) {
return shopsInWorld.values().stream().flatMap((map) -> map.values().stream()).distinct()
.collect(Collectors.toList());
}
}
@Override
public Optional<Shop> getShop(int id) {
@ -161,9 +163,11 @@ public class ShopManagerImpl implements ShopManager {
return Optional.empty();
}
synchronized (shopsInWorld) {
return Optional.ofNullable(shopsInWorld.get(location.getWorld().getName()))
.map(map -> map.get(toBlockLocation(location)));
}
}
@Override
public Collection<Shop> getShops(OfflinePlayer vendor) {
@ -174,72 +178,79 @@ public class ShopManagerImpl implements ShopManager {
@Override
public Collection<Shop> getShops(World world) {
synchronized (shopsInWorld) {
if (!shopsInWorld.containsKey(world.getName())) {
return new ArrayList<>();
}
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) {
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) {
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());
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);
}
}

View File

@ -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;
}
/**

View File

@ -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);

View File

@ -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 -> {
((ShopManagerImpl) plugin.getShopManager()).loadShops(newLoadedChunks.toArray(new Chunk[chunkCount]))
.exceptionally(ex -> {
Logger.severe("Failed to load shops in newly loaded chunks");
Logger.severe(error);
}
);
Logger.severe(ex);
return null;
});
newLoadedChunks.clear();
}, 10L);
}

View File

@ -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) {