summaryrefslogtreecommitdiff
path: root/src/main/java/de/waldheinz/fs/fat/FatFile.java
blob: 95942e06af275913aeeeef852ebab728351e4f21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
 * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
 *
 * 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 de.waldheinz.fs.fat;

import de.waldheinz.fs.AbstractFsObject;
import java.io.IOException;
import de.waldheinz.fs.FsFile;
import de.waldheinz.fs.ReadOnlyException;
import java.io.EOFException;
import java.nio.ByteBuffer;

/**
 * The in-memory representation of a single file (chain of clusters) on a
 * FAT file system.
 * 
 * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
 * @since 0.6
 */
public final class FatFile extends AbstractFsObject implements FsFile {
    private final FatDirectoryEntry entry;
    private final ClusterChain chain;
    
    private FatFile(FatDirectoryEntry myEntry, ClusterChain chain) {
        super(myEntry.isReadOnly());
        
        this.entry = myEntry;
        this.chain = chain;
    }
    
    static FatFile get(Fat fat, FatDirectoryEntry entry)
            throws IOException {
        
        if (entry.isDirectory())
            throw new IllegalArgumentException(entry + " is a directory");
            
        final ClusterChain cc = new ClusterChain(
                fat, entry.getStartCluster(), entry.isReadonlyFlag());
                
        if (entry.getLength() > cc.getLengthOnDisk()) throw new IOException(
                "entry is larger than associated cluster chain");
                
        return new FatFile(entry, cc);
    }
    
    /**
     * Returns the length of this file in bytes. This is the length that
     * is stored in the directory entry that is associated with this file.
     * 
     * @return long the length that is recorded for this file
     */
    @Override
    public long getLength() {
        checkValid();
        
        return entry.getLength();
    }
    
    /**
     * Sets the size (in bytes) of this file. Because
     * {@link #write(long, java.nio.ByteBuffer) writing} to the file will grow
     * it automatically if needed, this method is mainly usefull for truncating
     * a file. 
     *
     * @param length the new length of the file in bytes
     * @throws ReadOnlyException if this file is read-only
     * @throws IOException on error updating the file size
     */
    @Override
    public void setLength(long length) throws ReadOnlyException, IOException {
        checkWritable();
        
        if (getLength() == length) return;
        
        updateTimeStamps(true);
        chain.setSize(length);
        
        this.entry.setStartCluster(chain.getStartCluster());
        this.entry.setLength(length);
    }
    
    /**
     * <p>
     * {@inheritDoc}
     * </p><p>
     * Unless this file is {@link #isReadOnly() read-ony}, this method also
     * updates the "last accessed" field in the directory entry that is
     * associated with this file.
     * </p>
     * 
     * @param offset {@inheritDoc}
     * @param dest {@inheritDoc}
     * @see FatDirectoryEntry#setLastAccessed(long)
     */
    @Override
    public void read(long offset, ByteBuffer dest) throws IOException {
        checkValid();
        
        final int len = dest.remaining();
        
        if (len == 0) return;
        
        if (offset + len > getLength()) {
            throw new EOFException();
        }
        
        if (!isReadOnly()) {
            updateTimeStamps(false);
        }
        
        chain.readData(offset, dest);
    }

    /**
     * <p>
     * {@inheritDoc}
     * </p><p>
     * If the data to be written extends beyond the current
     * {@link #getLength() length} of this file, an attempt is made to
     * {@link #setLength(long) grow} the file so that the data will fit.
     * Additionally, this method updates the "last accessed" and "last modified"
     * fields on the directory entry that is associated with this file.
     * </p>
     *
     * @param offset {@inheritDoc}
     * @param srcBuf {@inheritDoc}
     */
    @Override
    public void write(long offset, ByteBuffer srcBuf)
            throws ReadOnlyException, IOException {

        checkWritable();

        updateTimeStamps(true);
        
        final long lastByte = offset + srcBuf.remaining();

        if (lastByte > getLength()) {
            setLength(lastByte);
        }
        
        chain.writeData(offset, srcBuf);
    }
    
    private void updateTimeStamps(boolean write) {
        final long now = System.currentTimeMillis();
        entry.setLastAccessed(now);
        
        if (write) {
            entry.setLastModified(now);
        }
    }

    /**
     * Has no effect besides possibly throwing an {@code ReadOnlyException}. To
     * make sure that all data is written out to disk use the
     * {@link FatFileSystem#flush()} method.
     *
     * @throws ReadOnlyException if this {@code FatFile} is read-only
     */
    @Override
    public void flush() throws ReadOnlyException {
        checkWritable();
        
        /* nothing else to do */
    }
    
    /**
     * Returns the {@code ClusterChain} that holds the contents of
     * this {@code FatFile}.
     *
     * @return the file's {@code ClusterChain}
     */
    ClusterChain getChain() {
        checkValid();
        
        return chain;
    }
    
    /**
     * Returns a human-readable string representation of this {@code FatFile},
     * mainly for debugging purposes.
     *
     * @return a string describing this {@code FatFile}
     */
    @Override
    public String toString() {
        return getClass().getSimpleName() + " [length=" + getLength() +
                ", first cluster=" + chain.getStartCluster() + "]";
    }
    
}