Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add info command line subcommand #1330

Merged
merged 6 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Special color for unavailable items
- changelog with all the relevant user-facing changes to the project
- `info` command line subcommand to show platform specific information

### Changed

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ ncspot is available on macOS (Homebrew), Windows (Scoop), Linux (native package
BSD's. Detailed installation instructions for each platform can be found [here](/doc/users.md).

## Configuration
A configuration file can be provided at `$XDG_CONFIG_HOME/ncspot/config.toml`. Detailed
configuration information can be found [here](/doc/users.md#configuration).
A configuration file can be provided. Detailed configuration information can be found
ThomasFrans marked this conversation as resolved.
Show resolved Hide resolved
[here](/doc/users.md#configuration).

## Building
Building ncspot requires a working [Rust installation](https://www.rust-lang.org/tools/install) and
Expand Down
3 changes: 2 additions & 1 deletion doc/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ and attach a debugger. On Linux this can be achieved with `gdb` or `lldb`. It is
work. To disable it, execute `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`. This will allow
any process to inspect the memory of another process. It is automatically re-enabled after a reboot.

If ncspot has crashed you can find the latest backtrace at `~/.cache/ncspot/backtrace.log`.
If ncspot has crashed you can find the latest backtrace at `$NCSPOT_CACHE_DIRECTORY/backtrace.log`.
The cache directory can be shown by running `ncspot info`.

## Compiling
Compile and install the latest release with `cargo-install`:
Expand Down
26 changes: 12 additions & 14 deletions doc/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,12 @@ Note: \<FOO\> - mandatory arg; [BAR] - optional arg

## Remote control (IPC)
Apart from MPRIS, ncspot will also create a domain socket on UNIX platforms (Linux, macOS, *BSD).
The socket will be created in the platform's runtime directory. If XDG_RUNTIME_DIR is set, it will
be created under `$XDG_RUNTIME_DIR/ncspot`. If XDG_RUNTIME_DIR isn't set, it will be created under
`/run/user/<uid>` for Linux if it exists. In all other cases, it will be created under
`/tmp/ncspot-<uid>`. Applications or scripts can connect to this socket to send commands or be
notified of the currently playing track, i.e. with `netcat`:
The socket will be created in the platform's runtime directory. Run `ncspot info` to show the
location of this directory on your platform. Applications or scripts can connect to this socket to
send commands or be notified of the currently playing track, i.e. with `netcat`:

```
% nc -U ~/.cache/ncspot/ncspot.sock
% nc -U $NCSPOT_CACHE_DIRECTORY/ncspot.sock
play
{"mode":{"Playing":{"secs_since_epoch":1672249086,"nanos_since_epoch":547517730}},"playable":{"type":"Track","id":"2wcrQZ7ZJolYEfIaPP9yL4","uri":"spotify:track:2wcrQZ7ZJolYEfIaPP9yL4","title":"Hit Me Where It Hurts","track_number":4,"disc_number":1,"duration":184132,"artists":["Caroline Polachek"],"artist_ids":["4Ge8xMJNwt6EEXOzVXju9a"],"album":"Pang","album_id":"4ClyeVlAKJJViIyfVW0yQD","album_artists":["Caroline Polachek"],"cover_url":"https://i.scdn.co/image/ab67616d0000b2737d983e7bf67c2806218c2759","url":"https://open.spotify.com/track/2wcrQZ7ZJolYEfIaPP9yL4","added_at":"2022-12-19T22:41:05Z","list_index":0}}
playpause
Expand All @@ -212,7 +210,7 @@ as they typically tend to keep the connection to the socket open. OpenBSD's
specific number of packets have been received.

```
% nc -W 1 -U ~/.cache/ncspot/ncspot.sock
% nc -W 1 -U $NCSPOT_CACHE_DIRECTORY/ncspot.sock
{"mode":{"Playing":{"secs_since_epoch":1675188934,"nanos_since_epoch":50913345}},"playable":{"type":"Track","id":"5Cp6a1h2VnuOtsh1Nqxfv6","uri":"spotify:track:5Cp6a1h2VnuOtsh1Nqxfv6","title":"New Track","track_number":1,"disc_number":1,"duration":498358,"artists":["Francis Bebey"],"artist_ids":["0mdmrbu5UZ32uRcRp2z6mr"],"album":"African Electronic Music (1975-1982)","album_id":"7w99Aae1tYSTSb1OiDnxYY","album_artists":["Francis Bebey"],"cover_url":"https://i.scdn.co/image/ab67616d0000b2736ab57cedf27177fae1eaed87","url":"https://open.spotify.com/track/5Cp6a1h2VnuOtsh1Nqxfv6","added_at":"2020-12-22T09:57:17Z","list_index":0}}
```

Expand All @@ -221,17 +219,17 @@ For example, you can get the currently playing artist and title in your
terminal as follows:

```
% nc -W 1 -U ~/.cache/ncspot/ncspot.sock | jq '.playable.title'
% nc -W 1 -U $NCSPOT_CACHE_DIRECTORY/ncspot.sock | jq '.playable.title'
"PUMPIN' JUMPIN'"

% nc -W 1 -U ~/.cache/ncspot/ncspot.sock | jq '.playable.artists[0]'
% nc -W 1 -U $NCSPOT_CACHE_DIRECTORY/ncspot.sock | jq '.playable.artists[0]'
"Hideki Naganuma"
```

## Configuration
Configuration is saved to `~/.config/ncspot/config.toml` (or
`%AppData%\ncspot\config.toml` on Windows). To reload the configuration during
runtime use the `reload` command.
Configuration is saved to the `config.toml` file in the platform's standard configuration directory.
Run `ncspot info` to show the location of this directory on your platform. To reload the
configuration during runtime use the `reload` command.

Possible configuration values are:

Expand Down Expand Up @@ -448,8 +446,8 @@ cover_max_scale = 2
`ncspot` prompts for a Spotify username and password on first launch, uses this
to generate an OAuth token, and stores it to disk.

The credentials are stored in `~/.cache/ncspot/librespot/credentials.json`
(unless the base path has been changed with the `--basepath` option).
The credentials are stored in `librespot/credentials.json` in the user's cache directory. Run
`ncspot info` to show the location of this directory.

The `logout` command can be used to remove cached credentials. See
[Vim-Like Commands](#vim-like-commands).
Expand Down
35 changes: 35 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::config::{user_cache_directory, user_configuration_directory};

/// Print platform info like which platform directories will be used.
pub fn info() -> Result<(), String> {
let user_configuration_directory = user_configuration_directory();
let user_cache_directory = user_cache_directory();

println!(
"USER_CONFIGURATION_PATH {}",
user_configuration_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);
println!(
"USER_CACHE_PATH {}",
user_cache_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);

#[cfg(unix)]
{
use crate::utils::user_runtime_directory;

let user_runtime_directory = user_runtime_directory();
println!(
"USER_RUNTIME_PATH {}",
user_runtime_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);
}

Ok(())
}
39 changes: 21 additions & 18 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{fs, process};

use cursive::theme::Theme;
use log::{debug, error};
use ncspot::CONFIGURATION_FILE_NAME;
use platform_dirs::AppDirs;

use crate::command::{SortDirection, SortKey};
Expand Down Expand Up @@ -192,7 +193,7 @@ impl Config {
/// Generate the configuration from the user configuration file and the runtime state file.
/// `filename` can be used to look for a differently named configuration file.
pub fn new(filename: Option<String>) -> Self {
let filename = filename.unwrap_or("config.toml".to_owned());
let filename = filename.unwrap_or(CONFIGURATION_FILE_NAME.to_owned());
let values = load(&filename).unwrap_or_else(|e| {
eprintln!("could not load config: {e}");
process::exit(1);
Expand Down Expand Up @@ -268,16 +269,6 @@ fn load(filename: &str) -> Result<ConfigValues, String> {
TOML.load_or_generate_default(path, || Ok(ConfigValues::default()), false)
}

/// Returns the platform app directories for ncspot.
///
/// # Panics
///
/// This panics if the project directories could not be determined. Use `try_proj_dirs` for a
/// non-panicking version.
fn proj_dirs() -> AppDirs {
try_proj_dirs().unwrap()
}

/// Returns the plaform app directories for ncspot if they could be determined,
/// or an error otherwise.
pub fn try_proj_dirs() -> Result<AppDirs, String> {
Expand All @@ -296,19 +287,32 @@ pub fn try_proj_dirs() -> Result<AppDirs, String> {
}
}

/// Return the path to the current user's configuration directory, or None if it couldn't be found.
/// This function does not guarantee correct permissions or ownership of the directory!
pub fn user_configuration_directory() -> Option<PathBuf> {
let project_directories = try_proj_dirs().ok()?;
Some(project_directories.config_dir)
}

/// Return the path to the current user's cache directory, or None if one couldn't be found. This
/// function does not guarantee correct permissions or ownership of the directory!
pub fn user_cache_directory() -> Option<PathBuf> {
let project_directories = try_proj_dirs().ok()?;
Some(project_directories.cache_dir)
}

/// Force create the configuration directory at the default project location, removing anything that
/// isn't a directory but has the same name. Return the path to the configuration file inside the
/// directory.
///
/// This doesn't create the file, only the containing directory.
pub fn config_path(file: &str) -> PathBuf {
let proj_dirs = proj_dirs();
let cfg_dir = &proj_dirs.config_dir;
let cfg_dir = user_configuration_directory().unwrap();
if cfg_dir.exists() && !cfg_dir.is_dir() {
fs::remove_file(cfg_dir).expect("unable to remove old config file");
fs::remove_file(&cfg_dir).expect("unable to remove old config file");
}
if !cfg_dir.exists() {
fs::create_dir_all(cfg_dir).expect("can't create config folder");
fs::create_dir_all(&cfg_dir).expect("can't create config folder");
}
let mut cfg = cfg_dir.to_path_buf();
cfg.push(file);
Expand All @@ -320,10 +324,9 @@ pub fn config_path(file: &str) -> PathBuf {
///
/// This doesn't create the file, only the containing directory.
pub fn cache_path(file: &str) -> PathBuf {
let proj_dirs = proj_dirs();
let cache_dir = &proj_dirs.cache_dir;
let cache_dir = user_cache_directory().unwrap();
if !cache_dir.exists() {
fs::create_dir_all(cache_dir).expect("can't create cache folder");
fs::create_dir_all(&cache_dir).expect("can't create cache folder");
}
let mut pb = cache_dir.to_path_buf();
pb.push(file);
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use librespot_playback::audio_backend;

pub const AUTHOR: &str = "Henrik Friedrichsen <[email protected]> and contributors";
pub const BIN_NAME: &str = "ncspot";
pub const CONFIGURATION_FILE_NAME: &str = "config.toml";

/// Return the [Command](clap::Command) that models the program's command line arguments. The
/// command can be used to parse the actual arguments passed to the program, or to automatically
Expand Down Expand Up @@ -40,6 +41,7 @@ pub fn program_arguments() -> clap::Command {
.long("config")
.value_name("FILE")
.help("Filename of config file in basepath")
.default_value("config.toml"),
.default_value(CONFIGURATION_FILE_NAME),
)
.subcommands([clap::Command::new("info").about("Print platform information like paths")])
}
15 changes: 11 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use ncspot::program_arguments;

mod application;
mod authentication;
mod cli;
mod command;
mod commands;
mod config;
Expand Down Expand Up @@ -56,9 +57,15 @@ fn main() -> Result<(), String> {
// path.
set_configuration_base_path(matches.get_one::<PathBuf>("basepath").cloned());

// Create the application.
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;
match matches.subcommand() {
Some(("info", _subcommand_matches)) => cli::info(),
Some((_, _)) => unreachable!(),
None => {
// Create the application.
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;

// Start the application event loop.
application.run()
// Start the application event loop.
application.run()
}
}
}
5 changes: 4 additions & 1 deletion src/ui/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use cursive::utils::markup::StyledString;
use cursive::view::ViewWrapper;
use cursive::views::{ScrollView, TextView};
use cursive::Cursive;
use ncspot::CONFIGURATION_FILE_NAME;

use crate::command::{Command, MoveAmount, MoveMode};
use crate::commands::CommandResult;
Expand All @@ -22,7 +23,9 @@ impl HelpView {

let note = format!(
"Custom bindings can be set in {} within the [keybindings] section.\n\n",
config_path("config.toml").to_str().unwrap_or_default()
config_path(CONFIGURATION_FILE_NAME)
.to_str()
.unwrap_or_default()
);
text.append(StyledString::styled(note, Effect::Italic));

Expand Down
34 changes: 20 additions & 14 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,7 @@ pub fn create_runtime_directory() -> Result<PathBuf, Box<dyn std::error::Error>>
os::unix::prelude::PermissionsExt,
};

let linux_runtime_directory =
PathBuf::from(format!("/run/user/{}/", unsafe { libc::getuid() }));
let unix_runtime_directory = PathBuf::from("/tmp/");

let user_runtime_directory = if let Some(xdg_runtime_directory) = xdg_runtime_directory() {
Some(xdg_runtime_directory.join("ncspot"))
} else if cfg!(linux) && linux_runtime_directory.exists() {
Some(linux_runtime_directory.join("ncspot"))
} else if unix_runtime_directory.exists() {
Some(unix_runtime_directory.join(format!("ncspot-{}", unsafe { libc::getuid() })))
} else {
None
}
.ok_or("no runtime directory found")?;
let user_runtime_directory = user_runtime_directory().ok_or("no runtime directory found")?;

let creation_result = fs::create_dir(&user_runtime_directory);

Expand All @@ -106,6 +93,25 @@ pub fn create_runtime_directory() -> Result<PathBuf, Box<dyn std::error::Error>>
}
}

/// Return the path to the current user's runtime directory, or None if it couldn't be found.
/// This function does not guarantee correct ownership or permissions of the directory.
#[cfg(unix)]
pub fn user_runtime_directory() -> Option<PathBuf> {
let linux_runtime_directory =
PathBuf::from(format!("/run/user/{}/", unsafe { libc::getuid() }));
let unix_runtime_directory = PathBuf::from("/tmp/");

if let Some(xdg_runtime_directory) = xdg_runtime_directory() {
Some(xdg_runtime_directory.join("ncspot"))
} else if cfg!(linux) && linux_runtime_directory.exists() {
Some(linux_runtime_directory.join("ncspot"))
} else if unix_runtime_directory.exists() {
Some(unix_runtime_directory.join(format!("ncspot-{}", unsafe { libc::getuid() })))
} else {
None
}
}

#[cfg(unix)]
fn xdg_runtime_directory() -> Option<PathBuf> {
std::env::var("XDG_RUNTIME_DIR").ok().map(Into::into)
Expand Down