source: josm/trunk/src/org/openstreetmap/josm/io/CacheCustomContent.java@ 16426

Last change on this file since 16426 was 16426, checked in by simon04, 4 years ago

see #18712 - Add NetworkManager.isOffline(String) to test offline status of given URL

Deprecates OnlineResource.checkOfflineAccess(String, String)

  • Property svn:eol-style set to native
File size: 6.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.BufferedInputStream;
5import java.io.BufferedOutputStream;
6import java.io.File;
7import java.io.IOException;
8import java.nio.charset.StandardCharsets;
9import java.nio.file.Files;
10import java.nio.file.InvalidPathException;
11import java.util.concurrent.TimeUnit;
12
13import org.openstreetmap.josm.spi.preferences.Config;
14import org.openstreetmap.josm.tools.Logging;
15import org.openstreetmap.josm.tools.Utils;
16
17/**
18 * Use this class if you want to cache and store a single file that gets updated regularly.
19 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files, use CacheFiles.
20 * @author xeen
21 * @param <T> a {@link Throwable} that may be thrown during {@link #updateData()},
22 * use {@link RuntimeException} if no exception must be handled.
23 * @since 1450
24 */
25public abstract class CacheCustomContent<T extends Throwable> {
26
27 /** Update interval meaning an update is always needed */
28 public static final int INTERVAL_ALWAYS = -1;
29 /** Update interval meaning an update is needed each hour */
30 public static final int INTERVAL_HOURLY = (int) TimeUnit.HOURS.toSeconds(1);
31 /** Update interval meaning an update is needed each day */
32 public static final int INTERVAL_DAILY = (int) TimeUnit.DAYS.toSeconds(1);
33 /** Update interval meaning an update is needed each week */
34 public static final int INTERVAL_WEEKLY = (int) TimeUnit.DAYS.toSeconds(7);
35 /** Update interval meaning an update is needed each month */
36 public static final int INTERVAL_MONTHLY = (int) TimeUnit.DAYS.toSeconds(28);
37 /** Update interval meaning an update is never needed */
38 public static final int INTERVAL_NEVER = Integer.MAX_VALUE;
39
40 /**
41 * Where the data will be stored
42 */
43 private byte[] data;
44
45 /**
46 * The ident that identifies the stored file. Includes file-ending.
47 */
48 private final String ident;
49
50 /**
51 * The (file-)path where the data will be stored
52 */
53 private final File path;
54
55 /**
56 * How often to update the cached version
57 */
58 private final int updateInterval;
59
60 /**
61 * This function will be executed when an update is required. It has to be implemented by the
62 * inheriting class and should use a worker if it has a long wall time as the function is
63 * executed in the current thread.
64 * @return the data to cache
65 * @throws T a {@link Throwable}
66 */
67 protected abstract byte[] updateData() throws T;
68
69 /**
70 * Initializes the class. Note that all read data will be stored in memory until it is flushed
71 * by flushData().
72 * @param ident ident that identifies the stored file. Includes file-ending.
73 * @param updateInterval update interval in seconds. -1 means always
74 */
75 public CacheCustomContent(String ident, int updateInterval) {
76 this.ident = ident;
77 this.updateInterval = updateInterval;
78 this.path = new File(Config.getDirs().getCacheDirectory(true), ident);
79 }
80
81 /**
82 * This function serves as a comfort hook to perform additional checks if the cache is valid
83 * @return True if the cached copy is still valid
84 */
85 protected boolean isCacheValid() {
86 return true;
87 }
88
89 private boolean needsUpdate() {
90 if (isOffline()) {
91 return false;
92 }
93 return Config.getPref().getInt("cache." + ident, 0) + updateInterval < TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
94 || !isCacheValid();
95 }
96
97 /**
98 * Checks underlying resource is not accessed in offline mode.
99 * @return whether resource is accessed in offline mode
100 */
101 protected abstract boolean isOffline();
102
103 /**
104 * Updates data if required
105 * @return Returns the data
106 * @throws T if an error occurs
107 */
108 public byte[] updateIfRequired() throws T {
109 if (needsUpdate())
110 return updateForce();
111 return getData();
112 }
113
114 /**
115 * Updates data if required
116 * @return Returns the data as string
117 * @throws T if an error occurs
118 */
119 public String updateIfRequiredString() throws T {
120 if (needsUpdate())
121 return updateForceString();
122 return getDataString();
123 }
124
125 /**
126 * Executes an update regardless of updateInterval
127 * @return Returns the data
128 * @throws T if an error occurs
129 */
130 private byte[] updateForce() throws T {
131 this.data = updateData();
132 saveToDisk();
133 Config.getPref().putInt("cache." + ident, (int) (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
134 return data;
135 }
136
137 /**
138 * Executes an update regardless of updateInterval
139 * @return Returns the data as String
140 * @throws T if an error occurs
141 */
142 public String updateForceString() throws T {
143 updateForce();
144 return new String(data, StandardCharsets.UTF_8);
145 }
146
147 /**
148 * Returns the data without performing any updates
149 * @return the data
150 * @throws T if an error occurs
151 */
152 public byte[] getData() throws T {
153 if (data == null) {
154 loadFromDisk();
155 }
156 return Utils.copyArray(data);
157 }
158
159 /**
160 * Returns the data without performing any updates
161 * @return the data as String
162 * @throws T if an error occurs
163 */
164 public String getDataString() throws T {
165 byte[] array = getData();
166 if (array == null) {
167 return null;
168 }
169 return new String(array, StandardCharsets.UTF_8);
170 }
171
172 /**
173 * Tries to load the data using the given ident from disk. If this fails, data will be updated, unless run in offline mode
174 * @throws T a {@link Throwable}
175 */
176 private void loadFromDisk() throws T {
177 try (BufferedInputStream input = new BufferedInputStream(Files.newInputStream(path.toPath()))) {
178 this.data = new byte[input.available()];
179 if (input.read(this.data) < this.data.length) {
180 Logging.error("Failed to read expected contents from "+path);
181 }
182 } catch (IOException | InvalidPathException e) {
183 Logging.trace(e);
184 if (!isOffline()) {
185 this.data = updateForce();
186 }
187 }
188 }
189
190 /**
191 * Stores the data to disk
192 */
193 private void saveToDisk() {
194 try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(path.toPath()))) {
195 output.write(this.data);
196 output.flush();
197 } catch (IOException | InvalidPathException | SecurityException e) {
198 Logging.log(Logging.LEVEL_ERROR, "Unable to save data", e);
199 }
200 }
201
202 /**
203 * Flushes the data from memory. Class automatically reloads it from disk or updateData() if required
204 */
205 public void flushData() {
206 data = null;
207 }
208}
Note: See TracBrowser for help on using the repository browser.