This commit is contained in:
YouROK
2020-11-10 14:06:13 +03:00
parent c95eaccc57
commit 6a11651cf5
15 changed files with 566 additions and 209 deletions

View File

@@ -126,9 +126,9 @@ func (v *TDB) List(xpath string) []string {
} }
} }
buckt.ForEach(func(_, v []byte) error { buckt.ForEach(func(k, _ []byte) error {
if len(v) > 0 { if len(k) > 0 {
ret = append(ret, string(v)) ret = append(ret, string(k))
} }
return nil return nil
}) })

View File

@@ -7,7 +7,7 @@ import (
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
"server/torr" "server/torr/state"
) )
type TorrentDB struct { type TorrentDB struct {
@@ -18,7 +18,7 @@ type TorrentDB struct {
Timestamp int64 `json:"timestamp,omitempty"` Timestamp int64 `json:"timestamp,omitempty"`
Files []torr.TorrentFileStat `json:"files,omitempty"` Files []state.TorrentFileStat `json:"files,omitempty"`
} }
type File struct { type File struct {
@@ -63,7 +63,7 @@ func ListTorrent() []*TorrentDB {
buf := tdb.Get("Torrents", key) buf := tdb.Get("Torrents", key)
if len(buf) > 0 { if len(buf) > 0 {
var torr *TorrentDB var torr *TorrentDB
err := json.Unmarshal(buf, torr) err := json.Unmarshal(buf, &torr)
if err == nil { if err == nil {
list = append(list, torr) list = append(list, torr)
} }

View File

@@ -0,0 +1,79 @@
package settings
import (
"encoding/json"
"server/log"
)
type Viewed struct {
Hash string `json:"hash"`
FileIndex int `json:"file_index"`
}
func SetViewed(vv *Viewed) {
buf := tdb.Get("Viewed", vv.Hash)
var indeces map[int]struct{}
err := json.Unmarshal(buf, &indeces)
if err == nil {
indeces[vv.FileIndex] = struct{}{}
buf, err = json.Marshal(indeces)
if err == nil {
tdb.Set("Viewed", vv.Hash, buf)
}
}
if err != nil {
log.TLogln("Error set viewed:", err)
}
}
func RemViewed(vv *Viewed) {
buf := tdb.Get("Viewed", vv.Hash)
var indeces map[int]struct{}
err := json.Unmarshal(buf, &indeces)
if err == nil {
delete(indeces, vv.FileIndex)
buf, err = json.Marshal(indeces)
if err == nil {
tdb.Set("Viewed", vv.Hash, buf)
}
}
if err != nil {
log.TLogln("Error rem viewed:", err)
}
}
func ListViewed(hash string) []*Viewed {
var err error
if hash != "" {
buf := tdb.Get("Viewed", hash)
var indeces map[int]struct{}
err = json.Unmarshal(buf, &indeces)
if err == nil {
var ret []*Viewed
for i, _ := range indeces {
ret = append(ret, &Viewed{hash, i})
}
return ret
}
} else {
var ret []*Viewed
keys := tdb.List("Viewed")
for _, key := range keys {
buf := tdb.Get("Viewed", key)
var indeces map[int]struct{}
err = json.Unmarshal(buf, &indeces)
if err == nil {
for i, _ := range indeces {
ret = append(ret, &Viewed{hash, i})
}
}
}
return ret
}
if err != nil {
log.TLogln("Error list viewed:", err)
}
return nil
}

View File

@@ -83,6 +83,9 @@ func (bt *BTServer) configure() {
bt.config.HTTPUserAgent = userAgent bt.config.HTTPUserAgent = userAgent
bt.config.ExtendedHandshakeClientVersion = cliVers bt.config.ExtendedHandshakeClientVersion = cliVers
bt.config.EstablishedConnsPerTorrent = settings.BTsets.ConnectionsLimit bt.config.EstablishedConnsPerTorrent = settings.BTsets.ConnectionsLimit
bt.config.DefaultRequestStrategy = torrent.RequestStrategyFastest()
if settings.BTsets.DhtConnectionLimit > 0 { if settings.BTsets.DhtConnectionLimit > 0 {
bt.config.ConnTracker.SetMaxEntries(settings.BTsets.DhtConnectionLimit) bt.config.ConnTracker.SetMaxEntries(settings.BTsets.DhtConnectionLimit)
} }

View File

@@ -1,130 +0,0 @@
package torr
import (
"fmt"
"sort"
"github.com/anacrolix/torrent"
)
type BTState struct {
LocalPort int
PeerID string
BannedIPs int
DHTs []torrent.DhtServer
Torrents []*Torrent
}
func (bt *BTServer) BTState() *BTState {
bt.mu.Lock()
defer bt.mu.Unlock()
btState := new(BTState)
btState.LocalPort = bt.client.LocalPort()
btState.PeerID = fmt.Sprintf("%x", bt.client.PeerID())
btState.BannedIPs = len(bt.client.BadPeerIPs())
btState.DHTs = bt.client.DhtServers()
for _, t := range bt.torrents {
btState.Torrents = append(btState.Torrents, t)
}
return btState
}
type TorrentStats struct {
Name string `json:"name,omitempty"`
Hash string `json:"hash,omitempty"`
TorrentStatus TorrentStatus `json:"torrent_status,omitempty"`
TorrentStatusString string `json:"torrent_status_string,omitempty"`
LoadedSize int64 `json:"loaded_size,omitempty"`
TorrentSize int64 `json:"torrent_size,omitempty"`
PreloadedBytes int64 `json:"preloaded_bytes,omitempty"`
PreloadSize int64 `json:"preload_size,omitempty"`
DownloadSpeed float64 `json:"download_speed,omitempty"`
UploadSpeed float64 `json:"upload_speed,omitempty"`
TotalPeers int `json:"total_peers,omitempty"`
PendingPeers int `json:"pending_peers,omitempty"`
ActivePeers int `json:"active_peers,omitempty"`
ConnectedSeeders int `json:"connected_seeders,omitempty"`
HalfOpenPeers int `json:"half_open_peers,omitempty"`
BytesWritten int64 `json:"bytes_written,omitempty"`
BytesWrittenData int64 `json:"bytes_written_data,omitempty"`
BytesRead int64 `json:"bytes_read,omitempty"`
BytesReadData int64 `json:"bytes_read_data,omitempty"`
BytesReadUsefulData int64 `json:"bytes_read_useful_data,omitempty"`
ChunksWritten int64 `json:"chunks_written,omitempty"`
ChunksRead int64 `json:"chunks_read,omitempty"`
ChunksReadUseful int64 `json:"chunks_read_useful,omitempty"`
ChunksReadWasted int64 `json:"chunks_read_wasted,omitempty"`
PiecesDirtiedGood int64 `json:"pieces_dirtied_good,omitempty"`
PiecesDirtiedBad int64 `json:"pieces_dirtied_bad,omitempty"`
FileStats []TorrentFileStat `json:"file_stats,omitempty"`
}
type TorrentFileStat struct {
Id int `json:"id,omitempty"`
Path string `json:"path,omitempty"`
Length int64 `json:"length,omitempty"`
}
func (t *Torrent) Stats() TorrentStats {
t.muTorrent.Lock()
defer t.muTorrent.Unlock()
st := TorrentStats{}
st.Name = t.Name()
st.Hash = t.hash.HexString()
st.TorrentStatus = t.Status
st.TorrentStatusString = t.Status.String()
if t.Torrent != nil {
st.LoadedSize = t.Torrent.BytesCompleted()
st.TorrentSize = t.Length()
st.PreloadedBytes = t.PreloadedBytes
st.PreloadSize = t.PreloadSize
st.DownloadSpeed = t.DownloadSpeed
st.UploadSpeed = t.UploadSpeed
tst := t.Torrent.Stats()
st.BytesWritten = tst.BytesWritten.Int64()
st.BytesWrittenData = tst.BytesWrittenData.Int64()
st.BytesRead = tst.BytesRead.Int64()
st.BytesReadData = tst.BytesReadData.Int64()
st.BytesReadUsefulData = tst.BytesReadUsefulData.Int64()
st.ChunksWritten = tst.ChunksWritten.Int64()
st.ChunksRead = tst.ChunksRead.Int64()
st.ChunksReadUseful = tst.ChunksReadUseful.Int64()
st.ChunksReadWasted = tst.ChunksReadWasted.Int64()
st.PiecesDirtiedGood = tst.PiecesDirtiedGood.Int64()
st.PiecesDirtiedBad = tst.PiecesDirtiedBad.Int64()
st.TotalPeers = tst.TotalPeers
st.PendingPeers = tst.PendingPeers
st.ActivePeers = tst.ActivePeers
st.ConnectedSeeders = tst.ConnectedSeeders
st.HalfOpenPeers = tst.HalfOpenPeers
files := t.Files()
sort.Slice(files, func(i, j int) bool {
return files[i].Path() < files[j].Path()
})
for i, f := range files {
st.FileStats = append(st.FileStats, TorrentFileStat{
Id: i,
Path: f.Path(),
Length: f.Length(),
})
}
}
return st
}

View File

@@ -0,0 +1,74 @@
package state
type TorrentStatus int
func (t TorrentStatus) String() string {
switch t {
case TorrentAdded:
return "Torrent added"
case TorrentGettingInfo:
return "Torrent getting info"
case TorrentPreload:
return "Torrent preload"
case TorrentWorking:
return "Torrent working"
case TorrentClosed:
return "Torrent closed"
case TorrentInDB:
return "Torrent in db"
default:
return "Torrent unknown status"
}
}
const (
TorrentAdded = TorrentStatus(iota)
TorrentGettingInfo
TorrentPreload
TorrentWorking
TorrentClosed
TorrentInDB
)
type TorrentStats struct {
Name string `json:"name,omitempty"`
Hash string `json:"hash,omitempty"`
TorrentStatus TorrentStatus `json:"torrent_status,omitempty"`
TorrentStatusString string `json:"torrent_status_string,omitempty"`
LoadedSize int64 `json:"loaded_size,omitempty"`
TorrentSize int64 `json:"torrent_size,omitempty"`
PreloadedBytes int64 `json:"preloaded_bytes,omitempty"`
PreloadSize int64 `json:"preload_size,omitempty"`
DownloadSpeed float64 `json:"download_speed,omitempty"`
UploadSpeed float64 `json:"upload_speed,omitempty"`
TotalPeers int `json:"total_peers,omitempty"`
PendingPeers int `json:"pending_peers,omitempty"`
ActivePeers int `json:"active_peers,omitempty"`
ConnectedSeeders int `json:"connected_seeders,omitempty"`
HalfOpenPeers int `json:"half_open_peers,omitempty"`
BytesWritten int64 `json:"bytes_written,omitempty"`
BytesWrittenData int64 `json:"bytes_written_data,omitempty"`
BytesRead int64 `json:"bytes_read,omitempty"`
BytesReadData int64 `json:"bytes_read_data,omitempty"`
BytesReadUsefulData int64 `json:"bytes_read_useful_data,omitempty"`
ChunksWritten int64 `json:"chunks_written,omitempty"`
ChunksRead int64 `json:"chunks_read,omitempty"`
ChunksReadUseful int64 `json:"chunks_read_useful,omitempty"`
ChunksReadWasted int64 `json:"chunks_read_wasted,omitempty"`
PiecesDirtiedGood int64 `json:"pieces_dirtied_good,omitempty"`
PiecesDirtiedBad int64 `json:"pieces_dirtied_bad,omitempty"`
FileStats []TorrentFileStat `json:"file_stats,omitempty"`
}
type TorrentFileStat struct {
Id int `json:"id,omitempty"`
Path string `json:"path,omitempty"`
Length int64 `json:"length,omitempty"`
}

View File

@@ -1,11 +1,11 @@
package torrstor package torrstor
import ( import (
"log"
"sort" "sort"
"sync" "sync"
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
"server/log"
"server/settings" "server/settings"
"server/torr/utils" "server/torr/utils"
@@ -52,7 +52,7 @@ func NewCache(capacity int64, storage *Storage) *Cache {
} }
func (c *Cache) Init(info *metainfo.Info, hash metainfo.Hash) { func (c *Cache) Init(info *metainfo.Info, hash metainfo.Hash) {
log.Println("Create cache for:", info.Name) log.TLogln("Create cache for:", info.Name)
if c.capacity == 0 { if c.capacity == 0 {
c.capacity = info.PieceLength * 6 c.capacity = info.PieceLength * 6
} }
@@ -89,7 +89,7 @@ func (c *Cache) Piece(m metainfo.Piece) storage.PieceImpl {
func (c *Cache) Close() error { func (c *Cache) Close() error {
c.isRemove = false c.isRemove = false
log.Println("Close cache for:", c.hash) log.TLogln("Close cache for:", c.hash)
if _, ok := c.s.caches[c.hash]; ok { if _, ok := c.s.caches[c.hash]; ok {
delete(c.s.caches, c.hash) delete(c.s.caches, c.hash)
} }

View File

@@ -2,11 +2,13 @@ package torr
import ( import (
"io" "io"
"log" "sort"
"sync" "sync"
"time" "time"
"server/log"
"server/settings" "server/settings"
"server/torr/state"
"server/torr/utils" "server/torr/utils"
utils2 "server/utils" utils2 "server/utils"
@@ -16,43 +18,13 @@ import (
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
) )
type TorrentStatus int
func (t TorrentStatus) String() string {
switch t {
case TorrentAdded:
return "Torrent added"
case TorrentGettingInfo:
return "Torrent getting info"
case TorrentPreload:
return "Torrent preload"
case TorrentWorking:
return "Torrent working"
case TorrentClosed:
return "Torrent closed"
case TorrentInDB:
return "Torrent in db"
default:
return "Torrent unknown status"
}
}
const (
TorrentAdded = TorrentStatus(iota)
TorrentGettingInfo
TorrentPreload
TorrentWorking
TorrentClosed
TorrentInDB
)
type Torrent struct { type Torrent struct {
///// info for db ///// info for db
Title string Title string
Poster string Poster string
*torrent.TorrentSpec *torrent.TorrentSpec
Status TorrentStatus Status state.TorrentStatus
///// /////
*torrent.Torrent *torrent.Torrent
@@ -103,7 +75,7 @@ func NewTorrent(spec *torrent.TorrentSpec, bt *BTServer) (*Torrent, error) {
torr := new(Torrent) torr := new(Torrent)
torr.Torrent = goTorrent torr.Torrent = goTorrent
torr.Status = TorrentAdded torr.Status = state.TorrentAdded
torr.lastTimeSpeed = time.Now() torr.lastTimeSpeed = time.Now()
torr.bt = bt torr.bt = bt
torr.closed = goTorrent.Closed() torr.closed = goTorrent.Closed()
@@ -135,12 +107,12 @@ func (t *Torrent) WaitInfo() bool {
} }
func (t *Torrent) GotInfo() bool { func (t *Torrent) GotInfo() bool {
if t.Status == TorrentClosed { if t.Status == state.TorrentClosed {
return false return false
} }
t.Status = TorrentGettingInfo t.Status = state.TorrentGettingInfo
if t.WaitInfo() { if t.WaitInfo() {
t.Status = TorrentWorking t.Status = state.TorrentWorking
t.expiredTime = time.Now().Add(time.Minute * 5) t.expiredTime = time.Now().Add(time.Minute * 5)
return true return true
} else { } else {
@@ -166,6 +138,7 @@ func (t *Torrent) watch() {
func (t *Torrent) progressEvent() { func (t *Torrent) progressEvent() {
if t.expired() { if t.expired() {
log.TLogln("Torrent close by timeout", t.Torrent.InfoHash().HexString())
t.drop() t.drop()
return return
} }
@@ -207,7 +180,7 @@ func (t *Torrent) updateRA() {
} }
func (t *Torrent) expired() bool { func (t *Torrent) expired() bool {
return t.cache.ReadersLen() == 0 && t.expiredTime.Before(time.Now()) && (t.Status == TorrentWorking || t.Status == TorrentClosed) return t.cache.ReadersLen() == 0 && t.expiredTime.Before(time.Now()) && (t.Status == state.TorrentWorking || t.Status == state.TorrentClosed)
} }
func (t *Torrent) Files() []*torrent.File { func (t *Torrent) Files() []*torrent.File {
@@ -230,7 +203,7 @@ func (t *Torrent) Length() int64 {
} }
func (t *Torrent) NewReader(file *torrent.File, readahead int64) *Reader { func (t *Torrent) NewReader(file *torrent.File, readahead int64) *Reader {
if t.Status == TorrentClosed { if t.Status == state.TorrentClosed {
return nil return nil
} }
reader := NewReader(t, file, readahead) reader := NewReader(t, file, readahead)
@@ -252,14 +225,14 @@ func (t *Torrent) Preload(index int, size int64) {
return return
} }
if t.Status == TorrentGettingInfo { if t.Status == state.TorrentGettingInfo {
t.WaitInfo() t.WaitInfo()
// wait change status // wait change status
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
t.muTorrent.Lock() t.muTorrent.Lock()
if t.Status != TorrentWorking { if t.Status != state.TorrentWorking {
t.muTorrent.Unlock() t.muTorrent.Unlock()
return return
} }
@@ -271,12 +244,12 @@ func (t *Torrent) Preload(index int, size int64) {
t.muTorrent.Unlock() t.muTorrent.Unlock()
return return
} }
t.Status = TorrentPreload t.Status = state.TorrentPreload
t.muTorrent.Unlock() t.muTorrent.Unlock()
defer func() { defer func() {
if t.Status == TorrentPreload { if t.Status == state.TorrentPreload {
t.Status = TorrentWorking t.Status = state.TorrentWorking
} }
}() }()
@@ -321,10 +294,10 @@ func (t *Torrent) Preload(index int, size int64) {
t.PreloadSize = size t.PreloadSize = size
var lastSize int64 = 0 var lastSize int64 = 0
errCount := 0 errCount := 0
for t.Status == TorrentPreload { for t.Status == state.TorrentPreload {
t.expiredTime = time.Now().Add(time.Minute * 5) t.expiredTime = time.Now().Add(time.Minute * 5)
t.PreloadedBytes = t.Torrent.BytesCompleted() t.PreloadedBytes = t.Torrent.BytesCompleted()
log.Println("Preload:", file.Torrent().InfoHash().HexString(), utils2.Format(float64(t.PreloadedBytes)), "/", utils2.Format(float64(t.PreloadSize)), "Speed:", utils2.Format(t.DownloadSpeed), "Peers:[", t.Torrent.Stats().ConnectedSeeders, "]", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers) log.TLogln("Preload:", file.Torrent().InfoHash().HexString(), utils2.Format(float64(t.PreloadedBytes)), "/", utils2.Format(float64(t.PreloadSize)), "Speed:", utils2.Format(t.DownloadSpeed), "Peers:[", t.Torrent.Stats().ConnectedSeeders, "]", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers)
if t.PreloadedBytes >= t.PreloadSize { if t.PreloadedBytes >= t.PreloadSize {
return return
} }
@@ -352,7 +325,7 @@ func (t *Torrent) drop() {
} }
func (t *Torrent) Close() { func (t *Torrent) Close() {
t.Status = TorrentClosed t.Status = state.TorrentClosed
t.bt.mu.Lock() t.bt.mu.Lock()
defer t.bt.mu.Unlock() defer t.bt.mu.Unlock()
@@ -362,3 +335,59 @@ func (t *Torrent) Close() {
t.drop() t.drop()
} }
func (t *Torrent) Stats() *state.TorrentStats {
t.muTorrent.Lock()
defer t.muTorrent.Unlock()
st := new(state.TorrentStats)
st.TorrentStatus = t.Status
st.TorrentStatusString = t.Status.String()
if t.TorrentSpec != nil {
st.Hash = t.TorrentSpec.InfoHash.HexString()
}
if t.Torrent != nil {
st.Name = t.Torrent.Name()
st.Hash = t.Torrent.InfoHash().HexString()
st.LoadedSize = t.Torrent.BytesCompleted()
st.TorrentSize = t.Length()
st.PreloadedBytes = t.PreloadedBytes
st.PreloadSize = t.PreloadSize
st.DownloadSpeed = t.DownloadSpeed
st.UploadSpeed = t.UploadSpeed
tst := t.Torrent.Stats()
st.BytesWritten = tst.BytesWritten.Int64()
st.BytesWrittenData = tst.BytesWrittenData.Int64()
st.BytesRead = tst.BytesRead.Int64()
st.BytesReadData = tst.BytesReadData.Int64()
st.BytesReadUsefulData = tst.BytesReadUsefulData.Int64()
st.ChunksWritten = tst.ChunksWritten.Int64()
st.ChunksRead = tst.ChunksRead.Int64()
st.ChunksReadUseful = tst.ChunksReadUseful.Int64()
st.ChunksReadWasted = tst.ChunksReadWasted.Int64()
st.PiecesDirtiedGood = tst.PiecesDirtiedGood.Int64()
st.PiecesDirtiedBad = tst.PiecesDirtiedBad.Int64()
st.TotalPeers = tst.TotalPeers
st.PendingPeers = tst.PendingPeers
st.ActivePeers = tst.ActivePeers
st.ConnectedSeeders = tst.ConnectedSeeders
st.HalfOpenPeers = tst.HalfOpenPeers
files := t.Files()
sort.Slice(files, func(i, j int) bool {
return files[i].Path() < files[j].Path()
})
for i, f := range files {
st.FileStats = append(st.FileStats, state.TorrentFileStat{
Id: i,
Path: f.Path(),
Length: f.Length(),
})
}
}
return st
}

View File

@@ -0,0 +1,89 @@
package utils
import (
"path/filepath"
"server/torr/state"
)
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 state.TorrentStats) []state.TorrentFileStat {
files := make([]state.TorrentFileStat, 0)
for _, f := range st.FileStats {
if GetMimeType(f.Path) != "*/*" {
files = append(files, f)
}
}
return files
}

109
src/server/web/api/m3u.go Normal file
View File

@@ -0,0 +1,109 @@
package api
import (
"bytes"
"fmt"
"net/http"
"net/url"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
sets "server/settings"
"server/torr/state"
"server/utils"
)
func allPlayList(c *gin.Context) {
_, fromlast := c.GetQuery("fromlast")
stats := listTorrents()
host := "http://" + c.Request.Host
list := "#EXTM3U\n"
for _, stat := range stats {
list += getM3uList(stat, host, fromlast)
}
c.Header("Content-Type", "audio/x-mpegurl")
c.Header("Connection", "close")
c.Header("Content-Disposition", `attachment; filename="all.m3u"`)
http.ServeContent(c.Writer, c.Request, "all.m3u", time.Now(), bytes.NewReader([]byte(list)))
c.Status(200)
}
func playList(c *gin.Context) {
hash := c.Query("torrhash")
_, fromlast := c.GetQuery("fromlast")
if hash == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("hash is empty"))
return
}
stats := listTorrents()
var stat *state.TorrentStats
for _, st := range stats {
if st.Hash == hash {
stat = st
break
}
}
if stat == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
// TODO проверить
host := "http://" + c.Request.Host
list := getM3uList(stat, host, fromlast)
list = "#EXTM3U\n" + list
c.Header("Content-Type", "audio/x-mpegurl")
c.Header("Connection", "close")
c.Header("Content-Disposition", `attachment; filename="playlist.m3u"`)
http.ServeContent(c.Writer, c.Request, "playlist.m3u", time.Now(), bytes.NewReader([]byte(list)))
c.Status(200)
}
func getM3uList(tor *state.TorrentStats, host string, fromLast bool) string {
m3u := ""
from := 0
if fromLast {
pos := searchLastPlayed(tor)
if pos != -1 {
from = pos
}
}
for i, f := range tor.FileStats {
if i >= from {
if utils.GetMimeType(f.Path) != "*/*" {
fn := filepath.Base(f.Path)
if fn == "" {
fn = f.Path
}
m3u += "#EXTINF:0," + fn + "\n"
// http://127.0.0.1:8090/stream/fname?link=...&index=0&play
m3u += host + "/stream/" + url.QueryEscape(f.Path) + "?link=" + tor.Hash + "&file=" + fmt.Sprint(f.Id) + "\n"
}
}
}
return m3u
}
func searchLastPlayed(tor *state.TorrentStats) int {
//TODO проверить
viewed := sets.ListViewed(tor.Hash)
for i := len(tor.FileStats); i > 0; i-- {
stat := tor.FileStats[i]
for _, v := range viewed {
if stat.Id == v.FileIndex {
return v.FileIndex
}
}
}
return -1
}

View File

@@ -25,6 +25,11 @@ func SetupRouteApi(route *gin.Engine, serv *torr.BTServer) {
route.GET("/stream", stream) route.GET("/stream", stream)
route.GET("/stream/*fname", stream) route.GET("/stream/*fname", stream)
route.POST("/viewed", viewed)
route.GET("/playlist/all.m3u", allPlayList)
route.GET("/playlist", playList)
} }
func echo(c *gin.Context) { func echo(c *gin.Context) {

View File

@@ -13,10 +13,6 @@ type setsReqJS struct {
requestI requestI
Sets *settings2.BTSets `json:"sets,omitempty"` Sets *settings2.BTSets `json:"sets,omitempty"`
} }
type setsRespJS struct {
responseI
Sets *settings2.BTSets `json:"sets,omitempty"`
}
func settings(c *gin.Context) { func settings(c *gin.Context) {
var req setsReqJS var req setsReqJS
@@ -27,9 +23,7 @@ func settings(c *gin.Context) {
} }
if req.Action == "get" { if req.Action == "get" {
resp := setsRespJS{} c.JSON(200, settings2.BTsets)
resp.Sets = settings2.BTsets
c.JSON(200, resp)
return return
} }
if req.Action == "set" { if req.Action == "set" {

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -10,6 +11,13 @@ import (
"server/web/api/utils" "server/web/api/utils"
) )
// http://127.0.0.1:8090/stream/fname?link=...&index=0&stat
// http://127.0.0.1:8090/stream/fname?link=...&index=0&m3u
// http://127.0.0.1:8090/stream/fname?link=...&index=0&play
// http://127.0.0.1:8090/stream/fname?link=...&save&title=...&poster=...
// http://127.0.0.1:8090/stream/fname?link=...&index=0&play&save
// http://127.0.0.1:8090/stream/fname?link=...&index=0&play&save&title=...&poster=...
func stream(c *gin.Context) { func stream(c *gin.Context) {
link := c.Query("link") link := c.Query("link")
indexStr := c.Query("index") indexStr := c.Query("index")
@@ -21,13 +29,19 @@ func stream(c *gin.Context) {
title := c.Query("title") title := c.Query("title")
poster := c.Query("poster") poster := c.Query("poster")
// TODO unescape args
if link == "" { if link == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("link should not be empty")) c.AbortWithError(http.StatusBadRequest, errors.New("link should not be empty"))
return return
} }
if title == "" {
title = c.Param("fname")
}
link, _ = url.QueryUnescape(link)
title, _ = url.QueryUnescape(title)
poster, _ = url.QueryUnescape(poster)
spec, err := utils.ParseLink(link) spec, err := utils.ParseLink(link)
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
@@ -65,6 +79,7 @@ func stream(c *gin.Context) {
// save to db // save to db
if save { if save {
utils.AddTorrent(tor) utils.AddTorrent(tor)
c.Status(200)
} }
// wait torrent info // wait torrent info
if !tor.WaitInfo() { if !tor.WaitInfo() {
@@ -94,13 +109,13 @@ func stream(c *gin.Context) {
if stat || (!m3u && !play) { if stat || (!m3u && !play) {
c.JSON(200, tor.Stats()) c.JSON(200, tor.Stats())
return return
} } else
// return m3u if query // return m3u if query
if m3u { if m3u {
//TODO m3u //TODO m3u
c.JSON(200, tor.Stats()) c.JSON(200, tor.Stats())
return return
} } else
// return play if query // return play if query
if play { if play {
tor.Stream(index, c.Request, c.Writer) tor.Stream(index, c.Request, c.Writer)

View File

@@ -5,11 +5,14 @@ import (
"github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/metainfo"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors"
"server/log"
"server/torr" "server/torr"
"server/torr/state"
"server/web/api/utils" "server/web/api/utils"
) )
//Action: add, get, rem, list //Action: add, get, rem, list, drop
type torrReqJS struct { type torrReqJS struct {
requestI requestI
Link string `json:"link,omitempty"` Link string `json:"link,omitempty"`
@@ -26,43 +29,62 @@ func torrents(c *gin.Context) {
c.AbortWithError(http.StatusBadRequest, err) c.AbortWithError(http.StatusBadRequest, err)
return return
} }
c.Status(http.StatusBadRequest)
switch req.Action { switch req.Action {
case "add": case "add":
{ {
add(req, c) addTorrent(req, c)
} }
case "get": case "get":
{ {
get(req, c) getTorrent(req, c)
} }
case "rem": case "rem":
{ {
rem(req, c) remTorrent(req, c)
} }
case "list": case "list":
{ {
list(req, c) listTorrent(req, c)
}
case "drop":
{
dropTorrent(req, c)
} }
} }
} }
func add(req torrReqJS, c *gin.Context) { func addTorrent(req torrReqJS, c *gin.Context) {
log.TLogln("add torrent", req.Link)
torrSpec, err := utils.ParseLink(req.Link) torrSpec, err := utils.ParseLink(req.Link)
if err != nil { if err != nil {
log.TLogln("error add torrent:", err)
c.AbortWithError(http.StatusBadRequest, err) c.AbortWithError(http.StatusBadRequest, err)
return return
} }
torr, err := torr.NewTorrent(torrSpec, bts) torr, err := torr.NewTorrent(torrSpec, bts)
if err != nil { if err != nil {
log.TLogln("error add torrent:", err)
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
return return
} }
if !torr.WaitInfo() {
log.TLogln("error add torrent:", "timeout connection torrent")
c.AbortWithError(http.StatusNotFound, errors.New("timeout connection torrent"))
return
}
torr.Title = req.Title torr.Title = req.Title
torr.Poster = req.Poster torr.Poster = req.Poster
if torr.Title == "" {
torr.Title = torr.Name()
}
if req.SaveToDB { if req.SaveToDB {
log.TLogln("save to db:", torr.Torrent.InfoHash().HexString())
utils.AddTorrent(torr) utils.AddTorrent(torr)
} }
@@ -70,7 +92,7 @@ func add(req torrReqJS, c *gin.Context) {
c.JSON(200, st) c.JSON(200, st)
} }
func get(req torrReqJS, c *gin.Context) { func getTorrent(req torrReqJS, c *gin.Context) {
hash := metainfo.NewHashFromHex(req.Hash) hash := metainfo.NewHashFromHex(req.Hash)
tor := bts.GetTorrent(hash) tor := bts.GetTorrent(hash)
if tor == nil { if tor == nil {
@@ -85,17 +107,22 @@ func get(req torrReqJS, c *gin.Context) {
} }
} }
func rem(req torrReqJS, c *gin.Context) { func remTorrent(req torrReqJS, c *gin.Context) {
hash := metainfo.NewHashFromHex(req.Hash) hash := metainfo.NewHashFromHex(req.Hash)
bts.RemoveTorrent(hash) bts.RemoveTorrent(hash)
utils.RemTorrent(hash) utils.RemTorrent(hash)
c.Status(200) c.Status(200)
} }
func list(req torrReqJS, c *gin.Context) { func listTorrent(req torrReqJS, c *gin.Context) {
stats := listTorrents()
c.JSON(200, stats)
}
func listTorrents() []*state.TorrentStats {
btlist := bts.ListTorrents() btlist := bts.ListTorrents()
dblist := utils.ListTorrents() dblist := utils.ListTorrents()
var stats []torr.TorrentStats var stats []*state.TorrentStats
for _, tr := range btlist { for _, tr := range btlist {
stats = append(stats, tr.Stats()) stats = append(stats, tr.Stats())
} }
@@ -109,6 +136,16 @@ mainloop:
} }
stats = append(stats, db.Stats()) stats = append(stats, db.Stats())
} }
return stats
c.JSON(200, stats) }
func dropTorrent(req torrReqJS, c *gin.Context) {
if req.Hash == "" {
c.AbortWithError(http.StatusBadRequest, errors.New("hash is empty"))
return
}
hash := metainfo.NewHashFromHex(req.Hash)
bts.RemoveTorrent(hash)
c.Status(200)
} }

View File

@@ -0,0 +1,53 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
sets "server/settings"
)
// Action: set, rem, list
type viewedReqJS struct {
requestI
*sets.Viewed
}
func viewed(c *gin.Context) {
var req viewedReqJS
err := c.ShouldBindJSON(&req)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
switch req.Action {
case "set":
{
setViewed(req, c)
}
case "rem":
{
remViewed(req, c)
}
case "list":
{
listViewed(req, c)
}
}
}
func setViewed(req viewedReqJS, c *gin.Context) {
sets.SetViewed(req.Viewed)
c.Status(200)
}
func remViewed(req viewedReqJS, c *gin.Context) {
sets.RemViewed(req.Viewed)
c.Status(200)
}
func listViewed(req viewedReqJS, c *gin.Context) {
list := sets.ListViewed(req.Hash)
c.JSON(200, list)
}