response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ return null;
+ }
+
+ /**
+ * Synchronizes the given library with the server.
+ *
+ * Pre-condition: Connection with server works
+ */
+ public void synchronize(BibDatabaseContext bibDatabaseContext) {
+ }
+}
diff --git a/src/main/java/org/jabref/http/dto/BibEntryDTO.java b/src/main/java/org/jabref/http/dto/BibEntryDTO.java
index a0d20d4353e..54b3af9477f 100644
--- a/src/main/java/org/jabref/http/dto/BibEntryDTO.java
+++ b/src/main/java/org/jabref/http/dto/BibEntryDTO.java
@@ -3,6 +3,8 @@
import java.io.IOException;
import java.io.StringWriter;
+import org.jabref.gui.Globals;
+import org.jabref.http.server.ServerPreferences;
import org.jabref.logic.bibtex.BibEntryWriter;
import org.jabref.logic.bibtex.FieldWriter;
import org.jabref.logic.bibtex.FieldWriterPreferences;
@@ -36,6 +38,19 @@ public BibEntryDTO(BibEntry bibEntry, BibDatabaseMode bibDatabaseMode, FieldWrit
);
}
+ public BibEntryDTO(BibEntry entry, BibDatabaseMode mode) {
+ this(entry, mode, ServerPreferences.fieldWriterPreferences(), Globals.entryTypesManager);
+ }
+
+ /**
+ * Creates a DTO based on Bibtex and default field writer preferences
+ *
+ * TODO: We should check how the BibLaTeX mode influences serialization (it should not?!)
+ */
+ public BibEntryDTO(BibEntry entry) {
+ this(entry, BibDatabaseMode.BIBTEX);
+ }
+
private static String convertToString(BibEntry entry, BibDatabaseMode bibDatabaseMode, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager bibEntryTypesManager) {
StringWriter rawEntry = new StringWriter();
BibWriter bibWriter = new BibWriter(rawEntry, "\n");
diff --git a/src/main/java/org/jabref/http/server/Application.java b/src/main/java/org/jabref/http/server/Application.java
index 00567335edb..1f9b7e98448 100644
--- a/src/main/java/org/jabref/http/server/Application.java
+++ b/src/main/java/org/jabref/http/server/Application.java
@@ -10,16 +10,63 @@
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
+import io.swagger.v3.jaxrs2.Reader;
+import io.swagger.v3.jaxrs2.integration.JaxrsApplicationScanner;
+import io.swagger.v3.oas.integration.GenericOpenApiContextBuilder;
+import io.swagger.v3.oas.integration.OpenApiConfigurationException;
+import io.swagger.v3.oas.integration.SwaggerConfiguration;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
@ApplicationPath("/")
public class Application extends jakarta.ws.rs.core.Application {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
+
@Inject
ServiceLocator serviceLocator;
+ public Application() {
+ super();
+ JaxrsApplicationScanner scanner = new JaxrsApplicationScanner();
+ scanner.setApplication(this);
+ Reader reader = new Reader(new OpenAPI());
+ OpenAPI openAPI = reader.read(scanner.classes());
+ Info info = new Info()
+ .title("JabRef http API")
+ .description("This is a sample JabDrive synchronization server.")
+ .version("0.1.0")
+ // .termsOfService("http://swagger.io/terms/")
+ .contact(new Contact()
+ .email("jabdrive@jabref.org"))
+ .license(new License()
+ .name("MIT")
+ .url("https://github.com/JabRef/jabref/blob/main/LICENSE.md"));
+ openAPI.info(info);
+
+ SwaggerConfiguration oasConfig = new SwaggerConfiguration()
+ .openAPI(openAPI)
+ .prettyPrint(true);
+
+ try {
+ new GenericOpenApiContextBuilder()
+ .resourceClasses(Set.of("org.jabref.testutils.interactive.sync.server.MweSyncRootResource"))
+ .openApiConfiguration(oasConfig)
+ .ctxId("org.jabref.sync")
+ .buildContext(true);
+ } catch (OpenApiConfigurationException e) {
+ LOGGER.error("Error in OpenAPI configuration", e);
+ }
+ }
+
@Override
public Set> getClasses() {
initialize();
- return Set.of(RootResource.class, LibrariesResource.class, LibraryResource.class, CORSFilter.class);
+ return Set.of(RootResource.class, LibrariesResource.class, LibraryResource.class, UpdatesResource.class, CORSFilter.class);
}
/**
diff --git a/src/main/java/org/jabref/http/server/LibraryResource.java b/src/main/java/org/jabref/http/server/LibraryResource.java
index 9e10afa7fa1..9e8cdb107b9 100644
--- a/src/main/java/org/jabref/http/server/LibraryResource.java
+++ b/src/main/java/org/jabref/http/server/LibraryResource.java
@@ -47,7 +47,7 @@ public String getJson(@PathParam("id") String id) {
bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry));
return bibEntry;
})
- .map(entry -> new BibEntryDTO(entry, parserResult.getDatabaseContext().getMode(), preferences.getFieldWriterPreferences(), Globals.entryTypesManager))
+ .map(entry -> new BibEntryDTO(entry, parserResult.getDatabaseContext().getMode()))
.toList();
return gson.toJson(list);
}
diff --git a/src/main/java/org/jabref/http/server/RootResource.java b/src/main/java/org/jabref/http/server/RootResource.java
index c55f2583db3..3545a0d5438 100644
--- a/src/main/java/org/jabref/http/server/RootResource.java
+++ b/src/main/java/org/jabref/http/server/RootResource.java
@@ -1,5 +1,8 @@
package org.jabref.http.server;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.oas.integration.OpenApiContextLocator;
+import io.swagger.v3.oas.models.OpenAPI;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@@ -11,12 +14,20 @@ public class RootResource {
@Produces(MediaType.TEXT_HTML)
public String get() {
return """
-
-
-
- JabRef http API runs. Please navigate to libraries.
-
-
-""";
+
+
+
+ JabRef http API runs. Please navigate to libraries.
+
+
+ """;
+ }
+
+ @GET
+ @Path("openapi.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getOpenApiJson() {
+ OpenAPI openAPI = OpenApiContextLocator.getInstance().getOpenApiContext("org.jabref.sync").read();
+ return Json.pretty(openAPI);
}
}
diff --git a/src/main/java/org/jabref/http/server/ServerPreferences.java b/src/main/java/org/jabref/http/server/ServerPreferences.java
new file mode 100644
index 00000000000..938353c0f7c
--- /dev/null
+++ b/src/main/java/org/jabref/http/server/ServerPreferences.java
@@ -0,0 +1,15 @@
+package org.jabref.http.server;
+
+import java.util.List;
+
+import org.jabref.logic.bibtex.FieldContentFormatterPreferences;
+import org.jabref.logic.bibtex.FieldWriterPreferences;
+
+public class ServerPreferences {
+
+ public static FieldWriterPreferences fieldWriterPreferences() {
+ FieldContentFormatterPreferences fieldContentFormatterPreferences = new FieldContentFormatterPreferences(List.of());
+ FieldWriterPreferences fieldWriterPreferences = new FieldWriterPreferences(false, List.of(), fieldContentFormatterPreferences);
+ return fieldWriterPreferences;
+ }
+}
diff --git a/src/main/java/org/jabref/http/server/UpdatesResource.java b/src/main/java/org/jabref/http/server/UpdatesResource.java
new file mode 100644
index 00000000000..6251bdc9b57
--- /dev/null
+++ b/src/main/java/org/jabref/http/server/UpdatesResource.java
@@ -0,0 +1,39 @@
+package org.jabref.http.server;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import org.jabref.http.dto.BibEntryDTO;
+import org.jabref.http.sync.state.SyncState;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("updates")
+public class UpdatesResource {
+ @Inject
+ Gson gson;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public String get(@QueryParam("since") int since) {
+ List changes = SyncState.INSTANCE.changes(since);
+ return gson.toJson(changes);
+ }
+
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ public void acceptChanges(String changes) {
+ Type listType = new TypeToken>() {
+ }.getType();
+ List result = gson.fromJson(changes, listType);
+ System.out.println(result);
+ }
+}
diff --git a/src/main/java/org/jabref/http/sync/state/SyncState.java b/src/main/java/org/jabref/http/sync/state/SyncState.java
new file mode 100644
index 00000000000..b39d81bf220
--- /dev/null
+++ b/src/main/java/org/jabref/http/sync/state/SyncState.java
@@ -0,0 +1,50 @@
+package org.jabref.http.sync.state;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.jabref.gui.Globals;
+import org.jabref.http.server.ServerPreferences;
+import org.jabref.model.database.BibDatabaseMode;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.http.dto.BibEntryDTO;
+
+public enum SyncState {
+ INSTANCE;
+
+ // mapping from the shared ID to the DTO
+ private Map lastStateOfEntries = new HashMap<>();
+
+ // globalRevisionId -> set of IDs
+ private Map> idUpdated = new HashMap<>();
+
+ /**
+ * Adds or updates an entry
+ */
+ public void putEntry(Integer globalRevision, BibEntry entry) {
+ int sharedID = entry.getSharedBibEntryData().getSharedID();
+ assert sharedID >= 0;
+ lastStateOfEntries.put(sharedID, new BibEntryDTO(entry));
+ idUpdated.computeIfAbsent(globalRevision, k -> new HashSet<>()).add(sharedID);
+ }
+
+ /**
+ * Returns all changes between the given revisions.
+ *
+ * @param fromRevision the revision to start from (exclusive)
+ * @return a list of all changes
+ */
+ public List changes(Integer fromRevision) {
+ return idUpdated.entrySet().stream()
+ .filter(entry -> entry.getKey() > fromRevision)
+ .flatMap(entry -> entry.getValue().stream())
+ .distinct()
+ .sorted()
+ .map(sharedId -> lastStateOfEntries.get(sharedId))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java
index 5f5301d633a..0523efef49a 100644
--- a/src/main/java/org/jabref/model/entry/BibEntry.java
+++ b/src/main/java/org/jabref/model/entry/BibEntry.java
@@ -1157,4 +1157,5 @@ public void mergeWith(BibEntry other, Set otherPrioritizedFields) {
}
}
}
+
}
diff --git a/src/test/java/org/jabref/http/server/ServerTest.java b/src/test/java/org/jabref/http/server/ServerTest.java
index 039b0e68131..1736d7f248f 100644
--- a/src/test/java/org/jabref/http/server/ServerTest.java
+++ b/src/test/java/org/jabref/http/server/ServerTest.java
@@ -82,15 +82,14 @@ private static void initializePreferencesService() {
when(importFormatPreferences.bibEntryPreferences()).thenReturn(bibEntryPreferences);
when(bibEntryPreferences.getKeywordSeparator()).thenReturn(',');
- FieldWriterPreferences fieldWriterPreferences = mock(FieldWriterPreferences.class);
- when(preferencesService.getFieldWriterPreferences()).thenReturn(fieldWriterPreferences);
- when(fieldWriterPreferences.isResolveStrings()).thenReturn(false);
+ when(preferencesService.getFieldWriterPreferences()).thenReturn(ServerPreferences.fieldWriterPreferences());
// defaults are in {@link org.jabref.preferences.JabRefPreferences.NON_WRAPPABLE_FIELDS}
FieldContentFormatterPreferences fieldContentFormatterPreferences = new FieldContentFormatterPreferences(List.of());
// used twice, once for reading and once for writing
when(importFormatPreferences.fieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences);
- when(preferencesService.getFieldWriterPreferences().getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences);
+ // for writing, we use the "real" Server Preferences
+ // when(preferencesService.getFieldWriterPreferences().getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences);
guiPreferences = mock(GuiPreferences.class);
when(preferencesService.getGuiPreferences()).thenReturn(guiPreferences);
diff --git a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
new file mode 100644
index 00000000000..67e42876114
--- /dev/null
+++ b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
@@ -0,0 +1,44 @@
+package org.jabref.http.server;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class UpdatesResourceTest extends ServerTest {
+
+ @Override
+ protected jakarta.ws.rs.core.Application configure() {
+ ResourceConfig resourceConfig = new ResourceConfig(UpdatesResource.class);
+ addPreferencesToResourceConfig(resourceConfig);
+ addGsonToResourceConfig(resourceConfig);
+ return resourceConfig.getApplication();
+ }
+
+ @Test
+ void initialData() {
+ assertEquals("""
+ [
+ {
+ "sharingMetadata": {
+ "sharedID": 1,
+ "version": 2
+ },
+ "type": "Misc",
+ "citationKey": "e1.v2",
+ "content": {},
+ "userComments": ""
+ },
+ {
+ "sharingMetadata": {
+ "sharedID": 2,
+ "version": 1
+ },
+ "type": "Misc",
+ "citationKey": "e2.v1",
+ "content": {},
+ "userComments": ""
+ }
+ ]""", target("/updates").queryParam("lastUpdate", "0").request().get(String.class));
+ }
+}
diff --git a/src/test/java/org/jabref/http/sync/state/SyncStateTest.java b/src/test/java/org/jabref/http/sync/state/SyncStateTest.java
new file mode 100644
index 00000000000..ef0d752da4f
--- /dev/null
+++ b/src/test/java/org/jabref/http/sync/state/SyncStateTest.java
@@ -0,0 +1,28 @@
+package org.jabref.http.sync.state;
+
+import java.util.List;
+
+import org.jabref.model.entry.BibEntry;
+import org.jabref.http.dto.BibEntryDTO;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class SyncStateTest {
+ @Test void test() {
+ BibEntry entryE1V1 = new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1);
+ BibEntry entryE1V2 = new BibEntry().withCitationKey("e1.v2").withSharedBibEntryData(1, 2);
+ BibEntry entryE2V1 = new BibEntry().withCitationKey("e2.v1").withSharedBibEntryData(2, 1);
+
+ SyncState.INSTANCE.putEntry(
+ 1, entryE1V1);
+ SyncState.INSTANCE.putEntry(
+ 1, entryE2V1);
+ SyncState.INSTANCE.putEntry(
+ 2, entryE1V2);
+
+ List changes = SyncState.INSTANCE.changes(0);
+ assertEquals(List.of(new BibEntryDTO(entryE1V2), new BibEntryDTO(entryE2V1)), changes);
+ }
+}
diff --git a/src/test/java/org/jabref/testutils/interactive/http/SyncClientDemo.java b/src/test/java/org/jabref/testutils/interactive/http/SyncClientDemo.java
new file mode 100644
index 00000000000..a31c57bc880
--- /dev/null
+++ b/src/test/java/org/jabref/testutils/interactive/http/SyncClientDemo.java
@@ -0,0 +1,9 @@
+package org.jabref.testutils.interactive.http;
+
+import org.jabref.http.client.SyncClient;
+
+public class SyncClientDemo {
+ public static final void main(String[] args) {
+ SyncClient client = new SyncClient(null);
+ }
+}
diff --git a/src/test/java/org/jabref/testutils/interactive/http/rest-api.http b/src/test/java/org/jabref/testutils/interactive/http/rest-api.http
index 4994efdd6e2..ccbdecc4ad5 100644
--- a/src/test/java/org/jabref/testutils/interactive/http/rest-api.http
+++ b/src/test/java/org/jabref/testutils/interactive/http/rest-api.http
@@ -33,3 +33,37 @@ Accept: application/x-bibtex-library-csl+json
GET https://localhost:6051/libraries/jabref-authors.bib-026bd7ec
Accept: application/json
+
+###
+
+POST http://localhost:8080/updates
+Accept: application/json
+
+[
+ {
+ "sharingMetadata": {
+ "sharedID": 1,
+ "version": 2
+ },
+ "type": "Misc",
+ "citationKey": "e1.v2",
+ "content": {},
+ "userComments": ""
+ },
+ {
+ "sharingMetadata": {
+ "sharedID": 2,
+ "version": 1
+ },
+ "type": "Misc",
+ "citationKey": "e2.v1",
+ "content": {},
+ "userComments": ""
+ }
+]
+
+### Fetch OpenAPI description
+
+// Client can be generated with npx openapi-generator-cli generate -g java -o generated-client --library=jersey3 -i C:\git-repositories\jabref\.idea\httpRequests\2023-04-04T102607.200.json
+GET http://localhost:8080/openapi.json
+Accept: application/json
From 13e0623d80eea9c35c79618b418b5e4c180a3327 Mon Sep 17 00:00:00 2001
From: Oliver Kopp
Date: Sat, 22 Apr 2023 12:39:04 +0200
Subject: [PATCH 07/10] Fix ADR name
---
...-return-bibtex-string.md => 0028-http-return-bibtex-string.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/decisions/{0027-http-return-bibtex-string.md => 0028-http-return-bibtex-string.md} (100%)
diff --git a/docs/decisions/0027-http-return-bibtex-string.md b/docs/decisions/0028-http-return-bibtex-string.md
similarity index 100%
rename from docs/decisions/0027-http-return-bibtex-string.md
rename to docs/decisions/0028-http-return-bibtex-string.md
From f3e7c2d44396ee1520052f10c9b5fe380259bd53 Mon Sep 17 00:00:00 2001
From: Oliver Kopp
Date: Sat, 22 Apr 2023 14:24:53 +0200
Subject: [PATCH 08/10] Add "withChanged" to enable proper BibEntryDTOTest
---
.github/workflows/tests.yml | 2 +-
.../java/org/jabref/http/dto/BibEntryDTO.java | 2 +-
.../java/org/jabref/http/server/Server.java | 7 +-
.../org/jabref/http/sync/state/SyncState.java | 18 ++--
.../java/org/jabref/model/entry/BibEntry.java | 11 ++
.../architecture/MainArchitectureTest.java | 8 ++
.../org/jabref/http/dto/BibEntryDTOTest.java | 100 ++++++++++++++++++
.../http/server/UpdatesResourceTest.java | 28 +++++
8 files changed, 165 insertions(+), 11 deletions(-)
create mode 100644 src/test/java/org/jabref/http/dto/BibEntryDTOTest.java
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 2f6c9545f3f..50eca74f0a2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -57,7 +57,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3
with:
- java-version: 20
+ java-version: 19
distribution: 'temurin'
cache: 'gradle'
- name: Run tests
diff --git a/src/main/java/org/jabref/http/dto/BibEntryDTO.java b/src/main/java/org/jabref/http/dto/BibEntryDTO.java
index 54b3af9477f..7fc89afc348 100644
--- a/src/main/java/org/jabref/http/dto/BibEntryDTO.java
+++ b/src/main/java/org/jabref/http/dto/BibEntryDTO.java
@@ -51,7 +51,7 @@ public BibEntryDTO(BibEntry entry) {
this(entry, BibDatabaseMode.BIBTEX);
}
- private static String convertToString(BibEntry entry, BibDatabaseMode bibDatabaseMode, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager bibEntryTypesManager) {
+ static String convertToString(BibEntry entry, BibDatabaseMode bibDatabaseMode, FieldWriterPreferences fieldWriterPreferences, BibEntryTypesManager bibEntryTypesManager) {
StringWriter rawEntry = new StringWriter();
BibWriter bibWriter = new BibWriter(rawEntry, "\n");
BibEntryWriter bibtexEntryWriter = new BibEntryWriter(new FieldWriter(fieldWriterPreferences), bibEntryTypesManager);
diff --git a/src/main/java/org/jabref/http/server/Server.java b/src/main/java/org/jabref/http/server/Server.java
index dc8411e8aa9..05f48aa111d 100644
--- a/src/main/java/org/jabref/http/server/Server.java
+++ b/src/main/java/org/jabref/http/server/Server.java
@@ -12,6 +12,7 @@
import javafx.collections.ObservableList;
+import org.jabref.architecture.AllowedToUseStandardStreams;
import org.jabref.logic.util.OS;
import org.jabref.preferences.JabRefPreferences;
@@ -87,12 +88,12 @@ private static void startServer() {
SeBootstrap.start(Application.class, configuration).thenAccept(instance -> {
LOGGER.debug("Server started.");
instance.stopOnShutdown(stopResult ->
- System.out.printf("Stop result: %s [Native stop result: %s].%n", stopResult,
+ LOGGER.info("Stop result: {} [Native stop result: {}}].", stopResult,
stopResult.unwrap(Object.class)));
final URI uri = instance.configuration().baseUri();
- System.out.printf("Instance %s running at %s [Native handle: %s].%n", instance, uri,
+ LOGGER.info("Instance {} running at {} [Native handle: {}].", instance, uri,
instance.unwrap(Object.class));
- System.out.println("Send SIGKILL to shutdown.");
+ LOGGER.info("Send SIGKILL to shutdown.");
serverInstance = instance;
});
}
diff --git a/src/main/java/org/jabref/http/sync/state/SyncState.java b/src/main/java/org/jabref/http/sync/state/SyncState.java
index b39d81bf220..07235fc37c2 100644
--- a/src/main/java/org/jabref/http/sync/state/SyncState.java
+++ b/src/main/java/org/jabref/http/sync/state/SyncState.java
@@ -7,9 +7,6 @@
import java.util.Set;
import java.util.stream.Collectors;
-import org.jabref.gui.Globals;
-import org.jabref.http.server.ServerPreferences;
-import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.BibEntry;
import org.jabref.http.dto.BibEntryDTO;
@@ -20,7 +17,7 @@ public enum SyncState {
private Map lastStateOfEntries = new HashMap<>();
// globalRevisionId -> set of IDs
- private Map> idUpdated = new HashMap<>();
+ private Map> idsUpdated = new HashMap<>();
/**
* Adds or updates an entry
@@ -29,7 +26,7 @@ public void putEntry(Integer globalRevision, BibEntry entry) {
int sharedID = entry.getSharedBibEntryData().getSharedID();
assert sharedID >= 0;
lastStateOfEntries.put(sharedID, new BibEntryDTO(entry));
- idUpdated.computeIfAbsent(globalRevision, k -> new HashSet<>()).add(sharedID);
+ idsUpdated.computeIfAbsent(globalRevision, k -> new HashSet<>()).add(sharedID);
}
/**
@@ -39,7 +36,7 @@ public void putEntry(Integer globalRevision, BibEntry entry) {
* @return a list of all changes
*/
public List changes(Integer fromRevision) {
- return idUpdated.entrySet().stream()
+ return idsUpdated.entrySet().stream()
.filter(entry -> entry.getKey() > fromRevision)
.flatMap(entry -> entry.getValue().stream())
.distinct()
@@ -47,4 +44,13 @@ public List changes(Integer fromRevision) {
.map(sharedId -> lastStateOfEntries.get(sharedId))
.collect(Collectors.toList());
}
+
+ /**
+ * Required at testing to work around the single instance.
+ * May be used at testing only.
+ */
+ public void reset() {
+ lastStateOfEntries = new HashMap<>();
+ idsUpdated = new HashMap<>();
+ }
}
diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java
index 0523efef49a..fa472858de0 100644
--- a/src/main/java/org/jabref/model/entry/BibEntry.java
+++ b/src/main/java/org/jabref/model/entry/BibEntry.java
@@ -748,6 +748,16 @@ public void setChanged(boolean changed) {
this.changed = changed;
}
+
+ /**
+ * Required for our "builder".
+ * Each with...
method sets changed
to false
.
+ */
+ public BibEntry withChanged(boolean changed) {
+ this.changed = changed;
+ return this;
+ }
+
public Optional putKeywords(List keywords, Character delimiter) {
Objects.requireNonNull(delimiter);
return putKeywords(new KeywordList(keywords), delimiter);
@@ -915,6 +925,7 @@ public BibEntry withField(Field field, String value) {
*/
public BibEntry withFields(Map content) {
this.fields = FXCollections.observableMap(new HashMap<>(content));
+ this.setChanged(false);
return this;
}
diff --git a/src/test/java/org/jabref/architecture/MainArchitectureTest.java b/src/test/java/org/jabref/architecture/MainArchitectureTest.java
index b7aff2a46f9..142e1e1a635 100644
--- a/src/test/java/org/jabref/architecture/MainArchitectureTest.java
+++ b/src/test/java/org/jabref/architecture/MainArchitectureTest.java
@@ -7,6 +7,7 @@
import com.tngtech.archunit.junit.ArchIgnore;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.library.GeneralCodingRules;
+import org.jabref.http.sync.state.SyncState;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
@@ -133,4 +134,11 @@ public static void restrictStandardStreams(JavaClasses classes) {
.because("logging framework should be used instead or the class be marked explicitly as @AllowedToUseStandardStreams")
.check(classes);
}
+
+ @ArchTest
+ public static void SyncStateResetMayOnlyBeCalledAtTests(JavaClasses classes) {
+ noClasses()
+ .that().doNotHaveFullyQualifiedName("org.jabref.http.server.UpdatesResourceTest")
+ .should().callMethod(SyncState.class, "reset");
+ }
}
diff --git a/src/test/java/org/jabref/http/dto/BibEntryDTOTest.java b/src/test/java/org/jabref/http/dto/BibEntryDTOTest.java
new file mode 100644
index 00000000000..9fdd7f0915c
--- /dev/null
+++ b/src/test/java/org/jabref/http/dto/BibEntryDTOTest.java
@@ -0,0 +1,100 @@
+package org.jabref.http.dto;
+
+import com.google.gson.Gson;
+import org.jabref.gui.Globals;
+import org.jabref.http.server.ServerPreferences;
+import org.jabref.model.database.BibDatabaseMode;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.StandardField;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BibEntryDTOTest {
+
+ private static GsonFactory gsonFactory = new GsonFactory();
+
+ public static Stream checkSerialization() {
+ return Stream.of(
+ Arguments.of("""
+ {
+ "sharingMetadata": {
+ "sharedID": -1,
+ "version": 1
+ },
+ "userComments": "",
+ "citationKey": "",
+ "bibtex": "@Misc{,\\n}\\n"
+ }""", new BibEntry().withChanged(true)),
+ Arguments.of("""
+ {
+ "sharingMetadata": {
+ "sharedID": -1,
+ "version": 1
+ },
+ "userComments": "",
+ "citationKey": "key",
+ "bibtex": "@Misc{key,\\n}\\n"
+ }""", new BibEntry().withCitationKey("key").withChanged(true)),
+ Arguments.of("""
+ {
+ "sharingMetadata": {
+ "sharedID": -1,
+ "version": 1
+ },
+ "userComments": "",
+ "citationKey": "key",
+ "bibtex": "@Misc{key,\\n author \\u003d {Author},\\n}\\n"
+ }""", new BibEntry().withCitationKey("key").withField(StandardField.AUTHOR, "Author").withChanged(true)),
+ Arguments.of("""
+ {
+ "sharingMetadata": {
+ "sharedID": 1,
+ "version": 1
+ },
+ "userComments": "",
+ "citationKey": "e1.v1",
+ "bibtex": "@Misc{e1.v1,\\n}\\n"
+ }""", new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1).withChanged(true))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void checkSerialization(String expected, BibEntry entry) {
+ Gson gson = gsonFactory.provide();
+ assertEquals(expected, gson.toJson(new BibEntryDTO(entry)));
+ }
+
+ public static Stream convertToString() {
+ return Stream.of(
+ Arguments.of("""
+ @Misc{,
+ }
+ """, new BibEntry().withChanged(true)),
+ Arguments.of("""
+ @Misc{key,
+ }
+ """, new BibEntry().withCitationKey("key").withChanged(true)),
+ Arguments.of("""
+ @Misc{key,
+ author = {Author},
+ }
+ """, new BibEntry().withCitationKey("key").withField(StandardField.AUTHOR, "Author").withChanged(true)),
+ Arguments.of("""
+ @Misc{e1.v1,
+ }
+ """, new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1).withChanged(true))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void convertToString(String expected, BibEntry entry) {
+ assertEquals(expected, BibEntryDTO.convertToString(entry, BibDatabaseMode.BIBTEX, ServerPreferences.fieldWriterPreferences(), Globals.entryTypesManager));
+ }
+}
diff --git a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
index 67e42876114..e560ee00e1d 100644
--- a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
+++ b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
@@ -1,12 +1,20 @@
package org.jabref.http.server;
import org.glassfish.jersey.server.ResourceConfig;
+import org.jabref.http.sync.state.SyncState;
+import org.jabref.model.entry.BibEntry;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UpdatesResourceTest extends ServerTest {
+ @BeforeEach
+ void resetState() {
+ SyncState.INSTANCE.reset();
+ }
+
@Override
protected jakarta.ws.rs.core.Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(UpdatesResource.class);
@@ -15,8 +23,28 @@ protected jakarta.ws.rs.core.Application configure() {
return resourceConfig.getApplication();
}
+ @Test
+ void noLastUpdateSupplied() {
+ assertEquals("[]", target("/updates").request().get(String.class));
+ }
+
@Test
void initialData() {
+ assertEquals("[]", target("/updates").queryParam("lastUpdate", "0").request().get(String.class));
+ }
+
+ @Test
+ void twoVersions() {
+ BibEntry entryE1V1 = new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1);
+ BibEntry entryE1V2 = new BibEntry().withCitationKey("e1.v2").withSharedBibEntryData(1, 2);
+ BibEntry entryE2V1 = new BibEntry().withCitationKey("e2.v1").withSharedBibEntryData(2, 1);
+
+ SyncState.INSTANCE.putEntry(
+ 1, entryE1V1);
+ SyncState.INSTANCE.putEntry(
+ 1, entryE2V1);
+ SyncState.INSTANCE.putEntry(
+ 2, entryE1V2);
assertEquals("""
[
{
From f59e57f9b0bf83cbf2b0624c4da2d35c19f6433a Mon Sep 17 00:00:00 2001
From: Oliver Kopp
Date: Sat, 22 Apr 2023 15:08:43 +0200
Subject: [PATCH 09/10] Fix test
---
.../http/server/UpdatesResourceTest.java | 46 +++++++++----------
1 file changed, 22 insertions(+), 24 deletions(-)
diff --git a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
index e560ee00e1d..7683937c1bd 100644
--- a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
+++ b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
@@ -35,9 +35,9 @@ void initialData() {
@Test
void twoVersions() {
- BibEntry entryE1V1 = new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1);
- BibEntry entryE1V2 = new BibEntry().withCitationKey("e1.v2").withSharedBibEntryData(1, 2);
- BibEntry entryE2V1 = new BibEntry().withCitationKey("e2.v1").withSharedBibEntryData(2, 1);
+ BibEntry entryE1V1 = new BibEntry().withCitationKey("e1.v1").withSharedBibEntryData(1, 1).withChanged(true);
+ BibEntry entryE1V2 = new BibEntry().withCitationKey("e1.v2").withSharedBibEntryData(1, 2).withChanged(true);
+ BibEntry entryE2V1 = new BibEntry().withCitationKey("e2.v1").withSharedBibEntryData(2, 1).withChanged(true);
SyncState.INSTANCE.putEntry(
1, entryE1V1);
@@ -47,26 +47,24 @@ void twoVersions() {
2, entryE1V2);
assertEquals("""
[
- {
- "sharingMetadata": {
- "sharedID": 1,
- "version": 2
- },
- "type": "Misc",
- "citationKey": "e1.v2",
- "content": {},
- "userComments": ""
- },
- {
- "sharingMetadata": {
- "sharedID": 2,
- "version": 1
- },
- "type": "Misc",
- "citationKey": "e2.v1",
- "content": {},
- "userComments": ""
- }
- ]""", target("/updates").queryParam("lastUpdate", "0").request().get(String.class));
+ {
+ "sharingMetadata": {
+ "sharedID": 1,
+ "version": 2
+ },
+ "userComments": "",
+ "citationKey": "e1.v2",
+ "bibtex": "@Misc{e1.v2,\\n}\\n"
+ },
+ {
+ "sharingMetadata": {
+ "sharedID": 2,
+ "version": 1
+ },
+ "userComments": "",
+ "citationKey": "e2.v1",
+ "bibtex": "@Misc{e2.v1,\\n}\\n"
+ }
+ ]""", target("/updates").queryParam("lastUpdate", "0").request().get(String.class));
}
}
From d7897d1e9e2751cfdf2ece84ff80ca6864fc4f0d Mon Sep 17 00:00:00 2001
From: Oliver Kopp
Date: Mon, 24 Apr 2023 08:19:38 +0200
Subject: [PATCH 10/10] WIP: Make SyncState library-aware
---
build.gradle | 2 ++
docs/decisions/0029-use-cuid2.md | 26 ++++++++++++++++++
src/main/java/module-info.java | 1 +
.../jabref/http/server/LibraryResource.java | 12 ++++-----
.../jabref/http/server/UpdatesResource.java | 13 ++++++---
.../org/jabref/http/sync/package-info.java | 5 ++++
.../http/sync/state/ChangesAndServerView.java | 8 ++++++
.../org/jabref/http/sync/state/HashInfo.java | 20 ++++++++++++++
.../org/jabref/http/sync/state/SyncState.java | 22 ++++++++++-----
.../model/entry/SharedBibEntryData.java | 2 ++
.../http/server/UpdatesResourceTest.java | 27 +++++++++++--------
.../jabref/http/sync/state/SyncStateTest.java | 26 +++++++++++++-----
12 files changed, 131 insertions(+), 33 deletions(-)
create mode 100644 docs/decisions/0029-use-cuid2.md
create mode 100644 src/main/java/org/jabref/http/sync/package-info.java
create mode 100644 src/main/java/org/jabref/http/sync/state/ChangesAndServerView.java
create mode 100644 src/main/java/org/jabref/http/sync/state/HashInfo.java
diff --git a/build.gradle b/build.gradle
index b1ccb23b722..7ed9102c2ca 100644
--- a/build.gradle
+++ b/build.gradle
@@ -221,6 +221,8 @@ dependencies {
implementation 'io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.9'
// Allow objects "magically" to be mapped to JSON using GSON
// implementation 'org.glassfish.jersey.media:jersey-media-json-gson:3.1.1'
+ // We use CUID2 instead of UUID. See ADR-0029
+ // implementation 'io.github.thibaultmeyer:cuid:2.0.2'
testImplementation 'io.github.classgraph:classgraph:4.8.157'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
diff --git a/docs/decisions/0029-use-cuid2.md b/docs/decisions/0029-use-cuid2.md
new file mode 100644
index 00000000000..c79363a2ce0
--- /dev/null
+++ b/docs/decisions/0029-use-cuid2.md
@@ -0,0 +1,26 @@
+---
+nav_order: 29
+parent: Decision Records
+---
+
+# Use CUID2 as globally unique identifiers
+
+## Context and Problem Statement
+
+Each BibEntry needs to have a unique id.
+See [Remote Storage - JabDrive](../code-howtos/remote-storage-jabdrive.md) for details.
+
+## Decision Drivers
+
+* "Nice and modern looking" identifiers
+* Easy to generate
+
+## Considered Options
+
+* UUIDv4
+* [CUID2](https://github.com/paralleldrive/cuid2)
+* [Nano ID](https://github.com/ai/nanoid)
+
+## Decision Outcome
+
+Chosen option: "CUID2", because resolves all decision drivers.
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 8170e2ff111..66fa2c7263e 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -61,6 +61,7 @@
requires java.net.http;
requires jakarta.ws.rs;
requires grizzly.framework;
+ // requires cuid;
// OpenAPI generation
requires io.swagger.v3.core;
diff --git a/src/main/java/org/jabref/http/server/LibraryResource.java b/src/main/java/org/jabref/http/server/LibraryResource.java
index 9e8cdb107b9..94e3e5a7505 100644
--- a/src/main/java/org/jabref/http/server/LibraryResource.java
+++ b/src/main/java/org/jabref/http/server/LibraryResource.java
@@ -41,7 +41,7 @@ public class LibraryResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String getJson(@PathParam("id") String id) {
- ParserResult parserResult = getParserResult(id);
+ ParserResult parserResult = getParserResult(preferences, id);
List list = parserResult.getDatabase().getEntries().stream()
.map(bibEntry -> {
bibEntry.getSharedBibEntryData().setSharedID(Objects.hash(bibEntry));
@@ -55,14 +55,14 @@ public String getJson(@PathParam("id") String id) {
@GET
@Produces(org.jabref.http.MediaType.JSON_CSL_ITEM)
public String getClsItemJson(@PathParam("id") String id) {
- ParserResult parserResult = getParserResult(id);
+ ParserResult parserResult = getParserResult(preferences, id);
JabRefItemDataProvider jabRefItemDataProvider = new JabRefItemDataProvider();
jabRefItemDataProvider.setData(parserResult.getDatabaseContext(), new BibEntryTypesManager());
return jabRefItemDataProvider.toJson();
}
- private ParserResult getParserResult(String id) {
- java.nio.file.Path library = getLibraryPath(id);
+ static ParserResult getParserResult(PreferencesService preferences, String id) {
+ java.nio.file.Path library = getLibraryPath(preferences, id);
ParserResult parserResult;
try {
parserResult = new BibtexImporter(preferences.getImportFormatPreferences(), new DummyFileUpdateMonitor()).importDatabase(library);
@@ -76,7 +76,7 @@ private ParserResult getParserResult(String id) {
@GET
@Produces(org.jabref.http.MediaType.BIBTEX)
public Response getBibtex(@PathParam("id") String id) {
- java.nio.file.Path library = getLibraryPath(id);
+ java.nio.file.Path library = getLibraryPath(preferences, id);
String libraryAsString;
try {
libraryAsString = Files.readString(library);
@@ -89,7 +89,7 @@ public Response getBibtex(@PathParam("id") String id) {
.build();
}
- private java.nio.file.Path getLibraryPath(String id) {
+ private static java.nio.file.Path getLibraryPath(PreferencesService preferences, String id) {
java.nio.file.Path library = preferences.getGuiPreferences().getLastFilesOpened()
.stream()
.map(java.nio.file.Path::of)
diff --git a/src/main/java/org/jabref/http/server/UpdatesResource.java b/src/main/java/org/jabref/http/server/UpdatesResource.java
index 6251bdc9b57..25733425b4d 100644
--- a/src/main/java/org/jabref/http/server/UpdatesResource.java
+++ b/src/main/java/org/jabref/http/server/UpdatesResource.java
@@ -4,7 +4,9 @@
import java.util.List;
import org.jabref.http.dto.BibEntryDTO;
+import org.jabref.http.sync.state.ChangesAndServerView;
import org.jabref.http.sync.state.SyncState;
+import org.jabref.preferences.PreferencesService;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@@ -12,19 +14,24 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
-@Path("updates")
+@Path("libraries/{id}/updates")
public class UpdatesResource {
@Inject
Gson gson;
+ @Inject
+ PreferencesService preferences;
+
@GET
@Produces(MediaType.APPLICATION_JSON)
- public String get(@QueryParam("since") int since) {
- List changes = SyncState.INSTANCE.changes(since);
+ public String get(@PathParam("id") String id, @QueryParam("since") int since) {
+ SyncState syncState = new SyncState(LibraryResource.getParserResult(preferences, id).getDatabaseContext());
+ ChangesAndServerView changes = syncState.changesAndServerView(since);
return gson.toJson(changes);
}
diff --git a/src/main/java/org/jabref/http/sync/package-info.java b/src/main/java/org/jabref/http/sync/package-info.java
new file mode 100644
index 00000000000..b635841a76e
--- /dev/null
+++ b/src/main/java/org/jabref/http/sync/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * This package is responsible for the synchronization with a server.
+ * General documentation available at docs/code-howtos/remote-storage-jabdrive.md
+ */
+package org.jabref.http.sync;
diff --git a/src/main/java/org/jabref/http/sync/state/ChangesAndServerView.java b/src/main/java/org/jabref/http/sync/state/ChangesAndServerView.java
new file mode 100644
index 00000000000..4c58722601f
--- /dev/null
+++ b/src/main/java/org/jabref/http/sync/state/ChangesAndServerView.java
@@ -0,0 +1,8 @@
+package org.jabref.http.sync.state;
+
+import org.jabref.http.dto.BibEntryDTO;
+
+import java.util.List;
+
+public record ChangesAndServerView(List changes, List hashes) {
+}
diff --git a/src/main/java/org/jabref/http/sync/state/HashInfo.java b/src/main/java/org/jabref/http/sync/state/HashInfo.java
new file mode 100644
index 00000000000..ddef8c105ad
--- /dev/null
+++ b/src/main/java/org/jabref/http/sync/state/HashInfo.java
@@ -0,0 +1,20 @@
+package org.jabref.http.sync.state;
+
+import com.google.common.base.Strings;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.SharedBibEntryData;
+import org.jabref.model.strings.StringUtil;
+
+public record HashInfo(String id, Integer hash) {
+ /**
+ * Converts the {@link SharedBibEntryData#sharedID} to a string following CUID2 for the structure.
+ * We need to convert from int (covering 64k entries) to CUID2 to be able to serve endless numbers of entries
+ */
+ public HashInfo(int id, Integer hash) {
+ this(Strings.padStart(Integer.toString(id), 10, '0'), hash);
+ }
+
+ public HashInfo(BibEntry entry) {
+ this(entry.getSharedBibEntryData().getSharedID(), entry.hashCode());
+ }
+}
diff --git a/src/main/java/org/jabref/http/sync/state/SyncState.java b/src/main/java/org/jabref/http/sync/state/SyncState.java
index 07235fc37c2..1aba356ba2b 100644
--- a/src/main/java/org/jabref/http/sync/state/SyncState.java
+++ b/src/main/java/org/jabref/http/sync/state/SyncState.java
@@ -7,20 +7,24 @@
import java.util.Set;
import java.util.stream.Collectors;
+import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.http.dto.BibEntryDTO;
-public enum SyncState {
- INSTANCE;
-
+public class SyncState {
+ private final BibDatabaseContext context;
// mapping from the shared ID to the DTO
private Map lastStateOfEntries = new HashMap<>();
// globalRevisionId -> set of IDs
private Map> idsUpdated = new HashMap<>();
+ public SyncState(BibDatabaseContext context) {
+ this.context = context;
+ }
+
/**
- * Adds or updates an entry
+ * Adds or updates an entry. Caller has to ensure consistent state with BibDatabaseContext
*/
public void putEntry(Integer globalRevision, BibEntry entry) {
int sharedID = entry.getSharedBibEntryData().getSharedID();
@@ -31,18 +35,22 @@ public void putEntry(Integer globalRevision, BibEntry entry) {
/**
* Returns all changes between the given revisions.
+ * It also contains the hash values of all BibEntries of the server to enable a client to flag its view as dirty.
*
* @param fromRevision the revision to start from (exclusive)
- * @return a list of all changes
*/
- public List changes(Integer fromRevision) {
- return idsUpdated.entrySet().stream()
+ public ChangesAndServerView changesAndServerView(Integer fromRevision) {
+ List changes = idsUpdated.entrySet().stream()
.filter(entry -> entry.getKey() > fromRevision)
.flatMap(entry -> entry.getValue().stream())
.distinct()
.sorted()
.map(sharedId -> lastStateOfEntries.get(sharedId))
.collect(Collectors.toList());
+ List hashInfos = context.getEntries().stream()
+ .map(entry -> new HashInfo(entry))
+ .toList();
+ return new ChangesAndServerView(changes, hashInfos);
}
/**
diff --git a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java
index 69c3c6bf976..c3be22bb6aa 100644
--- a/src/main/java/org/jabref/model/entry/SharedBibEntryData.java
+++ b/src/main/java/org/jabref/model/entry/SharedBibEntryData.java
@@ -11,6 +11,8 @@ public class SharedBibEntryData implements Comparable {
// It has to be unique on remote DBS for all connected JabRef instances.
// The old id above does not satisfy this requirement.
// This is "ID" in JabDrive sync
+ // TODO: Migrate to CUID - see ADR0029, why we chose CUID over UUIDs
+ // We can even limit the length to 10: https://github.com/paralleldrive/cuid2#configuration
private int sharedID;
// Needed for version controlling if used on shared database
diff --git a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
index 7683937c1bd..18a2cbaea92 100644
--- a/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
+++ b/src/test/java/org/jabref/http/server/UpdatesResourceTest.java
@@ -2,7 +2,11 @@
import org.glassfish.jersey.server.ResourceConfig;
import org.jabref.http.sync.state.SyncState;
+import org.jabref.model.database.BibDatabase;
+import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
+import org.jabref.model.metadata.MetaData;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -10,10 +14,11 @@
class UpdatesResourceTest extends ServerTest {
- @BeforeEach
- void resetState() {
- SyncState.INSTANCE.reset();
- }
+ private final String path = "/li changes = SyncState.INSTANCE.changes(0);
- assertEquals(List.of(new BibEntryDTO(entryE1V2), new BibEntryDTO(entryE2V1)), changes);
+ ChangesAndServerView changes = syncState.changesAndServerView(0);
+ assertEquals(new ChangesAndServerView(
+ List.of(new BibEntryDTO(entryE1V2), new BibEntryDTO(entryE2V1)),
+ List.of(
+ new HashInfo(1, entryE1V2.hashCode()),
+ new HashInfo(2, entryE2V1.hashCode()))),
+ changes);
}
}