package tape_vtl import ( "net/http" "github.com/atlasos/calypso/internal/common/database" "github.com/atlasos/calypso/internal/common/logger" "github.com/atlasos/calypso/internal/tasks" "github.com/gin-gonic/gin" ) // Handler handles virtual tape library API requests type Handler struct { service *Service taskEngine *tasks.Engine db *database.DB logger *logger.Logger } // NewHandler creates a new VTL handler func NewHandler(db *database.DB, log *logger.Logger) *Handler { return &Handler{ service: NewService(db, log), taskEngine: tasks.NewEngine(db, log), db: db, logger: log, } } // ListLibraries lists all virtual tape libraries func (h *Handler) ListLibraries(c *gin.Context) { libraries, err := h.service.ListLibraries(c.Request.Context()) if err != nil { h.logger.Error("Failed to list libraries", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list libraries"}) return } c.JSON(http.StatusOK, gin.H{"libraries": libraries}) } // GetLibrary retrieves a library by ID func (h *Handler) GetLibrary(c *gin.Context) { libraryID := c.Param("id") lib, err := h.service.GetLibrary(c.Request.Context(), libraryID) if err != nil { if err.Error() == "library not found" { c.JSON(http.StatusNotFound, gin.H{"error": "library not found"}) return } h.logger.Error("Failed to get library", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get library"}) return } // Get drives drives, _ := h.service.GetLibraryDrives(c.Request.Context(), libraryID) // Get tapes tapes, _ := h.service.GetLibraryTapes(c.Request.Context(), libraryID) c.JSON(http.StatusOK, gin.H{ "library": lib, "drives": drives, "tapes": tapes, }) } // CreateLibraryRequest represents a library creation request type CreateLibraryRequest struct { Name string `json:"name" binding:"required"` Description string `json:"description"` BackingStorePath string `json:"backing_store_path" binding:"required"` SlotCount int `json:"slot_count" binding:"required"` DriveCount int `json:"drive_count" binding:"required"` } // CreateLibrary creates a new virtual tape library func (h *Handler) CreateLibrary(c *gin.Context) { var req CreateLibraryRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } // Validate slot and drive counts if req.SlotCount < 1 || req.SlotCount > 1000 { c.JSON(http.StatusBadRequest, gin.H{"error": "slot_count must be between 1 and 1000"}) return } if req.DriveCount < 1 || req.DriveCount > 8 { c.JSON(http.StatusBadRequest, gin.H{"error": "drive_count must be between 1 and 8"}) return } userID, _ := c.Get("user_id") lib, err := h.service.CreateLibrary( c.Request.Context(), req.Name, req.Description, req.BackingStorePath, req.SlotCount, req.DriveCount, userID.(string), ) if err != nil { h.logger.Error("Failed to create library", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, lib) } // DeleteLibrary deletes a virtual tape library func (h *Handler) DeleteLibrary(c *gin.Context) { libraryID := c.Param("id") if err := h.service.DeleteLibrary(c.Request.Context(), libraryID); err != nil { if err.Error() == "library not found" { c.JSON(http.StatusNotFound, gin.H{"error": "library not found"}) return } h.logger.Error("Failed to delete library", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "library deleted successfully"}) } // GetLibraryDrives lists drives for a library func (h *Handler) GetLibraryDrives(c *gin.Context) { libraryID := c.Param("id") drives, err := h.service.GetLibraryDrives(c.Request.Context(), libraryID) if err != nil { h.logger.Error("Failed to get drives", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get drives"}) return } c.JSON(http.StatusOK, gin.H{"drives": drives}) } // GetLibraryTapes lists tapes for a library func (h *Handler) GetLibraryTapes(c *gin.Context) { libraryID := c.Param("id") tapes, err := h.service.GetLibraryTapes(c.Request.Context(), libraryID) if err != nil { h.logger.Error("Failed to get tapes", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get tapes"}) return } c.JSON(http.StatusOK, gin.H{"tapes": tapes}) } // CreateTapeRequest represents a tape creation request type CreateTapeRequest struct { Barcode string `json:"barcode" binding:"required"` SlotNumber int `json:"slot_number" binding:"required"` TapeType string `json:"tape_type" binding:"required"` SizeGB int64 `json:"size_gb" binding:"required"` } // CreateTape creates a new virtual tape func (h *Handler) CreateTape(c *gin.Context) { libraryID := c.Param("id") var req CreateTapeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } sizeBytes := req.SizeGB * 1024 * 1024 * 1024 tape, err := h.service.CreateTape( c.Request.Context(), libraryID, req.Barcode, req.SlotNumber, req.TapeType, sizeBytes, ) if err != nil { h.logger.Error("Failed to create tape", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, tape) } // LoadTapeRequest represents a load tape request type LoadTapeRequest struct { SlotNumber int `json:"slot_number" binding:"required"` DriveNumber int `json:"drive_number" binding:"required"` } // LoadTape loads a tape from slot to drive func (h *Handler) LoadTape(c *gin.Context) { libraryID := c.Param("id") var req LoadTapeRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid load tape request", "error", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()}) return } userID, _ := c.Get("user_id") // Create async task taskID, err := h.taskEngine.CreateTask(c.Request.Context(), tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{ "operation": "load_tape", "library_id": libraryID, "slot_number": req.SlotNumber, "drive_number": req.DriveNumber, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"}) return } // Run load in background go func() { ctx := c.Request.Context() h.taskEngine.StartTask(ctx, taskID) h.taskEngine.UpdateProgress(ctx, taskID, 50, "Loading tape...") if err := h.service.LoadTape(ctx, libraryID, req.SlotNumber, req.DriveNumber); err != nil { h.taskEngine.FailTask(ctx, taskID, err.Error()) return } h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape loaded") h.taskEngine.CompleteTask(ctx, taskID, "Tape loaded successfully") }() c.JSON(http.StatusAccepted, gin.H{"task_id": taskID}) } // UnloadTapeRequest represents an unload tape request type UnloadTapeRequest struct { DriveNumber int `json:"drive_number" binding:"required"` SlotNumber int `json:"slot_number" binding:"required"` } // UnloadTape unloads a tape from drive to slot func (h *Handler) UnloadTape(c *gin.Context) { libraryID := c.Param("id") var req UnloadTapeRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid unload tape request", "error", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()}) return } userID, _ := c.Get("user_id") // Create async task taskID, err := h.taskEngine.CreateTask(c.Request.Context(), tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{ "operation": "unload_tape", "library_id": libraryID, "slot_number": req.SlotNumber, "drive_number": req.DriveNumber, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"}) return } // Run unload in background go func() { ctx := c.Request.Context() h.taskEngine.StartTask(ctx, taskID) h.taskEngine.UpdateProgress(ctx, taskID, 50, "Unloading tape...") if err := h.service.UnloadTape(ctx, libraryID, req.DriveNumber, req.SlotNumber); err != nil { h.taskEngine.FailTask(ctx, taskID, err.Error()) return } h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape unloaded") h.taskEngine.CompleteTask(ctx, taskID, "Tape unloaded successfully") }() c.JSON(http.StatusAccepted, gin.H{"task_id": taskID}) }