Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4381750c6e |
Generated
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user