Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin
});

axiosPost("/objectdetection/rename", "rename an object detection model", {
modelPath: model.modelPath.replace("file:", ""),
modelPath: model.modelPath,
newName: newName
});
showRenameDialog.value.show = false;
Expand Down Expand Up @@ -418,7 +418,7 @@ const handleBulkImport = () => {
<a
ref="exportIndividualModel"
style="color: black; text-decoration: none; display: none"
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath.replace('file:', '')}`"
:href="`http://${address}/api/objectdetection/exportIndividual?modelPath=${showInfo.model.modelPath}`"
:download="`${showInfo.model.nickname}_${showInfo.model.family}_${showInfo.model.version}_${showInfo.model.resolutionWidth}x${showInfo.model.resolutionHeight}_${showInfo.model.labels.join('_')}.${showInfo.model.family.toLowerCase()}`"
target="_blank"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,9 @@

package org.photonvision.common.util.file;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
Expand All @@ -37,97 +31,48 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.io.EofException;

public class JacksonUtils {
public static class UIMap extends HashMap<String, Object> {}

// Custom Path serializer that outputs just the path string without file:/ prefix
public static class PathSerializer extends JsonSerializer<Path> {
@Override
public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (value == null) {
gen.writeNull();
} else {
gen.writeString(value.toString());
}
}
}

// Custom Path deserializer that reads path strings
public static class PathDeserializer extends JsonDeserializer<Path> {
@Override
public Path deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String pathString = p.getValueAsString();
if (pathString == null || pathString.isEmpty()) {
return null;
}

// Handle case where old serialized data might still have file:/ prefix
if (pathString.startsWith("file:/")) {
pathString = pathString.substring(6); // Remove "file:/" prefix
}

return Paths.get(pathString);
}
}

// Custom Path key deserializer for Maps with Path keys
public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer {
@Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
if (key == null || key.isEmpty()) {
return null;
}

// Handle case where old serialized data might still have file:/ prefix
if (key.startsWith("file:/")) {
key = key.substring(6); // Remove "file:/" prefix
}

return Paths.get(key);
}
}

// Helper method to create ObjectMapper with Path serialization support
private static ObjectMapper createObjectMapperWithPathSupport(Class<?> baseType) {
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build();

SimpleModule pathModule = new SimpleModule();
pathModule.addSerializer(Path.class, new PathSerializer());
pathModule.addDeserializer(Path.class, new PathDeserializer());
pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());

return JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.addModule(pathModule)
.build();
}

public static <T> void serialize(Path path, T object) throws IOException {
serialize(path, object, true);
}

public static <T> String serializeToString(T object) throws IOException {
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
ObjectMapper objectMapper =
JsonMapper.builder()
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.build();
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}

public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass());
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
ObjectMapper objectMapper =
JsonMapper.builder()
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.build();
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
saveJsonString(json, path, forceSync);
}

public static <T> T deserialize(Map<?, ?> s, Class<T> ref) throws IOException {
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
ObjectMapper objectMapper =
JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.build();

return objectMapper.convertValue(s, ref);
}

Expand All @@ -136,14 +81,28 @@ public static <T> T deserialize(String s, Class<T> ref) throws IOException {
throw new EofException("Provided empty string for class " + ref.getName());
}

ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
ObjectMapper objectMapper =
JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.build();

return objectMapper.readValue(s, ref);
}

public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref);
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
ObjectMapper objectMapper =
JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
.build();
File jsonFile = new File(path.toString());
if (jsonFile.exists() && jsonFile.length() > 0) {
return objectMapper.readValue(jsonFile, ref);
Expand All @@ -156,12 +115,6 @@ public static <T> T deserialize(Path path, Class<T> ref, StdDeserializer<T> dese
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(ref, deserializer);

// Add Path support to custom deserializer case as well
module.addSerializer(Path.class, new PathSerializer());
module.addDeserializer(Path.class, new PathDeserializer());
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());

objectMapper.registerModule(module);

File jsonFile = new File(path.toString());
Expand All @@ -182,12 +135,6 @@ public static <T> void serialize(
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(ref, serializer);

// Add Path support to custom serializer case as well
module.addSerializer(Path.class, new PathSerializer());
module.addDeserializer(Path.class, new PathDeserializer());
module.addKeyDeserializer(Path.class, new PathKeyDeserializer());

objectMapper.registerModule(module);
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
saveJsonString(json, path, forceSync);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -850,33 +850,30 @@ public static void onBulkImportObjectDetectionModelRequest(Context ctx) {
}
}

private record DeleteObjectDetectionModelRequest(String modelPath) {}
private record DeleteObjectDetectionModelRequest(Path modelPath) {}

public static void onDeleteObjectDetectionModelRequest(Context ctx) {
logger.info("Deleting object detection model");
Path modelPath;

try {
DeleteObjectDetectionModelRequest request =
JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class);

modelPath = Path.of(request.modelPath.substring(7));

if (modelPath == null) {
if (request.modelPath == null) {
ctx.status(400);
ctx.result("The provided model path was malformed");
logger.error("The provided model path was malformed");
return;
}

if (!modelPath.toFile().exists()) {
if (!request.modelPath.toFile().exists()) {
ctx.status(400);
ctx.result("The provided model path does not exist");
logger.error("The provided model path does not exist");
return;
}

if (!modelPath.toFile().delete()) {
if (!request.modelPath.toFile().delete()) {
ctx.status(500);
ctx.result("Unable to delete the model file");
logger.error("Unable to delete the model file");
Expand All @@ -886,7 +883,7 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) {
if (!ConfigManager.getInstance()
.getConfig()
.neuralNetworkPropertyManager()
.removeModel(modelPath)) {
.removeModel(request.modelPath)) {
ctx.status(400);
ctx.result("The model's information was not found in the config");
logger.error("The model's information was not found in the config");
Expand All @@ -910,26 +907,24 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) {
UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig())));
}

private record RenameObjectDetectionModelRequest(String modelPath, String newName) {}
private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {}

public static void onRenameObjectDetectionModelRequest(Context ctx) {
try {
RenameObjectDetectionModelRequest request =
JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class);

Path modelPath = Path.of(request.modelPath);

if (modelPath == null) {
if (request.modelPath == null) {
ctx.status(400);
ctx.result("The provided model path was malformed");
logger.error("The provided model path was malformed");
return;
}

if (!modelPath.toFile().exists()) {
if (!request.modelPath.toFile().exists()) {
ctx.status(400);
ctx.result("The provided model path does not exist");
logger.error("The model path: " + modelPath + " does not exist");
logger.error("The model path: " + request.modelPath + " does not exist");
return;
}

Expand All @@ -943,7 +938,7 @@ public static void onRenameObjectDetectionModelRequest(Context ctx) {
if (!ConfigManager.getInstance()
.getConfig()
.neuralNetworkPropertyManager()
.renameModel(modelPath, request.newName)) {
.renameModel(request.modelPath, request.newName)) {
ctx.status(400);
ctx.result("The model's information was not found in the config");
logger.error("The model's information was not found in the config");
Expand Down
Loading