Add option to turn on logging to IndexedDB + delete logs + download
This commit is contained in:
		
							parent
							
								
									a8c279e13e
								
							
						
					
					
						commit
						7fddf30657
					
				
							
								
								
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										142
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2,6 +2,18 @@
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "accessory"
 | 
			
		||||
version = "1.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "macroific",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "addr2line"
 | 
			
		||||
version = "0.22.0"
 | 
			
		||||
@ -148,6 +160,18 @@ dependencies = [
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "delegate-display"
 | 
			
		||||
version = "2.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "macroific",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "either"
 | 
			
		||||
version = "1.13.0"
 | 
			
		||||
@ -160,6 +184,18 @@ version = "1.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fancy_constructor"
 | 
			
		||||
version = "1.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "macroific",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fnv"
 | 
			
		||||
version = "1.0.7"
 | 
			
		||||
@ -701,6 +737,23 @@ dependencies = [
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexed_db_futures"
 | 
			
		||||
version = "0.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "accessory",
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "delegate-display",
 | 
			
		||||
 "fancy_constructor",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "uuid",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
 "wasm-bindgen-futures",
 | 
			
		||||
 "web-sys",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "2.3.0"
 | 
			
		||||
@ -728,9 +781,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "js-sys"
 | 
			
		||||
version = "0.3.69"
 | 
			
		||||
version = "0.3.70"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
 | 
			
		||||
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
@ -747,6 +800,53 @@ version = "0.4.22"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "macroific"
 | 
			
		||||
version = "1.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "macroific_attr_parse",
 | 
			
		||||
 "macroific_core",
 | 
			
		||||
 "macroific_macro",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "macroific_attr_parse"
 | 
			
		||||
version = "1.3.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "macroific_core"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "macroific_macro"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "macroific_attr_parse",
 | 
			
		||||
 "macroific_core",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.73",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memchr"
 | 
			
		||||
version = "2.7.4"
 | 
			
		||||
@ -1299,6 +1399,16 @@ version = "0.2.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "uuid"
 | 
			
		||||
version = "1.10.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "getrandom",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "version_check"
 | 
			
		||||
version = "0.9.5"
 | 
			
		||||
@ -1333,19 +1443,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen"
 | 
			
		||||
version = "0.2.92"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
 | 
			
		||||
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "wasm-bindgen-macro",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-backend"
 | 
			
		||||
version = "0.2.92"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
 | 
			
		||||
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bumpalo",
 | 
			
		||||
 "log",
 | 
			
		||||
@ -1358,9 +1469,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-futures"
 | 
			
		||||
version = "0.4.42"
 | 
			
		||||
version = "0.4.43"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
 | 
			
		||||
checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
@ -1370,9 +1481,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-macro"
 | 
			
		||||
version = "0.2.92"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 | 
			
		||||
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
 "wasm-bindgen-macro-support",
 | 
			
		||||
@ -1380,9 +1491,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-macro-support"
 | 
			
		||||
version = "0.2.92"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 | 
			
		||||
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -1393,9 +1504,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "wasm-bindgen-shared"
 | 
			
		||||
version = "0.2.92"
 | 
			
		||||
version = "0.2.93"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
 | 
			
		||||
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "web-sys"
 | 
			
		||||
@ -1424,18 +1535,21 @@ dependencies = [
 | 
			
		||||
 "console_error_panic_hook",
 | 
			
		||||
 "gc-arena",
 | 
			
		||||
 "im",
 | 
			
		||||
 "indexed_db_futures",
 | 
			
		||||
 "itertools",
 | 
			
		||||
 "minicrossterm",
 | 
			
		||||
 "nom",
 | 
			
		||||
 "piccolo",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde-wasm-bindgen 0.6.5",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "strip-ansi-escapes",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "unicode-segmentation",
 | 
			
		||||
 "unicode-width",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
 "wasm-bindgen-futures",
 | 
			
		||||
 "web-sys",
 | 
			
		||||
 "yew",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc92429217
 | 
			
		||||
unicode-segmentation = "1.11.0"
 | 
			
		||||
unicode-width = "0.1.13"
 | 
			
		||||
wasm-bindgen = "0.2.92"
 | 
			
		||||
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration"] }
 | 
			
		||||
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement"] }
 | 
			
		||||
yew = { version = "0.21.0", features = ["csr"] }
 | 
			
		||||
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "0c8c6d4f0cf445adf7bb957811081a1b710bd933" }
 | 
			
		||||
thiserror = "1.0.63"
 | 
			
		||||
@ -24,3 +24,6 @@ serde_json = "1.0.127"
 | 
			
		||||
gc-arena = { git = "https://github.com/kyren/gc-arena.git", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9" }
 | 
			
		||||
regex = "1.10.6"
 | 
			
		||||
strip-ansi-escapes = "0.2.0"
 | 
			
		||||
indexed_db_futures = "0.5.0"
 | 
			
		||||
wasm-bindgen-futures = "0.4.43"
 | 
			
		||||
serde-wasm-bindgen = "0.6.5"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										544
									
								
								src/logging.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								src/logging.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,544 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::BTreeMap,
 | 
			
		||||
    mem::replace,
 | 
			
		||||
    ops::{Deref, DerefMut},
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use indexed_db_futures::{
 | 
			
		||||
    idb_object_store::IdbObjectStoreParameters,
 | 
			
		||||
    request::{IdbOpenDbRequestLike, OpenDbRequest},
 | 
			
		||||
    IdbDatabase, IdbKeyPath, IdbQuerySource, IdbVersionChangeEvent,
 | 
			
		||||
};
 | 
			
		||||
use itertools::Itertools;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use wasm_bindgen::{JsCast, JsValue};
 | 
			
		||||
use wasm_bindgen_futures::{
 | 
			
		||||
    js_sys::{Array, Uint8Array},
 | 
			
		||||
    spawn_local,
 | 
			
		||||
};
 | 
			
		||||
use web_sys::{
 | 
			
		||||
    console, js_sys::Date, Blob, BlobPropertyBag, DomException, HtmlAnchorElement, IdbKeyRange,
 | 
			
		||||
    IdbTransactionMode, Url,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{echo_to_term_frame, GlobalMemoCell, TermFrame};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub enum QueuedAction {
 | 
			
		||||
    LogEvent(LogEvent),
 | 
			
		||||
    ReportOnLogStreams(TermFrame),
 | 
			
		||||
    DeleteLogs(DeleteLogs),
 | 
			
		||||
    DownloadLogs(DownloadLogs),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct DeleteLogs {
 | 
			
		||||
    pub reply_to: TermFrame,
 | 
			
		||||
    pub stream: String,
 | 
			
		||||
    pub min_date: String,
 | 
			
		||||
    pub max_date: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct DownloadLogs {
 | 
			
		||||
    pub reply_to: TermFrame,
 | 
			
		||||
    pub stream: String,
 | 
			
		||||
    pub min_date: String,
 | 
			
		||||
    pub max_date: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
pub struct LogEvent {
 | 
			
		||||
    pub log_stream: String,
 | 
			
		||||
    pub timestamp: Date,
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct StoredLogEvent {
 | 
			
		||||
    pub stream: String,
 | 
			
		||||
    pub timestamp: String,
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&LogEvent> for StoredLogEvent {
 | 
			
		||||
    fn from(value: &LogEvent) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            stream: value.log_stream.clone(),
 | 
			
		||||
            timestamp: value
 | 
			
		||||
                .timestamp
 | 
			
		||||
                .to_iso_string()
 | 
			
		||||
                .as_string()
 | 
			
		||||
                .expect("timestamp to_iso_string wasn't string"),
 | 
			
		||||
            message: value.message.clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub enum LoggingEngine {
 | 
			
		||||
    Uninitialised,
 | 
			
		||||
    Initialising { backlog: Vec<QueuedAction> },
 | 
			
		||||
    Unavailable,
 | 
			
		||||
    Ready { db: Arc<IdbDatabase> },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for LoggingEngine {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Uninitialised
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn async_init_logging() -> Result<LoggingEngine, DomException> {
 | 
			
		||||
    let mut db_req: OpenDbRequest = IdbDatabase::open_u32("logging", 1)?;
 | 
			
		||||
    db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
 | 
			
		||||
        if evt
 | 
			
		||||
            .db()
 | 
			
		||||
            .object_store_names()
 | 
			
		||||
            .find(|n| n == "logs")
 | 
			
		||||
            .is_none()
 | 
			
		||||
        {
 | 
			
		||||
            let store = evt.db().create_object_store_with_params(
 | 
			
		||||
                "logs",
 | 
			
		||||
                <IdbObjectStoreParameters as Default>::default().auto_increment(true),
 | 
			
		||||
            )?;
 | 
			
		||||
            store.create_index(
 | 
			
		||||
                "by_stream_timestamp",
 | 
			
		||||
                &IdbKeyPath::str_sequence(&["stream", "timestamp"]),
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }));
 | 
			
		||||
    let db: IdbDatabase = db_req.await?;
 | 
			
		||||
 | 
			
		||||
    Ok(LoggingEngine::Ready { db: db.into() })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn logging_broken_message(global: &GlobalMemoCell, frame: &TermFrame) {
 | 
			
		||||
    let _ = echo_to_term_frame(global, frame, "Couldn't enable logging. This sometimes happens if your browser doesn't enable indexed storage, or is in a mode (e.g. private browsing) where such storage is not permitted.\r\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn init_logging(global: &GlobalMemoCell) {
 | 
			
		||||
    let global: GlobalMemoCell = global.clone();
 | 
			
		||||
    spawn_local(async move {
 | 
			
		||||
        match async_init_logging().await {
 | 
			
		||||
            Ok(new_engine) => {
 | 
			
		||||
                let old_engine = replace(global.log_engine.borrow_mut().deref_mut(), new_engine);
 | 
			
		||||
                if let LoggingEngine::Initialising { backlog } = old_engine {
 | 
			
		||||
                    for action in backlog.into_iter() {
 | 
			
		||||
                        match action {
 | 
			
		||||
                            QueuedAction::LogEvent(ev) => queue_immediate_log(&global, ev),
 | 
			
		||||
                            QueuedAction::ReportOnLogStreams(frame) => {
 | 
			
		||||
                                immediate_report_log_frame(&global, frame);
 | 
			
		||||
                            }
 | 
			
		||||
                            QueuedAction::DeleteLogs(act) => {
 | 
			
		||||
                                immediate_delete_logs(&global, act);
 | 
			
		||||
                            }
 | 
			
		||||
                            QueuedAction::DownloadLogs(act) => {
 | 
			
		||||
                                immediate_download_logs(&global, act);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                if let LoggingEngine::Initialising { .. } = global.log_engine.borrow().deref() {
 | 
			
		||||
                    logging_broken_message(&global, &TermFrame(1));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn queue_immediate_log_refutable(db: &IdbDatabase, event: &LogEvent) -> Result<(), String> {
 | 
			
		||||
    let trans = db
 | 
			
		||||
        .transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)
 | 
			
		||||
        .map_err(|e| e.message())?;
 | 
			
		||||
    let store = trans.object_store("logs").map_err(|e| e.message())?;
 | 
			
		||||
    store
 | 
			
		||||
        .put_val(
 | 
			
		||||
            &serde_wasm_bindgen::to_value::<StoredLogEvent>(&event.into())
 | 
			
		||||
                .map_err(|_| "Can't serialise event")?,
 | 
			
		||||
        )
 | 
			
		||||
        .map_err(|e| e.message())?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn queue_immediate_log(global: &GlobalMemoCell, event: LogEvent) {
 | 
			
		||||
    if let LoggingEngine::Ready { db } = global.log_engine.borrow().deref() {
 | 
			
		||||
        match queue_immediate_log_refutable(db, &event) {
 | 
			
		||||
            Ok(()) => {}
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                console::log_2(
 | 
			
		||||
                    &JsValue::from_str("Error writing event to IndexedDb"),
 | 
			
		||||
                    &JsValue::from(e),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_report_log_frame_refutable(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: &IdbDatabase,
 | 
			
		||||
    frame: TermFrame,
 | 
			
		||||
) -> Result<(), DomException> {
 | 
			
		||||
    let trans = db.transaction_on_one("logs")?;
 | 
			
		||||
    let store = trans.object_store("logs")?;
 | 
			
		||||
    let curs = store.open_cursor()?.await?;
 | 
			
		||||
    let curs = match curs {
 | 
			
		||||
        None => {
 | 
			
		||||
            let _ = echo_to_term_frame(&global, &frame, "No logs are currently stored.\r\n");
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
        Some(curs) => curs,
 | 
			
		||||
    };
 | 
			
		||||
    let mut event_stats = BTreeMap::<String, u64>::new();
 | 
			
		||||
    loop {
 | 
			
		||||
        if let Ok(ev) = serde_wasm_bindgen::from_value::<StoredLogEvent>(curs.value()) {
 | 
			
		||||
            event_stats
 | 
			
		||||
                .entry(ev.stream)
 | 
			
		||||
                .and_modify(|v| *v += 1)
 | 
			
		||||
                .or_insert(1);
 | 
			
		||||
        };
 | 
			
		||||
        if !curs.continue_cursor()?.await? {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let msg = event_stats
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|(k, v)| format!("Log stream {} has {} log records.\r\n", k, v))
 | 
			
		||||
        .join("");
 | 
			
		||||
    let _ = echo_to_term_frame(&global, &frame, &msg);
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_report_log_frame_async(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: Arc<IdbDatabase>,
 | 
			
		||||
    frame: TermFrame,
 | 
			
		||||
) {
 | 
			
		||||
    match immediate_report_log_frame_refutable(global, &db, frame).await {
 | 
			
		||||
        Ok(()) => {}
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            console::log_2(
 | 
			
		||||
                &JsValue::from_str("Error reading log stats from IndexedDb"),
 | 
			
		||||
                &JsValue::from(e),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn immediate_report_log_frame(global: &GlobalMemoCell, frame: TermFrame) {
 | 
			
		||||
    let log_engine = global.log_engine.borrow();
 | 
			
		||||
    if let LoggingEngine::Ready { db } = log_engine.deref() {
 | 
			
		||||
        spawn_local(immediate_report_log_frame_async(
 | 
			
		||||
            global.clone(),
 | 
			
		||||
            db.clone(),
 | 
			
		||||
            frame,
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_delete_logs_refutable(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: &IdbDatabase,
 | 
			
		||||
    act: DeleteLogs,
 | 
			
		||||
) -> Result<(), DomException> {
 | 
			
		||||
    let trans = db.transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)?;
 | 
			
		||||
    let store = trans.object_store("logs")?;
 | 
			
		||||
    let idx = store.index("by_stream_timestamp")?;
 | 
			
		||||
    let range = IdbKeyRange::bound(
 | 
			
		||||
        &JsValue::from(
 | 
			
		||||
            [
 | 
			
		||||
                JsValue::from_str(&act.stream),
 | 
			
		||||
                JsValue::from_str(&act.min_date),
 | 
			
		||||
            ]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .collect::<Array>(),
 | 
			
		||||
        ),
 | 
			
		||||
        &JsValue::from(
 | 
			
		||||
            [
 | 
			
		||||
                JsValue::from_str(&act.stream),
 | 
			
		||||
                JsValue::from_str(&act.max_date),
 | 
			
		||||
            ]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .collect::<Array>(),
 | 
			
		||||
        ),
 | 
			
		||||
    )?;
 | 
			
		||||
    let cur = match idx.open_key_cursor_with_range(&range)?.await? {
 | 
			
		||||
        None => {
 | 
			
		||||
            let _ = echo_to_term_frame(
 | 
			
		||||
                &global,
 | 
			
		||||
                &act.reply_to,
 | 
			
		||||
                "No logs matched; no logs deleted.\r\n",
 | 
			
		||||
            );
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
        Some(cur) => cur,
 | 
			
		||||
    };
 | 
			
		||||
    let mut records: u64 = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        if let Some(pk) = cur.primary_key() {
 | 
			
		||||
            store.delete(&pk)?;
 | 
			
		||||
            records += 1;
 | 
			
		||||
        }
 | 
			
		||||
        if !cur.continue_cursor()?.await? {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let _ = echo_to_term_frame(
 | 
			
		||||
        &global,
 | 
			
		||||
        &act.reply_to,
 | 
			
		||||
        &format!("{} logs matched and were deleted.\r\n", records),
 | 
			
		||||
    );
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_delete_logs_async(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: Arc<IdbDatabase>,
 | 
			
		||||
    act: DeleteLogs,
 | 
			
		||||
) {
 | 
			
		||||
    match immediate_delete_logs_refutable(global, &db, act).await {
 | 
			
		||||
        Ok(()) => {}
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            console::log_2(
 | 
			
		||||
                &JsValue::from_str("Error deleting logs from IndexedDb"),
 | 
			
		||||
                &JsValue::from(e),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn immediate_delete_logs(global: &GlobalMemoCell, act: DeleteLogs) {
 | 
			
		||||
    let log_engine = global.log_engine.borrow();
 | 
			
		||||
    if let LoggingEngine::Ready { db } = log_engine.deref() {
 | 
			
		||||
        spawn_local(immediate_delete_logs_async(
 | 
			
		||||
            global.clone(),
 | 
			
		||||
            db.clone(),
 | 
			
		||||
            act.clone(),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_download_logs_refutable(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: &IdbDatabase,
 | 
			
		||||
    act: DownloadLogs,
 | 
			
		||||
) -> Result<(), DomException> {
 | 
			
		||||
    let trans = db.transaction_on_one_with_mode("logs", IdbTransactionMode::Readwrite)?;
 | 
			
		||||
    let store = trans.object_store("logs")?;
 | 
			
		||||
    let idx = store.index("by_stream_timestamp")?;
 | 
			
		||||
    let range = IdbKeyRange::bound(
 | 
			
		||||
        &JsValue::from(
 | 
			
		||||
            [
 | 
			
		||||
                JsValue::from_str(&act.stream),
 | 
			
		||||
                JsValue::from_str(&act.min_date),
 | 
			
		||||
            ]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .collect::<Array>(),
 | 
			
		||||
        ),
 | 
			
		||||
        &JsValue::from(
 | 
			
		||||
            [
 | 
			
		||||
                JsValue::from_str(&act.stream),
 | 
			
		||||
                JsValue::from_str(&act.max_date),
 | 
			
		||||
            ]
 | 
			
		||||
            .iter()
 | 
			
		||||
            .collect::<Array>(),
 | 
			
		||||
        ),
 | 
			
		||||
    )?;
 | 
			
		||||
    let cur = match idx.open_key_cursor_with_range(&range)?.await? {
 | 
			
		||||
        None => {
 | 
			
		||||
            let _ = echo_to_term_frame(
 | 
			
		||||
                &global,
 | 
			
		||||
                &act.reply_to,
 | 
			
		||||
                "No logs matched; no logs downloaded.\r\n",
 | 
			
		||||
            );
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
        Some(cur) => cur,
 | 
			
		||||
    };
 | 
			
		||||
    let mut buf: String = String::new();
 | 
			
		||||
    loop {
 | 
			
		||||
        if let Some(k) = cur.primary_key() {
 | 
			
		||||
            if let Some(Ok(rec)) = store
 | 
			
		||||
                .get(&k)?
 | 
			
		||||
                .await?
 | 
			
		||||
                .map(serde_wasm_bindgen::from_value::<StoredLogEvent>)
 | 
			
		||||
            {
 | 
			
		||||
                buf.push_str(&rec.message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if !cur.continue_cursor()?.await? {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let buf_bytes = buf.as_bytes();
 | 
			
		||||
    let buf_array = Uint8Array::new_with_length(buf_bytes.len() as u32);
 | 
			
		||||
    buf_array.copy_from(buf_bytes);
 | 
			
		||||
    let mut blobprops: BlobPropertyBag = Default::default();
 | 
			
		||||
    blobprops.type_("text/plain");
 | 
			
		||||
    let blob = Blob::new_with_u8_array_sequence_and_options(
 | 
			
		||||
        &[&buf_array].into_iter().collect::<Array>(),
 | 
			
		||||
        &blobprops,
 | 
			
		||||
    )?;
 | 
			
		||||
    console::log_2(&buf_array, &blob);
 | 
			
		||||
    let url = Url::create_object_url_with_blob(&blob)?;
 | 
			
		||||
    if let Some((doc, body)) = web_sys::window()
 | 
			
		||||
        .and_then(|w| w.document())
 | 
			
		||||
        .and_then(|d| d.body().map(|b| (d, b)))
 | 
			
		||||
    {
 | 
			
		||||
        let el_anchor = JsCast::unchecked_into::<HtmlAnchorElement>(doc.create_element("a")?);
 | 
			
		||||
        body.append_child(&el_anchor)?;
 | 
			
		||||
        el_anchor.set_href(&url);
 | 
			
		||||
        el_anchor.set_download(&format!("{}-logs.txt", act.stream));
 | 
			
		||||
        el_anchor.click();
 | 
			
		||||
        body.remove_child(&el_anchor)?;
 | 
			
		||||
    }
 | 
			
		||||
    // Url::revoke_object_url(&url)?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn immediate_download_logs_async(
 | 
			
		||||
    global: GlobalMemoCell,
 | 
			
		||||
    db: Arc<IdbDatabase>,
 | 
			
		||||
    act: DownloadLogs,
 | 
			
		||||
) {
 | 
			
		||||
    match immediate_download_logs_refutable(global, &db, act).await {
 | 
			
		||||
        Ok(()) => {}
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            console::log_2(
 | 
			
		||||
                &JsValue::from_str("Error downloading logs from IndexedDb"),
 | 
			
		||||
                &JsValue::from(e),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn immediate_download_logs(global: &GlobalMemoCell, act: DownloadLogs) {
 | 
			
		||||
    let log_engine = global.log_engine.borrow();
 | 
			
		||||
    if let LoggingEngine::Ready { db } = log_engine.deref() {
 | 
			
		||||
        spawn_local(immediate_download_logs_async(
 | 
			
		||||
            global.clone(),
 | 
			
		||||
            db.clone(),
 | 
			
		||||
            act.clone(),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn log(global: &GlobalMemoCell, event: &LogEvent) {
 | 
			
		||||
    let mut engine_borrow = global.log_engine.borrow_mut();
 | 
			
		||||
    match engine_borrow.deref_mut() {
 | 
			
		||||
        LoggingEngine::Uninitialised => {
 | 
			
		||||
            *engine_borrow = LoggingEngine::Initialising {
 | 
			
		||||
                backlog: vec![QueuedAction::LogEvent(event.clone())],
 | 
			
		||||
            };
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            init_logging(global);
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Initialising { ref mut backlog } => {
 | 
			
		||||
            backlog.push(QueuedAction::LogEvent(event.clone()));
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Unavailable => {
 | 
			
		||||
            // Assume the user has already been informed it failed, so do nothing.
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Ready { .. } => {
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            queue_immediate_log(global, event.clone());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn start_listing_logs(global: &GlobalMemoCell, send_to: &TermFrame) {
 | 
			
		||||
    let mut engine_borrow = global.log_engine.borrow_mut();
 | 
			
		||||
    match engine_borrow.deref_mut() {
 | 
			
		||||
        LoggingEngine::Uninitialised => {
 | 
			
		||||
            *engine_borrow = LoggingEngine::Initialising {
 | 
			
		||||
                backlog: vec![QueuedAction::ReportOnLogStreams(send_to.clone())],
 | 
			
		||||
            };
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            init_logging(global);
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Initialising { ref mut backlog } => {
 | 
			
		||||
            backlog.push(QueuedAction::ReportOnLogStreams(send_to.clone()));
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Unavailable => {
 | 
			
		||||
            // Assume the user has already been informed it failed, so do nothing.
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Ready { .. } => {
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            immediate_report_log_frame(global, send_to.clone());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn start_deleting_logs(
 | 
			
		||||
    global: &GlobalMemoCell,
 | 
			
		||||
    reply_to: &TermFrame,
 | 
			
		||||
    stream: &str,
 | 
			
		||||
    min_date: &str,
 | 
			
		||||
    max_date: &str,
 | 
			
		||||
) {
 | 
			
		||||
    let mut engine_borrow = global.log_engine.borrow_mut();
 | 
			
		||||
    let act = DeleteLogs {
 | 
			
		||||
        reply_to: reply_to.clone(),
 | 
			
		||||
        stream: stream.to_owned(),
 | 
			
		||||
        min_date: min_date.to_owned(),
 | 
			
		||||
        max_date: max_date.to_owned(),
 | 
			
		||||
    };
 | 
			
		||||
    match engine_borrow.deref_mut() {
 | 
			
		||||
        LoggingEngine::Uninitialised => {
 | 
			
		||||
            *engine_borrow = LoggingEngine::Initialising {
 | 
			
		||||
                backlog: vec![QueuedAction::DeleteLogs(act)],
 | 
			
		||||
            };
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            init_logging(global);
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Initialising { ref mut backlog } => {
 | 
			
		||||
            backlog.push(QueuedAction::DeleteLogs(act));
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Unavailable => {
 | 
			
		||||
            // Assume the user has already been informed it failed, so do nothing.
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Ready { .. } => {
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            immediate_delete_logs(global, act);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn start_downloading_logs(
 | 
			
		||||
    global: &GlobalMemoCell,
 | 
			
		||||
    reply_to: &TermFrame,
 | 
			
		||||
    stream: &str,
 | 
			
		||||
    min_date: &str,
 | 
			
		||||
    max_date: &str,
 | 
			
		||||
) {
 | 
			
		||||
    let mut engine_borrow = global.log_engine.borrow_mut();
 | 
			
		||||
    let act = DownloadLogs {
 | 
			
		||||
        reply_to: reply_to.clone(),
 | 
			
		||||
        stream: stream.to_owned(),
 | 
			
		||||
        min_date: min_date.to_owned(),
 | 
			
		||||
        max_date: max_date.to_owned(),
 | 
			
		||||
    };
 | 
			
		||||
    match engine_borrow.deref_mut() {
 | 
			
		||||
        LoggingEngine::Uninitialised => {
 | 
			
		||||
            *engine_borrow = LoggingEngine::Initialising {
 | 
			
		||||
                backlog: vec![QueuedAction::DownloadLogs(act)],
 | 
			
		||||
            };
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            init_logging(global);
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Initialising { ref mut backlog } => {
 | 
			
		||||
            backlog.push(QueuedAction::DownloadLogs(act));
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Unavailable => {
 | 
			
		||||
            // Assume the user has already been informed it failed, so do nothing.
 | 
			
		||||
        }
 | 
			
		||||
        LoggingEngine::Ready { .. } => {
 | 
			
		||||
            drop(engine_borrow);
 | 
			
		||||
            immediate_download_logs(global, act);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -147,6 +147,15 @@ pub fn install_lua_globals(
 | 
			
		||||
                        )
 | 
			
		||||
                        .map_err(|_| Error::msg("Can't add command"))?;
 | 
			
		||||
                };
 | 
			
		||||
                ($sym: ident, $name: literal) => {
 | 
			
		||||
                    cmd_table
 | 
			
		||||
                        .set(
 | 
			
		||||
                            ctx,
 | 
			
		||||
                            ctx.intern_static($name.as_bytes()),
 | 
			
		||||
                            $sym(ctx, &global_memo, &global_layout),
 | 
			
		||||
                        )
 | 
			
		||||
                        .map_err(|_| Error::msg("Can't add command"))?;
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            macro_rules! register_stateless_command {
 | 
			
		||||
                ($sym: ident) => {
 | 
			
		||||
@ -171,11 +180,15 @@ pub fn install_lua_globals(
 | 
			
		||||
            register_command!(close_mud);
 | 
			
		||||
            register_command!(connect_mud);
 | 
			
		||||
            register_stateless_command!(create_match_table);
 | 
			
		||||
            register_command!(cmd_delete_logs, "deletelogs");
 | 
			
		||||
            register_command!(delete_mud);
 | 
			
		||||
            register_command!(cmd_download_logs, "downloadlogs");
 | 
			
		||||
            register_command!(echo);
 | 
			
		||||
            register_command!(echo_frame);
 | 
			
		||||
            register_command!(echo_frame_raw);
 | 
			
		||||
            register_command!(hsplit);
 | 
			
		||||
            register_command!(cmd_list_logs, "listlogs");
 | 
			
		||||
            register_command!(mud_log, "log");
 | 
			
		||||
            register_command!(panel_merge);
 | 
			
		||||
            register_command!(sendmud_raw);
 | 
			
		||||
            register_stateless_command!(mud_trigger, "untrigger");
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ use yew::UseStateSetter;
 | 
			
		||||
use crate::{
 | 
			
		||||
    command_handler::execute_queue,
 | 
			
		||||
    id_intern::{intern_id, unintern_id},
 | 
			
		||||
    logging::{start_deleting_logs, start_downloading_logs, start_listing_logs},
 | 
			
		||||
    match_table::{create_match_table, match_table_add, match_table_remove},
 | 
			
		||||
    telnet::{parse_telnet_buf, TelnetOutput},
 | 
			
		||||
    websocket::{connect_websocket, send_message_to_mud, WebSocketId},
 | 
			
		||||
@ -148,7 +149,7 @@ pub(super) fn connect_mud<'gc>(
 | 
			
		||||
            }
 | 
			
		||||
            break v;
 | 
			
		||||
        };
 | 
			
		||||
        let name: Value<'gc> = ctx.intern(&name.as_bytes()).into();
 | 
			
		||||
        let name: Value<'gc> = ctx.intern(name.as_bytes()).into();
 | 
			
		||||
 | 
			
		||||
        let muds: Table = ctx.get_global("muds")?;
 | 
			
		||||
        if !muds.get_value(ctx, name).is_nil() {
 | 
			
		||||
@ -392,10 +393,6 @@ pub(super) fn mudoutput_line<'gc>(
 | 
			
		||||
            .unwrap_or_else(|| Ok(ctx.get_global::<Table>("frames")?.get(ctx, 1_i64)?))?
 | 
			
		||||
            .get(ctx, "frame")?;
 | 
			
		||||
 | 
			
		||||
        console::log_1(&JsValue::from_str(&format!(
 | 
			
		||||
            "Line to match: {:?}",
 | 
			
		||||
            line.as_bytes()
 | 
			
		||||
        )));
 | 
			
		||||
        let seq = async_sequence(&ctx, |locals, mut seq| {
 | 
			
		||||
            let frameroutes: Vec<StashedTable> = frameroutes
 | 
			
		||||
                .iter()
 | 
			
		||||
@ -635,3 +632,166 @@ pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> {
 | 
			
		||||
        Ok(piccolo::CallbackReturn::Sequence(seq))
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) fn mud_log<'gc>(
 | 
			
		||||
    ctx: Context<'gc>,
 | 
			
		||||
    global_memo: &GlobalMemoCell,
 | 
			
		||||
    _global_layout: &UseStateSetter<GlobalLayoutCell>,
 | 
			
		||||
) -> Callback<'gc> {
 | 
			
		||||
    let global_memo = global_memo.clone();
 | 
			
		||||
    Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
 | 
			
		||||
        let name: String = stack.from_front(ctx)?;
 | 
			
		||||
 | 
			
		||||
        let muds: Table = ctx.get_global("muds")?;
 | 
			
		||||
        let mud_value: Value = muds.get(ctx, name)?;
 | 
			
		||||
        if mud_value.is_nil() {
 | 
			
		||||
            Err(Error::msg(
 | 
			
		||||
                "Attempt to delete MUD connection that wasn't found",
 | 
			
		||||
            ))?
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let log_dest: String = stack.from_back(ctx)?;
 | 
			
		||||
        let log_dest = if log_dest == "none" || log_dest == "off" {
 | 
			
		||||
            None
 | 
			
		||||
        } else {
 | 
			
		||||
            Some(log_dest)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
 | 
			
		||||
        match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) {
 | 
			
		||||
            None => Err(Error::msg("That MUD connection doesn't exist."))?,
 | 
			
		||||
            Some(v) => {
 | 
			
		||||
                v.log_dest = log_dest;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(CallbackReturn::Return)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) fn cmd_list_logs<'gc>(
 | 
			
		||||
    ctx: Context<'gc>,
 | 
			
		||||
    global_memo: &GlobalMemoCell,
 | 
			
		||||
    _global_layout: &UseStateSetter<GlobalLayoutCell>,
 | 
			
		||||
) -> Callback<'gc> {
 | 
			
		||||
    let global_memo = global_memo.clone();
 | 
			
		||||
    Callback::from_fn(&ctx, move |ctx, _ex, _stack| {
 | 
			
		||||
        let frame = try_unwrap_frame(
 | 
			
		||||
            ctx,
 | 
			
		||||
            &ctx.get_global::<Table>("info")?
 | 
			
		||||
                .get::<&str, Value>(ctx, "current_frame")?,
 | 
			
		||||
        )?;
 | 
			
		||||
        start_listing_logs(&global_memo, &frame);
 | 
			
		||||
        Ok(CallbackReturn::Return)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) fn cmd_delete_logs<'gc>(
 | 
			
		||||
    ctx: Context<'gc>,
 | 
			
		||||
    global_memo: &GlobalMemoCell,
 | 
			
		||||
    _global_layout: &UseStateSetter<GlobalLayoutCell>,
 | 
			
		||||
) -> Callback<'gc> {
 | 
			
		||||
    let global_memo = global_memo.clone();
 | 
			
		||||
    Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
 | 
			
		||||
        let frame = try_unwrap_frame(
 | 
			
		||||
            ctx,
 | 
			
		||||
            &ctx.get_global::<Table>("info")?
 | 
			
		||||
                .get::<&str, Value>(ctx, "current_frame")?,
 | 
			
		||||
        )?;
 | 
			
		||||
        let stream = String::from_utf8(
 | 
			
		||||
            piccolo::String::from_value(
 | 
			
		||||
                ctx,
 | 
			
		||||
                stack
 | 
			
		||||
                    .pop_front()
 | 
			
		||||
                    .ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?,
 | 
			
		||||
            )?
 | 
			
		||||
            .as_bytes()
 | 
			
		||||
            .to_vec(),
 | 
			
		||||
        )?;
 | 
			
		||||
        let (min_date, max_date) = match stack.len() {
 | 
			
		||||
            2 => {
 | 
			
		||||
                let min_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                let max_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                (min_date, max_date)
 | 
			
		||||
            }
 | 
			
		||||
            1 => {
 | 
			
		||||
                let max_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                ("0000-00-00".to_owned(), max_date)
 | 
			
		||||
            }
 | 
			
		||||
            0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()),
 | 
			
		||||
            _ => Err(anyhow::Error::msg(
 | 
			
		||||
                "At most three arguments expected: stream min-date max-date",
 | 
			
		||||
            ))?,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        start_deleting_logs(&global_memo, &frame, &stream, &min_date, &max_date);
 | 
			
		||||
        Ok(CallbackReturn::Return)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) fn cmd_download_logs<'gc>(
 | 
			
		||||
    ctx: Context<'gc>,
 | 
			
		||||
    global_memo: &GlobalMemoCell,
 | 
			
		||||
    _global_layout: &UseStateSetter<GlobalLayoutCell>,
 | 
			
		||||
) -> Callback<'gc> {
 | 
			
		||||
    let global_memo = global_memo.clone();
 | 
			
		||||
    Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
 | 
			
		||||
        let frame = try_unwrap_frame(
 | 
			
		||||
            ctx,
 | 
			
		||||
            &ctx.get_global::<Table>("info")?
 | 
			
		||||
                .get::<&str, Value>(ctx, "current_frame")?,
 | 
			
		||||
        )?;
 | 
			
		||||
        let stream = String::from_utf8(
 | 
			
		||||
            piccolo::String::from_value(
 | 
			
		||||
                ctx,
 | 
			
		||||
                stack
 | 
			
		||||
                    .pop_front()
 | 
			
		||||
                    .ok_or_else(|| anyhow::Error::msg("No stream name argument given"))?,
 | 
			
		||||
            )?
 | 
			
		||||
            .as_bytes()
 | 
			
		||||
            .to_vec(),
 | 
			
		||||
        )?;
 | 
			
		||||
        let (min_date, max_date) = match stack.len() {
 | 
			
		||||
            2 => {
 | 
			
		||||
                let min_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                let max_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                (min_date, max_date)
 | 
			
		||||
            }
 | 
			
		||||
            1 => {
 | 
			
		||||
                let max_date = String::from_utf8(
 | 
			
		||||
                    piccolo::String::from_value(ctx, stack.pop_front().unwrap())?
 | 
			
		||||
                        .as_bytes()
 | 
			
		||||
                        .to_vec(),
 | 
			
		||||
                )?;
 | 
			
		||||
                ("0000-00-00".to_owned(), max_date)
 | 
			
		||||
            }
 | 
			
		||||
            0 => ("0000-00-00".to_owned(), "9999-12-31".to_owned()),
 | 
			
		||||
            _ => Err(anyhow::Error::msg(
 | 
			
		||||
                "At most three arguments expected: stream min-date max-date",
 | 
			
		||||
            ))?,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        start_downloading_logs(&global_memo, &frame, &stream, &min_date, &max_date);
 | 
			
		||||
        Ok(CallbackReturn::Return)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ use std::cell::RefCell;
 | 
			
		||||
use std::collections::VecDeque;
 | 
			
		||||
use std::rc::Rc;
 | 
			
		||||
 | 
			
		||||
use logging::LoggingEngine;
 | 
			
		||||
use parsing::ParsedCommand;
 | 
			
		||||
use term_split::TermSplit;
 | 
			
		||||
use yew::prelude::*;
 | 
			
		||||
@ -9,6 +10,7 @@ use yew::prelude::*;
 | 
			
		||||
pub mod command_handler;
 | 
			
		||||
pub mod id_intern;
 | 
			
		||||
pub mod lineengine;
 | 
			
		||||
pub mod logging;
 | 
			
		||||
pub mod lua_engine;
 | 
			
		||||
pub mod match_table;
 | 
			
		||||
pub mod parsing;
 | 
			
		||||
@ -29,6 +31,7 @@ pub struct GlobalMemoState {
 | 
			
		||||
    lua_engine: RefCell<LuaState>,
 | 
			
		||||
    ws_registry: RefCell<RegisteredWebSockets>,
 | 
			
		||||
    command_queue: RefCell<VecDeque<(TermFrame, ParsedCommand)>>,
 | 
			
		||||
    log_engine: RefCell<LoggingEngine>,
 | 
			
		||||
 | 
			
		||||
    // A cache of the latest layout info (separate from the state).
 | 
			
		||||
    // Updating this doesn't force a relayout, so only update the cache when
 | 
			
		||||
@ -72,6 +75,7 @@ fn app() -> Html {
 | 
			
		||||
        command_queue: VecDeque::new().into(),
 | 
			
		||||
        lua_engine: LuaState::setup().expect("Can create interpreter").into(),
 | 
			
		||||
        layout: RefCell::new((*global_layout).clone()),
 | 
			
		||||
        log_engine: RefCell::new(Default::default()),
 | 
			
		||||
    });
 | 
			
		||||
    use_memo((), |_| {
 | 
			
		||||
        install_lua_globals(&global_memo, global_layout.setter())
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ use std::collections::BTreeMap;
 | 
			
		||||
use gc_arena::Collect;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
 | 
			
		||||
use wasm_bindgen_futures::js_sys::Date;
 | 
			
		||||
use web_sys::{
 | 
			
		||||
    console,
 | 
			
		||||
    js_sys::{ArrayBuffer, JsString, Uint8Array},
 | 
			
		||||
@ -10,6 +11,7 @@ use web_sys::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    logging::{self, LogEvent},
 | 
			
		||||
    lua_engine::muds::{handle_websocket_close_log_err, handle_websocket_output_log_err},
 | 
			
		||||
    GlobalMemoCell,
 | 
			
		||||
};
 | 
			
		||||
@ -23,6 +25,7 @@ pub struct WebSocketData {
 | 
			
		||||
    pub closed: bool,
 | 
			
		||||
    pub url: String,
 | 
			
		||||
    pub retained_closures: Option<(Closure<dyn FnMut(MessageEvent)>, Closure<dyn FnMut()>)>,
 | 
			
		||||
    pub log_dest: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
pub type RegisteredWebSockets = BTreeMap<WebSocketId, WebSocketData>;
 | 
			
		||||
 | 
			
		||||
@ -52,6 +55,7 @@ pub fn connect_websocket(
 | 
			
		||||
        closed: false,
 | 
			
		||||
        url: url.to_owned(),
 | 
			
		||||
        retained_closures: None,
 | 
			
		||||
        log_dest: None,
 | 
			
		||||
    };
 | 
			
		||||
    data.connection.set_binary_type(BinaryType::Arraybuffer);
 | 
			
		||||
 | 
			
		||||
@ -63,6 +67,23 @@ pub fn connect_websocket(
 | 
			
		||||
    let data_closure: Closure<dyn FnMut(MessageEvent)> = Closure::new(move |ev: MessageEvent| {
 | 
			
		||||
        let data = ev.data();
 | 
			
		||||
        if data.has_type::<ArrayBuffer>() {
 | 
			
		||||
            let log_dest = data_globals
 | 
			
		||||
                .ws_registry
 | 
			
		||||
                .borrow()
 | 
			
		||||
                .get(&data_new_id)
 | 
			
		||||
                .and_then(|d| d.log_dest.clone());
 | 
			
		||||
            if let Some(log_dest) = &log_dest {
 | 
			
		||||
                let str_rendering =
 | 
			
		||||
                    String::from_utf8_lossy(&Uint8Array::new(&data).to_vec()).to_string();
 | 
			
		||||
                logging::log(
 | 
			
		||||
                    &data_globals,
 | 
			
		||||
                    &LogEvent {
 | 
			
		||||
                        log_stream: log_dest.clone(),
 | 
			
		||||
                        timestamp: Date::new_0(),
 | 
			
		||||
                        message: format!("R {}", &str_rendering),
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            handle_websocket_output_log_err(
 | 
			
		||||
                &data_new_id,
 | 
			
		||||
                &data_globals,
 | 
			
		||||
@ -126,6 +147,16 @@ pub fn send_message_to_mud(
 | 
			
		||||
        None => Err(anyhow::Error::msg("MUD connection not found")),
 | 
			
		||||
        Some(sock_data) if sock_data.closed => Err(anyhow::Error::msg("MUD connection is closed")),
 | 
			
		||||
        Some(sock_data) => {
 | 
			
		||||
            if let Some(log_dest) = &sock_data.log_dest {
 | 
			
		||||
                logging::log(
 | 
			
		||||
                    global,
 | 
			
		||||
                    &LogEvent {
 | 
			
		||||
                        log_stream: log_dest.clone(),
 | 
			
		||||
                        timestamp: Date::new_0(),
 | 
			
		||||
                        message: format!("W {}", String::from_utf8_lossy(msg)),
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            sock_data.connection.send_with_u8_array(msg).map_err(|e| {
 | 
			
		||||
                e.dyn_into::<DomException>()
 | 
			
		||||
                    .map(|e| anyhow::Error::msg(e.message()))
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user