package org.mapdb.volume; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mapdb.CC; import org.mapdb.DBException; import org.mapdb.DataIO; import org.mapdb.DataInput2; import java.io.EOFException; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Volume which uses FileChannel. * Uses global lock and does not use mapped memory. */ public final class FileChannelVol extends Volume { public static final VolumeFactory FACTORY = new VolumeFactory() { @Override public Volume makeVolume(String file, boolean readOnly, long fileLockWait, int sliceShift, long initSize, boolean fixedSize) { return new org.mapdb.volume.FileChannelVol(new File(file),readOnly, fileLockWait, sliceShift,initSize); } @NotNull @Override public boolean exists(@Nullable String file) { return new File(file).exists(); } @Override public boolean handlesReadonly() { return true; } }; protected final File file; protected final int sliceSize; protected FileChannel channel; protected final boolean readOnly; protected final FileLock fileLock; protected volatile long size; protected final Lock growLock = new ReentrantLock(); public FileChannelVol(File file, boolean readOnly, long fileLockWait, int sliceShift, long initSize){ this.file = file; this.readOnly = readOnly; this.sliceSize = 1<<sliceShift; Set<OpenOption> options = new HashSet(); options.add(StandardOpenOption.READ); if(!readOnly){ options.add(StandardOpenOption.WRITE); options.add(StandardOpenOption.CREATE); } try { checkFolder(file, readOnly); if (readOnly && !file.exists()) { channel = null; size = 0; } else { channel = FileChannel.open(file.toPath(), options); size = channel.size(); } fileLock = Volume.lockFile(file,channel,readOnly,fileLockWait); if(initSize!=0 && !readOnly){ long oldSize = channel.size(); if(initSize>oldSize){ clear(oldSize,initSize); } } }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } public FileChannelVol(File file) { this(file, false, 0L, CC.PAGE_SHIFT,0L); } protected static void checkFolder(File file, boolean readOnly) throws IOException { File parent = file.getParentFile(); if(parent == null) { parent = file.getCanonicalFile().getParentFile(); } if (parent == null) { throw new IOException("Parent folder could not be determined for: "+file); } if(!parent.exists() || !parent.isDirectory()) throw new IOException("Parent folder does not exist: "+file); if(!parent.canRead()) throw new IOException("Parent folder is not readable: "+file); if(!readOnly && !parent.canWrite()) throw new IOException("Parent folder is not writable: "+file); } @Override public void ensureAvailable(long offset) { offset= DataIO.roundUp(offset,sliceSize); if(offset>size){ growLock.lock(); try { channel.position(offset-1); channel.write(ByteBuffer.allocate(1)); size = offset; } catch (IOException e) { throw new DBException.VolumeIOError(e); }finally { growLock.unlock(); } } } @Override public void truncate(long size) { growLock.lock(); try { this.size = size; channel.truncate(size); }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); }finally{ growLock.unlock(); } } protected void writeFully(long offset, ByteBuffer buf){ int remaining = buf.limit()-buf.position(); if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+remaining){ new IOException("VOL STACK:").printStackTrace(); } try { while(remaining>0){ int write = channel.write(buf, offset); if(write<0) throw new EOFException(); remaining-=write; } }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } @Override public void putLong(long offset, long value) { if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+8){ new IOException("VOL STACK:").printStackTrace(); } ByteBuffer buf = ByteBuffer.allocate(8); buf.putLong(0, value); writeFully(offset, buf); } @Override public void putInt(long offset, int value) { if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+4){ new IOException("VOL STACK:").printStackTrace(); } ByteBuffer buf = ByteBuffer.allocate(4); buf.putInt(0, value); writeFully(offset, buf); } @Override public void putByte(long offset, byte value) { if(CC.VOLUME_PRINT_STACK_AT_OFFSET!=0 && CC.VOLUME_PRINT_STACK_AT_OFFSET>=offset && CC.VOLUME_PRINT_STACK_AT_OFFSET <= offset+1){ new IOException("VOL STACK:").printStackTrace(); } ByteBuffer buf = ByteBuffer.allocate(1); buf.put(0, value); writeFully(offset, buf); } @Override public void putData(long offset, byte[] src, int srcPos, int srcSize) { ByteBuffer buf = ByteBuffer.wrap(src,srcPos, srcSize); writeFully(offset, buf); } @Override public void putData(long offset, ByteBuffer buf) { writeFully(offset,buf); } protected void readFully(long offset, ByteBuffer buf){ int remaining = buf.limit()-buf.position(); try{ while(remaining>0){ int read = channel.read(buf, offset); if(read<0) throw new EOFException(); remaining-=read; } }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } @Override public long getLong(long offset) { ByteBuffer buf = ByteBuffer.allocate(8); readFully(offset, buf); return buf.getLong(0); } @Override public int getInt(long offset) { ByteBuffer buf = ByteBuffer.allocate(4); readFully(offset,buf); return buf.getInt(0); } @Override public byte getByte(long offset) { ByteBuffer buf = ByteBuffer.allocate(1); readFully(offset,buf); return buf.get(0); } @Override public DataInput2.ByteBuffer getDataInput(long offset, int size) { ByteBuffer buf = ByteBuffer.allocate(size); readFully(offset,buf); return new DataInput2.ByteBuffer(buf,0); } @Override public void getData(long offset, byte[] bytes, int bytesPos, int size) { ByteBuffer buf = ByteBuffer.wrap(bytes,bytesPos,size); readFully(offset,buf); } @Override public synchronized void close() { try{ if (!closed.compareAndSet(false,true)) return; if(fileLock!=null && fileLock.isValid()){ fileLock.release(); } if(channel!=null) channel.close(); channel = null; }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } @Override public void sync() { try{ channel.force(true); }catch(ClosedByInterruptException e){ throw new DBException.VolumeClosedByInterrupt(e); }catch(ClosedChannelException e){ throw new DBException.VolumeClosed(e); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } @Override public int sliceSize() { return -1; } @Override public boolean isSliced() { return false; } @Override public long length() { try { return channel.size(); } catch (IOException e) { throw new DBException.VolumeIOError(e); } } @Override public boolean isReadOnly() { return readOnly; } @Override public File getFile() { return file; } @Override public boolean getFileLocked() { return fileLock!=null && fileLock.isValid(); } @Override public void clear(long startOffset, long endOffset) { FileChannelVol.clear(channel, startOffset, endOffset); } static public void clear(FileChannel channel, long startOffset, long endOffset) { try { ByteBuffer b = ByteBuffer.wrap(CLEAR); while(startOffset<endOffset){ b.rewind(); b.limit((int) Math.min(CLEAR.length, endOffset - startOffset)); channel.write(b, startOffset); startOffset+=CLEAR.length; } } catch (IOException e) { throw new DBException.VolumeIOError(e); } } }