package spimedb.server; import jcog.bloom.StableBloomFilter; import jcog.bloom.hash.StringHashProvider; import org.eclipse.collections.api.set.ImmutableSet; import org.eclipse.collections.impl.factory.Sets; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import spimedb.FilteredNObject; import spimedb.NObject; import spimedb.SpimeDB; import spimedb.index.DObject; import spimedb.query.Query; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /** * interactive session (ie, Client as seen from Server) * TODO: https://github.com/undertow-io/undertow/blob/master/examples/src/main/java/io/undertow/examples/sessionhandling/SessionServer.java */ public class ClientSession extends Session { static final ImmutableSet<String> mapIncludesFields = Sets.immutable.with( NObject.POLYGON, NObject.LINESTRING, NObject.NAME, NObject.TAG, NObject.INH ); ///final ObjectFloatHashMap<String> attention = new ObjectFloatHashMap<>(); //final UnBloomFilter<String> sent = new UnBloomFilter<>(ALREADY_SENT_MEMORY_CAPACITY, String::getBytes); public ClientSession(SpimeDB db, double outputRateLimitBytesPerSecond) { super(db, outputRateLimitBytesPerSecond); set("me", new API()); } static final ImmutableSet<String> tagFields = Sets.immutable.of( NObject.ID, NObject.NAME, NObject.DESC ); /** * API accessible by clients */ public class API { final StableBloomFilter<String> remoteMemory = new StableBloomFilter<>( /* size */ 32 * 1024, 3, 0.01f, new StringHashProvider()); private Task currentFocus; /** send predicted-to-be-known items after sending predicted-to-be-unknown-by-client */ private final boolean ensureSent = false; public String status() { return db.toString(); } public Task get(String[] id) { return new Task(ClientSession.this) { @Override public void run() { for (String x : id) { try { trySend(this, x, true, tagFields); } catch (IOException e) { break; } } } }; } /** * provides root-level startup tags */ public Task tagRoots() { return this.currentFocus = new Task(ClientSession.this) { @Override public void run() { Iterator<String> r = db.roots(); try { while (r.hasNext()) { trySend(this, r.next(), true, tagFields); } } catch (IOException e) { return; } } }; } /** * allows client to inform server of its forgotten items */ public void forgot(String... ids) { for (String id : ids) { remoteMemory.remove(id); } } public Task focusLonLat(double[][] bounds) { //logger.info("start {} focusLonLat {}", this, bounds); double[] lon = new double[]{bounds[0][0], bounds[1][0]}; double[] lat = new double[]{bounds[0][1], bounds[1][1]}; String[] tags = new String[]{}; return new Task(ClientSession.this) { @Override public void run() { if (currentFocus != null) { currentFocus.stop(); currentFocus = null; } currentFocus = this; Set<NObject> lowPriority = new HashSet<>(1024); db.get(new Query().where(lon, lat).in(tags)).forEach((dd,s)->{ NObject n = transmittable(dd); if (!running.get()) //early exit test return false; try { if (!trySend(this, n, false) && ensureSent) lowPriority.add(n); //buffer it for sending later (low-priority) } catch (IOException e) { stop(); return false; //likely a disconnect } return running.get(); //continue }); if (running.get()) { for (NObject n : lowPriority) { try { trySend(this, n, true); } catch (IOException e) { break; } } } } @NotNull private FilteredNObject transmittable(NObject n) { return new FilteredNObject( n, mapIncludesFields); } }; } private boolean trySend(Task t, String id, boolean force, @Nullable ImmutableSet<String> includeKeys) throws IOException { int[] idHash = remoteMemory.hash(id); if (force || !remoteMemory.contains(idHash)) { DObject d = db.get(id); if (d!=null) { NObject n = /*db.graphed( */includeKeys!=null ? new FilteredNObject(d, includeKeys) : d; if (n != null) { chan.forEach(c -> { try { t.sendJSON(c, n); } catch (IOException e) { e.printStackTrace(); } }); remoteMemory.add(idHash); return true; } else { return false; } } } return false; } private boolean trySend(Task t, NObject n, boolean force) throws IOException { int[] idHash = remoteMemory.hash(n.id()); if (force || !remoteMemory.contains(idHash)) { chan.forEach(c -> { try { t.sendJSON(c, n); } catch (IOException e) { e.printStackTrace(); } }); remoteMemory.add(idHash); return true; } return false; } } // public boolean remoteProbablyHas(NObject n) { // return remoteMemory.addIfMissing(n.id()); // } // public void stopAll() { // active.forEach(Task::stop); // } // // /** // * returns the input task // */ // public Task stopAllExcept(Task t) { // active.forEach(x -> { // if (t != x) // x.stop(); // }); // return t; // } }