package api import ( "encoding/gob" "fmt" "log" "net/http" "time" "git.sp4ke.com/sp4ke/bit4sat/db" "git.sp4ke.com/sp4ke/bit4sat/ln" "git.sp4ke.com/sp4ke/bit4sat/storage" "git.sp4ke.com/sp4ke/bit4sat/utils" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/mediocregopher/radix/v3" ) func download(c *gin.Context) { var ok bool dlId := c.Param("dlId") // Get upload id for dl id upId, err := storage.GetUploadIdForDlId(dlId) if err != nil { utils.JSONErr(c, http.StatusNotFound, "this file does not exist anymore") return } // Get files metadata for upload var upFilesMeta []storage.FileUpload upFilesMeta, err = storage.GetUploadFilesMeta(upId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Get the download session session, err := SessionStore.Get(c.Request, DlSessionKey) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } dlSessVal := session.Values["session-id"] var dlSess string if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok { log.Printf("error parsing session dlSessVal") utils.JSONErr(c, http.StatusExpectationFailed, "start a new download session session") return } dlIdVal := session.Values["dlId"] var sessDlId string // If no dlId was stored before than this is a new download if dlIdVal == nil { sessDlId = dlId } else { if sessDlId, ok = dlIdVal.(string); !ok { log.Printf("cannot cast sessDlId value") utils.JSONErr(c, http.StatusExpectationFailed, "start a new download session session") return } } // Test if we are alread in a download session // If the stored dlId is different than the // current dl id it means we're downloading a different // file if dlSessVal == nil || (sessDlId != dlId) { // This is a new download session log.Println("new download session") session.Values["dlId"] = dlId // Create a unique session id for this download session sessId, err := storage.GetShortId() if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } log.Printf("going to generate invoice for %s", upId) //TODO: use fee asked by uploader // // Get upload data asked by uploader up, err := storage.GetUploadById(upId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } invoiceOpts := ln.InvoiceOpts{ Amount: up.AskAmount, Curr: ln.CurrencyID[up.AskCurrency], Memo: fmt.Sprintf("bit4sat download: %s", sessId), } log.Printf("invoice opts %#v", invoiceOpts) invoice, err := ln.NewInvoice(invoiceOpts) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Store invoice_id ---> upload_id key := fmt.Sprintf("invoice_id_%s_upload_id", invoice.RHash) err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", key, upId)) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Store invoice_ invoiceKey := fmt.Sprintf("invoice_%s", invoice.RHash) err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceKey, invoice)) session.Values["session-id"] = sessId session.Values["invoice-id"] = invoice.RHash invoiceExpires := time.Time(invoice.ExpiresAt).Sub(time.Now()).Seconds() //TODO: this session can be reused during this hour session.Options.MaxAge = int(invoiceExpires) //log.Printf("session: %#v", session.Values) err = session.Save(c.Request, c.Writer) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } c.JSON(http.StatusPaymentRequired, gin.H{ "invoice": invoice, "files": upFilesMeta, }) // This is a returning download session to pay the invoice } else { log.Printf("continue download session id: %s", dlSess) // Get invoice-id linked to sessionId invIdVal := session.Values["invoice-id"] if invIdVal == nil { session.Options.MaxAge = -1 err = session.Save(c.Request, c.Writer) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } utils.JSONErr(c, http.StatusExpectationFailed, "invoice not found, start a new download session session") return } var invoiceId string if invoiceId, ok = invIdVal.(string); !ok { log.Printf("Could not find invoice in session %s", dlSess) session.Options.MaxAge = -1 err = session.Save(c.Request, c.Writer) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } utils.JSONErr(c, http.StatusExpectationFailed, "invoice not found, start a new download session session") return } // Get invoice from invoice_id invoiceKey := fmt.Sprintf("invoice_%s", invoiceId) invoice := ln.Invoice{} err = db.DB.Redis.Do(radix.FlatCmd(&invoice, "GET", invoiceKey)) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // If invoice paid create an authorized download link linked to session // The link will expire after after one hour // Also bump up the expirey of the DlSession to one hour if invoice.Settled { log.Println("dl invoice paid, sending") downKey, err := storage.GetShortId() if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey) // We store the upload id in the value to use it when retreiving // files db.SetKeyVal(key, upId) db.ExpireKey(key, MaxAgeDlKeySession) // Session will also expire in 1 hour session.Options.MaxAge = MaxAgeDlKeySession err = session.Save(c.Request, c.Writer) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } c.JSON(http.StatusOK, gin.H{ "download_key": downKey, "invoice": invoice, }) return } c.JSON(http.StatusPaymentRequired, gin.H{ "invoice": invoice, "files": upFilesMeta, }) } return } func getFiles(c *gin.Context) { downKey := c.Param("dlKey") // Get the session session, err := SessionStore.Get(c.Request, DlSessionKey) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } dlSessVal := session.Values["session-id"] var dlSess string var ok bool if dlSess, ok = dlSessVal.(string); dlSessVal != nil && !ok { log.Printf("error parsing session dlSessVal") utils.JSONErr(c, http.StatusExpectationFailed, "user session lost, start a new download session session") return } if dlSessVal == nil { utils.JSONErr(c, http.StatusGone, "the download session has expired") return } key := fmt.Sprintf("session_%s_download_key_%s", dlSess, downKey) // Check if download key exists in db exists, err := db.Exists(key) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } if !exists { utils.JSONErr(c, http.StatusGone, "the download session has expired") return } // We should now allow the user to get the files // We retreive the files from the upId stored on the session download key var upId string err = db.GetFromKey(key, &upId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } // Get files metadata for upload var upFilesMeta []storage.FileUpload upFilesMeta, err = storage.GetUploadFilesMeta(upId) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } c.Header("Content-Type", "application/zip") c.Header("Content-Disposition", "attachment; filename=download.zip") err = storage.ZipFiles(upFilesMeta, c.Writer) if err != nil { utils.JSONErrPriv(c, http.StatusInternalServerError, err) return } return } func TestDownHandler(c *gin.Context) { sess := sessions.Default(c) test := sess.Get("test") log.Printf("%#v", test) if test != nil { sess.Clear() sess.Save() c.String(http.StatusOK, "I remember you") } else { sess.Set("test", 1) sess.Save() c.String(http.StatusOK, "i dont remember you") } return } func init() { gob.Register(&ln.Invoice{}) gob.Register(&[]storage.FileUpload{}) }