Speed up library search on large libraries
search_library_courses was walking via list_library_directory, which computes a full recursive file count (rglob) and thumbnail lookup for every course at every level - turning a search into an O(every file in every course) scan regardless of how few results actually match. Give search its own lightweight directory-only walk and defer the expensive per-course lookups until after a name match is confirmed, so cost now scales with matches found rather than total library size. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+41
-12
@@ -735,23 +735,52 @@ def get_library_root() -> str:
|
||||
def search_library_courses(dir_path: str, query: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Recursively search the library for courses whose name contains `query`
|
||||
(case-insensitive). Walks via list_library_directory, so it reuses the
|
||||
exact same course/directory detection and hidden-path filtering as
|
||||
normal browsing - a hidden course or folder never shows up in results.
|
||||
(case-insensitive), respecting hidden paths exactly like normal browsing.
|
||||
|
||||
Deliberately doesn't reuse list_library_directory for the walk itself:
|
||||
that function computes media_count (a full recursive file count via
|
||||
rglob) and thumbnail presence for *every* course at each level, which is
|
||||
fine for showing one directory's worth of courses but turns a full-
|
||||
library search into an O(every file in every course) scan. Here, the
|
||||
directory walk only touches directory entries (cheap - iterdir, no file
|
||||
stats), and the expensive per-course lookups only run for the handful of
|
||||
courses whose name actually matches.
|
||||
"""
|
||||
query_lower = query.lower()
|
||||
hidden_set = set(get_hidden_paths())
|
||||
results: List[Dict[str, Any]] = []
|
||||
|
||||
def walk(path: str):
|
||||
level = list_library_directory(path)
|
||||
for item in level['items']:
|
||||
if item['type'] == 'course':
|
||||
if query_lower in item['name'].lower():
|
||||
results.append(item)
|
||||
else:
|
||||
walk(item['path'])
|
||||
def walk(directory: Path):
|
||||
try:
|
||||
entries = sorted(
|
||||
(p for p in directory.iterdir() if p.is_dir() and not p.name.startswith('.')),
|
||||
key=lambda p: p.name.lower()
|
||||
)
|
||||
except (PermissionError, OSError):
|
||||
return
|
||||
|
||||
walk(dir_path)
|
||||
for entry in entries:
|
||||
if os.path.abspath(str(entry)) in hidden_set:
|
||||
continue
|
||||
|
||||
if _looks_like_course(entry):
|
||||
if query_lower in entry.name.lower():
|
||||
media_count = len([
|
||||
f for f in entry.rglob('*')
|
||||
if f.is_file() and f.suffix.lower() in VIDEO_EXTENSIONS | AUDIO_EXTENSIONS
|
||||
])
|
||||
results.append({
|
||||
'type': 'course',
|
||||
'name': entry.name,
|
||||
'path': str(entry),
|
||||
'media_files': media_count,
|
||||
'hidden': False,
|
||||
'has_thumbnail': find_course_thumbnail(str(entry)) is not None
|
||||
})
|
||||
else:
|
||||
walk(entry)
|
||||
|
||||
walk(Path(dir_path))
|
||||
return results
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user