1
20 package org.crosswire.jsword.book.sword;
21
22 import java.io.BufferedInputStream;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.RandomAccessFile;
30
31 import org.crosswire.jsword.book.BookException;
32 import org.crosswire.jsword.book.BookMetaData;
33 import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
34 import org.crosswire.jsword.book.sword.state.RawBackendState;
35 import org.crosswire.jsword.book.sword.state.RawFileBackendState;
36 import org.crosswire.jsword.passage.Key;
37 import org.crosswire.jsword.passage.KeyUtil;
38 import org.crosswire.jsword.passage.Verse;
39 import org.crosswire.jsword.versification.Testament;
40 import org.crosswire.jsword.versification.Versification;
41 import org.crosswire.jsword.versification.system.Versifications;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
68 public class RawFileBackend extends RawBackend<RawFileBackendState> {
69
70 public RawFileBackend(SwordBookMetaData sbmd, int datasize) {
71 super(sbmd, datasize);
72 }
73
74 @Override
75 public RawFileBackendState initState() throws BookException {
76 return OpenFileStateManager.instance().getRawFileBackendState(getBookMetaData());
77 }
78
79
82 @Override
83 protected String getEntry(RawBackendState state, String name, Testament testament, long index) throws IOException {
84 RandomAccessFile idxRaf;
85 RandomAccessFile txtRaf;
86 idxRaf = state.getIdxRaf(testament);
87 txtRaf = state.getTextRaf(testament);
88
89 DataIndex dataIndex = getIndex(idxRaf, index);
90 int size = dataIndex.getSize();
91 if (size == 0) {
92 return "";
93 }
94
95 if (size < 0) {
96 log.error("In {}: Verse {} has a bad index size of {}.", getBookMetaData().getInitials(), name, Integer.toString(size));
97 return "";
98 }
99
100 try {
101 File dataFile = getDataTextFile(txtRaf, dataIndex);
102 byte[] textBytes = readTextDataFile(dataFile);
103 decipher(textBytes);
104 return SwordUtil.decode(name, textBytes, getBookMetaData().getBookCharset());
105 } catch (BookException e) {
106 throw new IOException(e.getMessage());
107 }
108 }
109
110
114 public void setRawText(RawFileBackendState state, Key key, String text) throws BookException, IOException {
115
116 String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
117 Versification v11n = Versifications.instance().getVersification(v11nName);
118 Verse verse = KeyUtil.getVerse(key);
119 int index = verse.getOrdinal();
120 Testament testament = v11n.getTestament(index);
121 index = v11n.getTestamentOrdinal(index);
122
123 RandomAccessFile idxRaf = state.getIdxRaf(testament);
124 RandomAccessFile txtRaf = state.getTextRaf(testament);
125 File txtFile = state.getTextFile(testament);
126
127 DataIndex dataIndex = getIndex(idxRaf, index);
128 File dataFile;
129 if (dataIndex.getSize() == 0) {
130 dataFile = createDataTextFile(state.getIncfileValue());
131 updateIndexFile(idxRaf, index, txtRaf.length());
132 updateDataFile(state.getIncfileValue(), txtFile);
133 checkAndIncrementIncfile(state, state.getIncfileValue());
134 } else {
135 dataFile = getDataTextFile(txtRaf, dataIndex);
136 }
137
138 byte[] textData = text.getBytes("UTF-8");
139 encipher(textData);
140 writeTextDataFile(dataFile, textData);
141 }
142
143 public void setAliasKey(RawFileBackendState state, Key alias, Key source) throws IOException {
144 String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
145 Versification v11n = Versifications.instance().getVersification(v11nName);
146 Verse aliasVerse = KeyUtil.getVerse(alias);
147 Verse sourceVerse = KeyUtil.getVerse(source);
148 int aliasIndex = aliasVerse.getOrdinal();
149 Testament testament = v11n.getTestament(aliasIndex);
150 aliasIndex = v11n.getTestamentOrdinal(aliasIndex);
151 RandomAccessFile idxRaf = state.getIdxRaf(testament);
152
153 int sourceOIndex = sourceVerse.getOrdinal();
154 sourceOIndex = v11n.getTestamentOrdinal(sourceOIndex);
155 DataIndex dataIndex = getIndex(idxRaf, sourceOIndex);
156
157 updateIndexFile(idxRaf, aliasIndex, dataIndex.getOffset());
160 }
161
162 private File createDataTextFile(int index) throws BookException, IOException {
163 String dataPath = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
164 dataPath += File.separator + String.format("%07d", Integer.valueOf(index));
165 File dataFile = new File(dataPath);
166 if (!dataFile.exists() && !dataFile.createNewFile()) {
167 throw new IOException("Could not create data file.");
168 }
169 return dataFile;
170 }
171
172
183 private String getTextFilename(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException {
184 byte[] data = SwordUtil.readRAF(txtRaf, dataIndex.getOffset(), dataIndex.getSize());
187 decipher(data);
188 if (data.length == 7) {
189 return new String(data, 0, 7);
190 }
191 log.error("Read data is not of appropriate size of 9 bytes!");
192 throw new IOException("Datalength is not 9 bytes!");
193 }
194
195
207 private File getDataTextFile(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException, BookException {
208 String dataFilename = getTextFilename(txtRaf, dataIndex);
209 String dataPath = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath() + File.separator + dataFilename;
210 return new File(dataPath);
211 }
212
213 protected void updateIndexFile(RandomAccessFile idxRaf, long index, long dataFileStartPosition) throws IOException {
214 long indexFileWriteOffset = index * entrysize;
215 int dataFileLengthValue = 7; byte[] startPositionData = littleEndian32BitByteArrayFromInt((int) dataFileStartPosition);
217 byte[] lengthValueData = littleEndian16BitByteArrayFromShort((short) dataFileLengthValue);
218 byte[] indexFileWriteData = new byte[6];
219
220 indexFileWriteData[0] = startPositionData[0];
221 indexFileWriteData[1] = startPositionData[1];
222 indexFileWriteData[2] = startPositionData[2];
223 indexFileWriteData[3] = startPositionData[3];
224 indexFileWriteData[4] = lengthValueData[0];
225 indexFileWriteData[5] = lengthValueData[1];
226
227 SwordUtil.writeRAF(idxRaf, indexFileWriteOffset, indexFileWriteData);
228 }
229
230 protected void updateDataFile(long ordinal, File txtFile) throws IOException {
231 String fileName = String.format("%07d\r\n", Long.valueOf(ordinal));
232 BufferedOutputStream bos = null;
233 try {
234 bos = new BufferedOutputStream(new FileOutputStream(txtFile, true));
235 bos.write(fileName.getBytes(getBookMetaData().getBookCharset()));
236 } finally {
237 if (bos != null) {
238 bos.close();
239 }
240 }
241 }
242
243 private void checkAndIncrementIncfile(RawFileBackendState state, int index) throws IOException {
244 if (index >= state.getIncfileValue()) {
245 int incValue = index + 1;
246
247 state.setIncfileValue(incValue);
253 writeIncfile(state, incValue);
254 }
255 }
256
257
262 @Override
266 public void create() throws IOException, BookException {
267 super.create();
268
269 createDataFiles();
270 createIndexFiles();
271
272 RawFileBackendState state = null;
273 try {
274 state = initState();
275
276 createIncfile(state);
277
278 prepopulateIndexFiles(state);
279 prepopulateIncfile(state);
280 } finally {
281 OpenFileStateManager.instance().release(state);
282 }
283 }
284
285 private void createDataFiles() throws IOException, BookException {
286 String path = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
287
288 File otTextFile = new File(path + File.separator + SwordConstants.FILE_OT);
289 if (!otTextFile.exists() && !otTextFile.createNewFile()) {
290 throw new IOException("Could not create ot text file.");
291 }
292
293 File ntTextFile = new File(path + File.separator + SwordConstants.FILE_NT);
294 if (!ntTextFile.exists() && !ntTextFile.createNewFile()) {
295 throw new IOException("Could not create nt text file.");
296 }
297 }
298
299 private void createIndexFiles() throws IOException, BookException {
300 String path = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
301 File otIndexFile = new File(path + File.separator + SwordConstants.FILE_OT + SwordConstants.EXTENSION_VSS);
302 if (!otIndexFile.exists() && !otIndexFile.createNewFile()) {
303 throw new IOException("Could not create ot index file.");
304 }
305
306 File ntIndexFile = new File(path + File.separator + SwordConstants.FILE_NT + SwordConstants.EXTENSION_VSS);
307 if (!ntIndexFile.exists() && !ntIndexFile.createNewFile()) {
308 throw new IOException("Could not create nt index file.");
309 }
310 }
311
312 private void prepopulateIndexFiles(RawFileBackendState state) throws IOException {
313
314 String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
315 Versification v11n = Versifications.instance().getVersification(v11nName);
316 int otCount = v11n.getCount(Testament.OLD);
317 int ntCount = v11n.getCount(Testament.NEW) + 1;
318 BufferedOutputStream otIdxBos = new BufferedOutputStream(new FileOutputStream(state.getIdxFile(Testament.OLD), false));
319 try {
320 for (int i = 0; i < otCount; i++) {
321 writeInitialIndex(otIdxBos);
322 }
323 } finally {
324 otIdxBos.close();
325 }
326
327 BufferedOutputStream ntIdxBos = new BufferedOutputStream(new FileOutputStream(state.getIdxFile(Testament.NEW), false));
328 try {
329 for (int i = 0; i < ntCount; i++) {
330 writeInitialIndex(ntIdxBos);
331 }
332 } finally {
333 ntIdxBos.close();
334 }
335 }
336
337 private void createIncfile(RawFileBackendState state) throws IOException, BookException {
338 File tempIncfile = new File(SwordUtil.getExpandedDataPath(getBookMetaData()).getPath() + File.separator + RawFileBackendState.INCFILE);
339 if (!tempIncfile.exists() && !tempIncfile.createNewFile()) {
340 throw new IOException("Could not create incfile file.");
341 }
342 state.setIncfile(tempIncfile);
343 }
344
345 private void prepopulateIncfile(RawFileBackendState state) throws IOException {
346 writeIncfile(state, 1);
347 }
348
349 private void writeIncfile(RawFileBackendState state, int value) throws IOException {
350 FileOutputStream fos = null;
351 try {
352 fos = new FileOutputStream(state.getIncfile(), false);
353 fos.write(littleEndian32BitByteArrayFromInt(value));
354 } catch (FileNotFoundException e) {
355 log.error("Error on writing to incfile, file should exist already!", e);
356 } finally {
357 if (fos != null) {
358 fos.close();
359 }
360 }
361 }
362
363 private void writeInitialIndex(BufferedOutputStream outStream) throws IOException {
364 outStream.write(littleEndian32BitByteArrayFromInt(0)); outStream.write(littleEndian16BitByteArrayFromShort((short) 0)); }
367
368 private byte[] readTextDataFile(File dataFile) throws IOException {
369 BufferedInputStream inStream = null;
370 try {
371 int len = (int) dataFile.length();
372 byte[] textData = new byte[len];
373 inStream = new BufferedInputStream(new FileInputStream(dataFile));
374 if (inStream.read(textData) != len) {
375 log.error("Read data is not of appropriate size of {} bytes!", Integer.toString(len));
376 throw new IOException("data is not " + len + " bytes long");
377 }
378 return textData;
379 } catch (FileNotFoundException ex) {
380 log.error("Could not read text data file, file not found: {}", dataFile.getName(), ex);
381 throw ex;
382 } finally {
383 if (inStream != null) {
384 inStream.close();
385 }
386 }
387 }
388
389 private void writeTextDataFile(File dataFile, byte[] textData) throws IOException {
390 BufferedOutputStream bos = null;
391 try {
392 bos = new BufferedOutputStream(new FileOutputStream(dataFile, false));
393 bos.write(textData);
394 } finally {
395 if (bos != null) {
396 bos.close();
397 }
398 }
399 }
400
401 private byte[] littleEndian32BitByteArrayFromInt(int val) {
402 byte[] buffer = new byte[4];
403 SwordUtil.encodeLittleEndian32(val, buffer, 0);
404 return buffer;
405 }
406
407 private byte[] littleEndian16BitByteArrayFromShort(short val) {
408 byte[] buffer = new byte[2];
409 SwordUtil.encodeLittleEndian16(val, buffer, 0);
410 return buffer;
411 }
412
413 private static final Logger log = LoggerFactory.getLogger(RawFileBackend.class);
414 }
415