Skip to content

Commit 129c450

Browse files
authored
Merge pull request #7 from 2024-dissertation/develop
Introduced AWS S3 Storage
2 parents 6445c63 + 38b3c4c commit 129c450

18 files changed

Lines changed: 920 additions & 423 deletions

app/Dockerfile.dev

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,40 +43,49 @@ RUN git clone --branch develop --depth=1 https://github.com/cdcseacave/openMVS.g
4343
# ------------------------------
4444
# Stage 3: Build Blender (minimal)
4545
# ------------------------------
46-
FROM ubuntu:22.04 AS blender_builder
47-
48-
RUN apt-get update && apt-get install -y --no-install-recommends \
49-
wget xz-utils && \
50-
rm -rf /var/lib/apt/lists/*
51-
52-
RUN wget -q https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz --no-check-certificate && \
53-
mkdir -p /opt/blender && \
54-
tar -xf blender-4.4.0-linux-x64.tar.xz -C /opt/blender --strip-components=1 && \
55-
rm blender-4.4.0-linux-x64.tar.xz
46+
FROM linuxserver/blender:4.4.3 AS blender_builder
5647

5748
# -------------------------
5849
# Stage 4: Dev Environment
5950
# -------------------------
60-
FROM ubuntu:latest AS final
51+
FROM ubuntu:22.04 AS final
52+
53+
ENV TZ=UTC
54+
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
55+
56+
RUN sed -i'' 's/archive\.ubuntu\.com/us\.archive\.ubuntu\.com/' /etc/apt/sources.list
6157

62-
RUN apt-get update && apt-get install -y wget xz-utils libcgal-qt5-dev \
63-
libceres-dev libboost-all-dev libopencv-dev build-essential && \
64-
wget https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz && \
65-
wget https://go.dev/dl/go1.23.8.linux-amd64.tar.gz && \
58+
# Create non-root user
59+
RUN groupadd -r appuser && useradd -r -g appuser -s /bin/false appuser
60+
61+
ENV DEBIAN_FRONTEND=noninteractive
62+
ARG DEBIAN_FRONTEND=noninteractive
63+
64+
# Install only runtime dependencies
65+
# Seperate for cache issues
66+
RUN apt-get -y update
67+
RUN apt-get install -y --no-install-recommends \
68+
libcgal-qt5-dev libceres2 libboost-system1.74.0 libboost-filesystem1.74.0 \
69+
build-essential \
70+
libboost-program-options1.74.0 libboost-serialization1.74.0 \
71+
libopencv-core4.5d libopencv-imgproc4.5d libopencv-imgcodecs4.5d \
72+
libjpeg8 libpng16-16 libtiff5 libglu1-mesa libglew2.2 \
73+
libglfw3 libgomp1 ca-certificates curl wget libboost-all-dev libopencv-dev \
74+
xorg && \
75+
apt-get clean && rm -rf /var/lib/apt/lists/*
76+
77+
RUN wget https://go.dev/dl/go1.23.8.linux-amd64.tar.gz && \
6678
tar -C /usr/local -xzf go1.23.8.linux-amd64.tar.gz && \
6779
rm -rf go1.23.8.linux-amd64.tar.gz && \
6880
apt-get clean && rm -rf /var/lib/apt/lists/*
6981

70-
# RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server
71-
# RUN chmod +x ./server
72-
7382
# Copy OpenMVG and OpenMVS builds from cv_builder
7483
COPY --from=cv_builder /openMVG_build/Linux-x86_64-RELEASE /usr/local/bin
7584
COPY --from=cv_builder /openMVS_build/bin /usr/local/bin
7685

7786
# Copy Blender (only essential parts)
78-
COPY --from=blender_builder /opt/blender/blender /usr/local/bin/blender
79-
COPY --from=blender_builder /opt/blender/4.4/ /opt/blender/4.4/
87+
COPY --from=blender_builder /blender /opt/blender
88+
ENV PATH="/opt/blender:$PATH"
8089

8190
ENV PATH="$PATH:/usr/local/go/bin:/root/go/bin"
8291

app/controller/object_controller.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,56 @@ package controller
22

33
import (
44
"fmt"
5+
"io"
56
"net/http"
6-
"os"
7+
"strconv"
78

9+
"github.com/Soup666/modelmaker/services"
810
"github.com/gin-gonic/gin"
911
)
1012

1113
// AuthController is the controller for handling authentication requests
12-
type ObjectController struct{}
14+
type ObjectController struct {
15+
storageService services.StorageService
16+
}
1317

14-
func NewObjectController() *ObjectController {
15-
return &ObjectController{}
18+
func NewObjectController(storageService services.StorageService) *ObjectController {
19+
return &ObjectController{
20+
storageService: storageService,
21+
}
1622
}
1723

1824
func (c *ObjectController) GetObject(ctx *gin.Context) {
1925
taskId := ctx.Param("taskID")
2026

21-
// Construct the full file path
22-
filePath := fmt.Sprintf("objects/%s/%s", taskId, "mvs/final_model.glb")
27+
// Convert taskId to uint
28+
taskIdInt, err := strconv.ParseUint(taskId, 10, 32)
29+
if err != nil {
30+
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
31+
return
32+
}
2333

24-
// Check if the file exists
25-
if _, err := os.Stat(filePath); os.IsNotExist(err) {
34+
// Get file from object storage
35+
file, err := c.storageService.GetFile(fmt.Sprintf("objects/%d/final.glb", taskIdInt))
36+
if err != nil {
2637
ctx.JSON(http.StatusNotFound, gin.H{"error": "Object not found"})
2738
return
2839
}
29-
30-
// Serve the file
31-
ctx.File(filePath)
40+
defer file.Close()
41+
42+
// Add headers
43+
ctx.Header("Content-Type", "model/gltf-binary")
44+
ctx.Header("Content-Disposition", "attachment; filename=final.glb")
45+
46+
// // Stream the file to the response
47+
// ctx.Stream(func(w io.Writer) bool {
48+
// _, err := io.Copy(w, file)
49+
// return err == nil
50+
// })
51+
// Copy file contents to response writer
52+
_, err = io.Copy(ctx.Writer, file)
53+
if err != nil {
54+
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to stream file"})
55+
return
56+
}
3257
}

app/controller/task_controller.go

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controller
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
"log"
78
"mime/multipart"
89
"net/http"
@@ -23,10 +24,16 @@ type TaskController struct {
2324
TaskService services.TaskService
2425
AppFileService services.AppFileService
2526
VisionService services.VisionService
27+
StorageService services.StorageService
2628
}
2729

28-
func NewTaskController(taskService services.TaskService, appFileService services.AppFileService, visionService services.VisionService) *TaskController {
29-
return &TaskController{TaskService: taskService, AppFileService: appFileService, VisionService: visionService}
30+
func NewTaskController(taskService services.TaskService, appFileService services.AppFileService, visionService services.VisionService, storageService services.StorageService) *TaskController {
31+
return &TaskController{
32+
TaskService: taskService,
33+
AppFileService: appFileService,
34+
VisionService: visionService,
35+
StorageService: storageService,
36+
}
3037
}
3138

3239
func (c *TaskController) GetUnarchivedTasks(ctx *gin.Context) {
@@ -136,7 +143,6 @@ func (c *TaskController) CreateTask(ctx *gin.Context) {
136143
// @Failure 500 {object} map[string]string
137144
// @Router /tasks/{taskID}/upload [post]
138145
func (c *TaskController) UploadFileToTask(ctx *gin.Context) {
139-
140146
// Get the Task ID from the route
141147
taskIdParam := ctx.Param("taskID")
142148
taskId, err := strconv.Atoi(taskIdParam)
@@ -164,13 +170,6 @@ func (c *TaskController) UploadFileToTask(ctx *gin.Context) {
164170
return
165171
}
166172

167-
// Define the upload folder
168-
folderPath := fmt.Sprintf("uploads/%d", taskId)
169-
if err := os.MkdirAll(folderPath, os.ModePerm); err != nil {
170-
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"})
171-
return
172-
}
173-
174173
var uploadedImages []model.AppFile
175174
var wg sync.WaitGroup
176175
var mu sync.Mutex
@@ -192,21 +191,18 @@ func (c *TaskController) UploadFileToTask(ctx *gin.Context) {
192191
return
193192
}
194193

195-
// Generate a unique filename
196-
filename := fmt.Sprintf("%d-%d%s", taskId, index, fileExt)
197-
savePath := filepath.Join(folderPath, filename)
198-
199-
// Save the file
200-
if err := ctx.SaveUploadedFile(file, savePath); err != nil {
194+
// Upload to object storage
195+
url, err := c.StorageService.UploadFile(file, uint(taskId), "upload")
196+
if err != nil {
201197
ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to save file %s", file.Filename)})
202198
hasError = true
203199
return
204200
}
205201

206202
// Save metadata to DB
207203
image := model.AppFile{
208-
Filename: filename,
209-
Url: fmt.Sprintf("/uploads/%d/%s", taskId, filename),
204+
Filename: file.Filename,
205+
Url: url,
210206
TaskId: uint(taskId),
211207
FileType: "upload",
212208
}
@@ -232,37 +228,42 @@ func (c *TaskController) UploadFileToTask(ctx *gin.Context) {
232228
tx.Commit()
233229

234230
go func() {
235-
// Generate caption
236-
result, err := c.VisionService.AnalyseImage(fmt.Sprintf("./uploads/%d/%s", taskId, uploadedImages[0].Filename), "")
231+
// Get the first image URL from object storage
232+
file, err := c.StorageService.GetFile(fmt.Sprintf("uploads/%d/%s", taskId, uploadedImages[0].Filename))
233+
if err != nil {
234+
log.Printf("Unable to get image for analysis: %v", err)
235+
return
236+
}
237+
defer file.Close()
237238

239+
// Create a temporary file
240+
tempFile, err := os.CreateTemp("", "analysis-*.jpg")
238241
if err != nil {
239-
log.Printf("Unable to analyze the image: %v", err)
242+
log.Printf("Unable to create temp file: %v", err)
240243
return
241244
}
245+
defer os.Remove(tempFile.Name())
246+
defer tempFile.Close()
242247

243-
if err := c.TaskService.UpdateMeta(&task, "ai-description", result); err != nil {
244-
log.Printf("Failed to update task metadata: %v", err)
248+
// Copy the file content
249+
if _, err := io.Copy(tempFile, file); err != nil {
250+
log.Printf("Unable to copy file content: %v", err)
251+
return
245252
}
246-
}()
247253

248-
go func() {
249254
// Generate caption
250-
result, err := c.VisionService.AnalyseImage(fmt.Sprintf("./uploads/%d/%s", taskId, uploadedImages[0].Filename), "categorize the model in this image, use one word only")
251-
255+
result, err := c.VisionService.AnalyseImage(tempFile.Name(), "")
252256
if err != nil {
253257
log.Printf("Unable to analyze the image: %v", err)
254258
return
255259
}
256260

257-
if err := c.TaskService.UpdateMeta(&task, "ai-title", result); err != nil {
261+
if err := c.TaskService.UpdateMeta(&task, "ai-description", result); err != nil {
258262
log.Printf("Failed to update task metadata: %v", err)
259263
}
260264
}()
261265

262-
ctx.JSON(http.StatusOK, gin.H{
263-
"message": "Files uploaded successfully",
264-
"images": uploadedImages,
265-
})
266+
ctx.JSON(http.StatusOK, gin.H{"message": "Files uploaded successfully", "images": uploadedImages})
266267
}
267268

268269
// StartProcess handles the process of starting the photogrammetry process
@@ -375,7 +376,7 @@ func (c *TaskController) SendMessage(ctx *gin.Context) {
375376
return
376377
}
377378

378-
imagePath := fmt.Sprintf("./uploads/%d/%s", taskId, task.Images[0].Filename)
379+
imagePath := fmt.Sprintf("uploads/%d/%s", taskId, task.Images[0].Filename)
379380

380381
if _, err := os.Stat(imagePath); os.IsNotExist(err) {
381382
log.Printf("Image file does not exist: %v\n", err)

app/controller/task_controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ func TestTaskController(t *testing.T) {
1717
mockTaskService := new(mocks.MockTaskService)
1818
mockAppFileService := new(mocks.MockAppFileService)
1919
mockVisionService := new(mocks.MockVisionService)
20+
mockStorageService := new(mocks.MockStorageService)
2021

21-
taskController := controller.NewTaskController(mockTaskService, mockAppFileService, mockVisionService)
22+
taskController := controller.NewTaskController(mockTaskService, mockAppFileService, mockVisionService, mockStorageService)
2223

2324
t.Run("GetUnarchivedTasks", func(t *testing.T) {
2425
recorder, c := utils.SetupRecorder()

0 commit comments

Comments
 (0)