From ec2651207cedd3834204107a95f47c6bc55b11ae Mon Sep 17 00:00:00 2001
From: Matthew Dillon <matthewrdillon@gmail.com>
Date: Thu, 12 Nov 2020 16:09:28 -0700
Subject: [PATCH] Initial implementation (#4)

---
 Cargo.lock     | 209 ++++++++++++++++++++++++++++++++++++++++++++++---
 Cargo.toml     |   6 +-
 src/lib.rs     |  14 ++--
 src/utils.rs   |  48 ++++++++++--
 www/index.html |   1 +
 www/index.js   |  28 ++++++-
 6 files changed, 281 insertions(+), 25 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 3a67285..a313527 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,46 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "addr2line"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
+
+[[package]]
+name = "assert_approx_eq"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backtrace"
+version = "0.3.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2baad346b2d4e94a24347adeee9c7a93f412ee94b9cc26e5b59dea23848e9f28"
+dependencies = [
+ "addr2line",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "bumpalo"
 version = "3.4.0"
@@ -12,21 +53,79 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
 [[package]]
 name = "console_error_panic_hook"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "error-chain"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "geo-types"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "866e8f6dbd2218b05ea8a25daa1bfac32b0515fe7e0a37cb6a7b9ed0ed82a07e"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "gimli"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+
+[[package]]
+name = "gpx"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96f48f1635cee5d19d368d8142fb38b3ca5a6ae8b805f0cf42590751f3b33be"
+dependencies = [
+ "assert_approx_eq",
+ "chrono",
+ "error-chain",
+ "geo-types",
+ "xml-rs",
+]
+
 [[package]]
 name = "gpx-web-utils"
 version = "0.0.1"
 dependencies = [
  "console_error_panic_hook",
+ "gpx",
+ "js-sys",
  "wasm-bindgen",
  "wasm-bindgen-test",
 ]
@@ -46,20 +145,61 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
+[[package]]
+name = "libc"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+
 [[package]]
 name = "log"
 version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
 ]
 
 [[package]]
-name = "proc-macro2"
-version = "1.0.21"
+name = "miniz_oxide"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
 dependencies = [
  "unicode-xid",
 ]
@@ -73,6 +213,12 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
@@ -81,28 +227,45 @@ checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
 
 [[package]]
 name = "syn"
-version = "1.0.41"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-xid",
 ]
 
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "wasm-bindgen-macro",
 ]
 
@@ -127,7 +290,7 @@ version = "0.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -195,3 +358,31 @@ dependencies = [
  "js-sys",
  "wasm-bindgen",
 ]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
diff --git a/Cargo.toml b/Cargo.toml
index 675a95c..e16062d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,8 @@ default = ["console_error_panic_hook"]
 
 [dependencies]
 wasm-bindgen = "0.2.63"
+js-sys = "0.3.45"
+gpx = "0.8.1"
 
 console_error_panic_hook = { version = "0.1.6", optional = true }
 
@@ -22,5 +24,5 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
 wasm-bindgen-test = "0.3.13"
 
 [profile.release]
-# Tell `rustc` to optimize for small code size.
-opt-level = "s"
+opt-level = 3
+lto = true
diff --git a/src/lib.rs b/src/lib.rs
index 77a676f..78ea0c8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,14 +2,16 @@ mod utils;
 
 use wasm_bindgen::prelude::*;
 
-#[wasm_bindgen]
-extern "C" {
-    fn alert(s: &str);
-}
+#[cfg(feature = "wee_alloc")]
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
 
 #[wasm_bindgen]
-pub fn greet() {
+pub fn merge(files: js_sys::Array) -> wasm_bindgen::JsValue {
     utils::set_panic_hook();
+    let files: Vec<String> = utils::translate_js_to_rust(files);
+    let merged: gpx::Gpx = utils::join_gpx_files(files);
+    let out_vec: Vec<u8> = utils::write_gpx_to_buffer(merged);
 
-    alert("Hello, gpx-web-utils!");
+    JsValue::from_str(&String::from_utf8(out_vec).unwrap())
 }
diff --git a/src/utils.rs b/src/utils.rs
index b1d7929..c8cd025 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,10 +1,46 @@
 pub fn set_panic_hook() {
-    // When the `console_error_panic_hook` feature is enabled, we can call the
-    // `set_panic_hook` function at least once during initialization, and then
-    // we will get better error messages if our code ever panics.
-    //
-    // For more details see
-    // https://github.com/rustwasm/console_error_panic_hook#readme
     #[cfg(feature = "console_error_panic_hook")]
     console_error_panic_hook::set_once();
 }
+
+pub fn translate_js_to_rust(files: js_sys::Array) -> Vec<String> {
+    // https://github.com/rustwasm/wasm-bindgen/issues/111
+    files.iter().map(|f| f.as_string().unwrap()).collect()
+}
+
+pub fn join_gpx_files(files: Vec<String>) -> gpx::Gpx {
+    let mut merged: gpx::Gpx = Default::default();
+
+    for file in files.iter() {
+        let buffer = std::io::BufReader::new(file.as_bytes());
+        let mut parsed_gpx: gpx::Gpx = gpx::read(buffer).expect("invalid gpx");
+
+        merged.tracks.append(&mut parsed_gpx.tracks);
+        merged.waypoints.append(&mut parsed_gpx.waypoints);
+    }
+
+    let link = gpx::Link {
+        href: String::from("https://gpx.thermokar.st"),
+        ..Default::default()
+    };
+    let author = gpx::Person {
+        link: Some(link),
+        ..Default::default()
+    };
+    let metadata = gpx::Metadata {
+        name: Some(String::from("merged")),
+        author: Some(author),
+        ..Default::default()
+    };
+    merged.metadata = Some(metadata);
+    merged.version = gpx::GpxVersion::Gpx11;
+
+    merged
+}
+
+pub fn write_gpx_to_buffer(gpx: gpx::Gpx) -> Vec<u8> {
+    let mut buffer = Vec::new();
+    gpx::write(&gpx, &mut buffer).unwrap();
+
+    buffer
+}
diff --git a/www/index.html b/www/index.html
index 9f40409..1951cd3 100644
--- a/www/index.html
+++ b/www/index.html
@@ -6,6 +6,7 @@
   </head>
   <body>
     <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
+    <input id="input" type="file" multiple>
     <script src="./bootstrap.js"></script>
   </body>
 </html>
diff --git a/www/index.js b/www/index.js
index 53c5ada..9a40654 100644
--- a/www/index.js
+++ b/www/index.js
@@ -1,3 +1,27 @@
-import * as wasm from "gpx-web-utils";
+import * as gpx from "gpx-web-utils";
 
-wasm.greet();
+const inputElement = document.getElementById("input");
+
+inputElement.addEventListener("change", readFiles, false);
+
+function readFiles() {
+  if (inputElement.files.length < 2) { alert("open two or more files"); return; }
+
+  const files = Array.from(inputElement.files);
+  const promises = files.map(f => f.text());
+
+  Promise.all(promises).then(gpxes => {
+    const merged = gpx.merge(gpxes);
+    writeOutput(merged);
+    inputElement.value = "";
+  });
+}
+
+function writeOutput(file) {
+    const blob = new Blob([file], {type: "text/gpx"});
+    const a = document.createElement("a");
+    a.href = URL.createObjectURL(blob);
+    a.download = "merged.gpx";
+    a.click();
+    URL.revokeObjectURL(a.href);
+}