Get activity title from uploaded file, if it is present (#7)

* Use activity title from GPX file when present

* Extend test

* Test shortening of long names

* Extract constant

---------

Co-authored-by: Niklas Deutschmann <sonstharmlos@noreply.codeberg.org>
This commit is contained in:
Niklas 2026-04-13 10:12:38 +02:00 committed by GitHub
parent 6afd7a5dad
commit 03b8e6d315
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 56 additions and 15 deletions

View file

@ -378,10 +378,16 @@ public class ActivityFileService {
byte[] rawFile, byte[] rawFile,
ProcessingOptions options ProcessingOptions options
) throws JsonProcessingException { ) throws JsonProcessingException {
// Generate title if not provided String activityTitle;
String activityTitle = title != null && !title.isBlank() if (title != null && !title.isBlank()) {
? title activityTitle = title;
: ActivityFormatter.generateActivityTitle(parsedData.getStartTime(), parsedData.getActivityType()); } 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 // Default to PUBLIC if visibility not specified
Activity.Visibility activityVisibility = visibility != null ? visibility : Activity.Visibility.PRIVATE; Activity.Visibility activityVisibility = visibility != null ? visibility : Activity.Visibility.PRIVATE;

View file

@ -26,6 +26,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static net.javahippie.fitpub.util.ParsedActivityData.MAX_TITLE_LENGTH;
/** /**
* Parser for GPX (GPS Exchange Format) files. * Parser for GPX (GPS Exchange Format) files.
* Extracts GPS coordinates, activity metrics from track points. * Extracts GPS coordinates, activity metrics from track points.
@ -80,8 +82,12 @@ public class GpxParser {
// Calculate duration // Calculate duration
parsedData.setTotalDuration(Duration.between(firstPoint.getTimestamp(), lastPoint.getTimestamp())); parsedData.setTotalDuration(Duration.between(firstPoint.getTimestamp(), lastPoint.getTimestamp()));
// Extract activity type from metadata // Extract activity type and title from metadata
extractActivityType(doc, parsedData); Optional<Element> track = getFirstTrack(doc);
if (track.isPresent()) {
extractActivityType(track.get(), parsedData);
extractActivityTitle(track.get(), parsedData);
}
// Determine timezone from first GPS coordinate // Determine timezone from first GPS coordinate
determineTimezone(parsedData); determineTimezone(parsedData);
@ -111,6 +117,8 @@ public class GpxParser {
} }
} }
/** /**
* Extracts track points from GPX document. * Extracts track points from GPX document.
*/ */
@ -245,21 +253,40 @@ public class GpxParser {
} }
} }
/** /*
* Extracts activity type from GPX metadata. * Returns the first <trk> element from the GPS XML
*/ */
private void extractActivityType(Document doc, ParsedActivityData parsedData) { private Optional<Element> getFirstTrack(Document doc) {
NodeList tracks = doc.getElementsByTagName("trk"); NodeList tracks = doc.getElementsByTagName("trk");
if (tracks.getLength() == 0) { if (tracks.getLength() == 0) {
tracks = doc.getElementsByTagNameNS("*", "trk"); tracks = doc.getElementsByTagNameNS("*", "trk");
} }
if (tracks.getLength() > 0) { return tracks.getLength() > 0 ? Optional.of((Element) tracks.item(0)) : Optional.empty();
Element track = (Element) tracks.item(0); }
String type = getElementText(track, "type");
if (type != null) { /**
parsedData.setActivityType(mapGpxTypeToActivityType(type)); * 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);
} }
} }

View file

@ -20,6 +20,9 @@ import java.util.List;
*/ */
@Data @Data
public class ParsedActivityData { public class ParsedActivityData {
static final int MAX_TITLE_LENGTH = 255;
private List<TrackPointData> trackPoints = new ArrayList<>(); private List<TrackPointData> trackPoints = new ArrayList<>();
private LocalDateTime startTime; private LocalDateTime startTime;
private LocalDateTime endTime; private LocalDateTime endTime;
@ -30,6 +33,7 @@ public class ParsedActivityData {
private BigDecimal elevationGain; private BigDecimal elevationGain;
private BigDecimal elevationLoss; private BigDecimal elevationLoss;
private Activity.ActivityType activityType = Activity.ActivityType.OTHER; private Activity.ActivityType activityType = Activity.ActivityType.OTHER;
private String title;
private ActivityMetricsData metrics; private ActivityMetricsData metrics;
private String sourceFormat; // "FIT" or "GPX" private String sourceFormat; // "FIT" or "GPX"
private Boolean indoor = false; // Indicates if this is an indoor activity private Boolean indoor = false; // Indicates if this is an indoor activity

View file

@ -112,6 +112,10 @@ class GpxParserIntegrationTest {
// Verify at least some basic data // Verify at least some basic data
assertNotNull(parsedData.getActivityType(), "Activity type should be determined"); 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(), assertEquals(Activity.ActivityType.RUN, parsedData.getActivityType(),
"Activity type should be RUN (from GPX <type>running</type>)"); "Activity type should be RUN (from GPX <type>running</type>)");
assertTrue(parsedData.getTrackPoints().size() > 0, "Should have at least one track point"); assertTrue(parsedData.getTrackPoints().size() > 0, "Should have at least one track point");

View file

@ -4,7 +4,7 @@
<time>2022-07-03T19:47:51Z</time> <time>2022-07-03T19:47:51Z</time>
</metadata> </metadata>
<trk> <trk>
<name>Einmal Frust loswerden</name> <name>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</name>
<type>running</type> <type>running</type>
<trkseg> <trkseg>
<trkpt lat="48.0140070" lon="7.8513840"> <trkpt lat="48.0140070" lon="7.8513840">