Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 25 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,32 @@ 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()
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 "",
)
)
)

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