Exclude media-free subtrees from the destination picker too
The previous leaf-item fix only caught a single stray file with no subfolders - it missed a course whose bundled extras (downloaded source code, a Python virtualenv/dependency dump, project assets) have plenty of subfolders but no video/audio anywhere in them, which still left the course itself, and every one of those subfolders, selectable as destinations. Add a subtree-wide media check (bounded depth) so a folder with subfolders but no media anywhere beneath it is excluded the same way, while a folder that still has a real course's media somewhere inside it - or is simply empty, e.g. a freshly-created category - stays pickable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,12 +107,15 @@ silently stay blank instead of erroring.
|
|||||||
dropdown and typing just the new folder's own name, with a live "Will
|
dropdown and typing just the new folder's own name, with a live "Will
|
||||||
create: X/Y" preview so the resulting path is confirmed before applying -
|
create: X/Y" preview so the resulting path is confirmed before applying -
|
||||||
the same picker Manage Library's Move action uses. The picker excludes
|
the same picker Manage Library's Move action uses. The picker excludes
|
||||||
actual course/item folders, not just organizational ones - including a
|
actual course/item folders, not just organizational ones: a leaf folder
|
||||||
leaf folder holding a single non-video/audio file (an ebook, an
|
holding a single non-video/audio file (an ebook, an audiobook in a
|
||||||
audiobook in a format this app doesn't play, etc.) that Library
|
format this app doesn't play, etc.), and a folder whose whole subtree
|
||||||
browsing's own course detection wouldn't catch either, since there's no
|
has no video/audio anywhere in it at all (a course's bundled source
|
||||||
video/audio file to key off. Nothing on disk moves until you review and
|
code, a Python virtualenv, project assets, ...) even though it has
|
||||||
hit Apply. Matching ignores common noise (e-learning
|
plenty of subfolders - both cases Library browsing's own course
|
||||||
|
detection wouldn't catch either, since there's no video/audio file to
|
||||||
|
key off. Nothing on disk moves until you review and hit Apply. Matching
|
||||||
|
ignores common noise (e-learning
|
||||||
platform names, release/distribution-group tags, dates) via a stopword
|
platform names, release/distribution-group tags, dates) via a stopword
|
||||||
list, and beyond that treats a match against a folder's own deliberate
|
list, and beyond that treats a match against a folder's own deliberate
|
||||||
name as always stronger evidence than a word only borrowed from a
|
name as always stronger evidence than a word only borrowed from a
|
||||||
|
|||||||
+46
-7
@@ -1286,6 +1286,44 @@ def _is_unrecognized_leaf_item(directory: Path) -> bool:
|
|||||||
return any(e.is_file() and not e.name.startswith('.') for e in entries)
|
return any(e.is_file() and not e.name.startswith('.') for e in entries)
|
||||||
|
|
||||||
|
|
||||||
|
def _subtree_has_any_media(directory: Path, max_depth: int = 8) -> bool:
|
||||||
|
"""Whether `directory` or anything under it (bounded depth, so a huge
|
||||||
|
non-course tree can't make a scan slow) contains at least one
|
||||||
|
recognized video/audio file anywhere. False for a folder that's really
|
||||||
|
a course's bundled extras - downloaded source code, a Python
|
||||||
|
virtualenv/dependency dump, a project's asset tree, etc. - which can
|
||||||
|
have plenty of subfolders (so _is_unrecognized_leaf_item doesn't catch
|
||||||
|
it) but no lecture content anywhere in them."""
|
||||||
|
try:
|
||||||
|
entries = list(directory.iterdir())
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
return False
|
||||||
|
for e in entries:
|
||||||
|
if e.is_file() and not e.name.startswith('.') and e.suffix.lower() in VIDEO_EXTENSIONS | AUDIO_EXTENSIONS:
|
||||||
|
return True
|
||||||
|
if max_depth <= 0:
|
||||||
|
return False
|
||||||
|
for e in entries:
|
||||||
|
if e.is_dir() and not e.name.startswith('.') and _subtree_has_any_media(e, max_depth - 1):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _is_media_free_subtree(directory: Path) -> bool:
|
||||||
|
"""A folder that has subfolders (so it isn't just an intentionally
|
||||||
|
empty, freshly-created category) but no video/audio anywhere beneath
|
||||||
|
it - excluded from the destination picker for the same reason as
|
||||||
|
_is_unrecognized_leaf_item, just for a deeper non-course tree instead
|
||||||
|
of a single stray file."""
|
||||||
|
try:
|
||||||
|
has_subdir = any(e.is_dir() and not e.name.startswith('.') for e in directory.iterdir())
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
return False
|
||||||
|
if not has_subdir:
|
||||||
|
return False
|
||||||
|
return not _subtree_has_any_media(directory)
|
||||||
|
|
||||||
|
|
||||||
def _build_category_index() -> List[Dict[str, Any]]:
|
def _build_category_index() -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Every category/subcategory folder currently in the library (e.g. IT,
|
Every category/subcategory folder currently in the library (e.g. IT,
|
||||||
@@ -1305,13 +1343,14 @@ def _build_category_index() -> List[Dict[str, Any]]:
|
|||||||
categories' course titles) can't outweigh a specific match
|
categories' course titles) can't outweigh a specific match
|
||||||
elsewhere just by sharing more of it.
|
elsewhere just by sharing more of it.
|
||||||
Stops recursing into a folder once it reads as a course itself
|
Stops recursing into a folder once it reads as a course itself
|
||||||
(_looks_like_course, the same rule the Library browser uses) or as an
|
(_looks_like_course, the same rule the Library browser uses), an
|
||||||
unrecognized leaf item (_is_unrecognized_leaf_item - an ebook/
|
unrecognized leaf item (_is_unrecognized_leaf_item - an ebook/
|
||||||
audiobook/misc file that isn't video or audio, so _looks_like_course
|
audiobook/misc file that isn't video or audio), or a subtree with no
|
||||||
doesn't catch it either), so individual courses and standalone files
|
video/audio anywhere in it at all (_is_media_free_subtree - a course's
|
||||||
never show up as if they were categories to file things under. The
|
bundled source code/project files, a Python virtualenv, etc.), so
|
||||||
Unsorted folder itself is excluded - it's the source, never a valid
|
individual courses and their non-lecture contents never show up as if
|
||||||
destination.
|
they were categories to file things under. The Unsorted folder itself
|
||||||
|
is excluded - it's the source, never a valid destination.
|
||||||
"""
|
"""
|
||||||
library_root = Path(get_library_root())
|
library_root = Path(get_library_root())
|
||||||
try:
|
try:
|
||||||
@@ -1334,7 +1373,7 @@ def _build_category_index() -> List[Dict[str, Any]]:
|
|||||||
continue
|
continue
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if _looks_like_course(entry) or _is_unrecognized_leaf_item(entry):
|
if _looks_like_course(entry) or _is_unrecognized_leaf_item(entry) or _is_media_free_subtree(entry):
|
||||||
continue
|
continue
|
||||||
new_parts = path_parts + [entry.name]
|
new_parts = path_parts + [entry.name]
|
||||||
path_tokens: Set[str] = set()
|
path_tokens: Set[str] = set()
|
||||||
|
|||||||
Reference in New Issue
Block a user