Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default function FolderTreeView() {
isDir: false,
size: 0,
lastModified: 0,
isBrokenSymlink: false,
isLoaded: false,
loading: false,
isExpanded: false,
Expand All @@ -85,6 +86,8 @@ export default function FolderTreeView() {
isDir: node.isDir,
size: node.size,
lastModified: node.lastModified,
isBrokenSymlink: node.isBrokenSymlink,
symlinkTarget: node.symlinkTarget,
isLoaded: node.isLoaded,
loading: node.loading,
isExpanded: node.isExpanded,
Expand Down
21 changes: 19 additions & 2 deletions smoosense-gui/src/components/folder-browser/TreeNodeComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface ArboristNodeData {
isDir: boolean
size: number
lastModified: number
isBrokenSymlink: boolean
symlinkTarget?: string
isLoaded: boolean
loading: boolean
isExpanded: boolean
Expand Down Expand Up @@ -114,6 +116,10 @@ export default function TreeNodeComponent({ node, style }: TreeNodeComponentProp
}, [nodeData.path, dispatch])

const renderIcon = () => {
if (nodeData.isBrokenSymlink) {
return ICONS.BROKEN_SYMLINK
}

if (nodeData.isDir) {
return node.isOpen ? ICONS.FOLDER_OPEN : ICONS.FOLDER_CLOSED
}
Expand Down Expand Up @@ -228,8 +234,19 @@ export default function TreeNodeComponent({ node, style }: TreeNodeComponentProp
>
{renderExpandIcon()}
{renderIcon()}
<span className="truncate flex-1" title={nodeData.name}>
{nodeData.name}
<span
className={cn(
"truncate flex-1",
nodeData.isBrokenSymlink && "text-red-500",
nodeData.symlinkTarget && !nodeData.isBrokenSymlink && "text-blue-500",
)}
title={nodeData.symlinkTarget
? `${nodeData.name} (→ ${nodeData.symlinkTarget})${nodeData.isBrokenSymlink ? ' (broken symlink)' : ''}`
: nodeData.name}
>
{nodeData.symlinkTarget
? `${nodeData.name} (→ ${nodeData.symlinkTarget})`
: nodeData.name}
</span>

{/* File info display */}
Expand Down
6 changes: 6 additions & 0 deletions smoosense-gui/src/lib/features/folderTree/folderTreeSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface FSItem {
size: number
lastModified: number
isDir: boolean
isBrokenSymlink?: boolean
symlinkTarget?: string
}

export type SortBy = 'name' | 'size' | 'modified'
Expand All @@ -22,6 +24,8 @@ export interface TreeNode {
isDir: boolean
size: number
lastModified: number
isBrokenSymlink: boolean
symlinkTarget: string
children?: TreeNode[]
childrenTotal: number // 0 = unknown/not loaded yet
isLoaded: boolean
Expand Down Expand Up @@ -107,6 +111,8 @@ function createTreeNode(item: FSItem, parentPath: string): TreeNode {
isDir: item.isDir,
size: item.size,
lastModified: item.lastModified,
isBrokenSymlink: item.isBrokenSymlink ?? false,
symlinkTarget: item.symlinkTarget ?? '',
children: item.isDir ? [] : undefined,
childrenTotal: 0,
isLoaded: false,
Expand Down
2 changes: 2 additions & 0 deletions smoosense-gui/src/lib/utils/iconUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Folder,
FolderOpen,
File,
FileX,
Braces,
Columns3,
Rows3,
Expand Down Expand Up @@ -29,6 +30,7 @@ export const ICONS = {
VIDEO: <Video className={CLS_ICON} />,
TEXT: <Type className={CLS_ICON} />,
FILE_DEFAULT: <File className={CLS_ICON} />,
BROKEN_SYMLINK: <FileX className={`${CLS_ICON} text-red-500`} />,

// Tree navigation
CHEVRON_DOWN: <ChevronDown className={CLS_ICON} />,
Expand Down
31 changes: 24 additions & 7 deletions smoosense-py/smoosense/utils/local_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,31 @@ def list_one_level(
for entry in os.scandir(path):
if entry.name.startswith(".") and not show_hidden:
continue
all_items.append(
FSItem(
name=entry.name,
size=entry.stat().st_size,
lastModified=int(1000 * entry.stat().st_mtime),
isDir=entry.is_dir(),
try:
stat = entry.stat()
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)
)
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your version here is a bit too much based on the vibe-coded version I showcased in this comment.

I'd reduce the scope of the try statement to just the entry.stat() to both make intent clearer as well as don't unnecessarily catch other exceptions that shouldn't be caught.

Also, I'd personally always rather see what exceptions happened and therefore include the stack trace (either through exc_info or by using logger.exception). But that is for @slinjhu to decide 😄

Suggested change
try:
stat = entry.stat()
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)
)
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.warning(f"Failed to stat entry: {entry.path}")
try:
stat = entry.stat()
except OSError:
if entry.is_symlink():
all_items.append(
FSItem(
name=entry.name,
size=0,
lastModified=0,
isDir=False,
isBrokenSymlink=True,
symlinkTarget=os.readlink(entry.path),
)
)
else:
logger.exception(f"Failed to stat entry: {entry.path}")
else:
all_items.append(
FSItem(
name=entry.name,
size=stat.st_size,
lastModified=int(1000 * stat.st_mtime),
isDir=entry.is_dir(),
symlinkTarget=os.readlink(entry.path) if entry.is_symlink() else "",
)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. In general it is better to keep try-catch scope smaller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I applied the suggestion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change logger.warning to logger.exception so we can see full traceback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to exception 🙂


if pattern:
all_items = [
Expand Down
2 changes: 2 additions & 0 deletions smoosense-py/smoosense/utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class FSItem(ImmutableBaseModel):
size: int
lastModified: int
isDir: bool
isBrokenSymlink: bool = False
symlinkTarget: str = ""


SortBy = Literal["name", "size", "modified"]
Expand Down
Loading