001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import java.io.BufferedInputStream; 005import java.io.BufferedOutputStream; 006import java.io.File; 007import java.io.FileInputStream; 008import java.io.FileOutputStream; 009import java.io.IOException; 010import java.nio.charset.StandardCharsets; 011 012import org.openstreetmap.josm.Main; 013 014/** 015 * Use this class if you want to cache and store a single file that gets updated regularly. 016 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files, use CacheFiles. 017 * @author xeen 018 * @param <T> a {@link Throwable} that may be thrown during {@link #updateData()}, 019 * use {@link RuntimeException} if no exception must be handled. 020 * @since 1450 021 */ 022public abstract class CacheCustomContent<T extends Throwable> { 023 024 /** Update interval meaning an update is always needed */ 025 public static final int INTERVAL_ALWAYS = -1; 026 /** Update interval meaning an update is needed each hour */ 027 public static final int INTERVAL_HOURLY = 60*60; 028 /** Update interval meaning an update is needed each day */ 029 public static final int INTERVAL_DAILY = INTERVAL_HOURLY * 24; 030 /** Update interval meaning an update is needed each week */ 031 public static final int INTERVAL_WEEKLY = INTERVAL_DAILY * 7; 032 /** Update interval meaning an update is needed each month */ 033 public static final int INTERVAL_MONTHLY = INTERVAL_WEEKLY * 4; 034 /** Update interval meaning an update is never needed */ 035 public static final int INTERVAL_NEVER = Integer.MAX_VALUE; 036 037 /** 038 * Where the data will be stored 039 */ 040 private byte[] data; 041 042 /** 043 * The ident that identifies the stored file. Includes file-ending. 044 */ 045 private final String ident; 046 047 /** 048 * The (file-)path where the data will be stored 049 */ 050 private final File path; 051 052 /** 053 * How often to update the cached version 054 */ 055 private final int updateInterval; 056 057 /** 058 * This function will be executed when an update is required. It has to be implemented by the 059 * inheriting class and should use a worker if it has a long wall time as the function is 060 * executed in the current thread. 061 * @return the data to cache 062 * @throws T a {@link Throwable} 063 */ 064 protected abstract byte[] updateData() throws T; 065 066 /** 067 * Initializes the class. Note that all read data will be stored in memory until it is flushed 068 * by flushData(). 069 * @param ident ident that identifies the stored file. Includes file-ending. 070 * @param updateInterval update interval in seconds. -1 means always 071 */ 072 public CacheCustomContent(String ident, int updateInterval) { 073 this.ident = ident; 074 this.updateInterval = updateInterval; 075 this.path = new File(Main.pref.getCacheDirectory(), ident); 076 } 077 078 /** 079 * This function serves as a comfort hook to perform additional checks if the cache is valid 080 * @return True if the cached copy is still valid 081 */ 082 protected boolean isCacheValid() { 083 return true; 084 } 085 086 private boolean needsUpdate() { 087 if (isOffline()) { 088 return false; 089 } 090 return Main.pref.getInteger("cache." + ident, 0) + updateInterval < System.currentTimeMillis()/1000 091 || !isCacheValid(); 092 } 093 094 private boolean isOffline() { 095 try { 096 checkOfflineAccess(); 097 return false; 098 } catch (OfflineAccessException e) { 099 Main.trace(e); 100 return true; 101 } 102 } 103 104 protected void checkOfflineAccess() { 105 // To be overriden by subclasses 106 } 107 108 /** 109 * Updates data if required 110 * @return Returns the data 111 * @throws T if an error occurs 112 */ 113 public byte[] updateIfRequired() throws T { 114 if (needsUpdate()) 115 return updateForce(); 116 return getData(); 117 } 118 119 /** 120 * Updates data if required 121 * @return Returns the data as string 122 * @throws T if an error occurs 123 */ 124 public String updateIfRequiredString() throws T { 125 if (needsUpdate()) 126 return updateForceString(); 127 return getDataString(); 128 } 129 130 /** 131 * Executes an update regardless of updateInterval 132 * @return Returns the data 133 * @throws T if an error occurs 134 */ 135 public byte[] updateForce() throws T { 136 this.data = updateData(); 137 saveToDisk(); 138 Main.pref.putInteger("cache." + ident, (int) (System.currentTimeMillis()/1000)); 139 return data; 140 } 141 142 /** 143 * Executes an update regardless of updateInterval 144 * @return Returns the data as String 145 * @throws T if an error occurs 146 */ 147 public String updateForceString() throws T { 148 updateForce(); 149 return new String(data, StandardCharsets.UTF_8); 150 } 151 152 /** 153 * Returns the data without performing any updates 154 * @return the data 155 * @throws T if an error occurs 156 */ 157 public byte[] getData() throws T { 158 if (data == null) { 159 loadFromDisk(); 160 } 161 return data; 162 } 163 164 /** 165 * Returns the data without performing any updates 166 * @return the data as String 167 * @throws T if an error occurs 168 */ 169 public String getDataString() throws T { 170 byte[] array = getData(); 171 if (array == null) { 172 return null; 173 } 174 return new String(array, StandardCharsets.UTF_8); 175 } 176 177 /** 178 * Tries to load the data using the given ident from disk. If this fails, data will be updated, unless run in offline mode 179 * @throws T a {@link Throwable} 180 */ 181 private void loadFromDisk() throws T { 182 try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(path))) { 183 this.data = new byte[input.available()]; 184 if (input.read(this.data) < this.data.length) { 185 Main.error("Failed to read expected contents from "+path); 186 } 187 } catch (IOException e) { 188 Main.trace(e); 189 if (!isOffline()) { 190 this.data = updateForce(); 191 } 192 } 193 } 194 195 /** 196 * Stores the data to disk 197 */ 198 private void saveToDisk() { 199 try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(path))) { 200 output.write(this.data); 201 output.flush(); 202 } catch (IOException e) { 203 Main.error(e); 204 } 205 } 206 207 /** 208 * Flushes the data from memory. Class automatically reloads it from disk or updateData() if required 209 */ 210 public void flushData() { 211 data = null; 212 } 213}