mirror of
https://github.com/talwat/lowfi
synced 2025-09-10 10:40:46 +00:00
feat: prepare for 1.7.0 release
docs: explain music situation docs: more internal documentation feat: make timeout configurable chore: clean up some sections of code
This commit is contained in:
parent
dd9aab7118
commit
84887ae01b
@ -1,5 +1,10 @@
|
|||||||
# Using the chillhop list
|
# Using the chillhop list
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> As of lowfi 1.7.0, the chillhop list is included by default. For a more
|
||||||
|
> detailed explanation, see [MUSIC.md](MUSIC.md). This document is included
|
||||||
|
> to preserve any old links or references. The instructions are still valid.
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1508,7 +1508,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lowfi"
|
name = "lowfi"
|
||||||
version = "1.7.1-dev"
|
version = "1.7.2-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lowfi"
|
name = "lowfi"
|
||||||
version = "1.7.1-dev"
|
version = "1.7.2-dev"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "An extremely simple lofi player."
|
description = "An extremely simple lofi player."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
75
MUSIC.md
Normal file
75
MUSIC.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# The State of Lowfi's Music
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This document will be a bit long and has almost nothing to do with the actual
|
||||||
|
> usage of lowfi, just the music embedded by default.
|
||||||
|
|
||||||
|
Before that though, some context. lowfi includes an extensive track list
|
||||||
|
embedded into the software, so you can download it and have it "just work"
|
||||||
|
out of the box.
|
||||||
|
|
||||||
|
I always hated apps that required extensive configuration just to be usable.
|
||||||
|
Sometimes it's justified, but often, it's just pointless when most will end up
|
||||||
|
with the same set of "defaults" that aren't really defaults.
|
||||||
|
|
||||||
|
Lowfi is so nice and simple because of the "plug and play" aspect,
|
||||||
|
but it's become a lot harder to continue it as of late.
|
||||||
|
|
||||||
|
## The Lofi Girl List
|
||||||
|
|
||||||
|
Originally, it was planned that lowfi would use music scraped from Lofi Girl's own
|
||||||
|
website. The scraper actually came before the rest of the program, believe it or not.
|
||||||
|
|
||||||
|
However, after a long period of downtime, the Lofi Girl website was redone without the
|
||||||
|
mp3 track files. Those are now pretty much inaccessible aside from paying for individual
|
||||||
|
albums on bandcamp which gets very expensive very quickly.
|
||||||
|
|
||||||
|
Doing this was never actually disallowed, but it is now simply impossible. So, the question was,
|
||||||
|
what to do next after losing lowfi's primary source of music?
|
||||||
|
|
||||||
|
## Tracklists
|
||||||
|
|
||||||
|
I was originally against the idea of custom tracklists, because of my almost purist
|
||||||
|
ideals of a 100% no config at all vision for lowfi. But eventually, I gave in, which proved
|
||||||
|
to be a very good decision in hindsight. Now, regardless of what choices I make on the music
|
||||||
|
which is embedded, all may opt out of that and choose whatever they like.
|
||||||
|
|
||||||
|
This culminated in a few templates located in the `data` directory of this repository
|
||||||
|
which included a handful of tracklists, and in particular, the chillhop list by user
|
||||||
|
[danielwerg](https://github.com/danielwerg).
|
||||||
|
|
||||||
|
## The Switch
|
||||||
|
|
||||||
|
After `lofigirl.com` went down, I thought a bit and eventually decided
|
||||||
|
to just bite the bullet and switch to the chillhop list. This was despite the fact
|
||||||
|
that chillhop entirely bans third party players in their TOS. They also ban
|
||||||
|
scrapers, which I only learned after writing one.
|
||||||
|
|
||||||
|
So, is lowfi really going to have to violate the TOS of it's own music provider?
|
||||||
|
Well, yes. I thought about it, and came to the conclusion that lowfi is probably
|
||||||
|
not much of a threat for a few reasons.
|
||||||
|
|
||||||
|
Firstly, it emulates exactly the behavior of chillhop's own radio player.
|
||||||
|
The only difference is that one shoves you into a web browser, and the other,
|
||||||
|
into a nice terminal window.
|
||||||
|
|
||||||
|
Then, I also realize that lowfi is just a small program used by few.
|
||||||
|
I'm not making money on any of this, and I think degrading the experience for my
|
||||||
|
fellow nerds who just want to listen to some lowfi without all the crap is not worth it.
|
||||||
|
|
||||||
|
At the end of the day, lowfi has a distinct UserAgent. Should chillhop ever take issue with
|
||||||
|
it's behaviour, banning it is extremely simple. I don't want that to happen, but I
|
||||||
|
understand if it does.
|
||||||
|
|
||||||
|
## Well, *I* Hate the Chillhop Music
|
||||||
|
|
||||||
|
It's not as "lofi". It is almost certainly a compromise, that much I cannot even pretend to
|
||||||
|
deny. I find myself hitting the skip button almost three times as often with chillhop.
|
||||||
|
|
||||||
|
If you are undeterred enough by TOS's to read this far, then you can use the `archive.txt`
|
||||||
|
list in the `data` folder. The list is a product of me worrying that the tracks on `lofigirl.com`
|
||||||
|
could've possibly been lost somehow, relating to the website going down.
|
||||||
|
|
||||||
|
It's hosted on `archive.org`, and could be taken down at any point for any reason.
|
||||||
|
Being derived from my own local archive, it retains ~2700 out of the ~3700 tracks.
|
||||||
|
That's not perfect, the organization is also *bad*, but it exists.
|
16
README.md
16
README.md
@ -8,9 +8,8 @@ It'll do this as simply as it can: no albums, no ads, just lofi.
|
|||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
As of the 1.7.0 version of lowfi, **all** of the audio files embedded
|
As of the 1.7.0 version of lowfi, **all** of the audio files embedded
|
||||||
by default are from [chillhop](https://chillhop.com/).
|
by default are from [chillhop](https://chillhop.com/). Read
|
||||||
|
[MUSIC.md] for more information.
|
||||||
<!-- TODO: Make seperate write-up about using chillhop. -->
|
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
@ -154,15 +153,10 @@ slightly tweak the UI or behaviour of the menu. The flags can be viewed with `lo
|
|||||||
|
|
||||||
### Scraping
|
### Scraping
|
||||||
|
|
||||||
lowfi also has a `scrape` command which is usually not relevant, but
|
lowfi also has an optional `scrape` command enabled by the `scrape` feature.
|
||||||
if you're trying to download some files from Lofi Girls' website,
|
It's usually not very useful, but is included for transparency's sake.
|
||||||
it can be useful.
|
|
||||||
|
|
||||||
An example of scrape is as follows,
|
More information can be found by running `lowfi help scrape`.
|
||||||
|
|
||||||
`lowfi scrape --extension zip --include-full`
|
|
||||||
|
|
||||||
where more information can be found by running `lowfi help scrape`.
|
|
||||||
|
|
||||||
### Custom Track Lists
|
### Custom Track Lists
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ mod scrapers;
|
|||||||
|
|
||||||
#[cfg(feature = "scrape")]
|
#[cfg(feature = "scrape")]
|
||||||
use crate::scrapers::Source;
|
use crate::scrapers::Source;
|
||||||
|
|
||||||
/// An extremely simple lofi player.
|
/// An extremely simple lofi player.
|
||||||
#[derive(Parser, Clone)]
|
#[derive(Parser, Clone)]
|
||||||
#[command(about, version)]
|
#[command(about, version)]
|
||||||
@ -41,6 +42,10 @@ struct Args {
|
|||||||
#[clap(long, short, default_value_t = 12)]
|
#[clap(long, short, default_value_t = 12)]
|
||||||
fps: u8,
|
fps: u8,
|
||||||
|
|
||||||
|
/// Timeout in seconds for music downloads.
|
||||||
|
#[clap(long, default_value_t = 3)]
|
||||||
|
timeout: u64,
|
||||||
|
|
||||||
/// Include ALSA & other logs.
|
/// Include ALSA & other logs.
|
||||||
#[clap(long, short)]
|
#[clap(long, short)]
|
||||||
debug: bool,
|
debug: bool,
|
||||||
@ -50,7 +55,7 @@ struct Args {
|
|||||||
width: usize,
|
width: usize,
|
||||||
|
|
||||||
/// Use a custom track list
|
/// Use a custom track list
|
||||||
#[clap(long, short, alias = "list", short_alias = 'l')]
|
#[clap(long, short, alias = "list", alias = "tracks", short_alias = 'l')]
|
||||||
track_list: Option<String>,
|
track_list: Option<String>,
|
||||||
|
|
||||||
/// Internal song buffer size.
|
/// Internal song buffer size.
|
||||||
|
@ -70,7 +70,9 @@ pub async fn play(args: Args) -> eyre::Result<(), player::Error> {
|
|||||||
|
|
||||||
drop(stream);
|
drop(stream);
|
||||||
player.sink.stop();
|
player.sink.stop();
|
||||||
ui.map(|x| x.abort());
|
if let Some(x) = ui {
|
||||||
|
x.abort();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,6 @@ pub use error::Error;
|
|||||||
#[cfg(feature = "mpris")]
|
#[cfg(feature = "mpris")]
|
||||||
pub mod mpris;
|
pub mod mpris;
|
||||||
|
|
||||||
/// The time to wait in between errors.
|
|
||||||
/// TODO: Make this configurable.
|
|
||||||
const TIMEOUT: Duration = Duration::from_secs(3);
|
|
||||||
|
|
||||||
/// Main struct responsible for queuing up & playing tracks.
|
/// Main struct responsible for queuing up & playing tracks.
|
||||||
// TODO: Consider refactoring [Player] from being stored in an [Arc], into containing many smaller [Arc]s.
|
// TODO: Consider refactoring [Player] from being stored in an [Arc], into containing many smaller [Arc]s.
|
||||||
// TODO: In other words, this would change the type from `Arc<Player>` to just `Player`.
|
// TODO: In other words, this would change the type from `Arc<Player>` to just `Player`.
|
||||||
@ -76,6 +72,9 @@ pub struct Player {
|
|||||||
/// The bookmarks, which are saved on quit.
|
/// The bookmarks, which are saved on quit.
|
||||||
pub bookmarks: Bookmarks,
|
pub bookmarks: Bookmarks,
|
||||||
|
|
||||||
|
/// The timeout for track downloads, as a [Duration].
|
||||||
|
timeout: Duration,
|
||||||
|
|
||||||
/// The actual list of tracks to be played.
|
/// The actual list of tracks to be played.
|
||||||
list: List,
|
list: List,
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ impl Player {
|
|||||||
"/",
|
"/",
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
))
|
))
|
||||||
.timeout(TIMEOUT * 5)
|
.timeout(Duration::from_secs(args.timeout * 5))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let player = Self {
|
let player = Self {
|
||||||
@ -152,6 +151,7 @@ impl Player {
|
|||||||
buffer_size: args.buffer_size,
|
buffer_size: args.buffer_size,
|
||||||
current: ArcSwapOption::new(None),
|
current: ArcSwapOption::new(None),
|
||||||
progress: AtomicF32::new(0.0),
|
progress: AtomicF32::new(0.0),
|
||||||
|
timeout: Duration::from_secs(args.timeout),
|
||||||
bookmarks,
|
bookmarks,
|
||||||
client,
|
client,
|
||||||
sink,
|
sink,
|
||||||
@ -301,7 +301,7 @@ impl Player {
|
|||||||
let current = player.current.load();
|
let current = player.current.load();
|
||||||
let current = current.as_ref().unwrap();
|
let current = current.as_ref().unwrap();
|
||||||
|
|
||||||
player.bookmarks.bookmark(&¤t).await?;
|
player.bookmarks.bookmark(current).await?;
|
||||||
}
|
}
|
||||||
Message::Quit => break,
|
Message::Quit => break,
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use std::io::SeekFrom;
|
//! Module for handling saving, loading, and adding
|
||||||
|
//! bookmarks.
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use tokio::fs::{create_dir_all, File, OpenOptions};
|
use tokio::fs::{create_dir_all, File, OpenOptions};
|
||||||
use tokio::io::{self, AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{data_dir, tracks};
|
use crate::{data_dir, tracks};
|
||||||
|
|
||||||
|
/// Errors that might occur while managing bookmarks.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum BookmarkError {
|
pub enum BookmarkError {
|
||||||
#[error("data directory not found")]
|
#[error("data directory not found")]
|
||||||
@ -18,24 +21,36 @@ pub enum BookmarkError {
|
|||||||
|
|
||||||
/// Manages the bookmarks in the current player.
|
/// Manages the bookmarks in the current player.
|
||||||
pub struct Bookmarks {
|
pub struct Bookmarks {
|
||||||
|
/// The different entries in the bookmarks file.
|
||||||
entries: RwLock<Vec<String>>,
|
entries: RwLock<Vec<String>>,
|
||||||
file: RwLock<File>,
|
|
||||||
|
/// The internal bookmarked register, which keeps track
|
||||||
|
/// of whether a track is bookmarked or not.
|
||||||
|
///
|
||||||
|
/// This is much more efficient than checking every single frame.
|
||||||
bookmarked: AtomicBool,
|
bookmarked: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bookmarks {
|
impl Bookmarks {
|
||||||
pub async fn load() -> eyre::Result<Self, BookmarkError> {
|
/// Actually opens the bookmarks file itself.
|
||||||
|
pub async fn open(write: bool) -> eyre::Result<File, BookmarkError> {
|
||||||
let data_dir = data_dir().map_err(|_| BookmarkError::DataDir)?;
|
let data_dir = data_dir().map_err(|_| BookmarkError::DataDir)?;
|
||||||
create_dir_all(data_dir.clone()).await?;
|
create_dir_all(data_dir.clone()).await?;
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(write)
|
||||||
.read(true)
|
.read(true)
|
||||||
.append(false)
|
.append(false)
|
||||||
.truncate(false)
|
.truncate(true)
|
||||||
.open(data_dir.join("bookmarks.txt"))
|
.open(data_dir.join("bookmarks.txt"))
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(BookmarkError::Io)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads bookmarks from the `bookmarks.txt` file.
|
||||||
|
pub async fn load() -> eyre::Result<Self, BookmarkError> {
|
||||||
|
let mut file = Self::open(false).await?;
|
||||||
|
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
file.read_to_string(&mut text).await?;
|
file.read_to_string(&mut text).await?;
|
||||||
@ -45,29 +60,27 @@ impl Bookmarks {
|
|||||||
.trim()
|
.trim()
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|x| {
|
.filter_map(|x| {
|
||||||
if !x.is_empty() {
|
if x.is_empty() {
|
||||||
Some(x.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
Some(x.to_string())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
entries: RwLock::new(lines),
|
entries: RwLock::new(lines),
|
||||||
file: RwLock::new(file),
|
|
||||||
bookmarked: AtomicBool::new(false),
|
bookmarked: AtomicBool::new(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saves the bookmarks to the `bookmarks.txt` file.
|
||||||
pub async fn save(&self) -> eyre::Result<(), BookmarkError> {
|
pub async fn save(&self) -> eyre::Result<(), BookmarkError> {
|
||||||
|
let mut file = Self::open(true).await?;
|
||||||
let text = format!("noheader\n{}", self.entries.read().await.join("\n"));
|
let text = format!("noheader\n{}", self.entries.read().await.join("\n"));
|
||||||
|
|
||||||
let mut lock = self.file.write().await;
|
file.write_all(text.as_bytes()).await?;
|
||||||
lock.seek(SeekFrom::Start(0)).await?;
|
file.flush().await?;
|
||||||
lock.set_len(0).await?;
|
|
||||||
lock.write_all(text.as_bytes()).await?;
|
|
||||||
lock.flush().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -91,10 +104,14 @@ impl Bookmarks {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether a track is bookmarked or not by using the internal
|
||||||
|
/// bookmarked register.
|
||||||
pub fn bookmarked(&self) -> bool {
|
pub fn bookmarked(&self) -> bool {
|
||||||
self.bookmarked.load(std::sync::atomic::Ordering::Relaxed)
|
self.bookmarked.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the internal bookmarked register by checking against
|
||||||
|
/// the current track's info.
|
||||||
pub async fn set_bookmarked(&self, track: &tracks::Info) {
|
pub async fn set_bookmarked(&self, track: &tracks::Info) {
|
||||||
let val = self.entries.read().await.contains(&track.to_entry());
|
let val = self.entries.read().await.contains(&track.to_entry());
|
||||||
self.bookmarked
|
self.bookmarked
|
||||||
|
@ -8,7 +8,7 @@ use tokio::{
|
|||||||
time::sleep,
|
time::sleep,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Player, TIMEOUT};
|
use super::Player;
|
||||||
|
|
||||||
/// This struct is responsible for downloading tracks in the background.
|
/// This struct is responsible for downloading tracks in the background.
|
||||||
///
|
///
|
||||||
@ -53,7 +53,7 @@ impl Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !error.is_timeout() {
|
if !error.is_timeout() {
|
||||||
sleep(TIMEOUT).await;
|
sleep(self.player.timeout).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use tokio::{sync::mpsc::Sender, time::sleep};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
messages::Message,
|
messages::Message,
|
||||||
player::{downloader::Downloader, Player, TIMEOUT},
|
player::{downloader::Downloader, Player},
|
||||||
tracks,
|
tracks,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !error.is_timeout() {
|
if !error.is_timeout() {
|
||||||
sleep(TIMEOUT).await;
|
sleep(player.timeout).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.send(Message::TryAgain).await?;
|
tx.send(Message::TryAgain).await?;
|
||||||
|
@ -12,6 +12,7 @@ pub mod archive;
|
|||||||
pub mod chillhop;
|
pub mod chillhop;
|
||||||
pub mod lofigirl;
|
pub mod lofigirl;
|
||||||
|
|
||||||
|
/// Represents the different sources which can be scraped.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, ValueEnum)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, ValueEnum)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
Lofigirl,
|
Lofigirl,
|
||||||
@ -20,6 +21,7 @@ pub enum Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
|
/// Gets the cache directory name, for example, `chillhop`.
|
||||||
pub fn cache_dir(&self) -> &'static str {
|
pub fn cache_dir(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Source::Lofigirl => "lofigirl",
|
Source::Lofigirl => "lofigirl",
|
||||||
@ -28,6 +30,7 @@ impl Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the full root URL of the source.
|
||||||
pub fn url(&self) -> &'static str {
|
pub fn url(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Source::Chillhop => "https://chillhop.com",
|
Source::Chillhop => "https://chillhop.com",
|
||||||
|
@ -135,7 +135,7 @@ impl Info {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Formats a name with [convert_case].
|
/// Formats a name with [`convert_case`].
|
||||||
///
|
///
|
||||||
/// This will also strip the first few numbers that are
|
/// This will also strip the first few numbers that are
|
||||||
/// usually present on most lofi tracks and do some other
|
/// usually present on most lofi tracks and do some other
|
||||||
|
@ -44,7 +44,7 @@ where
|
|||||||
Kind: From<E>,
|
Kind: From<E>,
|
||||||
{
|
{
|
||||||
fn from((track, err): (T, E)) -> Self {
|
fn from((track, err): (T, E)) -> Self {
|
||||||
Error {
|
Self {
|
||||||
track: track.into(),
|
track: track.into(),
|
||||||
kind: Kind::from(err),
|
kind: Kind::from(err),
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ impl List {
|
|||||||
|
|
||||||
let name = custom_name.map_or_else(
|
let name = custom_name.map_or_else(
|
||||||
|| super::TrackName::Raw(path.clone()),
|
|| super::TrackName::Raw(path.clone()),
|
||||||
|formatted| super::TrackName::Formatted(formatted),
|
super::TrackName::Formatted,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(QueuedTrack {
|
Ok(QueuedTrack {
|
||||||
@ -153,7 +153,7 @@ impl List {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
lines,
|
lines,
|
||||||
path: path.map(|s| s.to_owned()),
|
path: path.map(ToOwned::to_owned),
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,11 +168,9 @@ impl List {
|
|||||||
let raw = fs::read_to_string(path.clone()).await?;
|
let raw = fs::read_to_string(path.clone()).await?;
|
||||||
|
|
||||||
// Get rid of special noheader case for tracklists without a header.
|
// Get rid of special noheader case for tracklists without a header.
|
||||||
let raw = if let Some(stripped) = raw.strip_prefix("noheader") {
|
let raw = raw
|
||||||
stripped
|
.strip_prefix("noheader")
|
||||||
} else {
|
.map_or(raw.as_ref(), |stripped| stripped);
|
||||||
&raw
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = path
|
let name = path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user