/*
* Copyright 2013 mpowers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.trsst.server;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import com.trsst.Common;
/**
* Dumb file persistence for small nodes and test cases. This will get you up
* and running quickly, but you'll want to replace it with an implementation
* backed by some kind of database or document store.
*
* Files are placed in a "trsstd" directory inside of your home directory, or
* inside the directory in the system property "com.trsst.storage" if specified.
*
* @author mpowers
*/
public class FileStorage implements Storage {
public static final String FEED_XML = "feed.xml";
public static final String ENTRY_SUFFIX = ".atom";
public static final String ENCODING = "UTF-8";
private File root;
public FileStorage() {
this(Common.getServerRoot());
}
public FileStorage(File root) {
this.root = root;
System.err.println("File storage serving from: " + root);
}
public String[] getFeedIds(int start, int length) {
/* Returns feeds for which we have a keystore. */
File[] files = root.listFiles();
if (files == null) {
return new String[0];
}
List<String> result = new LinkedList<String>();
int i;
for (File f : files) {
i = f.getName().indexOf(Common.KEY_EXTENSION);
if (i != -1) {
result.add(Common.unescapeHTML(f.getName().substring(0, i)));
}
}
return result.toArray(new String[0]);
}
public String[] getCategories(int start, int length) {
// TODO: implement category trackers
return new String[0];
}
public int getEntryCount(Date after, Date before, String query,
String[] mentions, String[] tags, String verb) {
// not supported
return -1;
}
public String[] getEntryIds(int start, int length, Date after, Date before,
String query, String[] mentions, String[] tags, String verb) {
// not supported
return null;
}
public int getEntryCountForFeedId(String feedId, Date after, Date before,
String query, String[] mentions, String[] tags, String verb) {
File[] files = new File(root, feedId).listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getName().toLowerCase().endsWith(ENTRY_SUFFIX);
}
});
return files.length;
}
public long[] getEntryIdsForFeedId(String feedId, int start, int length,
Date after, Date before, String query, String[] mentions,
String[] tags, String verb) {
if (start < 0 || length < 1) {
throw new IllegalArgumentException("Invalid range: start: " + start
+ " : length: " + length);
}
long[] all = getEntryIdsForFeedId(feedId, after, before);
if (start >= all.length) {
return new long[0];
}
// TODO: implement query/tag/mention/verb filter
int end = Math.min(start + length, all.length);
long[] result = new long[end - start];
System.arraycopy(all, start, result, 0, result.length);
return result;
}
public String readFeed(String feedId) throws FileNotFoundException,
IOException {
return readStringFromFile(getFeedFileForFeedId(feedId));
}
public void updateFeed(String feedId, Date lastUpdated, String feed)
throws FileNotFoundException, IOException {
File file = getFeedFileForFeedId(feedId);
writeStringToFile(feed, file);
if (lastUpdated != null) {
file.setLastModified(lastUpdated.getTime());
}
}
public String readEntry(String feedId, long entryId)
throws FileNotFoundException, IOException {
return readStringFromFile(getEntryFileForFeedEntry(feedId, entryId));
}
public void updateEntry(String feedId, long entryId, Date publishDate,
String entry) throws IOException {
File file = getEntryFileForFeedEntry(feedId, entryId);
writeStringToFile(entry, file);
if (publishDate != null) {
file.setLastModified(publishDate.getTime());
}
}
public void deleteEntry(String feedId, long entryId) throws IOException {
File file = getEntryFileForFeedEntry(feedId, entryId);
if (file.exists()) {
file.delete();
}
}
public String readFeedEntryResourceType(String feedId, long entryId,
String resourceId) throws IOException {
return getMimeTypeForFile(getResourceFileForFeedEntry(feedId, entryId,
resourceId));
}
public InputStream readFeedEntryResource(String feedId, long entryId,
String resourceId) throws IOException {
return new BufferedInputStream(new FileInputStream(
getResourceFileForFeedEntry(feedId, entryId, resourceId)));
}
public void updateFeedEntryResource(String feedId, long entryId,
String resourceId, String mimetype, Date publishDate, byte[] data)
throws IOException {
File file = getResourceFileForFeedEntry(feedId, entryId, resourceId);
OutputStream output = new BufferedOutputStream(new FileOutputStream(
file));
try {
output.write(data, 0, data.length);
output.flush();
System.err.println("wrote: " + file.getAbsolutePath());
} finally {
try {
output.close();
} catch (IOException ioe) {
// suppress any futher error on closing
}
}
if (publishDate != null) {
file.setLastModified(publishDate.getTime());
}
}
public void deleteFeedEntryResource(String feedId, long entryId,
String resourceId) {
File file = getResourceFileForFeedEntry(feedId, entryId, resourceId);
if (file.exists()) {
file.delete();
}
}
private static final String getMimeTypeForFile(File file) {
return URLConnection.getFileNameMap().getContentTypeFor(file.getName());
}
private long[] getEntryIdsForFeedId(String feedId, Date after, Date before) {
feedId = Common.encodeURL(feedId);
final long afterTime = after != null ? after.getTime() : 0;
final long beforeTime = before != null ? before.getTime() : 0;
File[] files = new File(root, feedId).listFiles(new FileFilter() {
public boolean accept(File file) {
if ((file.getName().toLowerCase().endsWith(ENTRY_SUFFIX))
&& (afterTime == 0 || file.lastModified() > afterTime)
&& (beforeTime == 0 || file.lastModified() < beforeTime)) {
return true;
}
return false;
}
});
Arrays.sort(files, new Comparator<File>() {
public int compare(File o1, File o2) {
// System.out.println( new Date(o1.lastModified() ) + " : " +
// new Date(o2.lastModified()) );
return o1.lastModified() > o2.lastModified() ? -1 : o1
.lastModified() < o2.lastModified() ? 1 : 0;
}
});
String name;
int suffix = ENTRY_SUFFIX.length();
long[] result = new long[files.length];
for (int i = 0; i < files.length; i++) {
name = files[i].getName();
result[i] = Long.parseLong(
name.substring(0, name.length() - suffix), 16);
}
return result;
}
private static final String readStringFromFile(File file)
throws IOException {
StringBuffer result = new StringBuffer();
Reader reader = null;
try {
reader = new InputStreamReader(new BufferedInputStream(
new FileInputStream(file)), ENCODING);
int c;
char[] buf = new char[256];
while ((c = reader.read(buf)) > 0) {
result.append(buf, 0, c);
}
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException ioe) {
// suppress any futher error on closing
}
}
return result.toString();
}
private static final void writeStringToFile(String text, File file)
throws IOException {
Writer writer = null;
try {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs(); // ensure directory exists
}
writer = new OutputStreamWriter(new BufferedOutputStream(
new FileOutputStream(file)), ENCODING);
writer.write(text);
writer.flush();
System.err.println("wrote: " + file.getAbsolutePath());
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException ioe) {
// suppress any futher error on closing
}
}
}
public File getFeedFileForFeedId(String feedId) {
feedId = Common.encodeURL(feedId);
return new File(new File(root, feedId), FEED_XML);
}
public File getEntryFileForFeedEntry(String feedId, long entryId) {
feedId = Common.encodeURL(feedId);
return new File(new File(root, feedId),
Long.toHexString(entryId) + ENTRY_SUFFIX);
}
public File getResourceFileForFeedEntry(String feedId, long entryId,
String resourceid) {
feedId = Common.encodeURL(feedId);
return new File(new File(root, feedId),
Long.toHexString(entryId) + '-' + resourceid);
}
}