Compare commits

..
1 Commits
Author SHA1 Message Date
Ultradesu 4381750c6e Extend DHT scheme
Build and Publish / Build and Publish Docker Image (push) Successful in 5m5s
2026-07-20 16:29:21 +03:00
5 changed files with 62 additions and 20 deletions
Generated
+2 -2
View File
@@ -1717,7 +1717,7 @@ dependencies = [
[[package]] [[package]]
name = "federation-net" name = "federation-net"
version = "0.1.0" version = "0.1.0"
source = "git+https://gt.hexor.cy/ab/frid.git#3f15e9aebd574f7dd922e623e724eb7fde741b59" source = "git+https://gt.hexor.cy/ab/frid.git#a897737978d476c223b66e65b960b70376084f2a"
dependencies = [ dependencies = [
"blake3", "blake3",
"data-encoding", "data-encoding",
@@ -3621,7 +3621,7 @@ dependencies = [
[[package]] [[package]]
name = "music-dht" name = "music-dht"
version = "0.1.0" version = "0.1.0"
source = "git+https://gt.hexor.cy/ab/frid.git#3f15e9aebd574f7dd922e623e724eb7fde741b59" source = "git+https://gt.hexor.cy/ab/frid.git#a897737978d476c223b66e65b960b70376084f2a"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"blake3", "blake3",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "furumusic" name = "furumusic"
version = "0.6.2-fd" version = "0.6.3-fd"
edition = "2024" edition = "2024"
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL" description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
+27 -8
View File
@@ -355,8 +355,12 @@ impl Federation {
kind: ItemKind::Artist, kind: ItemKind::Artist,
name: row.get(1), name: row.get(1),
artist_names: Vec::new(), artist_names: Vec::new(),
featured_artist_names: Vec::new(),
year: None, year: None,
release_type: None, release_type: None,
release_title: None,
track_number: None,
disc_number: None,
duration_seconds: None, duration_seconds: None,
}); });
} }
@@ -389,29 +393,40 @@ impl Federation {
kind: ItemKind::Release, kind: ItemKind::Release,
name: row.get(1), name: row.get(1),
artist_names: artists_of_release.remove(&id).unwrap_or_default(), artist_names: artists_of_release.remove(&id).unwrap_or_default(),
featured_artist_names: Vec::new(),
year: row.get(2), year: row.get(2),
release_type: row.get(3), release_type: row.get(3),
release_title: None,
track_number: None,
disc_number: None,
duration_seconds: None, duration_seconds: None,
}); });
} }
let track_artists = sqlx::query( let track_artists = sqlx::query(
"SELECT ta.track_id, a.name FROM furumusic__track_artist ta "SELECT ta.track_id, a.name, ta.role FROM furumusic__track_artist ta
JOIN furumusic__artist a ON a.id = ta.artist_id JOIN furumusic__artist a ON a.id = ta.artist_id
WHERE ta.role IN ('main', 'featuring') WHERE ta.role IN ('main', 'featuring')
ORDER BY ta.track_id, ta.position", ORDER BY ta.track_id,
CASE ta.role WHEN 'main' THEN 0 ELSE 1 END,
ta.position",
) )
.fetch_all(&pool) .fetch_all(&pool)
.await?; .await?;
let mut artists_of_track: std::collections::HashMap<i64, Vec<String>> = Default::default(); let mut artists_of_track: std::collections::HashMap<i64, Vec<String>> = Default::default();
let mut featured_of_track: std::collections::HashMap<i64, Vec<String>> = Default::default();
for row in &track_artists { for row in &track_artists {
artists_of_track let id: i64 = row.get(0);
.entry(row.get(0)) let name: String = row.get(1);
.or_default() if row.get::<String, _>(2) == "featuring" {
.push(row.get(1)); featured_of_track.entry(id).or_default().push(name);
} else {
artists_of_track.entry(id).or_default().push(name);
}
} }
let tracks = sqlx::query( let tracks = sqlx::query(
"SELECT t.id, t.title, COALESCE(t.year, r.year), t.duration_seconds "SELECT t.id, t.title, COALESCE(t.year, r.year), t.duration_seconds,
r.title, r.release_type, t.track_number, t.disc_number
FROM furumusic__track t FROM furumusic__track t
JOIN furumusic__release r ON r.id = t.release_id JOIN furumusic__release r ON r.id = t.release_id
WHERE t.is_hidden = false AND r.is_hidden = false", WHERE t.is_hidden = false AND r.is_hidden = false",
@@ -426,8 +441,12 @@ impl Federation {
kind: ItemKind::Track, kind: ItemKind::Track,
name: row.get(1), name: row.get(1),
artist_names: artists_of_track.remove(&id).unwrap_or_default(), artist_names: artists_of_track.remove(&id).unwrap_or_default(),
featured_artist_names: featured_of_track.remove(&id).unwrap_or_default(),
year: row.get(2), year: row.get(2),
release_type: None, release_type: row.get(5),
release_title: Some(row.get(4)),
track_number: row.get(6),
disc_number: row.get(7),
duration_seconds: (duration > 0.0).then_some(duration), duration_seconds: (duration > 0.0).then_some(duration),
}); });
} }
+31 -8
View File
@@ -150,7 +150,10 @@ async fn read_line<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Vec<u8>> {
} }
} }
async fn write_line<W: AsyncWriteExt + Unpin>(writer: &mut W, value: &impl Serialize) -> Result<()> { async fn write_line<W: AsyncWriteExt + Unpin>(
writer: &mut W,
value: &impl Serialize,
) -> Result<()> {
let mut line = serde_json::to_vec(value)?; let mut line = serde_json::to_vec(value)?;
line.push(b'\n'); line.push(b'\n');
writer.write_all(&line).await?; writer.write_all(&line).await?;
@@ -186,7 +189,10 @@ fn guess_mime(path: &Path) -> &'static str {
} }
/// Reads an image media file from disk, bounded by [`MAX_IMAGE_BYTES`]. /// Reads an image media file from disk, bounded by [`MAX_IMAGE_BYTES`].
async fn read_image(storage_dir: &str, media: Option<(String, String)>) -> Option<(Vec<u8>, String)> { async fn read_image(
storage_dir: &str,
media: Option<(String, String)>,
) -> Option<(Vec<u8>, String)> {
let (file_path, mime) = media?; let (file_path, mime) = media?;
let path = resolve_media_path(storage_dir, &file_path); let path = resolve_media_path(storage_dir, &file_path);
let size = tokio::fs::metadata(&path).await.ok()?.len(); let size = tokio::fs::metadata(&path).await.ok()?.len();
@@ -367,7 +373,9 @@ async fn serve_audio_one(
Some(item_id) => match resolve_track_id(&pool, &own, item_id).await { Some(item_id) => match resolve_track_id(&pool, &own, item_id).await {
Ok(Some(track_id)) => track_id, Ok(Some(track_id)) => track_id,
Ok(None) => return refuse_audio(stream, "track not found in the library").await, Ok(None) => return refuse_audio(stream, "track not found in the library").await,
Err(err) => return refuse_audio(stream, &format!("library lookup failed: {err:#}")).await, Err(err) => {
return refuse_audio(stream, &format!("library lookup failed: {err:#}")).await;
}
}, },
None => return refuse_audio(stream, "malformed item_id").await, None => return refuse_audio(stream, "malformed item_id").await,
}; };
@@ -378,7 +386,9 @@ async fn serve_audio_one(
let path = resolve_media_path(&storage_dir, &file_path); let path = resolve_media_path(&storage_dir, &file_path);
let mut file = match tokio::fs::File::open(&path).await { let mut file = match tokio::fs::File::open(&path).await {
Ok(file) => file, Ok(file) => file,
Err(err) => return refuse_audio(stream, &format!("audio file is not readable: {err}")).await, Err(err) => {
return refuse_audio(stream, &format!("audio file is not readable: {err}")).await;
}
}; };
let total_size = file.metadata().await?.len(); let total_size = file.metadata().await?.len();
let offset = request.offset.min(total_size); let offset = request.offset.min(total_size);
@@ -395,10 +405,17 @@ async fn serve_audio_one(
}; };
let (cover, artist_image) = if request.want_cover { let (cover, artist_image) = if request.want_cover {
( (
read_image(&storage_dir, track_cover_file(&pool, track_id).await.ok().flatten()).await,
read_image( read_image(
&storage_dir, &storage_dir,
track_artist_image_file(&pool, track_id).await.ok().flatten(), track_cover_file(&pool, track_id).await.ok().flatten(),
)
.await,
read_image(
&storage_dir,
track_artist_image_file(&pool, track_id)
.await
.ok()
.flatten(),
) )
.await, .await,
) )
@@ -511,7 +528,10 @@ async fn serve_catalog_one(
artist: None, artist: None,
}, },
}; };
stream.send.write_all(&serde_json::to_vec(&response)?).await?; stream
.send
.write_all(&serde_json::to_vec(&response)?)
.await?;
} }
Some(want @ ("artist_image" | "release_cover")) => { Some(want @ ("artist_image" | "release_cover")) => {
let media = if want == "release_cover" { let media = if want == "release_cover" {
@@ -549,7 +569,10 @@ async fn serve_catalog_one(
error: Some(format!("unknown request kind '{other}'")), error: Some(format!("unknown request kind '{other}'")),
artist: None, artist: None,
}; };
stream.send.write_all(&serde_json::to_vec(&response)?).await?; stream
.send
.write_all(&serde_json::to_vec(&response)?)
.await?;
} }
} }
stream.send.finish()?; stream.send.finish()?;
+1 -1
View File
@@ -3,6 +3,7 @@ mod agent;
mod api; mod api;
mod auth; mod auth;
mod config; mod config;
mod federation;
mod i18n; mod i18n;
mod jobs; mod jobs;
mod lastfm; mod lastfm;
@@ -12,7 +13,6 @@ mod music;
mod oidc; mod oidc;
mod player; mod player;
mod scheduler; mod scheduler;
mod federation;
mod torrents; mod torrents;
mod user; mod user;