diff --git a/Essentials/src/com/earth2me/essentials/UserMap.java b/Essentials/src/com/earth2me/essentials/UserMap.java index b41298ed2..467c2693b 100644 --- a/Essentials/src/com/earth2me/essentials/UserMap.java +++ b/Essentials/src/com/earth2me/essentials/UserMap.java @@ -85,8 +85,7 @@ public class UserMap extends CacheLoader implements IConf return new User(player, ess); } } - final File userFolder = new File(ess.getDataFolder(), "userdata"); - final File userFile = new File(userFolder, Util.sanitizeFileName(name) + ".yml"); + final File userFile = getUserFile(name); if (userFile.exists()) { keys.add(name.toLowerCase(Locale.ENGLISH)); @@ -116,4 +115,10 @@ public class UserMap extends CacheLoader implements IConf { return keys.size(); } + + public File getUserFile(final String name) + { + final File userFolder = new File(ess.getDataFolder(), "userdata"); + return new File(userFolder, Util.sanitizeFileName(name) + ".yml"); + } } diff --git a/Essentials/src/com/earth2me/essentials/storage/AbstractDelayedYamlFileWriter.java b/Essentials/src/com/earth2me/essentials/storage/AbstractDelayedYamlFileWriter.java new file mode 100644 index 000000000..697ef7730 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/storage/AbstractDelayedYamlFileWriter.java @@ -0,0 +1,55 @@ +package com.earth2me.essentials.storage; + +import com.earth2me.essentials.IEssentials; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; + + +public abstract class AbstractDelayedYamlFileWriter implements Runnable +{ + private final transient File file; + + public AbstractDelayedYamlFileWriter(IEssentials ess, File file) + { + this.file = file; + ess.scheduleAsyncDelayedTask(this); + } + + public abstract StorageObject getObject(); + + @Override + public void run() + { + PrintWriter pw = null; + try + { + final StorageObject object = getObject(); + final File folder = file.getParentFile(); + if (!folder.exists()) + { + folder.mkdirs(); + } + pw = new PrintWriter(file); + new YamlStorageWriter(pw).save(object); + } + catch (FileNotFoundException ex) + { + Bukkit.getLogger().log(Level.SEVERE, file.toString(), ex); + } + finally + { + onFinish(); + if (pw != null) + { + pw.close(); + } + } + + } + + public abstract void onFinish(); +} diff --git a/Essentials/src/com/earth2me/essentials/userdata/Ban.java b/Essentials/src/com/earth2me/essentials/user/Ban.java similarity index 85% rename from Essentials/src/com/earth2me/essentials/userdata/Ban.java rename to Essentials/src/com/earth2me/essentials/user/Ban.java index 6e86780cf..ba1c22333 100644 --- a/Essentials/src/com/earth2me/essentials/userdata/Ban.java +++ b/Essentials/src/com/earth2me/essentials/user/Ban.java @@ -1,4 +1,4 @@ -package com.earth2me.essentials.userdata; +package com.earth2me.essentials.user; import com.earth2me.essentials.storage.StorageObject; import lombok.Data; diff --git a/Essentials/src/com/earth2me/essentials/user/IOfflinePlayer.java b/Essentials/src/com/earth2me/essentials/user/IOfflinePlayer.java new file mode 100644 index 000000000..4cdee715b --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/IOfflinePlayer.java @@ -0,0 +1,15 @@ +package com.earth2me.essentials.user; + +import org.bukkit.Location; + + +public interface IOfflinePlayer +{ + String getName(); + + String getDisplayName(); + + Location getBedSpawnLocation(); + + void setBanned(boolean bln); +} \ No newline at end of file diff --git a/Essentials/src/com/earth2me/essentials/user/IOfflineUser.java b/Essentials/src/com/earth2me/essentials/user/IOfflineUser.java new file mode 100644 index 000000000..4ff54b37b --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/IOfflineUser.java @@ -0,0 +1,7 @@ +package com.earth2me.essentials.user; + + +public interface IOfflineUser extends IUserData, IOfflinePlayer +{ + +} diff --git a/Essentials/src/com/earth2me/essentials/user/IUserData.java b/Essentials/src/com/earth2me/essentials/user/IUserData.java new file mode 100644 index 000000000..dcaeaf1a7 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/IUserData.java @@ -0,0 +1,13 @@ +package com.earth2me.essentials.user; + + +public interface IUserData +{ + UserData getData(); + + void aquireReadLock(); + + void aquireWriteLock(); + + void close(); +} diff --git a/Essentials/src/com/earth2me/essentials/userdata/Inventory.java b/Essentials/src/com/earth2me/essentials/user/Inventory.java similarity index 94% rename from Essentials/src/com/earth2me/essentials/userdata/Inventory.java rename to Essentials/src/com/earth2me/essentials/user/Inventory.java index 91a19e2d1..0812f54a0 100644 --- a/Essentials/src/com/earth2me/essentials/userdata/Inventory.java +++ b/Essentials/src/com/earth2me/essentials/user/Inventory.java @@ -1,4 +1,4 @@ -package com.earth2me.essentials.userdata; +package com.earth2me.essentials.user; import com.earth2me.essentials.storage.MapKeyType; import com.earth2me.essentials.storage.MapValueType; diff --git a/Essentials/src/com/earth2me/essentials/user/User.java b/Essentials/src/com/earth2me/essentials/user/User.java new file mode 100644 index 000000000..6ad35bfae --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/User.java @@ -0,0 +1,111 @@ +package com.earth2me.essentials.user; + +import com.earth2me.essentials.IEssentials; +import com.earth2me.essentials.storage.AbstractDelayedYamlFileWriter; +import com.earth2me.essentials.storage.StorageObject; +import com.earth2me.essentials.storage.YamlStorageReader; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import lombok.Cleanup; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +// this is a prototype for locking userdata +public class User extends UserBase implements IOfflineUser +{ + private transient UserData data = new UserData(); + private final transient ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + + public User(final Player base, final IEssentials ess) + { + super(base, ess); + } + + public User(final OfflinePlayer offlinePlayer, final IEssentials ess) + { + super(offlinePlayer, ess); + } + + public void loadUserData() + { + data = new YamlStorageReader(null).load(UserData.class); + } + + @Override + public UserData getData() + { + return data; + } + + @Override + public void aquireReadLock() + { + rwl.readLock().lock(); + } + + @Override + public void aquireWriteLock() + { + while (rwl.getReadHoldCount() > 0) + { + rwl.readLock().unlock(); + } + rwl.writeLock().lock(); + rwl.readLock().lock(); + } + + @Override + public void close() + { + if (rwl.isWriteLockedByCurrentThread()) + { + rwl.writeLock().unlock(); + scheduleSaving(); + } + while (rwl.getReadHoldCount() > 0) + { + rwl.readLock().unlock(); + } + } + + public void example() + { + // Cleanup will call close at the end of the function + @Cleanup + final User user = this; + + // read lock allows to read data from the user + user.aquireReadLock(); + final double money = user.getData().getMoney(); + + // write lock allows only one thread to modify the data + user.aquireWriteLock(); + user.getData().setMoney(10 + money); + } + + private void scheduleSaving() + { + new UserDataWriter(); + } + + + private class UserDataWriter extends AbstractDelayedYamlFileWriter + { + public UserDataWriter() + { + super(ess, ess.getUserMap().getUserFile(User.this.getName())); + } + + @Override + public StorageObject getObject() + { + aquireReadLock(); + return getData(); + } + + @Override + public void onFinish() + { + close(); + } + } +} diff --git a/Essentials/src/com/earth2me/essentials/user/UserBase.java b/Essentials/src/com/earth2me/essentials/user/UserBase.java new file mode 100644 index 000000000..cc071817e --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/UserBase.java @@ -0,0 +1,115 @@ +package com.earth2me.essentials.user; + +import com.earth2me.essentials.IEssentials; +import com.earth2me.essentials.craftbukkit.OfflineBedLocation; +import lombok.Delegate; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.ServerOperator; +import org.bukkit.OfflinePlayer; + + +public class UserBase implements Player, IOfflinePlayer +{ + + @Delegate(types = + { + Player.class, Entity.class, CommandSender.class, ServerOperator.class, + HumanEntity.class, ConfigurationSerializable.class, LivingEntity.class, + Permissible.class + },excludes=IOfflinePlayer.class) + protected Player base; + protected transient OfflinePlayer offlinePlayer; + protected final transient IEssentials ess; + + public UserBase(final Player base, final IEssentials ess) + { + this.base = base; + this.ess = ess; + } + + public UserBase(final OfflinePlayer offlinePlayer, final IEssentials ess) + { + this.offlinePlayer = offlinePlayer; + this.ess = ess; + } + + public final Player getBase() + { + return base; + } + + public final Player setBase(final Player base) + { + return this.base = base; + } + + public void update(final Player base) + { + setBase(base); + } + + public void update(final OfflinePlayer offlinePlayer) + { + this.offlinePlayer = offlinePlayer; + } + + public void dispose() + { + this.offlinePlayer = Bukkit.getOfflinePlayer(base.getName()); + this.base = null; + } + + public boolean isOnlineUser() { + return base != null; + } + + @Override + public String getName() + { + if (isOnlineUser()) { + return base.getName(); + } else { + return offlinePlayer.getName(); + } + } + + @Override + public String getDisplayName() + { + if (isOnlineUser()) { + return base.getDisplayName(); + } else { + return offlinePlayer.getName(); + } + } + + @Override + public Location getBedSpawnLocation() + { + if (isOnlineUser()) { + return base.getBedSpawnLocation(); + } else { + return OfflineBedLocation.getBedLocation(base.getName(), ess); + } + } + + @Override + public void setBanned(boolean bln) + { + if (isOnlineUser()) { + base.setBanned(bln); + } else { + offlinePlayer.setBanned(bln); + } + } + + +} diff --git a/Essentials/src/com/earth2me/essentials/userdata/UserData.java b/Essentials/src/com/earth2me/essentials/user/UserData.java similarity index 97% rename from Essentials/src/com/earth2me/essentials/userdata/UserData.java rename to Essentials/src/com/earth2me/essentials/user/UserData.java index fe537cae8..4586d0627 100644 --- a/Essentials/src/com/earth2me/essentials/userdata/UserData.java +++ b/Essentials/src/com/earth2me/essentials/user/UserData.java @@ -1,4 +1,4 @@ -package com.earth2me.essentials.userdata; +package com.earth2me.essentials.user; import com.earth2me.essentials.storage.ListType; import com.earth2me.essentials.storage.MapKeyType; diff --git a/Essentials/src/com/earth2me/essentials/user/UserMap.java b/Essentials/src/com/earth2me/essentials/user/UserMap.java new file mode 100644 index 000000000..821ee4c8f --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/user/UserMap.java @@ -0,0 +1,128 @@ +package com.earth2me.essentials.user; + +import com.earth2me.essentials.IConf; +import com.earth2me.essentials.IEssentials; +import com.earth2me.essentials.Util; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.io.File; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.ExecutionException; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + + +public class UserMap extends CacheLoader implements IConf +{ + private final transient IEssentials ess; + private final transient Cache users = CacheBuilder.newBuilder().softValues().build(this); + private final transient ConcurrentSkipListSet keys = new ConcurrentSkipListSet(); + + public UserMap(final IEssentials ess) + { + super(); + this.ess = ess; + loadAllUsersAsync(ess); + } + + private void loadAllUsersAsync(final IEssentials ess) + { + ess.scheduleAsyncDelayedTask(new Runnable() + { + @Override + public void run() + { + final File userdir = new File(ess.getDataFolder(), "userdata"); + if (!userdir.exists()) + { + return; + } + keys.clear(); + users.invalidateAll(); + for (String string : userdir.list()) + { + if (!string.endsWith(".yml")) + { + continue; + } + final String name = string.substring(0, string.length() - 4); + keys.add(name.toLowerCase(Locale.ENGLISH)); + } + } + }); + } + + public boolean userExists(final String name) + { + return keys.contains(name.toLowerCase(Locale.ENGLISH)); + } + + public User getUser(final String name) + { + try + { + return users.get(name.toLowerCase(Locale.ENGLISH)); + } + catch (ExecutionException ex) + { + return null; + } + catch (UncheckedExecutionException ex) + { + return null; + } + } + + @Override + public User load(final String name) throws Exception + { + for (Player player : ess.getServer().getOnlinePlayers()) + { + if (player.getName().equalsIgnoreCase(name)) + { + keys.add(name.toLowerCase(Locale.ENGLISH)); + return new User(player, ess); + } + } + final File userFile = getUserFile(name); + if (userFile.exists()) + { + keys.add(name.toLowerCase(Locale.ENGLISH)); + return new User(Bukkit.getOfflinePlayer(name), ess); + } + throw new Exception("User not found!"); + } + + @Override + public void reloadConfig() + { + loadAllUsersAsync(ess); + } + + public void removeUser(final String name) + { + keys.remove(name.toLowerCase(Locale.ENGLISH)); + users.invalidate(name.toLowerCase(Locale.ENGLISH)); + } + + public Set getAllUniqueUsers() + { + return Collections.unmodifiableSet(keys); + } + + public int getUniqueUsers() + { + return keys.size(); + } + + public File getUserFile(final String name) + { + final File userFolder = new File(ess.getDataFolder(), "userdata"); + return new File(userFolder, Util.sanitizeFileName(name) + ".yml"); + } +} diff --git a/Essentials/src/com/earth2me/essentials/userdata/User.java b/Essentials/src/com/earth2me/essentials/userdata/User.java deleted file mode 100644 index 232e0b95e..000000000 --- a/Essentials/src/com/earth2me/essentials/userdata/User.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.earth2me.essentials.userdata; - -import com.earth2me.essentials.storage.YamlStorageReader; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import lombok.Cleanup; - -// this is a prototype for locking userdata -public class User -{ - UserData data = new UserData(); - ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - - public void loadUserData() - { - data = new YamlStorageReader(null).load(UserData.class); - } - - public void aquireReadLock() - { - rwl.readLock().lock(); - } - - public void aquireWriteLock() - { - while (rwl.getReadHoldCount() > 0) - { - rwl.readLock().unlock(); - } - rwl.writeLock().lock(); - rwl.readLock().lock(); - } - - public void close() - { - if (rwl.isWriteLockedByCurrentThread()) - { - scheduleSaving(); - rwl.writeLock().unlock(); - } - while (rwl.getReadHoldCount() > 0) - { - rwl.readLock().unlock(); - } - } - - public void example() - { - // Cleanup will call close at the end of the function - @Cleanup - final User user = this; - - // read lock allows to read data from the user - user.aquireReadLock(); - double i = user.data.getMoney(); - - // write lock allows only one thread to modify the data - user.aquireWriteLock(); - user.data.setMoney(10 + user.data.getMoney()); - } - - private void scheduleSaving() - { - System.out.println("Schedule saving..."); - } -} diff --git a/Essentials/test/com/earth2me/essentials/StorageTest.java b/Essentials/test/com/earth2me/essentials/StorageTest.java index e6850ce91..d57d88542 100644 --- a/Essentials/test/com/earth2me/essentials/StorageTest.java +++ b/Essentials/test/com/earth2me/essentials/StorageTest.java @@ -80,11 +80,11 @@ public class StorageTest extends TestCase ext.start(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); final Reader reader = new InputStreamReader(bais); - final com.earth2me.essentials.userdata.UserData userdata = new YamlStorageReader(reader).load(com.earth2me.essentials.userdata.UserData.class); + final com.earth2me.essentials.user.UserData userdata = new YamlStorageReader(reader).load(com.earth2me.essentials.user.UserData.class); ext.mark("load empty user"); final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]); final Reader reader3 = new InputStreamReader(bais3); - final com.earth2me.essentials.userdata.UserData userdata3 = new YamlStorageReader(reader3).load(com.earth2me.essentials.userdata.UserData.class); + final com.earth2me.essentials.user.UserData userdata3 = new YamlStorageReader(reader3).load(com.earth2me.essentials.user.UserData.class); ext.mark("load empty user (class cached)"); for (int j = 0; j < 10000; j++) @@ -107,16 +107,16 @@ public class StorageTest extends TestCase ext.mark("debug output"); final ByteArrayInputStream bais2 = new ByteArrayInputStream(written); final Reader reader2 = new InputStreamReader(bais2); - final com.earth2me.essentials.userdata.UserData userdata2 = new YamlStorageReader(reader2).load(com.earth2me.essentials.userdata.UserData.class); + final com.earth2me.essentials.user.UserData userdata2 = new YamlStorageReader(reader2).load(com.earth2me.essentials.user.UserData.class); ext.mark("reload file"); final ByteArrayInputStream bais4 = new ByteArrayInputStream(written); final Reader reader4 = new InputStreamReader(bais4); - final com.earth2me.essentials.userdata.UserData userdata4 = new YamlStorageReader(reader4).load(com.earth2me.essentials.userdata.UserData.class); + final com.earth2me.essentials.user.UserData userdata4 = new YamlStorageReader(reader4).load(com.earth2me.essentials.user.UserData.class); ext.mark("reload file (cached)"); System.out.println(userdata.toString()); System.out.println(userdata2.toString()); System.out.println(ext.end()); - com.earth2me.essentials.userdata.User test = new com.earth2me.essentials.userdata.User(); + com.earth2me.essentials.user.User test = new com.earth2me.essentials.user.User(null, ess); test.example(); }