Working download !!!

master
Chakib Benziane 5 years ago
parent 4985b3aea3
commit 44de8bc448

@ -7,11 +7,13 @@ import (
"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) {
@ -26,6 +28,14 @@ func download(c *gin.Context) {
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 {
@ -80,16 +90,6 @@ func download(c *gin.Context) {
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
}
session.Values["files"] = upFilesMeta
invoiceOpts := ln.InvoiceOpts{
Amount: up.AskAmount,
Curr: ln.CurrencyID[up.AskCurrency],
@ -104,10 +104,20 @@ func download(c *gin.Context) {
return
}
// Store invoice
// 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_<invoice_id>
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"] = invoice
session.Values["invoice-id"] = invoice.RHash
invoiceExpires := time.Time(invoice.ExpiresAt).Sub(time.Now()).Seconds()
@ -131,10 +141,9 @@ func download(c *gin.Context) {
} else {
log.Printf("continue download session id: %s", dlSess)
invVal := session.Values["invoice"]
var invoice = &ln.Invoice{}
if invoice, ok = invVal.(*ln.Invoice); !ok {
log.Printf("Could not find invoice in session %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)
@ -148,14 +157,9 @@ func download(c *gin.Context) {
return
}
//TODO: Check if invoice paid ??
//If paid return the files else return the invoice to pay
filesVal := session.Values["files"]
var filesMeta = &[]storage.FileUpload{}
if filesMeta, ok = filesVal.(*[]storage.FileUpload); !ok {
// The cookie broke ???
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)
@ -165,13 +169,42 @@ func download(c *gin.Context) {
}
utils.JSONErr(c, http.StatusExpectationFailed,
"start a new download session session")
"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 send the files
if invoice.Settled {
log.Println("dl invoice paid, sending")
// set response type to zip mime type
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename=download.zip")
err := storage.ZipFiles(upFilesMeta, c.Writer)
if err != nil {
log.Fatal(err)
}
//c.JSON(http.StatusOK, gin.H{
//"download": "ok",
//"invoice": invoice,
//})
return
}
c.JSON(http.StatusPaymentRequired, gin.H{
"invoice": invoice,
"files": filesMeta,
"files": upFilesMeta,
})
}

@ -7,7 +7,6 @@ import (
"git.sp4ke.com/sp4ke/bit4sat/db"
"git.sp4ke.com/sp4ke/bit4sat/ln"
"git.sp4ke.com/sp4ke/bit4sat/lndrpc"
"git.sp4ke.com/sp4ke/bit4sat/storage"
"git.sp4ke.com/sp4ke/bit4sat/utils"
"github.com/gin-gonic/gin"
@ -73,25 +72,63 @@ func pollInvoice(c *gin.Context) {
invoiceId := c.Param("rhash")
// First check if already paid
lnInv, err := lndrpc.LookupInvoiceRhashStr(invoiceId)
// Get the 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 {
log.Println(err)
utils.JSONErr(c, http.StatusNotFound,
"invoice not found !")
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
invoice := ln.InvoiceFromLndIn(lnInv)
res := gin.H{
"invoice": invoice,
}
// If paid return ok
if lnInv.Settled {
c.JSON(http.StatusOK, res)
if invoice.Settled {
c.JSON(http.StatusOK, gin.H{
"invoice": invoice,
})
// Poll
} else {
c.JSON(http.StatusPaymentRequired, res)
invoiceChan := make(chan *ln.Invoice)
errorChan := make(chan error)
log.Printf("starting invoice poll for %s", invoice.RHash)
// If waiting payment, wait until invoice is paid
go ln.PollPaidInvoice(invoice.RHash, invoiceChan, errorChan)
select {
case invoice := <-invoiceChan:
// if expired return with expired response
if invoice.Expired {
c.JSON(http.StatusGone, gin.H{
"invoice": invoice,
})
return
}
////////////////
///// Invoice was paid
////////////////
//
log.Println("POLL: invoice was paid")
c.JSON(http.StatusOK, gin.H{
"invoice": invoice,
})
case err := <-errorChan:
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
}
// We stopped waiting for the invoice, it must have expired
c.JSON(http.StatusGone, gin.H{
"invoice": invoice,
})
}
return
@ -115,7 +152,7 @@ func invoiceCbHandler(c *gin.Context) {
// get upload id related to invoice
var uploadId string
invoiceUploadKey := fmt.Sprintf("invoice_%s_upload", invoice.RHash)
invoiceUploadKey := fmt.Sprintf("invoice_%s_uploadid", invoice.RHash)
err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", invoiceUploadKey))
if err != nil {

@ -289,21 +289,6 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
case invoice := <-invoiceChan:
log.Printf("poll: received invoice notif %s", invoice.RHash)
// if expired return with expired response
if invoice.Expired {
err := storage.SetUploadStatus(uploadId, storage.UpPayExpired)
if err != nil {
log.Printf("Redis error: %s", err)
}
c.JSON(http.StatusGone, gin.H{
"status": uploadStatus,
"invoice": invoice,
})
return
}
////////////////
///// Invoice was paid
////////////////
@ -368,8 +353,22 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
case err := <-errorChan:
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// We stopped waiting for the invoice, it must have expired
err := storage.SetUploadStatus(uploadId, storage.UpPayExpired)
if err != nil {
log.Printf("Redis error: %s", err)
}
c.JSON(http.StatusGone, gin.H{
"status": uploadStatus,
"invoice": invoice,
})
return
} else {
c.JSON(http.StatusOK, gin.H{
"status": uploadStatus,

@ -0,0 +1,13 @@
package db
import (
"github.com/mediocregopher/radix/v3"
)
func GetFromKey(key string, target interface{}) error {
return DB.Redis.Do(radix.FlatCmd(target, "GET", key))
}
func SetKeyVal(key string, val interface{}) error {
return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, val))
}

@ -180,3 +180,11 @@ func UpdateInvoiceFromLnd(storedIn *Invoice, newIn *lnrpc.Invoice) *Invoice {
return storedIn
}
func IsExpiredInvoice(creation, expiry int64) bool {
createdAt := time.Unix(int64(creation), 0)
expiresAt := createdAt.Add(time.Second * time.Duration(expiry))
return time.Now().After(expiresAt)
}

@ -56,10 +56,19 @@ const (
);
`
DBUploadPayments = `
CREATE TABLE IF NOT EXISTS upload_payments (
payment_id_rhash varchar(64) PRIMARY KEY,
upload_id text NOT NULL references uploads(upload_id),
msat_amount real NOT NULL,
created timestamp DEFAULT now()
);
`
//TODO: store all invoices from redis to local sqldb
DBInvoicesSchema = `
CREATE TABLE IF NOT EXISTS invoices (
rhash varchar(64) PRIMARY KEY,
payload text DEFAULT ''
rhash varchar(64) PRIMARY KEY
);
`
@ -256,6 +265,16 @@ func (u *FileUpload) UnmarshalBinary(b []byte) error {
//func SyncUploadStatusToDB(){
//}
//
func AddPaymentToUpload(uploadId string, rhash string, msatAmount float64) error {
query := `INSERT INTO
upload_payments (payment_id_rhash, upload_id, msat_amount)
VALUES ($1, $2, $3)
`
_, err := DB.Sql.Exec(query, rhash, uploadId, msatAmount)
return err
}
func GetDownloadId(uploadId string) (string, error) {
key := fmt.Sprintf("download_for_upload_%s", uploadId)
@ -285,9 +304,9 @@ func GetDownloadId(uploadId string) (string, error) {
}
// Store it back on redis cache
err = DB.Redis.Do(radix.FlatCmd(nil, "SET", key, downloadId))
err = db.SetKeyVal(key, downloadId)
} else {
err = DB.Redis.Do(radix.FlatCmd(&downloadId, "GET", key))
err = db.GetFromKey(key, &downloadId)
}
return downloadId, err
@ -321,9 +340,9 @@ func GetUploadIdForDlId(dlId string) (string, error) {
}
// Store it back on redis cache
err = DB.Redis.Do(radix.FlatCmd(nil, "SET", key, upId))
err = db.SetKeyVal(key, upId)
} else {
err = DB.Redis.Do(radix.FlatCmd(&upId, "GET", key))
err = db.GetFromKey(key, &upId)
}
return upId, err
@ -334,11 +353,13 @@ func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
uploadInvoiceKey := fmt.Sprintf("upload_%s_invoice", uploadId)
err := DB.Redis.Do(radix.FlatCmd(&invoice, "GET", uploadInvoiceKey))
err := db.GetFromKey(uploadInvoiceKey, &invoice)
if err != nil {
return nil, err
}
log.Printf("GetUploadInvoice returned %#v")
return &invoice, nil
}
@ -394,11 +415,11 @@ func GetUploadById(id string) (*Upload, error) {
}
// Sotre it back on redis
err = DB.Redis.Do(radix.FlatCmd(nil, "SET", key, up))
err = db.SetKeyVal(key, up)
// Get it from redis
} else {
err = DB.Redis.Do(radix.FlatCmd(&up, "GET", key))
err = db.GetFromKey(key, &up)
}
return &up, err
@ -447,14 +468,14 @@ func GetUploadStatus(id string) (status UpStatus, err error) {
key := fmt.Sprintf("upload_status_%s", id)
err = DB.Redis.Do(radix.FlatCmd(&status, "GET", key))
err = db.GetFromKey(key, &status)
return
}
func SetUploadAdminToken(uploadId, token string) error {
key := fmt.Sprintf("upload_admin_%s", uploadId)
err := DB.Redis.Do(radix.FlatCmd(nil, "SET", key, token))
err := db.SetKeyVal(key, token)
if err != nil {
return err
}
@ -467,26 +488,21 @@ func SetUploadAdminToken(uploadId, token string) error {
func GetUploadAdminToken(uploadId string) (string, error) {
var token string
key := fmt.Sprintf("upload_admin_%s", uploadId)
err := DB.Redis.Do(radix.FlatCmd(&token, "GET", key))
err := db.GetFromKey(key, &token)
return token, err
}
func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error {
uploadInvoiceKey := fmt.Sprintf("upload_%s_invoice", uploadId)
invoiceJson, err := json.Marshal(invoice)
if err != nil {
return err
}
err = DB.Redis.Do(radix.FlatCmd(nil, "SET", uploadInvoiceKey, invoiceJson))
err := db.SetKeyVal(uploadInvoiceKey, invoice)
if err != nil {
return err
}
// Set inverse relation
invoiceUploadKey := fmt.Sprintf("invoice_%s_upload", invoice.RHash)
err = DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceUploadKey, uploadId))
invoiceUploadKey := fmt.Sprintf("invoice_%s_upload_id", invoice.RHash)
err = db.SetKeyVal(invoiceUploadKey, uploadId)
if err != nil {
return err
}
@ -498,11 +514,11 @@ func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error {
}
// Add invoice to invoices table
query := `INSERT INTO invoices(rhash, payload)
VALUES($1,$2)
query := `INSERT INTO invoices(rhash)
VALUES($1)
ON CONFLICT DO NOTHING
`
_, err = tx.Exec(query, invoice.RHash, invoiceJson)
_, err = tx.Exec(query, invoice.RHash)
if err != nil {
return err
}
@ -518,9 +534,9 @@ func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error {
}
func GetUploadIdForInvoice(invoiceId string) (string, error) {
key := fmt.Sprintf("invoice_%s_upload", invoiceId)
key := fmt.Sprintf("invoice_%s_upload_id", invoiceId)
err := DB.Redis.Do(radix.FlatCmd(&invoiceId, "GET", key))
err := db.GetFromKey(key, &invoiceId)
return invoiceId, err
}

@ -0,0 +1,57 @@
package storage
import (
"archive/zip"
"io"
"os"
)
func ZipFiles(files []FileUpload, w io.Writer) error {
zipWriter := zip.NewWriter(w)
defer zipWriter.Close()
// Add files to zip
for _, file := range files {
if err := AddFileToZip(zipWriter, file); err != nil {
return err
}
}
return nil
}
// https://golangcode.com/create-zip-files-in-go/
func AddFileToZip(zipWriter *zip.Writer, file FileUpload) error {
fileToZip, err := os.Open(GetStoreDestination(file.SHA256 + file.FileExt))
if err != nil {
return err
}
defer fileToZip.Close()
// get file info
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// Reset original file name
header.Name = file.FileName
// Change to deflate for better compression
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
return nil
}

@ -2,12 +2,10 @@ package watchers
import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"regexp"
"time"
"git.sp4ke.com/sp4ke/bit4sat/bus"
"git.sp4ke.com/sp4ke/bit4sat/db"
@ -83,51 +81,11 @@ func WatchInvoice() {
continue
}
// Invoice expired
if expiredInvoice(invoice.CreationDate, invoice.Expiry) {
handleExpiredInvoice(invoice)
}
// Invoice was paid, we handle it
handleSettledInvoice(invoice)
}
}
//TODO
func handleExpiredInvoice(invoice *lnrpc.Invoice) {
matchUp, err := regexp.MatchString(ReUploadInvoice, invoice.Memo)
if err != nil {
log.Printf("Error regex match: %s", err)
}
if matchUp {
// Update upload status to "expired"
uploadId, err := storage.GetUploadIdForInvoice(hex.EncodeToString(invoice.RHash))
if err != nil {
log.Fatal(fmt.Errorf("handleExpiredInvoice: %s", err))
}
err = storage.SetUploadStatus(uploadId, storage.UpPayExpired)
if err != nil {
log.Fatal(fmt.Errorf("handleExpiredInvoice %s", err))
}
}
// No need to store the invoice
// TODO
// TODO: flush all invoices and uploads
// TODO
}
func expiredInvoice(creation, expiry int64) bool {
createdAt := time.Unix(int64(creation), 0)
expiresAt := createdAt.Add(time.Second * time.Duration(expiry))
return time.Now().After(expiresAt)
}
func handleSettledInvoice(invoice *lnrpc.Invoice) {
// we need to save it and also notify the pubsub channel
@ -140,51 +98,100 @@ func handleSettledInvoice(invoice *lnrpc.Invoice) {
log.Printf("Error regex match: %s", err)
}
matchDown, err := regexp.MatchString(ReDownInvoice, invoice.Memo)
if err != nil {
log.Printf("Error regex match: %s", err)
}
// Handle upload related invoices
if matchUp {
// Update upload status to "paid"
uploadId, err := storage.GetUploadIdForInvoice(hex.EncodeToString(invoice.RHash))
log.Printf("watcher: found upload id %s for invoice %s", uploadId,
hex.EncodeToString(invoice.RHash))
if err != nil {
log.Fatal(fmt.Errorf("handleSettledInvoice: %s", err))
log.Printf("error handleSettledInvoice: %s", err)
return
}
err = storage.SetUploadStatus(uploadId, storage.UpPaid)
if err != nil {
log.Fatal(fmt.Errorf("handleSettledInvoice: %s", err))
log.Printf("error handleSettledInvoice: %s", err)
return
}
// Get stored invoice for upload
storedInvoice, err := storage.GetUploadInvoice(uploadId)
if err != nil {
log.Fatal(fmt.Errorf("handleSettledInvoice: %s", err))
log.Printf("error handleSettledInvoice: %s", err)
return
}
log.Printf("stored invoice %#v", storedInvoice)
// Update stored invoice fields
newInvoice := ln.UpdateInvoiceFromLnd(storedInvoice, invoice)
log.Printf("new invoice %#v", newInvoice)
// Set invoice for upload
err = storage.SetUploadInvoice(uploadId, newInvoice)
if err != nil {
log.Fatal(fmt.Errorf("handleSettledInvoice: %s", err))
}
newInvoiceJson, err := json.Marshal(newInvoice)
if err != nil {
log.Fatal(fmt.Errorf("handleSettledInvoice: %s", err))
log.Printf("error handleSettledInvoice: %s", err)
return
}
log.Printf("Notifying invoice paid %s", uploadId)
log.Printf("Notifying invoice paid for upload %s", uploadId)
// publish invoice was updated to upload_id_paid channel
key := fmt.Sprintf("%s:%s", bus.InvoicePaidChannelPrefix,
newInvoice.RHash)
err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
key, newInvoiceJson))
key, newInvoice))
}
// This is a download invoice
if matchDown {
// Store the full invoice
key := fmt.Sprintf("invoice_%s", hex.EncodeToString(invoice.RHash))
storedInvoice := ln.InvoiceFromLndIn(invoice)
err = db.DB.Redis.Do(radix.FlatCmd(nil, "SET", key, storedInvoice))
if err != nil {
log.Printf("error handleSettledInvoice: %s", err)
return
}
// Get upload_id related to this invoice
key = fmt.Sprintf("invoice_id_%s_upload_id", storedInvoice.RHash)
log.Printf("looking for %s", key)
var uploadId string
err := db.DB.Redis.Do(radix.FlatCmd(&uploadId, "GET", key))
if err != nil {
log.Printf("error handleSettledInvoice: %s", err)
return
}
// Add payment to the corresponding upload_id (user account)
log.Printf("updating payment for upload %s", uploadId)
err = storage.AddPaymentToUpload(uploadId, storedInvoice.RHash,
storedInvoice.Msatoshi)
if err != nil {
log.Printf("error handleSettledInvoice: %s", err)
return
}
log.Printf("Notifying invoice paid for download")
key = fmt.Sprintf("%s:%s", bus.InvoicePaidChannelPrefix,
storedInvoice.RHash)
err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
key, storedInvoice))
}
if err != nil {
panic(err)
log.Printf("error handleSettledInvoice: %s", err)
return
}
}

@ -1,9 +1,11 @@
<template>
<div id="download">
<pay v-if="!error" :objectId="dlId" :invoice="invoice"></pay>
<pay v-if="!error && !showDl" :objectId="dlId" :invoice="invoice"></pay>
<div v-if="error" class="f4 mv5 light-red ttu">{{errorMsg}}</div>
<upload v-if="error"></upload>
<a v-if="showDl" :href="dlLink" download="download.zip">download file</a>
</div>
</template>
@ -20,6 +22,8 @@ export default {
props: ['dlId'],
data(){
return {
dlLink: "if",
showDl: false,
error: false,
errorMsg: "",
}
@ -37,15 +41,39 @@ export default {
// ask payment
} else if ( res.status === 402) {
res.json()
return res.json()
.then((data)=>{
this.$store.commit('setInvoice', data.invoice)
this.$store.commit('setFiles', data.files)
Api.pollInvoice(data.invoice.rhash)
.then((res)=>{
// invoice paid we can try again to download
if(res.ok){
//Api.download(self.dlId)
console.log("calling set download")
self.setDownload()
}
})
})
.catch((e)=>{console.log(e)})
// download
} else {
console.log("calling set download")
self.setDownload()
}
})
},
methods: {
setDownload(){
this.showDl = true;
this.dlLink = Api.endPoints.download + '/' + this.dlId
}
},
computed: {
...mapState({

@ -7,9 +7,10 @@ const endPoints = {
upload: '/api/v1/u',
session: '/api/v1/session',
pollstatus: '/api/v1/u/poll',
pollupstatus: '/api/v1/u/poll',
checkstatus: '/api/v1/u/check',
download: '/api/v1/d'
download: '/api/v1/d',
pollinvoice: '/api/v1/pollinvoice'
}
@ -50,11 +51,21 @@ export async function checkUploadStatus(uploadId){
return fetch(req).catch((e)=>{console.error(e)})
}
export function pollInvoice(invoiceId){
let req = new Request(endPoints.pollinvoice + '/' + invoiceId,{
method: 'GET',
credentials: 'same-origin'
})
return fetch(req).catch((e)=>{console.error(e)})
}
export async function pollUploadStatus(uploadId){
//console.log('polling upload status')
let req = new Request(endPoints.pollstatus + '/' + uploadId,{
let req = new Request(endPoints.pollupstatus + '/' + uploadId,{
method: 'GET',
credentials: 'same-origin'
})
@ -132,4 +143,5 @@ export default {
pollUploadStatus: pollUploadStatus,
checkUploadStatus: checkUploadStatus,
download: download,
pollInvoice: pollInvoice,
}

Loading…
Cancel
Save