/**
* NanoDoA - File based document archive
*
* Copyright (C) 2011-2012 Christian Packenius, christian.packenius@googlemail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.chris_soft.nanoarchive;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Properties;
import de.chris_soft.utilities.FileUtils;
import de.chris_soft.utilities.FulltextIndexAndSearchUtils;
import de.chris_soft.utilities.LogUtils;
/**
* Archive system that stores documentes as files in the file system.
* @author Christian Packenius.
*/
public class FileSystemArchive_YYYY_MM_DD implements Archive {
/**
* Root directory where the document files are stored in sub directories.
*/
private final File rootDirectory;
/**
* Full text index system.
*/
final FulltextIndexAndSearchUtils fulltextIndex;
/**
* Konstruktor.
* @param rootDirectory Root directory for archiving files.
* @throws IOException
*/
public FileSystemArchive_YYYY_MM_DD(File rootDirectory) throws IOException {
File fulltextIndexDir = new File(rootDirectory, "fulltextIndex");
checkRootDirectory(rootDirectory, fulltextIndexDir);
this.rootDirectory = rootDirectory;
fulltextIndex = new FulltextIndexAndSearchUtils(fulltextIndexDir);
}
private void checkRootDirectory(File rootDirectory, File fulltextIndexDir) throws IOException {
rootDirectory.mkdirs();
fulltextIndexDir.mkdirs();
if (!rootDirectory.exists()) {
throw new IllegalArgumentException("Can't create " + rootDirectory.getCanonicalPath() + " as directory!");
}
if (!rootDirectory.isDirectory()) {
throw new IllegalArgumentException("No directory: " + rootDirectory.getCanonicalPath() + "!");
}
}
/**
* @see de.chris_soft.nanoarchive.Archive#store(java.io.File, java.lang.String, java.util.Properties)
*/
@Override
public String store(File document, String fulltext, Properties metadata) throws IOException {
String subDirectoryPath = getSubDirectoryPathForDocument(metadata);
String id = getFileNamesFromDocument(metadata);
String ext = FileUtils.getFileExtension(document);
try {
return storeTripleFiles(document, fulltext, metadata, subDirectoryPath, id, ext);
} catch (IOException ioe) {
File dir = getDirectoryFromSubDirPath(subDirectoryPath);
removeTripleFiles(dir, id, ext);
throw ioe;
}
}
private String getFileNamesFromDocument(Properties metadata) {
String name = metadata.getProperty(Metadata.DOCUMENT_ID);
return name;
}
private String storeTripleFiles(File document, String fulltext, Properties metadata, String subDirectoryPath, String id,
String ext) throws IOException {
if (ext.equals("fulltext") || ext.equals("metadata")) {
throw new IllegalArgumentException("File to be archived must not have extension .fulltext or .metadata!");
}
File dir = getDirectoryFromSubDirPath(subDirectoryPath);
File archivedFile = new File(dir, id + "." + ext);
FileUtils.copyFile(document, archivedFile);
if (document.length() != archivedFile.length()) {
String docPath = document.getCanonicalPath();
String afPath = archivedFile.getCanonicalPath();
String errmsg = docPath + " could not be stored as " + afPath + "!";
throw new IOException(errmsg);
}
createFulltextIndex(fulltext, subDirectoryPath, id);
FileUtils.storeProperties(new File(dir, id + ".metadata"), metadata);
return archivedFile.getCanonicalPath();
}
private void createFulltextIndex(String fulltext, String subDirectoryPath, String id) throws IOException {
// FileUtils.storeStringInFile(new File(dir, id + ".fulltext"), fulltext);
fulltextIndex.add(subDirectoryPath + "/" + id, fulltext);
}
private void removeTripleFiles(File dir, String id, String ext) {
FileUtils.deleteFile(new File(dir, id + "." + ext));
FileUtils.deleteFile(new File(dir, id + ".fulltext"));
FileUtils.deleteFile(new File(dir, id + ".metadata"));
}
private String getSubDirectoryPathForDocument(Properties metadata) {
String docID = metadata.getProperty(Metadata.DOCUMENT_ID);
docID = docID.replace("-", "");
long longDocID = Long.parseLong(docID);
return getSubDirectoryPathFromUniqueDocID(longDocID);
}
private String getSubDirectoryPathFromUniqueDocID(long uniqueDocID) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
GregorianCalendar gc = new GregorianCalendar();
gc.setTimeInMillis(uniqueDocID);
String subDirectoryPath = sdf.format(gc.getTime());
return subDirectoryPath;
}
private File getDirectoryFromSubDirPath(String subDirectoryPath) {
subDirectoryPath = subDirectoryPath.replace('\\', '/');
File dir = rootDirectory;
while (!subDirectoryPath.isEmpty()) {
int k = subDirectoryPath.indexOf('/');
final String subDirName;
if (k >= 0) {
subDirName = subDirectoryPath.substring(0, k);
subDirectoryPath = subDirectoryPath.substring(k + 1);
}
else {
subDirName = subDirectoryPath;
subDirectoryPath = "";
}
if (!subDirName.isEmpty()) {
dir = new File(dir, subDirName);
}
}
dir.mkdirs();
return dir;
}
/**
* @see de.chris_soft.nanoarchive.Archive#getRootDirectories()
*/
@Override
public List<String> getRootDirectories() {
return FileUtils.getDirectoryNames(rootDirectory);
}
/**
* @see de.chris_soft.nanoarchive.Archive#getDirectories(java.lang.String)
*/
@Override
public List<String> getDirectories(String parentSubDirName) {
return FileUtils.getDirectoryNames(getDirectoryFromSubDirPath(parentSubDirName));
}
/**
* @see de.chris_soft.nanoarchive.Archive#getFilesFromSubDirectory(java.lang.String)
*/
@Override
public List<String> getFilesFromSubDirectory(String subDirectoryPath) {
return FileUtils.getFileNames(getDirectoryFromSubDirPath(subDirectoryPath));
}
/**
* @see de.chris_soft.nanoarchive.Archive#getName()
*/
@Override
public String getName() throws IOException {
return getClass().getSimpleName() + "::" + rootDirectory.getCanonicalPath();
}
/**
* @see de.chris_soft.nanoarchive.Archive#documentSearch(java.lang.String, de.chris_soft.nanoarchive.DocumentFoundListener)
*/
@Override
public void documentSearch(final String searchTerm, final DocumentFoundListener listener) {
final Archive archive = this;
new Thread(new Runnable() {
@Override
public void run() {
DocumentSearch search = new DocumentSearch(searchTerm, fulltextIndex, archive);
try {
search.searchDocuments(listener);
} catch (Exception exception) {
LogUtils.log(exception);
}
}
}).start();
}
/**
* @see de.chris_soft.nanoarchive.Archive#getMetadataFromDocID(java.lang.String)
*/
@Override
public Properties getMetadataFromDocID(String docID) {
try {
File file = getMetadataFileFromDocumentID(docID);
return FileUtils.loadProperties(file);
} catch (IOException exception) {
LogUtils.log(exception);
return new Properties();
}
}
private File getMetadataFileFromDocumentID(String docID) {
return new File(getDocumentBaseNameViaDocID(docID) + ".metadata");
}
private File getPdfFileFromDocumentID(String docID) {
return new File(getDocumentBaseNameViaDocID(docID) + ".pdf");
}
/**
* @see de.chris_soft.nanoarchive.Archive#getDocumentFileFromDocID(java.lang.String)
*/
@Override
public File getDocumentFileFromDocID(String docID) {
return getPdfFileFromDocumentID(docID);
}
/**
* @throws IOException
* @see de.chris_soft.nanoarchive.Archive#deleteDocumentById(java.lang.String)
*/
@Override
public void deleteDocumentById(String documentID) throws IOException {
FileUtils.deleteFile(getPdfFileFromDocumentID(documentID));
FileUtils.deleteFile(getMetadataFileFromDocumentID(documentID));
fulltextIndex.remove(documentID);
}
/**
* @see de.chris_soft.nanoarchive.Archive#putMetadataViaDocID(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void putMetadataViaDocID(String docID, String key, String value) throws IOException {
Properties metadata = getMetadataFromDocID(docID);
if (metadata != null) {
metadata.setProperty(key, value);
File file = getMetadataFileFromDocumentID(docID);
FileUtils.storeProperties(file, metadata);
}
}
private String getDocumentBaseNameViaDocID(String docID) {
docID = docID.replace('\\', '/');
int k = docID.lastIndexOf('/');
if (k >= 0) {
docID = docID.substring(k + 1);
}
k = docID.lastIndexOf('.');
if (k >= 0) {
docID = docID.substring(0, k);
}
long uniqueDocID = Long.parseLong(docID.replace("-", ""));
String subDir = getSubDirectoryPathFromUniqueDocID(uniqueDocID);
docID = subDir + "/" + docID;
return rootDirectory.getAbsolutePath() + "/" + docID;
}
}