-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
File Cache API #3553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ucr
Are you sure you want to change the base?
File Cache API #3553
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| package com.google.appinventor.components.runtime.util; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add standard preamble here. |
||
|
|
||
| import java.io.File; | ||
| import java.util.HashMap; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
||
| public class FileCache { | ||
| public File cacheDir; | ||
|
||
| private final HashMap<String, CompletableFuture<Void>> fileMap = new HashMap<>(); | ||
|
||
| private static final Logger LOG = Logger.getLogger(FileCache.class.getName()); | ||
|
|
||
| /** | ||
| * Creates a new FileCache instance with the specified cache directory. If the directory doesn't | ||
| * exist, it will be created. | ||
| * | ||
| * @param cacheDir the directory to use for caching files | ||
| */ | ||
| public FileCache(File cacheDir) { | ||
| this.cacheDir = cacheDir; | ||
| if (!cacheDir.exists()) { | ||
| cacheDir.mkdirs(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on the value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which specific value do I need to check to throw an IOException? |
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Registers a file for download from the specified URL to the cache. If the file doesn't exist in | ||
| * the cache, it will be downloaded asynchronously. | ||
| * | ||
| * @param path the relative path within the cache directory where the file formshould be stored | ||
| * @param url the URL from which to download the file | ||
| * @return a CompletableFuture that completes when the download is finished, or immediately if the | ||
| * file already exists | ||
| */ | ||
| public CompletableFuture<Void> registerFile(final String path, final String url) { | ||
|
||
| final File file = new File(cacheDir, path); | ||
| if (!file.exists()) { | ||
| CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| try { | ||
| FileUtil.downloadUrlToFile(url, file.getAbsolutePath()); | ||
| fileMap.remove(path); | ||
|
||
| } catch (Exception error) { | ||
| LOG.log(Level.SEVERE, "Exception downloading file to cache", error); | ||
| } | ||
| } | ||
| }); | ||
| fileMap.put(path, future); | ||
| return future; | ||
|
||
| } | ||
| return CompletableFuture.completedFuture(null); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves a file from the cache at the specified path. If the file is currently being | ||
| * downloaded, this method will wait for the download to complete. | ||
| * | ||
| * @param path the relative path of the file within the cache directory | ||
| * @return a CompletableFuture containing the File if it exists and is ready, or a failed future | ||
| * with an exception if the file doesn't exist or download failed | ||
| */ | ||
| public CompletableFuture<File> getFile(String path) { | ||
| File file = new File(cacheDir, path); | ||
| if (!file.exists()) { | ||
| return CompletableFuture.failedFuture(new Exception("File does not exist: " + path)); | ||
| } else if (fileMap.containsKey(path)) { | ||
| try { | ||
| fileMap.get(path).get(); | ||
|
||
| } catch (Exception e) { | ||
| return CompletableFuture.failedFuture(e); | ||
| } | ||
| return CompletableFuture.completedFuture(file); | ||
| } else { | ||
| return CompletableFuture.completedFuture(file); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Recursively deletes a folder and all its contents. This is a helper method used by | ||
| * resetCache(). | ||
| * | ||
| * @param folder the folder to delete | ||
| */ | ||
| private void deleteFolder(File folder) { | ||
|
||
| if (folder.isDirectory()) { | ||
| for (File file : folder.listFiles()) { | ||
| deleteFolder(file); | ||
| } | ||
| } | ||
| folder.delete(); | ||
| } | ||
|
|
||
| /** | ||
| * Resets the cache by deleting all cached files and clearing the internal file map. This will | ||
| * remove the entire cache directory and recreate it as an empty directory. | ||
| */ | ||
| public void resetCache() { | ||
| if (cacheDir.exists()) { | ||
| deleteFolder(cacheDir); | ||
| } | ||
| fileMap.clear(); | ||
| } | ||
|
Comment on lines
174
to
193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should think about if this is really necessary. If extensions call this for example it may corrupt existing component instances that assume the file is already cached (NPE due to missing entry in the table). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's pretty easy to corrupt the file cache by just initiating a fetch without wifi or cutting off wifi midway through a download. We need to be able to reset the cache because of how easily it can be corrupted. There is no other way to verify file integrity and automatically do it unless we refetch every time we have internet, which uses a lot of bandwidth for no reason, especially when the types of files being cached are large. We also can't rely on the ETag header because it is not mandatory. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this
private finaland provide a getter function.