125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
|
)
|
|
|
|
func (owner *tentacleLifecycle) handleRequest(call tentacleManagementCall) {
|
|
if err := call.ctx.Err(); err != nil {
|
|
call.result <- err
|
|
return
|
|
}
|
|
if owner.loadError != nil {
|
|
call.result <- errors.New("tentacle registry could not be loaded; repair it and restart")
|
|
return
|
|
}
|
|
owner.runtime.mutex.Lock()
|
|
disabled := owner.runtime.disabled
|
|
owner.runtime.mutex.Unlock()
|
|
if disabled {
|
|
call.result <- tentacle.ErrDisabled
|
|
return
|
|
}
|
|
if strings.HasPrefix(call.request.Action, "repository-") {
|
|
next, err := change_tentacle_repositories(owner.repositories, call.request, call.repository)
|
|
if err == nil {
|
|
err = owner.runtime.registry.SaveState(owner.entries, next)
|
|
}
|
|
if err == nil || errors.Is(err, tentacle.ErrRegistrySync) {
|
|
owner.repositories = next
|
|
owner.setSnapshot()
|
|
}
|
|
call.result <- err
|
|
return
|
|
}
|
|
if call.packageFiles != nil {
|
|
index := slices.IndexFunc(owner.repositories, func(value tentacle.Repository) bool { return value.URL == call.repository.URL })
|
|
if index < 0 || !reflect.DeepEqual(owner.repositories[index], *call.repository) {
|
|
call.result <- errors.New("Repository changed during installation; refresh and try again.")
|
|
return
|
|
}
|
|
}
|
|
if _, starting := owner.startups[call.request.ID]; starting && call.request.Action != "disable" && call.request.Action != "remove" {
|
|
call.result <- tentacle.ErrBusy
|
|
return
|
|
}
|
|
if startup, exists := owner.startups[call.request.ID]; exists && startup.previous != nil {
|
|
owner.failStartup(call.request.ID, "Tentacle update was cancelled by another operation.")
|
|
}
|
|
next, err := change_tentacle_entries(owner.entries, call.request)
|
|
if err == nil && call.request.Action != "retry" && reflect.DeepEqual(next, owner.entries) {
|
|
// Saved enablement is intent, not proof of a live session. An
|
|
// explicit enable/update must recover a failed or starting entry.
|
|
index := slices.IndexFunc(next, func(entry tentacle.Entry) bool { return entry.ID == call.request.ID })
|
|
state := tentacle.Stopped
|
|
if session := owner.sessions[call.request.ID]; session != nil {
|
|
state, _ = session.Status()
|
|
}
|
|
if !next[index].Enabled || state == tentacle.Active {
|
|
call.result <- nil
|
|
return
|
|
}
|
|
}
|
|
if err == nil && call.request.Action != "retry" {
|
|
err = owner.runtime.registry.Save(next)
|
|
}
|
|
if err == nil || errors.Is(err, tentacle.ErrRegistrySync) {
|
|
id := call.request.ID
|
|
var previous *tentacle.Entry
|
|
if call.packageFiles != nil && call.request.Action == "update" {
|
|
index := slices.IndexFunc(owner.entries, func(entry tentacle.Entry) bool { return entry.ID == id })
|
|
if index >= 0 && owner.entries[index].Enabled {
|
|
saved := tentacle.CloneEntries(owner.entries[index : index+1])[0]
|
|
previous = &saved
|
|
}
|
|
}
|
|
owner.replyStartup(id, errors.New("Tentacle startup was cancelled by another operation."))
|
|
if call.packageFiles != nil {
|
|
call.packageFiles.Retain()
|
|
}
|
|
if call.request.Action == "remove" || call.request.Action == "update" {
|
|
for _, entry := range owner.entries {
|
|
if entry.ID != id || entry.PackageDirectory == "" {
|
|
continue
|
|
}
|
|
if call.request.Action == "update" && (previous != nil ||
|
|
entry.PackageDirectory == call.request.Entry.PackageDirectory) {
|
|
continue
|
|
}
|
|
owner.removedPackages[entry.PackageDirectory] = tentacleRetiredPackage{entry: entry, session: owner.sessions[id]}
|
|
}
|
|
}
|
|
owner.revoke(id)
|
|
if owner.current != nil && owner.current.TentacleID() == id {
|
|
owner.current, owner.ready = nil, nil
|
|
}
|
|
owner.entries = next
|
|
delete(owner.problems, id)
|
|
delete(owner.pending, id)
|
|
for _, entry := range owner.entries {
|
|
if entry.ID == id {
|
|
owner.pending[id] = entry.Enabled
|
|
}
|
|
}
|
|
// Immediately withdraw dispatch, without waiting for key release.
|
|
stillActive := slices.DeleteFunc(slices.Clone(owner.active), func(session *tentacle.Session) bool { state, _ := session.Status(); return state != tentacle.Active })
|
|
owner.runtime.publishCommands(stillActive)
|
|
owner.setSnapshot()
|
|
if owner.pending[id] {
|
|
owner.startups[id] = tentacleStartupCall{call: call, saveError: err, stop: context.AfterFunc(call.ctx, owner.notify), previous: previous}
|
|
return
|
|
}
|
|
}
|
|
call.result <- err
|
|
}
|