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,
ProcessingOptions options
) throws JsonProcessingException {
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
String activityTitle = title != null && !title.isBlank()
? title
: ActivityFormatter.generateActivityTitle(parsedData.getStartTime(), parsedData.getActivityType());
activityTitle = ActivityFormatter.generateActivityTitle(parsedData.getStartTime(), parsedData.getActivityType());
}
// Default to PUBLIC if visibility not specified
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.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<Element> 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,22 +253,41 @@ 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");
if (tracks.getLength() == 0) {
tracks = doc.getElementsByTagNameNS("*", "trk");
}
if (tracks.getLength() > 0) {
Element track = (Element) tracks.item(0);
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);
}
}
/**

View file

@ -20,6 +20,9 @@ import java.util.List;
*/
@Data
public class ParsedActivityData {
static final int MAX_TITLE_LENGTH = 255;
private List<TrackPointData> 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

View file

@ -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 <type>running</type>)");
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>
</metadata>
<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>
<trkseg>
<trkpt lat="48.0140070" lon="7.8513840">