Files
LitePan/internal/api/fuse_admin.go
T
2026-07-27 19:31:43 +08:00

267 lines
6.2 KiB
Go

package api
import (
"context"
"net/http"
"strings"
"litepan/internal/domain"
"litepan/internal/fusemount"
)
type fuseMountDTO struct {
ID int64 `json:"id,omitempty"`
Name string `json:"name"`
AccountID int64 `json:"account_id"`
AccountName string `json:"account_name,omitempty"`
RootItemID string `json:"root_item_id"`
RootPath string `json:"root_path"`
MountPoint string `json:"mount_point"`
ReadOnly bool `json:"read_only"`
AutoMount bool `json:"auto_mount"`
UID uint32 `json:"uid"`
GID uint32 `json:"gid"`
DirMode string `json:"dir_mode"`
FileMode string `json:"file_mode"`
Enabled bool `json:"enabled"`
State string `json:"state"`
LastError string `json:"last_error,omitempty"`
SortOrder int `json:"sort_order"`
}
type fuseConfigDTO struct {
Enabled bool `json:"enabled"`
}
func (h *Handler) fuseStatus(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
writeOK(w, h.fuse.Status(r.Context()))
}
func (h *Handler) listFuseMounts(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
list, err := h.fuse.List(r.Context())
if err != nil {
writeErr(w, err)
return
}
accNames := h.accountNameMap(r.Context())
out := make([]fuseMountDTO, 0, len(list))
for _, m := range list {
out = append(out, toFuseMountDTO(m, accNames[m.AccountID]))
}
writeOK(w, out)
}
func (h *Handler) createFuseMount(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
var in fuseMountDTO
if err := decodeJSON(r, &in); err != nil {
writeErr(w, err)
return
}
m, err := fromFuseMountDTO(in)
if err != nil {
writeErr(w, err)
return
}
created, err := h.fuse.Create(r.Context(), m)
if err != nil {
writeErr(w, err)
return
}
writeOK(w, toFuseMountDTO(created, h.accountName(r.Context(), created.AccountID)))
}
func (h *Handler) updateFuseMount(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
id, err := pathID(r)
if err != nil {
writeErr(w, err)
return
}
if id <= 0 {
writeErr(w, domain.Errorf(domain.CodeValidation, "无效的挂载 ID"))
return
}
var in fuseMountDTO
if err := decodeJSON(r, &in); err != nil {
writeErr(w, err)
return
}
m, err := fromFuseMountDTO(in)
if err != nil {
writeErr(w, err)
return
}
m.ID = id
updated, err := h.fuse.Update(r.Context(), m)
if err != nil {
writeErr(w, err)
return
}
writeOK(w, toFuseMountDTO(updated, h.accountName(r.Context(), updated.AccountID)))
}
func (h *Handler) deleteFuseMount(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
id, err := pathID(r)
if err != nil {
writeErr(w, err)
return
}
if id <= 0 {
writeErr(w, domain.Errorf(domain.CodeValidation, "无效的挂载 ID"))
return
}
if err := h.fuse.Delete(r.Context(), id); err != nil {
writeErr(w, err)
return
}
writeOK(w, map[string]any{"deleted": true})
}
func (h *Handler) mountFuse(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
id, err := pathID(r)
if err != nil {
writeErr(w, err)
return
}
if id <= 0 {
writeErr(w, domain.Errorf(domain.CodeValidation, "无效的挂载 ID"))
return
}
if err := h.fuse.Mount(r.Context(), id); err != nil {
writeErr(w, err)
return
}
m, _ := h.fuse.Get(r.Context(), id)
writeOK(w, toFuseMountDTO(m, h.accountName(r.Context(), m.AccountID)))
}
func (h *Handler) unmountFuse(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
id, err := pathID(r)
if err != nil {
writeErr(w, err)
return
}
if id <= 0 {
writeErr(w, domain.Errorf(domain.CodeValidation, "无效的挂载 ID"))
return
}
if err := h.fuse.Unmount(r.Context(), id); err != nil {
writeErr(w, err)
return
}
m, _ := h.fuse.Get(r.Context(), id)
writeOK(w, toFuseMountDTO(m, h.accountName(r.Context(), m.AccountID)))
}
func (h *Handler) updateFuseConfig(w http.ResponseWriter, r *http.Request) {
if !ensureServiceReady(w, h.fuse != nil) {
return
}
var in fuseConfigDTO
if err := decodeJSON(r, &in); err != nil {
writeErr(w, err)
return
}
if err := h.fuse.SetEnabled(r.Context(), in.Enabled); err != nil {
writeErr(w, err)
return
}
writeOK(w, h.fuse.Status(r.Context()))
}
func toFuseMountDTO(m *domain.FuseMount, accountName string) fuseMountDTO {
if m == nil {
return fuseMountDTO{}
}
return fuseMountDTO{
ID: m.ID,
Name: m.Name,
AccountID: m.AccountID,
AccountName: accountName,
RootItemID: m.RootItemID,
RootPath: m.RootPath,
MountPoint: m.MountPoint,
ReadOnly: m.ReadOnly,
AutoMount: m.AutoMount,
UID: m.UID,
GID: m.GID,
DirMode: fusemount.FormatMode(m.DirMode),
FileMode: fusemount.FormatMode(m.FileMode),
Enabled: m.Enabled,
State: m.State,
LastError: m.LastError,
SortOrder: m.SortOrder,
}
}
func fromFuseMountDTO(in fuseMountDTO) (*domain.FuseMount, error) {
dirMode, err := fusemount.ParseModeOctal(in.DirMode, 0o755)
if err != nil {
return nil, err
}
fileMode, err := fusemount.ParseModeOctal(in.FileMode, 0o644)
if err != nil {
return nil, err
}
mp := strings.TrimSpace(in.MountPoint)
if mp == "" && strings.TrimSpace(in.Name) != "" {
mp = fusemount.MountRoot + "/" + strings.TrimSpace(in.Name)
}
return &domain.FuseMount{
Name: strings.TrimSpace(in.Name),
AccountID: in.AccountID,
RootItemID: strings.TrimSpace(in.RootItemID),
RootPath: strings.TrimSpace(in.RootPath),
MountPoint: mp,
ReadOnly: in.ReadOnly || true,
AutoMount: in.AutoMount,
UID: in.UID,
GID: in.GID,
DirMode: dirMode,
FileMode: fileMode,
Enabled: in.Enabled || true,
SortOrder: in.SortOrder,
}, nil
}
func (h *Handler) accountNameMap(ctx context.Context) map[int64]string {
out := map[int64]string{}
if h.accountSvc == nil {
return out
}
list, err := h.accountSvc.List(ctx)
if err != nil {
return out
}
for _, v := range list {
if v.Account != nil {
out[v.Account.ID] = v.Account.Name
}
}
return out
}
func (h *Handler) accountName(ctx context.Context, id int64) string {
return h.accountNameMap(ctx)[id]
}