/* * Copyright (c) 2008-2014 MongoDB, 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.mongodb.gridfs; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.MongoException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * This class enables retrieving a GridFS file metadata and content. Operations include: * <ul> * <li>Writing data to a file on disk or an OutputStream </li> * <li>Creating an {@code InputStream} to stream the data into</li> * </ul> * * @mongodb.driver.manual core/gridfs/ GridFS */ public class GridFSDBFile extends GridFSFile { /** * Returns an InputStream from which data can be read. * * @return the input stream */ public InputStream getInputStream() { return new GridFSInputStream(); } /** * Writes the file's data to a file on disk. * * @param filename the file name on disk * @return number of bytes written * @throws IOException if there are problems writing to the file */ public long writeTo(final String filename) throws IOException { return writeTo(new File(filename)); } /** * Writes the file's data to a file on disk. * * @param file the File object * @return number of bytes written * @throws IOException if there are problems writing to the {@code file} */ public long writeTo(final File file) throws IOException { FileOutputStream out = null; try { out = new FileOutputStream(file); return writeTo(out); } finally { if (out != null) { out.close(); } } } /** * Writes the file's data to an OutputStream. * * @param out the OutputStream * @return number of bytes written * @throws IOException if there are problems writing to {@code out} */ public long writeTo(final OutputStream out) throws IOException { int nc = numChunks(); for (int i = 0; i < nc; i++) { out.write(getChunk(i)); } return length; } private byte[] getChunk(final int chunkNumber) { if (fs == null) { throw new IllegalStateException("No GridFS instance defined!"); } DBObject chunk = fs.getChunksCollection().findOne(new BasicDBObject("files_id", id).append("n", chunkNumber)); if (chunk == null) { throw new MongoException("Can't find a chunk! file id: " + id + " chunk: " + chunkNumber); } return (byte[]) chunk.get("data"); } /** * Removes file from GridFS i.e. removes documents from files and chunks collections. */ void remove() { fs.getFilesCollection().remove(new BasicDBObject("_id", id)); fs.getChunksCollection().remove(new BasicDBObject("files_id", id)); } private class GridFSInputStream extends InputStream { private final int numberOfChunks; private int currentChunkId = -1; private int offset = 0; private byte[] buffer = null; GridFSInputStream() { this.numberOfChunks = numChunks(); } @Override public int available() { if (buffer == null) { return 0; } return buffer.length - offset; } @Override public int read() { byte[] b = new byte[1]; int res = read(b); if (res < 0) { return -1; } return b[0] & 0xFF; } @Override public int read(final byte[] b) { return read(b, 0, b.length); } @Override public int read(final byte[] b, final int off, final int len) { if (buffer == null || offset >= buffer.length) { if (currentChunkId + 1 >= numberOfChunks) { return -1; } buffer = getChunk(++currentChunkId); offset = 0; } int r = Math.min(len, buffer.length - offset); System.arraycopy(buffer, offset, b, off, r); offset += r; return r; } /** * Will smartly skip over chunks without fetching them if possible. */ @Override public long skip(final long bytesToSkip) throws IOException { if (bytesToSkip <= 0) { return 0; } if (currentChunkId == numberOfChunks) { //We're actually skipping over the back end of the file, short-circuit here //Don't count those extra bytes to skip in with the return value return 0; } // offset in the whole file long offsetInFile = 0; if (currentChunkId >= 0) { offsetInFile = currentChunkId * chunkSize + offset; } if (bytesToSkip + offsetInFile >= length) { currentChunkId = numberOfChunks; buffer = null; return length - offsetInFile; } int temp = currentChunkId; currentChunkId = (int) ((bytesToSkip + offsetInFile) / chunkSize); if (temp != currentChunkId) { buffer = getChunk(currentChunkId); } offset = (int) ((bytesToSkip + offsetInFile) % chunkSize); return bytesToSkip; } } }