mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-06 10:39:57 +02:00
Start working on structs for download functionallity
This commit is contained in:
parent
b57e85a8ec
commit
3f3f12aa70
6 changed files with 513 additions and 2 deletions
78
Cargo.lock
generated
78
Cargo.lock
generated
|
@ -3,6 +3,14 @@ name = "adler32"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -240,6 +248,7 @@ dependencies = [
|
||||||
"hyper 0.11.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.11.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"openssl 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -467,6 +476,14 @@ name = "matches"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
|
@ -750,6 +767,26 @@ dependencies = [
|
||||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "relay"
|
name = "relay"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -960,6 +997,15 @@ dependencies = [
|
||||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.39"
|
version = "0.1.39"
|
||||||
|
@ -1036,6 +1082,11 @@ name = "typenum"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-util"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -1075,6 +1126,14 @@ name = "unicode-xid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unreachable"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
@ -1085,6 +1144,11 @@ dependencies = [
|
||||||
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-ranges"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -1108,6 +1172,11 @@ name = "version_check"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -1166,6 +1235,7 @@ dependencies = [
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
|
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
|
||||||
|
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
|
"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
|
||||||
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
|
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
|
||||||
|
@ -1222,6 +1292,7 @@ dependencies = [
|
||||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||||
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||||
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
|
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
|
||||||
|
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||||
"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd"
|
"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd"
|
||||||
"checksum mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130ea3c9c1b65dba905ab5a4d9ac59234a9585c24d135f264e187fe7336febbd"
|
"checksum mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130ea3c9c1b65dba905ab5a4d9ac59234a9585c24d135f264e187fe7336febbd"
|
||||||
"checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe"
|
"checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe"
|
||||||
|
@ -1253,6 +1324,8 @@ dependencies = [
|
||||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
||||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||||
|
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
|
||||||
|
"checksum regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2550876c31dc914696a6c2e01cbce8afba79a93c8ae979d2fe051c0230b3756"
|
||||||
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
|
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
|
||||||
"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5"
|
"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5"
|
||||||
"checksum reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "241faa9a8ca28a03cbbb9815a5d085f271d4c0168a19181f106aa93240c22ddb"
|
"checksum reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "241faa9a8ca28a03cbbb9815a5d085f271d4c0168a19181f106aa93240c22ddb"
|
||||||
|
@ -1278,6 +1351,7 @@ dependencies = [
|
||||||
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
|
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
|
||||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||||
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||||
|
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||||
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
|
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
|
||||||
"checksum tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "52b4e32d8edbf29501aabb3570f027c6ceb00ccef6538f4bddba0200503e74e8"
|
"checksum tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "52b4e32d8edbf29501aabb3570f027c6ceb00ccef6538f4bddba0200503e74e8"
|
||||||
"checksum tokio-io 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b9532748772222bf70297ec0e2ad0f17213b4a7dd0e6afb68e0a0768f69f4e4f"
|
"checksum tokio-io 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b9532748772222bf70297ec0e2ad0f17213b4a7dd0e6afb68e0a0768f69f4e4f"
|
||||||
|
@ -1285,17 +1359,21 @@ dependencies = [
|
||||||
"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
|
"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
|
||||||
"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913"
|
"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913"
|
||||||
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
|
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"
|
||||||
|
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
||||||
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
|
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
|
||||||
"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a"
|
"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a"
|
||||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||||
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
||||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
|
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||||
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
|
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
|
||||||
|
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||||
"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
|
"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
|
||||||
"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
|
"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
|
||||||
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||||
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
||||||
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
||||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
|
|
|
@ -11,6 +11,7 @@ hkdf = "0.3"
|
||||||
hyper = "0.11.9" # same as reqwest
|
hyper = "0.11.9" # same as reqwest
|
||||||
mime_guess = "2.0.0-alpha.2"
|
mime_guess = "2.0.0-alpha.2"
|
||||||
openssl = "0.10"
|
openssl = "0.10"
|
||||||
|
regex = "0.2"
|
||||||
reqwest = "0.8"
|
reqwest = "0.8"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
314
api/src/action/download.rs
Normal file
314
api/src/action/download.rs
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use mime_guess::{get_mime_type, Mime};
|
||||||
|
use openssl::symm::encrypt_aead;
|
||||||
|
use reqwest::{
|
||||||
|
Client,
|
||||||
|
Error as ReqwestError,
|
||||||
|
Request,
|
||||||
|
};
|
||||||
|
use reqwest::header::Authorization;
|
||||||
|
use reqwest::mime::APPLICATION_OCTET_STREAM;
|
||||||
|
use reqwest::multipart::{Form, Part};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crypto::key_set::KeySet;
|
||||||
|
use reader::{
|
||||||
|
EncryptedFileReaderTagged,
|
||||||
|
ExactLengthReader,
|
||||||
|
ProgressReader,
|
||||||
|
ProgressReporter,
|
||||||
|
};
|
||||||
|
use file::file::File as SendFile;
|
||||||
|
use file::metadata::{Metadata, XFileMetadata};
|
||||||
|
|
||||||
|
pub type Result<T> = ::std::result::Result<T, DownloadError>;
|
||||||
|
|
||||||
|
/// A file upload action to a Send server.
|
||||||
|
pub struct Download {
|
||||||
|
/// The Send host to upload the file to.
|
||||||
|
host: Url,
|
||||||
|
|
||||||
|
/// The file to upload.
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Download {
|
||||||
|
/// Construct a new upload action.
|
||||||
|
pub fn new(host: Url, path: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
host,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoke the upload action.
|
||||||
|
pub fn invoke(
|
||||||
|
self,
|
||||||
|
client: &Client,
|
||||||
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
|
) -> Result<SendFile> {
|
||||||
|
// Create file data, generate a key
|
||||||
|
let file = FileData::from(Box::new(&self.path))?;
|
||||||
|
let key = KeySet::generate(true);
|
||||||
|
|
||||||
|
// Crpate metadata and a file reader
|
||||||
|
let metadata = self.create_metadata(&key, &file)?;
|
||||||
|
let reader = self.create_reader(&key, reporter.clone())?;
|
||||||
|
let reader_len = reader.len().unwrap();
|
||||||
|
|
||||||
|
// Create the request to send
|
||||||
|
let req = self.create_request(
|
||||||
|
client,
|
||||||
|
&key,
|
||||||
|
metadata,
|
||||||
|
reader,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start the reporter
|
||||||
|
reporter.lock()
|
||||||
|
.expect("unable to start progress, failed to get lock")
|
||||||
|
.start(reader_len);
|
||||||
|
|
||||||
|
// Execute the request
|
||||||
|
let result = self.execute_request(req, client, &key);
|
||||||
|
|
||||||
|
// Mark the reporter as finished
|
||||||
|
reporter.lock()
|
||||||
|
.expect("unable to finish progress, failed to get lock")
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a blob of encrypted metadata.
|
||||||
|
fn create_metadata(&self, key: &KeySet, file: &FileData)
|
||||||
|
-> Result<Vec<u8>>
|
||||||
|
{
|
||||||
|
// Construct the metadata
|
||||||
|
let metadata = Metadata::from(
|
||||||
|
key.iv(),
|
||||||
|
file.name().to_owned(),
|
||||||
|
file.mime().clone(),
|
||||||
|
).to_json().into_bytes();
|
||||||
|
|
||||||
|
// Encrypt the metadata
|
||||||
|
let mut metadata_tag = vec![0u8; 16];
|
||||||
|
let mut metadata = match encrypt_aead(
|
||||||
|
KeySet::cipher(),
|
||||||
|
key.meta_key().unwrap(),
|
||||||
|
Some(&[0u8; 12]),
|
||||||
|
&[],
|
||||||
|
&metadata,
|
||||||
|
&mut metadata_tag,
|
||||||
|
) {
|
||||||
|
Ok(metadata) => metadata,
|
||||||
|
Err(_) => return Err(DownloadError::EncryptionError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Append the encryption tag
|
||||||
|
metadata.append(&mut metadata_tag);
|
||||||
|
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a reader that reads the file as encrypted stream.
|
||||||
|
fn create_reader(
|
||||||
|
&self,
|
||||||
|
key: &KeySet,
|
||||||
|
reporter: Arc<Mutex<ProgressReporter>>,
|
||||||
|
) -> Result<EncryptedReader> {
|
||||||
|
// Open the file
|
||||||
|
let file = match File::open(self.path.as_path()) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => return Err(DownloadError::FileError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an encrypted reader
|
||||||
|
let reader = match EncryptedFileReaderTagged::new(
|
||||||
|
file,
|
||||||
|
KeySet::cipher(),
|
||||||
|
key.file_key().unwrap(),
|
||||||
|
key.iv(),
|
||||||
|
) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(_) => return Err(DownloadError::EncryptionError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Buffer the encrypted reader
|
||||||
|
let reader = BufReader::new(reader);
|
||||||
|
|
||||||
|
// Wrap into the encrypted reader
|
||||||
|
let mut reader = ProgressReader::new(reader)
|
||||||
|
.expect("failed to create progress reader");
|
||||||
|
|
||||||
|
// Initialize and attach the reporter
|
||||||
|
reader.set_reporter(reporter);
|
||||||
|
|
||||||
|
Ok(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the request that will be send to the server.
|
||||||
|
fn create_request(
|
||||||
|
&self,
|
||||||
|
client: &Client,
|
||||||
|
key: &KeySet,
|
||||||
|
metadata: Vec<u8>,
|
||||||
|
reader: EncryptedReader,
|
||||||
|
) -> Request {
|
||||||
|
// Get the reader length
|
||||||
|
let len = reader.len().expect("failed to get reader length");
|
||||||
|
|
||||||
|
// Configure a form to send
|
||||||
|
let part = Part::reader_with_length(reader, len)
|
||||||
|
// .file_name(file.name())
|
||||||
|
.mime(APPLICATION_OCTET_STREAM);
|
||||||
|
let form = Form::new()
|
||||||
|
.part("data", part);
|
||||||
|
|
||||||
|
// Define the URL to call
|
||||||
|
let url = self.host.join("api/upload").expect("invalid host");
|
||||||
|
|
||||||
|
// Build the request
|
||||||
|
client.post(url.as_str())
|
||||||
|
.header(Authorization(
|
||||||
|
format!("send-v1 {}", key.auth_key_encoded().unwrap())
|
||||||
|
))
|
||||||
|
.header(XFileMetadata::from(&metadata))
|
||||||
|
.multipart(form)
|
||||||
|
.build()
|
||||||
|
.expect("failed to build an API request")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the given request, and create a file object that represents the
|
||||||
|
/// uploaded file.
|
||||||
|
fn execute_request(&self, req: Request, client: &Client, key: &KeySet)
|
||||||
|
-> Result<SendFile>
|
||||||
|
{
|
||||||
|
// Execute the request
|
||||||
|
let mut res = match client.execute(req) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => return Err(DownloadError::RequestError(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decode the response
|
||||||
|
let res: DownloadResponse = match res.json() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => return Err(DownloadError::DecodeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transform the responce into a file object
|
||||||
|
Ok(res.into_file(self.host.clone(), &key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that may occur in the upload action.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DownloadError {
|
||||||
|
/// The given file is not not an existing file.
|
||||||
|
/// Maybe it is a directory, or maybe it doesn't exist.
|
||||||
|
NotAFile,
|
||||||
|
|
||||||
|
/// An error occurred while opening or reading a file.
|
||||||
|
FileError,
|
||||||
|
|
||||||
|
/// An error occurred while encrypting the file.
|
||||||
|
EncryptionError,
|
||||||
|
|
||||||
|
/// An error occurred while while processing the request.
|
||||||
|
/// This also covers things like HTTP 404 errors.
|
||||||
|
RequestError(ReqwestError),
|
||||||
|
|
||||||
|
/// An error occurred while decoding the response data.
|
||||||
|
DecodeError,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The response from the server after a file has been uploaded.
|
||||||
|
/// This response contains the file ID and owner key, to manage the file.
|
||||||
|
///
|
||||||
|
/// It also contains the download URL, although an additional secret is
|
||||||
|
/// required.
|
||||||
|
///
|
||||||
|
/// The download URL can be generated using `download_url()` which will
|
||||||
|
/// include the required secret in the URL.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DownloadResponse {
|
||||||
|
/// The file ID.
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// The URL the file is reachable at.
|
||||||
|
/// This includes the file ID, but does not include the secret.
|
||||||
|
url: String,
|
||||||
|
|
||||||
|
/// The owner key, used to do further file modifications.
|
||||||
|
owner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DownloadResponse {
|
||||||
|
/// Convert this response into a file object.
|
||||||
|
///
|
||||||
|
/// The `host` and `key` must be given.
|
||||||
|
pub fn into_file(self, host: Url, key: &KeySet) -> SendFile {
|
||||||
|
SendFile::new_now(
|
||||||
|
self.id,
|
||||||
|
host,
|
||||||
|
Url::parse(&self.url)
|
||||||
|
.expect("upload response URL parse error"),
|
||||||
|
key.secret().to_vec(),
|
||||||
|
self.owner,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that holds various file properties, such as it's name and it's
|
||||||
|
/// mime type.
|
||||||
|
struct FileData<'a> {
|
||||||
|
/// The file name.
|
||||||
|
name: &'a str,
|
||||||
|
|
||||||
|
/// The file mime type.
|
||||||
|
mime: Mime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FileData<'a> {
|
||||||
|
/// Create a file data object, from the file at the given path.
|
||||||
|
pub fn from(path: Box<&'a Path>) -> Result<Self> {
|
||||||
|
// Make sure the given path is a file
|
||||||
|
if !path.is_file() {
|
||||||
|
return Err(DownloadError::NotAFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file name
|
||||||
|
let name = match path.file_name() {
|
||||||
|
Some(name) => name.to_str().expect("failed to convert string"),
|
||||||
|
None => return Err(DownloadError::FileError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the file extention
|
||||||
|
// TODO: handle cases where the file doesn't have an extention
|
||||||
|
let ext = match path.extension() {
|
||||||
|
Some(ext) => ext.to_str().expect("failed to convert string"),
|
||||||
|
None => return Err(DownloadError::FileError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
mime: get_mime_type(ext),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the file name.
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the file mime type.
|
||||||
|
pub fn mime(&self) -> &Mime {
|
||||||
|
&self.mime
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
|
//pub mod download;
|
||||||
pub mod upload;
|
pub mod upload;
|
||||||
|
|
|
@ -1,10 +1,25 @@
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
use url::Url;
|
use url::{
|
||||||
|
ParseError as UrlParseError,
|
||||||
|
Url,
|
||||||
|
};
|
||||||
use self::chrono::{DateTime, Utc};
|
use self::chrono::{DateTime, Utc};
|
||||||
|
use self::regex::Regex;
|
||||||
|
|
||||||
use crypto::b64;
|
use crypto::b64;
|
||||||
|
|
||||||
|
/// A pattern for Send download URL paths, capturing the file ID.
|
||||||
|
// TODO: match any sub-path?
|
||||||
|
// TODO: match URL-safe base64 chars for the file ID?
|
||||||
|
// TODO: constrain the ID length?
|
||||||
|
const DOWNLOAD_PATH_PATTERN: &'static str = r"$/?download/([[:alnum:]]+={0,3})/?^";
|
||||||
|
|
||||||
|
/// A pattern for Send download URL fragments, capturing the file secret.
|
||||||
|
// TODO: constrain the secret length?
|
||||||
|
const DOWNLOAD_FRAGMENT_PATTERN: &'static str = r"$([a-zA-Z0-9-_+\/]+)?\s*^";
|
||||||
|
|
||||||
/// A struct representing an uploaded file on a Send host.
|
/// A struct representing an uploaded file on a Send host.
|
||||||
///
|
///
|
||||||
/// The struct contains the file ID, the file URL, the key that is required
|
/// The struct contains the file ID, the file URL, the key that is required
|
||||||
|
@ -87,3 +102,104 @@ impl File {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: merge this struct with `File`.
|
||||||
|
pub struct DownloadFile {
|
||||||
|
/// The ID of the file on that server.
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// The host the file was uploaded to.
|
||||||
|
host: Url,
|
||||||
|
|
||||||
|
/// The file URL that was provided by the server.
|
||||||
|
url: Url,
|
||||||
|
|
||||||
|
/// The secret key that is required to download the file.
|
||||||
|
secret: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DownloadFile {
|
||||||
|
/// Construct a new instance.
|
||||||
|
pub fn new(
|
||||||
|
id: String,
|
||||||
|
host: Url,
|
||||||
|
url: Url,
|
||||||
|
secret: Vec<u8>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
host,
|
||||||
|
url,
|
||||||
|
secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to parse the given Send download URL.
|
||||||
|
///
|
||||||
|
/// The given URL is matched against a Send download URL pattern,
|
||||||
|
/// this does not check whether the host is a valid and online Send host.
|
||||||
|
///
|
||||||
|
/// If the URL fragmet contains a file secret, it is also parsed.
|
||||||
|
/// If it does not, the secret is left empty and must be specified manually.
|
||||||
|
pub fn parse_url(url: String) -> Result<DownloadFile, FileParseError> {
|
||||||
|
// Try to parse as an URL
|
||||||
|
let url = Url::parse(&url)
|
||||||
|
.map_err(|err| FileParseError::UrlFormatError(err))?;
|
||||||
|
|
||||||
|
// Build the host
|
||||||
|
let mut host = url.clone();
|
||||||
|
host.set_fragment(None);
|
||||||
|
host.set_query(None);
|
||||||
|
host.set_path("");
|
||||||
|
|
||||||
|
// Validate the path, get the file ID
|
||||||
|
let re_path = Regex::new(DOWNLOAD_PATH_PATTERN).unwrap();
|
||||||
|
let id = re_path.captures(url.path())
|
||||||
|
.ok_or(FileParseError::InvalidDownloadUrl)?[1]
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
// Get the file secret
|
||||||
|
let mut secret = Vec::new();
|
||||||
|
if let Some(fragment) = url.fragment() {
|
||||||
|
let re_fragment = Regex::new(DOWNLOAD_FRAGMENT_PATTERN).unwrap();
|
||||||
|
if let Some(raw) = re_fragment.captures(fragment)
|
||||||
|
.ok_or(FileParseError::InvalidSecret)?
|
||||||
|
.get(1)
|
||||||
|
{
|
||||||
|
secret = b64::decode(raw.as_str())
|
||||||
|
.map_err(|_| FileParseError::InvalidSecret)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the file
|
||||||
|
Ok(Self::new(
|
||||||
|
id,
|
||||||
|
host,
|
||||||
|
url,
|
||||||
|
secret,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a file secret is set.
|
||||||
|
/// This secret must be set to decrypt a downloaded Send file.
|
||||||
|
pub fn has_secret(&self) -> bool {
|
||||||
|
!self.secret.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the secret for this file.
|
||||||
|
/// An empty vector will clear the secret.
|
||||||
|
pub fn set_secret(&mut self, secret: Vec<u8>) {
|
||||||
|
self.secret = secret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FileParseError {
|
||||||
|
/// An URL format error.
|
||||||
|
UrlFormatError(UrlParseError),
|
||||||
|
|
||||||
|
/// An error for an invalid download URL format.
|
||||||
|
InvalidDownloadUrl,
|
||||||
|
|
||||||
|
/// An error for an invalid secret format, if an URL fragmet exists.
|
||||||
|
InvalidSecret,
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ impl ProgressReporter for ProgressBar {
|
||||||
// Initialize the progress bar
|
// Initialize the progress bar
|
||||||
let mut bar = Pbr::new(total);
|
let mut bar = Pbr::new(total);
|
||||||
bar.set_units(Units::Bytes);
|
bar.set_units(Units::Bytes);
|
||||||
|
bar.message("Upload/encrypt ");
|
||||||
|
|
||||||
self.bar = Some(bar);
|
self.bar = Some(bar);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +43,6 @@ impl ProgressReporter for ProgressBar {
|
||||||
fn finish(&mut self) {
|
fn finish(&mut self) {
|
||||||
self.bar.as_mut()
|
self.bar.as_mut()
|
||||||
.expect("progress bar not yet instantiated")
|
.expect("progress bar not yet instantiated")
|
||||||
.finish_print("File uploaded");
|
.finish_print("Upload complete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue