Index: src/org/openstreetmap/josm/gui/GettingStarted.java
===================================================================
--- src/org/openstreetmap/josm/gui/GettingStarted.java	(revision 1446)
+++ src/org/openstreetmap/josm/gui/GettingStarted.java	(working copy)
@@ -24,13 +24,13 @@
 import javax.swing.border.EmptyBorder;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.CacheCustomContent;
 import org.openstreetmap.josm.tools.OpenBrowser;
 import org.openstreetmap.josm.tools.WikiReader;
 import org.openstreetmap.josm.actions.AboutAction;
 
 public class GettingStarted extends JPanel {
-
-    static private String content = "";
+    private String content = "";
     static private String styles = "<style type=\"text/css\">\n"+
             "body { font-family: sans-serif; font-weight: bold; }\n"+
             "h1 {text-align: center;}\n"+
@@ -50,27 +50,45 @@
             }
         }
     }
-
-    public class readMOTD implements Callable<String> {
+    
+    /** 
+     * This class encapsulates the "reading URL" task and can be executed in background and in 
+     * parallel. Since the MOTD is many separate pages this speeds things up quite a lot. If no 
+     * localized version is available, it automatically falls back to the international one. 
+     */
+    private class readMOTD implements Callable<String> {
         private boolean isLocalized;
         private boolean isHelp;
         private String urlLoc;
         private String urlIntl;
         private String urlBase;
 
-        readMOTD(boolean isLocalized, String urlBase, String urlLoc, String urlIntl, boolean isHelp) {
+        /** 
+         * Read a MOTD page 
+         * @param isLocalized If true, tries to get localized version as defined in urlLoc 
+         * @param urlBase Base URL (i.e. http://www.openstreetmap.de/wiki/) 
+         * @param urlLoc Part to append to base URL to receive localized version 
+         * @param urlIntl Part to append to base URL to receive international version 
+         * @param makeList If true, the URL's contents will be wrapped in a list (<ul><li>) 
+         */ 
+        readMOTD(boolean isLocalized, String urlBase, String urlLoc, String urlIntl, boolean makeList) {
           this.isLocalized = isLocalized;
           this.urlBase = urlBase;
           this.urlLoc = urlLoc;
           this.urlIntl = urlIntl;
-          this.isHelp = isHelp;
+          this.isHelp = makeList;
         }
 
+        /* 
+         * Does the actual work 
+         * @see java.util.concurrent.Callable#call() 
+         */ 
         public String call() {
             WikiReader wr = new WikiReader(urlBase);
             String content = "";
             try {
-                // If we hit a non-localized link here, we already know there's no translated version available
+                // If we hit a non-localized link here, we already know there's no translated
+                // version available
                 String message = isLocalized ? wr.read(urlLoc) : "";
                 // Look for non-localized version
                 if (message.equals(""))
@@ -80,13 +98,15 @@
                     if(isHelp)
                         content += message;
                     else
-                        content += "<ul><li>"+ message.substring(8).replaceAll("\n *\\* +","</li><li>")+"</li></ul>";
+                        content += "<ul><li>"+ message.substring(8)
+                                            .replaceAll("\n *\\* +","</li><li>")+"</li></ul>";
             } catch (IOException ioe) {
                 try {
                     if(isHelp)
                         content += wr.read(urlIntl);
                     else
-                        content += "<ul><li>"+wr.read(urlIntl).substring(8).replaceAll("\n *\\* +","</li><li>")+"</li></ul>";
+                        content += "<ul><li>"+wr.read(urlIntl).substring(8)
+                                            .replaceAll("\n *\\* +","</li><li>")+"</li></ul>";
                 } catch (IOException ioe2) {
                 }
             }
@@ -95,104 +115,177 @@
         }
     }
 
-    private void assignContent() {
-        if (content.length() > 0 && Main.pref.getBoolean("help.displaymotd", true)) return;
-
-        String baseurl = Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
-        WikiReader wr = new WikiReader(baseurl);
-        String motdcontent = "";
-        try {
-            motdcontent = wr.read(baseurl + "/wiki/MessageOfTheDay?format=txt");
-        } catch (IOException ioe) {
-            motdcontent = "<html>" + styles + "<body><h1>" +
-                "JOSM - " + tr("Java OpenStreetMap Editor") +
-                "</h1>\n<h2 align=\"center\">(" +
-                tr ("Message of the day not available") +
-                ")</h2>";
+    /** 
+     * Grabs current MOTD from cache or webpage and parses it.
+     */ 
+    private class assignContent extends CacheCustomContent {
+        public assignContent() {
+            super("motd.html", CacheCustomContent.INTERVAL_DAILY);
         }
+        
+        final private int myVersion = AboutAction.getVersionNumber();
 
-        int myVersion = AboutAction.getVersionNumber();
-        String languageCode = Main.getLanguageCodeU();
-
-        // Finds wiki links like (underscores inserted for readability): [wiki:LANGCODE:messageoftheday_CONDITON_REVISION LANGCODE]
-        // Langcode usually consists of two letters describing the language and may be omitted
-        // Condition may be one of the following: >  <  <=  =>
-        // Revision is the JOSM version
-        Pattern versionPattern = Pattern.compile("\\[wiki:(?:[A-Z]+:)?MessageOfTheDay(\\>\\=|\\<\\=|\\<|\\>)([0-9]+)\\s*([A-Z]*)\\]", Pattern.CASE_INSENSITIVE);
-        // 1=condition, 2=targetVersion, 3=lang
-        Matcher matcher = versionPattern.matcher(motdcontent);
-        matcher.reset();
-
-        ArrayList<String[]> links = new ArrayList<String[]>();
-        String linksList="";
-        while (matcher.find()) {
-            // Discards all but the selected locale and non-localized links
-            if(!(matcher.group(3)+":").equals(languageCode) && !matcher.group(3).equals(""))
-                continue;
-
-            links.add(new String[] {matcher.group(1), matcher.group(2), matcher.group(3)});
-            linksList += matcher.group(1)+matcher.group(2)+matcher.group(3)+": ";
-        }
-
-        // We cannot use Main.worker here because it's single-threaded and
-        // setting it to multi-threading will cause problems elsewhere
-        ExecutorService slave = Executors.newCachedThreadPool();
-
-        ArrayList<Future<String>> linkContent = new ArrayList<Future<String>>();
-        for(int i=0; i < links.size(); i++) {
-            String[] obj = links.get(i);
-            int targetVersion = Integer.parseInt(obj[1]);
-            String condition = obj[0];
-            Boolean isLocalized = !obj[2].equals("");
-
-            // Prefer localized over non-localized links, if they're otherwise the same
-            if(!isLocalized && linksList.indexOf(condition + obj[1] + languageCode + " ") >= 0)
-                continue;
-
-            boolean included = false;
-
-            if(myVersion == 0)
-              included = true;
-            else if(condition.equals(">="))
-              included=myVersion >= targetVersion;
-            else if(condition.equals(">"))
-              included = myVersion > targetVersion;
-            else if(condition.equals("<"))
-              included=myVersion < targetVersion;
-            else
-              included = myVersion <= targetVersion;
-
-            if(!included) continue;
-
-            boolean isHelp = targetVersion == 1;
-            String urlStart = baseurl + "/wiki/";
-            String urlEnd = "MessageOfTheDay" + condition + targetVersion + (isHelp ? "" : "?format=txt");
-            String urlLoc = urlStart + languageCode + urlEnd;
-            String urlIntl = urlStart + urlEnd;
-
-            // This adds all links to the worker which will download them concurrently
-            linkContent.add(slave.submit(new readMOTD(isLocalized, baseurl, urlLoc, urlIntl, isHelp)));
-        }
-
-        for(int i=0; i < linkContent.size(); i++) {
+        /**
+         * This function gets executed whenever the cached files need updating
+         * @see org.openstreetmap.josm.io.CacheCustomContent#updateData()
+         */
+        protected byte[] updateData() {
+            String motd = "";
+            String baseurl = Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
+            WikiReader wr = new WikiReader(baseurl);
+            String motdcontent = "";
             try {
-                content += linkContent.get(i).get();
-            } catch (Exception e) {}
+                motdcontent = wr.read(baseurl + "/wiki/MessageOfTheDay?format=txt");
+            } catch (IOException ioe) {
+                motdcontent = "<html>" + styles + "<body><h1>" +
+                    "JOSM - " + tr("Java OpenStreetMap Editor") +
+                    "</h1>\n<h2 align=\"center\">(" +
+                    tr ("Message of the day not available") +
+                    ")</h2>";
+            }
+    
+            String languageCode = Main.getLanguageCodeU();
+    
+            // Finds wiki links like (underscores inserted for readability):
+            // [wiki:LANGCODE:messageoftheday_CONDITON_REVISION LANGCODE]
+            // Langcode usually consists of two letters describing the language and may be omitted
+            // Condition may be one of the following: >  <  <=  =>
+            // Revision is the JOSM version
+            Pattern versionPattern = Pattern.compile(
+                    "\\[wiki:(?:[A-Z]+:)?MessageOfTheDay(\\>\\=|\\<\\=|\\<|\\>)([0-9]+)\\s*([A-Z]*)\\]",
+                    Pattern.CASE_INSENSITIVE);
+            // 1=condition, 2=targetVersion, 3=lang
+            Matcher matcher = versionPattern.matcher(motdcontent);
+            matcher.reset();
+    
+            ArrayList<String[]> links = new ArrayList<String[]>();
+            String linksList="";
+            while (matcher.find()) {
+                // Discards all but the selected locale and non-localized links
+                if(!(matcher.group(3)+":").equals(languageCode) && !matcher.group(3).equals(""))
+                    continue;
+    
+                links.add(new String[] {matcher.group(1), matcher.group(2), matcher.group(3)});
+                linksList += matcher.group(1)+matcher.group(2)+matcher.group(3)+": ";
+            }
+    
+            // We cannot use Main.worker here because it's single-threaded and
+            // setting it to multi-threading will cause problems elsewhere
+            ExecutorService slave = Executors.newCachedThreadPool();
+    
+            ArrayList<Future<String>> linkContents = new ArrayList<Future<String>>();
+            for(int i=0; i < links.size(); i++) {
+                String[] obj = links.get(i);
+                int targetVersion = Integer.parseInt(obj[1]);
+                String condition = obj[0];
+                Boolean isLocalized = !obj[2].equals("");
+    
+                // Prefer localized over non-localized links, if they're otherwise the same
+                if(!isLocalized && linksList.indexOf(condition + obj[1] + languageCode + " ") >= 0)
+                    continue;
+    
+                boolean included = false;
+    
+                if(myVersion == 0)
+                  included = true;
+                else if(condition.equals(">="))
+                  included=myVersion >= targetVersion;
+                else if(condition.equals(">"))
+                  included = myVersion > targetVersion;
+                else if(condition.equals("<"))
+                  included=myVersion < targetVersion;
+                else
+                  included = myVersion <= targetVersion;
+    
+                if(!included) continue;
+    
+                boolean isHelp = targetVersion == 1;
+                String urlStart = baseurl + "/wiki/";
+                String urlEnd = "MessageOfTheDay" + condition + targetVersion 
+                                    + (isHelp ? "" : "?format=txt");
+                String urlLoc = urlStart + languageCode + urlEnd;
+                String urlIntl = urlStart + urlEnd;
+    
+                // This adds all links to the worker which will download them concurrently
+                linkContents.add(slave.submit(new readMOTD(isLocalized, baseurl, urlLoc, urlIntl, isHelp)));
+            }
+            // Gets newest version numbers
+            linkContents.add(slave.submit(new readMOTD(false, baseurl, "", 
+                    baseurl + "/version?format=txt", true))); 
+    
+            for(int i=0; i < linkContents.size()-1; i++) {
+                try {
+                    motd += linkContents.get(i).get();
+                } catch (Exception e) {}
+            }
+            
+            motd = "<html>"
+                + styles
+                + "<h1>JOSM - "
+                + tr("Java OpenStreetMap Editor")
+                + "</h1>"
+                + motd.replace("</html>", "")
+                + getVersionNumber(linkContents.get(linkContents.size()-1))
+                + "</html>";
+            
+            linkContents.clear();
+            try {
+                slave.shutdown(); 
+            } catch(SecurityException x) {}
+            
+            // Save this to prefs in case JOSM is updated so MOTD can be refreshed
+            Main.pref.putInteger("cache.motd.html.version", myVersion);
+            
+            return motd.getBytes();
         }
         
-        linkContent.clear();
-        try {
-            slave.shutdown(); 
-        } catch(SecurityException x) {}
+        /**
+         * Additionally check if JOSM has been updated and refresh MOTD
+         */
+        @Override
+        protected boolean isCacheValid() {
+            // We assume a default of myVersion because it only kicks in in two cases:
+            // 1. Not yet written – but so isn't the interval variable, so it gets updated anyway
+            // 2. Cannot be written (e.g. while developing). Obviously we don't want to update
+            //    everytime because of something we can't read.
+            return Main.pref.getInteger("cache.motd.html.version", myVersion) == myVersion;
+        }
         
-
-        content = "<html>\n"+
-            styles +
-            "<h1>JOSM - " + tr("Java OpenStreetMap Editor") + "</h1>\n"+
-            content+"\n"+
-            "</html>";
+        /**
+         * Tries to read the version number from a given Future<String>
+         * @param Future<String> that contains the version page
+         * @return String with HTML Code
+         */
+        private String getVersionNumber(Future<String> linkContent) {
+            String version = "";
+            int curVersion = 0;
+            try { 
+                String v[] = linkContent.get().split("\n");
+                // tested (6: <html>, 17: josm-tested.jar)
+                curVersion = Integer.parseInt(v[0].substring(6+17)); 
+                version = tr("Yours: {2}; Current: {0}; <font style=\"font-size:x-small\">" 
+                                + "(latest untested: {1} &#150; not recommended)</font>",
+                            curVersion, 
+                            new Integer(v[1].substring(17).trim()), // latest
+                            myVersion); 
+            } catch(Exception e) {}
+            
+            if(version.equals(""))
+                return "";
+            
+            return "<div style=\"text-align:right;font-size:small;font-weight:normal;\">"
+                        + "<b>"
+                        + (curVersion > myVersion ? tr("Update available") + " &#151; ": "")
+                        + tr("Version Details:") + "</b> "
+                        + version
+                        + "</div>"; 
+        }
     }
-
+    
+    /** 
+     * Initializes getting the MOTD as well as enabling the FileDrop Listener. 
+     * Displays a message while the MOTD is downloading. 
+     */ 
     public GettingStarted() {
         super(new BorderLayout());
         final LinkGeneral lg = new LinkGeneral(
@@ -211,7 +304,9 @@
         // Asynchronously get MOTD to speed-up JOSM startup
         Thread t = new Thread(new Runnable() {
             public void run() {
-                assignContent();
+                if (content.length() == 0 && Main.pref.getBoolean("help.displaymotd", true))
+                    content = new assignContent().updateIfRequiredString();
+                     
                 EventQueue.invokeLater(new Runnable() {
                     public void run() {
                        lg.setText(content);
Index: src/org/openstreetmap/josm/io/CacheCustomContent.java
===================================================================
--- src/org/openstreetmap/josm/io/CacheCustomContent.java	(revision 0)
+++ src/org/openstreetmap/josm/io/CacheCustomContent.java	(revision 0)
@@ -0,0 +1,165 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Date;
+
+import org.openstreetmap.josm.Main;
+
+public abstract class CacheCustomContent {
+    /**
+     * Common intervals
+     */
+    final static public int INTERVAL_ALWAYS = -1;
+    final static public int INTERVAL_HOURLY = 60*60;
+    final static public int INTERVAL_DAILY = INTERVAL_HOURLY * 24;
+    final static public int INTERVAL_WEEKLY = INTERVAL_DAILY * 7;
+    final static public int INTERVAL_MONTHLY = INTERVAL_WEEKLY * 4;
+    final static public int INTERVAL_NEVER = Integer.MAX_VALUE;
+    
+    /**
+     * Where the data will be stored    
+     */
+    private byte[] data = null;
+    
+    /**
+     * The ident that identifies the stored file. Includes file-ending.
+     */
+    final private String ident;
+    
+    /**
+     * The (file-)path where the data will be stored
+     */
+    final private File path;
+    
+    /**
+     * How often to update the cached version
+     */
+    final private int updateInterval;
+    
+    /**
+     * This function will be executed when an update is required. It has to be implemented by the
+     * inheriting class and should use a worker if it has a long wall time as the function is
+     * executed in the current thread.
+     * @return the data to cache
+     */
+    protected abstract byte[] updateData();
+    
+    /**
+     * This function serves as a comfort hook to perform additional checks if the cache is valid
+     * @return True if the cached copy is still valid
+     */
+    protected boolean isCacheValid() {
+        return true;
+    }    
+    
+    /**
+     * Initializes the class. Note that all read data will be stored in memory until it is flushed
+     * by flushData(). 
+     * @param ident
+     * @param updateInterval
+     */
+    public CacheCustomContent(String ident, int updateInterval) {
+        this.ident = ident;
+        this.updateInterval = updateInterval;
+        this.path = new File(Main.pref.getPreferencesDir(), ident);
+    }
+    
+    /**
+     * Updates data if required
+     * @return Returns the data
+     */
+    public byte[] updateIfRequired() {
+        if(Main.pref.getInteger("cache." + ident, 0) + updateInterval < new Date().getTime()/1000
+                || !isCacheValid())
+            return updateForce();
+        return getData();
+    }
+    
+    /**
+     * Updates data if required
+     * @return Returns the data as string
+     */
+    public String updateIfRequiredString() {
+        if(Main.pref.getInteger("cache." + ident, 0) + updateInterval < new Date().getTime()/1000
+                || !isCacheValid())
+            return updateForceString();
+        return getDataString();
+    }
+    
+    /**
+     * Executes an update regardless of updateInterval
+     * @return Returns the data
+     */
+    public byte[] updateForce() {
+        this.data = updateData();
+        saveToDisk();
+        Main.pref.putInteger("cache." + ident, (int)(new Date().getTime()/1000));
+        return data;
+    }
+    
+    /**
+     * Executes an update regardless of updateInterval
+     * @return Returns the data as String
+     */
+    public String updateForceString() {
+        updateForce();
+        return new String(data);
+    }
+
+    /**
+     * Returns the data without performing any updates
+     * @return the data
+     */
+    public byte[] getData() {
+        if(data == null)
+            loadFromDisk();
+        return data;
+    }
+    
+    /**
+     * Returns the data without performing any updates
+     * @return the data as String
+     */
+    public String getDataString() {
+        return new String(getData());
+    }
+
+    /**
+     * Tries to load the data using the given ident from disk. If this fails, data will be updated 
+     */
+    private void loadFromDisk() {
+        try {
+            BufferedInputStream input = new BufferedInputStream(new FileInputStream(path));
+            this.data = new byte[input.available()];
+            input.read(this.data);
+            input.close();
+        } catch(Exception e) {
+            this.data = updateForce();
+        }
+    }
+    
+    /**
+     * Stores the data to disk
+     */
+    private void saveToDisk() {
+        try {
+            BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(path));
+            output.write(this.data);
+            output.flush();
+            output.close();
+        } catch(Exception e) {}
+    }       
+    
+    /**
+     * Flushes the data from memory. Class automatically reloads it from disk or updateData() if
+     * required
+     */
+    public void flushData() {
+        data = null;
+    }
+}
