diff --git a/EssentialsGroupManager/src/Changelog.txt b/EssentialsGroupManager/src/Changelog.txt index 5264a88b9..b0d68e24c 100644 --- a/EssentialsGroupManager/src/Changelog.txt +++ b/EssentialsGroupManager/src/Changelog.txt @@ -64,4 +64,8 @@ v 1.5: - Added checking of subgroups for Info nodes. - Expanded 'canUserBuild()' to include inheritance and subgroups. - Added a config.yml setting of 'validate_toggle' for those who prefer 'mantogglevalidate' to always be off. - - Prevent setting 'minutes' in the config to zero causing an error. \ No newline at end of file + - Prevent setting 'minutes' in the config to zero causing an error. + - GM will now check to see if it's data files have been changed at each scheduled save. + If the files have been altered (on disc) it will reload, so long as the in-memory data hasn't changed. + If the files on Disc have changed AND there have been changes to it's in-memory data it will show a warning. + You then MUST issue a '/mansave force' to overwrite the disc files, or a '/manload' to overwrite the memory data. \ No newline at end of file diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GlobalGroups.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GlobalGroups.java index a7d0dd920..1114bef95 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GlobalGroups.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GlobalGroups.java @@ -33,10 +33,10 @@ public class GlobalGroups { private YamlConfiguration GGroups; private Map groups; - /** - * - */ + + protected long timeStampGroups = 0; protected boolean haveGroupsChanged = false; + protected File GlobalGroupsFile = null; public GlobalGroups(GroupManager plugin) { this.plugin = plugin; @@ -58,6 +58,19 @@ public class GlobalGroups { return false; } + /** + * @return the timeStampGroups + */ + public long getTimeStampGroups() { + return timeStampGroups; + } + /** + * @param timeStampGroups the timeStampGroups to set + */ + protected void setTimeStampGroups(long timeStampGroups) { + this.timeStampGroups = timeStampGroups; + } + /** * @param haveGroupsChanged * the haveGroupsChanged to set @@ -71,9 +84,12 @@ public class GlobalGroups { GGroups = new YamlConfiguration(); groups = new HashMap(); + + GroupManager.setLoaded(false); // READ globalGroups FILE - File GlobalGroupsFile = new File(plugin.getDataFolder(), "globalgroups.yml"); + if (GlobalGroupsFile == null) + GlobalGroupsFile = new File(plugin.getDataFolder(), "globalgroups.yml"); if (!GlobalGroupsFile.exists()) { try { @@ -129,7 +145,10 @@ public class GlobalGroups { addGroup(newGroup); } - GlobalGroupsFile = null; + removeGroupsChangedFlag(); + setTimeStampGroups(GlobalGroupsFile.lastModified()); + GroupManager.setLoaded(true); + //GlobalGroupsFile = null; } @@ -137,46 +156,78 @@ public class GlobalGroups { * Write the globalgroups.yml file */ - public void writeGroups() { + public void writeGroups(boolean overwrite) { - File GlobalGroupsFile = new File(plugin.getDataFolder(), "globalgroups.yml"); + //File GlobalGroupsFile = new File(plugin.getDataFolder(), "globalgroups.yml"); - Map root = new HashMap(); - - Map groupsMap = new HashMap(); - root.put("groups", groupsMap); - for (String groupKey : groups.keySet()) { - Group group = groups.get(groupKey); - - // Group header - Map aGroupMap = new HashMap(); - groupsMap.put(group.getName(), aGroupMap); - - // Info nodes - Map infoMap = new HashMap(); - aGroupMap.put("info", infoMap); - - for (String infoKey : group.getVariables().getVarKeyList()) { - infoMap.put(infoKey, group.getVariables().getVarObject(infoKey)); - } - - // Permission nodes - aGroupMap.put("permissions", group.getPermissionList()); - } - - if (!root.isEmpty()) { - DumperOptions opt = new DumperOptions(); - opt.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - final Yaml yaml = new Yaml(opt); - try { - yaml.dump(root, new OutputStreamWriter(new FileOutputStream(GlobalGroupsFile), "UTF-8")); - } catch (UnsupportedEncodingException ex) { - } catch (FileNotFoundException ex) { - } - } + if (haveGroupsChanged()) { + if (overwrite || (!overwrite && (getTimeStampGroups() >= GlobalGroupsFile.lastModified()))) { + Map root = new HashMap(); + + Map groupsMap = new HashMap(); + root.put("groups", groupsMap); + for (String groupKey : groups.keySet()) { + Group group = groups.get(groupKey); + + // Group header + Map aGroupMap = new HashMap(); + groupsMap.put(group.getName(), aGroupMap); + + // Info nodes + Map infoMap = new HashMap(); + aGroupMap.put("info", infoMap); + + for (String infoKey : group.getVariables().getVarKeyList()) { + infoMap.put(infoKey, group.getVariables().getVarObject(infoKey)); + } + + // Permission nodes + aGroupMap.put("permissions", group.getPermissionList()); + } + + if (!root.isEmpty()) { + DumperOptions opt = new DumperOptions(); + opt.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + final Yaml yaml = new Yaml(opt); + try { + yaml.dump(root, new OutputStreamWriter(new FileOutputStream(GlobalGroupsFile), "UTF-8")); + } catch (UnsupportedEncodingException ex) { + } catch (FileNotFoundException ex) { + } + } + setTimeStampGroups(GlobalGroupsFile.lastModified()); + } else { + // Newer file found. + GroupManager.logger.log(Level.WARNING, "Newer GlobalGroups file found, but we have local changes!"); + throw new IllegalStateException("Unable to save unless you issue a '/mansave force'"); + } + removeGroupsChangedFlag(); + } else { + //Check for newer file as no local changes. + if (getTimeStampGroups() < GlobalGroupsFile.lastModified()) { + System.out.print("Newer GlobalGroups file found (Loading changes)!"); + // Backup GlobalGroups file + backupFile(); + load(); + } + } } + /** + * Backup the BlobalGroups file + * @param w + */ + private void backupFile() { + + File backupFile = new File(plugin.getBackupFolder(), "bkp_ggroups_" + Tasks.getDateString() + ".yml"); + try { + Tasks.copy(GlobalGroupsFile, backupFile); + } catch (IOException ex) { + GroupManager.logger.log(Level.SEVERE, null, ex); + } + } + /** * Adds a group, or replaces an existing one. * @@ -322,4 +373,21 @@ public class GlobalGroups { } + /** + * @return the globalGroupsFile + */ + public File getGlobalGroupsFile() { + return GlobalGroupsFile; + } + + /** + * + */ + public void removeGroupsChangedFlag() { + setGroupsChanged(false); + for (Group g : groups.values()) { + g.flagAsSaved(); + } + } + } \ No newline at end of file diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java index ff77a76a4..8964c1cd1 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/GroupManager.java @@ -90,7 +90,11 @@ public class GroupManager extends JavaPlugin { disableScheduler(); // Shutdown before we save, so it doesn't interfere. if (worldsHolder != null) { - worldsHolder.saveChanges(); + try { + worldsHolder.saveChanges(false); + } catch (IllegalStateException ex) { + GroupManager.logger.log(Level.WARNING, ex.getMessage()); + } } WorldEvents = null; @@ -177,7 +181,12 @@ public class GroupManager extends JavaPlugin { @Override public void run() { - worldsHolder.saveChanges(); + try { + worldsHolder.saveChanges(false); + GroupManager.logger.log(Level.INFO, " Data files refreshed."); + } catch (IllegalStateException ex) { + GroupManager.logger.log(Level.WARNING, ex.getMessage()); + } } }; scheduler = new ScheduledThreadPoolExecutor(1); @@ -1504,9 +1513,20 @@ public class GroupManager extends JavaPlugin { return true; case mansave: - worldsHolder.saveChanges(); - sender.sendMessage(ChatColor.YELLOW + " The changes were saved."); + + boolean forced = false; + + if ((args.length == 1) && (args[0].equalsIgnoreCase("force"))) + forced = true; + + try { + worldsHolder.saveChanges(forced); + sender.sendMessage(ChatColor.YELLOW + " The changes were saved."); + } catch (IllegalStateException ex) { + sender.sendMessage(ChatColor.RED + ex.getMessage()); + } return true; + case manload: // THIS CASE DONT NEED SENDER if (args.length > 0) { diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/WorldDataHolder.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/WorldDataHolder.java index a9f7e3f6e..d71fc64c0 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/WorldDataHolder.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/WorldDataHolder.java @@ -45,20 +45,23 @@ public class WorldDataHolder { * The actual groups holder */ protected Map groups = new HashMap(); - /** + /** * The actual users holder */ protected Map users = new HashMap(); - /** + + /** * Points to the default group */ protected Group defaultGroup = null; + /** * The file, which this class loads/save data from/to * @deprecated */ @Deprecated protected File f; + /** * */ @@ -79,8 +82,17 @@ public class WorldDataHolder { * */ protected boolean haveGroupsChanged = false; - /** + * + */ + protected long timeStampGroups = 0; + /** + * + */ + protected long timeStampUsers = 0; + + + /** * Prevent direct instantiation * @param worldName */ @@ -302,14 +314,34 @@ public class WorldDataHolder { */ public void reload() { try { - WorldDataHolder ph = load(this.getName(), getGroupsFile(), getUsersFile()); - this.defaultGroup = ph.defaultGroup; - this.groups = ph.groups; - this.users = ph.users; + reloadGroups(); + reloadUsers(); } catch (Exception ex) { Logger.getLogger(WorldDataHolder.class.getName()).log(Level.SEVERE, null, ex); } } + + public void reloadGroups() { + GroupManager.setLoaded(false); + try { + resetGroups(); + loadGroups(this, getGroupsFile()); + } catch (Exception ex) { + Logger.getLogger(WorldDataHolder.class.getName()).log(Level.SEVERE, null, ex); + } + GroupManager.setLoaded(true); + } + + public void reloadUsers() { + GroupManager.setLoaded(false); + try { + resetUsers(); + loadUsers(this, getUsersFile()); + } catch (Exception ex) { + Logger.getLogger(WorldDataHolder.class.getName()).log(Level.SEVERE, null, ex); + } + GroupManager.setLoaded(true); + } /** * Save by yourself! @@ -469,7 +501,8 @@ public class WorldDataHolder { } /** - * Returns a data holder for the given file + * Returns a NEW data holder containing data read from the files + * * @param worldName * @param groupsFile * @param usersFile @@ -477,18 +510,57 @@ public class WorldDataHolder { * @throws FileNotFoundException * @throws IOException */ - @SuppressWarnings({"rawtypes", "unchecked"}) public static WorldDataHolder load(String worldName, File groupsFile, File usersFile) throws FileNotFoundException, IOException { - WorldDataHolder ph = new WorldDataHolder(worldName); - ph.groupsFile = groupsFile; - ph.usersFile = usersFile; + WorldDataHolder ph = new WorldDataHolder(worldName); + + GroupManager.setLoaded(false); + loadGroups(ph, groupsFile); + loadUsers(ph, usersFile); + GroupManager.setLoaded(true); + + return ph; + } + + /** + * Updates the WorldDataHolder from the files + * + * @param ph + * @param groupsFile + * @param usersFile + * @return + * @throws FileNotFoundException + * @throws IOException + */ + public static WorldDataHolder Update(WorldDataHolder ph, File groupsFile, File usersFile) throws FileNotFoundException, IOException { + GroupManager.setLoaded(false); + ph.resetGroups(); + loadGroups(ph, groupsFile); + + ph.resetUsers(); + loadUsers(ph, usersFile); + GroupManager.setLoaded(true); + + return ph; + } + + /** + * Updates the WorldDataHolder from the Groups file + * + * @param worldName + * @param groupsFile + * @return + * @throws FileNotFoundException + * @throws IOException + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + protected static void loadGroups(WorldDataHolder ph, File groupsFile) throws FileNotFoundException, IOException { //READ GROUPS FILE Yaml yamlGroups = new Yaml(new SafeConstructor()); Map groupsRootDataNode; if (!groupsFile.exists()) { - throw new IllegalArgumentException("The file which should contain permissions does not exist!\n" + groupsFile.getPath()); + throw new IllegalArgumentException("The file which should contain groups does not exist!\n" + groupsFile.getPath()); } FileInputStream groupsInputStream = new FileInputStream(groupsFile); try { @@ -584,13 +656,32 @@ public class WorldDataHolder { } } } + + ph.removeGroupsChangedFlag(); + // Update the LastModified time. + ph.groupsFile = groupsFile; + ph.setTimeStamps(); - + //return ph; + } + + /** + * Updates the WorldDataHolder from the Users file + * + * @param worldName + * @param usersFile + * @return + * @throws FileNotFoundException + * @throws IOException + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + protected static void loadUsers(WorldDataHolder ph, File usersFile) throws FileNotFoundException, IOException { + //READ USERS FILE Yaml yamlUsers = new Yaml(new SafeConstructor()); Map usersRootDataNode; - if (!groupsFile.exists()) { - throw new IllegalArgumentException("The file which should contain permissions does not exist!\n" + usersFile.getPath()); + if (!usersFile.exists()) { + throw new IllegalArgumentException("The file which should contain users does not exist!\n" + usersFile.getPath()); } FileInputStream usersInputStream = new FileInputStream(usersFile); try { @@ -609,7 +700,7 @@ public class WorldDataHolder { // Stop loading if the file is empty if (allUsersNode == null) - return ph; + return ; for (String usersKey : allUsersNode.keySet()) { Map thisUserNode = (Map) allUsersNode.get(usersKey); @@ -672,7 +763,13 @@ public class WorldDataHolder { thisUser.setGroup(ph.defaultGroup); } } - return ph; + + ph.removeUsersChangedFlag(); + // Update the LastModified time. + ph.usersFile = usersFile; + ph.setTimeStamps(); + + //return ph; } /** @@ -805,6 +902,11 @@ public class WorldDataHolder { } catch (FileNotFoundException ex) { } } + + // Update the LastModified time. + ph.groupsFile = groupsFile; + ph.setTimeStampGroups(groupsFile.lastModified()); + ph.removeGroupsChangedFlag(); /*FileWriter tx = null; try { @@ -870,6 +972,12 @@ public class WorldDataHolder { } catch (FileNotFoundException ex) { } } + + // Update the LastModified time. + ph.usersFile = usersFile; + ph.setTimeStampUsers(usersFile.lastModified()); + ph.removeUsersChangedFlag(); + /*FileWriter tx = null; try { tx = new FileWriter(usersFile, false); @@ -992,4 +1100,64 @@ public class WorldDataHolder { public String getName() { return name; } + + /** + * Resets Groups. + */ + public void resetGroups() { + this.defaultGroup = null; + this.groups = new HashMap(); + } + /** + * Resets Users + */ + public void resetUsers() { + this.users = new HashMap(); + } + + /** + * @return the groups + */ + public Map getGroups() { + return groups; + } + /** + * @return the users + */ + public Map getUsers() { + return users; + } + + /** + * @return the timeStampGroups + */ + public long getTimeStampGroups() { + return timeStampGroups; + } + /** + * @return the timeStampUsers + */ + public long getTimeStampUsers() { + return timeStampUsers; + } + + /** + * @param timeStampGroups the timeStampGroups to set + */ + protected void setTimeStampGroups(long timeStampGroups) { + this.timeStampGroups = timeStampGroups; + } + /** + * @param timeStampUsers the timeStampUsers to set + */ + protected void setTimeStampUsers(long timeStampUsers) { + this.timeStampUsers = timeStampUsers; + } + + public void setTimeStamps() { + if (groupsFile != null) + setTimeStampGroups(groupsFile.lastModified()); + if (usersFile != null) + setTimeStampUsers(usersFile.lastModified()); + } } diff --git a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java index f5b55a25a..8ea8e28c9 100644 --- a/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java +++ b/EssentialsGroupManager/src/org/anjocaido/groupmanager/dataholder/worlds/WorldsHolder.java @@ -160,47 +160,97 @@ public class WorldsHolder { public void reloadWorld(String worldName) { getWorldData(worldName).reload(); } + + /** + * Wrapper to retain backwards compatibility + * (call this function to auto overwrite files) + */ + public void saveChanges() { + saveChanges(true); + } /** * */ - public void saveChanges() { + public void saveChanges(boolean overwrite) { ArrayList alreadyDone = new ArrayList(); + Tasks.removeOldFiles(plugin, plugin.getBackupFolder()); + for (OverloadedWorldHolder w : worldsData.values()) { if (alreadyDone.contains(w)) { continue; } - Tasks.removeOldFiles(plugin, plugin.getBackupFolder()); if (w == null) { GroupManager.logger.severe("WHAT HAPPENED?"); continue; } if (w.haveGroupsChanged()) { - //String groupsFolderName = w.getGroupsFile().getParentFile().getName(); - File backupGroups = new File(plugin.getBackupFolder(), "bkp_" + w.getName() + "_g_" + Tasks.getDateString() + ".yml"); - try { - Tasks.copy(w.getGroupsFile(), backupGroups); - } catch (IOException ex) { - GroupManager.logger.log(Level.SEVERE, null, ex); - } - WorldDataHolder.writeGroups(w, w.getGroupsFile()); - w.removeGroupsChangedFlag(); + if (overwrite || (!overwrite && (w.getTimeStampGroups() >= w.getGroupsFile().lastModified()))) { + // Backup Groups file + backupFile(w,true); + + WorldDataHolder.writeGroups(w, w.getGroupsFile()); + //w.removeGroupsChangedFlag(); + } else { + // Newer file found. + GroupManager.logger.log(Level.WARNING, "Newer Groups file found for " + w.getName() + ", but we have local changes!"); + throw new IllegalStateException("Unable to save unless you issue a '/mansave force'"); + } + } else { + //Check for newer file as no local changes. + if (w.getTimeStampGroups() < w.getGroupsFile().lastModified()) { + System.out.print("Newer Groups file found (Loading changes)!"); + // Backup Users file + backupFile(w,false); + w.reloadGroups(); + } } if (w.haveUsersChanged()) { - File backupUsers = new File(plugin.getBackupFolder(), "bkp_" + w.getName() + "_u_" + Tasks.getDateString() + ".yml"); - try { - Tasks.copy(w.getUsersFile(), backupUsers); - } catch (IOException ex) { - GroupManager.logger.log(Level.SEVERE, null, ex); - } - WorldDataHolder.writeUsers(w, w.getUsersFile()); - w.removeUsersChangedFlag(); + if (overwrite || (!overwrite && (w.getTimeStampUsers() >= w.getUsersFile().lastModified()))) { + // Backup Users file + backupFile(w,false); + + WorldDataHolder.writeUsers(w, w.getUsersFile()); + //w.removeUsersChangedFlag(); + } else { + // Newer file found. + GroupManager.logger.log(Level.WARNING, "Newer Users file found for " + w.getName() + ", but we have local changes!"); + throw new IllegalStateException("Unable to save unless you issue a '/mansave force'"); + } + } else { + //Check for newer file as no local changes. + if (w.getTimeStampUsers() < w.getUsersFile().lastModified()) { + System.out.print("Newer Users file found (Loading changes)!"); + // Backup Users file + backupFile(w,false); + w.reloadUsers(); + } } alreadyDone.add(w); } // Write Global Groups if (GroupManager.getGlobalGroups().haveGroupsChanged()) { - GroupManager.getGlobalGroups().writeGroups(); + GroupManager.getGlobalGroups().writeGroups(overwrite); + } else { + if (GroupManager.getGlobalGroups().getTimeStampGroups() < GroupManager.getGlobalGroups().getGlobalGroupsFile().lastModified()) { + System.out.print("Newer GlobalGroups file found (Loading changes)!"); + GroupManager.getGlobalGroups().load(); + } + } + } + + /** + * Backup the Groups/Users file + * @param w + * @param groups + */ + private void backupFile(OverloadedWorldHolder w, Boolean groups) { + + File backupFile = new File(plugin.getBackupFolder(), "bkp_" + w.getName() + (groups ? "_g_" : "_u_") + Tasks.getDateString() + ".yml"); + try { + Tasks.copy((groups ? w.getGroupsFile() : w.getUsersFile()), backupFile); + } catch (IOException ex) { + GroupManager.logger.log(Level.SEVERE, null, ex); } } @@ -407,6 +457,10 @@ public class WorldsHolder { } try { OverloadedWorldHolder thisWorldData = new OverloadedWorldHolder(WorldDataHolder.load(worldName, groupsFile, usersFile)); + + // Set the file TimeStamps as it will be default from the initial load. + thisWorldData.setTimeStamps(); + if (thisWorldData != null) { GroupManager.logger.finest("Successful load of world " + worldName + "..."); worldsData.put(worldName.toLowerCase(), thisWorldData);