github.com/0chain/gosdk@v1.17.11/winsdk/stream.go (about)

     1  package main
     2  
     3  /*
     4  #include <stdlib.h>
     5  */
     6  import (
     7  	"C"
     8  )
     9  
    10  import (
    11  	"fmt"
    12  	"math"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"os"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"sync"
    20  
    21  	"github.com/0chain/gosdk/zboxcore/sdk"
    22  	lru "github.com/hashicorp/golang-lru/v2"
    23  )
    24  
    25  var (
    26  	numBlocks                 int   = 100
    27  	sizePerRequest            int64 = sdk.CHUNK_SIZE * int64(numBlocks) // 6400K 100 blocks
    28  	server                    *httptest.Server
    29  	streamAllocationID        string
    30  	cachedDownloadedBlocks, _ = lru.New[string, []byte](1000)
    31  )
    32  
    33  // StartStreamServer - start local media stream server
    34  // ## Inputs
    35  //   - allocationID
    36  //
    37  // ## Outputs
    38  //
    39  //	{
    40  //	"error":"",
    41  //	"result":"http://127.0.0.1:4313/",
    42  //	}
    43  //
    44  //export StartStreamServer
    45  func StartStreamServer(allocationID *C.char) *C.char {
    46  	allocID := C.GoString(allocationID)
    47  
    48  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    49  		streamingMedia(w, r)
    50  	})
    51  
    52  	if server != nil {
    53  		server.Close()
    54  	}
    55  
    56  	server = httptest.NewServer(handler)
    57  	streamAllocationID = allocID
    58  	log.Info("win: ", server.URL)
    59  	return WithJSON(server.URL, nil)
    60  }
    61  
    62  func streamingMedia(w http.ResponseWriter, req *http.Request) {
    63  
    64  	remotePath := req.URL.Path
    65  	log.Info("win: start streaming media: ", streamAllocationID, remotePath)
    66  
    67  	f, err := getFileMeta(streamAllocationID, remotePath)
    68  	if err != nil {
    69  		http.Error(w, err.Error(), 500)
    70  		return
    71  	}
    72  
    73  	w.Header().Set("Content-Type", f.MimeType)
    74  
    75  	rangeHeader := req.Header.Get("Range")
    76  
    77  	log.Info("win: range: ", rangeHeader, " mimetype: ", f.MimeType, " numBlocks:", f.NumBlocks, " ActualNumBlocks:", f.ActualNumBlocks, " ActualFileSize:", f.ActualFileSize)
    78  	// we can simply hint Chrome to send serial range requests for media file by
    79  	//
    80  	// if rangeHeader == "" {
    81  	// 	w.Header().Set("Accept-Ranges", "bytes")
    82  	// 	w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
    83  	// 	w.WriteHeader(200)
    84  	// 	fmt.Printf("hint browser to send range requests, total size: %d\n", size)
    85  	// 	return
    86  	// }
    87  	//
    88  	// but this not worked for Safari and Firefox
    89  	if rangeHeader == "" {
    90  		ra := httpRange{
    91  			start:  0,
    92  			length: sizePerRequest,
    93  			total:  f.ActualFileSize,
    94  		}
    95  		w.Header().Set("Accept-Ranges", "bytes")
    96  
    97  		w.Header().Set("Content-Range", ra.Header())
    98  
    99  		w.WriteHeader(http.StatusPartialContent)
   100  
   101  		if req.Method != "HEAD" {
   102  			buf, err := downloadBlocks(remotePath, f, ra)
   103  			if err != nil {
   104  				http.Error(w, err.Error(), 500)
   105  			}
   106  
   107  			written, err := w.Write(buf)
   108  
   109  			if err != nil {
   110  				http.Error(w, err.Error(), 500)
   111  			}
   112  
   113  			w.Header().Set("Content-Length", strconv.Itoa(written))
   114  
   115  			for k, v := range w.Header() {
   116  				log.Info("win: response ", k, " = ", v[0])
   117  			}
   118  
   119  		} else {
   120  			w.Header().Set("Content-Length", "0")
   121  		}
   122  		return
   123  	}
   124  
   125  	ranges, err := parseRange(rangeHeader, f.ActualFileSize)
   126  	if err != nil {
   127  		http.Error(w, err.Error(), 400)
   128  		return
   129  	}
   130  
   131  	// multi-part requests are not supported
   132  	if len(ranges) > 1 {
   133  		http.Error(w, "unsupported multi-part", http.StatusRequestedRangeNotSatisfiable)
   134  		return
   135  	}
   136  
   137  	ra := ranges[0]
   138  
   139  	w.Header().Set("Accept-Ranges", "bytes")
   140  	w.Header().Set("Content-Range", ra.Header())
   141  
   142  	w.WriteHeader(http.StatusPartialContent)
   143  
   144  	if req.Method != "HEAD" {
   145  
   146  		buf, err := downloadBlocks(remotePath, f, ra)
   147  
   148  		if err != nil {
   149  			http.Error(w, err.Error(), 500)
   150  		}
   151  
   152  		written, err := w.Write(buf)
   153  		if err != nil {
   154  			http.Error(w, err.Error(), 500)
   155  		}
   156  		w.Header().Set("Content-Length", strconv.Itoa(written))
   157  
   158  		for k, v := range w.Header() {
   159  			log.Info("win: response ", k, " = ", v[0])
   160  		}
   161  	} else {
   162  		w.Header().Set("Content-Length", "0")
   163  	}
   164  }
   165  
   166  func downloadBlocks(remotePath string, f *sdk.ConsolidatedFileMeta, ra httpRange) ([]byte, error) {
   167  	defer func() {
   168  		if r := recover(); r != nil {
   169  			log.Error("win: ", r)
   170  		}
   171  	}()
   172  	alloc, err := getAllocation(streamAllocationID)
   173  
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	var startBlock int64
   178  
   179  	if ra.start == 0 {
   180  		startBlock = 1
   181  	} else {
   182  		startBlock = int64(math.Floor(float64(ra.start)/float64(sdk.CHUNK_SIZE)/float64(alloc.DataShards))) + 1
   183  	}
   184  
   185  	if startBlock > f.NumBlocks {
   186  		startBlock = f.NumBlocks
   187  	}
   188  
   189  	blocks := int(math.Ceil(float64(ra.length) / float64(sdk.CHUNK_SIZE) / float64(alloc.DataShards)))
   190  
   191  	endBlock := startBlock + int64(blocks)
   192  
   193  	if endBlock > f.NumBlocks {
   194  		endBlock = f.NumBlocks
   195  	}
   196  
   197  	if startBlock == endBlock {
   198  		endBlock = 0
   199  	}
   200  
   201  	offset := 0
   202  	blockStart := (startBlock - 1) * sdk.CHUNK_SIZE * int64(alloc.DataShards)
   203  	if ra.start > blockStart {
   204  		offset = int(ra.start) - int(blockStart)
   205  	}
   206  
   207  	lookupHash := getLookupHash(streamAllocationID, remotePath)
   208  	key := lookupHash + fmt.Sprintf(":%v-%v", startBlock, endBlock)
   209  	log.Info("win: start download blocks ", startBlock, " - ", endBlock, "/", f.NumBlocks, "(", f.ActualNumBlocks, ") for ", remotePath)
   210  	buf, ok := cachedDownloadedBlocks.Get(key)
   211  	if ok {
   212  		return buf, nil
   213  	}
   214  
   215  	mf := filepath.Join(os.TempDir(), strings.ReplaceAll(remotePath, "/", "_")+fmt.Sprintf("_%v_%v", startBlock, endBlock))
   216  	defer os.Remove(mf)
   217  
   218  	// _, err = os.Stat(mf)
   219  
   220  	// if os.IsNotExist(err) {
   221  	statusBar := NewStatusBar(statusDownload, key)
   222  	status := statusBar.getStatus(key)
   223  	status.wg = &sync.WaitGroup{}
   224  	status.wg.Add(1)
   225  	log.Info("win: download blocks to ", mf)
   226  	err = alloc.DownloadFileByBlock(mf, remotePath, startBlock, endBlock, numBlocks, false, statusBar, true)
   227  	//err = alloc.DownloadFile(mf, remotePath, true, statusBar, true)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	log.Info("win: waiting for download to done")
   233  	status.wg.Wait()
   234  	// }
   235  
   236  	buf, err = os.ReadFile(mf)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	log.Info("win: downloaded blocks ", len(buf), " start:", ra.start, " blockStart:", blockStart, " offset:", offset, " len:", ra.length)
   242  	if len(buf) > 0 {
   243  		if len(buf) > int(ra.length) {
   244  			b := buf[offset : offset+int(ra.length)]
   245  			cachedDownloadedBlocks.Add(key, b)
   246  			return b, nil
   247  		}
   248  		cachedDownloadedBlocks.Add(key, buf)
   249  		return buf[offset:], nil
   250  	}
   251  
   252  	// os.Remove(mf)
   253  	return nil, nil
   254  }