diff --git a/client/src/main/java/me/retrodaredevil/solarthing/program/SolarMain.java b/client/src/main/java/me/retrodaredevil/solarthing/program/SolarMain.java index a915384f..39004e59 100644 --- a/client/src/main/java/me/retrodaredevil/solarthing/program/SolarMain.java +++ b/client/src/main/java/me/retrodaredevil/solarthing/program/SolarMain.java @@ -25,6 +25,7 @@ import me.retrodaredevil.solarthing.packets.instance.InstanceSourcePackets; import me.retrodaredevil.solarthing.program.action.RunActionMain; import me.retrodaredevil.solarthing.program.check.CheckMain; +import me.retrodaredevil.solarthing.program.couchdb.CouchDbSetupMain; import me.retrodaredevil.solarthing.program.pvoutput.PVOutputUploadMain; import org.apache.logging.log4j.LogManager; import org.slf4j.Logger; diff --git a/client/src/main/java/me/retrodaredevil/solarthing/program/CouchDbSetupMain.java b/client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CouchDbSetupMain.java similarity index 95% rename from client/src/main/java/me/retrodaredevil/solarthing/program/CouchDbSetupMain.java rename to client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CouchDbSetupMain.java index 72b8291e..78d2f1ba 100644 --- a/client/src/main/java/me/retrodaredevil/solarthing/program/CouchDbSetupMain.java +++ b/client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CouchDbSetupMain.java @@ -1,4 +1,4 @@ -package me.retrodaredevil.solarthing.program; +package me.retrodaredevil.solarthing.program.couchdb; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -144,7 +144,13 @@ private void addPacketFiltersDesign(SolarThingDatabaseType databaseType, CouchDb public int doCouchDbSetupMain() throws CouchDbException { out.println("You will now setup your CouchDB instance! Some databases will be automatically created (enter)"); - prompt.promptContinue(); + String customCommand = prompt.promptContinueOrCustomCommand(); + if (customCommand != null) { + if ("wmf-custom-2024-03-18".equals(customCommand)) { + return new CustomWmfCouchDbEdit20240318(instance, out, prompt).doCustom(); + } + return 1; + } for (SolarThingDatabaseType databaseType : SolarThingDatabaseType.values()) { createDatabase(databaseType.getName()); @@ -233,6 +239,7 @@ private UserEntry(String name, String password) { } public interface Prompt { void promptContinue(); + @Nullable String promptContinueOrCustomCommand(); @Nullable String promptUserName(SolarThingDatabaseType.UserType userType); @NotNull String promptUserPassword(SolarThingDatabaseType.UserType userType); } @@ -249,6 +256,12 @@ public void promptContinue() { scanner.nextLine(); } + @Override + public @Nullable String promptContinueOrCustomCommand() { + String r = scanner.nextLine(); + return r.isEmpty() ? null : r; + } + @Override public @Nullable String promptUserName(SolarThingDatabaseType.UserType userType) { String r = scanner.nextLine(); diff --git a/client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CustomWmfCouchDbEdit20240318.java b/client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CustomWmfCouchDbEdit20240318.java new file mode 100644 index 00000000..4e9dd451 --- /dev/null +++ b/client/src/main/java/me/retrodaredevil/solarthing/program/couchdb/CustomWmfCouchDbEdit20240318.java @@ -0,0 +1,94 @@ +package me.retrodaredevil.solarthing.program.couchdb; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import me.retrodaredevil.couchdbjava.CouchDbDatabase; +import me.retrodaredevil.couchdbjava.CouchDbInstance; +import me.retrodaredevil.couchdbjava.exception.CouchDbException; +import me.retrodaredevil.couchdbjava.json.jackson.CouchDbJacksonUtil; +import me.retrodaredevil.couchdbjava.json.jackson.JacksonJsonData; +import me.retrodaredevil.couchdbjava.request.ViewQuery; +import me.retrodaredevil.couchdbjava.request.ViewQueryParamsBuilder; +import me.retrodaredevil.couchdbjava.response.ViewResponse; +import me.retrodaredevil.solarthing.SolarThingConstants; +import me.retrodaredevil.solarthing.couchdb.SolarThingCouchDb; + +import java.io.PrintStream; +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +public class CustomWmfCouchDbEdit20240318 { + /** + * The very first bad data is from {@code 240318,05,14/30|zDw4/g} (tracer), which had a timestamp of 1710761245129. We will round down to the nearest second + */ + private static final Instant BAD_TIMESTAMP_START = Instant.ofEpochMilli(1710761245000L); + /** + * The first last bad data is from {@code 240318,06,06/37|3iyxcA} (mate), which had a timestamp of 1710763772164. We will round up to the nearest second + */ + private static final Instant BAD_TIMESTAMP_END = Instant.ofEpochMilli(1710763773000L); + private static final List BAD_IDS = List.of( + "3iyxcA", // Mate + "RatxnA", // Rover + "zDw4/g", // Tracer + "Mf9gVg", // Temperature outdoor + "tTSMQg" // Temperature battery room + ); + private final CouchDbInstance instance; + private final PrintStream out; + private final CouchDbSetupMain.Prompt prompt; + + public CustomWmfCouchDbEdit20240318(CouchDbInstance instance, PrintStream out, CouchDbSetupMain.Prompt prompt) { + this.instance = requireNonNull(instance); + this.out = requireNonNull(out); + this.prompt = requireNonNull(prompt); + } + + public int doCustom() throws CouchDbException { + out.println("Going to do some stuff to shift a specific set of data"); + CouchDbDatabase database = instance.getDatabase(SolarThingConstants.STATUS_DATABASE); + ViewQuery viewQuery = SolarThingCouchDb.createMillisNullView( + new ViewQueryParamsBuilder() + .startKey(BAD_TIMESTAMP_START.toEpochMilli()) + .endKey(BAD_TIMESTAMP_END.toEpochMilli()) + .includeDocs(true) + .build() + ); + ViewResponse response = database.queryView(viewQuery); + for (ViewResponse.DocumentEntry row : response.getRows()) { + if (BAD_IDS.stream().anyMatch(endId -> row.getId().endsWith("|" + endId))) { + out.println(row.getId()); + final JsonNode json; + try { + json = CouchDbJacksonUtil.getNodeFrom(row.getDoc()); + } catch (JsonProcessingException e) { + e.printStackTrace(out); + out.println("Could not parse JSON data from " + row.getId() + ". Exiting"); + return 1; + } + ObjectNode object = json.deepCopy(); + long dateMillis = object.get("dateMillis").asLong(); + if (dateMillis < BAD_TIMESTAMP_START.toEpochMilli() || dateMillis > BAD_TIMESTAMP_END.toEpochMilli()) { + out.println("Could not parse dateMillis for " + row.getId() + ". Exiting"); + return 1; + } + if (object.get("editReason") != null) { + out.println("Document " + row.getId() + " has already been edited!"); + continue; + } + long newDateMillis = dateMillis + Duration.ofSeconds(60 * 60 + 2 * 60 + 10).toMillis(); // plus 1 hour, 2 minutes, 10 seconds + object.set("dateMillis", new LongNode(newDateMillis)); + object.set("originalDateMillis", new LongNode(dateMillis)); + object.set("editReason", new TextNode("wmf-custom-2024-03-18")); + database.putDocument(row.getId(), new JacksonJsonData(object)); + } + } + + return 0; + } +} diff --git a/client/src/main/java/me/retrodaredevil/solarthing/program/receiver/TracerPacketListUpdater.java b/client/src/main/java/me/retrodaredevil/solarthing/program/receiver/TracerPacketListUpdater.java index 4b1bde9c..1822eb9c 100644 --- a/client/src/main/java/me/retrodaredevil/solarthing/program/receiver/TracerPacketListUpdater.java +++ b/client/src/main/java/me/retrodaredevil/solarthing/program/receiver/TracerPacketListUpdater.java @@ -66,7 +66,8 @@ public void receive(List packets) { desiredTime = Instant.now().atZone(zone).toLocalDateTime(); } if (Duration.between(currentTime, desiredTime).abs().compareTo(tracerClockOptions.getDurationThreshold()) > 0) { - LOGGER.info("Going to update time to " + desiredTime + " from " + currentTime); + // Logging both as summary is important because we want to be able to see the time in the summary logs + LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Going to update time to " + desiredTime + " from " + currentTime); write.setSolarThingLocalDateTime(desiredTime); LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Success updating time!"); } diff --git a/client/src/test/java/me/retrodaredevil/solarthing/integration/IntegrationSetup.java b/client/src/test/java/me/retrodaredevil/solarthing/integration/IntegrationSetup.java index 923fd656..96fd7e7c 100644 --- a/client/src/test/java/me/retrodaredevil/solarthing/integration/IntegrationSetup.java +++ b/client/src/test/java/me/retrodaredevil/solarthing/integration/IntegrationSetup.java @@ -6,7 +6,7 @@ import me.retrodaredevil.solarthing.annotations.NotNull; import me.retrodaredevil.solarthing.annotations.Nullable; import me.retrodaredevil.solarthing.annotations.UtilityClass; -import me.retrodaredevil.solarthing.program.CouchDbSetupMain; +import me.retrodaredevil.solarthing.program.couchdb.CouchDbSetupMain; import java.io.OutputStream; import java.io.PrintStream; @@ -29,6 +29,11 @@ private static class IntegrationPrompt implements CouchDbSetupMain.Prompt { public void promptContinue() { } + @Override + public @Nullable String promptContinueOrCustomCommand() { + return null; + } + @Override public @Nullable String promptUserName(SolarThingDatabaseType.UserType userType) { return IntegrationUtil.getAuthFor(userType).getUsername();