package com.cloudhopper.commons.io; /* * #%L * ch-commons-io * %% * Copyright (C) 2012 - 2013 Cloudhopper by Twitter * %% * 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. * #L% */ import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.security.NoSuchAlgorithmException; import com.cloudhopper.commons.util.Hasher; import com.cloudhopper.commons.util.Hasher.Algorithm; /** * A simple implementation of FileStore that uses NIO FileChannel direct ByteBuffer-ing, and * stores the file using a simple hash directory structure. * @author garth */ public class SimpleNIOFileStore implements FileStore { private static final int B16K = 16*1024; public SimpleNIOFileStore(IdGenerator idGen, String base) throws SecurityException { this.idGen = idGen; this.baseDir = new File(base); if (!baseDir.exists()) baseDir.mkdirs(); } private IdGenerator idGen; private File baseDir; @Override public Id write(InputStream is) throws FileStoreException { return write(Channels.newChannel(is)); } @Override public Id write(ReadableByteChannel channel) throws FileStoreException { File f = null; RandomAccessFile randomAccessFile = null; FileChannel fileChannel = null; Id id = idGen.newId(); try { f = createFile(id.getName()); randomAccessFile = new RandomAccessFile(f, "rw"); fileChannel = randomAccessFile.getChannel(); channelCopyToFile(channel, fileChannel); } catch (IOException e) { throw new FileStoreException(e); } finally { try { if (fileChannel != null) fileChannel.close(); } catch (Exception e) {} try { if (randomAccessFile != null) randomAccessFile.close(); } catch (Exception e) {} } return id; } @Override public InputStream readStream(Id id) throws FileStoreException { return Channels.newInputStream(getFileChannel(id)); } @Override public ReadableByteChannel readChannel(Id id) throws FileStoreException { return getFileChannel(id); } @Override public void transferToOutputStream(OutputStream os, Id id) throws FileStoreException { FileChannel fileChannel = null; try { fileChannel = getFileChannel(id); channelCopyFromFile(fileChannel, Channels.newChannel(os)); } catch (IOException e) { throw new FileStoreException(e); } finally { try { if (fileChannel != null) fileChannel.close(); } catch (Exception e) {} } } @Override public void transferToChannel(WritableByteChannel channel, Id id) throws FileStoreException { FileChannel fileChannel = null; try { fileChannel = getFileChannel(id); channelCopyFromFile(fileChannel, channel); } catch (IOException e) { throw new FileStoreException(e); } finally { try { if (fileChannel != null) fileChannel.close(); } catch (Exception e) {} } } @Override public FileChannel getFileChannel(Id id) throws FileStoreException { try { File f = getFile(id.getName()); RandomAccessFile randomAccessFile = new RandomAccessFile(f, "r"); FileChannel fileChannel = randomAccessFile.getChannel(); return fileChannel; } catch (IOException e) { throw new FileStoreException(e); } } @Override public void remove(Id id) throws FileStoreException { try { File f = getFile(id.getName()); f.delete(); } catch (IOException e) { throw new FileStoreException(e); } } /////// // Utilities for file and path /////// private void channelCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException { final ByteBuffer buffer = ByteBuffer.allocateDirect(B16K); while (src.read(buffer) != -1) { buffer.flip(); dest.write(buffer); buffer.compact(); } buffer.flip(); while (buffer.hasRemaining()) { dest.write(buffer); } } private void channelCopyToFile(final ReadableByteChannel src, final FileChannel dest) throws IOException { long position = 0; long transferred = 0; do { transferred = dest.transferFrom(src, position, B16K); position += transferred; } while (transferred >= B16K); } private void channelCopyFromFile(final FileChannel src, final WritableByteChannel dest) throws IOException { long position = 0; long transferred = 0; do { transferred = src.transferTo(position, B16K, dest); position += transferred; } while (transferred >= B16K); } private File createFile(String name) throws SecurityException { File tmpDir = new File(this.baseDir, getHashPathForLevel(name, 2)); if (!tmpDir.exists()) tmpDir.mkdirs(); File tmpFile = new File(tmpDir, name); if (tmpFile.exists()) throw new SecurityException("This file already exists. Use getFile() to use it."); return tmpFile; } private File getFile(String name) throws IOException { File tmpDir = new File(this.baseDir, getHashPathForLevel(name, 2)); if (!tmpDir.exists()) throw new FileNotFoundException("Could not find directory at "+tmpDir.getPath()); File tmpFile = new File(tmpDir, name); if (!tmpFile.exists()) throw new FileNotFoundException("Could not find file at "+tmpFile.getPath()); return tmpFile; } private String getHashPathForLevel(String name, int levels) { String path = ""; if (levels != 0) { try { Hasher hasher = new Hasher(Algorithm.MD5); String hash = hasher.toHashedHexString(name); path = ""; for (int i = 1; i <= levels; i++) { path += hash.substring( 0, i ) + "/"; } } catch (NoSuchAlgorithmException e) {} } return path; } }