/* SimpleMediaserver.java * * This file is part of Gzz. * * Gzz is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Gzz 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 Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with Gzz; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * */ /* * Written by Benja Fallenstein and Antti-Juhani Kaijanaho */ package gzz.mediaserver; import gzz.mediaserver.storage.*; import gzz.util.*; import java.io.*; import java.util.*; import java.text.*; import java.net.InetAddress; /** A mediaserver capable of retrieving and adding data. * This is a combination of a Storer and an IDSpace. Unlike a Storer, a * Mediaserver does not allow storing data under arbitrary IDs; rather, * when adding a new datum, a new ID is assigned to it. */ public class SimpleMediaserver implements Mediaserver { public static boolean dbg = false; private static void p(String s) { if(dbg) pa(s); } private static void pa(String s) { System.out.println(s); } private Storer store; private IDSpace idspace; private Properties properties; private static DateFormat dateFormat = new SimpleDateFormat("EE, d MMM yyyy HH:mm:ss", Locale.ENGLISH); static { dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } private Map cache = new HashMap(); public SimpleMediaserver(Storer store, IDSpace idspace, long flags) { this.store = store; this.idspace = idspace; } public SimpleMediaserver(Storer store) { this(store, IDSpace.instance, 0); } public Set getIDs() throws IOException { Set keys = store.getKeys(); Set ids = new HashSet(); for(Iterator i = keys.iterator(); i.hasNext(); ) { String s = (String)i.next(); if(!s.substring(0, 2).equals("b_")) continue; ids.add(new Id(HexUtil.hexToByteArr(s.substring(2)))); } return ids; } public MediaserverBlock getDatum(Id id) throws IOException { MediaserverBlock b = (MediaserverBlock)cache.get(id); if(b != null) return b; final String key = "b_" + id.getString(); File f = store.getFile(key); if (f == null) { InputStream is = store.retrieve(key); if(is == null) throw new IOException("Datum not found: "+id); b = new MediaserverBlock.ByteArrayBlock(id, this.getPoolName(), CopyUtil.readBytes(is)); } else { b = new MediaserverBlock.FileBlock(id, this.getPoolName(), f); } return b; /* // for now, do it the inefficient way: read all the input and then // parse it from the byte array in the end. byte[] bytes = copyUtil.readBytes(is); b = new MediaserverBlock.ByteArrayBlock(id, this.getPoolName(), bytes); return b; */ } void writeHeader(OutputStream os, Collection _headerLines, boolean addDefaultHeaders) throws IOException { SortedSet headerLines = new TreeSet(_headerLines); if(addDefaultHeaders) { String name = System.getProperty("user.name"); // Also get the local host name... try { InetAddress addr = InetAddress.getLocalHost(); name = name + "@" + addr.getHostName(); } catch(Exception e) { } // XXX Potential problem: mangled user.name with e.g. newlines if(name != null) headerLines.add("X-Injected-By: " + name); headerLines.add("Date: " + dateFormat.format(new Date()) + " +0000"); headerLines.add("Content-Transfer-Encoding: binary"); } HeaderUtil.writeHeader(os, headerLines); } public Id addDatum(byte[] data, String contentType, Id assocId) throws IOException { // Don't care about associated id as there's only one pool return addDatum(data, contentType); } public Id addDatum(byte[] data, String contentType) throws IOException { Collection headerLines = Collections.singleton("Content-Type: "+contentType); return addDatum(data, headerLines, null, true); } public Id addDatum(byte[] data, Collection headerLines, Id assocId, boolean addDefaultHeaders) throws IOException { Id id; // First, include header into block! ByteArrayOutputStream os = new ByteArrayOutputStream(); writeHeader(os, headerLines, addDefaultHeaders); os.write(data); os.close(); byte[] ndata = os.toByteArray(); id = idspace.createID(ndata); OutputStream sos = store.store("b_" + id.getString()); sos.write(ndata); sos.close(); blockAdded(id); return id; } public void storeDatum(Id id, byte[] data) throws IOException { MediaserverBlock block = new MediaserverBlock.ByteArrayBlock(id, this.getPoolName(), data); OutputStream os = store.store("b_" + id.getString()); os.write(data); os.close(); blockAdded(id); } public void expungeDatum(Id id) throws IOException { store.delete("b_" + id.getString()); } /** Get the pointer set for a specific pointer. * XXX performance * @param s The ID of the pointer whose pointer set we want to retrieve. */ public PointerSet getPointerSet(String s) throws IOException { InputStream is = store.retrieve("ps_" + s); if(is != null) return PointerSet.read(s, is); Map sets = PointerSet.getPointerSets(this); PointerSet set = (PointerSet)sets.get(s); if(set == null) set = new PointerSet(s); OutputStream os = store.store("ps_" + s); set.write(os); return set; } public void cache(MediaserverBlock b) { cache.put(b.getId(), b); } public void uncache(MediaserverBlock b) { cache.remove(b); } private Set getObsoletes(String s) throws IOException { PointerSet pset = getPointerSet(s); Set obsolete; if(pset != null) obsolete = pset.getActiveMap().keySet(); else obsolete = Collections.EMPTY_SET; return obsolete; } public void setPointer(String s, Id id) throws IOException { setPointer(s, id, getObsoletes(s)); } public void setPointer(String s, Id id, Id obsolete) throws IOException { Set o = new HashSet(); o.add(obsolete); setPointer(s, id, o); } public void setPointer(String s, Id id, Set obsoletes) throws IOException { if(dbg) { pa("Setting pointer "+s+" to "+id); Set obs = getObsoletes(s); for(Iterator i=obs.iterator(); i.hasNext(); ) { pa("Currently active record: "+i.next()); } for(Iterator i=obsoletes.iterator(); i.hasNext(); ) { Object o = i.next(); pa("Obsoleting pointer record: "+o); if (o == null) Thread.currentThread().dumpStack(); } } PointerRecord rec = new PointerRecord(s, id, obsoletes, null); rec.save(this); } public Id getPointer(String s) throws IOException { PointerSet pset = getPointerSet(s); if(pset != null) return pset.getSingleActive(); else return null; } public Set getPointers() throws IOException { Map map = PointerSet.getPointerSets(this); return map.keySet(); } /** Called when a new block is added; checks if it's a pointer * changing a pointer set. * XXX performance */ protected void blockAdded(Mediaserver.Id id) throws IOException { //pa(new String(CopyUtil.readBytes(store.retrieve("b_" + id.getString())))); MediaserverBlock b = getDatum(id); if(b.getContentType().equals("application/x-gzigzag-ptr")) { PointerRecord pr = PointerRecord.read(id, new ByteArrayInputStream(b.getBytes())); PointerSet set = getPointerSet(pr.id); set.add(pr); set.write(store.store("ps_" + pr.id)); } else if(b.getContentType().equals(MediaserverFiler.diffContentType)) { Id to = new Id(HeaderUtil.getOne(b.getHeader(), "X-Gzz-Diff-To")); Set t = getDiffsTo(to); t.add(id); writeIdSet("dt_" + to.getString(), t); if(!b.getHeader().containsKey("x-gzz-diff-from")) return; Id from = new Id(HeaderUtil.getOne(b.getHeader(), "X-Gzz-Diff-From")); Set s = getDiffsFrom(from); s.add(id); writeIdSet("df_" + from.getString(), s); } } protected Set readIdSet(String key) throws IOException { InputStream in = store.retrieve(key); if(in == null) return new HashSet(); BufferedReader r = new BufferedReader(new InputStreamReader(in)); Set s = new HashSet(); String id; while((id = r.readLine()) != null) s.add(new Id(id)); r.close(); return s; } protected void writeIdSet(String key, Set s) throws IOException { OutputStream os = store.store(key); Writer w = new BufferedWriter(new OutputStreamWriter(os)); for(Iterator i = s.iterator(); i.hasNext();) { Id id = (Id)i.next(); w.write(id.getString()); w.write('\n'); } w.close(); } public Set getDiffsFrom(Id id) throws IOException { return readIdSet("df_" + id.getString()); } public Set getDiffsTo(Id id) throws IOException { return readIdSet("dt_" + id.getString()); } public String getPoolName() throws IOException { String rv = store.getProperty("simplemediaserver.poolname"); if (rv == null) return ""; return rv; } public void setPoolName(String name) throws IOException { store.setProperty("simplemediaserver.poolname", name); } }