diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..b21378a --- /dev/null +++ b/.ignore @@ -0,0 +1,2 @@ +# for helix +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 22bcb8c..d52c9e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + [[package]] name = "async-broadcast" version = "0.7.1" @@ -240,15 +246,19 @@ dependencies = [ "gettext-rs", "glib-build-tools", "gtk4", + "http-cache", + "http-cache-reqwest", "libadwaita", "oo7", "rand", "reqwest", + "reqwest-middleware", "serde", "tokio", "tracing", "tracing-subscriber", "url", + "xdg", "zbus 5.1.1", ] @@ -273,12 +283,27 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -366,6 +391,33 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +[[package]] +name = "cacache" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61ff12b19d89c752c213316b87fdb4a587f073d219b893cc56974b8c9f39bf7" +dependencies = [ + "digest", + "either", + "futures", + "hex", + "libc", + "memmap2", + "miette", + "reflink-copy", + "serde", + "serde_derive", + "serde_json", + "sha1", + "sha2", + "ssri", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "walkdir", +] + [[package]] name = "cairo-rs" version = "0.20.5" @@ -544,6 +596,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -1201,12 +1262,73 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-cache" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b65cd1687caf2c7fff496741a2f264c26f54e6d6cec03dac8f276fa4e5430e" +dependencies = [ + "async-trait", + "bincode", + "cacache", + "http", + "http-cache-semantics", + "httpdate", + "serde", + "url", +] + +[[package]] +name = "http-cache-reqwest" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735586904a5ce0c13877c57cb4eb8195eb7c11ec1ffd64d4db053fb8559ca62e" +dependencies = [ + "anyhow", + "async-trait", + "http", + "http-cache", + "http-cache-semantics", + "reqwest", + "reqwest-middleware", + "serde", + "url", +] + +[[package]] +name = "http-cache-semantics" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92baf25cf0b8c9246baecf3a444546360a97b569168fdf92563ee6a47829920c" +dependencies = [ + "http", + "http-serde", + "serde", + "time", +] + +[[package]] +name = "http-serde" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" +dependencies = [ + "http", + "serde", +] + [[package]] name = "httparse" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.5.0" @@ -1275,7 +1397,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -1594,6 +1716,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -1603,6 +1734,29 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror 1.0.69", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.17" @@ -1720,6 +1874,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -1949,6 +2109,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1999,7 +2165,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls", "socket2", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] @@ -2018,7 +2184,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", "web-time", @@ -2077,6 +2243,17 @@ dependencies = [ "getrandom", ] +[[package]] +name = "reflink-copy" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17400ed684c3a0615932f00c271ae3eea13e47056a1455821995122348ab6438" +dependencies = [ + "cfg-if", + "rustix", + "windows", +] + [[package]] name = "regex" version = "1.11.1" @@ -2113,7 +2290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "async-compression", - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -2152,6 +2329,21 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + [[package]] name = "rgb" version = "0.8.50" @@ -2278,6 +2470,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.26" @@ -2380,6 +2581,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2457,6 +2669,23 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "ssri" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7a2b3c2bc9693bcb40870c4e9b5bf0d79f9cb46273321bf855ec513e919082" +dependencies = [ + "base64 0.21.7", + "digest", + "hex", + "miette", + "serde", + "sha-1", + "sha2", + "thiserror 1.0.69", + "xxhash-rust", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2544,13 +2773,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2574,6 +2823,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -2611,9 +2891,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -2625,6 +2917,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -2764,6 +3067,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "untrusted" version = "0.9.0" @@ -2812,6 +3121,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2930,12 +3249,31 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -2945,6 +3283,41 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -3078,6 +3451,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + [[package]] name = "xdg-home" version = "1.3.0" @@ -3088,6 +3467,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + [[package]] name = "yoke" version = "0.7.4" diff --git a/Cargo.toml b/Cargo.toml index f956e0d..62c7629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,12 @@ event-listener = "5.3.1" futures = "0.3.31" gettext-rs = { version = "0.7.2", features = ["gettext-system"] } gtk = { version = "0.9.2", package = "gtk4", features = ["v4_16"] } +http-cache = { version = "0.20.0", default-features = false, features = [ + "cacache", + "cacache-tokio", + "manager-cacache", +] } +http-cache-reqwest = "0.15.0" oo7 = "0.3.3" rand = "0.8" reqwest = { version = "0.12.9", default-features = false, features = [ @@ -22,6 +28,7 @@ reqwest = { version = "0.12.9", default-features = false, features = [ "http2", "rustls-tls-native-roots", ] } +reqwest-middleware = { version = "0.4.0", features = ["json", "http2", "charset"] } serde = { version = "1.0.214", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread"] } tracing = { version = "0.1.40", default-features = false, features = [ @@ -30,6 +37,7 @@ tracing = { version = "0.1.40", default-features = false, features = [ ] } tracing-subscriber = "0.3.18" url = { version = "2.5.2", features = ["serde"] } +xdg = "2.5.2" zbus = { version = "5.0.1", features = ["chrono"] } [build-dependencies] diff --git a/src/model/song.rs b/src/model/song.rs index eb96184..e72816e 100644 --- a/src/model/song.rs +++ b/src/model/song.rs @@ -64,8 +64,8 @@ mod imp { } } -// only fetch 20 thumbnails at a time -static SEM: Semaphore = Semaphore::const_new(20); +// only fetch 30 thumbnails at a time +static SEM: Semaphore = Semaphore::const_new(30); glib::wrapper! { pub struct Song(ObjectSubclass); @@ -112,6 +112,7 @@ impl Song { return; } }; + event!(target: "audrey::thumbnail_loader", Level::DEBUG, id = id, "fetched thumbnail"); let song = match song_weak.upgrade() { None => return, Some(song) => song, diff --git a/src/subsonic.rs b/src/subsonic.rs index b1b9fd2..4d3f67e 100644 --- a/src/subsonic.rs +++ b/src/subsonic.rs @@ -22,7 +22,7 @@ fn runtime() -> &'static tokio::runtime::Runtime { #[derive(Debug)] pub enum Error { UrlParseError(url::ParseError), - ReqwestError(reqwest::Error), + ReqwestError(reqwest_middleware::Error), SubsonicError(schema::Error), OtherError(&'static str), } @@ -49,16 +49,23 @@ impl std::error::Error for Error { } } -impl From for Error { - fn from(err: reqwest::Error) -> Self { +impl From for Error { + fn from(err: reqwest_middleware::Error) -> Self { // don't print secret salt/token combo Self::ReqwestError(err.without_url()) } } +impl From for Error { + fn from(err: reqwest::Error) -> Self { + // don't print secret salt/token combo + Self::ReqwestError(reqwest_middleware::Error::Reqwest(err.without_url())) + } +} + #[derive(Clone)] pub struct Client { - client: reqwest::Client, + client: reqwest_middleware::ClientWithMiddleware, base_url: reqwest::Url, } @@ -105,35 +112,65 @@ impl Client { return Err(Error::OtherError("url scheme is not HTTPS")); } - Ok(Client { - client: reqwest::Client::builder() - .user_agent(crate::USER_AGENT) - .build()?, - base_url, - }) + let xdg_dirs = xdg::BaseDirectories::with_prefix("audrey").expect("failed to get xdg dirs"); + let cache_dir = xdg_dirs + .create_cache_directory("http-cache") + .expect("failed to make cache dir"); + + use http_cache_reqwest::{ + CACacheManager, Cache, CacheMode, CacheOptions, HttpCache, HttpCacheOptions, + }; + + let http_cache = Cache(HttpCache { + mode: CacheMode::default(), + manager: CACacheManager { + path: cache_dir, + ..Default::default() + }, + options: HttpCacheOptions { + cache_options: Some(CacheOptions { + ignore_cargo_cult: true, + ..Default::default() + }), + ..Default::default() + }, + }); + + let reqwest_client = reqwest::Client::builder() + .user_agent(crate::USER_AGENT) + .build()?; + + let client = reqwest_middleware::ClientBuilder::new(reqwest_client) + .with(http_cache) + .build(); + + Ok(Client { client, base_url }) } async fn send( &self, - request: reqwest::Request, + request: reqwest_middleware::RequestBuilder, ) -> Result { // FIXME: is an entire channel per request overkill? maybe pool them? let (sender, receiver) = async_channel::bounded(1); - event!(target: "audrey::http_request_send", Level::DEBUG, method = request.method().as_str(), url = request.url().as_str()); - // let tokio take care of the request + further json parsing // this is because reqwest doesn't like the glib main loop // note that this is why those silly bounds on T are needed, because we're // sending back the result of the query from another thread - let future = self.client.execute(request); + let future = request.send(); runtime().spawn(async move { // wrap this logic in a fn so we can use ? async fn perform( - response: Result, - ) -> Result, reqwest::Error> { + response: Result, + ) -> Result, reqwest_middleware::Error> { let response = response?.error_for_status()?; - response.json::>().await + let xcache = response.headers()[http_cache::XCACHE].to_str().ok(); + event!(target: "audrey::http_request", Level::DEBUG, url=response.url().as_str(), status=response.status().as_str(), cache_status=xcache); + response + .json::>() + .await + .map_err(|e| reqwest_middleware::Error::Reqwest(e)) } sender @@ -169,8 +206,7 @@ impl Client { path: &[&str], query: &[(&str, &str)], ) -> Result { - self.send(self.client.get(self.url(path, query)).build()?) - .await + self.send(self.client.get(self.url(path, query))).await } pub async fn ping(&self) -> Result<(), Error> { @@ -205,9 +241,11 @@ impl Client { let future = self.client.get(url).send(); runtime().spawn(async move { async fn perform( - response: Result, + response: Result, ) -> Result { let response = response?.error_for_status()?; + let xcache = response.headers()[http_cache::XCACHE].to_str().ok(); + event!(target: "audrey::http_request_cover_art", Level::DEBUG, url=response.url().as_str(), status=response.status().as_str(), cache_status=xcache); if response.headers()[reqwest::header::CONTENT_TYPE] .to_str() .expect("bad content type")