267 lines
6.2 KiB
Go
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]
|
|
}
|