diff --git a/src/main/java/net/javahippie/fitpub/service/ActivityFileService.java b/src/main/java/net/javahippie/fitpub/service/ActivityFileService.java index dcd60f7..060d0f6 100644 --- a/src/main/java/net/javahippie/fitpub/service/ActivityFileService.java +++ b/src/main/java/net/javahippie/fitpub/service/ActivityFileService.java @@ -378,10 +378,16 @@ public class ActivityFileService { byte[] rawFile, ProcessingOptions options ) throws JsonProcessingException { - // Generate title if not provided - String activityTitle = title != null && !title.isBlank() - ? title - : ActivityFormatter.generateActivityTitle(parsedData.getStartTime(), parsedData.getActivityType()); + String activityTitle; + if (title != null && !title.isBlank()) { + activityTitle = title; + } else if (parsedData.getTitle() != null) { + // Try to use title from input file + activityTitle = parsedData.getTitle(); + } else { + // Generate title if not provided + activityTitle = ActivityFormatter.generateActivityTitle(parsedData.getStartTime(), parsedData.getActivityType()); + } // Default to PUBLIC if visibility not specified Activity.Visibility activityVisibility = visibility != null ? visibility : Activity.Visibility.PRIVATE; diff --git a/src/main/java/net/javahippie/fitpub/util/GpxParser.java b/src/main/java/net/javahippie/fitpub/util/GpxParser.java index 0900af8..f66c4d6 100644 --- a/src/main/java/net/javahippie/fitpub/util/GpxParser.java +++ b/src/main/java/net/javahippie/fitpub/util/GpxParser.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import static net.javahippie.fitpub.util.ParsedActivityData.MAX_TITLE_LENGTH; + /** * Parser for GPX (GPS Exchange Format) files. * Extracts GPS coordinates, activity metrics from track points. @@ -80,8 +82,12 @@ public class GpxParser { // Calculate duration parsedData.setTotalDuration(Duration.between(firstPoint.getTimestamp(), lastPoint.getTimestamp())); - // Extract activity type from metadata - extractActivityType(doc, parsedData); + // Extract activity type and title from metadata + Optional track = getFirstTrack(doc); + if (track.isPresent()) { + extractActivityType(track.get(), parsedData); + extractActivityTitle(track.get(), parsedData); + } // Determine timezone from first GPS coordinate determineTimezone(parsedData); @@ -111,6 +117,8 @@ public class GpxParser { } } + + /** * Extracts track points from GPX document. */ @@ -245,21 +253,40 @@ public class GpxParser { } } - /** - * Extracts activity type from GPX metadata. + /* + * Returns the first element from the GPS XML */ - private void extractActivityType(Document doc, ParsedActivityData parsedData) { + private Optional getFirstTrack(Document doc) { NodeList tracks = doc.getElementsByTagName("trk"); if (tracks.getLength() == 0) { tracks = doc.getElementsByTagNameNS("*", "trk"); } - if (tracks.getLength() > 0) { - Element track = (Element) tracks.item(0); - String type = getElementText(track, "type"); - if (type != null) { - parsedData.setActivityType(mapGpxTypeToActivityType(type)); + return tracks.getLength() > 0 ? Optional.of((Element) tracks.item(0)) : Optional.empty(); + } + + /** + * Extracts activity type from GPX metadata. + */ + private void extractActivityType(Element track, ParsedActivityData parsedData) { + String type = getElementText(track, "type"); + if (type != null) { + parsedData.setActivityType(mapGpxTypeToActivityType(type)); + } + } + + /** + * Extracts activity title from GPX metadata. + */ + private void extractActivityTitle(Element track, ParsedActivityData parsedData) { + String title = getElementText(track, "name"); + if (title != null) { + String shortenedTitle = title; + if (title.length() > MAX_TITLE_LENGTH) { + log.debug("Activity title was shortened to {} characters: {}", MAX_TITLE_LENGTH, title); + shortenedTitle = title.substring(0, MAX_TITLE_LENGTH); } + parsedData.setTitle(shortenedTitle); } } diff --git a/src/main/java/net/javahippie/fitpub/util/ParsedActivityData.java b/src/main/java/net/javahippie/fitpub/util/ParsedActivityData.java index 12ff347..d345f91 100644 --- a/src/main/java/net/javahippie/fitpub/util/ParsedActivityData.java +++ b/src/main/java/net/javahippie/fitpub/util/ParsedActivityData.java @@ -20,6 +20,9 @@ import java.util.List; */ @Data public class ParsedActivityData { + + static final int MAX_TITLE_LENGTH = 255; + private List trackPoints = new ArrayList<>(); private LocalDateTime startTime; private LocalDateTime endTime; @@ -30,6 +33,7 @@ public class ParsedActivityData { private BigDecimal elevationGain; private BigDecimal elevationLoss; private Activity.ActivityType activityType = Activity.ActivityType.OTHER; + private String title; private ActivityMetricsData metrics; private String sourceFormat; // "FIT" or "GPX" private Boolean indoor = false; // Indicates if this is an indoor activity diff --git a/src/test/java/net/javahippie/fitpub/util/GpxParserIntegrationTest.java b/src/test/java/net/javahippie/fitpub/util/GpxParserIntegrationTest.java index 55ab855..7bad2cb 100644 --- a/src/test/java/net/javahippie/fitpub/util/GpxParserIntegrationTest.java +++ b/src/test/java/net/javahippie/fitpub/util/GpxParserIntegrationTest.java @@ -112,6 +112,10 @@ class GpxParserIntegrationTest { // Verify at least some basic data assertNotNull(parsedData.getActivityType(), "Activity type should be determined"); + String parsedTitle = parsedData.getTitle(); + assertEquals(255, parsedTitle.length()); + assertTrue(parsedTitle.startsWith("Einmal Frust loswerden")); + assertFalse(parsedTitle.contains("Shouldn't appear")); assertEquals(Activity.ActivityType.RUN, parsedData.getActivityType(), "Activity type should be RUN (from GPX running)"); assertTrue(parsedData.getTrackPoints().size() > 0, "Should have at least one track point"); diff --git a/src/test/resources/7410863774.gpx b/src/test/resources/7410863774.gpx index 848716a..39647af 100644 --- a/src/test/resources/7410863774.gpx +++ b/src/test/resources/7410863774.gpx @@ -4,7 +4,7 @@ - Einmal Frust loswerden + Einmal Frust loswerden blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel blafasel Shouldn't appear running