/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.fs.ntfs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.jnode.fs.FSFile;
import org.jnode.fs.FSFileSlackSpace;
import org.jnode.fs.FSFileStreams;
import org.jnode.fs.FileSystem;
import org.jnode.fs.ntfs.attribute.NTFSAttribute;
import org.jnode.fs.ntfs.index.IndexEntry;
import org.jnode.util.ByteBufferUtils;
/**
* @author vali
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
public class NTFSFile implements FSFile, FSFileSlackSpace, FSFileStreams {
/**
* The associated file record.
*/
private FileRecord fileRecord;
/**
* The file system that contains this file.
*/
private NTFSFileSystem fs;
private IndexEntry indexEntry;
/**
* Initialize this instance.
*
* @param fs the file system.
* @param indexEntry the index entry.
*/
public NTFSFile(NTFSFileSystem fs, IndexEntry indexEntry) {
this.fs = fs;
this.indexEntry = indexEntry;
}
/**
* Initialize this instance.
*
* @param fs the file system.
* @param fileRecord the file record.
*/
public NTFSFile(NTFSFileSystem fs, FileRecord fileRecord) {
this.fs = fs;
this.fileRecord = fileRecord;
}
@Override
public long getLength() {
FileRecord.AttributeIterator attributes =
getFileRecord().findAttributesByTypeAndName(NTFSAttribute.Types.DATA, null);
NTFSAttribute attribute = attributes.next();
if (attribute == null && indexEntry != null) {
// Fall back to the size stored in the index entry if the data attribute is not present (even possible??)
return indexEntry.getRealFileSize();
}
return getFileRecord().getAttributeTotalSize(NTFSAttribute.Types.DATA, null);
}
/*
* (non-Javadoc)
* @see org.jnode.fs.FSFile#setLength(long)
*/
public void setLength(long length) {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see org.jnode.fs.FSFile#read(long, byte[], int, int)
*/
// public void read(long fileOffset, byte[] dest, int off, int len)
public void read(long fileOffset, ByteBuffer destBuf) throws IOException {
// TODO optimize it also to use ByteBuffer at lower level
final ByteBufferUtils.ByteArray destBA = ByteBufferUtils.toByteArray(destBuf);
final byte[] dest = destBA.toArray();
getFileRecord().readData(fileOffset, dest, 0, dest.length);
destBA.refreshByteBuffer();
}
/*
* (non-Javadoc)
* @see org.jnode.fs.FSFile#write(long, byte[], int, int)
*/
// public void write(long fileOffset, byte[] src, int off, int len) {
public void write(long fileOffset, ByteBuffer src) {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
* @see org.jnode.fs.FSObject#isValid()
*/
public boolean isValid() {
return true;
}
/*
* (non-Javadoc)
* @see org.jnode.fs.FSObject#getFileSystem()
*/
public FileSystem<?> getFileSystem() {
return fs;
}
/**
* @return Returns the fileRecord.
*/
public FileRecord getFileRecord() {
if (fileRecord == null) {
try {
fileRecord = indexEntry.getParentFileRecord().getVolume().getMFT().getIndexedFileRecord(indexEntry);
} catch (IOException e) {
e.printStackTrace();
}
}
return this.fileRecord;
}
/**
* @param fileRecord The fileRecord to set.
*/
public void setFileRecord(FileRecord fileRecord) {
this.fileRecord = fileRecord;
}
@Override
public byte[] getSlackSpace() throws IOException {
FileRecord.AttributeIterator dataAttributes = getFileRecord().findAttributesByTypeAndName(
NTFSAttribute.Types.DATA, null);
NTFSAttribute attribute = dataAttributes.next();
if (attribute == null || attribute.isResident()) {
// If the data attribute is missing there is no slack space. If it is resident then another attribute might
// immediately follow the data. So for now we'll ignore that case
return new byte[0];
}
int clusterSize = ((NTFSFileSystem) getFileSystem()).getNTFSVolume().getClusterSize();
int slackSpaceSize = clusterSize - (int) (getLength() % clusterSize);
if (slackSpaceSize == clusterSize) {
slackSpaceSize = 0;
}
byte[] slackSpace = new byte[slackSpaceSize];
getFileRecord().readData(getLength(), slackSpace, 0, slackSpace.length);
return slackSpace;
}
/**
* Flush any cached data to the disk.
*
* @throws IOException
*/
public void flush() throws IOException {
// TODO implement me
}
@Override
public Map<String, FSFile> getStreams() {
Set<String> streamNames = new LinkedHashSet<String>();
FileRecord.AttributeIterator dataAttributes = getFileRecord().findAttributesByType(NTFSAttribute.Types.DATA);
NTFSAttribute attribute = dataAttributes.next();
while (attribute != null) {
String attributeName = attribute.getAttributeName();
// The unnamed data attribute is the main file data, so ignore it
if (attributeName != null) {
streamNames.add(attributeName);
}
attribute = dataAttributes.next();
}
Map<String, FSFile> streams = new HashMap<String, FSFile>();
for (String streamName : streamNames) {
streams.put(streamName, new StreamFile(streamName));
}
return streams;
}
/**
* A file for reading data out of alternate streams.
*/
public class StreamFile implements FSFile {
/**
* The name of the alternate data stream.
*/
private final String attributeName;
/**
* Creates a new stream file.
*
* @param attributeName the name of the alternate data stream.
*/
public StreamFile(String attributeName) {
this.attributeName = attributeName;
}
/**
* Gets the name of this stream.
*
* @return the stream name.
*/
public String getStreamName() {
return attributeName;
}
/**
* Gets the associated file record.
*
* @return the file record.
*/
public FileRecord getFileRecord() {
return NTFSFile.this.getFileRecord();
}
@Override
public long getLength() {
return NTFSFile.this.getFileRecord().getAttributeTotalSize(NTFSAttribute.Types.DATA, attributeName);
}
@Override
public void setLength(long length) throws IOException {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void read(long fileOffset, ByteBuffer dest) throws IOException {
ByteBufferUtils.ByteArray destByteArray = ByteBufferUtils.toByteArray(dest);
byte[] destBuffer = destByteArray.toArray();
if (fileOffset + destBuffer.length > getLength()) {
throw new IOException("Attempt to read past the end of stream, offset: " + fileOffset);
}
getFileRecord().readData(attributeName, fileOffset, destBuffer, 0, destBuffer.length);
destByteArray.refreshByteBuffer();
}
@Override
public void write(long fileOffset, ByteBuffer src) throws IOException {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void flush() throws IOException {
}
@Override
public boolean isValid() {
return true;
}
@Override
public FileSystem<?> getFileSystem() {
return NTFSFile.this.getFileSystem();
}
}
}