mirror of
https://github.com/arduino/Arduino.git
synced 2025-01-31 20:52:13 +01:00
Add cache.json file and improve stability
This commit is contained in:
parent
00818af181
commit
a7d395f45e
236
arduino-core/src/cc/arduino/utils/network/CacheControl.java
Normal file
236
arduino-core/src/cc/arduino/utils/network/CacheControl.java
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package cc.arduino.utils.network;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a HTTP Cache-Control response header and parses it from string.
|
||||||
|
*
|
||||||
|
* <p>Note: This class ignores <tt>1#field-name</tt> parameter for
|
||||||
|
* <tt>private</tt> and <tt>no-cache</tt> directive and cache extensions.</p>
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">HTTP/1.1 section 14.9</a>
|
||||||
|
*/
|
||||||
|
public class CacheControl {
|
||||||
|
|
||||||
|
|
||||||
|
// copied from org.apache.abdera.protocol.util.CacheControlUtil
|
||||||
|
private static final Pattern PATTERN
|
||||||
|
= Pattern.compile("\\s*([\\w\\-]+)\\s*(=)?\\s*(\\-?\\d+|\\\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)+\\\")?\\s*");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to the <tt>max-age</tt> cache control directive.
|
||||||
|
* The default value is <tt>-1</tt>, i.e. not specified.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3">HTTP/1.1 section 14.9.3</a>
|
||||||
|
*/
|
||||||
|
private int maxAge = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to the <tt>s-maxage</tt> cache control directive.
|
||||||
|
* The default value is <tt>-1</tt>, i.e. not specified.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3">HTTP/1.1 section 14.9.3</a>
|
||||||
|
*/
|
||||||
|
private int sMaxAge = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>must-revalidate</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4">HTTP/1.1 section 14.9.4</a>
|
||||||
|
*/
|
||||||
|
private boolean isMustRevalidate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>no-cache</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1">HTTP/1.1 section 14.9.1</a>
|
||||||
|
*/
|
||||||
|
private boolean isNoCache = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>no-store</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.2">HTTP/1.1 section 14.9.2</a>
|
||||||
|
*/
|
||||||
|
private boolean isNoStore = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>no-transform</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5">HTTP/1.1 section 14.9.5</a>
|
||||||
|
*/
|
||||||
|
private boolean isNoTransform = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>private</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1">HTTP/1.1 section 14.9.1</a>
|
||||||
|
*/
|
||||||
|
private boolean isPrivate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>public</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1">HTTP/1.1 section 14.9.1</a>
|
||||||
|
*/
|
||||||
|
private boolean isPublic = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the <tt>proxy-revalidate</tt> directive is specified.
|
||||||
|
* The default value is <tt>false</tt>.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4">HTTP/1.1 section 14.9.4</a>
|
||||||
|
*/
|
||||||
|
private boolean isProxyRevalidate = false;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of CacheControl by parsing the supplied string.
|
||||||
|
*
|
||||||
|
* @param value A value the Cache-Control header.
|
||||||
|
*/
|
||||||
|
public static CacheControl valueOf(String value) {
|
||||||
|
CacheControl cc = new CacheControl();
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
Matcher matcher = PATTERN.matcher(value);
|
||||||
|
while (matcher.find()) {
|
||||||
|
switch (matcher.group(1).toLowerCase()) {
|
||||||
|
case "max-age":
|
||||||
|
cc.setMaxAge(Integer.parseInt(matcher.group(3))); break;
|
||||||
|
case "s-maxage":
|
||||||
|
cc.setSMaxAge(Integer.parseInt(matcher.group(3))); break;
|
||||||
|
case "must-revalidate":
|
||||||
|
cc.setMustRevalidate(true); break;
|
||||||
|
case "no-cache":
|
||||||
|
cc.setNoCache(true); break;
|
||||||
|
case "no-store":
|
||||||
|
cc.setNoStore(true); break;
|
||||||
|
case "no-transform":
|
||||||
|
cc.setNoTransform(true); break;
|
||||||
|
case "private":
|
||||||
|
cc.setPrivate(true); break;
|
||||||
|
case "public":
|
||||||
|
cc.setPublic(true); break;
|
||||||
|
case "proxy-revalidate":
|
||||||
|
cc.setProxyRevalidate(true); break;
|
||||||
|
default: //ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <tt>max-age</tt>, or <tt>s-maxage</tt> according to whether
|
||||||
|
* considering a shared cache, or a private cache. If shared cache and the
|
||||||
|
* <tt>s-maxage</tt> is negative (i.e. not set), then returns
|
||||||
|
* <tt>max-age</tt> instead.
|
||||||
|
*
|
||||||
|
* @param sharedCache <tt>true</tt> for a shared cache,
|
||||||
|
* or <tt>false</tt> for a private cache
|
||||||
|
* @return A {@link #maxAge}, or {@link #sMaxAge} according to the given
|
||||||
|
* sharedCache argument.
|
||||||
|
*/
|
||||||
|
public int getMaxAge(boolean sharedCache) {
|
||||||
|
if (sharedCache) {
|
||||||
|
return sMaxAge >= 0 ? sMaxAge : maxAge;
|
||||||
|
} else {
|
||||||
|
return maxAge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxAge(int maxAge) {
|
||||||
|
this.maxAge = maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxAge() {
|
||||||
|
return maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSMaxAge() {
|
||||||
|
return sMaxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSMaxAge(int sMaxAge) {
|
||||||
|
this.sMaxAge = sMaxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMustRevalidate() {
|
||||||
|
return isMustRevalidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMustRevalidate(boolean mustRevalidate) {
|
||||||
|
isMustRevalidate = mustRevalidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoCache() {
|
||||||
|
return isNoCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoCache(boolean noCache) {
|
||||||
|
isNoCache = noCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoStore() {
|
||||||
|
return isNoStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoStore(boolean noStore) {
|
||||||
|
isNoStore = noStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNoTransform() {
|
||||||
|
return isNoTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoTransform(boolean noTransform) {
|
||||||
|
isNoTransform = noTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrivate() {
|
||||||
|
return isPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrivate(boolean aPrivate) {
|
||||||
|
isPrivate = aPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPublic() {
|
||||||
|
return isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublic(boolean aPublic) {
|
||||||
|
isPublic = aPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProxyRevalidate() {
|
||||||
|
return isProxyRevalidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProxyRevalidate(boolean proxyRevalidate) {
|
||||||
|
isProxyRevalidate = proxyRevalidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CacheControl{" +
|
||||||
|
"maxAge=" + maxAge +
|
||||||
|
", sMaxAge=" + sMaxAge +
|
||||||
|
", isMustRevalidate=" + isMustRevalidate +
|
||||||
|
", isNoCache=" + isNoCache +
|
||||||
|
", isNoStore=" + isNoStore +
|
||||||
|
", isNoTransform=" + isNoTransform +
|
||||||
|
", isPrivate=" + isPrivate +
|
||||||
|
", isPublic=" + isPublic +
|
||||||
|
", isProxyRevalidate=" + isProxyRevalidate +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -30,9 +30,8 @@
|
|||||||
package cc.arduino.utils.network;
|
package cc.arduino.utils.network;
|
||||||
|
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
import org.slf4j.Logger;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.apache.logging.log4j.Logger;
|
||||||
import processing.app.BaseNoGui;
|
|
||||||
import processing.app.helpers.FileUtils;
|
import processing.app.helpers.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -48,7 +47,7 @@ import java.util.Observable;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class FileDownloader extends Observable {
|
public class FileDownloader extends Observable {
|
||||||
private static Logger log = LoggerFactory.getLogger(FileDownloader.class);
|
private static Logger log = LogManager.getLogger(FileDownloader.class);
|
||||||
|
|
||||||
public enum Status {
|
public enum Status {
|
||||||
CONNECTING, //
|
CONNECTING, //
|
||||||
@ -146,15 +145,14 @@ public class FileDownloader extends Observable {
|
|||||||
try {
|
try {
|
||||||
setStatus(Status.CONNECTING);
|
setStatus(Status.CONNECTING);
|
||||||
|
|
||||||
final File settingsFolder = BaseNoGui.getPlatform().getSettingsFolder();
|
final Optional<FileDownloaderCache.FileCached> fileCached = FileDownloaderCache.getFileCached(downloadUrl);
|
||||||
final String cacheFolder = Paths.get(settingsFolder.getPath(), "cache").toString();
|
|
||||||
final FileDownloaderCache fileDownloaderCache = FileDownloaderCache.getFileCached(cacheFolder, downloadUrl);
|
|
||||||
|
|
||||||
if (!fileDownloaderCache.isChange()) {
|
if (fileCached.isPresent() && !fileCached.get().isChange()) {
|
||||||
try {
|
try {
|
||||||
final Optional<File> fileFromCache =
|
final Optional<File> fileFromCache =
|
||||||
fileDownloaderCache.getFileFromCache();
|
fileCached.get().getFileFromCache();
|
||||||
if (fileFromCache.isPresent()) {
|
if (fileFromCache.isPresent()) {
|
||||||
|
log.info("No need to download using cached file: {}", fileCached.get());
|
||||||
FileUtils.copyFile(fileFromCache.get(), outputFile);
|
FileUtils.copyFile(fileFromCache.get(), outputFile);
|
||||||
setStatus(Status.COMPLETE);
|
setStatus(Status.COMPLETE);
|
||||||
return;
|
return;
|
||||||
@ -195,7 +193,7 @@ public class FileDownloader extends Observable {
|
|||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
stream = connection.getInputStream();
|
stream = connection.getInputStream();
|
||||||
}
|
}
|
||||||
byte buffer[] = new byte[10240];
|
byte[] buffer = new byte[10240];
|
||||||
while (status == Status.DOWNLOADING) {
|
while (status == Status.DOWNLOADING) {
|
||||||
int read = stream.read(buffer);
|
int read = stream.read(buffer);
|
||||||
if (read == -1)
|
if (read == -1)
|
||||||
@ -216,7 +214,9 @@ public class FileDownloader extends Observable {
|
|||||||
}
|
}
|
||||||
// Set the cache whe it finish to download the file
|
// Set the cache whe it finish to download the file
|
||||||
IOUtils.closeQuietly(randomAccessOutputFile);
|
IOUtils.closeQuietly(randomAccessOutputFile);
|
||||||
fileDownloaderCache.fillCache(outputFile);
|
if (fileCached.isPresent()) {
|
||||||
|
fileCached.get().updateCacheFile(outputFile);
|
||||||
|
}
|
||||||
setStatus(Status.COMPLETE);
|
setStatus(Status.COMPLETE);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
setStatus(Status.CANCELLED);
|
setStatus(Status.CANCELLED);
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
package cc.arduino.utils.network;
|
package cc.arduino.utils.network;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import cc.arduino.utils.FileHash;
|
||||||
import org.slf4j.LoggerFactory;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import processing.app.PreferencesData;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import processing.app.BaseNoGui;
|
||||||
import processing.app.helpers.FileUtils;
|
import processing.app.helpers.FileUtils;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
@ -15,91 +23,289 @@ import java.net.URL;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FileDownloaderCache {
|
public class FileDownloaderCache {
|
||||||
private static Logger log = LoggerFactory.getLogger(FileDownloaderCache.class);
|
private final static Logger log = LogManager
|
||||||
private final Path cacheFilePath;
|
.getLogger(FileDownloaderCache.class);
|
||||||
private final String remoteETag;
|
private static Map<String, FileCached> cachedFiles = Collections
|
||||||
private final String preferencesDataKey;
|
.synchronizedMap(new HashMap<>());
|
||||||
|
private static String cacheFolder;
|
||||||
|
|
||||||
// BaseNoGui.getSettingsFolder()
|
static {
|
||||||
private FileDownloaderCache(Path cacheFilePath, String remoteETag, String preferencesDataKey) {
|
final File settingsFolder;
|
||||||
this.cacheFilePath = cacheFilePath;
|
try {
|
||||||
this.remoteETag = remoteETag;
|
settingsFolder = BaseNoGui.getPlatform().getSettingsFolder();
|
||||||
this.preferencesDataKey = preferencesDataKey;
|
cacheFolder = Paths.get(settingsFolder.getPath(), "cache")
|
||||||
|
.toString();
|
||||||
|
final Path pathCacheInfo = getCachedInfoPath();
|
||||||
|
if (Files.exists(pathCacheInfo)) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
final JsonNode jsonNode = mapper.readTree(pathCacheInfo.toFile());
|
||||||
|
|
||||||
|
// Read the files array
|
||||||
|
TypeReference<List<FileCached>> typeRef = new TypeReference<List<FileCached>>() {
|
||||||
|
};
|
||||||
|
final List<FileCached> files = mapper
|
||||||
|
.readValue(mapper.treeAsTokens(jsonNode.get("files")), typeRef);
|
||||||
|
|
||||||
|
// Create a map with the remote url as a key and the file cache info as a value
|
||||||
|
cachedFiles = Collections
|
||||||
|
.synchronizedMap(files.stream().collect(
|
||||||
|
Collectors.toMap(FileCached::getRemoteURL, Function.identity())));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Cannot initialized the cache", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileDownloaderCache getFileCached(String cacheFolder, URL remoteURL)
|
public static Optional<FileCached> getFileCached(URL remoteURL)
|
||||||
throws IOException, NoSuchMethodException, ScriptException, URISyntaxException {
|
throws URISyntaxException, NoSuchMethodException, ScriptException,
|
||||||
|
IOException {
|
||||||
|
|
||||||
final String[] splitPath = remoteURL.getPath().split("/");
|
final Optional<FileCached> fileCachedOpt;
|
||||||
final String preferencesDataKey = "cache.file." + remoteURL.getPath();
|
|
||||||
final Path cacheFilePath;
|
// The file info must exist in the cachedFiles map but also the real file must exist in the file system
|
||||||
if (splitPath.length > 0) {
|
if (cachedFiles.containsKey(remoteURL.toString()) && cachedFiles.get(remoteURL.toString()).exists()) {
|
||||||
cacheFilePath = Paths.get(cacheFolder, splitPath);
|
fileCachedOpt = Optional.of(cachedFiles.get(remoteURL.toString()));
|
||||||
} else {
|
} else {
|
||||||
cacheFilePath = null;
|
// Update
|
||||||
}
|
fileCachedOpt = FileDownloaderCache.updateCacheInfo(remoteURL, (remoteETagClean, cacheControl) -> {
|
||||||
|
// Check cache control data
|
||||||
final HttpURLConnection headRequest = new HttpConnectionManager(remoteURL)
|
if (cacheControl.isNoCache() || cacheControl.isMustRevalidate()) {
|
||||||
.makeConnection((connection) -> {
|
log.warn("The file must not be cache due to cache control header {}",
|
||||||
try {
|
cacheControl);
|
||||||
connection.setRequestMethod("HEAD");
|
return Optional.empty();
|
||||||
} catch (ProtocolException e) {
|
|
||||||
log.error("Invalid protocol", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String[] splitPath = remoteURL.getPath().split("/");
|
||||||
|
final Path cacheFilePath;
|
||||||
|
if (splitPath.length > 0) {
|
||||||
|
Deque<String> addFirstRemoteURL = new LinkedList<>(Arrays.asList(splitPath));
|
||||||
|
addFirstRemoteURL.addFirst(remoteURL.getHost());
|
||||||
|
cacheFilePath = Paths.get(cacheFolder, addFirstRemoteURL.toArray(new String[0]));
|
||||||
|
return Optional.of(
|
||||||
|
new FileCached(remoteETagClean, remoteURL.toString(), cacheFilePath.toString(),
|
||||||
|
cacheControl));
|
||||||
|
}
|
||||||
|
log.warn("The remote path as no file name {}", remoteURL);
|
||||||
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return fileCachedOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Optional<FileCached> updateCacheInfo(URL remoteURL, BiFunction<String, CacheControl, Optional<FileCached>> getNewFile)
|
||||||
|
throws URISyntaxException, NoSuchMethodException, ScriptException,
|
||||||
|
IOException {
|
||||||
|
// Update the headers of the cached file
|
||||||
|
final HttpURLConnection headRequest = new HttpConnectionManager(
|
||||||
|
remoteURL).makeConnection((connection) -> {
|
||||||
|
try {
|
||||||
|
connection.setRequestMethod("HEAD");
|
||||||
|
} catch (ProtocolException e) {
|
||||||
|
log.error("Invalid protocol", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
final int responseCode = headRequest.getResponseCode();
|
final int responseCode = headRequest.getResponseCode();
|
||||||
headRequest.disconnect();
|
headRequest.disconnect();
|
||||||
// Something bad is happening return a conservative true to try to download the file
|
// Something bad is happening return a conservative true to try to download the file
|
||||||
if (responseCode < 200 || responseCode >= 300) {
|
if (responseCode < 200 || responseCode >= 300) {
|
||||||
log.warn("The head request return a bad response code " + responseCode);
|
log.warn("The head request return a bad response code " + responseCode);
|
||||||
// if something bad happend
|
// if something bad happend
|
||||||
return new FileDownloaderCache(cacheFilePath, null, preferencesDataKey);
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
// Get all the useful headers
|
||||||
final String remoteETag = headRequest.getHeaderField("ETag");
|
String remoteETag = headRequest.getHeaderField("ETag");
|
||||||
String remoteETagClean = null;
|
String cacheControlHeader = headRequest.getHeaderField("Cache-Control");
|
||||||
if (remoteETag != null) {
|
if (remoteETag != null && cacheControlHeader != null) {
|
||||||
remoteETagClean = remoteETag.trim().replace("\"", "");
|
final String remoteETagClean = remoteETag.trim().replace("\"", "");
|
||||||
|
final CacheControl cacheControl = CacheControl.valueOf(cacheControlHeader);
|
||||||
|
return getNewFile.apply(remoteETagClean, cacheControl);
|
||||||
}
|
}
|
||||||
|
log.warn("The head request do not return the ETag {} or the Cache-Control {}", remoteETag, cacheControlHeader);
|
||||||
return new FileDownloaderCache(cacheFilePath, remoteETagClean, preferencesDataKey);
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChange() {
|
private static void updateCacheFilesInfo() throws IOException {
|
||||||
|
if (cachedFiles != null) {
|
||||||
|
synchronized (cachedFiles) {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
final ObjectNode objectNode = mapper.createObjectNode();
|
||||||
|
objectNode.putArray("files").addAll(
|
||||||
|
cachedFiles.values().stream()
|
||||||
|
.map((v) -> mapper.convertValue(v, JsonNode.class))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
// Create the path Arduino15/cache
|
||||||
|
Path cachedFileInfo = getCachedInfoPath();
|
||||||
|
if (Files.notExists(cachedFileInfo)) {
|
||||||
|
Files.createDirectories(cachedFileInfo.getParent());
|
||||||
|
}
|
||||||
|
mapper.writeValue(cachedFileInfo.toFile(), objectNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String localETag = PreferencesData.get(preferencesDataKey);
|
private static Path getCachedInfoPath() {
|
||||||
|
return Paths.get(cacheFolder, "cache.json");
|
||||||
|
}
|
||||||
|
|
||||||
// If the header doesn't exist or the local cache doesn't exist you need to download the file
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
if (cacheFilePath == null || remoteETag == null || localETag == null) {
|
static class FileCached {
|
||||||
|
private String eTag;
|
||||||
|
@JsonIgnore
|
||||||
|
private final String lastETag;
|
||||||
|
private final String remoteURL;
|
||||||
|
private final String localPath;
|
||||||
|
private String md5;
|
||||||
|
private String createdAt;
|
||||||
|
private final CacheControl cacheControl;
|
||||||
|
|
||||||
|
FileCached() {
|
||||||
|
this.lastETag = null;
|
||||||
|
this.remoteURL = null;
|
||||||
|
this.localPath = null;
|
||||||
|
this.cacheControl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileCached(String lastETag, String remoteURL, String localPath, CacheControl cacheControl) {
|
||||||
|
this.lastETag = lastETag;
|
||||||
|
this.remoteURL = remoteURL;
|
||||||
|
this.localPath = localPath;
|
||||||
|
this.cacheControl = cacheControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isChange() {
|
||||||
|
// Check if the file is expire
|
||||||
|
if (this.getExpiresTime().isAfter(LocalDateTime.now())) {
|
||||||
|
log.info("The file \"{}\" is no expire, the etag will not be checked. Expire time: {}", localPath, this.getExpiresTime().toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastETag != null) {
|
||||||
|
// If are different means that the file is change
|
||||||
|
return !lastETag.equals(eTag);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If are different means that the file is change
|
@JsonIgnore
|
||||||
return !remoteETag.equals(localETag);
|
public boolean exists() throws IOException {
|
||||||
}
|
if (localPath != null && Files.exists(Paths.get(localPath))) {
|
||||||
|
try {
|
||||||
public Optional<File> getFileFromCache() {
|
final String md5Local = FileHash.hash(Paths.get(localPath).toFile(), "MD5");
|
||||||
if (Optional.ofNullable(cacheFilePath).isPresent() && Files.exists(cacheFilePath)) {
|
if (md5Local.equals(md5)) {
|
||||||
return Optional.of(new File(cacheFilePath.toUri()));
|
return true;
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.error("MD5 algorithm is not supported", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fillCache(File fileToCache) throws Exception {
|
|
||||||
if (Optional.ofNullable(remoteETag).isPresent() &&
|
|
||||||
Optional.ofNullable(cacheFilePath).isPresent()) {
|
|
||||||
|
|
||||||
PreferencesData.set(preferencesDataKey, remoteETag);
|
|
||||||
// If the cache directory does not exist create it
|
|
||||||
if (!Files.exists(cacheFilePath.getParent())) {
|
|
||||||
Files.createDirectories(cacheFilePath.getParent());
|
|
||||||
}
|
}
|
||||||
FileUtils.copyFile(fileToCache, cacheFilePath.toFile());
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Optional<File> getFileFromCache() {
|
||||||
|
if (localPath != null && Files.exists(Paths.get(localPath))) {
|
||||||
|
return Optional.of(new File(localPath));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCacheFile(File fileToCache) throws Exception {
|
||||||
|
if (Optional.ofNullable(lastETag).isPresent() && Optional
|
||||||
|
.ofNullable(localPath).isPresent()) {
|
||||||
|
Path cacheFilePath = Paths.get(localPath);
|
||||||
|
|
||||||
|
// If the cache directory does not exist create it
|
||||||
|
if (!Files.exists(cacheFilePath.getParent())) {
|
||||||
|
Files.createDirectories(cacheFilePath.getParent());
|
||||||
|
}
|
||||||
|
final File cacheFile = cacheFilePath.toFile();
|
||||||
|
FileUtils.copyFile(fileToCache, cacheFile);
|
||||||
|
eTag = lastETag;
|
||||||
|
createdAt = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
|
||||||
|
updateMD5();
|
||||||
|
}
|
||||||
|
log.info("Update cache file: {}", this);
|
||||||
|
cachedFiles.put(remoteURL, this);
|
||||||
|
updateCacheFilesInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMD5() throws IOException, NoSuchAlgorithmException {
|
||||||
|
if (localPath != null) {
|
||||||
|
md5 = FileHash.hash(Paths.get(localPath).toFile(), "MD5");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public LocalDateTime getExpiresTime() {
|
||||||
|
final int maxAge;
|
||||||
|
if (cacheControl != null) {
|
||||||
|
maxAge = cacheControl.getMaxAge();
|
||||||
|
} else {
|
||||||
|
maxAge = 0;
|
||||||
|
}
|
||||||
|
if (createdAt != null) {
|
||||||
|
return LocalDateTime.parse(createdAt, DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
.plusSeconds(maxAge);
|
||||||
|
}
|
||||||
|
return LocalDateTime.now();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpires() {
|
||||||
|
return getExpiresTime().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMD5() {
|
||||||
|
return md5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String geteTag() {
|
||||||
|
return eTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemoteURL() {
|
||||||
|
return remoteURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalPath() {
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMd5(String md5) {
|
||||||
|
this.md5 = md5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheControl getCacheControl() {
|
||||||
|
return cacheControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FileCached{" +
|
||||||
|
"eTag='" + eTag + '\'' +
|
||||||
|
", lastETag='" + lastETag + '\'' +
|
||||||
|
", remoteURL='" + remoteURL + '\'' +
|
||||||
|
", localPath='" + localPath + '\'' +
|
||||||
|
", md5='" + md5 + '\'' +
|
||||||
|
", createdAt='" + createdAt + '\'' +
|
||||||
|
", cacheControl=" + cacheControl +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user