diff --git a/pom.xml b/pom.xml
index 8281a29..052e69d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,30 @@
gethostname4j
1.0.0
+
+ es.blackleg
+ jlibnotify
+ 1.1.0
+
+
+ com.github.hypfvieh
+ dbus-java-core
+ 5.1.0-SNAPSHOT
+
+
+
+ com.github.hypfvieh
+ dbus-java-transport-native-unixsocket
+ 5.1.0-SNAPSHOT
+
+
+
+
+ com.github.hypfvieh
+ dbus-java-transport-tcp
+ 5.1.0-SNAPSHOT
+
+
diff --git a/src/main/java/moe/nekojimi/friendcloud/Main.java b/src/main/java/moe/nekojimi/friendcloud/Main.java
index bf99255..0bcbce1 100644
--- a/src/main/java/moe/nekojimi/friendcloud/Main.java
+++ b/src/main/java/moe/nekojimi/friendcloud/Main.java
@@ -11,6 +11,12 @@ import com.offbynull.portmapper.gateways.process.ProcessGateway;
import com.offbynull.portmapper.mapper.MappedPort;
import com.offbynull.portmapper.mapper.PortMapper;
import com.offbynull.portmapper.mapper.PortType;
+import es.blackleg.jlibnotify.JLibnotify;
+import es.blackleg.jlibnotify.JLibnotifyNotification;
+import es.blackleg.jlibnotify.core.DefaultJLibnotify;
+import es.blackleg.jlibnotify.core.DefaultJLibnotifyLoader;
+import es.blackleg.jlibnotify.exception.JLibnotifyInitException;
+import es.blackleg.jlibnotify.exception.JLibnotifyLoadException;
import jnr.ffi.Platform;
import moe.nekojimi.friendcloud.filesystem.FUSEAccess;
import moe.nekojimi.friendcloud.network.PeerConnection;
@@ -18,8 +24,10 @@ import moe.nekojimi.friendcloud.network.requests.ObjectListRequest;
import moe.nekojimi.friendcloud.objects.NetworkFile;
import moe.nekojimi.friendcloud.objects.NetworkObject;
import moe.nekojimi.friendcloud.objects.Peer;
-import moe.nekojimi.friendcloud.objects.PeerFileState;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
+import moe.nekojimi.friendcloud.storage.Model;
+import moe.nekojimi.friendcloud.storage.StupidJSONFileStore;
+import moe.nekojimi.friendcloud.tasks.JoinNetworkTask;
import org.slf4j.simple.SimpleLogger;
import java.io.File;
@@ -28,6 +36,7 @@ import java.net.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
@@ -37,7 +46,7 @@ public class Main
private static Main instance;
@Parameter(names="-share")
- private List sharedFiles = new ArrayList<>();
+ private List sharedFilePaths = new ArrayList<>();
@Parameter(names="-known-peer")
private List knownPeers = new ArrayList<>();
@@ -48,13 +57,15 @@ public class Main
@Parameter(names="-no-upnp")
private boolean noUpnp = false;
+ @Parameter(names="-create-network")
+ private boolean createNetwork = false;
+
// @Parameter(names="-file")
private ConnectionManager connectionManager;
-
private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(16);
-
private final FUSEAccess fuseAccess = new FUSEAccess();
+ private final Model model = new Model(new StupidJSONFileStore(new File("storage")));
public static void main(String[] args)
{
@@ -66,7 +77,7 @@ public class Main
try
{
instance.run();
- } catch (IOException | InterruptedException e)
+ } catch (IOException | InterruptedException | JLibnotifyLoadException | JLibnotifyInitException e)
{
e.printStackTrace(System.err);
try
@@ -81,7 +92,7 @@ public class Main
// TestMessage.SearchRequest request = TestMessage.SearchRequest.newBuilder().setQuery("bees!").setPageNumber(316).setResultsPerPage(42069).build();
}
- private void run() throws IOException, InterruptedException
+ private void run() throws IOException, InterruptedException, JLibnotifyLoadException, JLibnotifyInitException
{
connectionManager = new ConnectionManager(tcpPort);
@@ -111,14 +122,20 @@ public class Main
}
}));
- connectionManager.addNewConnectionConsumer(this::resquestCompleteState);
+ DefaultJLibnotify libnotify = (DefaultJLibnotify) DefaultJLibnotifyLoader.init().load();
+ libnotify.init("FriendCloud");
+ JLibnotifyNotification notification = libnotify.createNotification("Holy balls a notification!", "Woah!!!", "dialog-information");
+ notification.show();
+
+ connectionManager.addNewConnectionConsumer(this::requestCompleteState);
connectionManager.start();
String hostname = Hostname.getHostname();
- Model.getInstance().getSelfPeer().setSystemName(hostname);
- Model.getInstance().getSelfPeer().setUserName(System.getProperty("user.name") + "-" + tcpPort);
+ model.getSelfPeer().setSystemName(hostname);
+ model.getSelfPeer().setUserName(System.getProperty("user.name") + "-" + tcpPort);
addHostAddress(InetAddress.getLocalHost());
+ model.objectChanged(model.getSelfPeer());
/*
Startup procedure:
@@ -133,23 +150,43 @@ public class Main
if (!noUpnp)
setupIGP();
- for (String sharedFilePath: sharedFiles)
+ Set sharedFiles = new HashSet<>();
+ for (String sharedFilePath: sharedFilePaths)
+ {
+ sharedFiles.add(new File(sharedFilePath));
+ }
+
+ List knownFiles = model.listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE));
+
+ for (NetworkObject knownFile: knownFiles)
+ {
+ NetworkFile f = (NetworkFile) knownFile;
+ boolean removed = sharedFiles.remove(f.getLocalFile());
+ if (removed)
+ System.out.println("Identified known local file " + f.getObjectID() + " = " + f.getLocalFile());
+ }
+
+ for (File sharedFile: sharedFiles)
{
- File file = new File(sharedFilePath);
- if (file.exists())
+ if (sharedFile.exists())
{
- System.out.println("Adding shared network file: " + file.getAbsolutePath());
+ System.out.println("Adding shared network file: " + sharedFile.getAbsolutePath());
- NetworkFile networkFile = (NetworkFile) Model.getInstance().createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_FILE);
- networkFile.updateFromLocalFile(file);
+ NetworkFile networkFile = (NetworkFile) model.createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_FILE);
+ networkFile.updateFromLocalFile(sharedFile);
+ model.objectChanged(networkFile);
- PeerFileState peerFileState = (PeerFileState) Model.getInstance().createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_PEER_FILE_STATE);
- peerFileState.setNode(Model.getInstance().getSelfPeer());
- peerFileState.setFile(networkFile);
- peerFileState.setProgress(100);
+// PeerFileState peerFileState = (PeerFileState) model.createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_PEER_FILE_STATE);
+// peerFileState.setNode(model.getSelfPeer());
+// peerFileState.setFile(networkFile);
+// peerFileState.setProgress(100);
+// model.objectChanged(peerFileState);
}
}
+ JoinNetworkTask joinNetworkTask = new JoinNetworkTask();
+ executor.submit(joinNetworkTask);
+
for (String knownPeerAddress : knownPeers)
{
String[] split = knownPeerAddress.split(":");
@@ -165,21 +202,7 @@ public class Main
URI uri = new URI("tcp", null, address.getHostString(), address.getPort(), null, null, null);
PeerConnection nodeConnection = connectionManager.getNodeConnection(uri);
- resquestCompleteState(nodeConnection);
-
-// objectListFuture.whenComplete((networkObjects, throwable) -> {
-// for (NetworkObject networkObject: networkObjects)
-// {
-// if (networkObject instanceof NetworkFile)
-// {
-// System.out.println("Heard about NetworkFile " + networkObject + ", creating download task!");
-// FileDownloadTask fileDownloadTask = new FileDownloadTask((NetworkFile) networkObject, connectionManager);
-// executor.submit(fileDownloadTask);
-// }
-// }
-// });
-
-
+ requestCompleteState(nodeConnection);
} catch (ConnectException ex)
{
System.out.println("Couldn't connect to host " + address);
@@ -191,7 +214,7 @@ public class Main
}
}
- private void resquestCompleteState(PeerConnection nodeConnection)
+ private void requestCompleteState(PeerConnection nodeConnection)
{
CompletableFuture> objectListFuture = nodeConnection.makeRequest(new ObjectListRequest(Set.of(
ObjectStatements.ObjectType.OBJECT_TYPE_FILE,
@@ -202,7 +225,7 @@ public class Main
private void addHostAddress(InetAddress address)
{
String host = address.getCanonicalHostName();
- Peer selfNode = Model.getInstance().getSelfPeer();
+ Peer selfNode = model.getSelfPeer();
try
{
URI uri = new URI("tcp", null, host, tcpPort, null, null, null);
@@ -281,4 +304,9 @@ public class Main
{
return connectionManager;
}
+
+ public Model getModel()
+ {
+ return model;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/moe/nekojimi/friendcloud/Model.java b/src/main/java/moe/nekojimi/friendcloud/Model.java
deleted file mode 100644
index a77caaa..0000000
--- a/src/main/java/moe/nekojimi/friendcloud/Model.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package moe.nekojimi.friendcloud;
-
-import moe.nekojimi.friendcloud.objects.*;
-import moe.nekojimi.friendcloud.protos.ObjectStatements;
-
-import java.util.*;
-
-public class Model
-{
- private static Model instance = null;
- public static Model getInstance()
- {
- if (instance == null)
- instance = new Model();
- return instance;
- }
-
- private final Map objects = new HashMap<>();
- private final int systemID;
-
- private Peer selfPeer = null;
- private ObjectChangeRecord currentChange;
- private final Map changeRecords = new HashMap<>();
-
- private Model()
- {
- Random ran = new Random();
- systemID = ran.nextInt() & 0x00FFFFFF;
- }
-
- public void setSelfPeer(Peer selfPeer)
- {
- this.selfPeer = selfPeer;
- }
-
- public synchronized Peer getSelfPeer()
- {
-// if (selfPeer == null)
-// selfPeer = (Peer) createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_PEER);
- return selfPeer;
- }
- // private Map nodes = new HashMap<>();
-
- public synchronized NetworkObject.ObjectID getNextObjectID(ObjectStatements.ObjectType type)
- {
- Random ran = new Random();
- int randomNumber = ran.nextInt();
- NetworkObject.ObjectID objectID = new NetworkObject.ObjectID(type, systemID, randomNumber);
- System.out.println("Assigned new object ID: " + objectID);
- return objectID;
- }
-
- public synchronized NetworkObject createObjectByID(NetworkObject.ObjectID id)
- {
- ObjectStatements.ObjectType type = id.getType();
- System.out.println("Creating new object with type: " + type.name());
- NetworkObject ret = switch (type)
- {
-// case UNRECOGNIZED -> ;
- case OBJECT_TYPE_FILE -> new NetworkFile(id);
- case OBJECT_TYPE_UNSPECIFIED -> throw new IllegalArgumentException();
-// case OBJECT_TYPE_USER -> null;
- case OBJECT_TYPE_FOLDER -> new NetworkFolder(id);
- case OBJECT_TYPE_PEER -> new Peer(id);
- case OBJECT_TYPE_PEER_FILE_STATE -> new PeerFileState(id);
- default -> throw new UnsupportedOperationException("NYI");
- };
- objects.put(id, ret);
- return ret;
- }
-
- public synchronized NetworkObject createObjectByType(ObjectStatements.ObjectType type)
- {
- return createObjectByID(getNextObjectID(type));
- }
-
- public synchronized NetworkObject getOrCreateObject(NetworkObject.ObjectID id)
- {
- if (!objects.containsKey(id))
- {
- objects.put(id, createObjectByID(id));
- }
- return objects.get(id);
- }
-
- public synchronized List listObjects(Set types)
- {
- return objects.keySet().stream().filter((id)->(types.contains(id.getType()))).toList();
- }
-
- public synchronized NetworkObject getObject(NetworkObject.ObjectID objectID)
- {
- return objects.get(objectID);
- }
-
- public synchronized List listFSNodes(String path)
- {
- //TODO: dumbest algorithm in the world
-
- List ret = new ArrayList<>();
- for (NetworkObject.ObjectID nodeID : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE, ObjectStatements.ObjectType.OBJECT_TYPE_FOLDER)))
- {
- NetworkFSNode fsNode = (NetworkFSNode) getObject(nodeID);
- String networkPath = fsNode.getNetworkPath();
- if (networkPath.substring(0, networkPath.lastIndexOf("/")+1).equals(path))
- ret.add(fsNode);
- }
- return ret;
- }
-
- public synchronized NetworkFSNode getFSNode(String path)
- {
- for (NetworkObject.ObjectID nodeID : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE, ObjectStatements.ObjectType.OBJECT_TYPE_FOLDER)))
- {
- NetworkFSNode fsNode = (NetworkFSNode) getObject(nodeID);
- String networkPath = fsNode.getNetworkPath();
- if (networkPath.equals(path))
- return fsNode;
- }
- return null;
- }
-
- public synchronized void addChangeRecord(ObjectChangeRecord record)
- {
- changeRecords.put(record.getChangeID(), record);
- }
-
- public ObjectChangeRecord getChangeRecord(long id)
- {
- return changeRecords.get(id);
- }
-
- public void applyChangeRecord(ObjectChangeRecord record)
- {
- if (!record.getChangeHeads().contains(currentChange))
- throw new IllegalStateException("Change does not apply! Valid change heads=" + record.getChangeHeads() + ", we are in state " + currentChange.getChangeID());
- if (!changeRecords.containsKey(record.getChangeID()))
- addChangeRecord(record);
-
-
-// if (record == null)
-// throw new IllegalArgumentException("Cannot apply unknown change!");
- }
-
- public Set getChangeHeads()
- {
- // stupid algorithm - start with all of the changes, then remove the ones that are referenced by something
- // TODO: better algorithm
- Set ret = new HashSet<>(changeRecords.values());
- for (ObjectChangeRecord record : changeRecords.values())
- {
-
- }
- }
-
- public Set listOtherPeers()
- {
- Set ret = new HashSet<>();
- for (NetworkObject.ObjectID peerID : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_PEER)))
- {
- Peer peer = (Peer) getObject(peerID);
- if (peer != getSelfPeer())
- ret.add(peer);
- }
- return ret;
- }
-}
diff --git a/src/main/java/moe/nekojimi/friendcloud/ObjectChangeRecord.java b/src/main/java/moe/nekojimi/friendcloud/ObjectChangeRecord.java
index 637567b..973e010 100644
--- a/src/main/java/moe/nekojimi/friendcloud/ObjectChangeRecord.java
+++ b/src/main/java/moe/nekojimi/friendcloud/ObjectChangeRecord.java
@@ -2,19 +2,20 @@ package moe.nekojimi.friendcloud;
import moe.nekojimi.friendcloud.objects.NetworkObject;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
+import moe.nekojimi.friendcloud.storage.Storable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
-public class ObjectChangeRecord
+public class ObjectChangeRecord implements Storable
{
// private final long changeID;
- private final NetworkObject.ObjectID creatorPeer;
- private final Set changeHeads = new HashSet<>();
- private final Set changes = new HashSet<>();
+ private NetworkObject.ObjectID creatorPeer;
+ private Set changeHeads = new HashSet<>();
+ private Set changes = new HashSet<>();
public ObjectChangeRecord(NetworkObject.ObjectID creatorPeer)
{
@@ -69,6 +70,22 @@ public class ObjectChangeRecord
return builder;
}
+ @Override
+ public Map getStateMap()
+ {
+ return Map.of("changeHeads", changeHeads,
+ "changes", changes,
+ "creator", creatorPeer.toLong());
+ }
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ changeHeads = new HashSet<>((Collection) map.get("changeHeads"));
+ changes = new HashSet<>((Collection) map.get("changes"));
+ creatorPeer = new NetworkObject.ObjectID((Long) map.get("creator"));
+ }
+
public String toString()
{
StringBuilder sb = new StringBuilder();
@@ -103,68 +120,69 @@ public class ObjectChangeRecord
return creatorPeer;
}
- public static class Change
+ @Override
+ public long getStorageID()
{
- private final NetworkObject.ObjectID objectID;
- private final Map beforeValues;
- private final Map afterValues;
-
- public Change(NetworkObject.ObjectID objectID, Map before, Map after)
- {
- this.objectID = objectID;
- this.beforeValues = before;
- this.afterValues = after;
- }
-
- public static Change createFromObjectChange(ObjectStatements.ObjectChange change)
- {
- return new Change(new NetworkObject.ObjectID(change.getObjectId()), change.getBeforeMap(), change.getAfterMap());
- }
-
- public static Change createFromObjectStates(ObjectStatements.ObjectState before, ObjectStatements.ObjectState after)
- {
- Map beforeValues = new HashMap<>();
- Map afterValues = new HashMap<>();
- for (String key: after.getValuesMap().keySet())
- {
- String beforeValue = before.getValuesOrDefault(key, null);
- String afterValue = after.getValuesOrDefault(key, null);
- if (!afterValue.equals(beforeValue))
- {
- beforeValues.put(key,beforeValue);
- afterValues.put(key,afterValue);
- }
- }
- if (!afterValues.isEmpty())
- {
- return new Change(new NetworkObject.ObjectID(before.getObjectId()), beforeValues, afterValues);
- }
- return null;
- }
-
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
- sb.append(objectID.toLong()).append(";"); // The object ID, then ;
- // now all key-value pairs in alphabetical order
- List keys = new ArrayList<>(beforeValues.keySet());
- Collections.sort(keys);
- for (String key : keys)
- {
- sb.append(key).append(":").append(afterValues.get(key));
- }
- sb.append(";");
- return sb.toString();
- }
-
-
- public ObjectStatements.ObjectChange.Builder buildObjectChange()
- {
- ObjectStatements.ObjectChange.Builder builder = ObjectStatements.ObjectChange.newBuilder();
- builder.putAllBefore(beforeValues);
- builder.putAllAfter(afterValues);
- builder.setObjectId(objectID.toLong());
- return builder;
- }
+ return getChangeID();
}
+
+ public Set getChangeHeads()
+ {
+ return changeHeads;
+ }
+
+ public record Change(NetworkObject.ObjectID objectID, Map beforeValues, Map afterValues)
+ {
+
+ public static Change createFromObjectChange(ObjectStatements.ObjectChange change)
+ {
+ return new Change(new NetworkObject.ObjectID(change.getObjectId()), change.getBeforeMap(), change.getAfterMap());
+ }
+
+ public static Change createFromObjectStates(ObjectStatements.ObjectState before, ObjectStatements.ObjectState after)
+ {
+ Map beforeValues = new HashMap<>();
+ Map afterValues = new HashMap<>();
+ for (String key : after.getValuesMap().keySet())
+ {
+ String beforeValue = before.getValuesOrDefault(key, null);
+ String afterValue = after.getValuesOrDefault(key, null);
+ if (!afterValue.equals(beforeValue))
+ {
+ beforeValues.put(key, beforeValue);
+ afterValues.put(key, afterValue);
+ }
+ }
+ if (!afterValues.isEmpty())
+ {
+ return new Change(new NetworkObject.ObjectID(before.getObjectId()), beforeValues, afterValues);
+ }
+ return null;
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(objectID.toLong()).append(";"); // The object ID, then ;
+ // now all key-value pairs in alphabetical order
+ List keys = new ArrayList<>(beforeValues.keySet());
+ Collections.sort(keys);
+ for (String key : keys)
+ {
+ sb.append(key).append(":").append(afterValues.get(key));
+ }
+ sb.append(";");
+ return sb.toString();
+ }
+
+
+ public ObjectStatements.ObjectChange.Builder buildObjectChange()
+ {
+ ObjectStatements.ObjectChange.Builder builder = ObjectStatements.ObjectChange.newBuilder();
+ builder.putAllBefore(beforeValues);
+ builder.putAllAfter(afterValues);
+ builder.setObjectId(objectID.toLong());
+ return builder;
+ }
+ }
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/ObjectChangeTransaction.java b/src/main/java/moe/nekojimi/friendcloud/ObjectChangeTransaction.java
index fb7596f..5788394 100644
--- a/src/main/java/moe/nekojimi/friendcloud/ObjectChangeTransaction.java
+++ b/src/main/java/moe/nekojimi/friendcloud/ObjectChangeTransaction.java
@@ -37,7 +37,7 @@ public class ObjectChangeTransaction implements Closeable
public ObjectChangeTransaction addObjectBeforeChange(NetworkObject.ObjectID id)
{
- NetworkObject object = Model.getInstance().getObject(id);
+ NetworkObject object = Main.getInstance().getModel().getObject(id);
if (object != null)
beforeStates.put(id, object.buildObjectState().build());
return this;
@@ -52,7 +52,7 @@ public class ObjectChangeTransaction implements Closeable
for (Map.Entry entry : beforeStates.entrySet())
{
- ObjectStatements.ObjectState afterState = Model.getInstance().getObject(entry.getKey()).buildObjectState().build();
+ ObjectStatements.ObjectState afterState = Main.getInstance().getModel().getObject(entry.getKey()).buildObjectState().build();
ObjectChangeRecord.Change change = ObjectChangeRecord.Change.createFromObjectStates(entry.getValue(), afterState);
changes.add(change);
}
@@ -66,7 +66,7 @@ public class ObjectChangeTransaction implements Closeable
// end the transaction and get the change object
ObjectChangeRecord objectChangeRecord = endTransaction();
// add the new change to the model
- Model.getInstance().addChangeRecord(objectChangeRecord);
+ Main.getInstance().getModel().addChangeRecord(objectChangeRecord);
// create a task to propagate the change to other peers
Main.getInstance().getExecutor().submit(new PropagateMessageTask(objectChangeRecord.buildObjectChangeMessage().build()));
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/Util.java b/src/main/java/moe/nekojimi/friendcloud/Util.java
index 5e8a9ab..4534dbe 100644
--- a/src/main/java/moe/nekojimi/friendcloud/Util.java
+++ b/src/main/java/moe/nekojimi/friendcloud/Util.java
@@ -16,4 +16,5 @@ public class Util
}
return ret;
}
+
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/filesystem/FUSEAccess.java b/src/main/java/moe/nekojimi/friendcloud/filesystem/FUSEAccess.java
index 4ca96be..aeca1c3 100644
--- a/src/main/java/moe/nekojimi/friendcloud/filesystem/FUSEAccess.java
+++ b/src/main/java/moe/nekojimi/friendcloud/filesystem/FUSEAccess.java
@@ -2,7 +2,7 @@ package moe.nekojimi.friendcloud.filesystem;
import jnr.ffi.Pointer;
import moe.nekojimi.friendcloud.FileRemoteAccess;
-import moe.nekojimi.friendcloud.Model;
+import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.objects.NetworkFSNode;
import moe.nekojimi.friendcloud.objects.NetworkFile;
import moe.nekojimi.friendcloud.objects.NetworkFolder;
@@ -34,7 +34,7 @@ public class FUSEAccess extends FuseStubFS
filter.apply(buf, "..", null, 0);
// filter.apply(buf,"hello", null, 0);
- for (NetworkFSNode fsNode : Model.getInstance().listFSNodes(path))
+ for (NetworkFSNode fsNode : Main.getInstance().getModel().listFSNodes(path))
{
filter.apply(buf, fsNode.getName(), null, 0);
}
@@ -54,7 +54,7 @@ public class FUSEAccess extends FuseStubFS
}
else
{
- NetworkFSNode fsNode = Model.getInstance().getFSNode(path);
+ NetworkFSNode fsNode = Main.getInstance().getModel().getFSNode(path);
switch (fsNode)
{
case null ->
@@ -85,7 +85,7 @@ public class FUSEAccess extends FuseStubFS
public int open(String path, FuseFileInfo fi)
{
System.out.println("FUSE: Opening file " + path);
- NetworkFSNode fsNode = Model.getInstance().getFSNode(path);
+ NetworkFSNode fsNode = Main.getInstance().getModel().getFSNode(path);
if (fsNode == null)
{
System.err.println("FUSE: Failed to open file " + path + ": not found");
diff --git a/src/main/java/moe/nekojimi/friendcloud/network/PeerConnection.java b/src/main/java/moe/nekojimi/friendcloud/network/PeerConnection.java
index bdc5d04..574eff1 100644
--- a/src/main/java/moe/nekojimi/friendcloud/network/PeerConnection.java
+++ b/src/main/java/moe/nekojimi/friendcloud/network/PeerConnection.java
@@ -6,7 +6,7 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import moe.nekojimi.friendcloud.FilePieceAccess;
-import moe.nekojimi.friendcloud.Model;
+import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.objects.NetworkFile;
import moe.nekojimi.friendcloud.objects.NetworkObject;
import moe.nekojimi.friendcloud.objects.Peer;
@@ -86,7 +86,7 @@ public abstract class PeerConnection extends Thread
{
CommonMessages.MessageHeader.Builder headerBuilder = CommonMessages.MessageHeader.newBuilder()
.setMessageId(nextMessageId)
- .setSenderId(Model.getInstance().getSelfPeer().getObjectID().toLong());
+ .setSenderId(Main.getInstance().getModel().getSelfPeer().getObjectID().toLong());
if (inReplyTo != null)
headerBuilder.setReplyToMessageId(inReplyTo.getMessageId());
@@ -131,7 +131,7 @@ public abstract class PeerConnection extends Thread
NetworkObject.ObjectID senderID = new NetworkObject.ObjectID(header.getSenderId());
if (peer == null)
- peer = (Peer) Model.getInstance().getOrCreateObject(senderID);
+ peer = (Peer) Main.getInstance().getModel().getOrCreateObject(senderID);
else
{
if (!senderID.equals(peer.getObjectID()))
@@ -187,15 +187,12 @@ public abstract class PeerConnection extends Thread
if (body.is(ObjectStatements.ObjectListRequest.class))
{
ObjectStatements.ObjectListRequest objectListRequest = body.unpack(ObjectStatements.ObjectListRequest.class);
- List objectIDS = Model.getInstance().listObjects(new HashSet<>(objectListRequest.getTypesList()));
+ List objects = Main.getInstance().getModel().listObjects(new HashSet<>(objectListRequest.getTypesList()));
ObjectStatements.ObjectList.Builder objectList = ObjectStatements.ObjectList.newBuilder();
- for (NetworkObject.ObjectID objectID : objectIDS)
+ for (NetworkObject object : objects)
{
- NetworkObject networkObject = Model.getInstance().getOrCreateObject(objectID);
- objectList.addStates(networkObject.buildObjectState());
-// networkObject.updateFromStateMessage();
-// objectList.addState(networkObject.buildObjectState());
+ objectList.addStates(object.buildObjectState());
}
System.out.println("Replying to ObjectListRequest with ObjectList, objects=" + objectList.getStatesList());
sendMessage(wrapMessage(objectList.build(), header));
@@ -208,7 +205,7 @@ public abstract class PeerConnection extends Thread
replyWithError(CommonMessages.Error.ERROR_INVALID_ARGUMENT, header);
}
- NetworkFile networkFile = (NetworkFile) Model.getInstance().getObject(new NetworkObject.ObjectID(filePiecesRequestMessage.getFileId()));
+ NetworkFile networkFile = (NetworkFile) Main.getInstance().getModel().getObject(new NetworkObject.ObjectID(filePiecesRequestMessage.getFileId()));
if (networkFile == null)
{
replyWithError(CommonMessages.Error.ERROR_OBJECT_NOT_FOUND, header);
diff --git a/src/main/java/moe/nekojimi/friendcloud/network/requests/ObjectChangeRequest.java b/src/main/java/moe/nekojimi/friendcloud/network/requests/ObjectChangeRequest.java
index 5135b66..5dade64 100644
--- a/src/main/java/moe/nekojimi/friendcloud/network/requests/ObjectChangeRequest.java
+++ b/src/main/java/moe/nekojimi/friendcloud/network/requests/ObjectChangeRequest.java
@@ -2,8 +2,7 @@ package moe.nekojimi.friendcloud.network.requests;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.Message;
-import moe.nekojimi.friendcloud.Model;
+import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.ObjectChangeRecord;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
@@ -34,7 +33,7 @@ public class ObjectChangeRequest extends Request>
+public class ObjectListRequest extends Request>
{
private final Set types;
@@ -44,7 +44,7 @@ public class ObjectListRequest extends Request getStateMap()
+ {
+ Map ret = super.getStateMap();
+ ret.put("name", name);
+ ret.put("parent", parent != null ? parent.getStorageID() : 0L);
+ return ret;
+ }
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ name = map.get("name").toString();
+ parent = (NetworkFolder) Main.getInstance().getModel().getObject(new ObjectID(((Number)map.get("parent")).longValue()));
+ }
+
public String getName()
{
return name;
diff --git a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFile.java b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFile.java
index 5b6846a..9555359 100644
--- a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFile.java
+++ b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFile.java
@@ -9,10 +9,7 @@ import java.nio.file.Files;
import java.nio.file.attribute.FileAttribute;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.HexFormat;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.TimeUnit;
public class NetworkFile extends NetworkFSNode
@@ -20,6 +17,7 @@ public class NetworkFile extends NetworkFSNode
private static final int MIN_PIECE_SIZE = 0x400; // 1KiB
private static final int MAX_PIECE_SIZE = 0x100000; // 1 MiB
private static final int IDEAL_PIECE_COUNT = 1024;
+ private static File tempDirectory = null;
private long size = 0;
private long pieceSize = 0;
@@ -31,7 +29,6 @@ public class NetworkFile extends NetworkFSNode
private final Map fileStates = new HashMap<>();
private BitSet pieces = new BitSet();
- private static File tempDirectory = null;
// private List pieces = new ArrayList<>();
public NetworkFile(ObjectID objectID)
@@ -118,7 +115,6 @@ public class NetworkFile extends NetworkFSNode
hash = HexFormat.of().parseHex(state.getValuesOrThrow("hash"));
if (state.containsValues("pieceSize"))
pieceSize = Long.parseLong(state.getValuesOrThrow("pieceSize"));
-
}
@Override
@@ -131,6 +127,34 @@ public class NetworkFile extends NetworkFSNode
.putValues("pieceSize", Long.toString(pieceSize));
}
+ @Override
+ public Map getStateMap()
+ {
+ Map ret = super.getStateMap();
+ ret.put("size", size);
+ ret.put("hash", HexFormat.of().formatHex(hash));
+ ret.put("pieceSize", pieceSize);
+ ret.put("pieces", Arrays.stream(pieces.toLongArray()).boxed().toList());
+ ret.put("localFile", localFile != null ? localFile.getAbsolutePath() : "");
+ return ret;
+ }
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ super.updateFromStateMap(map);
+ size = ((Number) map.get("size")).longValue();
+ hash = HexFormat.of().parseHex((CharSequence) map.get("hash"));
+ pieceSize = ((Number) map.get("pieceSize")).longValue();
+ ArrayList pieces1 = (ArrayList) map.get("pieces");
+ pieces = BitSet.valueOf(pieces1.stream().mapToLong(Number::longValue).toArray());
+ String localFilePath = (String) map.get("localFile");
+ if (localFilePath.isEmpty())
+ localFile = null;
+ else
+ localFile = new File(localFilePath);
+ }
+
public File getLocalFile()
{
return localFile;
diff --git a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFolder.java b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFolder.java
index 9e6da28..b8f02c3 100644
--- a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFolder.java
+++ b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkFolder.java
@@ -2,13 +2,13 @@ package moe.nekojimi.friendcloud.objects;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
+import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
public class NetworkFolder extends NetworkFSNode
{
-// private final SortedSet children = new TreeSet<>((a,b)->Long.compare(a.getId(),b.getId()));
public NetworkFolder(ObjectID objectID)
{
@@ -18,8 +18,18 @@ public class NetworkFolder extends NetworkFSNode
@Override
public ObjectStatements.ObjectState.Builder buildObjectState()
{
- return super.buildObjectState().putValues("name", name);
+ return super.buildObjectState();
}
+ @Override
+ public Map getStateMap()
+ {
+ return super.getStateMap();
+ }
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ super.updateFromStateMap(map);
+ }
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkObject.java b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkObject.java
index f0897ef..07fcc1c 100644
--- a/src/main/java/moe/nekojimi/friendcloud/objects/NetworkObject.java
+++ b/src/main/java/moe/nekojimi/friendcloud/objects/NetworkObject.java
@@ -1,10 +1,13 @@
package moe.nekojimi.friendcloud.objects;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
+import moe.nekojimi.friendcloud.storage.Storable;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Objects;
-public abstract class NetworkObject
+public abstract class NetworkObject implements Storable
{
private final ObjectID objectID;
@@ -18,6 +21,21 @@ public abstract class NetworkObject
return objectID;
}
+ @Override
+ public long getStorageID()
+ {
+ return getObjectID().toLong();
+ }
+
+
+ @Override
+ public Map getStateMap()
+ {
+ Map ret = new HashMap<>();
+ ret.put("id", objectID.toLong());
+ return ret;
+ }
+
public synchronized void updateFromStateMessage(ObjectStatements.ObjectState state)
{
if (state.getObjectId() != objectID.toLong())
diff --git a/src/main/java/moe/nekojimi/friendcloud/objects/Peer.java b/src/main/java/moe/nekojimi/friendcloud/objects/Peer.java
index 9e67ee5..ca67e8d 100644
--- a/src/main/java/moe/nekojimi/friendcloud/objects/Peer.java
+++ b/src/main/java/moe/nekojimi/friendcloud/objects/Peer.java
@@ -4,10 +4,7 @@ import moe.nekojimi.friendcloud.protos.ObjectStatements;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
public class Peer extends NetworkObject
@@ -73,7 +70,26 @@ public class Peer extends NetworkObject
return builder;
}
- public void addAddress(URI address)
+ @Override
+ public Map getStateMap()
+ {
+ Map ret = super.getStateMap();
+ ret.put("userName", userName);
+ ret.put("systemName", systemName);
+ ret.put("addresses", addresses);
+ return ret;
+ }
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ userName = map.get("userName").toString();
+ systemName = map.get("systemName").toString();
+ addresses.clear();
+ addresses.addAll((Collection extends URI>) map.get("addresses"));
+ }
+
+ public void addAddress(URI address)
{
addresses.add(address);
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/objects/PeerFileState.java b/src/main/java/moe/nekojimi/friendcloud/objects/PeerFileState.java
index 813c557..79b80c6 100644
--- a/src/main/java/moe/nekojimi/friendcloud/objects/PeerFileState.java
+++ b/src/main/java/moe/nekojimi/friendcloud/objects/PeerFileState.java
@@ -1,8 +1,10 @@
package moe.nekojimi.friendcloud.objects;
-import moe.nekojimi.friendcloud.Model;
+import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
+import java.util.Map;
+
public class PeerFileState extends NetworkObject
{
private Peer peer;
@@ -19,8 +21,8 @@ public class PeerFileState extends NetworkObject
public void updateFromStateMessage(ObjectStatements.ObjectState state)
{
super.updateFromStateMessage(state);
- peer = (Peer) Model.getInstance().getOrCreateObject(new ObjectID(Long.parseLong(state.getValuesOrThrow("peer"))));
- file = (NetworkFile) Model.getInstance().getOrCreateObject(new ObjectID(Long.parseLong(state.getValuesOrThrow("file"))));
+ peer = (Peer) Main.getInstance().getModel().getOrCreateObject(new ObjectID(Long.parseLong(state.getValuesOrThrow("peer"))));
+ file = (NetworkFile) Main.getInstance().getModel().getOrCreateObject(new ObjectID(Long.parseLong(state.getValuesOrThrow("file"))));
if (state.containsValues("progress"))
progress = Double.parseDouble(state.getValuesOrThrow("progress"));
@@ -72,4 +74,10 @@ public class PeerFileState extends NetworkObject
{
return peer;
}
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+
+ }
}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/CachingDataStore.java b/src/main/java/moe/nekojimi/friendcloud/storage/CachingDataStore.java
new file mode 100644
index 0000000..6beb1ba
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/CachingDataStore.java
@@ -0,0 +1,92 @@
+package moe.nekojimi.friendcloud.storage;
+
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+public class CachingDataStore extends DataStore
+{
+ private final DataStore backend;
+
+ public CachingDataStore(DataStore backend)
+ {
+ this.backend = backend;
+ }
+
+ @Override
+ public synchronized DAO getDAOForClass(Class clazz)
+ {
+ if (daos.containsKey(clazz))
+ return (DAO) daos.get(clazz);
+ else
+ {
+ CachingDAO ret = new CachingDAO<>(clazz);
+ daos.put(clazz, ret);
+ return ret;
+ }
+ }
+
+ @Override
+ public FSNodeDAO getFSDAO()
+ {
+ return backend.getFSDAO();
+ }
+
+ private Map, CachingDAO>> daos = new HashMap<>();
+
+ public class CachingDAO implements DAO
+ {
+ private final DAO backendDao;
+ private WeakHashMap cache = new WeakHashMap<>();
+
+ public CachingDAO(Class clazz)
+ {
+ this.backendDao = backend.getDAOForClass(clazz);
+ }
+
+ @Override
+ public synchronized List getAll()
+ {
+ List ret = new ArrayList<>();
+ ret.addAll(cache.values());
+ for (T t : backendDao.getAll())
+ {
+ if (!cache.containsKey(t.getStorageID()))
+ {
+ ret.add(t);
+ cache.put(t.getStorageID(), t);
+ }
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean exists(long id)
+ {
+ return backendDao.exists(id);
+ }
+
+ @Override
+ public T create(long id)
+ {
+ T t = backendDao.create(id);
+ cache.put(id, t);
+ return t;
+ }
+
+ @Override
+ public T get(long id)
+ {
+ T t = backendDao.get(id);
+ cache.put(id, t);
+ return t;
+ }
+
+ @Override
+ public void update(T object)
+ {
+ long id = object.getStorageID();
+ backendDao.update(object);
+ cache.put(id, object);
+ }
+ }
+}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/DataStore.java b/src/main/java/moe/nekojimi/friendcloud/storage/DataStore.java
new file mode 100644
index 0000000..2b33099
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/DataStore.java
@@ -0,0 +1,89 @@
+package moe.nekojimi.friendcloud.storage;
+
+import moe.nekojimi.friendcloud.objects.NetworkFSNode;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class DataStore
+{
+ public abstract DAO getDAOForClass(Class clazz);
+ public abstract FSNodeDAO getFSDAO();
+
+ public interface DAO
+ {
+ List getAll();
+ boolean exists(long id);
+ T create(long id);
+ T get(long id);
+ default T getOrCreate(long id)
+ {
+ T old = get(id);
+ if (old != null)
+ return old;
+ return create(id);
+ }
+ void update(T object);
+ }
+
+ public interface FSNodeDAO extends DAO
+ {
+ default List getPathChildren(String path)
+ {
+ System.err.println("WARNING: using violently cursed implementation of getPathChildren, make a better one now!!!");
+ return getAll().stream().filter(networkFSNode -> networkFSNode.getNetworkPath().startsWith(path)).toList();
+ }
+ }
+
+ public class ClassFusionDAO implements DAO
+ {
+ private final Set> subclasses;
+
+ @SafeVarargs
+ public ClassFusionDAO(Class extends T>... subclasses)
+ {
+ this.subclasses = Set.of(subclasses);
+ }
+
+ @Override
+ public List getAll()
+ {
+ List ret = new ArrayList<>();
+ for (Class extends T> subclass : subclasses)
+ ret.addAll(getDAOForClass(subclass).getAll());
+ return ret;
+ }
+
+ @Override
+ public boolean exists(long id)
+ {
+ for (Class extends T> subclass : subclasses)
+ if (getDAOForClass(subclass).exists(id))
+ return true;
+ return false;
+ }
+
+ @Override
+ public T create(long id)
+ {
+ throw new UnsupportedOperationException("That type's abstract, can't create it");
+ }
+
+ @Override
+ public T get(long id)
+ {
+ for (Class extends T> subclass : subclasses)
+ if (getDAOForClass(subclass).exists(id))
+ return getDAOForClass(subclass).get(id);
+ return null;
+ }
+
+ @Override
+ public void update(T object)
+ {
+ throw new UnsupportedOperationException("That type's abstract, can't update it");
+ }
+ }
+}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/LocalData.java b/src/main/java/moe/nekojimi/friendcloud/storage/LocalData.java
new file mode 100644
index 0000000..e159629
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/LocalData.java
@@ -0,0 +1,40 @@
+package moe.nekojimi.friendcloud.storage;
+
+import moe.nekojimi.friendcloud.Main;
+import moe.nekojimi.friendcloud.objects.NetworkObject;
+import moe.nekojimi.friendcloud.objects.Peer;
+
+import java.util.Map;
+
+public class LocalData implements Storable
+{
+ private Peer localPeer;
+
+ public Peer getLocalPeer()
+ {
+ return localPeer;
+ }
+
+ public void setLocalPeer(Peer localPeer)
+ {
+ this.localPeer = localPeer;
+ }
+
+ @Override
+ public long getStorageID()
+ {
+ return 0;
+ }
+
+ @Override
+ public Map getStateMap()
+ {
+ return Map.of("localPeer", localPeer.getObjectID().toLong());
+ }
+
+ @Override
+ public void updateFromStateMap(Map map)
+ {
+ localPeer = (Peer) Main.getInstance().getModel().getObject(new NetworkObject.ObjectID((Long) map.get("localPeer")));
+ }
+}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/Model.java b/src/main/java/moe/nekojimi/friendcloud/storage/Model.java
new file mode 100644
index 0000000..1078689
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/Model.java
@@ -0,0 +1,184 @@
+package moe.nekojimi.friendcloud.storage;
+
+import moe.nekojimi.friendcloud.ObjectChangeRecord;
+import moe.nekojimi.friendcloud.objects.*;
+import moe.nekojimi.friendcloud.protos.ObjectStatements;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Model
+{
+
+ private final CachingDataStore dataStore;
+
+ private final int systemID;
+
+ private Peer selfPeer = null;
+ private ObjectChangeRecord currentChange;
+
+ public Model(DataStore dataStore)
+ {
+ this.dataStore = new CachingDataStore(dataStore);
+ Random ran = new Random();
+ systemID = ran.nextInt() & 0x00FFFFFF;
+ }
+
+ public void setSelfPeer(Peer selfPeer)
+ {
+ this.selfPeer = selfPeer;
+ }
+
+ public synchronized Peer getSelfPeer()
+ {
+ if (selfPeer == null)
+ selfPeer = createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_PEER);
+ return selfPeer;
+ }
+ // private Map nodes = new HashMap<>();
+
+ public synchronized NetworkObject.ObjectID getNextObjectID(ObjectStatements.ObjectType type)
+ {
+ Random ran = new Random();
+ int randomNumber = ran.nextInt();
+ NetworkObject.ObjectID objectID = new NetworkObject.ObjectID(type, systemID, randomNumber);
+ System.out.println("Assigned new object ID: " + objectID);
+ return objectID;
+ }
+
+ public static Class extends NetworkObject> getNetworkObjectClassByType(ObjectStatements.ObjectType type)
+ {
+ return switch (type)
+ {
+ case OBJECT_TYPE_FILE -> NetworkFile.class;
+ case OBJECT_TYPE_FOLDER -> NetworkFolder.class;
+ case OBJECT_TYPE_PEER -> Peer.class;
+ case OBJECT_TYPE_PEER_FILE_STATE -> PeerFileState.class;
+ case OBJECT_TYPE_UNSPECIFIED, UNRECOGNIZED -> throw new IllegalArgumentException("???");
+ default -> throw new UnsupportedOperationException("NYI");
+ };
+ }
+
+ public synchronized T createObjectByID(NetworkObject.ObjectID id)
+ {
+ if (id.toLong() == 0)
+ throw new IllegalArgumentException("Cannot create an object with ID=0!");
+ ObjectStatements.ObjectType type = id.getType();
+ System.out.println("Creating new object with type: " + type.name());
+ if (type == ObjectStatements.ObjectType.OBJECT_TYPE_UNSPECIFIED)
+ throw new IllegalArgumentException();
+ T ret = (T) dataStore.getDAOForClass(getNetworkObjectClassByType(type)).create(id.toLong());
+ return ret;
+ }
+
+ public synchronized T createObjectByType(ObjectStatements.ObjectType type)
+ {
+ return createObjectByID(getNextObjectID(type));
+ }
+
+ public synchronized T getObject(NetworkObject.ObjectID id)
+ {
+ if (id.toLong() == 0)
+ return null;
+ Class clazz = (Class) getNetworkObjectClassByType(id.getType());
+ return dataStore.getDAOForClass(clazz).get(id.toLong());
+ }
+
+ public synchronized T getOrCreateObject(NetworkObject.ObjectID id)
+ {
+ if (id.toLong() == 0)
+ return null;
+ Class clazz = (Class) getNetworkObjectClassByType(id.getType());
+ return dataStore.getDAOForClass(clazz).getOrCreate(id.toLong());
+ }
+
+ public synchronized List listObjects(Set types)
+ {
+ List ret = new ArrayList<>();
+ Set> classes = types.stream().map(Model::getNetworkObjectClassByType).collect(Collectors.toSet());
+ for (Class extends NetworkObject> clazz: classes)
+ {
+ List extends NetworkObject> list = dataStore.getDAOForClass(clazz).getAll();
+ ret.addAll(list);
+ }
+ return ret;
+ }
+
+ public synchronized List listFSNodes(String path)
+ {
+ //TODO: dumbest algorithm in the world
+
+ List ret = new ArrayList<>();
+ for (NetworkObject object : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE, ObjectStatements.ObjectType.OBJECT_TYPE_FOLDER)))
+ {
+ NetworkFSNode fsNode = (NetworkFSNode) object;
+ String networkPath = fsNode.getNetworkPath();
+ if (networkPath.substring(0, networkPath.lastIndexOf("/")+1).equals(path))
+ ret.add(fsNode);
+ }
+ return ret;
+ }
+
+ public synchronized NetworkFSNode getFSNode(String path)
+ {
+ for (NetworkObject object : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE, ObjectStatements.ObjectType.OBJECT_TYPE_FOLDER)))
+ {
+ NetworkFSNode fsNode = (NetworkFSNode) object;
+ String networkPath = fsNode.getNetworkPath();
+ if (networkPath.equals(path))
+ return fsNode;
+ }
+ return null;
+ }
+
+ public synchronized void addChangeRecord(ObjectChangeRecord record)
+ {
+ dataStore.getDAOForClass(ObjectChangeRecord.class).update(record);
+ }
+
+ public ObjectChangeRecord getChangeRecord(long id)
+ {
+ return dataStore.getDAOForClass(ObjectChangeRecord.class).get(id);
+ }
+
+ public void applyChangeRecord(ObjectChangeRecord record)
+ {
+ if (!record.getChangeHeads().contains(currentChange.getChangeID()))
+ throw new IllegalStateException("Change does not apply! Valid change heads=" + record.getChangeHeads() + ", we are in state " + currentChange.getChangeID());
+ addChangeRecord(record);
+
+
+// if (record == null)
+// throw new IllegalArgumentException("Cannot apply unknown change!");
+ }
+
+ public Set getChangeHeads()
+ {
+ // stupid algorithm - start with all of the changes, then remove the ones that are referenced by something
+ // TODO: better algorithm
+ Set ret = new HashSet<>(dataStore.getDAOForClass(ObjectChangeRecord.class).getAll());
+// for (ObjectChangeRecord record : changeRecords.values())
+// {
+// throw new UnsupportedOperationException("NYI");
+// }
+ throw new UnsupportedOperationException("NYI");
+ }
+
+ public Set listOtherPeers()
+ {
+ Set ret = new HashSet<>();
+ for (NetworkObject object : listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_PEER)))
+ {
+ Peer peer = (Peer) object;
+ if (peer != getSelfPeer())
+ ret.add(peer);
+ }
+ return ret;
+ }
+
+ public void objectChanged(T storable)
+ {
+ Class clazz = (Class) storable.getClass();
+ dataStore.getDAOForClass(clazz).update(storable);
+ }
+}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/Storable.java b/src/main/java/moe/nekojimi/friendcloud/storage/Storable.java
new file mode 100644
index 0000000..372a5a1
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/Storable.java
@@ -0,0 +1,29 @@
+package moe.nekojimi.friendcloud.storage;
+
+import java.util.Map;
+
+public interface Storable
+{
+ /**
+ * Gets the unique ID used for persisting the object on disk. Must remain constant for an object's lifetime.
+ * @return the unique ID.
+ */
+ long getStorageID();
+
+// /**
+// * Gets a constant string specifying the namespace that the ID numbers returned by getStorageID() are unique within. Defaults to the class name.
+// * @return the namespace name.
+// */
+// default String getStorageNamespace()
+// {
+// return this.getClass().getName().toLowerCase();
+// }
+
+ /**
+ * Gets a map representing all serializable (i.e. to be saved) fields of this object, by name.
+ * Fields may be any of the following types: String, Integer, Long, Double, Collection\, Boolean, Map\
+ */
+ Map getStateMap();
+
+ void updateFromStateMap(Map map);
+}
diff --git a/src/main/java/moe/nekojimi/friendcloud/storage/StupidJSONFileStore.java b/src/main/java/moe/nekojimi/friendcloud/storage/StupidJSONFileStore.java
new file mode 100644
index 0000000..37c1a9c
--- /dev/null
+++ b/src/main/java/moe/nekojimi/friendcloud/storage/StupidJSONFileStore.java
@@ -0,0 +1,334 @@
+package moe.nekojimi.friendcloud.storage;
+
+import moe.nekojimi.friendcloud.objects.*;
+import org.jetbrains.annotations.NotNull;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.util.*;
+
+public class StupidJSONFileStore extends DataStore
+{
+ private final File storageDirectory;
+
+ private final Map, DAO extends Storable>> daos = new HashMap<>();
+
+ public StupidJSONFileStore(File storageDirectory)
+ {
+ this.storageDirectory = storageDirectory;
+ if (!storageDirectory.exists())
+ {
+ if (!storageDirectory.mkdirs())
+ throw new RuntimeException("Unable to create storage directory " + storageDirectory.getAbsolutePath());
+ }
+ }
+
+ @Override
+ public DAO getDAOForClass(Class clazz)
+ {
+ if (daos.containsKey(clazz))
+ return (DAO) daos.get(clazz);
+
+ DAO ret;
+ if (clazz.equals(NetworkFile.class))
+ ret = (DAO) new NetworkFileDAO();
+ else if (clazz.equals(NetworkFolder.class))
+ ret = (DAO) new NetworkFolderDAO();
+ else if (clazz.equals(Peer.class))
+ ret = (DAO) new PeerDAO();
+ else if (clazz.equals(PeerFileState.class))
+ ret = (DAO) new PeerFileStateDAO();
+ else if (clazz.equals(NetworkFSNode.class))
+ ret = (DAO) new NetworkFSNodeDAO();
+ else
+ throw new UnsupportedOperationException("Requested DAO for unsupported type " + clazz.getCanonicalName());
+
+ daos.put(clazz, ret);
+ return (DAO) ret;
+ }
+
+ @Override
+ public FSNodeDAO getFSDAO()
+ {
+ return new NetworkFSNodeDAO();
+ }
+
+ private abstract class JSONObjectDAO implements DAO
+ {
+ protected final Set<@NotNull Class>> VALID_JSON_CLASSES = Set.of(new Class[]{Boolean.class, Double.class, Integer.class, Long.class, String.class, JSONArray.class, JSONObject.class, JSONObject.NULL.getClass()});
+
+ protected abstract String getNamespace();
+ protected File getNamespaceDirectory()
+ {
+ File ret = new File(storageDirectory, getNamespace());
+ if (!ret.exists())
+ ret.mkdir();
+ return ret;
+ }
+ protected abstract T makeBlank(long id);
+ protected T jsonToObject(JSONObject json)
+ {
+ long id = json.getLong("storageID");
+ T ret = makeBlank(id);
+ ret.updateFromStateMap(jsonToMap(json));
+ return ret;
+ }
+
+ protected JSONObject objectToJson(T object)
+ {
+ Map stateMap = object.getStateMap();
+ return mapToJson(stateMap).put("storageID", object.getStorageID());
+ }
+
+ private JSONObject mapToJson(Map stateMap)
+ {
+ JSONObject ret = new JSONObject();
+ for (Map.Entry e : stateMap.entrySet())
+ {
+ Class> valueClass = e.getValue().getClass();
+ if (VALID_JSON_CLASSES.contains(valueClass))
+ ret.put(e.getKey(), e.getValue());
+ else if (Collection.class.isAssignableFrom(valueClass))
+ {
+ Collection> valueCollection = (Collection>) e.getValue();
+ List