Skip to content

Commit

Permalink
Handle zipped files
Browse files Browse the repository at this point in the history
  • Loading branch information
gtkirk committed Feb 27, 2024
1 parent 44c6461 commit 6d95ed9
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 60 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"exif-js": "^2.3.0",
"fast-xml-parser": "^4.3.2",
"fflate": "^0.8.2",
"fit-file-parser": "^1.6.18",
"leaflet": "^1.3.3",
"leaflet-easybutton": "^2.3.0",
Expand Down
82 changes: 27 additions & 55 deletions src/track.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@

import { XMLParser } from 'fast-xml-parser';
import FitParser from 'fit-file-parser';
import Pako from 'pako';
import * as fflate from 'fflate';

const parser = new XMLParser({
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
attributesGroupName: '$',
});

const fitParser = new FitParser({
force: true,
mode: 'list',
});

function getSport(sport, name) {
sport = sport?.toLowerCase();

Expand Down Expand Up @@ -190,65 +195,32 @@ function extractFITTracks(fit, name) {
return points.length > 0 ? [{timestamp, points, name, sport}] : [];
}

function readFile(file, encoding, isGzipped) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const result = e.target.result;
try {
return resolve(isGzipped ? Pako.inflate(result) : result);
} catch (e) {
return reject(e);
}
};

if (encoding === 'binary') {
reader.readAsArrayBuffer(file);
} else {
reader.readAsText(file);
}
});
}

export default function extractTracks(file) {
const isGzipped = /\.gz$/i.test(file.name);
const strippedName = file.name.replace(/\.gz$/i, '');
const format = strippedName.split('.').pop().toLowerCase();

export default function extractTracks(name, contents) {
const format = name.split('.').pop().toLowerCase();
switch (format) {
case 'gpx':
case 'tcx': /* Handle XML based file formats the same way */

return readFile(file, 'text', isGzipped)
.then(textContents => new Promise((resolve, reject) => {
const result = parser.parse(textContents);
case 'gpx':
case 'tcx':
return new Promise((resolve, reject) => {
const result = xmlParser.parse(fflate.strFromU8(contents));
if (result.gpx) {
resolve(extractGPXTracks(result.gpx));
} else if (result.TrainingCenterDatabase) {
resolve(extractTCXTracks(result.TrainingCenterDatabase, strippedName));
resolve(extractTCXTracks(result.TrainingCenterDatabase, name));
} else {
reject(new Error('Invalid file type.'));
}
}));

case 'fit':
return readFile(file, 'binary', isGzipped)
.then(contents => new Promise((resolve, reject) => {
const parser = new FitParser({
force: true,
mode: 'list',
});

parser.parse(contents, (err, result) => {
if (err) {
reject(err);
} else {
resolve(extractFITTracks(result, strippedName));
}
});
}));

default:
throw `Unsupported file format: ${format}`;
});
case 'fit':
return new Promise((resolve, reject) => {
fitParser.parse(contents, (err, result) => {
if (err) {
reject(err);
} else {
resolve(extractFITTracks(result, name));
}
});
});
default:
throw `Unsupported file format: ${format}`;
}
}
56 changes: 51 additions & 5 deletions src/ui.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import picoModal from 'picomodal';
import extractTracks from './track';
import Image from './image';
import * as fflate from 'fflate';

const AVAILABLE_THEMES = [
'CartoDB.DarkMatter',
Expand Down Expand Up @@ -85,21 +86,66 @@ function handleFileSelect(map, evt) {
modal.addSuccess();
};

const handleTrackFile = async (file) => {
for (const track of await extractTracks(file)) {
const handleTrackFile = async (file, contents) => {
for (const track of await extractTracks(file, contents)) {
track.filename = file.name;
tracks.push(track);
map.addTrack(track);
}
modal.addSuccess();
};

async function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const result = e.target.result;
try {
return resolve(result);
} catch (e) {
return reject(e);
}
};

reader.readAsArrayBuffer(file);
});
}

async function unzip(bytes) {
return new Promise ((resolve, reject) => {
fflate.unzip(new Uint8Array(bytes),
{ filter(file) { return !file.name.endsWith('/'); } },
(err, data) => {
if (err) { reject(err); }
resolve(data);
});
});
}

const handleZipEntry = async entry =>
{
return new Promise((resolve) => setTimeout(resolve, 0))
.then(() => handleTrackFile(...entry));
};

const handleZip = async file => {
return readFile(file)
.then(contents => unzip(contents))
.then(unzipped => {
modal.addDirectoryEntries(Object.keys(unzipped).length);
return Promise.all(Object.entries(unzipped).map(handleZipEntry));
});
}

const handleFile = async file => {
try {
if (/\.jpe?g$/i.test(file.name)) {
return await handleImage(file);
}
return await handleTrackFile(file);
if (/\.zip$/i.test(file.name)) {
return await handleZip(file);
}
return await readFile(file).then((contents) => handleTrackFile(file.name, contents));
} catch (err) {
console.error(err);
modal.addFailure({name: file.name, error: err});
Expand Down Expand Up @@ -131,7 +177,7 @@ function handleFileSelect(map, evt) {
});
};

const readFile = async entry => {
const resolveFile = async entry => {
return new Promise(resolve => {
entry.file(resolve);
});
Expand All @@ -141,7 +187,7 @@ function handleFileSelect(map, evt) {
{
if (entry.isFile)
{
return await readFile(entry).then(handleFile);
return await resolveFile(entry).then(handleFile);
}
else if (entry.isDirectory)
{
Expand Down

0 comments on commit 6d95ed9

Please sign in to comment.