Solved Updater class

Discussion in 'Plugin Development' started by alex123099, Oct 26, 2013.

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

    alex123099

    Hi all,
    For those of you who have heard about the Updater class created by one of the mods in bukkit I believe, I am looking for a way to modify this class such that I can get the minecraft version my latest uploaded file is for. I have never used Java to read XML or HTML files before, so I do not know how to go about doing that. If someone could write the function that would do that, I would really appreciate it.

    This is the updater class:
    Code:java
    1. package extras;
    2.  
    3. import java.io.*;
    4. import java.lang.Runnable;
    5. import java.lang.Thread;
    6. import java.net.MalformedURLException;
    7. import java.net.URL;
    8. import java.net.URLConnection;
    9. import java.util.Enumeration;
    10. import java.util.zip.ZipEntry;
    11. import java.util.zip.ZipFile;
    12. import javax.xml.stream.XMLEventReader;
    13. import javax.xml.stream.XMLInputFactory;
    14. import javax.xml.stream.XMLStreamException;
    15. import javax.xml.stream.events.XMLEvent;
    16. import org.bukkit.configuration.file.YamlConfiguration;
    17. import org.bukkit.plugin.Plugin;
    18.  
    19. /**
    20. * Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed.
    21. * <p>
    22. * <b>VERY, VERY IMPORTANT</b>: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating.
    23. * <br>
    24. * It is a <b>BUKKIT POLICY</b> that you include a boolean value in your config that prevents the auto-updater from running <b>AT ALL</b>.
    25. * <br>
    26. * If you fail to include this option in your config, your plugin will be <b>REJECTED</b> when you attempt to submit it to dev.bukkit.org.
    27. * <p>
    28. * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater.
    29. * <br>
    30. * If you are unsure about these rules, please read the plugin submission guidelines: [url]http://goo.gl/8iU5l[/url]
    31. *
    32. * @author H31IX
    33. */
    34.  
    35. public class Updater
    36. {
    37. private Plugin plugin;
    38. private UpdateType type;
    39. private String versionTitle;
    40. private String versionLink;
    41. private long totalSize; // Holds the total size of the file
    42. //private double downloadedSize; TODO: Holds the number of bytes downloaded
    43. private int sizeLine; // Used for detecting file size
    44. private int multiplier; // Used for determining when to broadcast download updates
    45. private boolean announce; // Whether to announce file downloads
    46. private URL url; // Connecting to RSS
    47. private File file; // The plugin's file
    48. private Thread thread; // Updater thread
    49. private static final String DBOUrl = "[url]http://dev.bukkit.org/server-mods/[/url]"; // Slugs will be appended to this to get to the project's RSS feed
    50. private String [] noUpdateTag = {"-DEV","-PRE","-SNAPSHOT"}; // If the version number contains one of these, don't update.
    51. private static final int BYTE_SIZE = 1024; // Used for downloading files
    52. private String updateFolder = YamlConfiguration.loadConfiguration(new File("bukkit.yml")).getString("settings.update-folder"); // The folder that downloads will be placed in
    53. private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; // Used for determining the outcome of the update process
    54. // Strings for reading RSS
    55. private static final String TITLE = "title";
    56. private static final String LINK = "link";
    57. private static final String ITEM = "item";
    58.  
    59. /**
    60.   * Gives the dev the result of the update process. Can be obtained by called getResult().
    61.   */
    62. public enum UpdateResult
    63. {
    64. /**
    65.   * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads.
    66.   */
    67. SUCCESS,
    68. /**
    69.   * The updater did not find an update, and nothing was downloaded.
    70.   */
    71. NO_UPDATE,
    72. /**
    73.   * The updater found an update, but was unable to download it.
    74.   */
    75. FAIL_DOWNLOAD,
    76. /**
    77.   * For some reason, the updater was unable to contact dev.bukkit.org to download the file.
    78.   */
    79. FAIL_DBO,
    80. /**
    81.   * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'.
    82.   */
    83. FAIL_NOVERSION,
    84. /**
    85.   * The slug provided by the plugin running the updater was invalid and doesn't exist on DBO.
    86.   */
    87. FAIL_BADSLUG,
    88. /**
    89.   * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded.
    90.   */
    91. UPDATE_AVAILABLE
    92. }
    93.  
    94. /**
    95.   * Allows the dev to specify the type of update that will be run.
    96.   */
    97. public enum UpdateType
    98. {
    99. /**
    100.   * Run a version check, and then if the file is out of date, download the newest version.
    101.   */
    102. DEFAULT,
    103. /**
    104.   * Don't run a version check, just find the latest update and download it.
    105.   */
    106. NO_VERSION_CHECK,
    107. /**
    108.   * Get information about the version and the download size, but don't actually download anything.
    109.   */
    110. NO_DOWNLOAD
    111. }
    112.  
    113. /**
    114.   * Initialize the updater
    115.   *
    116.   * @param plugin
    117.   * The plugin that is checking for an update.
    118.   * @param slug
    119.   * The dev.bukkit.org slug of the project ([url]http://dev.bukkit.org/server-mods/SLUG_IS_HERE[/url])
    120.   * @param file
    121.   * The file that the plugin is running from, get this by doing this.getFile() from within your main class.
    122.   * @param type
    123.   * Specify the type of update this will be. See {@link UpdateType}
    124.   * @param announce
    125.   * True if the program should announce the progress of new updates in console
    126.   */
    127. public Updater(Plugin plugin, String slug, File file, UpdateType type, boolean announce)
    128. {
    129. this.plugin = plugin;
    130. this.type = type;
    131. this.announce = announce;
    132. this.file = file;
    133. try
    134. {
    135. // Obtain the results of the project's file feed
    136. url = new URL(DBOUrl + slug + "/files.rss");
    137. }
    138. {
    139. // Invalid slug
    140. plugin.getLogger().warning("The author of this plugin (" + plugin.getDescription().getAuthors().get(0) + ") has misconfigured their Auto Update system");
    141. plugin.getLogger().warning("The project slug given ('" + slug + "') is invalid. Please nag the author about this.");
    142. result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad! :(
    143. }
    144. thread = new Thread(new UpdateRunnable());
    145. thread.start();
    146. }
    147.  
    148. /**
    149.   * Get the result of the update process.
    150.   */
    151. public Updater.UpdateResult getResult()
    152. {
    153. waitForThread();
    154. return result;
    155. }
    156.  
    157. /**
    158.   * Get the total bytes of the file (can only be used after running a version check or a normal run).
    159.   */
    160. public long getFileSize()
    161. {
    162. waitForThread();
    163. return totalSize;
    164. }
    165.  
    166. /**
    167.   * Get the version string latest file avaliable online.
    168.   */
    169. public String getLatestVersionString()
    170. {
    171. waitForThread();
    172. return versionTitle;
    173. }
    174.  
    175. /**
    176.   * Get the latest version number only.
    177.   */
    178. public String getLatestVersion(){
    179. waitForThread();
    180. return versionTitle.split(" v")[1].split(" ")[0];
    181. }
    182.  
    183. /**
    184.   * As the result of Updater output depends on the thread's completion, it is necessary to wait for the thread to finish
    185.   * before allowing anyone to check the result.
    186.   */
    187. public void waitForThread() {
    188. if(thread.isAlive()) {
    189. try {
    190. thread.join();
    191. } catch (InterruptedException e) {
    192. e.printStackTrace();
    193. }
    194. }
    195. }
    196.  
    197. /**
    198.   * Save an update from dev.bukkit.org into the server's update folder.
    199.   */
    200. private void saveFile(File folder, String file, String u)
    201. {
    202. if(!folder.exists())
    203. {
    204. folder.mkdir();
    205. }
    206. FileOutputStream fout = null;
    207. try
    208. {
    209. // Download the file
    210. URL url = new URL(u);
    211. int fileLength = url.openConnection().getContentLength();
    212. in = new BufferedInputStream(url.openStream());
    213. fout = new FileOutputStream(folder.getAbsolutePath() + "/" + file);
    214.  
    215. byte[] data = new byte[BYTE_SIZE];
    216. int count;
    217. if(announce) plugin.getLogger().info("About to download a new update: " + versionTitle);
    218. long downloaded = 0;
    219. while ((count = in.read(data, 0, BYTE_SIZE)) != -1)
    220. {
    221. downloaded += count;
    222. fout.write(data, 0, count);
    223. int percent = (int) (downloaded * 100 / fileLength);
    224. if(announce & (percent % 10 == 0))
    225. {
    226. plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes.");
    227. }
    228. }
    229. //Just a quick check to make sure we didn't leave any files from last time...
    230. for(File xFile : new File("plugins/" + updateFolder).listFiles())
    231. {
    232. if(xFile.getName().endsWith(".zip"))
    233. {
    234. xFile.delete();
    235. }
    236. }
    237. // Check to see if it's a zip file, if it is, unzip it.
    238. File dFile = new File(folder.getAbsolutePath() + "/" + file);
    239. if(dFile.getName().endsWith(".zip"))
    240. {
    241. // Unzip
    242. unzip(dFile.getCanonicalPath());
    243. }
    244. if(announce) plugin.getLogger().info("Finished updating.");
    245. }
    246. catch (Exception ex)
    247. {
    248. plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful.");
    249. result = Updater.UpdateResult.FAIL_DOWNLOAD;
    250. }
    251. finally
    252. {
    253. try
    254. {
    255. if (in != null)
    256. {
    257. in.close();
    258. }
    259. if (fout != null)
    260. {
    261. fout.close();
    262. }
    263. }
    264. catch (Exception ex)
    265. {
    266. }
    267. }
    268. }
    269.  
    270. /**
    271.   * Part of Zip-File-Extractor, modified by H31IX for use with Bukkit
    272.   */
    273. private void unzip(String file)
    274. {
    275. try
    276. {
    277. File fSourceZip = new File(file);
    278. String zipPath = file.substring(0, file.length()-4);
    279. ZipFile zipFile = new ZipFile(fSourceZip);
    280. Enumeration<? extends ZipEntry> e = zipFile.entries();
    281. while(e.hasMoreElements())
    282. {
    283. ZipEntry entry = (ZipEntry)e.nextElement();
    284. File destinationFilePath = new File(zipPath,entry.getName());
    285. destinationFilePath.getParentFile().mkdirs();
    286. if(entry.isDirectory())
    287. {
    288. continue;
    289. }
    290. else
    291. {
    292. BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));
    293. int b;
    294. byte buffer[] = new byte[BYTE_SIZE];
    295. FileOutputStream fos = new FileOutputStream(destinationFilePath);
    296. BufferedOutputStream bos = new BufferedOutputStream(fos, BYTE_SIZE);
    297. while((b = bis.read(buffer, 0, BYTE_SIZE)) != -1)
    298. {
    299. bos.write(buffer, 0, b);
    300. }
    301. bos.flush();
    302. bos.close();
    303. bis.close();
    304. String name = destinationFilePath.getName();
    305. if(name.endsWith(".jar") && pluginFile(name))
    306. {
    307. destinationFilePath.renameTo(new File("plugins/" + updateFolder + "/" + name));
    308. }
    309. }
    310. entry = null;
    311. destinationFilePath = null;
    312. }
    313. e = null;
    314. zipFile.close();
    315. zipFile = null;
    316. // Move any plugin data folders that were included to the right place, Bukkit won't do this for us.
    317. for(File dFile : new File(zipPath).listFiles())
    318. {
    319. if(dFile.isDirectory())
    320. {
    321. if(pluginFile(dFile.getName()))
    322. {
    323. File oFile = new File("plugins/" + dFile.getName()); // Get current dir
    324. File [] contents = oFile.listFiles(); // List of existing files in the current dir
    325. for(File cFile : dFile.listFiles()) // Loop through all the files in the new dir
    326. {
    327. boolean found = false;
    328. for(File xFile : contents) // Loop through contents to see if it exists
    329. {
    330. if(xFile.getName().equals(cFile.getName()))
    331. {
    332. found = true;
    333. break;
    334. }
    335. }
    336. if(!found)
    337. {
    338. // Move the new file into the current dir
    339. cFile.renameTo(new File(oFile.getCanonicalFile() + "/" + cFile.getName()));
    340. }
    341. else
    342. {
    343. // This file already exists, so we don't need it anymore.
    344. cFile.delete();
    345. }
    346. }
    347. }
    348. }
    349. dFile.delete();
    350. }
    351. new File(zipPath).delete();
    352. fSourceZip.delete();
    353. }
    354. catch(IOException ex)
    355. {
    356. ex.printStackTrace();
    357. plugin.getLogger().warning("The auto-updater tried to unzip a new update file, but was unsuccessful.");
    358. result = Updater.UpdateResult.FAIL_DOWNLOAD;
    359. }
    360. new File(file).delete();
    361. }
    362.  
    363. /**
    364.   * Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip.
    365.   */
    366. public boolean pluginFile(String name)
    367. {
    368. for(File file : new File("plugins").listFiles())
    369. {
    370. if(file.getName().equals(name))
    371. {
    372. return true;
    373. }
    374. }
    375. return false;
    376. }
    377.  
    378. /**
    379.   * Obtain the direct download file url from the file's page.
    380.   */
    381. private String getFile(String link)
    382. {
    383. String download = null;
    384. try
    385. {
    386. // Open a connection to the page
    387. URL url = new URL(link);
    388. URLConnection urlConn = url.openConnection();
    389. InputStreamReader inStream = new InputStreamReader(urlConn.getInputStream());
    390. BufferedReader buff = new BufferedReader(inStream);
    391.  
    392. int counter = 0;
    393. String line;
    394. while((line = buff.readLine()) != null)
    395. {
    396. counter++;
    397. // Search for the download link
    398. if(line.contains("<li class=\"user-action user-action-download\">"))
    399. {
    400. // Get the raw link
    401. download = line.split("<a href=\"")[1].split("\">Download</a>")[0];
    402. }
    403. // Search for size
    404. else if (line.contains("<dt>Size</dt>"))
    405. {
    406. sizeLine = counter+1;
    407. }
    408. else if(counter == sizeLine)
    409. {
    410. String size = line.replaceAll("<dd>", "").replaceAll("</dd>", "");
    411. multiplier = size.contains("MiB") ? 1048576 : 1024;
    412. size = size.replace(" KiB", "").replace(" MiB", "");
    413. totalSize = (long)(Double.parseDouble(size)*multiplier);
    414. }
    415. }
    416. urlConn = null;
    417. inStream = null;
    418. buff.close();
    419. buff = null;
    420. }
    421. catch (Exception ex)
    422. {
    423. ex.printStackTrace();
    424. plugin.getLogger().warning("The auto-updater tried to contact dev.bukkit.org, but was unsuccessful.");
    425. result = Updater.UpdateResult.FAIL_DBO;
    426. return null;
    427. }
    428. return download;
    429. }
    430.  
    431. /**
    432.   * Check to see if the program should continue by evaluation whether the plugin is already updated, or shouldn't be updated
    433.   */
    434. private boolean versionCheck(String title)
    435. {
    436. if(type != UpdateType.NO_VERSION_CHECK)
    437. {
    438. String version = plugin.getDescription().getVersion();
    439. if(title.split(" v").length == 2)
    440. {
    441. String remoteVersion = title.split(" v")[1].split(" ")[0]; // Get the newest file's version number
    442. int remVer = -1,curVer=0;
    443. try
    444. {
    445. remVer = calVer(remoteVersion);
    446. curVer = calVer(version);
    447. }
    448. {
    449. remVer=-1;
    450. }
    451. if(hasTag(version)||version.equalsIgnoreCase(remoteVersion)||curVer>=remVer)
    452. {
    453. // We already have the latest version, or this build is tagged for no-update
    454. result = Updater.UpdateResult.NO_UPDATE;
    455. return false;
    456. }
    457. }
    458. else
    459. {
    460. // The file's name did not contain the string 'vVersion'
    461. plugin.getLogger().warning("The author of this plugin (" + plugin.getDescription().getAuthors().get(0) + ") has misconfigured their Auto Update system");
    462. plugin.getLogger().warning("Files uploaded to BukkitDev should contain the version number, seperated from the name by a 'v', such as PluginName v1.0");
    463. plugin.getLogger().warning("Please notify the author of this error.");
    464. result = Updater.UpdateResult.FAIL_NOVERSION;
    465. return false;
    466. }
    467. }
    468. return true;
    469. }
    470. /**
    471.   * Used to calculate the version string as an Integer
    472.   */
    473. private Integer calVer(String s) throws NumberFormatException
    474. {
    475. if(s.contains("."))
    476. {
    477. StringBuilder sb = new StringBuilder();
    478. for (int i = 0; i <s.length(); i++)
    479. {
    480. Character c = s.charAt(i);
    481. if (Character.isLetterOrDigit(c))
    482. {
    483. sb.append(c);
    484. }
    485. }
    486. return Integer.parseInt(sb.toString());
    487. }
    488. return Integer.parseInt(s);
    489. }
    490. /**
    491.   * Evaluate whether the version number is marked showing that it should not be updated by this program
    492.   */
    493. private boolean hasTag(String version)
    494. {
    495. for(String string : noUpdateTag)
    496. {
    497. if(version.contains(string))
    498. {
    499. return true;
    500. }
    501. }
    502. return false;
    503. }
    504.  
    505. /**
    506.   * Part of RSS Reader by Vogella, modified by H31IX for use with Bukkit
    507.   */
    508. private boolean readFeed()
    509. {
    510. try
    511. {
    512. // Set header values intial to the empty string
    513. String title = "";
    514. String link = "";
    515. // First create a new XMLInputFactory
    516. XMLInputFactory inputFactory = XMLInputFactory.newInstance();
    517. // Setup a new eventReader
    518. InputStream in = read();
    519. if(in != null)
    520. {
    521. XMLEventReader eventReader = inputFactory.createXMLEventReader(in);
    522. // Read the XML document
    523. while (eventReader.hasNext())
    524. {
    525. XMLEvent event = eventReader.nextEvent();
    526. if (event.isStartElement())
    527. {
    528. if (event.asStartElement().getName().getLocalPart().equals(TITLE))
    529. {
    530. event = eventReader.nextEvent();
    531. title = event.asCharacters().getData();
    532. continue;
    533. }
    534. if (event.asStartElement().getName().getLocalPart().equals(LINK))
    535. {
    536. event = eventReader.nextEvent();
    537. link = event.asCharacters().getData();
    538. continue;
    539. }
    540. }
    541. else if (event.isEndElement())
    542. {
    543. if (event.asEndElement().getName().getLocalPart().equals(ITEM))
    544. {
    545. // Store the title and link of the first entry we get - the first file on the list is all we need
    546. versionTitle = title;
    547. versionLink = link;
    548. // All done, we don't need to know about older files.
    549. break;
    550. }
    551. }
    552. }
    553. return true;
    554. }
    555. else
    556. {
    557. return false;
    558. }
    559. }
    560. catch (XMLStreamException e)
    561. {
    562. plugin.getLogger().warning("Could not reach dev.bukkit.org for update checking. Is it offline?");
    563. return false;
    564. }
    565. }
    566.  
    567. /**
    568.   * Open the RSS feed
    569.   */
    570. private InputStream read()
    571. {
    572. try
    573. {
    574. return url.openStream();
    575. }
    576. catch (IOException e)
    577. {
    578. plugin.getLogger().warning("Could not reach BukkitDev file stream for update checking. Is dev.bukkit.org offline?");
    579. return null;
    580. }
    581. }
    582.  
    583. private class UpdateRunnable implements Runnable {
    584.  
    585. public void run() {
    586. if(url != null)
    587. {
    588. // Obtain the results of the project's file feed
    589. if(readFeed())
    590. {
    591. if(versionCheck(versionTitle))
    592. {
    593. String fileLink = getFile(versionLink);
    594. if(fileLink != null && type != UpdateType.NO_DOWNLOAD)
    595. {
    596. String name = file.getName();
    597. // If it's a zip file, it shouldn't be downloaded as the plugin's name
    598. if(fileLink.endsWith(".zip"))
    599. {
    600. String [] split = fileLink.split("/");
    601. name = split[split.length-1];
    602. }
    603. saveFile(new File("plugins/" + updateFolder), name, fileLink);
    604. }
    605. else
    606. {
    607. result = UpdateResult.UPDATE_AVAILABLE;
    608. }
    609. }
    610. }
    611. }
    612. }
    613. }
    614. }


    Thank you!
     
  2. Offline

    iiHeroo

    I don't see why you need to check the version of the game that it's made for, but I doubt anyone has done that, since most people use H31IX's auto updater, and, I think you'd get a better response on the Updater's page.
     
  3. Offline

    alex123099

    iiHeroo
    I just wanted to make it so that you won't be able to update the plugin unless your minecraft server version is the same as the one the plugin is compiled with, so that you won't get a situation where you get the plugin made for a higher version than you are running at the moment.
    And what is the link to the Updater page?
     
  4. Offline

    iiHeroo


    http://forums.bukkit.org/threads/up...mpliant-auto-updating-for-your-plugins.96681/

    Anyways, I think they won't have issues with your plugins, unless you use NMS code, I believe. net.minecraft.server
     
  5. Offline

    alex123099

    iiHeroo
    Haven't been on that thread for a long time and I see they have added a getLatestGameVersion function now. The only problem with this new Updater class is that it has its own config file that it creates. Thanks anyways!
     
  6. Offline

    iiHeroo


    You're welcome, and the new file it creates is for every updater, so if a server uses plugins with the same updater, you can globally disable it in the "Updater" file. Also edit the thread to "Solved" or "Filled"
     
  7. Offline

    DioArcangel

Thread Status:
Not open for further replies.

Share This Page