mirror of
https://github.com/MobAI-App/simslim.git
synced 2026-10-03 20:56:42 +02:00
The repo root becomes `package simslim`, an importable library holding every piece of slimming logic; the CLI moves to `cmd/simslim`. Previously everything was `package main`, so nothing here could be imported at all. The library carries no external dependencies — urfave/cli stays on the CLI side, along with all terminal-facing code: the interactive profile wizard, writeJSON, truncate/humanBytes, fatal and usage. The library never writes to stdout; it returns values and reports progress through the Reporter callback. Renames forced by collisions with existing types: status() -> ReadStatus, statusForDevice() -> ReadStatusForDevice, diskCleanupPlan() -> PlanDiskCleanup. The shutdown/boot timeouts move to simctl.go as ShutdownTimeout/BootTimeout. `go install` gains a path element: github.com/mobai-app/simslim/cmd/simslim. The installed binary is still named simslim, and -X main.version still applies. packaging/homebrew/simslim.rb.in is updated to match, since std_go_args passes no package path. Verified live against a real simulator: list, profiles, status, doctor, size, disk-plan, measure, boot, shutdown, on, off, on --except, on --profile, clone, rename, delete and the interactive wizard all behave identically to the previous build. JSON output, human output, help text and exit codes are unchanged.
121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package simslim
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// Reporter receives human-readable progress lines. A nil Reporter is a no-op,
|
|
// so non-interactive callers can ignore progress entirely.
|
|
type Reporter func(string)
|
|
|
|
func (r Reporter) report(msg string) {
|
|
if r != nil {
|
|
r(msg)
|
|
}
|
|
}
|
|
|
|
// ensure brings the device to exactly the desired disabled state and boots it.
|
|
// The disabled overrides persist in the device's launchd DB, so once set a slim
|
|
// device comes up slim in a single boot; a reboot only happens when the state
|
|
// actually changes. A running device is required to read/mutate launchctl, so
|
|
// the device is booted first regardless. Each slow phase reports progress so the
|
|
// caller can show the user that a multi-minute reconfigure is still working.
|
|
func ensure(ctx context.Context, set, udid string, desired map[string]bool, report Reporter) (changed bool, err error) {
|
|
report.report("Booting the simulator (a first boot can take up to a minute)...")
|
|
if err := BootAndWait(ctx, set, udid); err != nil {
|
|
return false, err
|
|
}
|
|
current, err := readDisabled(ctx, set, udid)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
toDisable, toEnable := delta(current, desired, managedSet())
|
|
if len(toDisable) == 0 && len(toEnable) == 0 {
|
|
return false, nil
|
|
}
|
|
if len(toDisable) > 0 {
|
|
report.report(fmt.Sprintf("Disabling %d background services...", len(toDisable)))
|
|
}
|
|
if len(toEnable) > 0 {
|
|
report.report(fmt.Sprintf("Re-enabling %d background services...", len(toEnable)))
|
|
}
|
|
if err := applyDelta(ctx, set, udid, toDisable, toEnable, report); err != nil {
|
|
return true, err
|
|
}
|
|
report.report("Rebooting the simulator to apply the changes...")
|
|
if err := Shutdown(ctx, set, udid); err != nil {
|
|
return true, fmt.Errorf("shutdown before reboot: %w", err)
|
|
}
|
|
if err := WaitShutdown(ctx, set, udid, ShutdownTimeout); err != nil {
|
|
return true, err
|
|
}
|
|
return true, BootAndWait(ctx, set, udid)
|
|
}
|
|
|
|
// enableSlim disables the profile's daemons and boots the device slim.
|
|
func EnableSlim(ctx context.Context, set, udid string, p Profile, report Reporter) (bool, error) {
|
|
return ensure(ctx, set, udid, p.Desired(), report)
|
|
}
|
|
|
|
// disableSlim re-enables every managed daemon, returning the device to stock.
|
|
func DisableSlim(ctx context.Context, set, udid string, report Reporter) (bool, error) {
|
|
return ensure(ctx, set, udid, map[string]bool{}, report)
|
|
}
|
|
|
|
// Status describes how slim a device currently is.
|
|
type Status struct {
|
|
ManagedDisabled int `json:"managedDisabled"` // managed labels currently disabled
|
|
ManagedTotal int `json:"managedTotal"` // size of the managed universe
|
|
Booted bool `json:"booted"`
|
|
}
|
|
|
|
// status reports how slim a device is and returns the labels it currently has
|
|
// disabled (nil when the device is not booted).
|
|
func ReadStatus(ctx context.Context, udid string) (Status, map[string]bool, error) {
|
|
d, err := FindDevice(ctx, udid, "")
|
|
if err != nil {
|
|
return Status{}, nil, err
|
|
}
|
|
return ReadStatusForDevice(ctx, d)
|
|
}
|
|
|
|
func ReadStatusForDevice(ctx context.Context, d Device) (Status, map[string]bool, error) {
|
|
managed := SlimmableSet()
|
|
st := Status{ManagedTotal: len(managed), Booted: d.State == "Booted"}
|
|
if !st.Booted {
|
|
return st, nil, fmt.Errorf("simulator must be booted to read its state (it is %s)", d.State)
|
|
}
|
|
disabled, err := readDisabled(ctx, d.Set, d.UDID)
|
|
if err != nil {
|
|
return st, nil, err
|
|
}
|
|
for l := range disabled {
|
|
if managed[l] {
|
|
st.ManagedDisabled++
|
|
}
|
|
}
|
|
return st, disabled, nil
|
|
}
|
|
|
|
// droppedCategories groups the disabled managed daemons by category, in category
|
|
// order, omitting categories with nothing disabled.
|
|
func DroppedCategories(disabled map[string]bool) []DroppedCategory {
|
|
var out []DroppedCategory
|
|
for _, c := range Categories {
|
|
var labels []string
|
|
for _, l := range c.Labels {
|
|
if disabled[l] {
|
|
labels = append(labels, l)
|
|
}
|
|
}
|
|
if len(labels) == 0 {
|
|
continue
|
|
}
|
|
sort.Strings(labels)
|
|
out = append(out, DroppedCategory{ID: c.ID, Name: c.Name, Downside: c.Downside, Labels: labels})
|
|
}
|
|
return out
|
|
}
|