1pub mod color;
5pub mod db_cache;
6#[cfg(not(target_arch = "wasm32"))]
7pub mod hls_source;
8pub mod jellyfin_image;
9#[cfg(not(target_arch = "wasm32"))]
10pub mod logs;
11pub mod lyrics;
12pub mod musicbrainz;
13#[cfg(not(target_arch = "wasm32"))]
14pub mod range_source;
15#[cfg(not(target_arch = "wasm32"))]
16pub mod stream_buffer;
17pub mod subsonic_image;
18pub mod themes;
19use std::path::Path;
20use std::sync::Arc;
21
22pub type CoverUrl = Arc<str>;
23
24pub fn cover_url_from_string(url: String) -> CoverUrl {
25 Arc::from(url)
26}
27
28pub fn map_cover_url(url: Option<String>) -> Option<CoverUrl> {
29 url.map(cover_url_from_string)
30}
31
32pub async fn sleep(duration: std::time::Duration) {
34 #[cfg(not(target_arch = "wasm32"))]
35 {
36 tokio::time::sleep(duration).await;
37 }
38 #[cfg(target_arch = "wasm32")]
39 {
40 gloo_timers::future::sleep(duration).await;
41 }
42}
43
44fn format_artwork_url_impl(path: Option<&impl AsRef<Path>>, size: Option<u32>) -> Option<CoverUrl> {
45 let p = path?;
46 let p = p.as_ref();
47 let p_str = p.to_string_lossy();
48
49 let abs_path = if let Some(stripped) = p_str.strip_prefix("./") {
50 std::env::current_dir().unwrap_or_default().join(stripped)
51 } else {
52 p.to_path_buf()
53 };
54
55 let abs_str = abs_path.to_string_lossy();
56 let abs_str = if abs_str.starts_with('~') {
57 if let Ok(home) = std::env::var("HOME") {
58 std::borrow::Cow::Owned(abs_str.replacen('~', &home, 1))
59 } else {
60 abs_str
61 }
62 } else {
63 abs_str
64 };
65
66 #[cfg(target_os = "android")]
69 {
70 use base64::{Engine as _, engine::general_purpose};
71 return match std::fs::read(&*abs_str) {
72 Ok(bytes) => {
73 let mime = if abs_str.ends_with(".png") {
74 "image/png"
75 } else if abs_str.ends_with(".gif") {
76 "image/gif"
77 } else if abs_str.ends_with(".webp") {
78 "image/webp"
79 } else {
80 "image/jpeg"
81 };
82 let b64 = general_purpose::STANDARD.encode(&bytes);
83 Some(cover_url_from_string(format!("data:{mime};base64,{b64}")))
84 }
85 Err(_) => None,
86 };
87 }
88
89 const QUERY_VAL: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
90 .add(b' ')
91 .add(b'"')
92 .add(b'#')
93 .add(b'%')
94 .add(b'&')
95 .add(b'+')
96 .add(b'=')
97 .add(b'?')
98 .add(b'<')
99 .add(b'>')
100 .add(b'`')
101 .add(b'\\')
102 .add(b':');
103
104 if cfg!(target_os = "windows") {
105 let mut url = format!(
106 "http://artwork.dioxus.localhost/local?p={}",
107 percent_encoding::utf8_percent_encode(&abs_str, QUERY_VAL)
108 );
109 if let Some(size) = size {
110 url.push_str(&format!("&s={size}"));
111 }
112 Some(cover_url_from_string(url))
113 } else {
114 let mut url = format!(
115 "artwork://local?p={}",
116 percent_encoding::utf8_percent_encode(&abs_str, QUERY_VAL)
117 );
118 if let Some(size) = size {
119 url.push_str(&format!("&s={size}"));
120 }
121 Some(cover_url_from_string(url))
122 }
123}
124
125pub fn format_artwork_url(path: Option<&impl AsRef<Path>>) -> Option<CoverUrl> {
126 format_artwork_url_impl(path, None)
127}
128
129pub fn format_artwork_thumb_url(path: Option<&impl AsRef<Path>>, size: u32) -> Option<CoverUrl> {
130 format_artwork_url_impl(path, Some(size))
131}
132
133pub fn default_cover_url() -> CoverUrl {
134 cover_url_from_string(
135 "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'%3E%3Crect width='400' height='400' fill='%231e1b2e'/%3E%3Ccircle cx='200' cy='180' r='70' fill='none' stroke='%233d3466' stroke-width='6'/%3E%3Cpath d='M155 280 Q200 240 245 280' fill='none' stroke='%233d3466' stroke-width='6' stroke-linecap='round'/%3E%3C/svg%3E".to_string()
136 )
137}