First version of data persistence architecture

This commit is contained in:
Nekojimi 2025-09-18 11:01:12 +01:00
parent 54b31ac7d1
commit 5b7c6a857a
25 changed files with 1078 additions and 320 deletions

24
pom.xml
View file

@ -69,6 +69,30 @@
<artifactId>gethostname4j</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>es.blackleg</groupId>
<artifactId>jlibnotify</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-core</artifactId>
<version>5.1.0-SNAPSHOT</version>
</dependency>
<!-- Unixsocket support using native unix socket implementation of Java (since Java 16) -->
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-transport-native-unixsocket</artifactId>
<version>5.1.0-SNAPSHOT</version>
</dependency>
<!-- Add this if you want support for TCP based DBus connections -->
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>dbus-java-transport-tcp</artifactId>
<version>5.1.0-SNAPSHOT</version>
</dependency>
</dependencies>

View file

@ -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<String> sharedFiles = new ArrayList<>();
private List<String> sharedFilePaths = new ArrayList<>();
@Parameter(names="-known-peer")
private List<String> 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<File> sharedFiles = new HashSet<>();
for (String sharedFilePath: sharedFilePaths)
{
File file = new File(sharedFilePath);
if (file.exists())
sharedFiles.add(new File(sharedFilePath));
}
List<NetworkObject> knownFiles = model.listObjects(Set.of(ObjectStatements.ObjectType.OBJECT_TYPE_FILE));
for (NetworkObject knownFile: knownFiles)
{
System.out.println("Adding shared network file: " + file.getAbsolutePath());
NetworkFile f = (NetworkFile) knownFile;
boolean removed = sharedFiles.remove(f.getLocalFile());
if (removed)
System.out.println("Identified known local file " + f.getObjectID() + " = " + f.getLocalFile());
}
NetworkFile networkFile = (NetworkFile) Model.getInstance().createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_FILE);
networkFile.updateFromLocalFile(file);
for (File sharedFile: sharedFiles)
{
if (sharedFile.exists())
{
System.out.println("Adding shared network file: " + sharedFile.getAbsolutePath());
PeerFileState peerFileState = (PeerFileState) Model.getInstance().createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_PEER_FILE_STATE);
peerFileState.setNode(Model.getInstance().getSelfPeer());
peerFileState.setFile(networkFile);
peerFileState.setProgress(100);
NetworkFile networkFile = (NetworkFile) model.createObjectByType(ObjectStatements.ObjectType.OBJECT_TYPE_FILE);
networkFile.updateFromLocalFile(sharedFile);
model.objectChanged(networkFile);
// 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<List<NetworkObject>> 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;
}
}

View file

@ -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<NetworkObject.ObjectID, NetworkObject> objects = new HashMap<>();
private final int systemID;
private Peer selfPeer = null;
private ObjectChangeRecord currentChange;
private final Map<Long, ObjectChangeRecord> 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<Long, Node> 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<NetworkObject.ObjectID> listObjects(Set<ObjectStatements.ObjectType> 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<NetworkFSNode> listFSNodes(String path)
{
//TODO: dumbest algorithm in the world
List<NetworkFSNode> 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<ObjectChangeRecord> getChangeHeads()
{
// stupid algorithm - start with all of the changes, then remove the ones that are referenced by something
// TODO: better algorithm
Set<ObjectChangeRecord> ret = new HashSet<>(changeRecords.values());
for (ObjectChangeRecord record : changeRecords.values())
{
}
}
public Set<Peer> listOtherPeers()
{
Set<Peer> 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;
}
}

View file

@ -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<Long> changeHeads = new HashSet<>();
private final Set<Change> changes = new HashSet<>();
private NetworkObject.ObjectID creatorPeer;
private Set<Long> changeHeads = new HashSet<>();
private Set<Change> changes = new HashSet<>();
public ObjectChangeRecord(NetworkObject.ObjectID creatorPeer)
{
@ -69,6 +70,22 @@ public class ObjectChangeRecord
return builder;
}
@Override
public Map<String, Object> getStateMap()
{
return Map.of("changeHeads", changeHeads,
"changes", changes,
"creator", creatorPeer.toLong());
}
@Override
public void updateFromStateMap(Map<String, Object> 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,19 +120,20 @@ public class ObjectChangeRecord
return creatorPeer;
}
public static class Change
@Override
public long getStorageID()
{
private final NetworkObject.ObjectID objectID;
private final Map<String,String> beforeValues;
private final Map<String,String> afterValues;
public Change(NetworkObject.ObjectID objectID, Map<String, String> before, Map<String, String> after)
{
this.objectID = objectID;
this.beforeValues = before;
this.afterValues = after;
return getChangeID();
}
public Set<Long> getChangeHeads()
{
return changeHeads;
}
public record Change(NetworkObject.ObjectID objectID, Map<String, String> beforeValues, Map<String, String> afterValues)
{
public static Change createFromObjectChange(ObjectStatements.ObjectChange change)
{
return new Change(new NetworkObject.ObjectID(change.getObjectId()), change.getBeforeMap(), change.getAfterMap());

View file

@ -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<NetworkObject.ObjectID, ObjectStatements.ObjectState> 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()));
}

View file

@ -16,4 +16,5 @@ public class Util
}
return ret;
}
}

View file

@ -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");

View file

@ -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<NetworkObject.ObjectID> objectIDS = Model.getInstance().listObjects(new HashSet<>(objectListRequest.getTypesList()));
List<NetworkObject> 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);

View file

@ -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<ObjectStatements.ObjectChangeRe
{
ObjectStatements.ObjectChangeMessage objectChangeMessage = reply.unpack(ObjectStatements.ObjectChangeMessage.class);
ObjectChangeRecord objectChangeRecord = ObjectChangeRecord.createFromChangeMessage(objectChangeMessage);
Model.getInstance().applyChangeRecord(objectChangeRecord);
Main.getInstance().getModel().applyChangeRecord(objectChangeRecord);
return true;
}

View file

@ -2,7 +2,7 @@ package moe.nekojimi.friendcloud.network.requests;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import moe.nekojimi.friendcloud.Model;
import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.objects.NetworkObject;
import moe.nekojimi.friendcloud.protos.ObjectStatements;
@ -44,7 +44,7 @@ public class ObjectListRequest extends Request<ObjectStatements.ObjectListReques
for (ObjectStatements.ObjectState objectState : objectList.getStatesList())
{
System.out.println("Received state of object " + objectState.getObjectId());
NetworkObject object = Model.getInstance().getOrCreateObject(new NetworkObject.ObjectID(objectState.getObjectId()));
NetworkObject object = Main.getInstance().getModel().getOrCreateObject(new NetworkObject.ObjectID(objectState.getObjectId()));
object.updateFromStateMessage(objectState);
ret.add(object);
}

View file

@ -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 abstract class NetworkFSNode extends NetworkObject
{
// private String path = "";
@ -24,7 +26,7 @@ public abstract class NetworkFSNode extends NetworkObject
{
long parentID = Long.parseLong(state.getValuesOrThrow("parent"));
if (parentID != 0)
parent = (NetworkFolder) Model.getInstance().getObject(new ObjectID(parentID));
parent = (NetworkFolder) Main.getInstance().getModel().getObject(new ObjectID(parentID));
else
parent = null;
}
@ -37,6 +39,22 @@ public abstract class NetworkFSNode extends NetworkObject
.putValues("name", getName());
}
@Override
public Map<String, Object> getStateMap()
{
Map<String, Object> ret = super.getStateMap();
ret.put("name", name);
ret.put("parent", parent != null ? parent.getStorageID() : 0L);
return ret;
}
@Override
public void updateFromStateMap(Map<String, Object> map)
{
name = map.get("name").toString();
parent = (NetworkFolder) Main.getInstance().getModel().getObject(new ObjectID(((Number)map.get("parent")).longValue()));
}
public String getName()
{
return name;

View file

@ -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<Peer, PeerFileState> fileStates = new HashMap<>();
private BitSet pieces = new BitSet();
private static File tempDirectory = null;
// private List<FilePiece> 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<String, Object> getStateMap()
{
Map<String, Object> 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<String, Object> 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<Number> pieces1 = (ArrayList<Number>) 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;

View file

@ -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<ObjectID> 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<String, Object> getStateMap()
{
return super.getStateMap();
}
@Override
public void updateFromStateMap(Map<String, Object> map)
{
super.updateFromStateMap(map);
}
}

View file

@ -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<String, Object> getStateMap()
{
Map<String, Object> ret = new HashMap<>();
ret.put("id", objectID.toLong());
return ret;
}
public synchronized void updateFromStateMessage(ObjectStatements.ObjectState state)
{
if (state.getObjectId() != objectID.toLong())

View file

@ -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,6 +70,25 @@ public class Peer extends NetworkObject
return builder;
}
@Override
public Map<String, Object> getStateMap()
{
Map<String, Object> ret = super.getStateMap();
ret.put("userName", userName);
ret.put("systemName", systemName);
ret.put("addresses", addresses);
return ret;
}
@Override
public void updateFromStateMap(Map<String, Object> 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);

View file

@ -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<String, Object> map)
{
}
}

View file

@ -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 <T extends Storable> DAO<T> getDAOForClass(Class<T> clazz)
{
if (daos.containsKey(clazz))
return (DAO<T>) daos.get(clazz);
else
{
CachingDAO<T> ret = new CachingDAO<>(clazz);
daos.put(clazz, ret);
return ret;
}
}
@Override
public FSNodeDAO getFSDAO()
{
return backend.getFSDAO();
}
private Map<Class<? extends Storable>, CachingDAO<?>> daos = new HashMap<>();
public class CachingDAO<T extends Storable> implements DAO<T>
{
private final DAO<T> backendDao;
private WeakHashMap<Long, T> cache = new WeakHashMap<>();
public CachingDAO(Class<T> clazz)
{
this.backendDao = backend.getDAOForClass(clazz);
}
@Override
public synchronized List<T> getAll()
{
List<T> 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);
}
}
}

View file

@ -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 <T extends Storable> DAO<T> getDAOForClass(Class<T> clazz);
public abstract FSNodeDAO getFSDAO();
public interface DAO<T extends Storable>
{
List<T> 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<NetworkFSNode>
{
default List<NetworkFSNode> 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<T extends Storable> implements DAO<T>
{
private final Set<Class<? extends T>> subclasses;
@SafeVarargs
public ClassFusionDAO(Class<? extends T>... subclasses)
{
this.subclasses = Set.of(subclasses);
}
@Override
public List<T> getAll()
{
List<T> 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");
}
}
}

View file

@ -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<String, Object> getStateMap()
{
return Map.of("localPeer", localPeer.getObjectID().toLong());
}
@Override
public void updateFromStateMap(Map<String, Object> map)
{
localPeer = (Peer) Main.getInstance().getModel().getObject(new NetworkObject.ObjectID((Long) map.get("localPeer")));
}
}

View file

@ -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<Long, Node> 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 extends NetworkObject> 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 extends NetworkObject> T createObjectByType(ObjectStatements.ObjectType type)
{
return createObjectByID(getNextObjectID(type));
}
public synchronized <T extends NetworkObject> T getObject(NetworkObject.ObjectID id)
{
if (id.toLong() == 0)
return null;
Class<T> clazz = (Class<T>) getNetworkObjectClassByType(id.getType());
return dataStore.getDAOForClass(clazz).get(id.toLong());
}
public synchronized <T extends NetworkObject> T getOrCreateObject(NetworkObject.ObjectID id)
{
if (id.toLong() == 0)
return null;
Class<T> clazz = (Class<T>) getNetworkObjectClassByType(id.getType());
return dataStore.getDAOForClass(clazz).getOrCreate(id.toLong());
}
public synchronized List<NetworkObject> listObjects(Set<ObjectStatements.ObjectType> types)
{
List<NetworkObject> ret = new ArrayList<>();
Set<Class<? extends NetworkObject>> 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<NetworkFSNode> listFSNodes(String path)
{
//TODO: dumbest algorithm in the world
List<NetworkFSNode> 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<ObjectChangeRecord> getChangeHeads()
{
// stupid algorithm - start with all of the changes, then remove the ones that are referenced by something
// TODO: better algorithm
Set<ObjectChangeRecord> ret = new HashSet<>(dataStore.getDAOForClass(ObjectChangeRecord.class).getAll());
// for (ObjectChangeRecord record : changeRecords.values())
// {
// throw new UnsupportedOperationException("NYI");
// }
throw new UnsupportedOperationException("NYI");
}
public Set<Peer> listOtherPeers()
{
Set<Peer> 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 <T extends Storable> void objectChanged(T storable)
{
Class<T> clazz = (Class<T>) storable.getClass();
dataStore.getDAOForClass(clazz).update(storable);
}
}

View file

@ -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\<X\>, Boolean, Map\<String, X\>
*/
Map<String, Object> getStateMap();
void updateFromStateMap(Map<String, Object> map);
}

View file

@ -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<Class<? extends Storable>, 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 <T extends Storable> DAO<T> getDAOForClass(Class<T> clazz)
{
if (daos.containsKey(clazz))
return (DAO<T>) daos.get(clazz);
DAO<T> ret;
if (clazz.equals(NetworkFile.class))
ret = (DAO<T>) new NetworkFileDAO();
else if (clazz.equals(NetworkFolder.class))
ret = (DAO<T>) new NetworkFolderDAO();
else if (clazz.equals(Peer.class))
ret = (DAO<T>) new PeerDAO();
else if (clazz.equals(PeerFileState.class))
ret = (DAO<T>) new PeerFileStateDAO();
else if (clazz.equals(NetworkFSNode.class))
ret = (DAO<T>) new NetworkFSNodeDAO();
else
throw new UnsupportedOperationException("Requested DAO for unsupported type " + clazz.getCanonicalName());
daos.put(clazz, ret);
return (DAO<T>) ret;
}
@Override
public FSNodeDAO getFSDAO()
{
return new NetworkFSNodeDAO();
}
private abstract class JSONObjectDAO<T extends Storable> implements DAO<T>
{
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<String, Object> stateMap = object.getStateMap();
return mapToJson(stateMap).put("storageID", object.getStorageID());
}
private JSONObject mapToJson(Map<String, Object> stateMap)
{
JSONObject ret = new JSONObject();
for (Map.Entry<String, Object> 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<Object> writeList = new ArrayList<>(valueCollection.size());
for (Object item: valueCollection)
if (VALID_JSON_CLASSES.contains(item.getClass()))
writeList.add(item);
else
writeList.add(serialiseWeirdObject(item));
ret.put(e.getKey(), writeList);
}
else if (Map.class.isAssignableFrom(valueClass))
{
Map<String, Object> valueMap = (Map<String, Object>) e.getValue();
return mapToJson(valueMap);
}
else
{
ret.put(e.getKey(), serialiseWeirdObject(e.getValue()));
}
}
return ret;
}
private Map<String,Object> jsonToMap(JSONObject json)
{
Map<String, Object> ret = new HashMap<>();
for (String key : json.keySet())
{
ret.put(key,deserialiseSomething(json.get(key)));
}
return ret;
}
protected Object deserialiseSomething(Object value)
{
Object ret = value;
if (value instanceof JSONObject)
{
JSONObject valueJsonObject = (JSONObject) value;
if (valueJsonObject.has("weirdObjectClass"))
ret = deserialiseWeirdObject(valueJsonObject);
else
ret = jsonToMap(valueJsonObject);
}
else if (value instanceof JSONArray)
{
JSONArray valueJsonArray = (JSONArray) value;
List<Object> readList = new ArrayList<Object>(valueJsonArray.length());
for (int i = 0; i < valueJsonArray.length(); i++)
{
Object item = deserialiseSomething(valueJsonArray.get(i));
readList.add(item);
}
return readList;
}
return ret;
}
protected JSONObject serialiseWeirdObject(Object value) throws IllegalArgumentException
{
throw new IllegalArgumentException("Don't know how to serialise a " + value.getClass().getCanonicalName() );
}
protected Object deserialiseWeirdObject(JSONObject json)
{
throw new IllegalArgumentException("Don't know how to deserialise that.");
}
@Override
public boolean exists(long id)
{
File file = new File(getNamespaceDirectory(), Long.toHexString(id) + ".json");
return file.exists();
}
@Override
public List<T> getAll()
{
List<T> ret = new ArrayList<>();
// get all files in the storage directory
for (File file : Objects.requireNonNull(getNamespaceDirectory().listFiles()))
{
try
{
JSONObject json = new JSONObject(Files.readString(file.toPath()));
ret.add(jsonToObject(json));
} catch (IOException e)
{
e.printStackTrace(System.err);
}
}
return ret;
}
@Override
public T create(long id)
{
T ret = makeBlank(id);
update(ret);
return ret;
}
@Override
public T get(long id)
{
File file = new File(getNamespaceDirectory(), Long.toHexString(id) + ".json");
try
{
JSONObject json = new JSONObject(Files.readString(file.toPath()));
return jsonToObject(json);
} catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public void update(T object)
{
File file = new File(getNamespaceDirectory(), Long.toHexString(object.getStorageID()) + ".json");
try(FileWriter writer = new FileWriter(file, false))
{
objectToJson(object).write(writer);
writer.flush();
} catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
private abstract class NetworkObjectDAO<T extends NetworkObject> extends JSONObjectDAO<T>
{
@Override
protected String getNamespace()
{
return "networkObjects";
}
}
private class NetworkFSNodeDAO extends ClassFusionDAO<NetworkFSNode> implements FSNodeDAO
{
public NetworkFSNodeDAO()
{
super(NetworkFile.class, NetworkFolder.class);
}
}
private class NetworkFileDAO extends NetworkObjectDAO<NetworkFile>
{
@Override
protected String getNamespace()
{
return super.getNamespace() + "/files";
}
@Override
protected NetworkFile makeBlank(long id)
{
return new NetworkFile(new NetworkObject.ObjectID(id));
}
}
private class NetworkFolderDAO extends NetworkObjectDAO<NetworkFolder>
{
@Override
protected String getNamespace()
{
return super.getNamespace() + "/folders";
}
@Override
protected NetworkFolder makeBlank(long id)
{
return new NetworkFolder(new NetworkObject.ObjectID(id));
}
}
private class PeerDAO extends NetworkObjectDAO<Peer>
{
@Override
protected String getNamespace()
{
return super.getNamespace() + "/peers";
}
@Override
protected Peer makeBlank(long id)
{
return new Peer(new NetworkObject.ObjectID(id));
}
@Override
protected JSONObject serialiseWeirdObject(Object value) throws IllegalArgumentException
{
if (value instanceof URI)
{
URI uri = (URI) value;
return new JSONObject().put("weirdObjectClass", URI.class.getCanonicalName()).put("uri",uri.toString());
}
return super.serialiseWeirdObject(value);
}
@Override
protected Object deserialiseWeirdObject(JSONObject json)
{
String weirdType = json.getString("weirdObjectClass");
try
{
Class<?> weirdClass = Class.forName(weirdType);
if (weirdClass == URI.class)
return new URI(json.getString("uri"));
} catch (ClassNotFoundException e)
{
throw new IllegalArgumentException(e);
} catch (URISyntaxException e)
{
throw new RuntimeException(e);
}
return super.deserialiseWeirdObject(json);
}
}
private class PeerFileStateDAO extends NetworkObjectDAO<PeerFileState>
{
@Override
protected String getNamespace()
{
return super.getNamespace() + "/peerFileStates";
}
@Override
protected PeerFileState makeBlank(long id)
{
return new PeerFileState(new NetworkObject.ObjectID(id));
}
}
}

View file

@ -1,7 +1,6 @@
package moe.nekojimi.friendcloud.tasks;
import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.Model;
import moe.nekojimi.friendcloud.ObjectChangeTransaction;
import moe.nekojimi.friendcloud.objects.NetworkObject;
import moe.nekojimi.friendcloud.objects.Peer;
@ -19,11 +18,11 @@ public class JoinNetworkTask implements Runnable
NetworkObject.ObjectID peerID = null;
try (ObjectChangeTransaction builder = ObjectChangeTransaction.startTransaction(Main.getInstance().getConnectionManager(), peerID))
{
Peer selfPeer = Model.getInstance().getSelfPeer();
Peer selfPeer = Main.getInstance().getModel().getSelfPeer();
if (selfPeer != null)
peerID = selfPeer.getObjectID();
else
peerID = Model.getInstance().getNextObjectID(ObjectStatements.ObjectType.OBJECT_TYPE_PEER);
peerID = Main.getInstance().getModel().getNextObjectID(ObjectStatements.ObjectType.OBJECT_TYPE_PEER);
// synchronise with the network
SyncWithNetworkTask syncWithNetworkTask = new SyncWithNetworkTask();

View file

@ -3,10 +3,8 @@ package moe.nekojimi.friendcloud.tasks;
import com.google.protobuf.Message;
import moe.nekojimi.friendcloud.ConnectionManager;
import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.Model;
import moe.nekojimi.friendcloud.network.PeerConnection;
import moe.nekojimi.friendcloud.objects.Peer;
import moe.nekojimi.friendcloud.protos.CommonMessages;
import java.io.IOException;
@ -23,7 +21,7 @@ public class PropagateMessageTask implements Runnable
public void run()
{
ConnectionManager connectionManager = Main.getInstance().getConnectionManager();
for (Peer peer: Model.getInstance().listOtherPeers())
for (Peer peer: Main.getInstance().getModel().listOtherPeers())
{
try
{

View file

@ -1,7 +1,6 @@
package moe.nekojimi.friendcloud.tasks;
import moe.nekojimi.friendcloud.Main;
import moe.nekojimi.friendcloud.Model;
import moe.nekojimi.friendcloud.ObjectChangeRecord;
import moe.nekojimi.friendcloud.network.PeerConnection;
import moe.nekojimi.friendcloud.network.requests.ObjectChangeRequest;
@ -20,14 +19,14 @@ public class SyncWithNetworkTask implements Runnable
public void run()
{
// for each other peer:
for (Peer peer : Model.getInstance().listOtherPeers())
for (Peer peer : Main.getInstance().getModel().listOtherPeers())
{
// open a connection
try
{
PeerConnection connection = Main.getInstance().getConnectionManager().getNodeConnection(peer);
// send a ObjectChangeRequest
ObjectChangeRequest objectChangeRequest = new ObjectChangeRequest(Model.getInstance().getChangeHeads().stream().map(ObjectChangeRecord::getChangeID).collect(Collectors.toSet()));
ObjectChangeRequest objectChangeRequest = new ObjectChangeRequest(Main.getInstance().getModel().getChangeHeads().stream().map(ObjectChangeRecord::getChangeID).collect(Collectors.toSet()));
CompletableFuture<Set<ObjectStatements.ObjectChange>> future = connection.makeRequest(objectChangeRequest);
// integrate the returned changes with our change graph