1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 12:41:46 +00:00

feat: show scanning progress as percentage in disk analyzer

- Implemented progress percentage display (e.g., `(45%)`) in `cmd/analyze` to show scanning status based on cached total files.
- Kept the UI clean by avoiding a full progress bar.
- fix: formatting improvements in `bin/touchid.sh`.
This commit is contained in:
Tw93
2026-01-09 14:16:29 +08:00
parent bb4561e682
commit 2b5dd3f44c
5 changed files with 89 additions and 26 deletions

View File

@@ -85,17 +85,17 @@ enable_touchid() {
if grep -q "sudo_local" "$PAM_SUDO_FILE"; then
# Check if already correctly configured in sudo_local
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]] && grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
# It is in sudo_local, but let's check if it's ALSO in sudo (incomplete migration)
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
# Clean up legacy config
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
echo -e "${GREEN}${ICON_SUCCESS} Cleanup legacy configuration${NC}"
fi
fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled${NC}"
return 0
# It is in sudo_local, but let's check if it's ALSO in sudo (incomplete migration)
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
# Clean up legacy config
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
echo -e "${GREEN}${ICON_SUCCESS} Cleanup legacy configuration${NC}"
fi
fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled${NC}"
return 0
fi
# Not configured in sudo_local yet.
@@ -132,12 +132,12 @@ enable_touchid() {
if $write_success; then
# If we migrated from legacy, clean it up now
if $is_legacy_configured; then
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
log_success "Touch ID migrated to sudo_local"
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
log_success "Touch ID migrated to sudo_local"
else
log_success "Touch ID enabled (via sudo_local) - try: sudo ls"
log_success "Touch ID enabled (via sudo_local) - try: sudo ls"
fi
return 0
else
@@ -210,15 +210,15 @@ disable_touchid() {
grep -v "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" > "$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE" 2> /dev/null; then
# Since we modified sudo_local, we should also check if it's in sudo file (legacy cleanup)
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled (removed from sudo_local)${NC}"
echo ""
return 0
# Since we modified sudo_local, we should also check if it's in sudo file (legacy cleanup)
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
temp_file=$(mktemp)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled (removed from sudo_local)${NC}"
echo ""
return 0
else
log_error "Failed to disable Touch ID from sudo_local"
return 1

View File

@@ -30,6 +30,7 @@ func snapshotFromModel(m model) historyEntry {
Entries: cloneDirEntries(m.entries),
LargeFiles: cloneFileEntries(m.largeFiles),
TotalSize: m.totalSize,
TotalFiles: m.totalFiles,
Selected: m.selected,
EntryOffset: m.offset,
LargeSelected: m.largeSelected,
@@ -250,6 +251,7 @@ func saveCacheToDisk(path string, result scanResult) error {
Entries: result.Entries,
LargeFiles: result.LargeFiles,
TotalSize: result.TotalSize,
TotalFiles: result.TotalFiles,
ModTime: info.ModTime(),
ScanTime: time.Now(),
}
@@ -264,6 +266,29 @@ func saveCacheToDisk(path string, result scanResult) error {
return encoder.Encode(entry)
}
// peekCacheTotalFiles attempts to read the total file count from cache,
// ignoring expiration. Used for initial scan progress estimates.
func peekCacheTotalFiles(path string) (int64, error) {
cachePath, err := getCachePath(path)
if err != nil {
return 0, err
}
file, err := os.Open(cachePath)
if err != nil {
return 0, err
}
defer file.Close() //nolint:errcheck
var entry cacheEntry
decoder := gob.NewDecoder(file)
if err := decoder.Decode(&entry); err != nil {
return 0, err
}
return entry.TotalFiles, nil
}
func invalidateCache(path string) {
cachePath, err := getCachePath(path)
if err == nil {

View File

@@ -35,12 +35,14 @@ type scanResult struct {
Entries []dirEntry
LargeFiles []fileEntry
TotalSize int64
TotalFiles int64
}
type cacheEntry struct {
Entries []dirEntry
LargeFiles []fileEntry
TotalSize int64
TotalFiles int64
ModTime time.Time
ScanTime time.Time
}
@@ -50,6 +52,7 @@ type historyEntry struct {
Entries []dirEntry
LargeFiles []fileEntry
TotalSize int64
TotalFiles int64
Selected int
EntryOffset int
LargeSelected int
@@ -114,6 +117,8 @@ type model struct {
height int // Terminal height
multiSelected map[string]bool // Track multi-selected items by path (safer than index)
largeMultiSelected map[string]bool // Track multi-selected large files by path (safer than index)
totalFiles int64 // Total files found in current/last scan
lastTotalFiles int64 // Total files from previous scan (for progress bar)
}
func (m model) inOverviewMode() bool {
@@ -195,6 +200,13 @@ func newModel(path string, isOverview bool) model {
}
}
// Try to peek last total files for progress bar, even if cache is stale
if !isOverview {
if total, err := peekCacheTotalFiles(path); err == nil && total > 0 {
m.lastTotalFiles = total
}
}
return m
}
@@ -355,6 +367,7 @@ func (m model) scanCmd(path string) tea.Cmd {
Entries: cached.Entries,
LargeFiles: cached.LargeFiles,
TotalSize: cached.TotalSize,
TotalFiles: 0, // Cache doesn't store file count currently, minor UI limitation
}
return scanResultMsg{result: result, err: nil}
}
@@ -441,6 +454,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.entries = filteredEntries
m.largeFiles = msg.result.LargeFiles
m.totalSize = msg.result.TotalSize
m.totalFiles = msg.result.TotalFiles
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
m.clampEntrySelection()
m.clampLargeSelection()
@@ -685,6 +699,9 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
invalidateCache(m.path)
m.status = "Refreshing..."
m.scanning = true
if m.totalFiles > 0 {
m.lastTotalFiles = m.totalFiles
}
atomic.StoreInt64(m.filesScanned, 0)
atomic.StoreInt64(m.dirsScanned, 0)
atomic.StoreInt64(m.bytesScanned, 0)
@@ -968,6 +985,7 @@ func (m model) enterSelectedDir() (tea.Model, tea.Cmd) {
m.entries = cloneDirEntries(cached.Entries)
m.largeFiles = cloneFileEntries(cached.LargeFiles)
m.totalSize = cached.TotalSize
m.totalFiles = cached.TotalFiles
m.selected = cached.Selected
m.offset = cached.EntryOffset
m.largeSelected = cached.LargeSelected
@@ -978,6 +996,10 @@ func (m model) enterSelectedDir() (tea.Model, tea.Cmd) {
m.scanning = false
return m, nil
}
m.lastTotalFiles = 0
if total, err := peekCacheTotalFiles(m.path); err == nil && total > 0 {
m.lastTotalFiles = total
}
return m, tea.Batch(m.scanCmd(m.path), tickCmd())
}
m.status = fmt.Sprintf("File: %s (%s)", selected.Name, humanizeBytes(selected.Size))

View File

@@ -251,6 +251,7 @@ func scanPathConcurrent(root string, filesScanned, dirsScanned, bytesScanned *in
Entries: entries,
LargeFiles: largeFiles,
TotalSize: total,
TotalFiles: atomic.LoadInt64(filesScanned),
}, nil
}

View File

@@ -75,10 +75,25 @@ func (m model) View() string {
if m.scanning {
filesScanned, dirsScanned, bytesScanned := m.getScanProgress()
fmt.Fprintf(&b, "%s%s%s%s Scanning: %s%s files%s, %s%s dirs%s, %s%s%s\n",
progressPrefix := ""
if m.lastTotalFiles > 0 {
percent := float64(filesScanned) / float64(m.lastTotalFiles) * 100
// Cap at 100% generally
if percent > 100 {
percent = 100
}
// While strictly scanning, cap at 99% to avoid "100% but still working" confusion
if m.scanning && percent >= 100 {
percent = 99
}
progressPrefix = fmt.Sprintf(" %s(%.0f%%)%s", colorCyan, percent, colorReset)
}
fmt.Fprintf(&b, "%s%s%s%s Scanning%s: %s%s files%s, %s%s dirs%s, %s%s%s\n",
colorCyan, colorBold,
spinnerFrames[m.spinner],
colorReset,
progressPrefix,
colorYellow, formatNumber(filesScanned), colorReset,
colorYellow, formatNumber(dirsScanned), colorReset,
colorGreen, humanizeBytes(bytesScanned), colorReset)