Skip to content
Draft
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
82 changes: 81 additions & 1 deletion backend/src/main/java/com/mcmasterbaja/FileUploadResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.core.MediaType;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -46,6 +50,7 @@ public void uploadFile(
}

String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
Path csvRoot = storageService.load(Paths.get("csv"));

switch (fileExtension) {
case "csv":
Expand All @@ -69,7 +74,6 @@ public void uploadFile(

case "fit":
// Prepare output directory csv/<FitName>
Path csvRoot = storageService.load(Paths.get("csv"));
String fitName = fileName.substring(0, fileName.lastIndexOf('.'));
Path fitDir = csvRoot.resolve(fitName);
try {
Expand Down Expand Up @@ -142,6 +146,82 @@ public void uploadFile(
}
break;

case "txt":
// Prepare output directory csv/<FitName>
String txtName = fileName.substring(0, fileName.lastIndexOf('.'));
Path txtDir = csvRoot.resolve(txtName);

// Create directory if it doesn't exist
try {
Files.createDirectories(txtDir);
} catch (IOException e) {
throw new StorageException("Failed to store TXT file: " + fileName, e);
}

try (BufferedReader reader = new BufferedReader(new InputStreamReader(fileData))) {

// Parse header
String headerLine = reader.readLine();
if (headerLine == null || headerLine.trim().isEmpty()) {
throw new InvalidInputFileException("TXT file is empty or missing header: " + fileName);
}

String[] headers = headerLine.trim().split(", ");
int columnCount = headers.length;
if (columnCount < 2) {
throw new InvalidInputFileException("TXT file must have at least two columns: " + fileName);
}

// Prepare StringBuilders for each column/CSV
List<StringBuilder> columnCSVs = new ArrayList<>(columnCount - 1);
for (int i = 1; i < columnCount; i++) {
StringBuilder sb = new StringBuilder();
sb.append("Timestamp (ms), ").append(headers[i]).append("\n");
columnCSVs.add(sb);
}

// Time conversion setup
long baseTimeMs = -1;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;

String[] parts = line.split("\\s*,\\s*|\\s+");
if (parts.length != columnCount) continue;

try {
java.time.LocalTime time = java.time.LocalTime.parse(parts[0]);
long currentMs = time.toSecondOfDay() * 1000L + time.getNano() / 1_000_000L;

if (baseTimeMs == -1) baseTimeMs = currentMs;
long relativeMs = currentMs - baseTimeMs;

for (int c = 1; c < columnCount; c++) {
columnCSVs.get(c - 1).append(relativeMs).append(',').append(parts[c]).append('\n');
}

} catch (Exception e) {
logger.warn("Skipping malformed line: " + line);
}
}

// Write to each CSV file
for (int i = 1; i < columnCount; i++) {
Path outputPath = txtDir.resolve(headers[i] + ".csv");
try (InputStream in = new ByteArrayInputStream(
columnCSVs.get(i - 1).toString().getBytes(StandardCharsets.UTF_8))) {
storageService.store(in, outputPath);
}
}


} catch (IOException e) {
throw new StorageException("Failed to read TXT file: " + fileName, e);
}

break;

default:
try {
fileData.close();
Expand Down
2 changes: 1 addition & 1 deletion front-end/src/components/ui/uploadForm/UploadForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface UploadFormProps {
accept?: string;
}

export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, .fit' }: UploadFormProps) => {
export const UploadForm = ({ files, setFiles, accept = '.csv, .bin, .mp4, .mov, .fit, .txt' }: UploadFormProps) => {
const [isDragging, setIsDragging] = React.useState(false);

const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
Expand Down
5 changes: 4 additions & 1 deletion front-end/src/types/ApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ export const dataTypesArray = [
'GPS SPEED',
'RPM PRIM',
'RPM SEC',
'IMU X'
'IMU X',
'IMU ACCEL X',
'IMU ACCEL Y',
'IMU ACCEL Z',
] as const;

// Derive the union type from the array
Expand Down
Loading