This commit is contained in:
yourok
2018-08-29 12:33:14 +03:00
commit 0ca43a2c4d
54 changed files with 5669 additions and 0 deletions

15
src/server/web/About.go Normal file
View File

@@ -0,0 +1,15 @@
package server
import (
"net/http"
"github.com/labstack/echo"
)
func initAbout(e *echo.Echo) {
e.GET("/about", aboutPage)
}
func aboutPage(c echo.Context) error {
return c.Render(http.StatusOK, "aboutPage", nil)
}

115
src/server/web/Info.go Normal file
View File

@@ -0,0 +1,115 @@
package server
import (
"fmt"
"net/http"
"sort"
"server/utils"
"github.com/anacrolix/torrent/metainfo"
"github.com/labstack/echo"
"github.com/labstack/gommon/bytes"
)
func initInfo(e *echo.Echo) {
server.GET("/cache", cachePage)
server.GET("/stat", statePage)
server.GET("/btstat", btStatePage)
}
func btStatePage(c echo.Context) error {
bts.WriteState(c.Response())
return c.NoContent(http.StatusOK)
}
func cachePage(c echo.Context) error {
return c.Render(http.StatusOK, "cachePage", nil)
}
func statePage(c echo.Context) error {
state := bts.BTState()
msg := ""
msg += fmt.Sprintf("Listen port: %d<br>\n", state.LocalPort)
msg += fmt.Sprintf("Peer ID: %+q<br>\n", state.PeerID)
msg += fmt.Sprintf("Banned IPs: %d<br>\n", state.BannedIPs)
for _, dht := range state.DHTs {
msg += fmt.Sprintf("%s DHT server at %s:<br>\n", dht.Addr().Network(), dht.Addr().String())
dhtStats := dht.Stats()
msg += fmt.Sprintf("\t&emsp;# Nodes: %d (%d good, %d banned)<br>\n", dhtStats.Nodes, dhtStats.GoodNodes, dhtStats.BadNodes)
msg += fmt.Sprintf("\t&emsp;Server ID: %x<br>\n", dht.ID())
msg += fmt.Sprintf("\t&emsp;Announces: %d<br>\n", dhtStats.SuccessfulOutboundAnnouncePeerQueries)
msg += fmt.Sprintf("\t&emsp;Outstanding transactions: %d<br>\n", dhtStats.OutstandingTransactions)
}
sort.Slice(state.Torrents, func(i, j int) bool {
return state.Torrents[i].Hash().HexString() < state.Torrents[j].Hash().HexString()
})
msg += "Torrents:<br>\n"
for _, t := range state.Torrents {
st := t.Stats()
msg += fmt.Sprintf("Name: %v<br>\n", st.Name)
msg += fmt.Sprintf("Hash: %v<br>\n", st.Hash)
msg += fmt.Sprintf("Status: %v<br>\n", st.TorrentStatus)
msg += fmt.Sprintf("Loaded Size: %v<br>\n", bytes.Format(st.LoadedSize))
msg += fmt.Sprintf("Torrent Size: %v<br>\n<br>\n", bytes.Format(st.TorrentSize))
msg += fmt.Sprintf("Preloaded Bytes: %v<br>\n", bytes.Format(st.PreloadedBytes))
msg += fmt.Sprintf("Preload Size: %v<br>\n<br>\n", bytes.Format(st.PreloadSize))
msg += fmt.Sprintf("Download Speed: %v/Sec<br>\n", utils.Format(st.DownloadSpeed))
msg += fmt.Sprintf("Upload Speed: %v/Sec<br>\n<br>\n", utils.Format(st.UploadSpeed))
msg += fmt.Sprintf("\t&emsp;TotalPeers: %v<br>\n", st.TotalPeers)
msg += fmt.Sprintf("\t&emsp;PendingPeers: %v<br>\n", st.PendingPeers)
msg += fmt.Sprintf("\t&emsp;ActivePeers: %v<br>\n", st.ActivePeers)
msg += fmt.Sprintf("\t&emsp;ConnectedSeeders: %v<br>\n", st.ConnectedSeeders)
msg += fmt.Sprintf("\t&emsp;HalfOpenPeers: %v<br>\n", st.HalfOpenPeers)
msg += fmt.Sprintf("\t&emsp;BytesWritten: %v (%v)<br>\n", st.BytesWritten, bytes.Format(st.BytesWritten))
msg += fmt.Sprintf("\t&emsp;BytesWrittenData: %v (%v)<br>\n", st.BytesWrittenData, bytes.Format(st.BytesWrittenData))
msg += fmt.Sprintf("\t&emsp;BytesRead: %v (%v)<br>\n", st.BytesRead, bytes.Format(st.BytesRead))
msg += fmt.Sprintf("\t&emsp;BytesReadData: %v (%v)<br>\n", st.BytesReadData, bytes.Format(st.BytesReadData))
msg += fmt.Sprintf("\t&emsp;BytesReadUsefulData: %v (%v)<br>\n", st.BytesReadUsefulData, bytes.Format(st.BytesReadUsefulData))
msg += fmt.Sprintf("\t&emsp;ChunksWritten: %v<br>\n", st.ChunksWritten)
msg += fmt.Sprintf("\t&emsp;ChunksRead: %v<br>\n", st.ChunksRead)
msg += fmt.Sprintf("\t&emsp;ChunksReadUseful: %v<br>\n", st.ChunksReadUseful)
msg += fmt.Sprintf("\t&emsp;ChunksReadWasted: %v<br>\n", st.ChunksReadWasted)
msg += fmt.Sprintf("\t&emsp;PiecesDirtiedGood: %v<br>\n", st.PiecesDirtiedGood)
msg += fmt.Sprintf("\t&emsp;PiecesDirtiedBad: %v<br>\n<br>\n", st.PiecesDirtiedBad)
if len(st.FileStats) > 0 {
msg += fmt.Sprintf("\t&emsp;Files:<br>\n")
for _, f := range st.FileStats {
msg += fmt.Sprintf("\t&emsp;\t&emsp;%v Size:%v<br>\n", f.Path, bytes.Format(f.Length))
}
}
hash := metainfo.NewHashFromHex(st.Hash)
cState := bts.CacheState(hash)
if cState != nil {
msg += fmt.Sprintf("CacheType:<br>\n")
msg += fmt.Sprintf("Capacity: %v<br>\n", bytes.Format(cState.Capacity))
msg += fmt.Sprintf("Filled: %v<br>\n", bytes.Format(cState.Filled))
msg += fmt.Sprintf("PiecesLength: %v<br>\n", bytes.Format(cState.PiecesLength))
msg += fmt.Sprintf("PiecesCount: %v<br>\n", cState.PiecesCount)
for _, p := range cState.Pieces {
msg += fmt.Sprintf("\t&emsp;Piece: %v\t&emsp; Access: %s\t&emsp; Buffer size: %d(%s)\t&emsp; Complete: %v\t&emsp; Hash: %s\n<br>", p.Id, p.Accessed.Format("15:04:05.000"), p.BufferSize, bytes.Format(int64(p.BufferSize)), p.Completed, p.Hash)
}
}
msg += "<hr><br><br>\n\n"
}
//msg += `
//<script>
//document.addEventListener("DOMContentLoaded", function(event) {
// setTimeout(function(){
// location.reload();
// }, 1000);
//});
//</script>
//
//`
return c.HTML(http.StatusOK, msg)
}

151
src/server/web/Server.go Normal file
View File

@@ -0,0 +1,151 @@
package server
import (
"fmt"
"log"
"net"
"net/http"
"os"
"runtime"
"time"
"server/settings"
"server/torr"
"server/version"
"server/web/mods"
"server/web/templates"
"github.com/anacrolix/sync"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
var (
server *echo.Echo
bts *torr.BTServer
mutex sync.Mutex
fnMutex sync.Mutex
err error
)
func Start(port string) {
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println("Start web server, version:", version.Version)
bts = torr.NewBTS()
err := bts.Connect()
if err != nil {
fmt.Println("Error start torrent client:", err)
return
}
mutex.Lock()
server = echo.New()
server.HideBanner = true
server.HidePort = true
server.HTTPErrorHandler = HTTPErrorHandler
//server.Use(middleware.Logger())
server.Use(middleware.Recover())
templates.InitTemplate(server)
initTorrent(server)
initSettings(server)
initInfo(server)
initAbout(server)
mods.InitMods(server)
server.GET("/", mainPage)
server.GET("/echo", echoPage)
server.POST("/shutdown", shutdownPage)
server.GET("/js/api.js", templates.Api_JS)
go func() {
defer mutex.Unlock()
server.Listener, err = net.Listen("tcp", "0.0.0.0:"+port)
if err == nil {
err = server.Start("0.0.0.0:" + port)
}
server = nil
if err != nil {
fmt.Println("Error start web server:", err)
}
}()
}
func Stop() {
fnMutex.Lock()
defer fnMutex.Unlock()
if server != nil {
fmt.Println("Stop web server")
server.Close()
server = nil
if bts != nil {
bts.Disconnect()
bts = nil
}
}
}
func Wait() error {
mutex.Lock()
mutex.Unlock()
return err
}
func mainPage(c echo.Context) error {
return c.Render(http.StatusOK, "mainPage", nil)
}
func echoPage(c echo.Context) error {
return c.String(http.StatusOK, version.Version)
}
func shutdownPage(c echo.Context) error {
go func() {
Stop()
settings.CloseDB()
time.Sleep(time.Second * 2)
os.Exit(5)
}()
return c.NoContent(http.StatusOK)
}
func HTTPErrorHandler(err error, c echo.Context) {
var (
code = http.StatusInternalServerError
msg interface{}
)
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
msg = he.Message
if he.Internal != nil {
msg = fmt.Sprintf("%v, %v", err, he.Internal)
}
} else {
msg = http.StatusText(code)
}
if _, ok := msg.(string); ok {
msg = echo.Map{"message": msg}
}
if code != 404 && c.Request().URL.Path != "/torrent/stat" {
log.Println("Web server error:", err, c.Request().URL)
}
// Send response
if !c.Response().Committed {
if c.Request().Method == echo.HEAD { // Issue #608
err = c.NoContent(code)
} else {
err = c.JSON(code, msg)
}
if err != nil {
c.Logger().Error(err)
}
}
}

View File

@@ -0,0 +1,52 @@
package server
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"server/settings"
"github.com/labstack/echo"
)
func initSettings(e *echo.Echo) {
e.GET("/settings", settingsPage)
e.POST("/settings/read", settingsRead)
e.POST("/settings/write", settingsWrite)
}
func settingsPage(c echo.Context) error {
return c.Render(http.StatusOK, "settingsPage", nil)
}
func settingsRead(c echo.Context) error {
return c.JSON(http.StatusOK, settings.Get())
}
func settingsWrite(c echo.Context) error {
err := getJsSettings(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
settings.SaveSettings()
return c.JSON(http.StatusOK, "Ok")
}
func getJsSettings(c echo.Context) error {
buf, _ := ioutil.ReadAll(c.Request().Body)
decoder := json.NewDecoder(bytes.NewBuffer(buf))
err := decoder.Decode(settings.Get())
if err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
} else if se, ok := err.(*json.SyntaxError); ok {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
} else {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
return nil
}

659
src/server/web/Torrent.go Normal file
View File

@@ -0,0 +1,659 @@
package server
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"server/settings"
"server/torr"
"server/utils"
"server/web/helpers"
"github.com/anacrolix/missinggo/httptoo"
"github.com/anacrolix/torrent/metainfo"
"github.com/labstack/echo"
)
func initTorrent(e *echo.Echo) {
e.POST("/torrent/add", torrentAdd)
e.POST("/torrent/upload", torrentUpload)
e.POST("/torrent/get", torrentGet)
e.POST("/torrent/rem", torrentRem)
e.POST("/torrent/list", torrentList)
e.POST("/torrent/stat", torrentStat)
e.POST("/torrent/cache", torrentCache)
e.POST("/torrent/drop", torrentDrop)
e.GET("/torrent/restart", torrentRestart)
e.GET("/torrent/playlist.m3u", torrentPlayListAll)
e.GET("/torrent/play", torrentPlay)
e.HEAD("/torrent/play", torrentPlay)
e.GET("/torrent/view/:hash/:file", torrentView)
e.HEAD("/torrent/view/:hash/:file", torrentView)
e.GET("/torrent/preload/:hash/:file", torrentPreload)
e.GET("/torrent/preload/:size/:hash/:file", torrentPreloadSize)
}
type TorrentJsonRequest struct {
Link string `json:",omitempty"`
Hash string `json:",omitempty"`
Title string `json:",omitempty"`
Info string `json:",omitempty"`
DontSave bool `json:",omitempty"`
}
type TorrentJsonResponse struct {
Name string
Magnet string
Hash string
AddTime int64
Length int64
Status torr.TorrentStatus
Playlist string
Info string
Files []TorFile `json:",omitempty"`
}
type TorFile struct {
Name string
Link string
Preload string
Size int64
Viewed bool
}
func torrentAdd(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Link == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Link must be non-empty")
}
magnet, err := helpers.GetMagnet(jreq.Link)
if err != nil {
fmt.Println("Error get magnet:", jreq.Hash, err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Title != "" {
magnet.DisplayName = jreq.Title
}
err = helpers.Add(bts, *magnet, !jreq.DontSave)
if err != nil {
fmt.Println("Error add torrent:", jreq.Hash, err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Info != "" {
go func() {
utils.AddInfo(magnet.InfoHash.HexString(), jreq.Info)
}()
}
return c.String(http.StatusOK, magnet.InfoHash.HexString())
}
func torrentUpload(c echo.Context) error {
form, err := c.MultipartForm()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
defer form.RemoveAll()
_, dontSave := form.Value["DontSave"]
var magnets []metainfo.Magnet
for _, file := range form.File {
torrFile, err := file[0].Open()
if err != nil {
return err
}
defer torrFile.Close()
mi, err := metainfo.Load(torrFile)
if err != nil {
fmt.Println("Error upload torrent", err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
info, err := mi.UnmarshalInfo()
if err != nil {
fmt.Println("Error upload torrent", err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
magnet := mi.Magnet(info.Name, mi.HashInfoBytes())
magnets = append(magnets, magnet)
}
ret := make([]string, 0)
for _, magnet := range magnets {
er := helpers.Add(bts, magnet, !dontSave)
if er != nil {
err = er
fmt.Println("Error add torrent:", magnet.String(), er)
}
ret = append(ret, magnet.InfoHash.HexString())
}
return c.JSON(http.StatusOK, ret)
}
func torrentGet(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Hash == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
tor, err := settings.LoadTorrentDB(jreq.Hash)
if err != nil {
fmt.Println("Error get torrent:", jreq.Hash, err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
torrStatus := torr.TorrentAdded
if tor == nil {
hash := metainfo.NewHashFromHex(jreq.Hash)
ts := bts.GetTorrent(hash)
if ts != nil {
torrStatus = ts.Status()
tor = toTorrentDB(ts)
}
}
if tor == nil {
fmt.Println("Error get: torrent not found", jreq.Hash)
return echo.NewHTTPError(http.StatusBadRequest, "Error get: torrent not found "+jreq.Hash)
}
js, err := getTorrentJS(tor)
if err != nil {
fmt.Println("Error get torrent:", tor.Hash, err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
js.Status = torrStatus
return c.JSON(http.StatusOK, js)
}
func torrentRem(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Hash == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
settings.RemoveTorrentDB(jreq.Hash)
bts.RemoveTorrent(metainfo.NewHashFromHex(jreq.Hash))
return c.JSON(http.StatusOK, nil)
}
func torrentList(c echo.Context) error {
buf, _ := ioutil.ReadAll(c.Request().Body)
jsstr := string(buf)
decoder := json.NewDecoder(bytes.NewBufferString(jsstr))
jsreq := struct {
Request int
}{}
decoder.Decode(&jsreq)
reqType := jsreq.Request
js := make([]TorrentJsonResponse, 0)
list, _ := settings.LoadTorrentsDB()
for _, tor := range list {
jsTor, err := getTorrentJS(tor)
if err != nil {
fmt.Println("Error get torrent:", err)
} else {
js = append(js, *jsTor)
}
}
sort.Slice(js, func(i, j int) bool {
if js[i].AddTime == js[j].AddTime {
return js[i].Name < js[j].Name
}
return js[i].AddTime > js[j].AddTime
})
slist := bts.List()
find := func(tjs []TorrentJsonResponse, t *torr.Torrent) bool {
for _, j := range tjs {
if t.Hash().HexString() == j.Hash {
return true
}
}
return false
}
for _, st := range slist {
if !find(js, st) {
tdb := toTorrentDB(st)
jsTor, err := getTorrentJS(tdb)
if err != nil {
fmt.Println("Error get torrent:", err)
} else {
jsTor.Status = st.Status()
js = append(js, *jsTor)
}
}
}
if reqType == 1 {
ret := make([]TorrentJsonResponse, 0)
for _, r := range js {
if r.Status == torr.TorrentWorking || len(r.Files) > 0 {
ret = append(ret, r)
}
}
return c.JSON(http.StatusOK, ret)
} else if reqType == 2 {
ret := make([]TorrentJsonResponse, 0)
for _, r := range js {
if r.Status == torr.TorrentGettingInfo {
ret = append(ret, r)
}
}
return c.JSON(http.StatusOK, ret)
}
return c.JSON(http.StatusOK, js)
}
func torrentStat(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Hash == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
hash := metainfo.NewHashFromHex(jreq.Hash)
tor := bts.GetTorrent(hash)
if tor == nil {
return echo.NewHTTPError(http.StatusNotFound)
}
stat := tor.Stats()
return c.JSON(http.StatusOK, stat)
}
func torrentCache(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Hash == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
hash := metainfo.NewHashFromHex(jreq.Hash)
stat := bts.CacheState(hash)
if stat == nil {
return echo.NewHTTPError(http.StatusNotFound)
}
return c.JSON(http.StatusOK, stat)
}
func preload(hashHex, fileLink string, size int64) *echo.HTTPError {
if size > 0 {
hash := metainfo.NewHashFromHex(hashHex)
tor := bts.GetTorrent(hash)
if tor == nil {
torrDb, err := settings.LoadTorrentDB(hashHex)
if err != nil || torrDb == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Torrent not found: "+hashHex)
}
m, err := metainfo.ParseMagnetURI(torrDb.Magnet)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error parser magnet in db: "+hashHex)
}
tor, err = bts.AddTorrent(m, nil)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
if !tor.WaitInfo() {
return echo.NewHTTPError(http.StatusBadRequest, "torrent closed befor get info")
}
file := helpers.FindFileLink(fileLink, tor.Torrent)
if file == nil {
return echo.NewHTTPError(http.StatusNotFound, "file in torrent not found: "+fileLink)
}
tor.Preload(file, size)
}
return nil
}
func torrentPreload(c echo.Context) error {
hashHex, err := url.PathUnescape(c.Param("hash"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
fileLink, err := url.PathUnescape(c.Param("file"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if hashHex == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
if fileLink == "" {
return echo.NewHTTPError(http.StatusBadRequest, "File link must be non-empty")
}
errHttp := preload(hashHex, fileLink, settings.Get().PreloadBufferSize)
if err != nil {
return errHttp
}
return c.NoContent(http.StatusOK)
}
func torrentPreloadSize(c echo.Context) error {
hashHex, err := url.PathUnescape(c.Param("hash"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
fileLink, err := url.PathUnescape(c.Param("file"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
szPreload, err := url.PathUnescape(c.Param("size"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if hashHex == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
if fileLink == "" {
return echo.NewHTTPError(http.StatusBadRequest, "File link must be non-empty")
}
var size = settings.Get().PreloadBufferSize
if szPreload != "" {
sz, err := strconv.Atoi(szPreload)
if err == nil && sz > 0 {
size = int64(sz) * 1024 * 1024
}
}
errHttp := preload(hashHex, fileLink, size)
if err != nil {
return errHttp
}
//redirectUrl := c.Scheme() + "://" + c.Request().Host + filepath.Join("/torrent/view/", hashHex, fileLink)
//return c.Redirect(http.StatusFound, redirectUrl)
return c.NoContent(http.StatusOK)
}
func torrentDrop(c echo.Context) error {
jreq, err := getJsReqTorr(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if jreq.Hash == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Hash must be non-empty")
}
bts.RemoveTorrent(metainfo.NewHashFromHex(jreq.Hash))
return c.NoContent(http.StatusOK)
}
func torrentRestart(c echo.Context) error {
fmt.Println("Restart torrent engine")
err := bts.Reconnect()
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.String(http.StatusOK, "Ok")
}
func torrentPlayListAll(c echo.Context) error {
list, err := settings.LoadTorrentsDB()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
m3u := helpers.MakeM3ULists(list, c.Scheme()+"://"+c.Request().Host)
c.Response().Header().Set("Content-Type", "audio/x-mpegurl")
c.Response().Header().Set("Content-Disposition", `attachment; filename="playlist.m3u"`)
http.ServeContent(c.Response(), c.Request(), "playlist.m3u", time.Now(), bytes.NewReader([]byte(m3u)))
return c.NoContent(http.StatusOK)
}
func torrentPlay(c echo.Context) error {
link := c.QueryParam("link")
if link == "" {
return echo.NewHTTPError(http.StatusBadRequest, "link should not be empty")
}
fmt.Println("Play:", c.QueryParams())
qsave := c.QueryParam("save")
qpreload := c.QueryParam("preload")
qfile := c.QueryParam("file")
qstat := c.QueryParam("stat")
mm3u := c.QueryParam("m3u")
preload := int64(0)
stat := strings.ToLower(qstat) == "true"
if qpreload != "" {
preload, _ = strconv.ParseInt(qpreload, 10, 64)
if preload > 0 {
preload *= 1024 * 1024
}
}
magnet, err := helpers.GetMagnet(link)
if err != nil {
fmt.Println("Error get magnet:", link, err)
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
tor := bts.GetTorrent(magnet.InfoHash)
if tor == nil {
tor, err = bts.AddTorrent(*magnet, nil)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
}
if stat {
return c.JSON(http.StatusOK, getTorPlayState(tor))
}
if !tor.WaitInfo() {
return echo.NewHTTPError(http.StatusBadRequest, "torrent closed befor get info")
}
if strings.ToLower(qsave) == "true" {
if t, err := settings.LoadTorrentDB(magnet.InfoHash.HexString()); t == nil && err == nil {
torrDb := toTorrentDB(tor)
if torrDb != nil {
settings.SaveTorrentDB(torrDb)
}
}
}
if strings.ToLower(mm3u) == "true" {
mt := tor.Torrent.Metainfo()
m3u := helpers.MakeM3UPlayList(tor.Stats(), mt.Magnet(tor.Name(), tor.Hash()).String(), c.Scheme()+"://"+c.Request().Host)
c.Response().Header().Set("Content-Type", "audio/x-mpegurl")
c.Response().Header().Set("Connection", "close")
name := utils.CleanFName(tor.Name()) + ".m3u"
c.Response().Header().Set("ETag", httptoo.EncodeQuotedString(fmt.Sprintf("%s/%s", tor.Hash().HexString(), name)))
c.Response().Header().Set("Content-Disposition", `attachment; filename="`+name+`"`)
http.ServeContent(c.Response(), c.Request(), name, time.Time{}, bytes.NewReader([]byte(m3u)))
return c.NoContent(http.StatusOK)
}
files := helpers.GetPlayableFiles(tor.Stats())
if len(files) == 1 {
file := helpers.FindFile(files[0].Id, tor)
if file == nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprint("File", files[0], "not found in torrent", tor.Name()))
}
return bts.Play(tor, file, preload, c)
}
if qfile == "" && len(files) > 1 {
return c.JSON(http.StatusOK, getTorPlayState(tor))
}
fileInd, _ := strconv.Atoi(qfile)
file := helpers.FindFile(fileInd, tor)
if file == nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprint("File", files[fileInd], "not found in torrent", tor.Name()))
}
return bts.Play(tor, file, preload, c)
}
func torrentView(c echo.Context) error {
hashHex, err := url.PathUnescape(c.Param("hash"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
fileLink, err := url.PathUnescape(c.Param("file"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
hash := metainfo.NewHashFromHex(hashHex)
tor := bts.GetTorrent(hash)
if tor == nil {
torrDb, err := settings.LoadTorrentDB(hashHex)
if err != nil || torrDb == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Torrent not found: "+hashHex)
}
m, err := metainfo.ParseMagnetURI(torrDb.Magnet)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error parser magnet in db: "+hashHex)
}
tor, err = bts.AddTorrent(m, nil)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
if !tor.WaitInfo() {
return echo.NewHTTPError(http.StatusBadRequest, "torrent closed befor get info")
}
file := helpers.FindFileLink(fileLink, tor.Torrent)
if file == nil {
return echo.NewHTTPError(http.StatusNotFound, "File in torrent not found: "+fileLink)
}
return bts.View(tor, file, c)
}
func toTorrentDB(t *torr.Torrent) *settings.Torrent {
if t == nil {
return nil
}
tor := new(settings.Torrent)
tor.Name = t.Name()
tor.Hash = t.Hash().HexString()
tor.Timestamp = settings.StartTime.Unix()
mi := t.Torrent.Metainfo()
tor.Magnet = mi.Magnet(t.Name(), t.Torrent.InfoHash()).String()
tor.Size = t.Length()
files := t.Files()
sort.Slice(files, func(i, j int) bool {
return files[i].Path() < files[j].Path()
})
for _, f := range files {
tf := settings.File{
Name: f.Path(),
Size: f.Length(),
Viewed: false,
}
tor.Files = append(tor.Files, tf)
}
return tor
}
func getTorrentJS(tor *settings.Torrent) (*TorrentJsonResponse, error) {
js := new(TorrentJsonResponse)
mag, err := metainfo.ParseMagnetURI(tor.Magnet)
js.Name = tor.Name
if err == nil && len(tor.Name) < len(mag.DisplayName) {
js.Name = mag.DisplayName
}
js.Magnet = tor.Magnet
js.Hash = tor.Hash
js.AddTime = tor.Timestamp
js.Length = tor.Size
//fname is fake param for file name
js.Playlist = "/torrent/play?link=" + url.QueryEscape(tor.Magnet) + "&m3u=true&fname=" + utils.CleanFName(tor.Name+".m3u")
var size int64 = 0
for _, f := range tor.Files {
size += f.Size
tf := TorFile{
Name: f.Name,
Link: "/torrent/view/" + js.Hash + "/" + utils.CleanFName(f.Name),
Preload: "/torrent/preload/" + js.Hash + "/" + utils.CleanFName(f.Name),
Size: f.Size,
Viewed: f.Viewed,
}
js.Files = append(js.Files, tf)
}
if tor.Size == 0 {
js.Length = size
}
js.Info = settings.GetInfo(tor.Hash)
return js, nil
}
func getJsReqTorr(c echo.Context) (*TorrentJsonRequest, error) {
buf, _ := ioutil.ReadAll(c.Request().Body)
jsstr := string(buf)
decoder := json.NewDecoder(bytes.NewBufferString(jsstr))
js := new(TorrentJsonRequest)
err := decoder.Decode(js)
if err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
} else if se, ok := err.(*json.SyntaxError); ok {
return nil, echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
} else {
return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
}
return js, nil
}

View File

@@ -0,0 +1,67 @@
package server
import (
"server/torr"
"server/web/helpers"
)
type TorrentStat struct {
Name string
Hash string
TorrentStatus int
TorrentStatusString string
LoadedSize int64
TorrentSize int64
PreloadedBytes int64
PreloadSize int64
DownloadSpeed float64
UploadSpeed float64
TotalPeers int
PendingPeers int
ActivePeers int
ConnectedSeeders int
FileStats []FileStat
}
type FileStat struct {
Id int
Path string
Length int64
}
func getTorPlayState(tor *torr.Torrent) TorrentStat {
tst := tor.Stats()
ts := TorrentStat{}
ts.Name = tst.Name
ts.Hash = tst.Hash
ts.TorrentStatus = int(tst.TorrentStatus)
ts.TorrentStatusString = tst.TorrentStatusString
ts.LoadedSize = tst.LoadedSize
ts.TorrentSize = tst.TorrentSize
ts.PreloadedBytes = tst.PreloadedBytes
ts.PreloadSize = tst.PreloadSize
ts.DownloadSpeed = tst.DownloadSpeed
ts.UploadSpeed = tst.UploadSpeed
ts.TotalPeers = tst.TotalPeers
ts.PendingPeers = tst.PendingPeers
ts.ActivePeers = tst.ActivePeers
ts.ConnectedSeeders = tst.ConnectedSeeders
files := helpers.GetPlayableFiles(tst)
ts.FileStats = make([]FileStat, len(files))
for i, f := range files {
ts.FileStats[i] = FileStat{
Id: f.Id,
Path: f.Path,
Length: f.Length,
}
}
return ts
}

View File

@@ -0,0 +1,42 @@
package helpers
import "bytes"
type SeekingBuffer struct {
str string
buffer *bytes.Buffer
offset int64
size int64
}
func NewSeekingBuffer(str string) *SeekingBuffer {
return &SeekingBuffer{
str: str,
buffer: bytes.NewBufferString(str),
offset: 0,
}
}
func (fb *SeekingBuffer) Read(p []byte) (n int, err error) {
n, err = fb.buffer.Read(p)
fb.offset += int64(n)
return n, err
}
func (fb *SeekingBuffer) Seek(offset int64, whence int) (ret int64, err error) {
var newoffset int64
switch whence {
case 0:
newoffset = offset
case 1:
newoffset = fb.offset + offset
case 2:
newoffset = int64(len(fb.str)) - offset
}
if newoffset == fb.offset {
return newoffset, nil
}
fb.buffer = bytes.NewBufferString(fb.str[newoffset:])
fb.offset = newoffset
return fb.offset, nil
}

View File

@@ -0,0 +1,33 @@
package helpers
import (
"fmt"
"net/url"
"server/settings"
"server/torr"
"server/utils"
)
func MakeM3ULists(torrents []*settings.Torrent, host string) string {
m3u := "#EXTM3U\n"
for _, t := range torrents {
m3u += "#EXTINF:0," + t.Name + "\n"
m3u += host + "/torrent/play?link=" + url.QueryEscape(t.Magnet) + "&m3u=true&fname=" + utils.CleanFName(t.Name+".m3u") + "\n\n"
}
return m3u
}
func MakeM3UPlayList(tor torr.TorrentStats, magnet string, host string) string {
m3u := "#EXTM3U\n"
for _, f := range tor.FileStats {
if GetMimeType(f.Path) != "*/*" {
m3u += "#EXTINF:-1," + f.Path + "\n"
mag := url.QueryEscape(magnet)
m3u += host + "/torrent/play?link=" + mag + "&file=" + fmt.Sprint(f.Id) + "\n\n"
}
}
return m3u
}

View File

@@ -0,0 +1,86 @@
package helpers
import (
"errors"
"net/http"
"net/url"
"strings"
"time"
"github.com/anacrolix/torrent/metainfo"
)
func GetMagnet(link string) (*metainfo.Magnet, error) {
url, err := url.Parse(link)
if err != nil {
return nil, err
}
var mag *metainfo.Magnet
switch strings.ToLower(url.Scheme) {
case "magnet":
mag, err = getMag(url.String())
case "http", "https":
mag, err = getMagFromHttp(url.String())
case "":
mag, err = getMag("magnet:?xt=urn:btih:" + url.Path)
default:
mag, err = getMagFromFile(url.Path)
}
if err != nil {
return nil, err
}
return mag, nil
}
func getMag(link string) (*metainfo.Magnet, error) {
mag, err := metainfo.ParseMagnetURI(link)
return &mag, err
}
func getMagFromHttp(url string) (*metainfo.Magnet, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
client := new(http.Client)
client.Timeout = time.Duration(time.Second * 30)
req.Header.Set("User-Agent", "DWL/1.1.1 (Torrent)")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, errors.New(resp.Status)
}
minfo, err := metainfo.Load(resp.Body)
if err != nil {
return nil, err
}
info, err := minfo.UnmarshalInfo()
if err != nil {
return nil, err
}
mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
return &mag, nil
}
func getMagFromFile(path string) (*metainfo.Magnet, error) {
minfo, err := metainfo.LoadFromFile(path)
if err != nil {
return nil, err
}
info, err := minfo.UnmarshalInfo()
if err != nil {
return nil, err
}
mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
return &mag, nil
}

View File

@@ -0,0 +1,89 @@
package helpers
import (
"path/filepath"
"server/torr"
)
var extVideo = map[string]interface{}{
".3g2": nil,
".3gp": nil,
".aaf": nil,
".asf": nil,
".avchd": nil,
".avi": nil,
".drc": nil,
".flv": nil,
".m2ts": nil,
".ts": nil,
".m2v": nil,
".m4p": nil,
".m4v": nil,
".mkv": nil,
".mng": nil,
".mov": nil,
".mp2": nil,
".mp4": nil,
".mpe": nil,
".mpeg": nil,
".mpg": nil,
".mpv": nil,
".mxf": nil,
".nsv": nil,
".ogg": nil,
".ogv": nil,
".qt": nil,
".rm": nil,
".rmvb": nil,
".roq": nil,
".svi": nil,
".vob": nil,
".webm": nil,
".wmv": nil,
".yuv": nil,
}
var extAudio = map[string]interface{}{
".aac": nil,
".aiff": nil,
".ape": nil,
".au": nil,
".flac": nil,
".gsm": nil,
".it": nil,
".m3u": nil,
".m4a": nil,
".mid": nil,
".mod": nil,
".mp3": nil,
".mpa": nil,
".pls": nil,
".ra": nil,
".s3m": nil,
".sid": nil,
".wav": nil,
".wma": nil,
".xm": nil,
}
func GetMimeType(filename string) string {
ext := filepath.Ext(filename)
if _, ok := extVideo[ext]; ok {
return "video/*"
}
if _, ok := extAudio[ext]; ok {
return "audio/*"
}
return "*/*"
}
func GetPlayableFiles(st torr.TorrentStats) []torr.TorrentFileStat {
files := make([]torr.TorrentFileStat, 0)
for _, f := range st.FileStats {
if GetMimeType(f.Path) != "*/*" {
files = append(files, f)
}
}
return files
}

View File

@@ -0,0 +1,66 @@
package helpers
import (
"fmt"
"sort"
"time"
"server/settings"
"server/torr"
"server/utils"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
)
func Add(bts *torr.BTServer, magnet metainfo.Magnet, save bool) error {
fmt.Println("Adding torrent", magnet.String())
_, err := bts.AddTorrent(magnet, func(torr *torr.Torrent) {
torDb := new(settings.Torrent)
torDb.Name = torr.Name()
torDb.Hash = torr.Hash().HexString()
torDb.Size = torr.Length()
torDb.Magnet = magnet.String()
torDb.Timestamp = time.Now().Unix()
files := torr.Files()
sort.Slice(files, func(i, j int) bool {
return files[i].Path() < files[j].Path()
})
for _, f := range files {
ff := settings.File{
f.Path(),
f.Length(),
false,
}
torDb.Files = append(torDb.Files, ff)
}
if save {
err := settings.SaveTorrentDB(torDb)
if err != nil {
fmt.Println("Error add torrent to db:", err)
}
}
})
if err != nil {
return err
}
return nil
}
func FindFileLink(fileLink string, torr *torrent.Torrent) *torrent.File {
for _, f := range torr.Files() {
if utils.CleanFName(f.Path()) == fileLink {
return f
}
}
return nil
}
func FindFile(fileInd int, tor *torr.Torrent) *torrent.File {
files := tor.Files()
if len(files) == 0 || fileInd < 0 || fileInd >= len(files) {
return nil
}
return files[fileInd]
}

View File

@@ -0,0 +1,22 @@
package mods
import (
"github.com/labstack/echo"
)
func InitMods(e *echo.Echo) {
e.GET("/test", test)
}
func test(c echo.Context) error {
return c.HTML(200, `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="/js/api.js"></script>
</head>
<body>
</body>`)
}

View File

@@ -0,0 +1,119 @@
package templates
import "server/version"
var aboutPage = `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="` + faviconB64 + `" rel="icon" type="image/x-icon">
<script src="/js/api.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<title>About</title>
</head>
<body>
<style type="text/css">
.inline{
display:inline;
padding-left: 2%;
}
.center {
display: block;
margin-left: auto;
margin-right: auto;
}
.content {
padding: 20px;
}
</style>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="btn navbar-btn pull-left" href="/"><i class="fas fa-arrow-left"></i></a>
<span class="navbar-brand mx-auto">
О программе
</span>
</nav>
<div class="content">
<img class="center" src='` + faviconB64 + `'/>
<h3 align="middle">TorrServer</h3>
<h4 align="middle">` + version.Version + `</h4>
<h4>Поддержка проекта:</h4>
<a class="inline" target="_blank" href="https://www.paypal.me/yourok">PayPal</a>
<br>
<a class="inline" target="_blank" href="https://money.yandex.ru/to/410013733697114/100">Yandex.Деньги</a>
<br>
<hr align="left" width="25%">
<br>
<h4>Инструкция по использованию:</h4>
<a class="inline" target="_blank" href="https://4pda.ru/forum/index.php?showtopic=896840&st=0#entry72570782">4pda.ru</a>
<p class="inline">Спасибо <b>MadAndron</b></p>
<br>
<hr align="left" width="25%">
<br>
<h4>Автор:</h4>
<b class="inline">YouROK</b>
<br>
<i class="inline">Email:</i>
<a target="_blank" class="inline" href="mailto:8yourok8@gmail.com">8YouROK8@gmail.com</a>
<br>
<i class="inline">Site: </i>
<a target="_blank" class="inline" href="https://github.com/YouROK">GitHub.com/YouROK</a>
<br>
<hr align="left" width="25%">
<br>
<h4>Спасибо всем, кто тестировал и помогал:</h4>
<b class="inline">kuzzman</b>
<br>
<i class="inline">Site: </i>
<a target="_blank" class="inline" href="https://4pda.ru/forum/index.php?showuser=1259550">4pda.ru</a>
<a target="_blank" class="inline" href="http://tv-box.pp.ua">tv-box.pp.ua</a>
<br>
<br>
<b class="inline">MadAndron</b>
<br>
<i class="inline">Site:</i>
<a target="_blank" class="inline" href="https://4pda.ru/forum/index.php?showuser=1543999">4pda.ru</a>
<br>
<br>
<b class="inline">SpAwN_LMG</b>
<br>
<i class="inline">Site:</i>
<a target="_blank" class="inline" href="https://4pda.ru/forum/index.php?showuser=700929">4pda.ru</a>
<br>
<br>
<b class="inline">Zivio</b>
<br>
<i class="inline">Site:</i>
<a target="_blank" class="inline" href="https://4pda.ru/forum/index.php?showuser=1195633">4pda.ru</a>
<a target="_blank" class="inline" href="http://forum.hdtv.ru/index.php?showtopic=19020">forum.hdtv.ru</a>
<br>
<br>
<b class="inline">Tw1cker Руслан Пахнев</b>
<br>
<i class="inline">Site:</i>
<a target="_blank" class="inline" href="https://4pda.ru/forum/index.php?showuser=2002724">4pda.ru</a>
<a target="_blank" class="inline" href="https://github.com/Nemiroff">GitHub.com/Nemiroff</a>
<br>
<br>
</div>
<footer class="page-footer navbar-dark bg-dark">
<span class="navbar-brand d-flex justify-content-center">
<center><h4>TorrServer ` + version.Version + `</h4></center>
</span>
</footer>
</body>
</html>
`
func (t *Template) parseAboutPage() {
parsePage(t, "aboutPage", aboutPage)
}

View File

@@ -0,0 +1,167 @@
package templates
import (
"server/version"
)
var cachePage = `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="` + faviconB64 + `" rel="icon" type="image/x-icon">
<script src="/js/api.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<title>TorrServer ` + version.Version + `</title>
</head>
<body>
<style>
.wrap {
white-space: normal;
word-wrap: break-word;
word-break: break-all;
}
.content {
margin: 1%;
}
.cache {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.piece {
border: 1px dashed white;
font-size: 16px;
padding: 2px;
text-align: center;
}
</style>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="btn navbar-btn pull-left" href="/"><i class="fas fa-arrow-left"></i></a>
<span class="navbar-brand mx-auto">
TorrServer ` + version.Version + `
</span>
</nav>
<div class="content">
<div id="torrents"></div>
<div id="cacheInfo"></div>
<div class="cache" id="cache"></div>
</div>
<footer class="page-footer navbar-dark bg-dark">
<span class="navbar-brand d-flex justify-content-center">
<a rel="external" style="text-decoration: none;" href="/about">Описание</a>
</span>
</footer>
</body>
<script>
$( document ).ready(function() {
setInterval(updateState, 1000);
});
var cacheHash = "";
var hashTorrents = "";
function updateTorrents(){
listTorrent(function(data){
var currHashTorrs = "";
for(var key in data) {
var tor = data[key];
currHashTorrs += tor.Hash;
}
if (currHashTorrs != hashTorrents){
hashTorrents = currHashTorrs;
var html = "";
html += '<div class="btn-group-vertical d-flex" role="group">';
for(var key in data) {
var tor = data[key];
html += '<button type="button" class="btn btn-secondary wrap w-100" onclick="setCache(\''+tor.Hash+'\')">'+tor.Name+'</button>';
}
html += '</div>'
$("#torrents").empty();
$(html).appendTo($("#torrents"));
}
});
}
function updateCache(){
var cache = $("#cache");
if (cacheHash!=""){
cacheTorrent(cacheHash, function(data){
var html = "";
var st = data;
html += '<span>Hash: '+st.Hash+'</span><br>';
html += '<span>Capacity: '+humanizeSize(st.Capacity)+'</span><br>';
html += '<span>Filled: '+humanizeSize(st.Filled)+'</span><br>';
html += '<span>Pieces length: '+humanizeSize(st.PiecesLength)+'</span><br>';
html += '<span>Pieces count: '+st.PiecesCount+'</span><br>';
$("#cacheInfo").html(html);
makePieces(st.PiecesCount);
for(var i = 0; i < st.PiecesCount; i++) {
var color = "silver";
var size = "";
var piece = st.Pieces[i];
if (piece){
if (piece.Completed && piece.BufferSize >= st.PiecesLength)
color = "green";
else if (piece.Completed && piece.BufferSize == 0)
color = "silver";
else if (!piece.Completed && piece.BufferSize > 0)
color = "red";
size = ' ' + humanizeSize(piece.BufferSize);
}
setPiece(i,color,size);
}
},function(){
$("#cacheInfo").empty();
cache.empty();
});
}
}
function makePieces(len){
var cache = $("#cache");
if (cache.children().length==len)
return;
var html = "";
for(var i = 0; i < len; i++) {
html += '<span class="piece" id="p'+i+'" style="background-color: silver;">'+i+'</span>';
}
cache.html(html);
}
function setPiece(i, color, size){
var piece = $("#p"+i);
piece.delay(100).css("background-color",color);
piece.text(i+''+size);
}
function contains(arr, elem) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].Id === elem) {
return true;
}
}
return false;
}
function updateState(){
updateTorrents();
updateCache();
}
function setCache(hash){
cacheHash = hash;
updateCache();
}
</script>
</html>
`
func (t *Template) parseCachePage() {
parsePage(t, "cachePage", cachePage)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,300 @@
package templates
import (
"server/version"
)
var mainPage = `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="` + faviconB64 + `" rel="icon" type="image/x-icon">
<script src="/js/api.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<title>TorrServer ` + version.Version + `</title>
</head>
<body>
<style>
.wrap {
white-space: normal;
word-wrap: break-word;
word-break: break-all;
}
.content {
margin: 1%;
}
</style>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<span class="navbar-brand mx-auto">
TorrServer ` + version.Version + `
</span>
</nav>
<div class="content">
<div>
<label for="magnet">Добавить торрент: </label>
<input id="magnet" class="w-100" autocomplete="off">
</div>
<div class="btn-group d-flex" role="group">
<button id="buttonAdd" class="btn w-100" onclick="addTorr()"><i class="fas fa-plus"></i> Добавить</button>
<button id="buttonUpload" class="btn w-100"><i class="fas fa-file-upload"></i> Загрузить файл</button>
</div>
<br>
<div>
<a href="/torrent/playlist.m3u" rel="external" class="btn btn-primary w-100" role="button" ><i class="fas fa-th-list"></i> Плейлист всех торрентов</a>
</div>
<br>
<h3>Торренты: </h3>
<div id="torrents"></div>
<br>
<div class="btn-group-vertical d-flex" role="group">
<a href="/settings" rel="external" class="btn btn-primary w-100" role="button"><i class="fas fa-cog"></i> Настройки</a>
<a href="/cache" rel="external" class="btn btn-primary w-100" role="button"><i class="fas fa-info"></i> Кэш</a>
<button id="buttonShutdown" class="btn btn-primary w-100" onclick="shutdown()"><i class="fas fa-power-off"></i> Закрыть сервер</button>
</div>
<form id="uploadForm" style="display:none" action="/torrent/upload" method="post">
<input type="file" id="filesUpload" style="display:none" multiple onchange="uploadTorrent()" name="files"/>
</form>
</div>
<footer class="page-footer navbar-dark bg-dark">
<span class="navbar-brand d-flex justify-content-center">
<a rel="external" style="text-decoration: none;" href="/about">Описание</a>
</span>
</footer>
<div class="modal fade" id="preloadModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title wrap" id="preloadName"></h4>
</div>
<div class="modal-body">
<p id="preloadStatus"></p>
<p id="preloadBuffer"></p>
<p id="preloadPeers"></p>
<p id="preloadSpeed"></p>
<div class="progress">
<div id="preloadProgress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
</div>
<br>
<a id="preloadFileLink" role="button" href="" class="btn btn-secondary wrap w-100"></a>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
<script>
function addTorr(){
var magnet = $("#magnet").val();
$("#magnet").val("");
if(magnet!=""){
addTorrent(magnet,true,
function( data ) {
loadTorrents();
},
function( data ) {
alert(data.responseJSON.message);
});
}
}
function removeTorr(hash){
if(hash!=""){
removeTorrent(hash,
function( data ) {
loadTorrents();
},
function( data ) {
alert(data.responseJSON.message);
});
}
};
function shutdown(){
shutdownServer(function( data ) {
alert(data.responseJSON.message);
});
}
$( document ).ready(function() {
watchInfo();
});
$('#buttonUpload').click(function() {
$('#filesUpload').click();
});
function uploadTorrent() {
var form = $("#uploadForm");
var formData = new FormData(document.getElementById("uploadForm"));
var data = new FormData();
$.each($('#filesUpload')[0].files, function(i, file) {
data.append('file-'+i, file);
});
$.ajax({
cache: false,
processData: false,
contentType: false,
type: form.attr('method'),
url: form.attr('action'),
data: data
}).done(function(data) {
loadTorrents();
}).fail(function(data) {
alert(data.responseJSON.message);
});
}
$('#uploadForm').submit(function(event) {
event.preventDefault();
var form = $(this);
$.ajax({
type: form.attr('method'),
url: form.attr('action'),
data: form.serialize()
}).done(function(data) {
loadTorrents();
});
});
function loadTorrents() {
listTorrent(
function( data ) {
var torrents = $("#torrents");
torrents.empty();
var html = "";
var queueInfo = [];
for(var key in data) {
var tor = data[key];
if (tor.Status==1){
queueInfo.push(tor);
continue;
}
html += tor2Html(tor);
}
if (queueInfo.length>0){
html += "<br><hr><h3>Got info: </h3>";
for(var key in queueInfo) {
var tor = queueInfo[key];
html += tor2Html(tor);
}
}
$(html).appendTo(torrents);
},
function( data ) {
alert(data.responseJSON.message);
});
}
function tor2Html(tor){
var html = '';
var name = "";
if (tor.Status==1)
name = tor.Name+' '+humanizeSize(tor.Length)+' '+tor.Hash;
else
name = tor.Name+' '+humanizeSize(tor.Length);
html += '<div class="btn-group d-flex" role="group">';
html += ' <button type="button" class="btn btn-secondary wrap w-100" data-toggle="collapse" data-target="#info_'+tor.Hash+'">'+name+'</button>';
if (tor.Status!=1)
html += ' <a role="button" class="btn btn-secondary" href="'+tor.Playlist+'"><i class="fas fa-th-list"></i> Плейлист</a>';
else
html += ' <button type="button" class="btn btn-secondary" onclick="showPreload(\'\', \''+ tor.Hash +'\');"><i class="fas fa-info"></i></a>';
html += ' <button type="button" class="btn btn-secondary" onclick="removeTorrent(\''+tor.Hash+'\');"><i class="fas fa-trash-alt"></i> Удалить</button>';
html += '</div>';
html += '<div class="collapse" id="info_'+tor.Hash+'">';
for(var i in tor.Files){
var file = tor.Files[i];
var ico = "";
if (file.Viewed)
ico = '<i class="far fa-eye"></i> ';
html += ' <div class="btn-group d-flex" role="group">';
html += ' <a role="button" href="'+file.Link+'" class="btn btn-secondary wrap w-100">'+ico+file.Name+" "+humanizeSize(file.Size)+'</a>';
html += ' <button type="button" class="btn btn-secondary" onclick="showPreload(\''+ file.Preload +'\', \''+ file.Link +'\', \''+ tor.Hash +'\');"><i class="fas fa-info"></i></button>';
html += ' </div>';
}
html += '<hr></div>';
return html;
}
function watchInfo(){
var lastTorrentCount = 0;
var lastGettingInfo = 0;
setInterval(function() {
listTorrent(
function( data ) {
var gettingInfo = 0;
for(var key in data) {
var tor = data[key];
if (tor.Status==1)
gettingInfo++;
}
if (lastTorrentCount!=data.length || gettingInfo!=lastGettingInfo){
loadTorrents();
lastTorrentCount = data.length;
lastGettingInfo = gettingInfo;
}
});
}, 1000);
}
function showPreload(preloadlink, fileLink, hash){
$('#preloadFileLink').hide(0);
$('#preloadFileLink').attr("href","");
$('#preloadProgress').width('100%');
if (preloadlink!='')
preloadTorrent(preloadlink);
var ptimer = setInterval(function() {
statTorrent(hash,function(data){
if (data!=null){
$('#preloadStatus').text("Status: " + data.TorrentStatusString);
$('#preloadName').text(data.Name);
$('#preloadPeers').text("Peers: [" + data.ConnectedSeeders + "] " + data.ActivePeers + " / " + data.TotalPeers);
if (data.DownloadSpeed>0)
$('#preloadSpeed').text("Speed: "+ humanizeSize(data.DownloadSpeed) + "/Sec");
else
$('#preloadSpeed').text("Speed:");
if (data.PreloadSize>0 && data.PreloadedBytes<data.PreloadSize){
var prc = data.PreloadedBytes * 100 / data.PreloadSize;
if (prc>100) prc = 100;
$('#preloadProgress').width(prc+'%');
$('#preloadBuffer').text("Loaded: " + humanizeSize(data.PreloadedBytes) + " / " + humanizeSize(data.PreloadSize)+" "+prc+"%");
}else{
$('#preloadProgress').width('100%');
$('#preloadBuffer').text("Loaded: " + humanizeSize(data.BytesReadUsefulData));
$('#preloadProgress').width('100%');
if (data.BytesReadUsefulData>0 && fileLink && !$('#preloadFileLink').attr("href")){
$('#preloadFileLink').text(data.Name);
$('#preloadFileLink').attr("href", fileLink);
$('#preloadFileLink').show();
}
}
}
},function(){
$('#preloadModal').modal('hide');
})
}, 500);
$('#preloadModal').modal('show');
$("#preloadModal").on('hidden.bs.modal', function () {
clearInterval(ptimer);
});
}
</script>
</body>
</html>
`
func (t *Template) parseMainPage() {
parsePage(t, "mainPage", mainPage)
}

View File

@@ -0,0 +1,200 @@
package templates
import "server/version"
var settingsPage = `
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="` + faviconB64 + `" rel="icon" type="image/x-icon">
<script src="/js/api.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<title>TorrServer ` + version.Version + ` Settings</title>
</head>
<body>
<style>
.wrap {
white-space: normal;
word-wrap: break-word;
word-break: break-all;
}
.content {
margin: 1%;
}
</style>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="btn navbar-btn pull-left" href="/"><i class="fas fa-arrow-left"></i></a>
<span class="navbar-brand mx-auto">
TorrServer ` + version.Version + `
</span>
</nav>
<div class="content">
<form id="settings">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Размер кэша</div>
</div>
<input id="CacheSize" class="form-control" type="number" autocomplete="off">
</div>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Размер буфера предзагрузки</div>
</div>
<input id="PreloadBufferSize" class="form-control" type="number" autocomplete="off">
</div>
<small class="form-text text-muted">Размеры кэша и буфера указываются в мегабайтах</small>
<br>
<div class="form-check">
<input id="DisableTCP" class="form-check-input" type="checkbox" autocomplete="off">
<label for="DisableTCP">Отключить TCP</label>
</div>
<div class="form-check">
<input id="DisableUTP" class="form-check-input" type="checkbox" autocomplete="off">
<label for="DisableUTP">Отключить UTP</label>
</div>
<div class="form-check">
<input id="DisableUPNP" class="form-check-input" type="checkbox" autocomplete="off">
<label for="DisableUPNP">Отключить UPNP</label>
</div>
<div class="form-check">
<input id="DisableDHT" class="form-check-input" type="checkbox" autocomplete="off">
<label for="DisableDHT">Отключить DHT</label>
</div>
<div class="form-check">
<input id="DisableUpload" class="form-check-input" type="checkbox" autocomplete="off">
<label for="DisableUpload">Отключить Отдачу</label>
</div>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Шифрование</div>
</div>
<select id="Encryption" class="form-control">
<option value="0">По умолчанию</option>
<option value="1">Отключить</option>
<option value="2">Принудительно</option>
</select>
</div>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Количество соединений</div>
</div>
<input id="ConnectionsLimit" class="form-control" type="number" autocomplete="off">
</div>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Ограничение загрузки</div>
</div>
<input id="DownloadRateLimit" class="form-control" type="number" autocomplete="off">
</div>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Ограничение отдачи</div>
</div>
<input id="UploadRateLimit" class="form-control" type="number" autocomplete="off">
</div>
<small class="form-text text-muted">Ограничение устанавливается в Килобайтах, 0 - не ограничивать</small>
<br>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Ретрекеры</div>
</div>
<select id="RetrackersMode" class="form-control">
<option value="0">Оставить как есть</option>
<option value="1">Добавить</option>
<option value="2">Удалить</option>
</select>
</div>
</form>
<br>
<div class="btn-group d-flex" role="group">
<button id="buttonSave" class="btn btn-primary w-100" data-icon="check" onclick="saveSettings()"><i class="far fa-save"></i> Сохранить</button>
<button id="buttonRefresh" class="btn btn-primary w-100" data-icon="refresh" onclick="refreshSettings()"><i class="fas fa-sync-alt"></i> Получить с сервера</button>
</div>
</div>
<footer class="page-footer navbar-dark bg-dark">
<span class="navbar-brand d-flex justify-content-center">
<a rel="external" style="text-decoration: none;" href="/about">Описание</a>
</span>
</footer>
<script>
function saveSettings() {
var data = {};
data.CacheSize = Number($('#CacheSize').val())*(1024*1024);
data.PreloadBufferSize = Number($('#PreloadBufferSize').val())*(1024*1024);
data.DisableTCP = $('#DisableTCP').prop('checked');
data.DisableUTP = $('#DisableUTP').prop('checked');
data.DisableUPNP = $('#DisableUPNP').prop('checked');
data.DisableDHT = $('#DisableDHT').prop('checked');
data.DisableUpload = $('#DisableUpload').prop('checked');
data.Encryption = Number($('#Encryption').val());
data.ConnectionsLimit = Number($('#ConnectionsLimit').val());
data.DownloadRateLimit = Number($('#DownloadRateLimit').val());
data.UploadRateLimit = Number($('#UploadRateLimit').val());
data.RetrackersMode = Number($('#RetrackersMode').val());
$.post("/settings/write", JSON.stringify(data))
.done(function(data) {
restartService();
alert(data);
})
.fail(function(data) {
alert(data.responseJSON.message);
});
}
function refreshSettings() {
$.post("/settings/read")
.done(function(data) {
$('#CacheSize').val(data.CacheSize/(1024*1024));
$('#PreloadBufferSize').val(data.PreloadBufferSize/(1024*1024));
$('#DisableTCP').prop('checked', data.DisableTCP);
$('#DisableUTP').prop('checked', data.DisableUTP);
$('#DisableUPNP').prop('checked', data.DisableUPNP);
$('#DisableDHT').prop('checked', data.DisableDHT);
$('#DisableUpload').prop('checked', data.DisableUpload);
$('#Encryption').val(data.Encryption);
$('#ConnectionsLimit').val(data.ConnectionsLimit);
$('#DownloadRateLimit').val(data.DownloadRateLimit);
$('#UploadRateLimit').val(data.UploadRateLimit);
$('#RetrackersMode').val(data.RetrackersMode);
});
};
$(document).ready(function() {
refreshSettings();
});
$(document).on("wheel", "input[type=number]", function (e) {
$(this).blur();
});
</script>
</body>
</html>
`
func (t *Template) parseSettingsPage() {
parsePage(t, "settingsPage", settingsPage)
}

View File

@@ -0,0 +1,45 @@
package templates
import (
"html/template"
"io"
"github.com/labstack/echo"
)
type Template struct {
templates *template.Template
}
func InitTemplate(e *echo.Echo) {
temp := new(Template)
temp.parseMainPage()
temp.parseSettingsPage()
temp.parseAboutPage()
temp.parseCachePage()
e.Renderer = temp
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func parsePage(temp *Template, name, page string) error {
s := page
var tmpl *template.Template
if temp.templates == nil {
temp.templates = template.New(name)
}
if name == temp.templates.Name() {
tmpl = temp.templates
} else {
tmpl = temp.templates.New(name)
}
_, err := tmpl.Parse(s)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,133 @@
package templates
import (
"net/http"
"server/settings"
"server/web/helpers"
"github.com/labstack/echo"
)
var apijs = `
function addTorrent(link, save, info, done, fail){
var reqJson = JSON.stringify({ Link: link, Info: info, DontSave: !save});
$.post('/torrent/add',reqJson)
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function getTorrent(hash, done, fail){
var reqJson = JSON.stringify({ Hash: hash});
$.post('/torrent/get',reqJson)
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function removeTorrent(hash, done, fail){
var reqJson = JSON.stringify({ Hash: hash});
$.post('/torrent/rem',reqJson)
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function statTorrent(hash, done, fail){
var reqJson = JSON.stringify({ Hash: hash});
$.post('/torrent/stat',reqJson)
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function cacheTorrent(hash, done, fail){
var reqJson = JSON.stringify({ Hash: hash});
$.post('/torrent/cache',reqJson)
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function listTorrent(done, fail){
$.post('/torrent/list')
.done(function( data ) {
if (done)
done(data);
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function restartService(done, fail){
$.get('/torrent/restart')
.done(function( data ) {
if (done)
done();
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function preloadTorrent(preloadLink, done, fail){
$.get(preloadLink)
.done(function( data ) {
if (done)
done();
})
.fail(function( data ) {
if (fail)
fail(data);
});
}
function shutdownServer(fail){
$.post('/shutdown')
.fail(function( data ) {
if (fail)
fail(data);
});
}
function humanizeSize(size) {
if (typeof size == 'undefined' || size == 0)
return "";
var i = Math.floor( Math.log(size) / Math.log(1024) );
return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}
`
func Api_JS(c echo.Context) error {
http.ServeContent(c.Response(), c.Request(), "api.js", settings.StartTime, helpers.NewSeekingBuffer(apijs))
return c.NoContent(http.StatusOK)
}