196 lines
4.1 KiB
Go
196 lines
4.1 KiB
Go
package steps
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"miasma-installer/tui/styles"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
type Disk struct {
|
|
name string
|
|
description string
|
|
size string
|
|
}
|
|
|
|
func (i Disk) FilterValue() string { return i.name }
|
|
func (i Disk) Title() string { return i.name }
|
|
func (i Disk) Description() string { return fmt.Sprintf("%s - %s", i.size, i.description) }
|
|
|
|
type ItemDelegate struct{}
|
|
|
|
func (i ItemDelegate) Height() int { return 1 }
|
|
func (i ItemDelegate) Spacing() int { return 0 }
|
|
func (i ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
|
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
|
disk, ok := item.(Disk)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
s := fmt.Sprintf("%d. %s - %s", index+1, disk.Title(), disk.Description())
|
|
|
|
if index == m.Index() {
|
|
s = styles.ActiveItemStyle.Render("> " + s)
|
|
} else {
|
|
s = styles.InactiveItemStyle.Render(" " + s)
|
|
}
|
|
fmt.Fprint(w, s)
|
|
}
|
|
|
|
type DiskSelModel struct {
|
|
list list.Model
|
|
Finished bool
|
|
SelectedDisk string
|
|
loading bool
|
|
}
|
|
|
|
type disksLoadedMsg struct {
|
|
disks []list.Item
|
|
}
|
|
|
|
func NewDiskSelModel(width, height int) DiskSelModel {
|
|
l := list.New([]list.Item{}, ItemDelegate{}, width, height-5)
|
|
l.Title = "Select Installation Disk"
|
|
l.SetShowStatusBar(false)
|
|
l.SetFilteringEnabled(false)
|
|
l.Styles.Title = styles.TitleStyle
|
|
|
|
return DiskSelModel{
|
|
list: l,
|
|
loading: true,
|
|
}
|
|
}
|
|
|
|
func (m DiskSelModel) Init() tea.Cmd {
|
|
return loadDisks
|
|
}
|
|
|
|
func loadDisks() tea.Msg {
|
|
disks, err := scanDisks()
|
|
if err != nil {
|
|
return disksLoadedMsg{disks: []list.Item{
|
|
Disk{name: "error", description: err.Error(), size: ""},
|
|
}}
|
|
}
|
|
return disksLoadedMsg{disks: disks}
|
|
}
|
|
|
|
func scanDisks() ([]list.Item, error) {
|
|
cmd := exec.Command("lsblk", "-ndo", "NAME,SIZE,TYPE,MODEL")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list disks: %w", err)
|
|
}
|
|
|
|
var disks []list.Item
|
|
scanner := bufio.NewScanner(strings.NewReader(string(output)))
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
|
|
name := fields[0]
|
|
size := fields[1]
|
|
diskType := fields[2]
|
|
model := ""
|
|
if len(fields) > 3 {
|
|
model = strings.Join(fields[3:], " ")
|
|
}
|
|
|
|
if diskType != "disk" {
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "sr") {
|
|
continue
|
|
}
|
|
|
|
disks = append(disks, Disk{
|
|
name: "/dev/" + name,
|
|
size: size,
|
|
description: model,
|
|
})
|
|
}
|
|
|
|
if len(disks) == 0 {
|
|
return nil, fmt.Errorf("no suitable disks found")
|
|
}
|
|
|
|
return disks, nil
|
|
}
|
|
|
|
func (m DiskSelModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if m.Finished {
|
|
return m, nil
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case disksLoadedMsg:
|
|
m.loading = false
|
|
m.list.SetItems(msg.disks)
|
|
return m, nil
|
|
|
|
case tea.KeyMsg:
|
|
if !m.loading && msg.String() == "enter" {
|
|
if item, ok := m.list.SelectedItem().(Disk); ok {
|
|
m.SelectedDisk = item.name
|
|
m.Finished = true
|
|
return m, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
m.list, cmd = m.list.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m DiskSelModel) View() string {
|
|
if m.loading {
|
|
return styles.MainStyle.Render(styles.TitleStyle.Render("Scanning disks..."))
|
|
}
|
|
|
|
s := m.list.View()
|
|
s += "\n" + styles.HelpStyle.Render("Use ↑/↓ to navigate, [Enter] to select disk.")
|
|
return s
|
|
}
|
|
|
|
func formatBytes(bytes uint64) string {
|
|
const unit = 1024
|
|
if bytes < unit {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
div, exp := uint64(unit), 0
|
|
for n := bytes / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
|
}
|
|
|
|
func getDiskSize(device string) (string, error) {
|
|
data, err := os.ReadFile(fmt.Sprintf("/sys/block/%s/size", strings.TrimPrefix(device, "/dev/")))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sectors, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
bytes := sectors * 512
|
|
return formatBytes(bytes), nil
|
|
}
|