Implement context archive handling and metadata extraction for render jobs. Add functionality to check for Blender availability, create context archives, and extract metadata from .blend files. Update job creation and retrieval processes to support new metadata structure and context file management. Enhance client-side components to display context files and integrate new API endpoints for context handling.
This commit is contained in:
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
"jiggablend/pkg/types"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
@@ -294,51 +293,37 @@ func (s *Server) handleUpdateTaskStep(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// handleDownloadFileForRunner allows runners to download job files
|
||||
func (s *Server) handleDownloadFileForRunner(w http.ResponseWriter, r *http.Request) {
|
||||
// handleDownloadJobContext allows runners to download the job context tar.gz
|
||||
func (s *Server) handleDownloadJobContext(w http.ResponseWriter, r *http.Request) {
|
||||
jobID, err := parseID(r, "jobId")
|
||||
if err != nil {
|
||||
s.respondError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Get the file path from the wildcard parameter (supports subdirectories)
|
||||
filePathParam := chi.URLParam(r, "*")
|
||||
if filePathParam == "" {
|
||||
s.respondError(w, http.StatusBadRequest, "File path not specified")
|
||||
return
|
||||
}
|
||||
// Remove leading slash if present
|
||||
filePathParam = strings.TrimPrefix(filePathParam, "/")
|
||||
// Construct the context file path
|
||||
contextPath := filepath.Join(s.storage.JobPath(jobID), "context.tar.gz")
|
||||
|
||||
// Find the file in the database by matching file_name (which stores relative path)
|
||||
var filePath string
|
||||
var storedFileName string
|
||||
err = s.db.QueryRow(
|
||||
`SELECT file_path, file_name FROM job_files WHERE job_id = ? AND file_name = ?`,
|
||||
jobID, filePathParam,
|
||||
).Scan(&filePath, &storedFileName)
|
||||
if err == sql.ErrNoRows {
|
||||
s.respondError(w, http.StatusNotFound, "File not found")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query file: %v", err))
|
||||
// Check if context file exists
|
||||
if !s.storage.FileExists(contextPath) {
|
||||
log.Printf("Context archive not found for job %d", jobID)
|
||||
s.respondError(w, http.StatusNotFound, "Context archive not found. The file may not have been uploaded successfully.")
|
||||
return
|
||||
}
|
||||
|
||||
// Open and serve file
|
||||
file, err := s.storage.GetFile(filePath)
|
||||
file, err := s.storage.GetFile(contextPath)
|
||||
if err != nil {
|
||||
s.respondError(w, http.StatusNotFound, "File not found on disk")
|
||||
s.respondError(w, http.StatusNotFound, "Context file not found on disk")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Use the stored file name for the download (preserves original filename)
|
||||
downloadFileName := filepath.Base(storedFileName)
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", downloadFileName))
|
||||
// Set appropriate headers for tar.gz file
|
||||
w.Header().Set("Content-Type", "application/gzip")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=context.tar.gz")
|
||||
|
||||
// Stream the file to the response
|
||||
io.Copy(w, file)
|
||||
}
|
||||
|
||||
@@ -488,6 +473,43 @@ func (s *Server) handleGetJobFilesForRunner(w http.ResponseWriter, r *http.Reque
|
||||
s.respondJSON(w, http.StatusOK, files)
|
||||
}
|
||||
|
||||
// handleGetJobMetadataForRunner allows runners to get job metadata
|
||||
func (s *Server) handleGetJobMetadataForRunner(w http.ResponseWriter, r *http.Request) {
|
||||
jobID, err := parseID(r, "jobId")
|
||||
if err != nil {
|
||||
s.respondError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var blendMetadataJSON sql.NullString
|
||||
err = s.db.QueryRow(
|
||||
`SELECT blend_metadata FROM jobs WHERE id = ?`,
|
||||
jobID,
|
||||
).Scan(&blendMetadataJSON)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
s.respondError(w, http.StatusNotFound, "Job not found")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query job: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !blendMetadataJSON.Valid || blendMetadataJSON.String == "" {
|
||||
s.respondJSON(w, http.StatusOK, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var metadata types.BlendMetadata
|
||||
if err := json.Unmarshal([]byte(blendMetadataJSON.String), &metadata); err != nil {
|
||||
s.respondError(w, http.StatusInternalServerError, "Failed to parse metadata")
|
||||
return
|
||||
}
|
||||
|
||||
s.respondJSON(w, http.StatusOK, metadata)
|
||||
}
|
||||
|
||||
// WebSocket message types
|
||||
type WSMessage struct {
|
||||
Type string `json:"type"`
|
||||
@@ -1020,7 +1042,7 @@ func (s *Server) updateJobStatusFromTasks(jobID int64) {
|
||||
log.Printf("Updated job %d status to %s (progress: %.1f%%, completed tasks: %d/%d)", jobID, jobStatus, progress, completedTasks, totalTasks)
|
||||
}
|
||||
|
||||
if outputFormatStr == "MP4" {
|
||||
if outputFormatStr == "EXR_264_MP4" || outputFormatStr == "EXR_AV1_MP4" {
|
||||
// Check if a video generation task already exists for this job (any status)
|
||||
var existingVideoTask int
|
||||
s.db.QueryRow(
|
||||
@@ -1603,6 +1625,9 @@ func (s *Server) assignTaskToRunner(runnerID int64, taskID int64) error {
|
||||
task.JobName = jobName
|
||||
if outputFormat.Valid {
|
||||
task.OutputFormat = outputFormat.String
|
||||
log.Printf("Task %d assigned with output_format: '%s' (from job %d)", taskID, outputFormat.String, task.JobID)
|
||||
} else {
|
||||
log.Printf("Task %d assigned with no output_format (job %d)", taskID, task.JobID)
|
||||
}
|
||||
task.TaskType = taskType
|
||||
|
||||
|
||||
Reference in New Issue
Block a user