Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.h2.store.fs.FilePathSplit Maven / Gradle / Ivy
/*
* Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
/**
* A file system that may split files into multiple smaller files.
* (required for a FAT32 because it only support files up to 2 GB).
*/
public class FilePathSplit extends FilePathWrapper {
private static final String PART_SUFFIX = ".part";
@Override
protected String getPrefix() {
return getScheme() + ":" + parse(name)[0] + ":";
}
@Override
public FilePath unwrap(String fileName) {
return FilePath.get(parse(fileName)[1]);
}
@Override
public boolean setReadOnly() {
boolean result = false;
for (int i = 0;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
result = f.setReadOnly();
} else {
break;
}
}
return result;
}
@Override
public void delete() {
for (int i = 0;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
f.delete();
} else {
break;
}
}
}
@Override
public long lastModified() {
long lastModified = 0;
for (int i = 0;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
long l = f.lastModified();
lastModified = Math.max(lastModified, l);
} else {
break;
}
}
return lastModified;
}
@Override
public long size() {
long length = 0;
for (int i = 0;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
length += f.size();
} else {
break;
}
}
return length;
}
@Override
public ArrayList newDirectoryStream() {
List list = getBase().newDirectoryStream();
ArrayList newList = new ArrayList<>();
for (FilePath f : list) {
if (!f.getName().endsWith(PART_SUFFIX)) {
newList.add(wrap(f));
}
}
return newList;
}
@Override
public InputStream newInputStream() throws IOException {
InputStream input = getBase().newInputStream();
for (int i = 1;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
InputStream i2 = f.newInputStream();
input = new SequenceInputStream(input, i2);
} else {
break;
}
}
return input;
}
@Override
public FileChannel open(String mode) throws IOException {
ArrayList list = new ArrayList<>();
list.add(getBase().open(mode));
for (int i = 1;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
list.add(f.open(mode));
} else {
break;
}
}
FileChannel[] array = list.toArray(new FileChannel[0]);
long maxLength = array[0].size();
long length = maxLength;
if (array.length == 1) {
long defaultMaxLength = getDefaultMaxLength();
if (maxLength < defaultMaxLength) {
maxLength = defaultMaxLength;
}
} else {
if (maxLength == 0) {
closeAndThrow(0, array, array[0], maxLength);
}
for (int i = 1; i < array.length - 1; i++) {
FileChannel c = array[i];
long l = c.size();
length += l;
if (l != maxLength) {
closeAndThrow(i, array, c, maxLength);
}
}
FileChannel c = array[array.length - 1];
long l = c.size();
length += l;
if (l > maxLength) {
closeAndThrow(array.length - 1, array, c, maxLength);
}
}
return new FileSplit(this, mode, array, length, maxLength);
}
private long getDefaultMaxLength() {
return 1L << Integer.decode(parse(name)[0]);
}
private void closeAndThrow(int id, FileChannel[] array, FileChannel o,
long maxLength) throws IOException {
String message = "Expected file length: " + maxLength + " got: " +
o.size() + " for " + getName(id);
for (FileChannel f : array) {
f.close();
}
throw new IOException(message);
}
@Override
public OutputStream newOutputStream(boolean append) throws IOException {
return new FileChannelOutputStream(open("rw"), append);
}
@Override
public void moveTo(FilePath path, boolean atomicReplace) {
FilePathSplit newName = (FilePathSplit) path;
for (int i = 0;; i++) {
FilePath o = getBase(i);
if (o.exists()) {
o.moveTo(newName.getBase(i), atomicReplace);
} else if (newName.getBase(i).exists()) {
newName.getBase(i).delete();
} else {
break;
}
}
}
/**
* Split the file name into size and base file name.
*
* @param fileName the file name
* @return an array with size and file name
*/
private String[] parse(String fileName) {
if (!fileName.startsWith(getScheme())) {
DbException.throwInternalError(fileName + " doesn't start with " + getScheme());
}
fileName = fileName.substring(getScheme().length() + 1);
String size;
if (fileName.length() > 0 && Character.isDigit(fileName.charAt(0))) {
int idx = fileName.indexOf(':');
size = fileName.substring(0, idx);
try {
fileName = fileName.substring(idx + 1);
} catch (NumberFormatException e) {
// ignore
}
} else {
size = Long.toString(SysProperties.SPLIT_FILE_SIZE_SHIFT);
}
return new String[] { size, fileName };
}
/**
* Get the file name of a part file.
*
* @param id the part id
* @return the file name including the part id
*/
FilePath getBase(int id) {
return FilePath.get(getName(id));
}
private String getName(int id) {
return id > 0 ? getBase().name + "." + id + PART_SUFFIX : getBase().name;
}
@Override
public String getScheme() {
return "split";
}
}
/**
* A file that may be split into multiple smaller files.
*/
class FileSplit extends FileBase {
private final FilePathSplit file;
private final String mode;
private final long maxLength;
private FileChannel[] list;
private long filePointer;
private long length;
FileSplit(FilePathSplit file, String mode, FileChannel[] list, long length,
long maxLength) {
this.file = file;
this.mode = mode;
this.list = list;
this.length = length;
this.maxLength = maxLength;
}
@Override
public void implCloseChannel() throws IOException {
for (FileChannel c : list) {
c.close();
}
}
@Override
public long position() {
return filePointer;
}
@Override
public long size() {
return length;
}
@Override
public synchronized int read(ByteBuffer dst, long position)
throws IOException {
int len = dst.remaining();
if (len == 0) {
return 0;
}
len = (int) Math.min(len, length - position);
if (len <= 0) {
return -1;
}
long offset = position % maxLength;
len = (int) Math.min(len, maxLength - offset);
FileChannel channel = getFileChannel(position);
return channel.read(dst, offset);
}
@Override
public int read(ByteBuffer dst) throws IOException {
int len = dst.remaining();
if (len == 0) {
return 0;
}
len = (int) Math.min(len, length - filePointer);
if (len <= 0) {
return -1;
}
long offset = filePointer % maxLength;
len = (int) Math.min(len, maxLength - offset);
FileChannel channel = getFileChannel(filePointer);
channel.position(offset);
len = channel.read(dst);
filePointer += len;
return len;
}
@Override
public FileChannel position(long pos) {
filePointer = pos;
return this;
}
private FileChannel getFileChannel(long position) throws IOException {
int id = (int) (position / maxLength);
while (id >= list.length) {
int i = list.length;
FileChannel[] newList = new FileChannel[i + 1];
System.arraycopy(list, 0, newList, 0, i);
FilePath f = file.getBase(i);
newList[i] = f.open(mode);
list = newList;
}
return list[id];
}
@Override
public FileChannel truncate(long newLength) throws IOException {
if (newLength >= length) {
return this;
}
filePointer = Math.min(filePointer, newLength);
int newFileCount = 1 + (int) (newLength / maxLength);
if (newFileCount < list.length) {
// delete some of the files
FileChannel[] newList = new FileChannel[newFileCount];
// delete backwards, so that truncating is somewhat transactional
for (int i = list.length - 1; i >= newFileCount; i--) {
// verify the file is writable
list[i].truncate(0);
list[i].close();
try {
file.getBase(i).delete();
} catch (DbException e) {
throw DbException.convertToIOException(e);
}
}
System.arraycopy(list, 0, newList, 0, newList.length);
list = newList;
}
long size = newLength - maxLength * (newFileCount - 1);
list[list.length - 1].truncate(size);
this.length = newLength;
return this;
}
@Override
public void force(boolean metaData) throws IOException {
for (FileChannel c : list) {
c.force(metaData);
}
}
@Override
public int write(ByteBuffer src, long position) throws IOException {
if (position >= length && position > maxLength) {
// may need to extend and create files
long oldFilePointer = position;
long x = length - (length % maxLength) + maxLength;
for (; x < position; x += maxLength) {
if (x > length) {
// expand the file size
position(x - 1);
write(ByteBuffer.wrap(new byte[1]));
}
position = oldFilePointer;
}
}
long offset = position % maxLength;
int len = src.remaining();
FileChannel channel = getFileChannel(position);
int l = (int) Math.min(len, maxLength - offset);
if (l == len) {
l = channel.write(src, offset);
} else {
int oldLimit = src.limit();
src.limit(src.position() + l);
l = channel.write(src, offset);
src.limit(oldLimit);
}
length = Math.max(length, position + l);
return l;
}
@Override
public int write(ByteBuffer src) throws IOException {
if (filePointer >= length && filePointer > maxLength) {
// may need to extend and create files
long oldFilePointer = filePointer;
long x = length - (length % maxLength) + maxLength;
for (; x < filePointer; x += maxLength) {
if (x > length) {
// expand the file size
position(x - 1);
write(ByteBuffer.wrap(new byte[1]));
}
filePointer = oldFilePointer;
}
}
long offset = filePointer % maxLength;
int len = src.remaining();
FileChannel channel = getFileChannel(filePointer);
channel.position(offset);
int l = (int) Math.min(len, maxLength - offset);
if (l == len) {
l = channel.write(src);
} else {
int oldLimit = src.limit();
src.limit(src.position() + l);
l = channel.write(src);
src.limit(oldLimit);
}
filePointer += l;
length = Math.max(length, filePointer);
return l;
}
@Override
public synchronized FileLock tryLock(long position, long size,
boolean shared) throws IOException {
return list[0].tryLock(position, size, shared);
}
@Override
public String toString() {
return file.toString();
}
}