Files
spotifast/examples/rootlist_probe.rs
Carmine Paolino ca6c269400 Log and record panics through fastframe-log
The log file, stderr and the panic log are set up by fastframe-log with
Spotifast's own default filters and paths, replacing the env_logger
set-up, its tee to the log file, and the panic hook.

Behaviour changes:

- A panic's line in panic.log now keeps its message with any link
  removed, on the same line as the location
  (`... on thread "main", panic at src/x.rs:1:2: message`), where it
  used to write the raw message, which could quote a URL with a token.
  The default hook is no longer chained, so the raw message and the
  backtrace hint are not printed to stderr either; the same redacted
  line is.
- Log lines read `[time LEVEL target] message`, with the level no longer
  padded or coloured on a terminal.
- The start line says `Starting spotifast <version> on <os> (<arch>)`,
  lowercase, and a log file that cannot be created is reported through
  the log (on stderr) instead of a bare message.
2026-09-25 15:51:37 +02:00

71 lines
2.8 KiB
Rust

//! Diagnostic: what Spotify's own permission service says about each
//! playlist in the account's rootlist, run with the stored playback
//! credential. The Web API's `collaborative` flag misses playlists shared by
//! invitation; the rootlist's decorated `capabilities` do not.
//!
//! cargo run --example rootlist_probe
use librespot_core::{Session, SessionConfig, cache::Cache};
use librespot_protocol::playlist4_external::SelectedListContent;
use protobuf::Message as _;
fn main() -> anyhow::Result<()> {
fastframe_log::Logging::new("spotifast", env!("CARGO_PKG_VERSION"))
.filter("warn")
.init()?;
let dirs = spotifast::paths::AppDirs::discover();
let cache = Cache::new::<&std::path::Path>(None, None, None, None)?.with_memory_credentials();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
runtime.block_on(async move {
let store = spotifast::credentials::Store::new(dirs);
let loaded = store.lease(spotifast::credentials::Slot::Playback).load().await?;
if let Some(warning) = loaded.warning {
eprintln!("{warning}");
}
let Some(spotifast::credentials::Grant::Playback(credentials)) = loaded.grant else {
anyhow::bail!("Enable playback in Spotifast first");
};
let session = Session::new(SessionConfig::default(), Some(cache));
session.connect(credentials, false).await?;
println!("connected as {}", session.username());
let mut from = 0usize;
loop {
let bytes = session.spclient().get_rootlist(from, Some(500)).await?;
let content = SelectedListContent::parse_from_bytes(&bytes)?;
let Some(contents) = content.contents.into_option() else {
break;
};
let count = contents.items.len();
let truncated = contents.truncated();
println!(
"page from={from}: {count} items, {} meta items",
contents.meta_items.len()
);
for (item, meta) in contents.items.iter().zip(contents.meta_items.iter()) {
let caps = meta.capabilities.as_ref();
println!(
"{:<40} owner={:<24} collaborative={:<5} can_view={:?} can_edit_items={:?} can_edit_metadata={:?} name={:?}",
item.uri(),
meta.owner_username(),
meta.attributes.collaborative(),
caps.map(|c| c.can_view()),
caps.map(|c| c.can_edit_items()),
caps.map(|c| c.can_edit_metadata()),
meta.attributes.name(),
);
}
if !truncated || count == 0 {
break;
}
from += count;
}
anyhow::Ok(())
})?;
Ok(())
}