package storage import ( "crypto/sha256" "encoding/hex" "fmt" "io" "log" "net/http" "os" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/ln" "git.sp4ke.com/sp4ke/bit4sat/utils" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) const ( SessLastUploadName = "last-upload" ) 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 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 } // Set upload status to new err = SetUploadStatus(uploadId, UpNew) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Upload seems valid, handle new upload procedure session := sessions.Default(c) // Set the uploadId as last upload for session session.Set(SessLastUploadName, uploadId) session.Save() // If not free upload generate a an invoice // TODO: check if upload fee is free if true { // New invoice for 10$ invoice, err := ln.NewInvoiceForUpload(100, ln.CurSat, uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Update Upload Invoice err = SetUploadInvoice(uploadId, invoice) 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 //} c.JSON(http.StatusOK, gin.H{ "status": UpNew, "invoice": invoice, "upload_id": uploadId, }) return // Handle free uploads } else { log.Println("new upload created") c.JSON(http.StatusOK, gin.H{ "status": "ready for upload", "result": gin.H{ "id": uploadId, }, }) } } func (ctrl UploadCtrl) CheckStatus(c *gin.Context) { 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, "upload id not found") return } // Get upload status uploadStatus, err := GetUploadStatus(uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } if uploadStatus.IsNew() { c.JSON(http.StatusOK, gin.H{ "status": uploadStatus, }) return } // Get invoice id for upload invoiceId, err := GetUploadInvoiceId(uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Get invoice invoice, err := ln.CheckInvoice(invoiceId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } c.JSON(InvoiceHttpCode(invoice), gin.H{ "upload_id": uploadId, "invoice": invoice, "status": uploadStatus, }) } func (ctrl UploadCtrl) PollStatus(c *gin.Context) { 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 } uploadStatus, err := GetUploadStatus(uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } if uploadStatus&UpNew != 0 { c.JSON(http.StatusCreated, gin.H{ "status": uploadStatus, }) return } // we are not in UpNew state so we should have an invoice invoice, err := GetUploadInvoice(uploadId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } if uploadStatus.WaitPay() { invoiceChan := make(chan *ln.Invoice) errorChan := make(chan error) log.Println("starting go routine") // If waiting payment, wait until invoice is paid go ln.PollPaidInvoice(invoice.RHash, invoiceChan, errorChan) // Block until payment done or error log.Println("blocking") select { case invoice := <-invoiceChan: // if expired return with expired response if invoice.Expired { err := SetUploadStatus(uploadId, UpPayExpired) if err != nil { log.Printf("Redis error: %s", err) } c.JSON(http.StatusGone, gin.H{ "status": uploadStatus, "invoice": invoice, }) return } //////////////// ///// Invoice was paid //////////////// uploadStatus |= UpPaid err := SetUploadStatus(uploadId, uploadStatus) if err != nil { log.Printf("Redis error: %s", err) } // Update Upload Invoice err = SetUploadInvoice(uploadId, invoice) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // invoice was paid c.JSON(http.StatusAccepted, gin.H{ "status": uploadStatus, "invoice": invoice, }) return case err := <-errorChan: utils.JSONErrPriv(c, http.StatusInternalServerError, err) } } else { c.JSON(http.StatusOK, gin.H{ "status": uploadStatus, "invoice": invoice, }) } return } 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 } // Check if payment done // NOTE: we pre upload and remove the files if the payment was expired //uploadStatus, err := GetUploadStatus(uploadId) //if err != nil { //utils.JSONErrPriv(c, http.StatusInternalServerError, err) //return //} //if uploadStatus < UpWaitingStorage { //utils.JSONErr(c, http.StatusPaymentRequired, //fmt.Sprintf("invoice not paid")) //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) // If file already stored for this upload report conflict if up.Stored { 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 file upload stored status") err = up.TxSetFileStored(tx) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) // Rollback and remove the file 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 } // Set the whole transaction as stored 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", "store_status": UpStored, }) }