Fix sleep bug, fix menu return
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m1s
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Successful in 4m7s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m5s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 19m12s
Build & Release / Create Release (push) Successful in 8s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m1s
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Successful in 4m7s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m5s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 19m12s
Build & Release / Create Release (push) Successful in 8s
This commit is contained in:
@@ -1029,6 +1029,7 @@ mod tests {
|
||||
genres: Some(vec!["Action".to_string(), "Adventure".to_string()]),
|
||||
runtime_ticks: Some(7200000000),
|
||||
production_year: Some(2024),
|
||||
premiere_date: None,
|
||||
community_rating: Some(8.5),
|
||||
official_rating: Some("PG-13".to_string()),
|
||||
primary_image_tag: Some("image-tag-123".to_string()),
|
||||
|
||||
@@ -39,6 +39,7 @@ impl OfflineRepository {
|
||||
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok()),
|
||||
runtime_ticks: item.runtime_ticks,
|
||||
production_year: item.production_year,
|
||||
premiere_date: item.premiere_date,
|
||||
community_rating: item.community_rating,
|
||||
official_rating: item.official_rating,
|
||||
primary_image_tag: item.primary_image_tag,
|
||||
@@ -101,6 +102,7 @@ struct CachedItem {
|
||||
genres: Option<String>,
|
||||
runtime_ticks: Option<i64>,
|
||||
production_year: Option<i32>,
|
||||
premiere_date: Option<String>,
|
||||
community_rating: Option<f64>,
|
||||
official_rating: Option<String>,
|
||||
primary_image_tag: Option<String>,
|
||||
@@ -145,8 +147,9 @@ fn row_to_cached_item(row: &rusqlite::Row) -> rusqlite::Result<CachedItem> {
|
||||
season_id: row.get(20)?,
|
||||
season_name: row.get(21)?,
|
||||
parent_index_number: row.get(22)?,
|
||||
// Appended as the final column in every SELECT that maps through this fn.
|
||||
// Appended as the final columns in every SELECT that maps through this fn.
|
||||
is_folder: row.get::<_, Option<i64>>(23)?.unwrap_or(0) != 0,
|
||||
premiere_date: row.get(24)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -238,7 +241,7 @@ impl OfflineRepository {
|
||||
genres, series_id, series_name,
|
||||
season_id, season_name, index_number, parent_index_number,
|
||||
album_id, album_name, album_artist, artists,
|
||||
production_year, runtime_ticks,
|
||||
production_year, premiere_date, runtime_ticks,
|
||||
primary_image_tag, backdrop_image_tags,
|
||||
community_rating, official_rating,
|
||||
synced_at
|
||||
@@ -248,10 +251,10 @@ impl OfflineRepository {
|
||||
?9, ?10, ?11,
|
||||
?12, ?13, ?14, ?15,
|
||||
?16, ?17, ?18, ?19,
|
||||
?20, ?21,
|
||||
?22, ?23,
|
||||
?24, ?25,
|
||||
?26
|
||||
?20, ?21, ?22,
|
||||
?23, ?24,
|
||||
?25, ?26,
|
||||
?27
|
||||
)",
|
||||
vec![
|
||||
QueryParam::String(item.id.clone()),
|
||||
@@ -318,6 +321,10 @@ impl OfflineRepository {
|
||||
Some(y) => QueryParam::Int(y),
|
||||
None => QueryParam::Null,
|
||||
},
|
||||
match &item.premiere_date {
|
||||
Some(d) => QueryParam::String(d.clone()),
|
||||
None => QueryParam::Null,
|
||||
},
|
||||
match item.runtime_ticks {
|
||||
Some(r) => QueryParam::Int64(r),
|
||||
None => QueryParam::Null,
|
||||
@@ -446,6 +453,13 @@ impl MediaRepository for OfflineRepository {
|
||||
let limit = opts.limit.unwrap_or(10000); // Match frontend limit for full library loading
|
||||
let start_index = opts.start_index.unwrap_or(0);
|
||||
|
||||
// SortBy=Random is the only sort the landing pages rely on offline (the
|
||||
// hero "surprise" pool); everything else keeps the stable name order.
|
||||
let order_by = match opts.sort_by.as_deref() {
|
||||
Some("Random") => "RANDOM()",
|
||||
_ => "i.sort_name ASC, i.name ASC",
|
||||
};
|
||||
|
||||
// Build type filter for optional filtering
|
||||
let type_filter = if let Some(include_item_types) = &opts.include_item_types {
|
||||
if !include_item_types.is_empty() {
|
||||
@@ -496,13 +510,13 @@ impl MediaRepository for OfflineRepository {
|
||||
i.runtime_ticks, i.production_year, i.community_rating, i.official_rating,
|
||||
i.primary_image_tag, i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id, i.season_name,
|
||||
i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
INNER JOIN available_items ai ON i.id = ai.id
|
||||
WHERE i.server_id = ? AND i.parent_id = ?{}
|
||||
ORDER BY i.sort_name ASC, i.name ASC
|
||||
ORDER BY {}
|
||||
LIMIT {} OFFSET {}",
|
||||
type_filter, limit, start_index
|
||||
type_filter, order_by, limit, start_index
|
||||
);
|
||||
|
||||
let query = Query::with_params(
|
||||
@@ -561,8 +575,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.runtime_ticks, i.production_year, i.community_rating, i.official_rating,
|
||||
i.primary_image_tag, i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id, i.season_name,
|
||||
i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
INNER JOIN downloaded_items di ON i.id = di.id
|
||||
WHERE i.id = ?",
|
||||
vec![QueryParam::String(item_id.to_string())],
|
||||
@@ -606,8 +620,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.runtime_ticks, i.production_year, i.community_rating, i.official_rating,
|
||||
i.primary_image_tag, i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id, i.season_name,
|
||||
i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
INNER JOIN downloaded_items di ON i.id = di.id
|
||||
WHERE i.server_id = ? AND i.library_id = ?
|
||||
ORDER BY i.synced_at DESC
|
||||
@@ -644,8 +658,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
JOIN user_data ud ON i.id = ud.item_id
|
||||
INNER JOIN downloads d ON i.id = d.item_id
|
||||
WHERE i.server_id = ? AND ud.user_id = ? AND i.library_id = ?
|
||||
@@ -669,8 +683,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
JOIN user_data ud ON i.id = ud.item_id
|
||||
INNER JOIN downloads d ON i.id = d.item_id
|
||||
WHERE i.server_id = ? AND ud.user_id = ?
|
||||
@@ -757,8 +771,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM ranked_plays rp
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM ranked_plays rp
|
||||
JOIN items i ON rp.display_id = i.id
|
||||
INNER JOIN downloaded_items di ON i.id = di.id
|
||||
ORDER BY rp.most_recent_play DESC",
|
||||
@@ -805,8 +819,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
JOIN user_data ud ON i.id = ud.item_id
|
||||
INNER JOIN downloads d ON i.id = d.item_id
|
||||
WHERE i.server_id = ? AND ud.user_id = ? AND i.item_type = 'Movie'
|
||||
@@ -939,8 +953,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
JOIN items_fts fts ON fts.rowid = i.rowid
|
||||
INNER JOIN downloaded_items di ON i.id = di.id
|
||||
WHERE i.server_id = ? AND items_fts MATCH ?{}
|
||||
@@ -1099,6 +1113,7 @@ impl MediaRepository for OfflineRepository {
|
||||
genres: None,
|
||||
runtime_ticks: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
primary_image_tag: person_data.3,
|
||||
@@ -1152,8 +1167,8 @@ impl MediaRepository for OfflineRepository {
|
||||
i.community_rating, i.official_rating, i.primary_image_tag,
|
||||
i.album_id, i.album_name, i.album_artist, i.artists,
|
||||
i.index_number, i.series_id, i.series_name, i.season_id,
|
||||
i.season_name, i.parent_index_number, i.is_folder
|
||||
FROM items i
|
||||
i.season_name, i.parent_index_number, i.is_folder, i.premiere_date
|
||||
FROM items i
|
||||
JOIN item_people ip ON i.id = ip.item_id
|
||||
INNER JOIN downloaded_items di ON i.id = di.id
|
||||
WHERE i.server_id = ? AND ip.person_id = ?
|
||||
@@ -1263,7 +1278,7 @@ impl MediaRepository for OfflineRepository {
|
||||
i.runtime_ticks, i.production_year, i.community_rating, i.official_rating, \
|
||||
i.primary_image_tag, i.album_id, i.album_name, i.album_artist, i.artists, \
|
||||
i.index_number, i.series_id, i.series_name, i.season_id, i.season_name, \
|
||||
i.parent_index_number, i.is_folder \
|
||||
i.parent_index_number, i.is_folder, i.premiere_date \
|
||||
FROM playlist_items pi \
|
||||
JOIN items i ON pi.item_id = i.id \
|
||||
WHERE pi.playlist_id = ? \
|
||||
@@ -1302,6 +1317,7 @@ impl MediaRepository for OfflineRepository {
|
||||
season_name: row.get(22)?,
|
||||
parent_index_number: row.get(23)?,
|
||||
is_folder: row.get::<_, Option<i64>>(24)?.unwrap_or(0) != 0,
|
||||
premiere_date: row.get(25)?,
|
||||
};
|
||||
Ok((entry_id.to_string(), cached))
|
||||
})
|
||||
@@ -1473,6 +1489,7 @@ mod tests {
|
||||
genres TEXT,
|
||||
runtime_ticks INTEGER,
|
||||
production_year INTEGER,
|
||||
premiere_date TEXT,
|
||||
community_rating REAL,
|
||||
official_rating TEXT,
|
||||
primary_image_tag TEXT,
|
||||
@@ -1549,6 +1566,7 @@ mod tests {
|
||||
genres: None,
|
||||
runtime_ticks: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
primary_image_tag: None,
|
||||
|
||||
@@ -407,6 +407,7 @@ struct JellyfinItem {
|
||||
overview: Option<String>,
|
||||
genres: Option<Vec<String>>,
|
||||
production_year: Option<i32>,
|
||||
premiere_date: Option<String>,
|
||||
community_rating: Option<f64>,
|
||||
official_rating: Option<String>,
|
||||
run_time_ticks: Option<i64>,
|
||||
@@ -497,6 +498,7 @@ impl JellyfinItem {
|
||||
overview: self.overview,
|
||||
genres: self.genres,
|
||||
production_year: self.production_year,
|
||||
premiere_date: self.premiere_date,
|
||||
community_rating: self.community_rating,
|
||||
official_rating: self.official_rating,
|
||||
runtime_ticks: self.run_time_ticks,
|
||||
@@ -624,7 +626,7 @@ impl MediaRepository for OnlineRepository {
|
||||
// Request image fields for list views (People only needed in get_item
|
||||
// detail view). Genres is needed so cached items carry their genres,
|
||||
// which lets the offline store derive genre lists + per-genre counts.
|
||||
endpoint.push_str("&Fields=BackdropImageTags,ParentBackdropImageTags,Genres");
|
||||
endpoint.push_str("&Fields=BackdropImageTags,ParentBackdropImageTags,Genres,PremiereDate");
|
||||
|
||||
let response: ItemsResponse = self.get_json(&endpoint).await?;
|
||||
|
||||
@@ -639,7 +641,7 @@ impl MediaRepository for OnlineRepository {
|
||||
}
|
||||
|
||||
async fn get_item(&self, item_id: &str) -> Result<MediaItem, RepoError> {
|
||||
let endpoint = format!("/Users/{}/Items/{}?Fields=BackdropImageTags,ParentBackdropImageTags,People,MediaStreams,MediaSources", self.user_id, item_id);
|
||||
let endpoint = format!("/Users/{}/Items/{}?Fields=BackdropImageTags,ParentBackdropImageTags,People,MediaStreams,MediaSources,PremiereDate", self.user_id, item_id);
|
||||
|
||||
let item: JellyfinItem = self.get_json(&endpoint).await?;
|
||||
let media_item = item.to_media_item(self.user_id.clone());
|
||||
@@ -776,6 +778,7 @@ impl MediaRepository for OnlineRepository {
|
||||
overview: None,
|
||||
genres: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
runtime_ticks: None,
|
||||
@@ -943,7 +946,7 @@ impl MediaRepository for OnlineRepository {
|
||||
|
||||
// Request image fields for list views (plus Genres so cached items
|
||||
// carry genres for offline genre lists/counts).
|
||||
endpoint.push_str("&Fields=BackdropImageTags,ParentBackdropImageTags,Genres");
|
||||
endpoint.push_str("&Fields=BackdropImageTags,ParentBackdropImageTags,Genres,PremiereDate");
|
||||
|
||||
let response: ItemsResponse = self.get_json(&endpoint).await?;
|
||||
Ok(SearchResult {
|
||||
|
||||
@@ -119,6 +119,10 @@ pub struct MediaItem {
|
||||
pub genres: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub production_year: Option<i32>,
|
||||
/// ISO-8601 release/air date (Jellyfin `PremiereDate`). Used to sort
|
||||
/// podcast episodes by release date.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub premiere_date: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub community_rating: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -506,6 +510,7 @@ mod tests {
|
||||
overview: None,
|
||||
genres: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
runtime_ticks: None,
|
||||
@@ -646,6 +651,7 @@ mod tests {
|
||||
overview: None,
|
||||
genres: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
runtime_ticks: None,
|
||||
@@ -706,6 +712,7 @@ mod tests {
|
||||
overview: None,
|
||||
genres: None,
|
||||
production_year: None,
|
||||
premiere_date: None,
|
||||
community_rating: None,
|
||||
official_rating: None,
|
||||
runtime_ticks: None,
|
||||
|
||||
Reference in New Issue
Block a user