When directory navigation fails (e.g., permission denied on Android), the browser now restores the previous path instead of staying in a broken empty state. NavigateTo saves the current path to History, and handleError restores it and re-builds the index for the previous directory when a BuildIndexTask fails.
184 lines
5.2 KiB
Go
184 lines
5.2 KiB
Go
package browser
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"pad/internal/io/pool"
|
|
"pad/internal/io/pool/types"
|
|
)
|
|
|
|
var ErrDirNotFound = fmt.Errorf("directory not found")
|
|
|
|
type BrowserManager struct {
|
|
state *BrowserState
|
|
workerPool *pool.WorkerPool
|
|
fs pool.FileSystem
|
|
}
|
|
|
|
func NewBrowserManager(state *BrowserState, wp *pool.WorkerPool, fs pool.FileSystem) (*BrowserManager, error) {
|
|
return &BrowserManager{
|
|
state: state,
|
|
workerPool: wp,
|
|
fs: fs,
|
|
}, nil
|
|
}
|
|
|
|
func (bm *BrowserManager) NavigateTo(dirPath string) {
|
|
fmt.Printf("NavigateTo: dirPath=%s, state.CurrentPath=%s\n", dirPath, bm.state.CurrentPath)
|
|
|
|
// Save the previous path so we can restore it on navigation failure.
|
|
bm.state.History = append(bm.state.History, bm.state.CurrentPath)
|
|
|
|
bm.state.CurrentPath = dirPath
|
|
navigateToDirectory(bm.state, dirPath)
|
|
bm.state.Loading = true
|
|
|
|
// Dispatch BuildIndexTask to the worker pool.
|
|
// In a real app, this would be a task that reads and sorts the directory.
|
|
task := pool.NewBuildIndexTask(dirPath, bm.fs)
|
|
bm.workerPool.Dispatch(task)
|
|
}
|
|
|
|
func (bm *BrowserManager) OnScroll() {
|
|
if bm.state.TotalEntries == 0 {
|
|
return
|
|
}
|
|
|
|
// Identify unloaded pages in the visible + prefetch range
|
|
minPage, maxPage := computeVisiblePageRange(bm.state)
|
|
var pagesToLoad []int
|
|
for i := minPage; i <= maxPage; i++ {
|
|
if page, ok := bm.state.Pages[i]; !ok || !page.Loaded {
|
|
pagesToLoad = append(pagesToLoad, i)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("OnScroll: minPage=%d, maxPage=%d, pagesToLoad=%v\n", minPage, maxPage, pagesToLoad)
|
|
|
|
// Dispatch LoadPagesTask for unloaded pages
|
|
if len(pagesToLoad) > 0 {
|
|
fmt.Printf("OnScroll: Dispatching LoadPagesTask for pages %v\n", pagesToLoad)
|
|
task := pool.NewLoadPagesTask(bm.state.CurrentPath, pagesToLoad, bm.fs)
|
|
bm.workerPool.Dispatch(task)
|
|
}
|
|
|
|
// Evict distant pages to save memory
|
|
bm.state.EvictPages()
|
|
}
|
|
|
|
func (bm *BrowserManager) HandleResult(result pool.Result) {
|
|
if !result.IsBrowserResult() {
|
|
return
|
|
}
|
|
|
|
switch result.TaskType {
|
|
case pool.TypeBuildIndex:
|
|
if result.Success {
|
|
bm.handleBuildIndexSuccess(result)
|
|
} else {
|
|
bm.handleError(result)
|
|
}
|
|
case pool.TypeLoadPages:
|
|
if result.Success {
|
|
bm.handleLoadPagesSuccess(result)
|
|
} else {
|
|
bm.handleError(result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (bm *BrowserManager) handleBuildIndexSuccess(result pool.Result) {
|
|
bm.state.Loading = false
|
|
|
|
// In the mock implementation, BuildIndexTask returns []types.DirEntry.
|
|
// We need to convert these to browser.Entry and build the SortIndex.
|
|
entries := result.Data.([]types.DirEntry)
|
|
|
|
var browserEntries []Entry
|
|
// Add ".." entry if not at root
|
|
// We check against "/" specifically. "." is just a relative path for current dir,
|
|
// which still has a parent.
|
|
if bm.state.CurrentPath != "/" && bm.state.CurrentPath != "" {
|
|
browserEntries = append(browserEntries, Entry{
|
|
Path: filepath.Dir(bm.state.CurrentPath),
|
|
Name: "..",
|
|
Size: 0,
|
|
ModTime: time.Now(),
|
|
IsDir: true,
|
|
})
|
|
}
|
|
|
|
for _, e := range entries {
|
|
info, _ := e.Info()
|
|
fmt.Printf("Adding entry: %s\n", e.Name())
|
|
browserEntries = append(browserEntries, Entry{
|
|
Path: filepath.Join(bm.state.CurrentPath, e.Name()),
|
|
Name: e.Name(),
|
|
Size: info.Size(),
|
|
ModTime: info.ModTime(),
|
|
IsDir: e.IsDir(),
|
|
})
|
|
}
|
|
|
|
// Build the sorted index with position maps
|
|
bm.state.SortIndex = &DirectoryIndex{
|
|
Path: bm.state.CurrentPath,
|
|
EntryCount: len(browserEntries),
|
|
Entries: browserEntries,
|
|
SortOrders: make(map[string][]int),
|
|
}
|
|
|
|
// Compute position maps for all sort modes
|
|
for mode := SortMode(0); mode < SortMode(modeCount()); mode++ {
|
|
key := sortModeKey(mode)
|
|
if key != "" {
|
|
bm.state.SortIndex.SortOrders[key] = buildPositionMap(browserEntries, mode)
|
|
}
|
|
}
|
|
|
|
bm.state.TotalEntries = len(browserEntries)
|
|
fmt.Printf("handleBuildIndexSuccess: TotalEntries set to %d\n", bm.state.TotalEntries)
|
|
|
|
// Trigger initial page load
|
|
bm.OnScroll()
|
|
}
|
|
|
|
func (bm *BrowserManager) handleLoadPagesSuccess(result pool.Result) {
|
|
// In a real implementation, LoadPagesTask would return the actual entry data.
|
|
// In our current mock/placeholder, it just returns the page indices.
|
|
// We'll load the data from the SortIndex.
|
|
pageIndices := result.Data.([]int)
|
|
|
|
for _, idx := range pageIndices {
|
|
page := loadPageFromIndex(bm.state, idx)
|
|
if page != nil {
|
|
bm.state.Pages[idx] = page
|
|
}
|
|
}
|
|
}
|
|
|
|
func (bm *BrowserManager) handleError(result pool.Result) {
|
|
fmt.Printf("handleError: TaskType=%d, Error=%v\n", result.TaskType, result.Error)
|
|
bm.state.Loading = false
|
|
|
|
// If the failed task was a BuildIndex (directory navigation), restore
|
|
// the previous path so the user is sent back to where they were.
|
|
if result.TaskType == pool.TypeBuildIndex && len(bm.state.History) > 0 {
|
|
prevPath := bm.state.History[len(bm.state.History)-1]
|
|
bm.state.History = bm.state.History[:len(bm.state.History)-1]
|
|
bm.state.CurrentPath = prevPath
|
|
navigateToDirectory(bm.state, prevPath)
|
|
// Re-dispatch the build index for the previous directory so the
|
|
// browser renders the old contents again.
|
|
task := pool.NewBuildIndexTask(prevPath, bm.fs)
|
|
bm.workerPool.Dispatch(task)
|
|
return
|
|
}
|
|
|
|
// For other errors, just reset the state or log the error.
|
|
// In production, we'd show a toast or error message.
|
|
bm.state.Reset()
|
|
}
|