mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Replace 'ByteArrayFileCache' by OHC class (#8774)
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
parent
6398773007
commit
e6986acb09
@ -26,7 +26,7 @@ import java.util.Map;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.chromecast.internal.handler.ChromecastHandler;
|
||||
import org.openhab.binding.chromecast.internal.utils.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ByteArrayFileCache;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
@ -63,6 +63,7 @@ import su.litvak.chromecast.api.v2.Volume;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChromecastStatusUpdater {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ChromecastStatusUpdater.class);
|
||||
|
||||
private final Thing thing;
|
||||
|
@ -1,305 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.chromecast.internal.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a simple file based cache implementation.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ByteArrayFileCache {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
|
||||
static final String CACHE_FOLDER_NAME = "cache";
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
private static final char UNIX_SEPARATOR = '/';
|
||||
private static final char WINDOWS_SEPARATOR = '\\';
|
||||
|
||||
private final File cacheFolder;
|
||||
|
||||
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
|
||||
private int expiry = 0;
|
||||
|
||||
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID) {
|
||||
// TODO track and limit folder size
|
||||
// TODO support user specific folder
|
||||
cacheFolder = new File(new File(OpenHAB.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
|
||||
if (!cacheFolder.exists()) {
|
||||
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
cacheFolder.mkdirs();
|
||||
}
|
||||
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID/</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
|
||||
* disables this functionality.
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID, int expiry) {
|
||||
this(servicePID);
|
||||
if (expiry < 0) {
|
||||
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
|
||||
}
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||
* new content.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void put(String key, byte[] content) {
|
||||
writeFile(getUniqueFile(key), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void putIfAbsent(String key, byte[] content) {
|
||||
File fileInCache = getUniqueFile(key);
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
} else {
|
||||
writeFile(fileInCache, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache and returns the content of the file.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] putIfAbsentAndGet(String key, byte[] content) {
|
||||
putIfAbsent(key, content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given content to the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @param content the content to be written
|
||||
*/
|
||||
private void writeFile(File fileInCache, byte[] content) {
|
||||
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||
try {
|
||||
Files.write(fileInCache.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the key is present in the cache.
|
||||
*
|
||||
* @param key the key whose presence in the cache is to be tested
|
||||
* @return true if the cache contains a file for the specified key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getUniqueFile(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file associated with the given key from the cache.
|
||||
*
|
||||
* @param key the key whose associated file is to be removed
|
||||
*/
|
||||
public void remove(String key) {
|
||||
deleteFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
*/
|
||||
private void deleteFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||
fileInCache.delete();
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all files from the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting all files from cache");
|
||||
Arrays.stream(filesInCache).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired files from the cache.
|
||||
*/
|
||||
public void clearExpired() {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return;
|
||||
}
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting expired files from cache");
|
||||
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link File} is expired.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
|
||||
*/
|
||||
private boolean isExpired(File fileInCache) {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return false;
|
||||
}
|
||||
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the file associated with the given key, if it is present.
|
||||
*
|
||||
* @param key the key whose associated file is to be returned
|
||||
* @return the content of the file associated with the given key
|
||||
* @throws FileNotFoundException if the given file could not be found in cache
|
||||
* @throws IOException if an I/O error occurs reading the given file
|
||||
*/
|
||||
public byte[] get(String key) throws FileNotFoundException, IOException {
|
||||
return readFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content from the given {@link File}, if it is present.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return the content of the file
|
||||
* @throws FileNotFoundException if the given file could not be found in cache
|
||||
* @throws IOException if an I/O error occurs reading the given file
|
||||
*/
|
||||
private byte[] readFile(File fileInCache) throws FileNotFoundException, IOException {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
try {
|
||||
return Files.readAllBytes(fileInCache.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||
throw new IOException(String.format("Could not read file '%s' from cache", fileInCache.getName()));
|
||||
}
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
throw new FileNotFoundException(String.format("File '%s' not found in cache", fileInCache.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file for the file associated with the given key
|
||||
*/
|
||||
File getUniqueFile(String key) {
|
||||
String uniqueFileName = getUniqueFileName(key);
|
||||
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
|
||||
return FILES_IN_CACHE.get(uniqueFileName);
|
||||
} else {
|
||||
String fileExtension = getFileExtension(key);
|
||||
File fileInCache = new File(cacheFolder,
|
||||
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
|
||||
return fileInCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name.
|
||||
*
|
||||
* @param fileName the file name to retrieve the extension of
|
||||
* @return the extension of the file or null if none exists
|
||||
*/
|
||||
@Nullable
|
||||
String getFileExtension(String fileName) {
|
||||
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
|
||||
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1).replaceFirst("\\?.*$", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file name from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file name for the file associated with the given key
|
||||
*/
|
||||
String getUniqueFileName(String key) {
|
||||
try {
|
||||
final MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
return String.format("%032x", new BigInteger(1, md.digest(key.getBytes(StandardCharsets.UTF_8))));
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// should not happen
|
||||
logger.error("Could not create MD5 hash for key '{}'", key, ex);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import static java.util.stream.Collectors.joining;
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpStatus.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -34,7 +35,7 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.darksky.internal.config.DarkSkyAPIConfiguration;
|
||||
import org.openhab.binding.darksky.internal.handler.DarkSkyAPIHandler;
|
||||
import org.openhab.binding.darksky.internal.model.DarkSkyJsonWeatherData;
|
||||
import org.openhab.binding.darksky.internal.utils.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
@ -126,7 +127,12 @@ public class DarkSkyConnection {
|
||||
|
||||
private static @Nullable RawType downloadWeatherIconFromCache(String url) {
|
||||
if (IMAGE_CACHE.containsKey(url)) {
|
||||
return new RawType(IMAGE_CACHE.get(url), PNG_CONTENT_TYPE);
|
||||
try {
|
||||
return new RawType(IMAGE_CACHE.get(url), PNG_CONTENT_TYPE);
|
||||
} catch (IOException e) {
|
||||
LoggerFactory.getLogger(DarkSkyConnection.class).trace("Failed to download the content of URL '{}'",
|
||||
url, e);
|
||||
}
|
||||
} else {
|
||||
RawType image = downloadWeatherIcon(url);
|
||||
if (image != null) {
|
||||
|
@ -1,307 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.darksky.internal.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a simple file based cache implementation.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ByteArrayFileCache {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
|
||||
static final String CACHE_FOLDER_NAME = "cache";
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
private static final char UNIX_SEPARATOR = '/';
|
||||
private static final char WINDOWS_SEPARATOR = '\\';
|
||||
|
||||
private final File cacheFolder;
|
||||
|
||||
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
|
||||
private int expiry = 0;
|
||||
|
||||
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID) {
|
||||
// TODO track and limit folder size
|
||||
// TODO support user specific folder
|
||||
cacheFolder = new File(new File(OpenHAB.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
|
||||
if (!cacheFolder.exists()) {
|
||||
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
cacheFolder.mkdirs();
|
||||
}
|
||||
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID/</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
|
||||
* disables this functionality.
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID, int expiry) {
|
||||
this(servicePID);
|
||||
if (expiry < 0) {
|
||||
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
|
||||
}
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||
* new content.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void put(String key, byte[] content) {
|
||||
writeFile(getUniqueFile(key), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void putIfAbsent(String key, byte[] content) {
|
||||
File fileInCache = getUniqueFile(key);
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
} else {
|
||||
writeFile(fileInCache, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache and returns the content of the file.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] putIfAbsentAndGet(String key, byte[] content) {
|
||||
putIfAbsent(key, content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given content to the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @param content the content to be written
|
||||
*/
|
||||
private void writeFile(File fileInCache, byte[] content) {
|
||||
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||
try {
|
||||
Files.write(fileInCache.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the key is present in the cache.
|
||||
*
|
||||
* @param key the key whose presence in the cache is to be tested
|
||||
* @return true if the cache contains a file for the specified key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getUniqueFile(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file associated with the given key from the cache.
|
||||
*
|
||||
* @param key the key whose associated file is to be removed
|
||||
*/
|
||||
public void remove(String key) {
|
||||
deleteFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
*/
|
||||
private void deleteFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||
fileInCache.delete();
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all files from the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting all files from cache");
|
||||
Arrays.stream(filesInCache).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired files from the cache.
|
||||
*/
|
||||
public void clearExpired() {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return;
|
||||
}
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting expired files from cache");
|
||||
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link File} is expired.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
|
||||
*/
|
||||
private boolean isExpired(File fileInCache) {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return false;
|
||||
}
|
||||
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the file associated with the given key, if it is present.
|
||||
*
|
||||
* @param key the key whose associated file is to be returned
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] get(String key) {
|
||||
return readFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content from the given {@link File}, if it is present.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return the content of the file
|
||||
*/
|
||||
private byte[] readFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
try {
|
||||
return Files.readAllBytes(fileInCache.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file for the file associated with the given key
|
||||
*/
|
||||
File getUniqueFile(String key) {
|
||||
String uniqueFileName = getUniqueFileName(key);
|
||||
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
|
||||
return FILES_IN_CACHE.get(uniqueFileName);
|
||||
} else {
|
||||
String fileExtension = getFileExtension(key);
|
||||
File fileInCache = new File(cacheFolder,
|
||||
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
|
||||
return fileInCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name.
|
||||
*
|
||||
* @param fileName the file name to retrieve the extension of
|
||||
* @return the extension of the file or null if none exists
|
||||
*/
|
||||
@Nullable
|
||||
String getFileExtension(String fileName) {
|
||||
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
|
||||
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file name from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file name for the file associated with the given key
|
||||
*/
|
||||
String getUniqueFileName(String key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
byte[] bytesOfKey = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] md5Hash = md.digest(bytesOfKey);
|
||||
BigInteger bigInt = new BigInteger(1, md5Hash);
|
||||
String fileNameHash = bigInt.toString(16);
|
||||
// Now we need to zero pad it if you actually want the full 32 chars
|
||||
while (fileNameHash.length() < 32) {
|
||||
fileNameHash = "0" + fileNameHash;
|
||||
}
|
||||
return fileNameHash;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// should not happen
|
||||
logger.error("Could not create MD5 hash for key '{}'", key, ex);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.darksky.internal.utils;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.OpenHAB;
|
||||
|
||||
/**
|
||||
* Test class for the {@link ByteArrayFileCache} class.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class ByteArrayFileCacheTest {
|
||||
|
||||
private static final String SERVICE_PID = "org.openhab.binding.darksky";
|
||||
|
||||
private static final File USERDATA_FOLDER = new File(OpenHAB.getUserDataFolder());
|
||||
private static final File CACHE_FOLDER = new File(USERDATA_FOLDER, ByteArrayFileCache.CACHE_FOLDER_NAME);
|
||||
private static final File SERVICE_CACHE_FOLDER = new File(CACHE_FOLDER, SERVICE_PID);
|
||||
|
||||
private static final String MP3_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.mp3";
|
||||
private static final String TXT_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.txt";
|
||||
|
||||
private static final byte[] EMPTY_BUFFER = new byte[0];
|
||||
|
||||
private ByteArrayFileCache subject;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
// delete all files
|
||||
subject.clear();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanUp() {
|
||||
// delete all folders
|
||||
SERVICE_CACHE_FOLDER.delete();
|
||||
CACHE_FOLDER.delete();
|
||||
USERDATA_FOLDER.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileExtension() {
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("/var/log/foo.bar/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension("/tmp/doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(MP3_FILE_NAME), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(TXT_FILE_NAME), is(equalTo("txt")));
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/.."), is(""));
|
||||
assertThat(subject.getFileExtension(".hidden"), is(equalTo("hidden")));
|
||||
assertThat(subject.getFileExtension("C:\\Program Files (x86)\\java\\bin\\javaw.exe"), is(equalTo("exe")));
|
||||
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=qYrpPrLY868"), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUniqueFileName() {
|
||||
String mp3UniqueFileName = subject.getUniqueFileName(MP3_FILE_NAME);
|
||||
assertThat(mp3UniqueFileName, is(equalTo(subject.getUniqueFileName(MP3_FILE_NAME))));
|
||||
|
||||
String txtUniqueFileName = subject.getUniqueFileName(TXT_FILE_NAME);
|
||||
assertThat(txtUniqueFileName, is(equalTo(subject.getUniqueFileName(TXT_FILE_NAME))));
|
||||
|
||||
assertThat(mp3UniqueFileName, is(not(equalTo(txtUniqueFileName))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsent() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.putIfAbsent(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsentAndGet() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
|
||||
assertThat(subject.putIfAbsentAndGet(MP3_FILE_NAME, buffer), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsKey() throws IOException {
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(false));
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.remove(MP3_FILE_NAME);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.clear();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpiredClearsNothing() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpired() throws IOException {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID, 1);
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
// manipulate time of last use
|
||||
File fileInCache = subject.getUniqueFile(MP3_FILE_NAME);
|
||||
fileInCache.setLastModified(System.currentTimeMillis() - 2 * ByteArrayFileCache.ONE_DAY_IN_MILLIS);
|
||||
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
private byte[] readFile() throws IOException {
|
||||
byte[] buffer;
|
||||
try (InputStream is = ByteArrayFileCacheTest.class.getResourceAsStream("/sounds/doorbell.mp3")) {
|
||||
buffer = new byte[is.available()];
|
||||
is.read(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
Binary file not shown.
@ -40,7 +40,7 @@ import org.openhab.binding.kodi.internal.model.KodiSubtitle;
|
||||
import org.openhab.binding.kodi.internal.model.KodiSystemProperties;
|
||||
import org.openhab.binding.kodi.internal.model.KodiUniqueID;
|
||||
import org.openhab.binding.kodi.internal.model.KodiVideoStream;
|
||||
import org.openhab.binding.kodi.internal.utils.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
@ -891,10 +891,14 @@ public class KodiConnection implements KodiClientSocketEventListener {
|
||||
|
||||
private @Nullable RawType downloadImageFromCache(String url) {
|
||||
if (IMAGE_CACHE.containsKey(url)) {
|
||||
byte[] bytes = IMAGE_CACHE.get(url);
|
||||
String contentType = HttpUtil.guessContentTypeFromData(bytes);
|
||||
return new RawType(bytes,
|
||||
contentType == null || contentType.isEmpty() ? RawType.DEFAULT_MIME_TYPE : contentType);
|
||||
try {
|
||||
byte[] bytes = IMAGE_CACHE.get(url);
|
||||
String contentType = HttpUtil.guessContentTypeFromData(bytes);
|
||||
return new RawType(bytes,
|
||||
contentType == null || contentType.isEmpty() ? RawType.DEFAULT_MIME_TYPE : contentType);
|
||||
} catch (IOException e) {
|
||||
logger.trace("Failed to download the content of URL '{}'", url, e);
|
||||
}
|
||||
} else {
|
||||
RawType image = downloadImage(url);
|
||||
if (image != null) {
|
||||
|
@ -1,307 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kodi.internal.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a simple file based cache implementation.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ByteArrayFileCache {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||
|
||||
private static final String MD5_ALGORITHM = "MD5";
|
||||
|
||||
static final String CACHE_FOLDER_NAME = "cache";
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
private static final char UNIX_SEPARATOR = '/';
|
||||
private static final char WINDOWS_SEPARATOR = '\\';
|
||||
|
||||
private final File cacheFolder;
|
||||
|
||||
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
|
||||
private int expiry = 0;
|
||||
|
||||
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID) {
|
||||
// TODO track and limit folder size
|
||||
// TODO support user specific folder
|
||||
cacheFolder = new File(new File(OpenHAB.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
|
||||
if (!cacheFolder.exists()) {
|
||||
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
cacheFolder.mkdirs();
|
||||
}
|
||||
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
|
||||
* <code>$userdata/cache/$servicePID/</code>.
|
||||
*
|
||||
* @param servicePID PID of the service
|
||||
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
|
||||
* disables this functionality.
|
||||
*/
|
||||
public ByteArrayFileCache(String servicePID, int expiry) {
|
||||
this(servicePID);
|
||||
if (expiry < 0) {
|
||||
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
|
||||
}
|
||||
this.expiry = expiry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||
* new content.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void put(String key, byte[] content) {
|
||||
writeFile(getUniqueFile(key), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void putIfAbsent(String key, byte[] content) {
|
||||
File fileInCache = getUniqueFile(key);
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
} else {
|
||||
writeFile(fileInCache, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache and returns the content of the file.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] putIfAbsentAndGet(String key, byte[] content) {
|
||||
putIfAbsent(key, content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given content to the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @param content the content to be written
|
||||
*/
|
||||
private void writeFile(File fileInCache, byte[] content) {
|
||||
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||
try {
|
||||
Files.write(fileInCache.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the key is present in the cache.
|
||||
*
|
||||
* @param key the key whose presence in the cache is to be tested
|
||||
* @return true if the cache contains a file for the specified key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getUniqueFile(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file associated with the given key from the cache.
|
||||
*
|
||||
* @param key the key whose associated file is to be removed
|
||||
*/
|
||||
public void remove(String key) {
|
||||
deleteFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
*/
|
||||
private void deleteFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||
fileInCache.delete();
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all files from the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting all files from cache");
|
||||
Arrays.stream(filesInCache).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired files from the cache.
|
||||
*/
|
||||
public void clearExpired() {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return;
|
||||
}
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting expired files from cache");
|
||||
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given {@link File} is expired.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
|
||||
*/
|
||||
private boolean isExpired(File fileInCache) {
|
||||
// exit if expiry is set to 0 (disabled)
|
||||
if (expiry <= 0) {
|
||||
return false;
|
||||
}
|
||||
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the file associated with the given key, if it is present.
|
||||
*
|
||||
* @param key the key whose associated file is to be returned
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] get(String key) {
|
||||
return readFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content from the given {@link File}, if it is present.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return the content of the file
|
||||
*/
|
||||
private byte[] readFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||
// update time of last use
|
||||
fileInCache.setLastModified(System.currentTimeMillis());
|
||||
try {
|
||||
return Files.readAllBytes(fileInCache.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file for the file associated with the given key
|
||||
*/
|
||||
File getUniqueFile(String key) {
|
||||
String uniqueFileName = getUniqueFileName(key);
|
||||
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
|
||||
return FILES_IN_CACHE.get(uniqueFileName);
|
||||
} else {
|
||||
String fileExtension = getFileExtension(key);
|
||||
File fileInCache = new File(cacheFolder,
|
||||
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
|
||||
return fileInCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name.
|
||||
*
|
||||
* @param fileName the file name to retrieve the extension of
|
||||
* @return the extension of the file or null if none exists
|
||||
*/
|
||||
@Nullable
|
||||
String getFileExtension(String fileName) {
|
||||
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
|
||||
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1).replaceFirst("\\?.*$", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file name from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file name for the file associated with the given key
|
||||
*/
|
||||
String getUniqueFileName(String key) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
|
||||
byte[] bytesOfKey = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] md5Hash = md.digest(bytesOfKey);
|
||||
BigInteger bigInt = new BigInteger(1, md5Hash);
|
||||
String fileNameHash = bigInt.toString(16);
|
||||
// We need to zero pad it if you actually want the full 32 chars
|
||||
while (fileNameHash.length() < 32) {
|
||||
fileNameHash = "0" + fileNameHash;
|
||||
}
|
||||
return fileNameHash;
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// should not happen
|
||||
logger.error("Could not create MD5 hash for key '{}'", key, ex);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kodi.internal.utils;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.OpenHAB;
|
||||
|
||||
/**
|
||||
* Test class for the {@link ByteArrayFileCache} class.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
public class ByteArrayFileCacheTest {
|
||||
|
||||
private static final String SERVICE_PID = "org.openhab.binding.kodi";
|
||||
|
||||
private static final File USERDATA_FOLDER = new File(OpenHAB.getUserDataFolder());
|
||||
private static final File CACHE_FOLDER = new File(USERDATA_FOLDER, ByteArrayFileCache.CACHE_FOLDER_NAME);
|
||||
private static final File SERVICE_CACHE_FOLDER = new File(CACHE_FOLDER, SERVICE_PID);
|
||||
|
||||
private static final String MP3_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.mp3";
|
||||
private static final String TXT_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.txt";
|
||||
|
||||
private static final byte[] EMPTY_BUFFER = new byte[0];
|
||||
|
||||
private ByteArrayFileCache subject;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
// delete all files
|
||||
subject.clear();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanUp() {
|
||||
// delete all folders
|
||||
SERVICE_CACHE_FOLDER.delete();
|
||||
CACHE_FOLDER.delete();
|
||||
USERDATA_FOLDER.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileExtension() {
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("/var/log/foo.bar/"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension("doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension("/tmp/doorbell.mp3"), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(MP3_FILE_NAME), is(equalTo("mp3")));
|
||||
assertThat(subject.getFileExtension(TXT_FILE_NAME), is(equalTo("txt")));
|
||||
assertThat(subject.getFileExtension("/var/log/openhab2/.."), is(""));
|
||||
assertThat(subject.getFileExtension(".hidden"), is(equalTo("hidden")));
|
||||
assertThat(subject.getFileExtension("C:\\Program Files (x86)\\java\\bin\\javaw.exe"), is(equalTo("exe")));
|
||||
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=qYrpPrLY868"), is(nullValue()));
|
||||
assertThat(subject.getFileExtension(
|
||||
"a52fc16cca77ab2bf6abe51e463ce501.jpg?response-content-type=image%2Fjpeg&test=1"), is("jpg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUniqueFileNameIsUnique() {
|
||||
String mp3UniqueFileName = subject.getUniqueFileName(MP3_FILE_NAME);
|
||||
assertThat(mp3UniqueFileName, is(equalTo(subject.getUniqueFileName(MP3_FILE_NAME))));
|
||||
|
||||
String txtUniqueFileName = subject.getUniqueFileName(TXT_FILE_NAME);
|
||||
assertThat(txtUniqueFileName, is(equalTo(subject.getUniqueFileName(TXT_FILE_NAME))));
|
||||
|
||||
assertThat(mp3UniqueFileName, is(not(equalTo(txtUniqueFileName))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsent() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.putIfAbsent(MP3_FILE_NAME, buffer);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsentAndGet() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
|
||||
assertThat(subject.putIfAbsentAndGet(MP3_FILE_NAME, buffer), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsKey() throws IOException {
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(false));
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
assertThat(subject.containsKey(MP3_FILE_NAME), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.remove(MP3_FILE_NAME);
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() throws IOException {
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
subject.clear();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpiredClearsNothing() throws IOException {
|
||||
byte[] buffer = readFile();
|
||||
subject.put(MP3_FILE_NAME, buffer);
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearExpired() throws IOException {
|
||||
subject = new ByteArrayFileCache(SERVICE_PID, 1);
|
||||
|
||||
subject.put(MP3_FILE_NAME, readFile());
|
||||
|
||||
// manipulate time of last use
|
||||
File fileInCache = subject.getUniqueFile(MP3_FILE_NAME);
|
||||
fileInCache.setLastModified(System.currentTimeMillis() - 2 * ByteArrayFileCache.ONE_DAY_IN_MILLIS);
|
||||
|
||||
subject.clearExpired();
|
||||
|
||||
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
|
||||
}
|
||||
|
||||
private byte[] readFile() throws IOException {
|
||||
byte[] buffer;
|
||||
try (InputStream is = ByteArrayFileCacheTest.class.getResourceAsStream("/sounds/doorbell.mp3")) {
|
||||
buffer = new byte[is.available()];
|
||||
is.read(buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
Binary file not shown.
@ -16,6 +16,7 @@ import static java.util.stream.Collectors.joining;
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpStatus.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -38,7 +39,7 @@ import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonHourlyF
|
||||
import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonUVIndexData;
|
||||
import org.openhab.binding.openweathermap.internal.dto.OpenWeatherMapJsonWeatherData;
|
||||
import org.openhab.binding.openweathermap.internal.handler.OpenWeatherMapAPIHandler;
|
||||
import org.openhab.binding.openweathermap.internal.utils.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ByteArrayFileCache;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
@ -224,7 +225,12 @@ public class OpenWeatherMapConnection {
|
||||
|
||||
private static @Nullable RawType downloadWeatherIconFromCache(String url) {
|
||||
if (IMAGE_CACHE.containsKey(url)) {
|
||||
return new RawType(IMAGE_CACHE.get(url), PNG_CONTENT_TYPE);
|
||||
try {
|
||||
return new RawType(IMAGE_CACHE.get(url), PNG_CONTENT_TYPE);
|
||||
} catch (IOException e) {
|
||||
LoggerFactory.getLogger(OpenWeatherMapConnection.class)
|
||||
.trace("Failed to download the content of URL '{}'", url, e);
|
||||
}
|
||||
} else {
|
||||
RawType image = downloadWeatherIcon(url);
|
||||
if (image != null) {
|
||||
|
@ -1,238 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.openweathermap.internal.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a simple file based cache implementation.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ByteArrayFileCache {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
|
||||
|
||||
private static final String CACHE_FOLDER_NAME = "cache";
|
||||
public static final char EXTENSION_SEPARATOR = '.';
|
||||
|
||||
protected final File cacheFolder;
|
||||
|
||||
public ByteArrayFileCache(String servicePID) {
|
||||
// TODO track and limit folder size
|
||||
// TODO support user specific folder
|
||||
cacheFolder = new File(new File(new File(OpenHAB.getUserDataFolder()), CACHE_FOLDER_NAME), servicePID);
|
||||
if (!cacheFolder.exists()) {
|
||||
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
cacheFolder.mkdirs();
|
||||
}
|
||||
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
|
||||
* new content.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void put(String key, byte[] content) {
|
||||
writeFile(getUniqueFile(key), content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
*/
|
||||
public void putIfAbsent(String key, byte[] content) {
|
||||
File fileInCache = getUniqueFile(key);
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("File '{}' present in cache", fileInCache.getName());
|
||||
} else {
|
||||
writeFile(fileInCache, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a file to the cache and returns the content of the file.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @param content the content for the file to be associated with the specified key
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] putIfAbsentAndGet(String key, byte[] content) {
|
||||
putIfAbsent(key, content);
|
||||
|
||||
// return get(key);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given content to the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @param content the content to be written
|
||||
*/
|
||||
private void writeFile(File fileInCache, byte[] content) {
|
||||
logger.debug("Caching file '{}'", fileInCache.getName());
|
||||
try {
|
||||
Files.write(fileInCache.toPath(), content);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the key is present in the cache.
|
||||
*
|
||||
* @param key the key whose presence in the cache is to be tested
|
||||
* @return true if the cache contains a file for the specified key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getUniqueFile(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the file associated with the given key from the cache.
|
||||
*
|
||||
* @param key the key whose associated file is to be removed
|
||||
*/
|
||||
public void remove(String key) {
|
||||
deleteFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given {@link File}.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
*/
|
||||
private void deleteFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
|
||||
fileInCache.delete();
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all files from the cache.
|
||||
*/
|
||||
public void clear() {
|
||||
File[] filesInCache = cacheFolder.listFiles();
|
||||
if (filesInCache != null && filesInCache.length > 0) {
|
||||
logger.debug("Deleting all files from cache");
|
||||
Arrays.stream(filesInCache).forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the file associated with the given key, if it is present.
|
||||
*
|
||||
* @param key the key whose associated file is to be returned
|
||||
* @return the content of the file associated with the given key
|
||||
*/
|
||||
public byte[] get(String key) {
|
||||
return readFile(getUniqueFile(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the content from the given {@link File}, if it is present.
|
||||
*
|
||||
* @param fileInCache the {@link File}
|
||||
* @return the content of the file
|
||||
*/
|
||||
private byte[] readFile(File fileInCache) {
|
||||
if (fileInCache.exists()) {
|
||||
logger.debug("Reading file '{}' from cache", fileInCache.getName());
|
||||
try {
|
||||
return Files.readAllBytes(fileInCache.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("File '{}' not found in cache", fileInCache.getName());
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique {@link File} from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file for the file associated with the given key
|
||||
*/
|
||||
private File getUniqueFile(String key) {
|
||||
// TODO: store / cache file internally for faster operations
|
||||
String fileExtension = getFileExtension(key);
|
||||
return new File(cacheFolder,
|
||||
getUniqueFileName(key) + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name.
|
||||
*
|
||||
* @param fileName the file name to retrieve the extension of
|
||||
* @return the extension of the file or null if none exists
|
||||
*/
|
||||
private @Nullable String getFileExtension(String fileName) {
|
||||
int index = fileName.lastIndexOf(EXTENSION_SEPARATOR);
|
||||
// exclude file names starting with a dot
|
||||
if (index > 0) {
|
||||
return fileName.substring(index + 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique file name from the key with which the file is to be associated.
|
||||
*
|
||||
* @param key the key with which the file is to be associated
|
||||
* @return unique file name for the file associated with the given key
|
||||
*/
|
||||
private String getUniqueFileName(String key) {
|
||||
try {
|
||||
byte[] bytesOfFileName = key.getBytes(StandardCharsets.UTF_8);
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] md5Hash = md.digest(bytesOfFileName);
|
||||
BigInteger bigInt = new BigInteger(1, md5Hash);
|
||||
StringBuilder fileNameHash = new StringBuilder(bigInt.toString(16));
|
||||
// Now we need to zero pad it if you actually want the full 32 chars
|
||||
while (fileNameHash.length() < 32) {
|
||||
fileNameHash.insert(0, "0");
|
||||
}
|
||||
return fileNameHash.toString();
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
// should not happen
|
||||
logger.error("Could not create MD5 hash for key '{}'", key, ex);
|
||||
return key.toString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user