/*
* Copyright (C) 2010-2015, Martin Goellnitz
*
* 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 2 of the License, or
* (at your option) 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301, USA
*/
package jfs.sync.encryption;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import jfs.sync.encrypted.EncryptedFileStorageAccess;
import jfs.sync.util.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract storage implementation dealing with storag using separate file mata data elements.
*
* Plain text meta data for a directory is stored in a separate file which is ignored for all other actions.
*/
public abstract class AbstractMetaStorageAccess extends EncryptedFileStorageAccess {
private static final Logger LOG = LoggerFactory.getLogger(AbstractMetaStorageAccess.class);
private static final DateFormat FORMATTER = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.MEDIUM);
private final Map<String, Map<String, FileInfo>> directoryCache = new HashMap<>();
public AbstractMetaStorageAccess(String cipher, boolean shortenPaths) {
super(cipher, shortenPaths);
} // AbstractMetaStorageAccess()
protected Map<String, FileInfo> getMetaData(String rootPath, String relativePath) {
if (directoryCache.containsKey(relativePath)) {
return directoryCache.get(relativePath);
} // if
Map<String, FileInfo> result = new HashMap<>();
ObjectInputStream ois = null;
try {
InputStream inputStream = getInputStream(rootPath, getMetaDataPath(relativePath));
byte[] credentials = getCredentials(relativePath);
Cipher cipher = SecurityUtils.getCipher(getCipherSpec(), Cipher.DECRYPT_MODE, credentials);
inputStream = new CipherInputStream(inputStream, cipher);
LOG.debug("getMetaData() reading infos for {}", relativePath);
ois = new ObjectInputStream(inputStream);
Object o;
while ((o = ois.readObject())!=null) {
if (o instanceof FileInfo) {
FileInfo fi = (FileInfo) o;
if (fi.isDirectory()) {
String date;
synchronized (FORMATTER) {
date = FORMATTER.format(new Date(fi.getModificationDate()));
}
LOG.debug("getMetaData() {}{}: {}", relativePath+getSeparator(), fi.getName(), date);
} // if
result.put(fi.getName(), fi);
} // if
} // while
ois.close();
} catch (FileNotFoundException|EOFException e) {
// empty directory or - who cares?
} catch (Exception e) {
LOG.info("getMetaData() possible issue while reading infos {}", e, e);
} finally {
try {
if (ois!=null) {
ois.close();
} // if
} catch (Exception ex) {
// who cares?
} // try/catch
} // try/catch
directoryCache.put(relativePath, result);
return result;
} // getMetaData()
protected abstract OutputStream getOutputStream(String rootPath, String relativePath, boolean forPayload) throws IOException;
@Override
public OutputStream getOutputStream(String rootPath, String relativePath) throws IOException {
return getOutputStream(rootPath, relativePath, true);
} // getOutputStream()
/**
* flushing listing as meta data info for pathAndName[0] in rootPath
*
* @param rootPath
* @param pathAndName
* path and name for the file and path for which this update takes place
* @param listing
*/
public void flushMetaData(String rootPath, String[] pathAndName, Map<String, FileInfo> listing) {
try {
LOG.debug("flushMetaData() flushing {}", listing);
for (FileInfo fi : listing.values()) {
if (!fi.isCanWrite()) {
LOG.error("flushMetaData() cannot write file {} / {}", fi.getPath(), fi.getName());
} // if
} // for
OutputStream os = getOutputStream(rootPath, getMetaDataPath(pathAndName[0]), false);
try {
byte[] credentials = getCredentials(pathAndName[0]);
Cipher cipher = SecurityUtils.getCipher(getCipherSpec(), Cipher.ENCRYPT_MODE, credentials);
os = new CipherOutputStream(os, cipher);
} catch (InvalidKeyException e) {
LOG.error("flushMetaData()", e);
} catch (NoSuchAlgorithmException e) {
LOG.error("flushMetaData()", e);
} catch (NoSuchPaddingException e) {
LOG.error("flushMetaData()", e);
} // try/catch
ObjectOutputStream oos = new ObjectOutputStream(os);
for (FileInfo info : listing.values()) {
LOG.debug("flushMetaData() writing {}", info.getName());
oos.writeObject(info);
} // for
oos.flush();
os.close();
if (LOG.isDebugEnabled()) {
Map<String, FileInfo> backtest = getMetaData(rootPath, pathAndName[0]);
for (FileInfo info : backtest.values()) {
LOG.debug("flushMetaData() reading {}", info);
} // for
} // if
} catch (IOException ioe) {
LOG.error("flushMetaData() error writing meta data ", ioe);
} // try/catch
} // flushMetaData()
public Map<String, FileInfo> getParentListing(String rootPath, String[] pathAndName) {
Map<String, FileInfo> listing = getMetaData(rootPath, pathAndName[0]);
LOG.debug("getParentListing({}) {}", pathAndName[0], listing);
return listing;
} // getParentListing()
@Override
public String[] list(String rootPath, String relativePath) {
Map<String, FileInfo> listing = getMetaData(rootPath, relativePath);
String[] result = new String[listing.size()];
int i = 0;
for (String name : listing.keySet()) {
result[i++] = name;
} // for
return result;
} // list()
@Override
public void flush(String rootPath, FileInfo info) {
String[] pathAndName = new String[2];
pathAndName[0] = info.getPath();
pathAndName[1] = info.getName();
Map<String, FileInfo> listing = getParentListing(rootPath, pathAndName);
if (listing.containsKey(info.getName())) {
listing.remove(info.getName());
} // if
listing.put(info.getName(), info);
LOG.info("flush() flushing {}/{}", pathAndName[0], pathAndName[1]);
flushMetaData(rootPath, pathAndName, listing);
} // flush()
} // AbstractMetaStorageAccess