You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bit4sat/storage/upload_ctrl.go

284 lines
6.0 KiB
Go

package storage
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"git.sp4ke.com/sp4ke/bit4sat/bus"
"git.sp4ke.com/sp4ke/bit4sat/db"
"git.sp4ke.com/sp4ke/bit4sat/ln"
"git.sp4ke.com/sp4ke/bit4sat/utils"
"git.sp4ke.com/sp4ke/bit4sat/ws"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/mediocregopher/radix/v3"
)
type UploadCtrl struct{}
//TODO: tell client to avoid sending duplicate if we already have hash
func (ctrl UploadCtrl) New(c *gin.Context) {
uploadForm := UploadForm{}
if err := c.ShouldBindJSON(&uploadForm); err != nil {
utils.JSONErr(c, http.StatusNotAcceptable,
"could not parse form")
return
}
// Create unique id
uploadId, err := GetSIDGenerator().Generate()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
tx, err := db.DB.Sql.Beginx()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
for _, file := range uploadForm.Files {
up := &Upload{}
up.ID = uploadId
up.FileName, up.FileExt = utils.CleanFileName(file.Name)
up.FileSize = file.Size
up.FileType = file.Type
up.SHA256 = file.SHA256
up.Status = UpNew
err := up.TxWrite(tx)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, nil)
return
}
}
err = tx.Commit()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Register this uploadId to the client's websocket update channel
// websocket on the pubsub channel update_websocket_[websocket id]
session := sessions.Default(c)
webSocketId := session.Get(ws.WebsocketIdName)
if webSocketId == nil {
utils.JSONErrPriv(c, http.StatusInternalServerError,
fmt.Errorf("no session id found for web socket"))
return
}
msg := bus.Message{
UploadId: uploadId,
}
msg.Type = bus.SetUploadId
jsonMsg, err := json.Marshal(msg)
channelName := fmt.Sprintf("%s_%s", bus.WebsocketPubSubPrefix, webSocketId)
if err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
channelName, jsonMsg)); err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
err = SetUploadStatus(uploadId, UpNew)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// If not free upload generate a an invoice
// TODO: check if upload fee is free
if true {
// New invoice for 10$
invoice, err := ln.UploadInvoice(100, ln.CurSat, uploadId)
invoice.UploadId = uploadId
//info, err := ln.Info()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
err = SetUploadStatus(uploadId, UpWaitingPayment)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Start watching upload payment status for this client
err = WatchUploadPayment(invoice)
if err != nil {
log.Println("error watching payment ", err)
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
result := gin.H{
"upload_id": uploadId,
"invoice": invoice,
}
c.JSON(http.StatusOK, gin.H{
"status": "waiting for payment",
"result": result,
})
return
} else {
//Else, free upload accepted
log.Println("new upload created")
c.JSON(http.StatusOK, gin.H{
"status": "ready for upload",
"result": gin.H{
"id": uploadId,
},
})
}
}
func (ctrl UploadCtrl) Upload(c *gin.Context) {
// Check that upload ID exists
uploadId := c.Param("id")
exists, err := IdExists(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if !exists {
utils.JSONErr(c, http.StatusNotFound, "id not found")
return
}
form, err := c.MultipartForm()
if err != nil {
utils.JSONErr(c, http.StatusNotAcceptable,
fmt.Sprintf("Could not parse form: %s", err.Error()))
return
}
files := form.File["upload[]"]
tx, err := db.DB.Sql.Beginx()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
for _, file := range files {
log.Printf("Handling %s", file.Filename)
// Get the file's sha256
hasher := sha256.New()
formFd, err := file.Open()
defer formFd.Close()
if err != nil {
utils.JSONErr(
c, http.StatusInternalServerError,
fmt.Sprintf("Could not read file %s", file.Filename),
)
db.RollbackTx(tx, nil)
return
}
_, err = io.Copy(hasher, formFd)
if err != nil {
utils.JSONErr(c, http.StatusInternalServerError,
"")
db.RollbackTx(tx, nil)
return
}
sum256 := hex.EncodeToString(hasher.Sum(nil))
// Get the file's metadata from upload table
up, err := GetByHashID(sum256, uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusNotFound, err)
db.RollbackTx(tx, nil)
return
}
log.Printf("Found received file's %s metadata in local database\n", up.FileName)
//log.Println(up)
// Store file
if up.Status == UpStored {
utils.JSONErr(c, http.StatusConflict,
fmt.Sprintf("%s already uploaded", up.FileName))
return
}
//
//
log.Println("Storing file")
err = StoreFormFile(formFd, up.SHA256+up.FileExt)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, nil)
return
}
log.Printf("%s stored at %s", up.FileName,
GetStoreDestination(up.SHA256+up.FileExt))
// Setting status to stored
log.Println("Updating upload status to stored")
up.Status = UpStored
err = up.TxSetState(tx, UpStored)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, func() {
err := os.Remove(GetStoreDestination(up.SHA256 + up.FileExt))
if err != nil {
log.Println(err)
}
})
return
}
}
err = tx.Commit()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
err = SetUploadStatus(uploadId, UpStored)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"result": "uploaded",
})
}