1
22 package org.crosswire.jsword.book.sword;
23
24 import java.io.BufferedInputStream;
25 import java.io.BufferedOutputStream;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.RandomAccessFile;
32
33 import org.crosswire.common.util.Logger;
34 import org.crosswire.jsword.book.BookException;
35 import org.crosswire.jsword.passage.Key;
36 import org.crosswire.jsword.passage.KeyUtil;
37 import org.crosswire.jsword.passage.Verse;
38 import org.crosswire.jsword.versification.Versification;
39 import org.crosswire.jsword.versification.Testament;
40 import org.crosswire.jsword.versification.system.Versifications;
41
42
70 public class RawFileBackend extends RawBackend {
71
72 public RawFileBackend(SwordBookMetaData sbmd, int datasize) {
73 super(sbmd, datasize);
74
75 initIncFile();
76 try {
77 incfileValue = readIncfile();
78 } catch (IOException e) {
79 log.error("Error on reading incfile!");
80 }
81 }
82
83
86 @Override
87 public String getRawText(Key key) throws BookException {
88 return super.getRawText(key);
89 }
90
91
94 @Override
95 protected String getEntry(String name, Testament testament, long index) throws IOException {
96 RandomAccessFile idxRaf = otIdxRaf;
97 RandomAccessFile txtRaf = otTxtRaf;
98 if (testament == Testament.NEW) {
99 idxRaf = ntIdxRaf;
100 txtRaf = ntTxtRaf;
101 }
102
103 DataIndex dataIndex = getIndex(idxRaf, index);
104 int size = dataIndex.getSize();
105 if (size == 0) {
106 return "";
107 }
108
109 if (size < 0) {
110 log.error("In " + getBookMetaData().getInitials() + ": Verse " + name + " has a bad index size of " + size);
111 return "";
112 }
113
114 try {
115 File dataFile = getDataTextFile(txtRaf, dataIndex);
116 byte[] textBytes = readTextDataFile(dataFile);
117 decipher(textBytes);
118 return SwordUtil.decode(name, textBytes, getBookMetaData().getBookCharset());
119 } catch (BookException e) {
120 throw new IOException(e.getMessage());
121 }
122 }
123
124
127 @Override
128 public void setRawText(Key key, String text) throws BookException, IOException {
129 checkActive();
130
131 Verse verse = KeyUtil.getVerse(key);
132 String v11nName = getBookMetaData().getProperty(ConfigEntryType.VERSIFICATION).toString();
133 Versification v11n = Versifications.instance().getVersification(v11nName);
134 int index = v11n.getOrdinal(verse);
135 Testament testament = v11n.getTestament(index);
136 index = v11n.getTestamentOrdinal(index);
137 RandomAccessFile idxRaf = otIdxRaf;
138 RandomAccessFile txtRaf = otTxtRaf;
139 File txtFile = otTxtFile;
140 if (testament == Testament.NEW) {
141 idxRaf = ntIdxRaf;
142 txtRaf = ntTxtRaf;
143 txtFile = ntTxtFile;
144 }
145
146 DataIndex dataIndex = getIndex(idxRaf, index);
147 File dataFile;
148 if (dataIndex.getSize() == 0) {
149 dataFile = createDataTextFile(incfileValue);
150 updateIndexFile(idxRaf, index, txtRaf.length());
151 updateDataFile(incfileValue, txtFile);
152 checkAndIncrementIncfile(incfileValue);
153 } else {
154 dataFile = getDataTextFile(txtRaf, dataIndex);
155 }
156
157 byte[] textData = text.getBytes("UTF-8");
158 encipher(textData);
159 writeTextDataFile(dataFile, textData);
160 }
161
162 @Override
163 public void setAliasKey(Key alias, Key source) throws IOException {
164 Verse aliasVerse = KeyUtil.getVerse(alias);
165 Verse sourceVerse = KeyUtil.getVerse(source);
166 String v11nName = getBookMetaData().getProperty(ConfigEntryType.VERSIFICATION).toString();
167 Versification v11n = Versifications.instance().getVersification(v11nName);
168 int aliasIndex = v11n.getOrdinal(aliasVerse);
169 Testament testament = v11n.getTestament(aliasIndex);
170 aliasIndex = v11n.getTestamentOrdinal(aliasIndex);
171 RandomAccessFile idxRaf = otIdxRaf;
172 if (testament == Testament.NEW) {
173 idxRaf = ntIdxRaf;
174 }
175
176 int sourceOIndex = v11n.getOrdinal(sourceVerse);
177 sourceOIndex = v11n.getTestamentOrdinal(sourceOIndex);
178 DataIndex dataIndex = getIndex(idxRaf, sourceOIndex);
179
180 updateIndexFile(idxRaf, aliasIndex, dataIndex.getOffset());
182 }
183
184 private void initIncFile() {
185 try {
186 File tempIncfile = new File(getExpandedDataPath().getPath() + File.separator + INCFILE);
187 if (tempIncfile.exists()) {
188 this.incfile = tempIncfile;
189 }
190 } catch (BookException e) {
191 log.error("Error on checking incfile: " + e.getMessage());
192 }
193 }
194
195 private File createDataTextFile(int index) throws BookException, IOException {
196 String dataPath = getExpandedDataPath().getPath();
197 dataPath += File.separator + String.format("%07d", Integer.valueOf(index));
198 File dataFile = new File(dataPath);
199 if (!dataFile.exists() && !dataFile.createNewFile()) {
200 throw new IOException("Could not create data file.");
201 }
202 return dataFile;
203 }
204
205
213 private String getTextFilename(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException {
214 byte[] data = SwordUtil.readRAF(txtRaf, dataIndex.getOffset(), dataIndex.getSize());
217 decipher(data);
218 if (data.length == 7) {
219 return new String(data, 0, 7);
220 }
221 log.error("Read data is not of appropriate size of 9 bytes!");
222 throw new IOException("Datalength is not 9 bytes!");
223 }
224
225
234 private File getDataTextFile(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException, BookException {
235 String dataFilename = getTextFilename(txtRaf, dataIndex);
236 String dataPath = getExpandedDataPath().getPath() + File.separator + dataFilename;
237 return new File(dataPath);
238 }
239
240
241 protected void updateIndexFile(RandomAccessFile idxRaf, long index, long dataFileStartPosition) throws IOException {
242 long indexFileWriteOffset = index * entrysize;
243 int dataFileLengthValue = 7; byte[] startPositionData = littleEndian32BitByteArrayFromInt((int) dataFileStartPosition);
245 byte[] lengthValueData = littleEndian16BitByteArrayFromShort((short) dataFileLengthValue);
246 byte[] indexFileWriteData = new byte[6];
247
248 indexFileWriteData[0] = startPositionData[0];
249 indexFileWriteData[1] = startPositionData[1];
250 indexFileWriteData[2] = startPositionData[2];
251 indexFileWriteData[3] = startPositionData[3];
252 indexFileWriteData[4] = lengthValueData[0];
253 indexFileWriteData[5] = lengthValueData[1];
254
255 SwordUtil.writeRAF(idxRaf, indexFileWriteOffset, indexFileWriteData);
256 }
257
258 protected void updateDataFile(long ordinal, File txtFile) throws IOException {
259 String fileName = String.format("%07d\r\n", Long.valueOf(ordinal));
260 BufferedOutputStream bos = null;
261 try {
262 bos = new BufferedOutputStream(new FileOutputStream(txtFile, true));
263 bos.write(fileName.getBytes(getBookMetaData().getBookCharset()));
264 } finally {
265 if (bos != null) {
266 bos.close();
267 }
268 }
269 }
270
271 private void checkAndIncrementIncfile(int index) throws IOException {
272 if (index >= this.incfileValue) {
273 this.incfileValue = index + 1;
274 writeIncfile(this.incfileValue);
275 }
276 }
277
278
283 @Override
284 public void create() throws IOException, BookException {
285 super.create();
286 createDataFiles();
287 createIndexFiles();
288 createIncfile();
289
290 checkActive();
291
292 prepopulateIndexFiles();
293 prepopulateIncfile();
294 }
295
296
299 @Override
300 public boolean isWritable() {
301 File incFile = this.incfile;
302
303 if (otTxtFile.exists() && otTxtFile.canRead() && otTxtFile.canWrite() && ntTxtFile.exists() && ntTxtFile.canRead() && ntTxtFile.canWrite()
304 && otIdxFile.exists() && otIdxFile.canRead() && otIdxFile.canWrite() && ntIdxFile.exists() && ntIdxFile.canRead()
305 && ntIdxFile.canWrite() && incFile.exists() && incFile.canRead() && incFile.canWrite())
306 {
307 return true;
308 }
309 return false;
310 }
311
312 private void createDataFiles() throws IOException, BookException {
313 String path = getExpandedDataPath().getPath();
314
315 File otTextFile = new File(path + File.separator + SwordConstants.FILE_OT);
316 if (!otTextFile.exists() && !otTextFile.createNewFile()) {
317 throw new IOException("Could not create ot text file.");
318 }
319
320 File ntTextFile = new File(path + File.separator + SwordConstants.FILE_NT);
321 if (!ntTextFile.exists() && !ntTextFile.createNewFile()) {
322 throw new IOException("Could not create nt text file.");
323 }
324 }
325
326 private void createIndexFiles() throws IOException, BookException {
327 String path = getExpandedDataPath().getPath();
328 File otIndexFile = new File(path + File.separator + SwordConstants.FILE_OT + SwordConstants.EXTENSION_VSS);
329 if (!otIndexFile.exists() && !otIndexFile.createNewFile()) {
330 throw new IOException("Could not create ot index file.");
331 }
332
333 File ntIndexFile = new File(path + File.separator + SwordConstants.FILE_NT + SwordConstants.EXTENSION_VSS);
334 if (!ntIndexFile.exists() && !ntIndexFile.createNewFile()) {
335 throw new IOException("Could not create nt index file.");
336 }
337 }
338
339 private void prepopulateIndexFiles() throws IOException {
340 String v11nName = getBookMetaData().getProperty(ConfigEntryType.VERSIFICATION).toString();
341 Versification v11n = Versifications.instance().getVersification(v11nName);
342 int otCount = v11n.getCount(Testament.OLD);
343 int ntCount = v11n.getCount(Testament.NEW) + 1;
344 BufferedOutputStream otIdxBos = new BufferedOutputStream(new FileOutputStream(otIdxFile, false));
345 try {
346 for (int i = 0; i < otCount; i++) {
347 writeInitialIndex(otIdxBos);
348 }
349 } finally {
350 otIdxBos.close();
351 }
352
353 BufferedOutputStream ntIdxBos = new BufferedOutputStream(new FileOutputStream(ntIdxFile, false));
354 try {
355 for (int i = 0; i < ntCount; i++) {
356 writeInitialIndex(ntIdxBos);
357 }
358 } finally {
359 ntIdxBos.close();
360 }
361 }
362
363 private void createIncfile() throws IOException, BookException {
364 File tempIncfile = new File(getExpandedDataPath().getPath() + File.separator + INCFILE);
365 if (!tempIncfile.exists() && !tempIncfile.createNewFile()) {
366 throw new IOException("Could not create incfile file.");
367 }
368 this.incfile = tempIncfile;
369 }
370
371 private void prepopulateIncfile() throws IOException {
372 writeIncfile(1);
373 }
374
375 private void writeIncfile(int value) throws IOException {
376 FileOutputStream fos = null;
377 try {
378 fos = new FileOutputStream(this.incfile, false);
379 fos.write(littleEndian32BitByteArrayFromInt(value));
380 } catch (FileNotFoundException e) {
381 log.error("Error on writing to incfile, file should exist already!");
382 log.error(e.getMessage());
383 } finally {
384 if (fos != null) {
385 fos.close();
386 }
387 }
388 }
389
390 private int readIncfile() throws IOException {
391 int ret = -1;
392 if (this.incfile != null) {
393 FileInputStream fis = null;
394 try {
395 fis = new FileInputStream(this.incfile);
396 byte[] buffer = new byte[4];
397 if (fis.read(buffer) != 4) {
398 log.error("Read data is not of appropriate size of 4 bytes!");
399 throw new IOException("Incfile is not 4 bytes long");
400 }
401 ret = SwordUtil.decodeLittleEndian32(buffer, 0);
402 } catch (FileNotFoundException e) {
403 log.error("Error on writing to incfile, file should exist already!");
404 log.error(e.getMessage());
405 } finally {
406 if (fis != null) {
407 fis.close();
408 }
409 }
410 }
411
412 return ret;
413 }
414
415 private void writeInitialIndex(BufferedOutputStream outStream) throws IOException {
416 outStream.write(littleEndian32BitByteArrayFromInt(0)); outStream.write(littleEndian16BitByteArrayFromShort((short) 0)); }
419
420 private byte[] readTextDataFile(File dataFile) throws IOException {
421 BufferedInputStream inStream = null;
422 try {
423 int len = (int) dataFile.length();
424 byte[] textData = new byte[len];
425 inStream = new BufferedInputStream(new FileInputStream(dataFile));
426 if (inStream.read(textData) != len) {
427 log.error("Read data is not of appropriate size of " + len + " bytes!");
428 throw new IOException("data is not " + len + " bytes long");
429 }
430 return textData;
431 } catch (FileNotFoundException ex) {
432 log.error(ex.getMessage());
433 throw new IOException("Could not read text data file, file not found: " + dataFile.getName());
434 } finally {
435 if (inStream != null) {
436 inStream.close();
437 }
438 }
439 }
440
441 private void writeTextDataFile(File dataFile, byte[] textData) throws IOException {
442 BufferedOutputStream bos = null;
443 try {
444 bos = new BufferedOutputStream(new FileOutputStream(dataFile, false));
445 bos.write(textData);
446 } finally {
447 if (bos != null) {
448 bos.close();
449 }
450 }
451 }
452
453 private byte[] littleEndian32BitByteArrayFromInt(int val) {
454 byte[] buffer = new byte[4];
455 SwordUtil.encodeLittleEndian32(val, buffer, 0);
456 return buffer;
457 }
458
459 private byte[] littleEndian16BitByteArrayFromShort(short val) {
460 byte[] buffer = new byte[2];
461 SwordUtil.encodeLittleEndian16(val, buffer, 0);
462 return buffer;
463 }
464
465 private static final String INCFILE = "incfile";
466
467 private File incfile;
468 private int incfileValue;
469
470 private static final Logger log = Logger.getLogger(RawFileBackend.class);
471 }
472