1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2013 - 2016
18   *
19   */
20  package org.crosswire.jsword.book.sword.state;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  
27  import org.crosswire.common.util.IOUtil;
28  import org.crosswire.jsword.book.BookException;
29  import org.crosswire.jsword.book.BookMetaData;
30  import org.crosswire.jsword.book.sword.SwordUtil;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * Stores the random access files required for processing the passage request
36   * 
37   * The caller is required to close to correctly free resources and avoid File
38   * pointer leaks
39   * 
40   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
41   * @author DM Smith
42   */
43  public class RawFileBackendState extends RawBackendState {
44      /**
45       * This is default package access for forcing the use of the
46       * OpenFileStateManager to manage the creation. Not doing so may result in
47       * new instances of OpenFileState being created for no reason, and as a
48       * result, if they are released to the OpenFileStateManager by mistake this
49       * would result in leakage
50       * 
51       * @param bookMetaData
52       *            the appropriate metadata for the book
53       */
54      RawFileBackendState(BookMetaData bookMetaData) throws BookException {
55          super(bookMetaData);
56          incfileValue = -1;
57      }
58  
59      /* (non-Javadoc)
60       * @see org.crosswire.jsword.book.sword.AbstractBackend#isWritable()
61       */
62      @Override
63      public boolean isWritable() {
64          File incFile = getIncfile();
65  
66          if (existsAndCanReadAndWrite(otTextFile)
67                  && existsAndCanReadAndWrite(ntTextFile)
68                  && existsAndCanReadAndWrite(otIdxFile)
69                  && existsAndCanReadAndWrite(otTextFile)
70                  && (incFile == null || existsAndCanReadAndWrite(incFile)))
71          {
72              return true;
73          }
74          return false;
75      }
76  
77      /**
78       * Returns true if the file exists, can be read and can be written to.
79       *
80       * @param file the file
81       * @return true, if successful
82       */
83      private boolean existsAndCanReadAndWrite(File file) {
84          return file.exists() && file.canRead() && file.canWrite();
85      }
86  
87      private int readIncfile() throws IOException {
88          int ret = -1;
89  
90          if (incfile == null) {
91              // then attempt to initialise it
92              initIncFile();
93          }
94  
95          if (this.incfile != null) {
96              FileInputStream fis = null;
97              try {
98                  fis = new FileInputStream(this.incfile);
99                  byte[] buffer = new byte[4];
100                 if (fis.read(buffer) != 4) {
101                     LOGGER.error("Read data is not of appropriate size of 4 bytes!");
102                     throw new IOException("Incfile is not 4 bytes long");
103                 }
104                 ret = SwordUtil.decodeLittleEndian32(buffer, 0);
105 
106                 // also store this
107                 this.incfileValue = ret;
108             } catch (FileNotFoundException e) {
109                 LOGGER.error("Error on writing to incfile, file should exist already!: {}", e.getMessage(), e);
110             } finally {
111                 IOUtil.close(fis);
112             }
113         }
114 
115         return ret;
116     }
117 
118     private void initIncFile() {
119         try {
120             File tempIncfile = new File(SwordUtil.getExpandedDataPath(getBookMetaData()).getPath() + File.separator + INCFILE);
121             if (tempIncfile.exists()) {
122                 this.incfile = tempIncfile;
123             }
124         } catch (BookException e) {
125             LOGGER.error("Error on checking incfile: {}", e.getMessage(), e);
126             this.incfile = null;
127         }
128     }
129 
130     /**
131      * @return the incfileValue
132      */
133     public int getIncfileValue() {
134         if (incfileValue == -1) {
135             try {
136                 readIncfile();
137             } catch (IOException e) {
138                 LOGGER.error("IO Error: {}", e.getMessage(), e);
139             }
140         }
141 
142         return incfileValue;
143     }
144 
145     public void setIncfileValue(int incValue) {
146         this.incfileValue = incValue;
147     }
148 
149     /**
150      * @return the incfile
151      */
152     public File getIncfile() {
153         if (incfile == null) {
154             initIncFile();
155         }
156         return incfile;
157     }
158 
159     /**
160      * @param incfile
161      *            the incfile to set
162      */
163     public void setIncfile(File incfile) {
164         this.incfile = incfile;
165     }
166 
167     public static final String INCFILE = "incfile";
168     private File incfile;
169     private int incfileValue;
170 
171     /**
172      * The log stream
173      */
174     private static final Logger LOGGER = LoggerFactory.getLogger(RawFileBackendState.class);
175 }
176