Merge 5ca38939bd into release

This commit is contained in:
github-actions[bot] 2024-02-03 04:27:44 +00:00 committed by GitHub
commit bb11c80ebd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 700 additions and 435 deletions

View file

@ -1,6 +1,9 @@
coverage: coverage:
range: "0...100" range: "0...100"
status: status:
patch:
default:
informational: true
project: project:
default: default:
informational: true informational: true

View file

@ -1,5 +1,17 @@
# Changelog # Changelog
## Polaris 0.14.1
### Server
- Fixed compilation issue when using musl toolchains
- Log messages that DDNS is not setup have been downgraded to debug level
### Web client
- Fixed a bug where non-ASCII files or directories were not always alphabetically sorted (thanks @dechamps)
- Fixed a bug where after linking a last.fm account, clicking the account name would not link to the expected page
## Polaris 0.14.0 ## Polaris 0.14.0
### General ### General

1038
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -14,17 +14,20 @@ ui = ["native-windows-gui", "native-windows-derive"]
actix-files = { version = "0.6" } actix-files = { version = "0.6" }
actix-web = { version = "4" } actix-web = { version = "4" }
actix-web-httpauth = { version = "0.8" } actix-web-httpauth = { version = "0.8" }
ape = "0.4.0" ape = "0.5"
base64 = "0.13" base64 = "0.21"
branca = "0.10.1" branca = "0.10.1"
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
diesel_migrations = { version = "2.0", features = ["sqlite"] } diesel_migrations = { version = "2.0", features = ["sqlite"] }
futures-util = { version = "0.3" } futures-util = { version = "0.3" }
getopts = "0.2.21" getopts = "0.2.21"
http = "0.2.8" http = "0.2.8"
id3 = { git = "https://github.com/polyfloyd/rust-id3.git", rev = "f3b5e3a" } # TODO update after 1.5.0 is released id3 = "1.7.0"
lewton = "0.10.2" lewton = "0.10.2"
libsqlite3-sys = { version = "0.25", features = ["bundled", "bundled-windows"], optional = true } libsqlite3-sys = { version = "0.26", features = [
"bundled",
"bundled-windows",
], optional = true }
log = "0.4.17" log = "0.4.17"
metaflac = "0.2.5" metaflac = "0.2.5"
mp3-duration = "0.1.10" mp3-duration = "0.1.10"
@ -42,9 +45,8 @@ serde_derive = "1.0.147"
serde_json = "1.0.87" serde_json = "1.0.87"
simplelog = "0.12.0" simplelog = "0.12.0"
thiserror = "1.0.37" thiserror = "1.0.37"
tokio = "1.21" toml = "0.7"
toml = "0.5" ureq = "2.7"
ureq = "1.5.5"
url = "2.3" url = "2.3"
[dependencies.diesel] [dependencies.diesel]
@ -58,11 +60,17 @@ default_features = false
features = ["bmp", "gif", "jpeg", "png"] features = ["bmp", "gif", "jpeg", "png"]
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
native-windows-gui = {version = "1.0.13", default-features = false, features = ["cursor", "image-decoder", "message-window", "menu", "tray-notification"], optional = true } native-windows-gui = { version = "1.0.13", default-features = false, features = [
"cursor",
"image-decoder",
"message-window",
"menu",
"tray-notification",
], optional = true }
native-windows-derive = { version = "1.0.5", optional = true } native-windows-derive = { version = "1.0.5", optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
daemonize = "0.4.1" daemonize = "0.5"
sd-notify = "0.4.1" sd-notify = "0.4.1"
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]

View file

@ -21,7 +21,9 @@ Password: `demo_password`
- Support for `flac`, `mp3`, `mp4`, `mpc`, `ogg`, `opus`, `ape`, `wav` and `aiff` files - Support for `flac`, `mp3`, `mp4`, `mpc`, `ogg`, `opus`, `ape`, `wav` and `aiff` files
- Easy to setup and administer, no configuration files needed - Easy to setup and administer, no configuration files needed
- Dark mode and customizable color themes - Dark mode and customizable color themes
- Listen to your music on the go with [Polaris Android](https://github.com/agersant/polaris-android) - Listen to your music on the go:
- Polaris Android ([Google Play Store](https://play.google.com/store/apps/details?id=agersant.polaris) · [F-Droid](https://f-droid.org/packages/agersant.polaris/) · [Repository](https://github.com/agersant/polaris-android))
- Polarios ([App Store](https://apps.apple.com/app/polarios/id1662366309) · [Repository](https://gitlab.com/elise/Polarios))
- [Last.fm](https://www.last.fm) scrobbling - [Last.fm](https://www.last.fm) scrobbling
## Tutorials ## Tutorials

Binary file not shown.

View file

@ -1,5 +1,6 @@
use base64::prelude::*;
use diesel::prelude::*; use diesel::prelude::*;
use log::{error, info}; use log::{debug, error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::thread; use std::thread;
use std::time; use std::time;
@ -12,6 +13,8 @@ const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
pub enum Error { pub enum Error {
#[error("DDNS update query failed with HTTP status code `{0}`")] #[error("DDNS update query failed with HTTP status code `{0}`")]
UpdateQueryFailed(u16), UpdateQueryFailed(u16),
#[error("DDNS update query failed due to a transport error")]
UpdateQueryTransport,
#[error(transparent)] #[error(transparent)]
DatabaseConnection(#[from] db::Error), DatabaseConnection(#[from] db::Error),
#[error(transparent)] #[error(transparent)]
@ -39,19 +42,23 @@ impl Manager {
fn update_my_ip(&self) -> Result<(), Error> { fn update_my_ip(&self) -> Result<(), Error> {
let config = self.config()?; let config = self.config()?;
if config.host.is_empty() || config.username.is_empty() { if config.host.is_empty() || config.username.is_empty() {
info!("Skipping DDNS update because credentials are missing"); debug!("Skipping DDNS update because credentials are missing");
return Ok(()); return Ok(());
} }
let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.host); let full_url = format!("{}?host={}", DDNS_UPDATE_URL, &config.host);
let credentials = format!("{}:{}", &config.username, &config.password);
let response = ureq::get(full_url.as_str()) let response = ureq::get(full_url.as_str())
.auth(&config.username, &config.password) .set(
"Authorization",
&format!("Basic {}", BASE64_STANDARD_NO_PAD.encode(credentials)),
)
.call(); .call();
if response.ok() { match response {
Ok(()) Ok(_) => Ok(()),
} else { Err(ureq::Error::Status(code, _)) => Err(Error::UpdateQueryFailed(code)),
Err(Error::UpdateQueryFailed(response.status())) Err(ureq::Error::Transport(_)) => Err(Error::UpdateQueryTransport),
} }
} }

View file

@ -56,7 +56,6 @@ impl From<id3::Tag> for SongTags {
let track_number = tag.track(); let track_number = tag.track();
let year = tag let year = tag
.year() .year()
.map(|y| y as i32)
.or_else(|| tag.date_released().map(|d| d.year)) .or_else(|| tag.date_released().map(|d| d.year))
.or_else(|| tag.original_date_released().map(|d| d.year)) .or_else(|| tag.original_date_released().map(|d| d.year))
.or_else(|| tag.date_recorded().map(|d| d.year)); .or_else(|| tag.date_recorded().map(|d| d.year));
@ -316,9 +315,7 @@ fn read_flac(path: &Path) -> Result<SongTags, Error> {
let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok()); let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
let mut streaminfo = tag.get_blocks(metaflac::BlockType::StreamInfo); let mut streaminfo = tag.get_blocks(metaflac::BlockType::StreamInfo);
let duration = match streaminfo.next() { let duration = match streaminfo.next() {
Some(&metaflac::Block::StreamInfo(ref s)) => { Some(metaflac::Block::StreamInfo(s)) => Some(s.total_samples as u32 / s.sample_rate),
Some((s.total_samples as u32 / s.sample_rate) as u32)
}
_ => None, _ => None,
}; };
let has_artwork = tag.pictures().count() > 0; let has_artwork = tag.pictures().count() > 0;

View file

@ -34,7 +34,7 @@ fn update_removes_missing_content() {
let copy_options = fs_extra::dir::CopyOptions::new(); let copy_options = fs_extra::dir::CopyOptions::new();
fs_extra::dir::copy( fs_extra::dir::copy(
&original_collection_dir, original_collection_dir,
&builder.test_directory, &builder.test_directory,
&copy_options, &copy_options,
) )
@ -55,7 +55,7 @@ fn update_removes_missing_content() {
} }
let khemmis_directory = test_collection_dir.join("Khemmis"); let khemmis_directory = test_collection_dir.join("Khemmis");
std::fs::remove_dir_all(&khemmis_directory).unwrap(); std::fs::remove_dir_all(khemmis_directory).unwrap();
ctx.index.update().unwrap(); ctx.index.update().unwrap();
{ {
let mut connection = ctx.db.connect().unwrap(); let mut connection = ctx.db.connect().unwrap();
@ -124,7 +124,7 @@ fn can_flatten_directory() {
.build(); .build();
ctx.index.update().unwrap(); ctx.index.update().unwrap();
let path: PathBuf = [TEST_MOUNT_NAME, "Tobokegao"].iter().collect(); let path: PathBuf = [TEST_MOUNT_NAME, "Tobokegao"].iter().collect();
let songs = ctx.index.flatten(&path).unwrap(); let songs = ctx.index.flatten(path).unwrap();
assert_eq!(songs.len(), 8); assert_eq!(songs.len(), 8);
} }
@ -135,7 +135,7 @@ fn can_flatten_directory_with_shared_prefix() {
.build(); .build();
ctx.index.update().unwrap(); ctx.index.update().unwrap();
let path: PathBuf = [TEST_MOUNT_NAME, "Tobokegao", "Picnic"].iter().collect(); // Prefix of '(Picnic Remixes)' let path: PathBuf = [TEST_MOUNT_NAME, "Tobokegao", "Picnic"].iter().collect(); // Prefix of '(Picnic Remixes)'
let songs = ctx.index.flatten(&path).unwrap(); let songs = ctx.index.flatten(path).unwrap();
assert_eq!(songs.len(), 7); assert_eq!(songs.len(), 7);
} }

View file

@ -99,7 +99,7 @@ impl Manager {
if let Some(sleep_duration) = new_settings.reindex_every_n_seconds { if let Some(sleep_duration) = new_settings.reindex_every_n_seconds {
diesel::update(misc_settings::table) diesel::update(misc_settings::table)
.set(misc_settings::index_sleep_duration_seconds.eq(sleep_duration as i32)) .set(misc_settings::index_sleep_duration_seconds.eq(sleep_duration))
.execute(&mut connection)?; .execute(&mut connection)?;
} }

View file

@ -26,7 +26,7 @@ pub struct ContextBuilder {
impl ContextBuilder { impl ContextBuilder {
pub fn new(test_name: String) -> Self { pub fn new(test_name: String) -> Self {
Self { Self {
test_directory: prepare_test_directory(&test_name), test_directory: prepare_test_directory(test_name),
config: config::Config::default(), config: config::Config::default(),
} }
} }

View file

@ -31,7 +31,7 @@ pub enum Error {
CliArgsParsing(getopts::Fail), CliArgsParsing(getopts::Fail),
#[cfg(unix)] #[cfg(unix)]
#[error("Failed to turn polaris process into a daemon:\n\n{0}")] #[error("Failed to turn polaris process into a daemon:\n\n{0}")]
Daemonize(daemonize::DaemonizeError), Daemonize(daemonize::Error),
#[error("Could not create log directory `{0}`:\n\n{1}")] #[error("Could not create log directory `{0}`:\n\n{1}")]
LogDirectoryCreationError(PathBuf, std::io::Error), LogDirectoryCreationError(PathBuf, std::io::Error),
#[error("Could not create log file `{0}`:\n\n{1}")] #[error("Could not create log file `{0}`:\n\n{1}")]

View file

@ -12,6 +12,7 @@ use actix_web::{
FromRequest, HttpRequest, HttpResponse, Responder, ResponseError, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
}; };
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use base64::prelude::*;
use futures_util::future::err; use futures_util::future::err;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
use std::future::Future; use std::future::Future;
@ -531,7 +532,7 @@ async fn get_audio(
}) })
.await?; .await?;
let named_file = NamedFile::open(&audio_path).map_err(|_| APIError::AudioFileIOError)?; let named_file = NamedFile::open(audio_path).map_err(|_| APIError::AudioFileIOError)?;
Ok(MediaFile::new(named_file)) Ok(MediaFile::new(named_file))
} }
@ -555,8 +556,7 @@ async fn get_thumbnail(
}) })
.await?; .await?;
let named_file = let named_file = NamedFile::open(thumbnail_path).map_err(|_| APIError::ThumbnailFileIOError)?;
NamedFile::open(&thumbnail_path).map_err(|_| APIError::ThumbnailFileIOError)?;
Ok(MediaFile::new(named_file)) Ok(MediaFile::new(named_file))
} }
@ -671,7 +671,8 @@ async fn lastfm_link(
let base64_content = percent_decode_str(&payload.content).decode_utf8_lossy(); let base64_content = percent_decode_str(&payload.content).decode_utf8_lossy();
// Base64 decode // Base64 decode
let popup_content = base64::decode(base64_content.as_bytes()) let popup_content = BASE64_STANDARD_NO_PAD
.decode(base64_content.as_bytes())
.map_err(|_| APIError::LastFMLinkContentBase64DecodeError)?; .map_err(|_| APIError::LastFMLinkContentBase64DecodeError)?;
// UTF-8 decode // UTF-8 decode

View file

@ -162,6 +162,7 @@ impl From<ddns::Error> for APIError {
ddns::Error::Database(e) => APIError::Database(e), ddns::Error::Database(e) => APIError::Database(e),
ddns::Error::DatabaseConnection(e) => e.into(), ddns::Error::DatabaseConnection(e) => e.into(),
ddns::Error::UpdateQueryFailed(s) => APIError::DdnsUpdateQueryFailed(s), ddns::Error::UpdateQueryFailed(s) => APIError::DdnsUpdateQueryFailed(s),
ddns::Error::UpdateQueryTransport => APIError::DdnsUpdateQueryFailed(0),
} }
} }
} }