Auto update configuration file

Discussion in 'Plugin Development' started by Staartvin, Feb 4, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Staartvin

    Hello everyone,

    While I was working on a plugin, I found a problem with my configuration files.

    Basically, I copy the config.yml I have in the jar over to the plugin data folder. This allows me to add comments to all options so that it is easier to understand what a config option does.

    This is the config.yml I have in my jar:

    Code:
    # Config file of Transactions.
     
    # The text that will be shown at the top of the list of available offers
    # You can use colour codes
    OfferListHeading: '&7Available offers:'
     
    # Do I need to broadcast when a new offer is made?
    Broadcast new offer: true
     
    # Message to broadcast
    Broadcast message: '&6%player% &ais offering &6%amount%&a new &6%item%&a!'
    I have this special class to load the file:

    Code:java
    1. /**
    2. *
    3. */
    4. package me.staartvin.masterrank;
    5.  
    6. import java.io.File;
    7. import java.io.FileOutputStream;
    8. import java.io.IOException;
    9. import java.io.InputStream;
    10. import java.io.OutputStream;
    11. import java.net.URL;
    12. import java.net.URLConnection;
    13.  
    14. import org.bukkit.configuration.file.YamlConfiguration;
    15. import org.bukkit.plugin.java.JavaPlugin;
    16.  
    17. /**
    18. * Date created: 19:52:14
    19. * 2 feb. 2014
    20. *
    21. * @author Staartvin
    22. *
    23. */
    24. public class FileUtil {
    25.  
    26. private static JavaPlugin plugin;
    27.  
    28. public FileUtil(JavaPlugin instance) {
    29. plugin = instance;
    30. }
    31.  
    32. public static void copy(InputStream input, File target) throws IOException {
    33. if (target.exists()) {
    34. throw new IOException("File already exists!");
    35. }
    36.  
    37. File parentDir = target.getParentFile();
    38.  
    39. if (!parentDir.exists()) {
    40. if (!parentDir.mkdirs()) {
    41. throw new IOException("Failed at creating directories!");
    42. }
    43. }
    44.  
    45. if (!parentDir.isDirectory()) {
    46. throw new IOException("The parent of this file is no directory!?");
    47. }
    48.  
    49. if (!target.createNewFile()) {
    50. throw new IOException("Failed at creating new empty file!");
    51. }
    52.  
    53. if (input == null) {
    54. throw new NullPointerException("Input is null!");
    55. }
    56.  
    57. byte[] buffer = new byte[1024];
    58.  
    59. OutputStream output = new FileOutputStream(target);
    60.  
    61. int realLength;
    62.  
    63. while (input != null && (realLength = input.read(buffer)) > 0) {
    64. output.write(buffer, 0, realLength);
    65. }
    66.  
    67. output.flush();
    68. output.close();
    69. }
    70.  
    71. public static InputStream getInputFromJar(String path) throws IOException {
    72. if (path == null) {
    73. throw new IllegalArgumentException("The path can not be null");
    74. }
    75.  
    76. URL url = plugin.getClass().getClassLoader().getResource(path);
    77.  
    78. if (url == null) {
    79. return null;
    80. }
    81.  
    82. URLConnection connection = url.openConnection();
    83. connection.setUseCaches(false);
    84. return connection.getInputStream();
    85. }
    86.  
    87. /**
    88.   * Copy an internal yml file to an certain path
    89.   * @param pathTo path to copy the new file to
    90.   * @param internalPath path to the file to copy
    91.   * @return loaded file
    92.   */
    93. public static YamlConfiguration loadFile(String pathTo, String internalPath) {
    94. File conf = new File(plugin.getDataFolder() + File.separator + pathTo);
    95.  
    96. if (!conf.exists()) {
    97.  
    98. //saveResource("config.yml", true);
    99. try {
    100. InputStream stream = getInputFromJar(internalPath);
    101.  
    102. copy(stream, conf);
    103. } catch (IOException e) {
    104. e.printStackTrace();
    105. }
    106.  
    107. plugin.getLogger().info("Creating " + pathTo + " for the first time..");
    108. }
    109.  
    110. return YamlConfiguration.loadConfiguration(conf);
    111. }
    112. }
    113.  


    I basically call this method in my onEnable so that it loads the file:
    Code:java
    1. public void loadConfig() {
    2. new FileUtil(this);
    3.  
    4. // Newly create config
    5. FileUtil.loadFile("config.yml", "config.yml");
    6.  
    7. getLogger().info("Loaded config.yml");
    8. }


    This works perfectly. Everything gets copied like it should and all is well.

    Now here comes the problem:
    Let's say I have updated my config with new options and I want to automatically add these to the already existing config.

    I cannot use the .set() and .save() methods, as that will erase all my comments.

    My possible solution:
    I've thought about a possible solution, because you know.. that's what you ought to do when you have a problem.

    I came up with the idea of storing a variable in the config called 'configVersion' and checking this when the plugin starts. If this version is outdated, I load the config.yml from the jar again, but I let it skip the parts that are already defined. I'm not sure how to check if it is defined though. As you can see I use an OutputStream to write to the file, but I'm not sure if I can check if the file already has a certain bytes before writing it.

    Could you guys help me?

    I'd love a solution for this problem. I'm sure a lot of other developers had something like this before; so there must be a way to fix it, right?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 6, 2016
  2. Offline

    d0mov0i

    Use properties or any other config alternative. Bukkit's built in file config has its strong points and of course it has to have some weak points. One thing is the comments thing. So it is better to make your own config methods to meet your expectations
     
  3. Offline

    bfgbfggf

    Hyym... I have something... but... it's just terrible to edit/read :D
    https://github.com/BukkitSmerf/Prof...Smerf/professionalWarns/config/CfgLoader.java
    (looks horrible!)
    And loader:
    https://github.com/BukkitSmerf/Prof...bukkitSmerf/professionalWarns/config/Cfg.java
    (looks better! maybe! :D)

    You just must write your config line by line....
    it's even easier for some own data-types :D

    So... I loading config, and then I write new...
    line by line, then I can add comments, and don't change any options, and also add missing options.
     
  4. Offline

    d0mov0i

    my god. there it is again. who started doing the 'hyym' thing?so fking irritating
     
    bfgbfggf likes this.
  5. Offline

    Staartvin

    Okay, I came up with something very sloppy.. It checks every line against the old line in the old config and check's whether it is up to date. If it is, it grabs the value from the old config. If not, it overrides the line (or creates a new one).

    This is the code I used (the update method is used to update the config):


    Code example (open)

    Code:java
    1. /**
    2. *
    3. */
    4. package me.staartvin.masterrank;
    5.  
    6. import java.io.ByteArrayInputStream;
    7. import java.io.File;
    8. import java.io.FileInputStream;
    9. import java.io.FileOutputStream;
    10. import java.io.IOException;
    11. import java.io.InputStream;
    12. import java.io.OutputStream;
    13. import java.net.URL;
    14. import java.net.URLConnection;
    15. import java.util.ArrayList;
    16. import java.util.List;
    17.  
    18. import org.bukkit.configuration.file.YamlConfiguration;
    19. import org.bukkit.plugin.java.JavaPlugin;
    20.  
    21. /**
    22. * Date created: 19:52:14
    23. * 2 feb. 2014
    24. *
    25. * @author Staartvin
    26. *
    27. */
    28. public class FileUtil {
    29.  
    30. private static JavaPlugin plugin;
    31.  
    32. private static String configVersion = "1.2.1";
    33.  
    34. public FileUtil(JavaPlugin instance) {
    35. plugin = instance;
    36. }
    37.  
    38. public static void copy(InputStream input, File target) throws IOException {
    39. if (target.exists()) {
    40. throw new IOException("File already exists!");
    41. }
    42.  
    43. File parentDir = target.getParentFile();
    44.  
    45. if (!parentDir.exists()) {
    46. if (!parentDir.mkdirs()) {
    47. throw new IOException("Failed at creating directories!");
    48. }
    49. }
    50.  
    51. if (!parentDir.isDirectory()) {
    52. throw new IOException("The parent of this file is no directory!?");
    53. }
    54.  
    55. if (!target.createNewFile()) {
    56. throw new IOException("Failed at creating new empty file!");
    57. }
    58.  
    59. if (input == null) {
    60. throw new NullPointerException("Input is null!");
    61. }
    62.  
    63. byte[] buffer = new byte[1024];
    64.  
    65. OutputStream output = new FileOutputStream(target);
    66.  
    67. int realLength;
    68.  
    69. while (input != null && (realLength = input.read(buffer)) > 0) {
    70. output.write(buffer, 0, realLength);
    71.  
    72. }
    73.  
    74. output.flush();
    75. output.close();
    76. }
    77.  
    78. public static void update(InputStream input, File target) throws IOException {
    79. // This basically does the same as copy, but it checks if an option already exists before adding it.
    80.  
    81. if (!target.exists()) {
    82. throw new IOException("File doesn't exist!");
    83. }
    84.  
    85. File parentDir = target.getParentFile();
    86.  
    87. if (!parentDir.exists()) {
    88. if (!parentDir.mkdirs()) {
    89. throw new IOException("Failed at creating directories!");
    90. }
    91. }
    92.  
    93. if (!parentDir.isDirectory()) {
    94. throw new IOException("The parent of this file is no directory!?");
    95. }
    96.  
    97. if (input == null) {
    98. throw new NullPointerException("Input is null!");
    99. }
    100.  
    101.  
    102. // First analyse input
    103. List<String> lines = readFile(target.getPath());
    104.  
    105. // Lines to use to compare other lines
    106. List<String> inputLines = getLines(convertStreamToString(input));
    107.  
    108. for (int i=0;i<inputLines.size();i++) {
    109. String inputLine = inputLines.get(i);
    110.  
    111. if (i >= lines.size()) {
    112. // Stop check, all the coming lines are new.
    113. break;
    114. }
    115.  
    116. String oldLine = lines.get(i);
    117.  
    118. // Skip this line, it does not contain a variable assignment.
    119. if (!inputLine.contains(":") || !oldLine.contains(":")) {
    120. continue;
    121. }
    122.  
    123. // Line is a comment, we do not care about it at all.
    124. if (inputLine.startsWith("#")) {
    125. continue;
    126. }
    127.  
    128. // Find place of ':' character
    129. int dividerPlace = inputLine.indexOf(":");
    130. int oldDividerPlace = oldLine.indexOf(":");
    131.  
    132. String variableAssigner = inputLine.substring(0, dividerPlace);
    133. String oldVariableAssigner = oldLine.substring(0, oldDividerPlace);
    134.  
    135. System.out.print("variableAssigner: " + variableAssigner);
    136. System.out.print("oldVariableAssigner: " + oldVariableAssigner);
    137.  
    138. if (oldVariableAssigner.trim().equals("configVersion")) {
    139. System.out.print("Update config version to " + configVersion);
    140. // Always update config version
    141. inputLines.set(i, "configVersion: " + configVersion);
    142. } else if (variableAssigner.equals(oldVariableAssigner)) {
    143. // This line does not have to be updated
    144. // Replace new line with old line so the data is stored.
    145. inputLines.set(i, oldLine);
    146. }
    147.  
    148. }
    149.  
    150. String fullString = "";
    151.  
    152. for (String line: inputLines) {
    153. System.out.print("Line: "+ line);
    154. fullString = fullString + line;
    155. }
    156.  
    157. // Replace with new input
    158. input = new ByteArrayInputStream(fullString.getBytes());
    159.  
    160. byte[] buffer = new byte[1024];
    161.  
    162. OutputStream output = new FileOutputStream(target);
    163.  
    164. int realLength;
    165.  
    166. while (input != null && (realLength = input.read(buffer)) > 0) {
    167. output.write(buffer, 0, realLength);
    168. }
    169.  
    170. output.flush();
    171. output.close();
    172. }
    173.  
    174. public static InputStream getInputFromJar(String path) throws IOException {
    175. if (path == null) {
    176. throw new IllegalArgumentException("The path can not be null");
    177. }
    178.  
    179. URL url = plugin.getClass().getClassLoader().getResource(path);
    180.  
    181. if (url == null) {
    182. return null;
    183. }
    184.  
    185. URLConnection connection = url.openConnection();
    186. connection.setUseCaches(false);
    187. return connection.getInputStream();
    188. }
    189.  
    190. /**
    191.   * Copy an internal yml file to an certain path
    192.   *
    193.   * @param pathTo path to copy the new file to
    194.   * @param internalPath path to the file to copy
    195.   * @return loaded file
    196.   */
    197. public static YamlConfiguration loadFile(String pathTo, String internalPath) {
    198. File conf = new File(plugin.getDataFolder() + File.separator + pathTo);
    199.  
    200. InputStream stream = null;
    201.  
    202. try {
    203. stream = getInputFromJar(internalPath);
    204. } catch (IOException e1) {
    205. e1.printStackTrace();
    206. }
    207.  
    208. if (!conf.exists()) {
    209.  
    210. try {
    211.  
    212.  
    213. copy(stream, conf);
    214. } catch (IOException e) {
    215. e.printStackTrace();
    216. }
    217.  
    218. plugin.getLogger().info(
    219. "Creating " + pathTo + " for the first time..");
    220. } else {
    221. try {
    222.  
    223. if (!plugin.getConfig().getString("configVersion", "1.0").equals(configVersion)) {
    224. System.out.print("Have to update!");
    225. update(stream, conf);
    226.  
    227. plugin.getLogger().info("Updated config to match new version '" + configVersion + "'.");
    228. }
    229. } catch (IOException e) {
    230. e.printStackTrace();
    231. }
    232. }
    233.  
    234. return YamlConfiguration.loadConfiguration(conf);
    235. }
    236.  
    237. public static List<String> readFile(String fn) throws IOException {
    238. // Read a file as one big string
    239. File f = new File(fn);
    240.  
    241. byte[] buffer = new byte[(int) f.length()];
    242. is.read(buffer);
    243. is.close();
    244.  
    245. // Convert string to multiple lines
    246. String fullString = new String(buffer);
    247.  
    248. return getLines(fullString);
    249. }
    250.  
    251. private static List<String> getLines(String fullString) {
    252. // Convert whole string to lines
    253. List<String> lines = new ArrayList<String>();
    254. List<Integer> lineBreaks = new ArrayList<Integer>();
    255.  
    256.  
    257. char[] charArray = fullString.toCharArray();
    258.  
    259. // Register all line breaks
    260. for (int i=0;i<charArray.length;i++) {
    261. char c = charArray[i];
    262.  
    263. // Check for linefeed character
    264. if ((int) c == 13) {
    265. lineBreaks.add(i);
    266. }
    267. }
    268.  
    269. for (int i=0;i<lineBreaks.size();i++) {
    270.  
    271. int breakPoint = lineBreaks.get(i);
    272.  
    273. // Last breakpoint
    274. if (i == (lineBreaks.size() - 1)) {
    275. lines.add(fullString.substring(breakPoint));
    276. } else {
    277.  
    278. int nextBreakPoint = lineBreaks.get(i + 1);
    279.  
    280. lines.add(fullString.substring(breakPoint, nextBreakPoint));
    281. }
    282. }
    283.  
    284. return lines;
    285. }
    286.  
    287. private static String convertStreamToString(java.io.InputStream is) {
    288. java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    289. return s.hasNext() ? s.next() : "";
    290. }
    291. }
    292. [/i]



    Would this be a good solution?
     
Thread Status:
Not open for further replies.

Share This Page