| /******************************************************************************* |
| * Copyright (c) 2010-2014 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.skalli.core.storage; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.text.MessageFormat; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.commons.io.FilenameUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.lang.StringUtils; |
| |
| public class Historian { |
| |
| private static final String HISTORY_FILE = ".history"; //$NON-NLS-1$ |
| private static final String CRLF = "\r\n"; //$NON-NLS-1$ |
| |
| private final File historyFile; |
| |
| public Historian(File storageBase) { |
| this.historyFile = new File(storageBase, HISTORY_FILE); |
| } |
| |
| void historize(File file) throws IOException { |
| if (file != null && file.exists()) { |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| in = new BufferedInputStream(new FileInputStream(file)); |
| out = new BufferedOutputStream(new FileOutputStream(historyFile, historyFile.exists())); |
| String header = MessageFormat.format("{0}:{1}:{2}", //$NON-NLS-1$ |
| getNextEntryName(FilenameUtils.getBaseName(file.getAbsolutePath())), |
| Long.toString(file.length()), |
| Long.toString(System.currentTimeMillis())); |
| out.write(header.getBytes("UTF-8")); //$NON-NLS-1$ |
| out.write(CRLF.getBytes("UTF-8")); //$NON-NLS-1$ |
| IOUtils.copy(in, out); |
| out.write(CRLF.getBytes("UTF-8")); //$NON-NLS-1$ |
| } finally { |
| IOUtils.closeQuietly(in); |
| IOUtils.closeQuietly(out); |
| } |
| } |
| } |
| |
| void historize(String id, long timestamp, InputStream blob) throws IOException { |
| OutputStream out = null; |
| try { |
| byte[] buf = IOUtils.toByteArray(blob); |
| out = new BufferedOutputStream(new FileOutputStream(historyFile, historyFile.exists())); |
| String header = MessageFormat.format("{0}:{1}:{2}", //$NON-NLS-1$ |
| getNextEntryName(id), |
| Long.toString(buf.length), |
| Long.toString(timestamp)); |
| out.write(header.getBytes("UTF-8")); //$NON-NLS-1$ |
| out.write(CRLF.getBytes("UTF-8")); //$NON-NLS-1$ |
| out.write(buf); |
| out.write(CRLF.getBytes("UTF-8")); //$NON-NLS-1$ |
| } finally { |
| IOUtils.closeQuietly(out); |
| } |
| } |
| |
| String getNextEntryName(String fileName) throws IOException { |
| int count = 0; |
| InputStream in = null; |
| try { |
| in = new BufferedInputStream(new FileInputStream(historyFile)); |
| String header = readLine(in); |
| while (header.length() > 0) { |
| String[] parts = StringUtils.split(header, ':'); |
| if (parts[0].startsWith(fileName)) { |
| ++count; |
| } |
| skip(in, Integer.valueOf(parts[2]) + CRLF.length()); |
| header = readLine(in); |
| } |
| } finally { |
| IOUtils.closeQuietly(in); |
| } |
| return fileName + ":" + count; //$NON-NLS-1$ |
| } |
| |
| HistoryIterator getHistory(String fileName) { |
| return new HistoryIterator(fileName); |
| } |
| |
| class HistoryEntry { |
| private String id; |
| private String content; |
| private int version; |
| private long timestamp; |
| |
| public HistoryEntry(String id, String content, int version, long timestamp) { |
| this.id = id; |
| this.content = content; |
| this.version = version; |
| this.timestamp = timestamp; |
| } |
| |
| public String getId() { |
| return id; |
| } |
| |
| public String getContent() { |
| return content; |
| } |
| |
| public int getVersion() { |
| return version; |
| } |
| |
| public long getTimestamp() { |
| return timestamp; |
| } |
| } |
| |
| class HistoryIterator { |
| private InputStream in; |
| private String id; |
| private byte[] content; |
| private String[] parts; |
| |
| HistoryIterator(String id) { |
| this.id = id; |
| } |
| |
| public boolean hasNext() throws IOException { |
| if (!historyFile.exists()) { |
| return false; |
| } |
| if (in == null) { |
| in = new BufferedInputStream(new FileInputStream(historyFile)); |
| } |
| if (content == null) { |
| String header = readLine(in); |
| while (header.length() > 0) { |
| parts = StringUtils.split(header, ':'); |
| if (id == null || id.equals(parts[0])) { |
| content = new byte[Integer.valueOf(parts[2])]; |
| read(in, content); |
| skip(in, CRLF.length()); |
| break; |
| } |
| header = readLine(in); |
| } |
| } |
| return content != null; |
| } |
| |
| public HistoryEntry next() throws IOException { |
| if (!hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| HistoryEntry next = new HistoryEntry(parts[0], new String(content, "UTF-8"), //$NON-NLS-1$ |
| Integer.valueOf(parts[1]), Long.valueOf(parts[3])); |
| content = null; |
| return next; |
| } |
| |
| public void close() { |
| IOUtils.closeQuietly(in); |
| } |
| } |
| |
| private static void skip(InputStream in, long n) throws IOException { |
| while (n > 0) { |
| n -= in.skip(n); |
| } |
| } |
| |
| private static void read(InputStream in, byte[] b) throws IOException { |
| int off = 0; |
| int len = b.length; |
| while (len > 0) { |
| int n = in.read(b, off, len); |
| len -= n; |
| off += n; |
| } |
| } |
| |
| private static String readLine(InputStream in) throws IOException { |
| StringBuffer b = new StringBuffer(); |
| String result = null; |
| boolean done = false; |
| while (!done) { |
| int next = in.read(); |
| switch (next) { |
| case -1: |
| result = b.toString(); |
| done = true; |
| break; |
| case '\n': |
| if (b.length() == 0) { |
| break; |
| } |
| result = b.toString(); |
| done = true; |
| break; |
| case '\r': |
| break; |
| default: |
| b.append((char) next); |
| } |
| } |
| return result; |
| } |
| } |