/*
* Copyright (C) 2014 Indeed Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.indeed.imhotep.io.caching;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;
public class CachedRemoteFileSystem extends RemoteFileSystem {
private RemoteFileSystem parentFS;
private String mountPoint;
private RemoteFileSystemMounter mounter;
private File localCacheDir;
private LoadingCache<String, File> cache;
public CachedRemoteFileSystem(Map<String,Object> settings,
RemoteFileSystem parentFS,
RemoteFileSystemMounter mounter) throws IOException {
final int cacheSize;
final String cacheDir;
this.parentFS = parentFS;
mountPoint = (String)settings.get("mountpoint");
if (! mountPoint.endsWith(DELIMITER)) {
/* add delimiter to the end */
mountPoint = mountPoint + DELIMITER;
}
mountPoint = mounter.getRootMountPoint() + mountPoint;
mountPoint = mountPoint.replace("//", "/");
this.mounter = mounter;
cacheDir = (String)settings.get("cache-dir");
/* create directory if it does not already exist */
localCacheDir = new File(cacheDir);
localCacheDir.mkdir();
cacheSize = (Integer)settings.get("cacheSizeMB");
cache =
CacheBuilder.newBuilder()
.initialCapacity(8192)
.maximumWeight(cacheSize * 1024)
.weigher(new Weigher<String, File>() {
public int weigh(String path, File cachedFile) {
int kb;
kb = (int)(cachedFile.length() / 1024);
/* don't return weights of 0 */
if (kb == 0) {
kb = 1;
}
return kb;
}
})
.removalListener(new RemovalListener<String, File>() {
public void onRemoval(RemovalNotification<String, File> rn) {
if (rn.getCause().equals(RemovalCause.REPLACED)) {
/* don't delete replaced files */
return;
}
removeFile(rn.getValue());
}
})
.build(new CacheLoader<String, File>() {
public File load(String path) throws Exception {
return downloadFile(path);
}
});
scanExistingFiles();
}
private void scanExistingFiles() throws IOException {
final Iterator<File> filesInCache;
final int prefixLen;
prefixLen = localCacheDir.getCanonicalPath().length() + DELIMITER.length();
filesInCache = FileUtils.iterateFiles(localCacheDir,
TrueFileFilter.INSTANCE,
TrueFileFilter.INSTANCE);
while (filesInCache.hasNext()) {
final File cachedFile = filesInCache.next();
final String path = cachedFile.getCanonicalPath();
String cachePath = path.substring(prefixLen);
cache.put(cachePath, cachedFile);
}
}
private void removeFile(File cachedFile) {
cachedFile.delete();
}
private File downloadFile(String fullPath) throws IOException {
final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
final File localFile;
localFile = new File(localCacheDir, relativePath);
/* create all the directories on the path to the file */
localFile.getParentFile().mkdirs();
parentFS.copyFileInto(fullPath, localFile);
return localFile;
}
@Override
public File loadFile(String fullPath) throws IOException {
try {
return cache.get(fullPath);
} catch (ExecutionException e) {
throw new IOException(e);
}
}
@Override
public String getMountPoint() {
return this.mountPoint;
}
@Override
public RemoteFileInfo stat(String fullPath) {
return parentFS.stat(fullPath);
}
@Override
public List<RemoteFileInfo> readDir(String fullPath) {
return parentFS.readDir(fullPath);
}
@Override
public void copyFileInto(String fullPath, File localFile) throws IOException {
try {
final File cachedFile;
cachedFile = cache.get(fullPath);
FileUtils.copyFile(cachedFile, localFile);
} catch (ExecutionException e) {
throw new IOException(e);
}
}
@Override
public Map<String,File> loadDirectory(String fullPath, File location) throws IOException {
final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
final Map<String,File> files;
final File localDir;
if (location != null) {
throw new UnsupportedOperationException("CachedRemoteFileSystem does not "
+ "support copying a directory.");
}
localDir = new File(localCacheDir, relativePath);
/* create all the directories on the path to the file */
localDir.getParentFile().mkdirs();
files = parentFS.loadDirectory(fullPath, localDir);
if (files == null) {
return null;
}
for (Map.Entry<String, File> entry : files.entrySet()) {
cache.put(entry.getKey(), entry.getValue());
}
files.put(fullPath, localDir);
return files;
}
@Override
public InputStream getInputStreamForFile(String fullPath,
long startOffset,
long maxReadLength) throws IOException {
final File file;
final FileInputStream fis;
file = loadFile(fullPath);
fis = new FileInputStream(file);
fis.skip(startOffset);
return fis;
}
}