package com.forgeessentials.permissions.persistence; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.Enumeration; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.io.FileUtils; import com.forgeessentials.api.UserIdent; import com.forgeessentials.api.permissions.AreaZone; import com.forgeessentials.api.permissions.FEPermissions; import com.forgeessentials.api.permissions.ServerZone; import com.forgeessentials.api.permissions.WorldZone; import com.forgeessentials.api.permissions.Zone; import com.forgeessentials.api.permissions.Zone.PermissionList; import com.forgeessentials.commons.selections.AreaBase; import com.forgeessentials.commons.selections.AreaShape; import com.forgeessentials.commons.selections.Point; import com.forgeessentials.permissions.core.ZonePersistenceProvider; import com.forgeessentials.util.output.LoggingHandler; public class FlatfileProvider extends ZonePersistenceProvider { public static final String PERMISSION_FILE_EXT = ".txt"; private File basePath; public static final FileFilter permissionFilter = new FileFilters.Extension(PERMISSION_FILE_EXT); public static final FileFilter directoryFilter = new FileFilters.Directory(); public static class SortedPermisssionProperties extends Properties { private static final long serialVersionUID = 1L; @Override public synchronized Enumeration<Object> keys() { TreeSet<Object> keys = new TreeSet<Object>(Zone.permissionComparator); keys.addAll(super.keySet()); return Collections.enumeration(keys); } } public static final String COMMENT_INFO = "\nDO NOT MODIFY OR REMOVE fe.internal PERMISSIONS UNLESS YOU KNOW WHAT YOU DO!" + "\nAfter you modified permissions in this file, remember to directly run \"/feperm reload\", or the changes get overwritten next time permissions are saved by the server."; // ------------------------------------------------------------ public FlatfileProvider(File basePath) { this.basePath = basePath; } // ------------------------------------------------------------ // -- Saving // ------------------------------------------------------------ public static void deleteDirectory(File dir) { try { if (dir.exists()) FileUtils.deleteDirectory(dir); } catch (IOException e) { /* do nothing */ } } @Override public void save(ServerZone serverZone) { File path = basePath; deleteDirectory(path); writeUserGroupPermissions(serverZone); saveServerZone(path, serverZone); saveZonePermissions(path, serverZone); for (WorldZone worldZone : serverZone.getWorldZones().values()) { File worldPath = new File(path, worldZone.getName()); saveWorldZone(worldPath, worldZone); saveZonePermissions(worldPath, worldZone); for (AreaZone areaZone : worldZone.getAreaZones()) { File areaPath = new File(worldPath, areaZone.getName()); saveAreaZone(areaPath, areaZone); saveZonePermissions(areaPath, areaZone); } } } public static void saveServerZone(File path, ServerZone serverZone) { // Store zone information path.mkdirs(); Properties p = new Properties(); p.setProperty("id", Integer.toString(serverZone.getId())); p.setProperty("maxZoneId", Integer.toString(serverZone.getMaxZoneID())); try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(path, "server.xml")))) { p.storeToXML(os, "Data of server"); } catch (IOException e) { e.printStackTrace(); } } public static void saveWorldZone(File path, WorldZone worldZone) { path.mkdirs(); Properties p = new Properties(); p.setProperty("id", Integer.toString(worldZone.getId())); p.setProperty("dimId", Integer.toString(worldZone.getDimensionID())); try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(path, "world.xml")))) { p.storeToXML(os, "Data of world " + worldZone.getName()); } catch (IOException e) { e.printStackTrace(); } } public static void saveAreaZone(File path, AreaZone areaZone) { path.mkdirs(); Properties p = new Properties(); p.setProperty("id", Integer.toString(areaZone.getId())); p.setProperty("name", areaZone.getName()); p.setProperty("x1", Integer.toString(areaZone.getArea().getLowPoint().getX())); p.setProperty("y1", Integer.toString(areaZone.getArea().getLowPoint().getY())); p.setProperty("z1", Integer.toString(areaZone.getArea().getLowPoint().getZ())); p.setProperty("x2", Integer.toString(areaZone.getArea().getHighPoint().getX())); p.setProperty("y2", Integer.toString(areaZone.getArea().getHighPoint().getY())); p.setProperty("z2", Integer.toString(areaZone.getArea().getHighPoint().getZ())); p.setProperty("shape", areaZone.getShape().toString()); try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(path, "area.xml")))) { p.storeToXML(os, "Data of area " + areaZone.getName()); } catch (IOException e) { e.printStackTrace(); } } public static void saveZonePermissions(File path, Zone zone) { File playersPath = new File(path, "players"); File groupsPath = new File(path, "groups"); for (Entry<UserIdent, PermissionList> entry : zone.getPlayerPermissions().entrySet()) { // Get filename and info String username = entry.getKey().getUsername() == null ? entry.getKey().getUuid().toString() : entry.getKey().getUsername(); UUID uuid = entry.getKey().getUuid(); String filename = username == null ? uuid.toString() : username; String comment = "Permissions for user " + (username != null ? username : "<unknown-username>") + " with UUID " + (uuid != null ? uuid.toString() : "<unknown-uuid>") + COMMENT_INFO; filename = filename.replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); // prevent overwriting files with same playername while (new File(playersPath, filename + PERMISSION_FILE_EXT).exists()) filename = filename + "_"; // Save permissions Properties p = permissionListToProperties(entry.getValue()); if (entry.getKey().getUsername() != null) p.setProperty(FEPermissions.PLAYER_NAME, entry.getKey().getUsername()); if (entry.getKey().getUuid() != null) p.setProperty(FEPermissions.PLAYER_UUID, entry.getKey().getUuid().toString()); saveProperties(p, playersPath, filename + PERMISSION_FILE_EXT, comment); } for (Entry<String, PermissionList> entry : zone.getGroupPermissions().entrySet()) { // Get filename and info String comment = "Permissions for group " + entry.getKey() + COMMENT_INFO; // Save permissions Properties p = permissionListToProperties(entry.getValue()); saveProperties(p, groupsPath, entry.getKey() + PERMISSION_FILE_EXT, comment); } } public static Properties permissionListToProperties(PermissionList list) { Properties p = new SortedPermisssionProperties(); for (Entry<String, String> permission : list.entrySet()) p.setProperty(permission.getKey(), permission.getValue() != null ? permission.getValue() : ""); return p; } public static void saveProperties(Properties properties, File path, String filename, String comment) { path.mkdirs(); try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(path, filename)))) { properties.store(os, comment); } catch (IOException e) { e.printStackTrace(); } } // ------------------------------------------------------------ // ------------------------------------------------------------ // -- Loading // ------------------------------------------------------------ @Override public ServerZone load() { File path = basePath; try { // Create ServerZone and load permissions ServerZone serverZone = new ServerZone(); loadZonePermissions(path, serverZone); readUserGroupPermissions(serverZone); int maxId = 2; File[] worldDirs = path.listFiles(directoryFilter); if (worldDirs == null) { LoggingHandler.felog.error("Error loading permissions: invalid path"); return null; } for (File worldPath : worldDirs) { File worldFile = new File(worldPath, "world.xml"); if (!worldFile.exists()) continue; Properties worldProperties = new Properties(); try (InputStream is = new BufferedInputStream(new FileInputStream(worldFile))) { worldProperties.loadFromXML(is); } catch (NumberFormatException | IOException e) { LoggingHandler.felog.error("Error reading world " + worldPath.getName()); continue; } // Read world data int worldId = Integer.parseInt(worldProperties.getProperty("id")); maxId = Math.max(maxId, worldId); int dimensionID = Integer.parseInt(worldProperties.getProperty("dimId")); // Create WorldZone and load permissions WorldZone worldZone = new WorldZone(serverZone, dimensionID, worldId); loadZonePermissions(worldPath, worldZone); for (File areaPath : worldPath.listFiles(directoryFilter)) { File areaFile = new File(areaPath, "area.xml"); if (!areaFile.exists()) continue; Properties areaProperties = new Properties(); try (InputStream is = new BufferedInputStream(new FileInputStream(areaFile))) { areaProperties.loadFromXML(is); } catch (NumberFormatException | IOException e) { LoggingHandler.felog.error("Error reading area " + worldPath.getName() + "/" + areaPath.getName()); continue; } // Read area data int areaId = Integer.parseInt(areaProperties.getProperty("id")); maxId = Math.max(maxId, areaId); String name = areaProperties.getProperty("name"); int x1 = Integer.parseInt(areaProperties.getProperty("x1")); int y1 = Integer.parseInt(areaProperties.getProperty("y1")); int z1 = Integer.parseInt(areaProperties.getProperty("z1")); int x2 = Integer.parseInt(areaProperties.getProperty("x2")); int y2 = Integer.parseInt(areaProperties.getProperty("y2")); int z2 = Integer.parseInt(areaProperties.getProperty("z2")); AreaShape shape = AreaShape.getByName(areaProperties.getProperty("shape")); if (name == null) throw new IllegalArgumentException(); // Create AreaZone and load permissions AreaZone areaZone = new AreaZone(worldZone, name, new AreaBase(new Point(x1, y1, z1), new Point(x2, y2, z2)), areaId); if (shape != null) areaZone.setShape(shape); loadZonePermissions(areaPath, areaZone); } } File serverFile = new File(path, "server.xml"); if (serverFile.exists()) { try { Properties serverProperties = new Properties(); serverProperties.loadFromXML(new BufferedInputStream(new FileInputStream(serverFile))); serverZone.setMaxZoneId(Integer.parseInt(serverProperties.getProperty("maxZoneId"))); } catch (IllegalArgumentException | IOException e) { LoggingHandler.felog.error("Error reading server data " + serverFile.getName()); serverZone.setMaxZoneId(maxId); } } else { serverZone.setMaxZoneId(maxId); } return serverZone; } catch (Exception e) { LoggingHandler.felog.error("Error loading permissions"); e.printStackTrace(); return null; } } public static void loadZonePermissions(File path, Zone zone) { File playersPath = new File(path, "players"); File groupsPath = new File(path, "groups"); if (playersPath.exists()) { for (File file : playersPath.listFiles(permissionFilter)) { Properties p = new Properties(); try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { p.load(is); } catch (IOException e) { LoggingHandler.felog.error("Error reading permissions from " + path.getAbsolutePath()); continue; } // Get player String username = p.getProperty(FEPermissions.PLAYER_NAME); String uuid = p.getProperty(FEPermissions.PLAYER_UUID); p.remove(FEPermissions.PLAYER_NAME); p.remove(FEPermissions.PLAYER_UUID); if (username == null && uuid == null) { LoggingHandler.felog.error("User identification missing in " + path.getAbsolutePath()); continue; } UserIdent ident = UserIdent.get(uuid, username); // Load permissions PermissionList permissions = zone.getOrCreatePlayerPermissions(ident); for (Entry<?, ?> permission : p.entrySet()) { permissions.put((String) permission.getKey(), (String) permission.getValue()); } } } if (groupsPath.exists()) { for (File file : groupsPath.listFiles(permissionFilter)) { Properties p = new Properties(); try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { p.load(is); } catch (IOException e) { LoggingHandler.felog.error("Error reading permissions from " + path.getAbsolutePath()); continue; } // Get group String groupName = file.getName().substring(0, file.getName().length() - PERMISSION_FILE_EXT.length()); // Load permissions PermissionList permissions = zone.getOrCreateGroupPermissions(groupName); for (Entry<?, ?> permission : p.entrySet()) { permissions.put((String) permission.getKey(), (String) permission.getValue()); } } } } }