Work on endpoints involving file management, To be tested, WIP

This commit is contained in:
Sonja Happ 2019-05-20 16:17:54 +02:00
parent 75f8ef280d
commit 17d88f4c67
4 changed files with 394 additions and 179 deletions

View file

@ -2,8 +2,14 @@ package file
import (
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/gin-gonic/gin"
_ "github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
@ -28,7 +34,6 @@ func FindUserFiles(user *common.User) ([]common.File, int, error) {
}
func FindFile(userID int, fileID string) ( common.File, error) {
//TODO Check here if user owns the file
var file common.File
db := common.GetDB()
fileID_i, _ := strconv.Atoi(fileID)
@ -37,4 +42,287 @@ func FindFile(userID int, fileID string) ( common.File, error) {
return file, err
}
func FindFileByPath(path string) (common.File, error) {
var file common.File
db := common.GetDB()
err := db.Where("Path = ?", path).Find(file).Error
return file, err
}
func RegisterFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){
// Extract file from PUT request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return;
}
// Obtain properties of file
filetype := file_header.Header.Get("Content-Type") // TODO make sure this is properly set in file header
filename := filepath.Base(file_header.Filename)
foldername := getFolderName(simulationID, simulationmodelID, widgetID)
size := file_header.Size
// Save file to local disc (NOT DB!)
err = modifyFileOnDisc(file_header, filename, foldername, uint(size), true)
if err != nil {
errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"error": errormsg,
})
return
}
// Add File object with parameters to DB
saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, simulationmodelID, true)
}
func UpdateFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){
// Extract file from PUT request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return;
}
filename := filepath.Base(file_header.Filename)
filetype := file_header.Header.Get("Content-Type") // TODO make sure this is properly set in file header
size := file_header.Size
foldername := getFolderName(simulationID, simulationmodelID, widgetID)
err = modifyFileOnDisc(file_header, filename, foldername, uint(size), false)
if err != nil {
errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"error": errormsg,
})
return
}
saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, simulationmodelID, false)
}
func ReadFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){
contentType := c.GetHeader("Content-Type")
db := common.GetDB()
var fileInDB common.File
if widgetID != -1 {
// get associated Widget
var wdgt common.Widget
err := db.First(&wdgt, simulationmodelID).Error
if common.ProvideErrorResponse(c, err) {
return
}
err = db.Model(&wdgt).Related(&fileInDB).Where("Type = ?", contentType).Error
if common.ProvideErrorResponse(c, err) {
return
}
} else if simulationmodelID != -1 {
// get associated Simulation Model
var model common.SimulationModel
err := db.First(&model, simulationmodelID).Error
if common.ProvideErrorResponse(c, err) {
return
}
err = db.Model(&model).Related(&fileInDB).Where("Type = ?", contentType).Error
if common.ProvideErrorResponse(c, err) {
return
}
}
//Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+fileInDB.Name )
c.Header("Content-Type", contentType)
c.File(fileInDB.Path)
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
func DeleteFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){
// TODO
}
func saveFileInDB(c *gin.Context, filename string, foldername string, filetype string, size uint, widgetID int, simulationmodelID int, createObj bool) {
filesavepath := filepath.Join(foldername, filename)
// get last modify time of target file
fileinfo, err := os.Stat(filesavepath)
if err != nil {
errormsg := fmt.Sprintf("Internal Server Error. Error stat on file: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"error": errormsg,
})
return
}
modTime := fileinfo.ModTime()
// Create file object for Database
var fileObj common.File
fileObj.Size = uint(size)
fileObj.Type = filetype
fileObj.Path = filesavepath
fileObj.Date = modTime
fileObj.Name = filename
fileObj.ImageHeight = 0
fileObj.ImageWidth = 0
// Check if file shall be associated with Widget or Simulation Model
db := common.GetDB()
if widgetID != -1 {
if createObj {
// associate to Widget
var wdgt common.Widget
err := db.First(&wdgt, widgetID).Error
if common.ProvideErrorResponse(c, err) {
return
}
err = db.Model(&wdgt).Association("Files").Append(&fileObj).Error
} else {
// update file obj in DB
fileInDB, err := FindFileByPath(filesavepath)
if common.ProvideErrorResponse(c, err){
return
}
err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error
}
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
"fileID": fileObj.ID,
})
return
}
}
if simulationmodelID != -1 {
if createObj {
// associate to Simulation Model
db := common.GetDB()
var model common.SimulationModel
err := db.First(&model, simulationmodelID).Error
if common.ProvideErrorResponse(c, err) {
return
}
err = db.Model(&model).Association("Files").Append(&fileObj).Error
} else {
// update file obj in DB
fileInDB, err := FindFileByPath(filesavepath)
if common.ProvideErrorResponse(c, err){
return
}
err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error
}
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
"fileID": fileObj.ID,
})
return
}
}
}
func modifyFileOnDisc(file_header *multipart.FileHeader, filename string, foldername string, size uint, createFile bool) error {
filesavepath := filepath.Join(foldername, filename)
var err error
if createFile {
// Ensure folder with name foldername exists
err = os.MkdirAll(foldername, os.ModePerm)
} else {
// test if file exists
_, err = os.Stat(filesavepath)
}
if err != nil {
return err
}
var open_options int
if createFile {
// create file it not exists, file MUST not exist
open_options = os.O_RDWR|os.O_CREATE|os.O_EXCL
} else {
open_options = os.O_RDWR
}
fileTarget , err := os.OpenFile(filesavepath, open_options, 0666)
if err != nil {
return err
}
defer fileTarget.Close()
// Save file to target path
uploadedFile, err := file_header.Open()
if err != nil {
return err
}
defer uploadedFile.Close()
var uploadContent = make([]byte, size)
for {
n, err := uploadedFile.Read(uploadContent)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
_, err = fileTarget.Write(uploadContent[:n])
if err != nil {
return err
}
}
return err
}
func getFolderName(simulationID int, simulationmodelID int, widgetID int) string {
base_foldername := "files/"
elementname := ""
elementid := 0
if simulationmodelID == -1{
elementname = "/widget_"
elementid = widgetID
} else {
elementname = "/simulationmodel_"
elementid = simulationmodelID
}
foldername := base_foldername + "simulation_"+ string(simulationID) + elementname + string(elementid) + "/"
return foldername
}

View file

@ -1,22 +1,21 @@
package simulation
import (
"github.com/gin-gonic/gin"
"fmt"
"net/http"
"path/filepath"
"strconv"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"github.com/gin-gonic/gin"
)
func SimulationsRegister(r *gin.RouterGroup) {
//simulations
r.GET("/", simulationsReadEp)
r.POST("/", simulationRegistrationEp)
r.PUT("/:SimulationID", simulationUpdateEp)
r.GET("/:SimulationID", simulationReadEp)
r.DELETE("/:SimulationID", simulationDeleteEp)
r.POST ("/:SimulationID/models/:SimulationModelID/file", fileRegistrationEp) // NEW
}
func simulationsReadEp(c *gin.Context) {
@ -51,66 +50,18 @@ func simulationDeleteEp(c *gin.Context) {
})
}
func fileRegistrationEp(c *gin.Context) {
func GetSimulationID(c *gin.Context) (int, error) {
// TODO Check if file upload is ok for this user or simulation (user or simulation exists)
var widgetID_s = c.Param("WidgetID")
var widgetID_i int
var simulationmodelID_s = c.Param("SimulationModelID")
var simulationmodelID_i int
simulationID, err := strconv.Atoi(c.Param("SimulationID"))
if widgetID_s != "" {
widgetID_i, _ = strconv.Atoi(widgetID_s)
} else {
widgetID_i = -1
}
if simulationmodelID_s != "" {
simulationmodelID_i, _ = strconv.Atoi(simulationmodelID_s)
} else {
simulationmodelID_i = -1
}
if simulationmodelID_i == -1 && widgetID_i == -1 {
errormsg := fmt.Sprintf("Bad request. Did not provide simulation model ID or widget ID for file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation ID")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return;
return -1, err
} else {
return simulationID, err
}
// Extract file from POST request form
file, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return;
}
// Obtain properties of file
filetype := file.Header.Get("Content-Type") // TODO make sure this is properly set in file header
filename := filepath.Base(file.Filename)
foldername := "files/testfolder" //TODO replace this placeholder with systematic foldername (e.g. simulation ID)
size := file.Size
// Save file to local disc (NOT DB!)
err = SaveFile(file, filename, foldername, uint(size))
if err != nil {
errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"error": errormsg,
})
return
}
// Add File object with parameters to DB
err = AddFile(filename, foldername, filetype, uint(size), widgetID_i, simulationmodelID_i )
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}

View file

@ -1,11 +1,6 @@
package simulation
import (
"io"
"mime/multipart"
"os"
"path/filepath"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
@ -22,102 +17,3 @@ func FindUserSimulations(user *common.User) ([]common.Simulation, int, error) {
err := db.Model(user).Related(&simulations, "Simulations").Error
return simulations, len(simulations), err
}
func AddFile(filename string, foldername string, filetype string, size uint, widgetID int, simulationmodelID int ) error {
filesavepath := filepath.Join(foldername, filename)
// get last modify time of target file
fileinfo, err := os.Stat(filesavepath)
if err != nil {
return err
}
modTime := fileinfo.ModTime()
// Create file object for Database
var fileObj common.File
fileObj.Size = uint(size)
fileObj.Type = filetype
fileObj.Path = filesavepath
fileObj.Date = modTime
fileObj.Name = filename
fileObj.ImageHeight = 0
fileObj.ImageWidth = 0
// Check if file shall be associated with Widget or Simulation Model
db := common.GetDB()
if widgetID != -1 {
// associate to Widget
var widget common.Widget
err := db.First(&widget, widgetID).Error
if err != nil {
return err
}
err = db.Model(&widget).Association("Files").Append(&fileObj).Error
}
if simulationmodelID != -1 {
// associate to Simulation Model
var model common.SimulationModel
err := db.First(&model, simulationmodelID).Error
if err != nil {
return err
}
err = db.Model(&model).Association("Files").Append(&fileObj).Error
}
return err
}
func SaveFile(file *multipart.FileHeader, filename string, foldername string, size uint, ) error {
filesavepath := filepath.Join(foldername, filename)
// Ensure folder with name foldername exists
err := os.MkdirAll(foldername, os.ModePerm)
if err != nil {
// TODO error handling
return err
}
fileTarget , errcreate := os.OpenFile(filesavepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if errcreate != nil {
// TODO error handling: File could not be created
return errcreate
}
defer fileTarget.Close()
// Save file to target path
uploadedFile, erropen := file.Open()
if erropen != nil {
// TODO error handling
return erropen
}
defer uploadedFile.Close()
var uploadContent = make([]byte, size)
for {
n, err := uploadedFile.Read(uploadContent)
if err != nil && err != io.EOF {
// TODO error handling
return err
}
if n == 0 {
break
}
_, err = fileTarget.Write(uploadContent[:n])
if err != nil {
// TODO error handling
return err
}
}
return err
}

View file

@ -1,18 +1,31 @@
package simulationmodel
import (
"github.com/gin-gonic/gin"
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func SimulationModelsRegister(r *gin.RouterGroup) {
r.GET("/", simulationmodelsReadEp)
r.POST("/", simulationmodelRegistrationEp)
r.PUT("/:SimulationModelID", simulationmodelUpdateEp)
r.GET("/:SimulationModelID", simulationmodelReadEp)
r.DELETE("/:SimulationModelID", simulationmodelDeleteEp)
r.GET("/:SimulationModelID/file", simulationmodelReadFileEp) // NEW in API
r.PUT("/:SimulationModelID/file", simulationmodelUpdateFileEp) // NEW in API
r.GET("/:SimulationID/models/", simulationmodelsReadEp)
r.POST("/:SimulationID/models/", simulationmodelRegistrationEp)
r.PUT("/:SimulationID/models/:SimulationModelID", simulationmodelUpdateEp)
r.GET("/:SimulationID/models/:SimulationModelID", simulationmodelReadEp)
r.DELETE("/:SimulationID/models/:SimulationModelID", simulationmodelDeleteEp)
// Files
r.POST ("/:SimulationID/models/:SimulationModelID/file", simulationmodelRegisterFileEp) // NEW in API
r.GET("/:SimulationID/models/:SimulationModelID/file", simulationmodelReadFileEp) // NEW in API
r.PUT("/:SimulationID/models/:SimulationModelID/file", simulationmodelUpdateFileEp) // NEW in API
r.DELETE("/:SimulationID/models/:SimulationModelID/file", simulationmodelDeleteFileEp) // NEW in API
}
func simulationmodelsReadEp(c *gin.Context) {
@ -47,14 +60,81 @@ func simulationmodelDeleteEp(c *gin.Context) {
})
}
func simulationmodelRegisterFileEp(c *gin.Context) {
simulationID, simulationmodelID, err := getRequestParams(c)
if err != nil{
return
}
// Save file locally and register file in DB, HTTP response is set by this method
file.RegisterFile(c,-1, simulationmodelID, simulationID)
}
func simulationmodelReadFileEp(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
simulationID, simulationmodelID, err := getRequestParams(c)
if err != nil{
return
}
// Read file from disk and return in HTTP response, no change to DB
file.ReadFile(c, -1, simulationmodelID, simulationID)
}
func simulationmodelUpdateFileEp(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
simulationID, simulationmodelID, err := getRequestParams(c)
if err != nil{
return
}
// Update file locally and update file entry in DB, HTTP response is set by this method
file.UpdateFile(c,-1, simulationmodelID, simulationID)
}
func simulationmodelDeleteFileEp(c *gin.Context) {
simulationID, simulationmodelID, err := getRequestParams(c)
if err != nil{
return
}
// Delete file from disk and remove entry from DB, HTTP response is set by this method
file.DeleteFile(c, -1, simulationmodelID, simulationID)
}
func GetSimulationmodelID(c *gin.Context) (int, error) {
simulationmodelID, err := strconv.Atoi(c.Param("SimulationModelID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation model ID")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return -1, err
} else {
return simulationmodelID, err
}
}
func getRequestParams(c *gin.Context) (int, int, error){
simulationID, err := simulation.GetSimulationID(c)
if err != nil{
return -1, -1, err
}
simulationmodelID, err := GetSimulationmodelID(c)
if err != nil{
return -1, -1, err
}
return simulationID, simulationmodelID, err
}