/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.cache.disk; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.Collection; import com.facebook.binaryresource.BinaryResource; import com.facebook.cache.common.CacheErrorLogger; import com.facebook.common.file.FileTree; import com.facebook.common.file.FileUtils; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.Supplier; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.logging.FLog; /** * A supplier of a DiskStorage concrete implementation. */ public class DynamicDefaultDiskStorage implements DiskStorage { private static final Class<?> TAG = DynamicDefaultDiskStorage.class; private final int mVersion; private final Supplier<File> mBaseDirectoryPathSupplier; private final String mBaseDirectoryName; private final CacheErrorLogger mCacheErrorLogger; @VisibleForTesting volatile State mCurrentState; /** * Represents the current 'cached' state. */ @VisibleForTesting static class State { public final @Nullable DiskStorage delegate; public final @Nullable File rootDirectory; @VisibleForTesting State(@Nullable File rootDirectory, @Nullable DiskStorage delegate) { this.delegate = delegate; this.rootDirectory = rootDirectory; } } public DynamicDefaultDiskStorage( int version, Supplier<File> baseDirectoryPathSupplier, String baseDirectoryName, CacheErrorLogger cacheErrorLogger) { mVersion = version; mCacheErrorLogger = cacheErrorLogger; mBaseDirectoryPathSupplier = baseDirectoryPathSupplier; mBaseDirectoryName = baseDirectoryName; mCurrentState = new State(null, null); } @Override public boolean isEnabled() { try { return get().isEnabled(); } catch (IOException ioe) { return false; } } @Override public boolean isExternal() { try { return get().isExternal(); } catch (IOException ioe) { return false; } } @Override public String getStorageName() { try { return get().getStorageName(); } catch (IOException ioe) { return ""; } } @Override public BinaryResource getResource(String resourceId, Object debugInfo) throws IOException { return get().getResource(resourceId, debugInfo); } @Override public boolean contains(String resourceId, Object debugInfo) throws IOException { return get().contains(resourceId, debugInfo); } @Override public boolean touch(String resourceId, Object debugInfo) throws IOException { return get().touch(resourceId, debugInfo); } @Override public void purgeUnexpectedResources() { try { get().purgeUnexpectedResources(); } catch (IOException ioe) { // this method in fact should throu IOException // for now we will swallow the exception as it's done in DefaultDiskStorage FLog.e(TAG, "purgeUnexpectedResources", ioe); } } @Override public Inserter insert(String resourceId, Object debugInfo) throws IOException { return get().insert(resourceId, debugInfo); } @Override public Collection<Entry> getEntries() throws IOException { return get().getEntries(); } @Override public long remove(Entry entry) throws IOException { return get().remove(entry); } @Override public long remove(String resourceId) throws IOException { return get().remove(resourceId); } @Override public void clearAll() throws IOException { get().clearAll(); } @Override public DiskDumpInfo getDumpInfo() throws IOException { return get().getDumpInfo(); } /** * Gets a concrete disk-storage instance. If nothing has changed since the last call, then * the last state is returned * @return an instance of the appropriate DiskStorage class * @throws IOException */ @VisibleForTesting /* package protected */ synchronized DiskStorage get() throws IOException { if (shouldCreateNewStorage()) { // discard anything we created deleteOldStorageIfNecessary(); createStorage(); } return Preconditions.checkNotNull(mCurrentState.delegate); } private boolean shouldCreateNewStorage() { State currentState = mCurrentState; return (currentState.delegate == null || currentState.rootDirectory == null || !currentState.rootDirectory.exists()); } @VisibleForTesting void deleteOldStorageIfNecessary() { if (mCurrentState.delegate != null && mCurrentState.rootDirectory != null) { // LATER: Actually delegate this call to the storage. We shouldn't be // making an end-run around it FileTree.deleteRecursively(mCurrentState.rootDirectory); } } private void createStorage() throws IOException { File rootDirectory = new File(mBaseDirectoryPathSupplier.get(), mBaseDirectoryName); createRootDirectoryIfNecessary(rootDirectory); DiskStorage storage = new DefaultDiskStorage(rootDirectory, mVersion, mCacheErrorLogger); mCurrentState = new State(rootDirectory, storage); } @VisibleForTesting void createRootDirectoryIfNecessary(File rootDirectory) throws IOException { try { FileUtils.mkdirs(rootDirectory); } catch (FileUtils.CreateDirectoryException cde) { mCacheErrorLogger.logError( CacheErrorLogger.CacheErrorCategory.WRITE_CREATE_DIR, TAG, "createRootDirectoryIfNecessary", cde); throw cde; } FLog.d(TAG, "Created cache directory %s", rootDirectory.getAbsolutePath()); } }