package com.laytonsmith.PureUtilities.VirtualFS; import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; /** * A collection of file system settings are tied to a glob, and * are passed in upon creation of the VirtualFileSystem. Any files * or folders that exist (or are attempted to be created) and match the glob * are first checked against these settings, upon which the request will either * be allowed, or be denied. * */ public class VirtualFileSystemSettings { /** * DO NOT CHANGE THIS. It will break future integrations if you do. * * This is the line that denotes the divider between the top and bottom parts * of a settings file. */ public static final String GENERATED_LINE = "This file is automatically generated, to keep up-to-date with new features.\n" + "# Comments you add to the file will not be retained.\n"; private static final String SETTING_TYPES; static{ List<String> list = new ArrayList<String>(); for(VirtualFileSystemSetting setting : VirtualFileSystemSetting.values()){ String s = "Setting name: " + setting.getName() + "\n" + "# Default value: " + setting.getDef().toString() + "\n" + "# Description: " + setting.getDescription() + "\n"; list.add(s); } SETTING_TYPES = StringUtils.Join(list, "\n# "); } public static String getDefaultSettingsString(){ return StreamUtils.GetString(VirtualFileSystemSettings.class.getResourceAsStream("example_settings.yml")) .replace("%%SETTING_TYPES%%", SETTING_TYPES) .replace("%%GENERATED_LINE%%", GENERATED_LINE); } public static enum VirtualFileSystemSetting{ HIDDEN("hidden", false, "If true, the file system will not allow the file or directory to be created, and if a file or directory already exists, it will not" + " be exposed. This is essentially a way to revoke both read and write privileges."), QUOTA("quota", -1, "Sets the quota for the total list of files or folders that match this glob. Quotas for a cordoned off file system will only affect files" + " that are in the virtual file system, and file sizes of externally created files won't count, but in a uncordoned file system, all files that match" + " this glob are calculated. Due to real time changes in file system size, for directory based globs, this quota may not be enforced precisely, however," + " it should generally be close. If the quota is set to -1, the quota is unrestricted, and if 0, it is \"full\". The unit of measure is bytes, so 1024 is a KB." + " This value is only applicable to the glob **, meaning that the quota can only be applied per entire virtual file system."), READONLY("readonly", false, "If true, this file or folder will not be writable."), CORDONED_OFF("cordoned-off", false, "If true, files and folders in this directory will not appear to the virtual file system, unless the file was created from within" + " the virtual file system. This glob must be the ** glob, meaning that either the whole file system is cordoned off, or the whole file system is not cordoned" + " off."), FOLDER_DEPTH("folder-depth", -1, "The number of folders deep that will be allowed to be created in this directory. The glob must be a directory if this is anything" + " other than -1. -1 means that the number of sub folders is unrestricted, 0 means that no folders can be created inside this one. This does not" + " affect existing folder structure."), ; private String name; private Object def; private String description; private VirtualFileSystemSetting(String name, Object def, String description){ this.name = name; this.def = def; this.description = description; } public String getName(){ return name; } public Object getDef() { return def; } public String getDescription() { return description; } static VirtualFileSystemSetting getSettingByName(String name){ for(VirtualFileSystemSetting s : VirtualFileSystemSetting.values()){ if(s.getName().equals(name)){ return s; } } return null; } } public static String serialize(Map<VirtualGlob, SettingGroup> settings){ DumperOptions options = new DumperOptions(); options.setPrettyFlow(true); Yaml yaml = new Yaml(options); Map<String, Map<String, Object>> serializable = new HashMap<String, Map<String, Object>>(); for(VirtualGlob glob : settings.keySet()){ Map<String, Object> inner = new HashMap<String, Object>(); for(VirtualFileSystemSetting setting : settings.get(glob).settingGroup.keySet()){ inner.put(setting.getName(), settings.get(glob).get(setting)); } serializable.put(glob.toString(), inner); } return yaml.dump(serializable); } public static Map<VirtualGlob, SettingGroup> deserialize(String settings){ Yaml yaml = new Yaml(); Map<String, Map<String, Object>> unserialized = (Map)yaml.load(settings); Map<VirtualGlob, SettingGroup> parsedSettings = new HashMap<VirtualGlob, SettingGroup>(); if(unserialized != null){ for(String glob : unserialized.keySet()){ VirtualGlob vglob = new VirtualGlob(glob); Map<String, Object> settingGroup = (Map)unserialized.get(glob); SettingGroup group = new SettingGroup(); for(String settingName : settingGroup.keySet()){ VirtualFileSystemSetting s = VirtualFileSystemSetting.getSettingByName(settingName); Object value = settingGroup.get(settingName); group.set(s, value); } parsedSettings.put(vglob, group); } } return parsedSettings; } public static class SettingGroup{ private Map<VirtualFileSystemSetting, Object> settingGroup; public SettingGroup(){ this.settingGroup = new EnumMap<VirtualFileSystemSetting, Object>(VirtualFileSystemSetting.class); } public SettingGroup(Map<VirtualFileSystemSetting, Object> settingGroup){ this.settingGroup = settingGroup; } public void set(VirtualFileSystemSetting setting, Object value){ settingGroup.put(setting, value); } public Object get(VirtualFileSystemSetting setting){ if(settingGroup.containsKey(setting)){ return settingGroup.get(setting); } else { return setting.getDef(); } } @Override public String toString() { StringBuilder b = new StringBuilder(); for(VirtualFileSystemSetting s : settingGroup.keySet()){ b.append(s.getName()).append(": ").append(settingGroup.get(s)).append("; "); } return b.toString().trim(); } } private static final Map<VirtualFileSystemSetting, Object> META_DIRECTORY_SETTINGS = new EnumMap<VirtualFileSystemSetting, Object>(VirtualFileSystemSetting.class); static{ META_DIRECTORY_SETTINGS.put(VirtualFileSystemSetting.HIDDEN, true); } private Map<VirtualGlob, SettingGroup> settings; private boolean hasQuota = false; private boolean cordonedOff = false; public VirtualFileSystemSettings(File settingsFile) throws IOException{ this(settingsFile.exists()?FileUtil.read(settingsFile):""); //Here, we will also output the serialized file, with the comment //block at top FileUtil.write(getDefaultSettingsString() + serialize(settings), settingsFile); } public VirtualFileSystemSettings(String unparsedSettings){ settings = (HashMap<VirtualGlob, SettingGroup>) deserialize(unparsedSettings); } public VirtualFileSystemSettings(Map<VirtualGlob, SettingGroup> settings){ this.settings = new HashMap<VirtualGlob, VirtualFileSystemSettings.SettingGroup>(settings); this.settings.put(new VirtualGlob(VirtualFileSystem.META_DIRECTORY), new SettingGroup(META_DIRECTORY_SETTINGS)); for(VirtualGlob g : settings.keySet()){ SettingGroup s = settings.get(g); if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.QUOTA)){ if((Integer)s.settingGroup.get(VirtualFileSystemSetting.QUOTA) >= 0){ if(g.matches(new VirtualFile("/"))){ hasQuota = true; } else { Logger.getLogger(VirtualFileSystemSettings.class.getName()).log(Level.WARNING, "The \"quota\" setting can only be applied to the root of the " + "file system at this time. The quota setting for " + g.toString() + " is being ignored."); } } } if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.CORDONED_OFF)){ if((Boolean)s.settingGroup.get(VirtualFileSystemSetting.CORDONED_OFF) == true){ if(g.matches(new VirtualFile("/"))){ cordonedOff = true; } else { Logger.getLogger(VirtualFileSystemSettings.class.getName()).log(Level.WARNING, "The \"cordoned-off\" setting can only be applied to the root" + " of the file system at this time. The setting for " + g.toString() + " is being ignored."); } } } } } /** * Gets the most specific value for the specified setting, for the specified file. * File specificity will match whatever is closest, so if this matches both the globs: ** and file/**, * then the file/** glob settings will win. * @param file * @param setting * @return */ public Object getSetting(VirtualFile file, VirtualFileSystemSetting setting){ SortedSet<VirtualGlob> matchedGlobs = new TreeSet<VirtualGlob>(); for(VirtualGlob glob : settings.keySet()){ if(glob.matches(file)){ matchedGlobs.add(glob); } } if(matchedGlobs.isEmpty()){ //trivial state return setting.getDef(); } else if(matchedGlobs.size() == 1){ //trivial state return settings.get(matchedGlobs.first()).get(setting); } else { throw new UnsupportedOperationException("Not supported yet."); } } public boolean hasQuota() { return hasQuota; } public boolean isCordonedOff(){ return cordonedOff; } }