DB-first library reads, mixed library indexing, and manual NFO refresh
- API reads now serve from media_items cache instead of scanning the filesystem on every request; scans (manual or scheduled) remain the write path - NFO metadata is no longer parsed automatically during scans; title falls back to folder/filename — metadata can be refreshed per-item via the kabob menu - Mixed libraries are now indexed in media_items (new mixed_file item type) with file_path stored; scanMixed walks recursively and upserts all files - Added file_path column to media_items and migrated item_type CHECK constraint to include mixed_file via safe table-recreation migration - New POST /api/nfo-refresh endpoint reads the .nfo for a single item and patches its DB row (supports movie, tv_series, tv_episode) - Added "Refresh metadata" button to movie and TV series kabob menus Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ export default function TvView({ libraryId }: Props) {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const [confirming, setConfirming] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [refreshingMeta, setRefreshingMeta] = useState(false)
|
||||
const [doomScrollActive, setDoomScrollActive] = useState(false)
|
||||
const [doomScrollItems, setDoomScrollItems] = useState<DoomScrollItem[]>([])
|
||||
const [doomScrollLoading, setDoomScrollLoading] = useState(false)
|
||||
@@ -149,6 +150,19 @@ export default function TvView({ libraryId }: Props) {
|
||||
.catch(() => setDeleting(false))
|
||||
}
|
||||
|
||||
const handleRefreshSeriesMetadata = () => {
|
||||
if (!selectedSeries) return
|
||||
setRefreshingMeta(true)
|
||||
setMenuOpen(false)
|
||||
const itemKey = `${libraryId}:tv_series:${selectedSeries.id}`
|
||||
fetch(
|
||||
`/api/nfo-refresh?libraryId=${encodeURIComponent(libraryId)}&itemType=tv_series&itemKey=${encodeURIComponent(itemKey)}`,
|
||||
{ method: 'POST' }
|
||||
)
|
||||
.then(() => fetchSeries())
|
||||
.finally(() => setRefreshingMeta(false))
|
||||
}
|
||||
|
||||
const handleDoomScroll = async () => {
|
||||
setDoomScrollLoading(true)
|
||||
try {
|
||||
@@ -415,6 +429,16 @@ export default function TvView({ libraryId }: Props) {
|
||||
className="absolute right-0 top-full mt-1 rounded-lg shadow-lg overflow-hidden z-20 min-w-max"
|
||||
style={{ backgroundColor: 'var(--surface)', border: '1px solid var(--border)' }}
|
||||
>
|
||||
<button
|
||||
onClick={handleRefreshSeriesMetadata}
|
||||
disabled={refreshingMeta}
|
||||
className="flex items-center gap-2 w-full px-4 py-2 text-sm text-left transition-colors disabled:opacity-50"
|
||||
style={{ color: 'var(--text-primary)' }}
|
||||
onMouseEnter={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'var(--border)')}
|
||||
onMouseLeave={(e) => ((e.currentTarget as HTMLElement).style.backgroundColor = 'transparent')}
|
||||
>
|
||||
{refreshingMeta ? 'Refreshing…' : 'Refresh metadata'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setMenuOpen(false); setConfirming(true) }}
|
||||
className="flex items-center gap-2 w-full px-4 py-2 text-sm text-left transition-colors"
|
||||
|
||||
Reference in New Issue
Block a user