Allow rescanning media folders
Co-Authored-By: codex <[email protected]>
This commit is contained in:
1 parent
0a026209aa
commit
8c0986161a
3 files changed
+62
-1
No files matched your search
+30
@@ -261,6 +261,32 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rescan_selected(&mut self) -> Result<()> {
|
||||
let Some(i) = self.list_state.selected() else {
|
||||
self.status = "Select a library, series, or season to rescan".into();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let target = match self.view {
|
||||
View::Libraries => self
|
||||
.home_entries
|
||||
.get(i)
|
||||
.filter(|item| item.id != RESUME_VIRTUAL_ID)
|
||||
.cloned(),
|
||||
View::Items => self.items.get(i).filter(|item| can_rescan(item)).cloned(),
|
||||
};
|
||||
|
||||
let Some(item) = target else {
|
||||
self.status = "Select a library, series, or season to rescan".into();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.status = format!("Requesting rescan for {}...", item.name);
|
||||
self.client.refresh_item(&item.id).await?;
|
||||
self.status = format!("Rescan started for {}", item.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn back(&mut self) -> Result<()> {
|
||||
if self.view != View::Items {
|
||||
return Ok(());
|
||||
@@ -496,6 +522,10 @@ fn queue_item_from(item: &Item) -> QueueItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn can_rescan(item: &Item) -> bool {
|
||||
item.is_folder && matches!(item.item_type.as_str(), "Series" | "Season")
|
||||
}
|
||||
|
||||
pub(crate) fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> {
|
||||
let mut entries: Vec<Item> = Vec::with_capacity(libraries.len() + 1);
|
||||
if !resume.is_empty() {
|
||||
|
||||
+26
@@ -250,6 +250,32 @@ impl Client {
|
||||
Ok(resp.items)
|
||||
}
|
||||
|
||||
/// Ask Emby to rescan/refresh a library folder or media folder.
|
||||
pub async fn refresh_item(&self, item_id: &str) -> Result<()> {
|
||||
let url = format!("{}/Items/{item_id}/Refresh", self.server);
|
||||
let resp = self
|
||||
.http
|
||||
.post(&url)
|
||||
.headers(self.headers())
|
||||
.query(&[
|
||||
("Recursive", "true"),
|
||||
("ImageRefreshMode", "Default"),
|
||||
("MetadataRefreshMode", "Default"),
|
||||
("ReplaceAllImages", "false"),
|
||||
("ReplaceAllMetadata", "false"),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("POST {url}"))?;
|
||||
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
return Err(anyhow!("refresh failed: HTTP {status}: {body}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stream_url(&self, item_id: &str) -> String {
|
||||
format!(
|
||||
"{}/Videos/{}/stream?Static=true&api_key={}&DeviceId={}",
|
||||
|
||||
@@ -96,6 +96,11 @@ async fn run_loop(term: &mut Term, app: &mut App) -> Result<()> {
|
||||
}
|
||||
}
|
||||
KeyCode::Backspace | KeyCode::Char('h') | KeyCode::Left => app.back().await?,
|
||||
KeyCode::Char('r') => {
|
||||
if let Err(e) = app.rescan_selected().await {
|
||||
app.status = format!("rescan error: {e}");
|
||||
}
|
||||
}
|
||||
KeyCode::Char('s') => settings::run(term, app).await?,
|
||||
_ => {}
|
||||
}
|
||||
@@ -196,7 +201,7 @@ fn draw(f: &mut ratatui::Frame, app: &mut App) {
|
||||
let help = if app.picker.is_some() {
|
||||
"↑/↓ select · Enter confirm · [E]pisode [S]eason [W]hole show · Esc cancel"
|
||||
} else {
|
||||
"↑/↓ move · Enter open/play · Backspace back · s settings · q quit (Q force)"
|
||||
"↑/↓ move · Enter open/play · r rescan · Backspace back · s settings · q quit (Q force)"
|
||||
};
|
||||
let status = Paragraph::new(vec![
|
||||
Line::from(Span::styled(
|
||||
|
||||
Reference in new issue
Block a user