Merge pull request 'v0.2.0' (#2) from v0.2.0 into master
Reviewed-on: https://git.etztech.xyz/ZeroHD/Albatross/pulls/2backup_error_fix
commit
cc85d287ef
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: compliance
|
||||||
|
type: docker
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
pull: always
|
||||||
|
image: rust:1.46.0
|
||||||
|
commands:
|
||||||
|
- cargo build --verbose
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
name: release
|
||||||
|
type: docker
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
pull: always
|
||||||
|
image: rust:1.46.0
|
||||||
|
commands:
|
||||||
|
- cargo build --verbose --release
|
||||||
|
- name: gitea-release
|
||||||
|
pull: always
|
||||||
|
image: jolheiser/drone-gitea-main:latest
|
||||||
|
settings:
|
||||||
|
token:
|
||||||
|
from_secret: gitea_token
|
||||||
|
base: https://git.etztech.xyz
|
||||||
|
files:
|
||||||
|
- "target/release/albatross"
|
|
@ -1,5 +1,20 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -17,17 +32,17 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "albatross"
|
name = "albatross"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"config",
|
"config",
|
||||||
|
"discord-hooks-rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
"serde_derive",
|
"structopt",
|
||||||
"tar",
|
"tar",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -57,6 +72,20 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide 0.4.3",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -93,6 +122,12 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -128,7 +163,7 @@ dependencies = [
|
||||||
"lazy_static 1.4.0",
|
"lazy_static 1.4.0",
|
||||||
"nom",
|
"nom",
|
||||||
"rust-ini",
|
"rust-ini",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
"serde-hjson",
|
"serde-hjson",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -157,7 +192,17 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "discord-hooks-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/joeyahines/discord-hooks-rs#70307a7a5d00b8c48c0dc10402c7d8325836539a"
|
||||||
|
dependencies = [
|
||||||
|
"failure",
|
||||||
|
"serde 1.0.117",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -172,7 +217,29 @@ version = "0.8.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
|
checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "failure"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"failure_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "failure_derive"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -181,7 +248,7 @@ version = "0.2.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
|
checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
|
@ -193,10 +260,10 @@ version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
|
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide 0.3.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -293,11 +360,17 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -317,6 +390,15 @@ dependencies = [
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
@ -484,7 +566,7 @@ version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -524,13 +606,23 @@ dependencies = [
|
||||||
"adler32",
|
"adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.6.22"
|
version = "0.6.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
|
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"fuchsia-zircon",
|
"fuchsia-zircon",
|
||||||
"fuchsia-zircon-sys",
|
"fuchsia-zircon-sys",
|
||||||
"iovec",
|
"iovec",
|
||||||
|
@ -579,7 +671,7 @@ version = "0.2.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
@ -632,6 +724,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -645,7 +743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
|
checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"lazy_static 1.4.0",
|
"lazy_static 1.4.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -722,10 +820,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro-error"
|
||||||
version = "1.0.18"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check 0.9.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check 0.9.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
@ -836,7 +958,7 @@ dependencies = [
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -854,6 +976,12 @@ version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
|
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -901,9 +1029,12 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.111"
|
version = "1.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
|
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde-hjson"
|
name = "serde-hjson"
|
||||||
|
@ -920,9 +1051,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.111"
|
version = "1.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
|
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -937,7 +1068,7 @@ checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -957,7 +1088,7 @@ checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dtoa",
|
"dtoa",
|
||||||
"itoa",
|
"itoa",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -979,7 +1110,7 @@ version = "0.3.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
|
checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
|
@ -992,16 +1123,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "structopt"
|
||||||
version = "1.0.30"
|
version = "0.3.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
|
checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"lazy_static 1.4.0",
|
||||||
|
"structopt-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structopt-derive"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ad5de3220ea04da322618ded2c42233d02baca219d6f160a3e9c87cda16c942"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar"
|
name = "tar"
|
||||||
version = "0.4.28"
|
version = "0.4.28"
|
||||||
|
@ -1020,7 +1187,7 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"rand",
|
"rand",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
|
@ -1104,7 +1271,7 @@ version = "0.4.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1146,6 +1313,12 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -1215,8 +1388,8 @@ version = "0.2.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
|
checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"serde 1.0.111",
|
"serde 1.0.117",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
@ -1242,7 +1415,7 @@ version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6"
|
checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 0.1.10",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "albatross"
|
name = "albatross"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Joey Hines <joey@ahines.net>"]
|
authors = ["Joey Hines <joey@ahines.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33.0"
|
structopt = "0.3.20"
|
||||||
serde = "1.0.106"
|
serde = { version="1.0.116", features=["derive"] }
|
||||||
serde_derive = "1.0.104"
|
|
||||||
config = "0.9"
|
config = "0.9"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
@ -17,3 +16,4 @@ regex = "1.3.9"
|
||||||
flate2 = "1.0.14"
|
flate2 = "1.0.14"
|
||||||
tar = "0.4.28"
|
tar = "0.4.28"
|
||||||
reqwest = { version = "0.10", features = ["blocking", "json"] }
|
reqwest = { version = "0.10", features = ["blocking", "json"] }
|
||||||
|
discord-hooks-rs = { git = "https://github.com/joeyahines/discord-hooks-rs" }
|
||||||
|
|
24
README.md
24
README.md
|
@ -4,6 +4,30 @@ Back up what you care about in your Minecraft worlds.
|
||||||
Albatross backs up player files and region files within a certain configurable radius. It can also send Discord
|
Albatross backs up player files and region files within a certain configurable radius. It can also send Discord
|
||||||
webhooks. Backups are compressed and stored as `tar.gz` archives.
|
webhooks. Backups are compressed and stored as `tar.gz` archives.
|
||||||
|
|
||||||
|
## Help
|
||||||
|
```
|
||||||
|
albatross 0.2.0
|
||||||
|
Backup your Minecraft Server!
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
albatross --config-path <config-path> <SUBCOMMAND>
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-c, --config-path <config-path> Path to the Albatross config [env: ALBATROSS_CONFIG=]
|
||||||
|
|
||||||
|
SUBCOMMANDS:
|
||||||
|
backup Backup a server
|
||||||
|
export Export a backup as a single player world
|
||||||
|
help Prints this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Process finished with exit code 1
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
```toml
|
```toml
|
||||||
[backup]
|
[backup]
|
||||||
|
|
|
@ -0,0 +1,380 @@
|
||||||
|
use crate::backup;
|
||||||
|
use crate::config::{AlbatrossConfig, WorldConfig, WorldType};
|
||||||
|
use crate::discord::send_webhook;
|
||||||
|
use crate::region::Region;
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
|
use flate2::Compression;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fs::{
|
||||||
|
copy, create_dir, create_dir_all, remove_dir_all, remove_file, rename, DirEntry, File,
|
||||||
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Instant;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
|
/// Backup a file
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `file_name` - file name
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
pub fn backup_file(
|
||||||
|
file_name: &str,
|
||||||
|
mut world_path: PathBuf,
|
||||||
|
mut backup_path: PathBuf,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
world_path.push(file_name);
|
||||||
|
backup_path.push(file_name);
|
||||||
|
|
||||||
|
copy(world_path, backup_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup a directory
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `dir_name` - directory name
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
pub fn backup_dir(
|
||||||
|
dir_name: &str,
|
||||||
|
world_path: &PathBuf,
|
||||||
|
backup_path: &PathBuf,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
let mut src_dir = world_path.clone();
|
||||||
|
src_dir.push(dir_name);
|
||||||
|
let mut backup_dir = backup_path.clone();
|
||||||
|
backup_dir.push(dir_name);
|
||||||
|
create_dir(&backup_dir)?;
|
||||||
|
|
||||||
|
let mut file_count = 0;
|
||||||
|
for entry in src_dir.read_dir()? {
|
||||||
|
let entry = entry?;
|
||||||
|
let mut target = backup_dir.clone();
|
||||||
|
target.push(entry.file_name());
|
||||||
|
|
||||||
|
copy(entry.path(), target)?;
|
||||||
|
file_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(file_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the regions
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `dir_name` - name of the backup folder
|
||||||
|
/// * `save_radius` - block radius to save
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
pub fn backup_region(
|
||||||
|
dir_name: &str,
|
||||||
|
save_radius: u64,
|
||||||
|
world_path: &PathBuf,
|
||||||
|
backup_path: &PathBuf,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
let mut count: u64 = 0;
|
||||||
|
let mut src_dir = world_path.clone();
|
||||||
|
src_dir.push(dir_name);
|
||||||
|
let mut backup_dir = backup_path.clone();
|
||||||
|
backup_dir.push(dir_name);
|
||||||
|
create_dir(&backup_dir)?;
|
||||||
|
|
||||||
|
let save_radius = (save_radius as f64 / 512.0).ceil() as i64;
|
||||||
|
|
||||||
|
for entry in src_dir.read_dir()? {
|
||||||
|
let entry = entry?;
|
||||||
|
let file_name = entry.file_name().to_str().unwrap().to_string();
|
||||||
|
|
||||||
|
if let Ok(region) = Region::try_from(file_name) {
|
||||||
|
if region.x.abs() <= save_radius && region.y.abs() <= save_radius {
|
||||||
|
let mut target = backup_dir.clone();
|
||||||
|
target.push(entry.file_name());
|
||||||
|
|
||||||
|
copy(entry.path(), target)?;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup a world
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
/// * `world_config` - world config options
|
||||||
|
pub fn backup_world(
|
||||||
|
world_path: PathBuf,
|
||||||
|
mut backup_path: PathBuf,
|
||||||
|
world_config: &WorldConfig,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
let region_count;
|
||||||
|
backup_path.push(&world_config.world_name);
|
||||||
|
create_dir(backup_path.as_path())?;
|
||||||
|
|
||||||
|
backup_region("poi", world_config.save_radius, &world_path, &backup_path)?;
|
||||||
|
region_count = backup_region(
|
||||||
|
"region",
|
||||||
|
world_config.save_radius,
|
||||||
|
&world_path,
|
||||||
|
&backup_path,
|
||||||
|
)?;
|
||||||
|
Ok(region_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the overworld
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
/// * `world_config` - world config options
|
||||||
|
pub fn backup_overworld(
|
||||||
|
world_path: PathBuf,
|
||||||
|
backup_path: PathBuf,
|
||||||
|
world_config: &WorldConfig,
|
||||||
|
) -> Result<(u64, u64), std::io::Error> {
|
||||||
|
backup_dir("data", &world_path, &backup_path)?;
|
||||||
|
backup_dir("stats", &world_path, &backup_path)?;
|
||||||
|
|
||||||
|
backup_file("level.dat", world_path.clone(), backup_path.clone())?;
|
||||||
|
backup_file("level.dat_old", world_path.clone(), backup_path.clone())?;
|
||||||
|
backup_file("session.lock", world_path.clone(), backup_path.clone())?;
|
||||||
|
backup_file("uid.dat", world_path.clone(), backup_path.clone())?;
|
||||||
|
|
||||||
|
let player_count = backup_dir("playerdata", &world_path, &backup_path)?;
|
||||||
|
let region_count = backup_world(world_path, backup_path, world_config)?;
|
||||||
|
|
||||||
|
Ok((region_count, player_count))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the nether
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
/// * `world_config` - world config options
|
||||||
|
pub fn backup_nether(
|
||||||
|
world_path: PathBuf,
|
||||||
|
backup_path: PathBuf,
|
||||||
|
world_config: &WorldConfig,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
let mut nether_path = world_path;
|
||||||
|
nether_path.push("DIM-1");
|
||||||
|
|
||||||
|
backup_world(nether_path, backup_path, world_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the end
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `world_path` - path to the world folder
|
||||||
|
/// * `backup_path` - path to the backup folder
|
||||||
|
/// * `world_config` - world config options
|
||||||
|
pub fn backup_end(
|
||||||
|
world_path: PathBuf,
|
||||||
|
backup_path: PathBuf,
|
||||||
|
world_config: &WorldConfig,
|
||||||
|
) -> Result<u64, std::io::Error> {
|
||||||
|
let mut end_path = world_path;
|
||||||
|
end_path.push("DIM1");
|
||||||
|
|
||||||
|
backup_world(end_path, backup_path, world_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress the backup after the files have been copied
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `tmp_dir`: tmp directory with the backed up files
|
||||||
|
/// * `output_file`: output archive
|
||||||
|
pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), std::io::Error> {
|
||||||
|
let archive = File::create(output_file)?;
|
||||||
|
let enc = GzEncoder::new(archive, Compression::default());
|
||||||
|
let mut tar_builder = tar::Builder::new(enc);
|
||||||
|
tar_builder.append_dir_all(".", tmp_dir)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes an existing backup and converts it to a singleplayer world
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * config - Albatross config
|
||||||
|
/// * backup - path of the backup to convert
|
||||||
|
/// * output - output path
|
||||||
|
pub fn convert_backup_to_sp(
|
||||||
|
config: &AlbatrossConfig,
|
||||||
|
backup: &PathBuf,
|
||||||
|
output: &PathBuf,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let backup_file = File::open(backup)?;
|
||||||
|
let dec = GzDecoder::new(backup_file);
|
||||||
|
let mut extract = Archive::new(dec);
|
||||||
|
let extract_path = PathBuf::from("tmp");
|
||||||
|
extract.unpack(&extract_path)?;
|
||||||
|
|
||||||
|
if let Some(worlds) = &config.world_config {
|
||||||
|
for world in worlds {
|
||||||
|
let world_type = match world.world_type.clone() {
|
||||||
|
Some(world_type) => world_type,
|
||||||
|
None => WorldType::OVERWORLD,
|
||||||
|
};
|
||||||
|
let src = PathBuf::from(&extract_path).join(&world.world_name);
|
||||||
|
let dest = PathBuf::from(&extract_path);
|
||||||
|
match world_type {
|
||||||
|
WorldType::OVERWORLD => {
|
||||||
|
rename(src.clone().join("poi"), dest.clone().join("poi"))?;
|
||||||
|
rename(src.clone().join("region"), dest.clone().join("region"))?;
|
||||||
|
}
|
||||||
|
WorldType::NETHER => {
|
||||||
|
rename(src, dest.clone().join("DIM-1"))?;
|
||||||
|
}
|
||||||
|
WorldType::END => {
|
||||||
|
rename(src, dest.clone().join("DIM1"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compress_backup(&extract_path, output)?;
|
||||||
|
remove_dir_all(&extract_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the time of the backup from a file name
|
||||||
|
///
|
||||||
|
/// # Param
|
||||||
|
/// * `archive_entry`: archive entry
|
||||||
|
fn get_time_from_file_name(
|
||||||
|
archive_entry: &DirEntry,
|
||||||
|
) -> Result<Option<NaiveDateTime>, std::io::Error> {
|
||||||
|
let file_name = archive_entry.file_name().to_str().unwrap().to_string();
|
||||||
|
let name: Vec<&str> = file_name.split('_').collect();
|
||||||
|
|
||||||
|
Ok(chrono::NaiveDateTime::parse_from_str(name[0], "%d-%m-%y_%H.%M.%S").ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the old backups from the ouput directory
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// * `output_dir` - output directory containing
|
||||||
|
/// * `keep` - number of backups to keep
|
||||||
|
fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize, std::io::Error> {
|
||||||
|
let mut backups = vec![];
|
||||||
|
let mut num_of_removed_backups: usize = 0;
|
||||||
|
|
||||||
|
for entry in output_dir.read_dir()? {
|
||||||
|
let entry = entry?;
|
||||||
|
|
||||||
|
if let Some(ext) = entry.path().extension() {
|
||||||
|
if ext == "gz" {
|
||||||
|
backups.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if backups.len() > keep as usize {
|
||||||
|
backups.sort_by(|a, b| {
|
||||||
|
let a_time = get_time_from_file_name(a).unwrap().unwrap();
|
||||||
|
let b_time = get_time_from_file_name(b).unwrap().unwrap();
|
||||||
|
|
||||||
|
b_time.cmp(&a_time)
|
||||||
|
});
|
||||||
|
|
||||||
|
num_of_removed_backups = backups.len() - keep as usize;
|
||||||
|
|
||||||
|
for _i in 0..num_of_removed_backups {
|
||||||
|
let oldest = backups.pop().unwrap();
|
||||||
|
remove_file(oldest.path())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(num_of_removed_backups)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Backup the configured worlds from a minecraft server
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// * `cfg` - config file
|
||||||
|
pub fn do_backup(cfg: AlbatrossConfig, output: Option<PathBuf>) -> Result<(), std::io::Error> {
|
||||||
|
let server_base_dir = cfg.backup.minecraft_dir.clone();
|
||||||
|
let worlds = cfg.world_config.clone().expect("No worlds configured");
|
||||||
|
let time_str = Utc::now().format("%d-%m-%y_%H.%M.%S").to_string();
|
||||||
|
let backup_name = format!("{}_backup.tar.gz", time_str);
|
||||||
|
let mut output_archive = match output {
|
||||||
|
Some(out_path) => out_path,
|
||||||
|
None => cfg.backup.output_dir.clone(),
|
||||||
|
};
|
||||||
|
output_archive.push(backup_name);
|
||||||
|
let mut tmp_dir = cfg.backup.output_dir.clone();
|
||||||
|
tmp_dir.push("tmp");
|
||||||
|
remove_dir_all(&tmp_dir).ok();
|
||||||
|
|
||||||
|
create_dir_all(tmp_dir.clone()).unwrap();
|
||||||
|
|
||||||
|
send_webhook("**Albatross is swooping in to backup your worlds!**", &cfg);
|
||||||
|
let timer = Instant::now();
|
||||||
|
for world in worlds {
|
||||||
|
let mut world_dir = server_base_dir.clone();
|
||||||
|
let world_name = world.world_name.clone();
|
||||||
|
let world_type = match world.world_type.clone() {
|
||||||
|
Some(world_type) => world_type,
|
||||||
|
None => WorldType::OVERWORLD,
|
||||||
|
};
|
||||||
|
world_dir.push(world_name.clone());
|
||||||
|
|
||||||
|
if world_dir.exists() && world_dir.is_dir() {
|
||||||
|
send_webhook(
|
||||||
|
format!("Starting backup of **{}**", world_name).as_str(),
|
||||||
|
&cfg,
|
||||||
|
);
|
||||||
|
let webhook_msg = match world_type {
|
||||||
|
WorldType::OVERWORLD => {
|
||||||
|
let (region_count, player_count) =
|
||||||
|
backup_overworld(world_dir.clone(), tmp_dir.clone(), &world)?;
|
||||||
|
format!(
|
||||||
|
"{} regions and {} player files backed up.",
|
||||||
|
region_count, player_count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
WorldType::NETHER => {
|
||||||
|
let region_count = backup_nether(world_dir, tmp_dir.clone(), &world)?;
|
||||||
|
format!("{} regions backed up.", region_count)
|
||||||
|
}
|
||||||
|
WorldType::END => {
|
||||||
|
let region_count = backup_end(world_dir, tmp_dir.clone(), &world)?;
|
||||||
|
format!("{} regions backed up.", region_count)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
send_webhook(&webhook_msg, &cfg);
|
||||||
|
} else {
|
||||||
|
send_webhook(format!("Error: {} not found.", world_name).as_str(), &cfg);
|
||||||
|
println!("World \"{}\" not found", world_name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backup::compress_backup(&tmp_dir, &output_archive)?;
|
||||||
|
|
||||||
|
remove_dir_all(&tmp_dir)?;
|
||||||
|
|
||||||
|
let backups_removed = remove_old_backups(&cfg.backup.output_dir, cfg.backup.backups_to_keep)?;
|
||||||
|
|
||||||
|
if backups_removed > 0 {
|
||||||
|
let msg = format!(
|
||||||
|
"Albatross mistook **{}** of your old backups for some french fries and ate them!! SKRAWWWW",
|
||||||
|
backups_removed
|
||||||
|
);
|
||||||
|
send_webhook(msg.as_str(), &cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let secs = timer.elapsed().as_secs();
|
||||||
|
send_webhook(
|
||||||
|
format!("**Full backup completed in {}s**! *SKREEEEEEEEEE*", secs).as_str(),
|
||||||
|
&cfg,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use config::{Config, ConfigError, File};
|
use config::{Config, ConfigError, File};
|
||||||
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// World types supported
|
/// World types supported
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::config::AlbatrossConfig;
|
||||||
|
use discord_hooks_rs::DiscordWebhook;
|
||||||
|
|
||||||
|
/// Sends a webhook to Discord if its configured
|
||||||
|
///
|
||||||
|
/// # Params
|
||||||
|
/// * `msg` - Message to send to discord
|
||||||
|
/// * `cfg` - Albatross config
|
||||||
|
pub fn send_webhook(msg: &str, cfg: &AlbatrossConfig) {
|
||||||
|
if let Some(webhook) = &cfg.backup.discord_webhook {
|
||||||
|
let json = DiscordWebhook::new().content(msg);
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
client.post(webhook).json(&json).send().ok();
|
||||||
|
}
|
||||||
|
}
|
429
src/main.rs
429
src/main.rs
|
@ -1,391 +1,72 @@
|
||||||
extern crate serde;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
use chrono::{NaiveDateTime, Utc};
|
|
||||||
use clap::{App, Arg};
|
|
||||||
use flate2::write::GzEncoder;
|
|
||||||
use flate2::Compression;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::fs::{copy, create_dir, create_dir_all, remove_dir_all, remove_file, DirEntry, File};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
mod albatross_config;
|
mod backup;
|
||||||
|
mod config;
|
||||||
|
mod discord;
|
||||||
|
mod region;
|
||||||
|
|
||||||
use albatross_config::{AlbatrossConfig, WorldConfig, WorldType};
|
use crate::backup::{convert_backup_to_sp, do_backup};
|
||||||
use std::collections::HashMap;
|
use crate::config::AlbatrossConfig;
|
||||||
|
|
||||||
/// Struct to store information about the region
|
#[derive(Debug, StructOpt)]
|
||||||
struct Region {
|
#[structopt(about = "Backup your Minecraft Server!")]
|
||||||
/// x position of the region
|
struct Albatross {
|
||||||
x: i64,
|
/// Path to the Albatross config
|
||||||
/// y position of the region
|
#[structopt(short, long, env = "ALBATROSS_CONFIG", parse(from_os_str))]
|
||||||
y: i64,
|
config_path: PathBuf,
|
||||||
|
/// Subcommand
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
sub_command: SubCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Region {
|
#[derive(Debug, StructOpt)]
|
||||||
fn from_string(string: String) -> Option<Self> {
|
enum SubCommand {
|
||||||
let re = Regex::new(r"r\.(?P<x>-?[0-9]*)+\.(?P<y>-?[0-9]*)").unwrap();
|
/// Backup a server
|
||||||
if re.is_match(string.as_str()) {
|
Backup {
|
||||||
let captures = re.captures(string.as_str()).unwrap();
|
/// Output location override
|
||||||
|
#[structopt(short = "o", long = "ouptut", parse(from_os_str))]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
/// Export a backup as a single player world
|
||||||
|
Export {
|
||||||
|
/// Convert backup to singleplayer world
|
||||||
|
#[structopt(parse(from_os_str))]
|
||||||
|
input_backup: PathBuf,
|
||||||
|
|
||||||
return Some(Region {
|
/// Output location override
|
||||||
x: captures["x"].parse::<i64>().unwrap(),
|
#[structopt(parse(from_os_str))]
|
||||||
y: captures["y"].parse::<i64>().unwrap(),
|
output: PathBuf,
|
||||||
});
|
},
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backup a directory
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
fn backup_dir(
|
|
||||||
dir_name: &str,
|
|
||||||
world_path: &PathBuf,
|
|
||||||
backup_path: &PathBuf,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
let mut src_dir = world_path.clone();
|
|
||||||
src_dir.push(dir_name);
|
|
||||||
let mut backup_dir = backup_path.clone();
|
|
||||||
backup_dir.push(dir_name);
|
|
||||||
create_dir(&backup_dir)?;
|
|
||||||
|
|
||||||
let mut file_count = 0;
|
|
||||||
for entry in src_dir.read_dir()? {
|
|
||||||
let entry = entry?;
|
|
||||||
let mut target = backup_dir.clone();
|
|
||||||
target.push(entry.file_name());
|
|
||||||
|
|
||||||
copy(entry.path(), target)?;
|
|
||||||
file_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(file_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup the regions
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `dir_name` - name of the backup folder
|
|
||||||
/// * `save_radius` - block radius to save
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
fn backup_region(
|
|
||||||
dir_name: &str,
|
|
||||||
save_radius: u64,
|
|
||||||
world_path: &PathBuf,
|
|
||||||
backup_path: &PathBuf,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
let mut count: u64 = 0;
|
|
||||||
let mut src_dir = world_path.clone();
|
|
||||||
src_dir.push(dir_name);
|
|
||||||
let mut backup_dir = backup_path.clone();
|
|
||||||
backup_dir.push(dir_name);
|
|
||||||
create_dir(&backup_dir)?;
|
|
||||||
|
|
||||||
let save_radius = (save_radius as f64 / 512.0).ceil() as i64;
|
|
||||||
|
|
||||||
for entry in src_dir.read_dir()? {
|
|
||||||
let entry = entry?;
|
|
||||||
let file_name = entry.file_name().to_str().unwrap().to_string();
|
|
||||||
|
|
||||||
if let Some(region) = Region::from_string(file_name) {
|
|
||||||
if region.x.abs() <= save_radius && region.y.abs() <= save_radius {
|
|
||||||
let mut target = backup_dir.clone();
|
|
||||||
target.push(entry.file_name());
|
|
||||||
|
|
||||||
copy(entry.path(), target)?;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup a world
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
/// * `world_config` - world config options
|
|
||||||
fn backup_world(
|
|
||||||
world_path: PathBuf,
|
|
||||||
backup_path: PathBuf,
|
|
||||||
world_config: &WorldConfig,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
let mut backup_path = backup_path.clone();
|
|
||||||
let region_count;
|
|
||||||
backup_path.push(&world_config.world_name);
|
|
||||||
create_dir(backup_path.as_path())?;
|
|
||||||
|
|
||||||
backup_region("poi", world_config.save_radius, &world_path, &backup_path)?;
|
|
||||||
region_count = backup_region(
|
|
||||||
"region",
|
|
||||||
world_config.save_radius,
|
|
||||||
&world_path,
|
|
||||||
&backup_path,
|
|
||||||
)?;
|
|
||||||
backup_dir("data", &world_path, &backup_path)?;
|
|
||||||
|
|
||||||
Ok(region_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup the overworld
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
/// * `world_config` - world config options
|
|
||||||
fn backup_overworld(
|
|
||||||
world_path: PathBuf,
|
|
||||||
backup_path: PathBuf,
|
|
||||||
world_config: &WorldConfig,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
backup_world(world_path, backup_path, world_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup the nether
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
/// * `world_config` - world config options
|
|
||||||
fn backup_nether(
|
|
||||||
world_path: PathBuf,
|
|
||||||
backup_path: PathBuf,
|
|
||||||
world_config: &WorldConfig,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
let mut nether_path = world_path.clone();
|
|
||||||
nether_path.push("DIM-1");
|
|
||||||
|
|
||||||
backup_world(nether_path, backup_path, world_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup the end
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `world_path` - path to the world folder
|
|
||||||
/// * `backup_path` - path to the backup folder
|
|
||||||
/// * `world_config` - world config options
|
|
||||||
fn backup_end(
|
|
||||||
world_path: PathBuf,
|
|
||||||
backup_path: PathBuf,
|
|
||||||
world_config: &WorldConfig,
|
|
||||||
) -> Result<u64, std::io::Error> {
|
|
||||||
let mut end_path = world_path.clone();
|
|
||||||
end_path.push("DIM1");
|
|
||||||
|
|
||||||
backup_world(end_path, backup_path, world_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compress the backup after the files have been copied
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `tmp_dir`: tmp directory with the backed up files
|
|
||||||
/// * `output_file`: output archive
|
|
||||||
fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), std::io::Error> {
|
|
||||||
let archive = File::create(output_file)?;
|
|
||||||
let enc = GzEncoder::new(archive, Compression::default());
|
|
||||||
let mut tar_builder = tar::Builder::new(enc);
|
|
||||||
tar_builder.append_dir_all(".", tmp_dir)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the time of the backup from a file name
|
|
||||||
///
|
|
||||||
/// # Param
|
|
||||||
/// * `archive_entry`: archive entry
|
|
||||||
fn get_time_from_file_name(
|
|
||||||
archive_entry: &DirEntry,
|
|
||||||
) -> Result<Option<NaiveDateTime>, std::io::Error> {
|
|
||||||
let file_name = archive_entry.file_name().to_str().unwrap().to_string();
|
|
||||||
let name: Vec<&str> = file_name.split("_").collect();
|
|
||||||
|
|
||||||
Ok(chrono::NaiveDateTime::parse_from_str(name[0], "%d-%m-%y-%H:%M:%S").ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the old backups from the ouput directory
|
|
||||||
///
|
|
||||||
/// # Params
|
|
||||||
/// * `output_dir` - output directory containing
|
|
||||||
/// * `keep` - number of backups to keep
|
|
||||||
fn remove_old_backups(output_dir: &PathBuf, keep: u64) -> Result<usize, std::io::Error> {
|
|
||||||
let mut backups = vec![];
|
|
||||||
let mut num_of_removed_backups: usize = 0;
|
|
||||||
|
|
||||||
for entry in output_dir.read_dir()? {
|
|
||||||
let entry = entry?;
|
|
||||||
|
|
||||||
if let Some(ext) = entry.path().extension() {
|
|
||||||
if ext == "gz" {
|
|
||||||
backups.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if backups.len() > keep as usize {
|
|
||||||
backups.sort_by(|a, b| {
|
|
||||||
let a_time = get_time_from_file_name(a).unwrap().unwrap();
|
|
||||||
let b_time = get_time_from_file_name(b).unwrap().unwrap();
|
|
||||||
|
|
||||||
b_time.cmp(&a_time)
|
|
||||||
});
|
|
||||||
|
|
||||||
num_of_removed_backups = backups.len() - keep as usize;
|
|
||||||
|
|
||||||
for _i in 0..num_of_removed_backups {
|
|
||||||
let oldest = backups.pop().unwrap();
|
|
||||||
remove_file(oldest.path())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(num_of_removed_backups)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends a webhook to Discord if its configured
|
|
||||||
///
|
|
||||||
/// # Params
|
|
||||||
/// * `msg` - Message to send to discord
|
|
||||||
/// * `cfg` - Albatross config
|
|
||||||
fn send_webhook(msg: &str, cfg: &AlbatrossConfig) {
|
|
||||||
if let Some(webhook) = &cfg.backup.discord_webhook {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
|
|
||||||
map.insert("content".to_string(), msg.to_string());
|
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
|
||||||
client.post(webhook).json(&map).send().ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backup the configured worlds from a minecraft server
|
|
||||||
///
|
|
||||||
/// # Params
|
|
||||||
/// * `cfg` - config file
|
|
||||||
fn do_backup(cfg: AlbatrossConfig) -> Result<(), std::io::Error> {
|
|
||||||
let server_base_dir = cfg.backup.minecraft_dir.clone();
|
|
||||||
let worlds = cfg.world_config.clone().expect("No worlds configured");
|
|
||||||
let time_str = Utc::now().format("%d-%m-%y-%H:%M:%S").to_string();
|
|
||||||
let backup_name = format!("{}_backup.tar.gz", time_str);
|
|
||||||
let mut output_archive = cfg.backup.output_dir.clone();
|
|
||||||
output_archive.push(backup_name);
|
|
||||||
let mut tmp_dir = cfg.backup.output_dir.clone();
|
|
||||||
tmp_dir.push("tmp");
|
|
||||||
remove_dir_all(&tmp_dir).ok();
|
|
||||||
|
|
||||||
create_dir_all(tmp_dir.clone()).unwrap();
|
|
||||||
|
|
||||||
send_webhook("**Albatross is swooping in to backup your worlds!**", &cfg);
|
|
||||||
let timer = Instant::now();
|
|
||||||
for world in worlds {
|
|
||||||
let mut world_dir = server_base_dir.clone();
|
|
||||||
let world_name = world.world_name.clone();
|
|
||||||
let world_type = match world.world_type.clone() {
|
|
||||||
Some(world_type) => world_type,
|
|
||||||
None => WorldType::OVERWORLD,
|
|
||||||
};
|
|
||||||
world_dir.push(world_name.clone());
|
|
||||||
|
|
||||||
if world_dir.exists() && world_dir.is_dir() {
|
|
||||||
send_webhook(
|
|
||||||
format!("Starting backup of **{}**", world_name).as_str(),
|
|
||||||
&cfg,
|
|
||||||
);
|
|
||||||
match world_type {
|
|
||||||
WorldType::OVERWORLD => {
|
|
||||||
let region_count =
|
|
||||||
backup_overworld(world_dir.clone(), tmp_dir.clone(), &world)?;
|
|
||||||
let player_count = backup_dir("playerdata", &world_dir, &tmp_dir)?;
|
|
||||||
send_webhook(
|
|
||||||
format!(
|
|
||||||
"{} regions and {} player files backed up.",
|
|
||||||
region_count, player_count
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
&cfg,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
WorldType::NETHER => {
|
|
||||||
let region_count = backup_nether(world_dir, tmp_dir.clone(), &world)?;
|
|
||||||
send_webhook(
|
|
||||||
format!("{} regions backed up.", region_count).as_str(),
|
|
||||||
&cfg,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
WorldType::END => {
|
|
||||||
let region_count = backup_end(world_dir, tmp_dir.clone(), &world)?;
|
|
||||||
send_webhook(
|
|
||||||
format!("{} regions backed up.", region_count).as_str(),
|
|
||||||
&cfg,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
send_webhook(format!("Error: {} not found.", world_name).as_str(), &cfg);
|
|
||||||
println!("World \"{}\" not found", world_name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compress_backup(&tmp_dir, &output_archive)?;
|
|
||||||
|
|
||||||
remove_dir_all(&tmp_dir)?;
|
|
||||||
|
|
||||||
let backups_removed = remove_old_backups(&cfg.backup.output_dir, cfg.backup.backups_to_keep)?;
|
|
||||||
|
|
||||||
if backups_removed > 0 {
|
|
||||||
let msg = format!(
|
|
||||||
"Albatross mistook **{}** of your old backups for some french fries and ate them!! SKRAWWWW",
|
|
||||||
backups_removed
|
|
||||||
);
|
|
||||||
send_webhook(msg.as_str(), &cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let secs = timer.elapsed().as_secs();
|
|
||||||
send_webhook(
|
|
||||||
format!("**Full backup completed in {}s**! *SKREEEEEEEEEE*", secs).as_str(),
|
|
||||||
&cfg,
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Albatross
|
|
||||||
///
|
|
||||||
/// Run backups of a Minecraft world
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new("Albatross").about("Backup your worlds").arg(
|
let opt = Albatross::from_args();
|
||||||
Arg::with_name("config")
|
|
||||||
.index(1)
|
|
||||||
.short("c")
|
|
||||||
.long("config")
|
|
||||||
.value_name("CONFIG_PATH")
|
|
||||||
.help("Config file path"),
|
|
||||||
);
|
|
||||||
// Get arg parser
|
|
||||||
let matches = app.clone().get_matches();
|
|
||||||
|
|
||||||
if let Some(cfg_path) = matches.value_of("config") {
|
let cfg = AlbatrossConfig::new(opt.config_path.into_os_string().to_str().unwrap())
|
||||||
let cfg = AlbatrossConfig::new(cfg_path).expect("Config not found");
|
.expect("Config not found");
|
||||||
|
|
||||||
if cfg.world_config.is_some() {
|
if cfg.world_config.is_some() {
|
||||||
println!("Starting backup");
|
match opt.sub_command {
|
||||||
match do_backup(cfg) {
|
SubCommand::Backup { output } => {
|
||||||
Err(e) => println!("Error doing backup: {}", e),
|
println!("Starting backup");
|
||||||
_ => {}
|
match do_backup(cfg, output) {
|
||||||
|
Ok(_) => println!("Backup complete!"),
|
||||||
|
Err(e) => println!("Error doing backup: {:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
SubCommand::Export {
|
||||||
|
input_backup,
|
||||||
|
output,
|
||||||
|
} => {
|
||||||
|
println!("Starting export");
|
||||||
|
match convert_backup_to_sp(&cfg, &input_backup, &output) {
|
||||||
|
Ok(_) => println!("Export complete!"),
|
||||||
|
Err(e) => println!("Error exporting backup: {:?}", e),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Backup complete");
|
|
||||||
} else {
|
|
||||||
println!("No worlds specified to backed up!")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
app.print_help().expect("Unable to print help");
|
println!("No worlds specified in config file!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
use regex::Regex;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RegionParseError;
|
||||||
|
|
||||||
|
impl fmt::Display for RegionParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Unable to parse region file name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for RegionParseError {}
|
||||||
|
|
||||||
|
/// Struct to store information about the region
|
||||||
|
pub struct Region {
|
||||||
|
/// x position of the region
|
||||||
|
pub x: i64,
|
||||||
|
/// y position of the region
|
||||||
|
pub y: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Region {
|
||||||
|
type Error = RegionParseError;
|
||||||
|
|
||||||
|
/// Try from string
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
let re = Regex::new(r"r\.(?P<x>-?[0-9]*)+\.(?P<y>-?[0-9]*)").unwrap();
|
||||||
|
if re.is_match(&value) {
|
||||||
|
let captures = re.captures(value.as_str()).unwrap();
|
||||||
|
|
||||||
|
return Ok(Region {
|
||||||
|
x: captures["x"].parse::<i64>().unwrap(),
|
||||||
|
y: captures["y"].parse::<i64>().unwrap(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(RegionParseError)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue