2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.openweathermap.internal.utils;
16 import java.io.IOException;
17 import java.math.BigInteger;
18 import java.nio.charset.StandardCharsets;
19 import java.nio.file.Files;
20 import java.security.MessageDigest;
21 import java.security.NoSuchAlgorithmException;
22 import java.util.Arrays;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.core.OpenHAB;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * This is a simple file based cache implementation.
33 * @author Christoph Weitkamp - Initial contribution
36 public class ByteArrayFileCache {
38 private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
40 private static final String CACHE_FOLDER_NAME = "cache";
41 public static final char EXTENSION_SEPARATOR = '.';
43 protected final File cacheFolder;
45 public ByteArrayFileCache(String servicePID) {
46 // TODO track and limit folder size
47 // TODO support user specific folder
48 cacheFolder = new File(new File(new File(OpenHAB.getUserDataFolder()), CACHE_FOLDER_NAME), servicePID);
49 if (!cacheFolder.exists()) {
50 logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
53 logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
57 * Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
60 * @param key the key with which the file is to be associated
61 * @param content the content for the file to be associated with the specified key
63 public void put(String key, byte[] content) {
64 writeFile(getUniqueFile(key), content);
68 * Adds a file to the cache.
70 * @param key the key with which the file is to be associated
71 * @param content the content for the file to be associated with the specified key
73 public void putIfAbsent(String key, byte[] content) {
74 File fileInCache = getUniqueFile(key);
75 if (fileInCache.exists()) {
76 logger.debug("File '{}' present in cache", fileInCache.getName());
78 writeFile(fileInCache, content);
83 * Adds a file to the cache and returns the content of the file.
85 * @param key the key with which the file is to be associated
86 * @param content the content for the file to be associated with the specified key
87 * @return the content of the file associated with the given key
89 public byte[] putIfAbsentAndGet(String key, byte[] content) {
90 putIfAbsent(key, content);
97 * Writes the given content to the given {@link File}.
99 * @param fileInCache the {@link File}
100 * @param content the content to be written
102 private void writeFile(File fileInCache, byte[] content) {
103 logger.debug("Caching file '{}'", fileInCache.getName());
105 Files.write(fileInCache.toPath(), content);
106 } catch (IOException e) {
107 logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
112 * Checks if the key is present in the cache.
114 * @param key the key whose presence in the cache is to be tested
115 * @return true if the cache contains a file for the specified key
117 public boolean containsKey(String key) {
118 return getUniqueFile(key).exists();
122 * Removes the file associated with the given key from the cache.
124 * @param key the key whose associated file is to be removed
126 public void remove(String key) {
127 deleteFile(getUniqueFile(key));
131 * Deletes the given {@link File}.
133 * @param fileInCache the {@link File}
135 private void deleteFile(File fileInCache) {
136 if (fileInCache.exists()) {
137 logger.debug("Deleting file '{}' from cache", fileInCache.getName());
138 fileInCache.delete();
140 logger.debug("File '{}' not found in cache", fileInCache.getName());
145 * Removes all files from the cache.
147 public void clear() {
148 File[] filesInCache = cacheFolder.listFiles();
149 if (filesInCache != null && filesInCache.length > 0) {
150 logger.debug("Deleting all files from cache");
151 Arrays.stream(filesInCache).forEach(File::delete);
156 * Returns the content of the file associated with the given key, if it is present.
158 * @param key the key whose associated file is to be returned
159 * @return the content of the file associated with the given key
161 public byte[] get(String key) {
162 return readFile(getUniqueFile(key));
166 * Reads the content from the given {@link File}, if it is present.
168 * @param fileInCache the {@link File}
169 * @return the content of the file
171 private byte[] readFile(File fileInCache) {
172 if (fileInCache.exists()) {
173 logger.debug("Reading file '{}' from cache", fileInCache.getName());
175 return Files.readAllBytes(fileInCache.toPath());
176 } catch (IOException e) {
177 logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
180 logger.debug("File '{}' not found in cache", fileInCache.getName());
186 * Creates a unique {@link File} from the key with which the file is to be associated.
188 * @param key the key with which the file is to be associated
189 * @return unique file for the file associated with the given key
191 private File getUniqueFile(String key) {
192 // TODO: store / cache file internally for faster operations
193 String fileExtension = getFileExtension(key);
194 return new File(cacheFolder,
195 getUniqueFileName(key) + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
199 * Gets the extension of a file name.
201 * @param fileName the file name to retrieve the extension of
202 * @return the extension of the file or null if none exists
204 private @Nullable String getFileExtension(String fileName) {
205 int index = fileName.lastIndexOf(EXTENSION_SEPARATOR);
206 // exclude file names starting with a dot
208 return fileName.substring(index + 1);
215 * Creates a unique file name from the key with which the file is to be associated.
217 * @param key the key with which the file is to be associated
218 * @return unique file name for the file associated with the given key
220 private String getUniqueFileName(String key) {
222 byte[] bytesOfFileName = key.getBytes(StandardCharsets.UTF_8);
223 MessageDigest md = MessageDigest.getInstance("MD5");
224 byte[] md5Hash = md.digest(bytesOfFileName);
225 BigInteger bigInt = new BigInteger(1, md5Hash);
226 StringBuilder fileNameHash = new StringBuilder(bigInt.toString(16));
227 // Now we need to zero pad it if you actually want the full 32 chars
228 while (fileNameHash.length() < 32) {
229 fileNameHash.insert(0, "0");
231 return fileNameHash.toString();
232 } catch (NoSuchAlgorithmException ex) {
234 logger.error("Could not create MD5 hash for key '{}'", key, ex);
235 return key.toString();