mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +05:00
init 1.2.x
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,3 +23,5 @@ pkg/
|
|||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
toolchains/
|
toolchains/
|
||||||
|
/src/crawshaw.io/
|
||||||
|
/src/go.etcd.io/
|
||||||
|
|||||||
17
build-all.sh
17
build-all.sh
@@ -23,7 +23,22 @@ PLATFORMS_ARM="linux"
|
|||||||
type setopt >/dev/null 2>&1
|
type setopt >/dev/null 2>&1
|
||||||
|
|
||||||
export GOPATH="${PWD}"
|
export GOPATH="${PWD}"
|
||||||
GOBIN="/usr/local/go_111/bin/go"
|
GOBIN="/usr/local/go/bin/go"
|
||||||
|
|
||||||
|
[ -d src/github.com/alexflint/go-arg ] && go get -v github.com/alexflint/go-arg
|
||||||
|
[ -d src/github.com/anacrolix/dht ] && go get -v github.com/anacrolix/dht
|
||||||
|
[ -d src/github.com/anacrolix/missinggo/httptoo ] && go get -v github.com/anacrolix/missinggo/httptoo
|
||||||
|
[ -d src/github.com/anacrolix/torrent ] && go get -v github.com/anacrolix/torrent
|
||||||
|
[ -d src/github.com/anacrolix/torrent/iplist ] && go get -v github.com/anacrolix/torrent/iplist
|
||||||
|
[ -d src/github.com/anacrolix/torrent/metainfo ] && go get -v github.com/anacrolix/torrent/metainfo
|
||||||
|
[ -d src/github.com/anacrolix/utp ] && go get -v github.com/anacrolix/utp
|
||||||
|
[ -d src/github.com/labstack/echo ] && go get -v github.com/labstack/echo
|
||||||
|
[ -d src/github.com/labstack/echo/middleware ] && go get -v github.com/labstack/echo/middleware
|
||||||
|
[ -d src/github.com/labstack/gommon/bytes ] && go get -v github.com/labstack/gommon/bytes
|
||||||
|
[ -d src/github.com/pion/webrtc/v2 ] && go get -v github.com/pion/webrtc/v2
|
||||||
|
[ -d src/go.etcd.io/bbolt ] && go get -v go.etcd.io/bbolt
|
||||||
|
|
||||||
|
#ln -s . src/github.com/pion/webrtc/v2
|
||||||
|
|
||||||
$GOBIN version
|
$GOBIN version
|
||||||
|
|
||||||
|
|||||||
143
src/main/test.go
143
src/main/test.go
@@ -1,143 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"server/utils"
|
|
||||||
"server/version"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"Name": "TorrServer",
|
|
||||||
"Version": "1.1.71",
|
|
||||||
"BuildDate": "17.05.2019",
|
|
||||||
"Links": {
|
|
||||||
"android-386": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-android-386",
|
|
||||||
"android-amd64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-android-amd64",
|
|
||||||
"android-arm64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-android-arm64",
|
|
||||||
"android-arm7": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-android-arm7",
|
|
||||||
"darwin-amd64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-darwin-amd64",
|
|
||||||
"linux-386": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-386",
|
|
||||||
"linux-amd64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-amd64",
|
|
||||||
"linux-arm5": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-arm5",
|
|
||||||
"linux-arm6": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-arm6",
|
|
||||||
"linux-arm64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-arm64",
|
|
||||||
"linux-arm7": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-arm7",
|
|
||||||
"linux-mips": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-mips",
|
|
||||||
"linux-mips64": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-mips64",
|
|
||||||
"linux-mips64le": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-mips64le",
|
|
||||||
"linux-mipsle": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-linux-mipsle",
|
|
||||||
"windows-386.exe": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-windows-386.exe",
|
|
||||||
"windows-amd64.exe": "https://github.com/YouROK/TorrServer/releases/download/1.1.71/TorrServer-windows-amd64.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
type release struct {
|
|
||||||
Version string
|
|
||||||
Links map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkReleasesJS() {
|
|
||||||
var releases []release
|
|
||||||
for i := 65; i <= version.VerInt; i++ {
|
|
||||||
links := map[string]string{
|
|
||||||
"android-386": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-android-386", i),
|
|
||||||
"android-amd64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-android-amd64", i),
|
|
||||||
"android-arm64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-android-arm64", i),
|
|
||||||
"android-arm7": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-android-arm7", i),
|
|
||||||
"darwin-amd64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-darwin-amd64", i),
|
|
||||||
"linux-386": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-386", i),
|
|
||||||
"linux-amd64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-amd64", i),
|
|
||||||
"linux-arm5": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-arm5", i),
|
|
||||||
"linux-arm6": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-arm6", i),
|
|
||||||
"linux-arm64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-arm64", i),
|
|
||||||
"linux-arm7": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-arm7", i),
|
|
||||||
"linux-mips": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-mips", i),
|
|
||||||
"linux-mips64": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-mips64", i),
|
|
||||||
"linux-mips64le": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-mips64le", i),
|
|
||||||
"linux-mipsle": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-linux-mipsle", i),
|
|
||||||
"windows-386.exe": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-windows-386.exe", i),
|
|
||||||
"windows-amd64.exe": fmt.Sprintf("https://github.com/YouROK/TorrServer/releases/download/1.1.%d/TorrServer-windows-amd64.exe", i),
|
|
||||||
}
|
|
||||||
rel := release{
|
|
||||||
Version: fmt.Sprintf("1.1.%d", i),
|
|
||||||
Links: links,
|
|
||||||
}
|
|
||||||
releases = append(releases, rel)
|
|
||||||
}
|
|
||||||
buf, _ := json.MarshalIndent(releases, "", " ")
|
|
||||||
if len(buf) > 0 {
|
|
||||||
ioutil.WriteFile("/home/yourok/surge/torrserve/releases.json", buf, 0666)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func test() {
|
|
||||||
config := torrent.NewDefaultClientConfig()
|
|
||||||
|
|
||||||
config.EstablishedConnsPerTorrent = 20
|
|
||||||
config.HalfOpenConnsPerTorrent = 65
|
|
||||||
config.DisableIPv6 = true
|
|
||||||
config.NoDHT = true
|
|
||||||
|
|
||||||
client, err := torrent.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Ubuntu
|
|
||||||
t, err := client.AddMagnet("magnet:?xt=urn:btih:e4be9e4db876e3e3179778b03e906297be5c8dbe&dn=ubuntu-18.04-desktop-amd64.iso&tr=http%3a%2f%2ftorrent.ubuntu.com%3a6969%2fannounce&tr=http%3a%2f%2fipv6.torrent.ubuntu.com%3a6969%2fannounce")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
<-t.GotInfo()
|
|
||||||
file := t.Files()[0]
|
|
||||||
|
|
||||||
reader := file.NewReader()
|
|
||||||
var wa sync.WaitGroup
|
|
||||||
wa.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
buf := make([]byte, t.Info().PieceLength)
|
|
||||||
for {
|
|
||||||
_, err := reader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wa.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
cl := t.Closed()
|
|
||||||
lastTimeSpeed := time.Now()
|
|
||||||
DownloadSpeed := 0.0
|
|
||||||
BytesReadUsefulData := int64(0)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-cl:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
client.WriteStatus(os.Stdout)
|
|
||||||
st := t.Stats()
|
|
||||||
deltaDlBytes := st.BytesReadUsefulData.Int64() - BytesReadUsefulData
|
|
||||||
deltaTime := time.Since(lastTimeSpeed).Seconds()
|
|
||||||
DownloadSpeed = float64(deltaDlBytes) / deltaTime
|
|
||||||
BytesReadUsefulData = st.BytesReadUsefulData.Int64()
|
|
||||||
lastTimeSpeed = time.Now()
|
|
||||||
fmt.Println("DL speed:", utils.Format(DownloadSpeed))
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wa.Wait()
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Start(settingsPath, port string) {
|
|
||||||
settings.Path = settingsPath
|
|
||||||
err := settings.ReadSettings()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error read settings:", err)
|
|
||||||
}
|
|
||||||
if port == "" {
|
|
||||||
port = "8090"
|
|
||||||
}
|
|
||||||
server.Start(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitServer() string {
|
|
||||||
err := server.Wait()
|
|
||||||
if err != nil {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stop() {
|
|
||||||
go server.Stop()
|
|
||||||
settings.CloseDB()
|
|
||||||
}
|
|
||||||
9
src/server/log/log.go
Normal file
9
src/server/log/log.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TLogln(v ...interface{}) {
|
||||||
|
log.Println(v...)
|
||||||
|
}
|
||||||
26
src/server/server.go
Normal file
26
src/server/server.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"server/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(settingsPath, port string, roSets bool) {
|
||||||
|
settings.InitSets(settingsPath, roSets)
|
||||||
|
if port == "" {
|
||||||
|
port = "8090"
|
||||||
|
}
|
||||||
|
//server.Start(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WaitServer() string {
|
||||||
|
//err := server.Wait()
|
||||||
|
// if err != nil {
|
||||||
|
// return err.Error()
|
||||||
|
// }
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() {
|
||||||
|
// go server.Stop()
|
||||||
|
settings.CloseDB()
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
db *bolt.DB
|
|
||||||
dbViewedName = []byte("Viewed")
|
|
||||||
dbInfosName = []byte("Infos")
|
|
||||||
dbTorrentsName = []byte("Torrents")
|
|
||||||
dbSettingsName = []byte("Settings")
|
|
||||||
Path string
|
|
||||||
)
|
|
||||||
|
|
||||||
func openDB() error {
|
|
||||||
if db != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var ro = Get().ReadOnlyMode
|
|
||||||
db, err = bolt.Open(filepath.Join(Path, "torrserver.db"), 0666, &bolt.Options{ReadOnly: ro})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.Update(func(tx *bolt.Tx) error {
|
|
||||||
_, err = tx.CreateBucketIfNotExists(dbSettingsName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create Settings bucket: %v", err)
|
|
||||||
}
|
|
||||||
_, err = tx.CreateBucketIfNotExists(dbTorrentsName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create Torrents bucket: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
CloseDB()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CloseDB() {
|
|
||||||
if db != nil {
|
|
||||||
db.Close()
|
|
||||||
db = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddInfo(hash, info string) error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
infoe := GetInfo(hash)
|
|
||||||
if infoe != "{}" {
|
|
||||||
return nil // already filled
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = strings.ToUpper(hash)
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
dbt, err := tx.CreateBucketIfNotExists([]byte(dbInfosName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dbi, err := dbt.CreateBucketIfNotExists([]byte(hash))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbi.Put([]byte("Info"), []byte(info))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent info %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInfo(hash string) string {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = strings.ToUpper(hash)
|
|
||||||
ret := "{}"
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
hdb := tx.Bucket(dbInfosName)
|
|
||||||
if hdb == nil {
|
|
||||||
return fmt.Errorf("could not find torrent info")
|
|
||||||
}
|
|
||||||
hdb = hdb.Bucket([]byte(hash))
|
|
||||||
if hdb != nil {
|
|
||||||
info := hdb.Get([]byte("Info"))
|
|
||||||
if info == nil {
|
|
||||||
return fmt.Errorf("error get torrent info")
|
|
||||||
}
|
|
||||||
ret = string(info)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sets *Settings
|
|
||||||
StartTime time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sets = new(Settings)
|
|
||||||
sets.CacheSize = 200 * 1024 * 1024
|
|
||||||
sets.EnableDebug = false
|
|
||||||
sets.PreloadBufferSize = 20 * 1024 * 1024
|
|
||||||
sets.ConnectionsLimit = 20
|
|
||||||
sets.DhtConnectionLimit = 500
|
|
||||||
sets.RetrackersMode = 1
|
|
||||||
sets.TorrentDisconnectTimeout = 30
|
|
||||||
StartTime = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Settings struct {
|
|
||||||
CacheSize int64 // in byte, def 200 mb
|
|
||||||
PreloadBufferSize int64 // in byte, buffer for preload
|
|
||||||
|
|
||||||
RetrackersMode int //0 - don`t add, 1 - add retrackers, 2 - remove retrackers
|
|
||||||
|
|
||||||
//BT Config
|
|
||||||
EnableIPv6 bool
|
|
||||||
EnableDebug bool
|
|
||||||
DisableTCP bool
|
|
||||||
DisableUTP bool
|
|
||||||
DisableUPNP bool
|
|
||||||
DisableDHT bool
|
|
||||||
DisableUpload bool
|
|
||||||
ReadOnlyMode bool
|
|
||||||
Encryption int // 0 - Enable, 1 - disable, 2 - force
|
|
||||||
DownloadRateLimit int // in kb, 0 - inf
|
|
||||||
UploadRateLimit int // in kb, 0 - inf
|
|
||||||
ConnectionsLimit int
|
|
||||||
DhtConnectionLimit int // 0 - inf
|
|
||||||
PeersListenPort int
|
|
||||||
|
|
||||||
TorrentDisconnectTimeout int // in seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
func Get() *Settings {
|
|
||||||
return sets
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Settings) String() string {
|
|
||||||
buf, _ := json.MarshalIndent(sets, "", " ")
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadSettings() error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf := make([]byte, 0)
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
sdb := tx.Bucket(dbSettingsName)
|
|
||||||
if sdb == nil {
|
|
||||||
return fmt.Errorf("error load settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = sdb.Get([]byte("json"))
|
|
||||||
if buf == nil {
|
|
||||||
return fmt.Errorf("error load settings")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
err = json.Unmarshal(buf, sets)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if sets.ConnectionsLimit <= 0 {
|
|
||||||
sets.ConnectionsLimit = 20
|
|
||||||
}
|
|
||||||
if sets.DhtConnectionLimit < 0 {
|
|
||||||
sets.DhtConnectionLimit = 1000
|
|
||||||
}
|
|
||||||
if sets.CacheSize < 0 {
|
|
||||||
sets.CacheSize = 200 * 1024 * 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
if sets.TorrentDisconnectTimeout < 1 {
|
|
||||||
sets.TorrentDisconnectTimeout = 1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveSettings() error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := json.Marshal(sets)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
setsDB, err := tx.CreateBucketIfNotExists(dbSettingsName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return setsDB.Put([]byte("json"), []byte(buf))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetRDB() {
|
|
||||||
SaveSettings()
|
|
||||||
fmt.Println("Enable Read-only DB mode")
|
|
||||||
CloseDB()
|
|
||||||
sets.ReadOnlyMode = true
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Torrent struct {
|
|
||||||
Name string
|
|
||||||
Magnet string
|
|
||||||
InfoBytes []byte
|
|
||||||
Hash string
|
|
||||||
Size int64
|
|
||||||
Timestamp int64
|
|
||||||
|
|
||||||
Files []File
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Name string
|
|
||||||
Id int
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveTorrentDB(torrent *Torrent) error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
dbt, err := tx.CreateBucketIfNotExists(dbTorrentsName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create Torrents bucket: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("Save torrent:", torrent.Name)
|
|
||||||
hdb, err := dbt.CreateBucketIfNotExists([]byte(torrent.Hash))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not create Torrent bucket: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hdb.Put([]byte("Name"), []byte(torrent.Name))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent: %v", err)
|
|
||||||
}
|
|
||||||
err = hdb.Put([]byte("Link"), []byte(torrent.Magnet))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent: %v", err)
|
|
||||||
}
|
|
||||||
err = hdb.Put([]byte("InfoBytes"), torrent.InfoBytes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent: %v", err)
|
|
||||||
}
|
|
||||||
err = hdb.Put([]byte("Size"), i2b(torrent.Size))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent: %v", err)
|
|
||||||
}
|
|
||||||
err = hdb.Put([]byte("Timestamp"), i2b(torrent.Timestamp))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fdb, err := hdb.CreateBucketIfNotExists([]byte("Files"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent files: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range torrent.Files {
|
|
||||||
ffdb, err := fdb.CreateBucketIfNotExists([]byte(f.Name))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent files: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ffdb.Put([]byte("Id"), i2b(int64(f.Id)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent files: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ffdb.Put([]byte("Size"), i2b(f.Size))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save torrent files: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveTorrentDB(hash string) error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
dbt := tx.Bucket(dbTorrentsName)
|
|
||||||
if dbt == nil {
|
|
||||||
return fmt.Errorf("could not find torrent")
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbt.DeleteBucket([]byte(hash))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadTorrentDB(hash string) (*Torrent, error) {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var torr *Torrent
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
hdb := tx.Bucket(dbTorrentsName)
|
|
||||||
if hdb == nil {
|
|
||||||
return fmt.Errorf("could not find torrent")
|
|
||||||
}
|
|
||||||
hdb = hdb.Bucket([]byte(hash))
|
|
||||||
if hdb != nil {
|
|
||||||
torr = new(Torrent)
|
|
||||||
torr.Hash = string(hash)
|
|
||||||
tmp := hdb.Get([]byte("Name"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Name = string(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Link"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Magnet = string(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("InfoBytes"))
|
|
||||||
if len(tmp) > 0 {
|
|
||||||
torr.InfoBytes = tmp
|
|
||||||
} else {
|
|
||||||
torr.InfoBytes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Size"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Size = b2i(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Timestamp"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Timestamp = b2i(tmp)
|
|
||||||
|
|
||||||
fdb := hdb.Bucket([]byte("Files"))
|
|
||||||
if fdb == nil {
|
|
||||||
return fmt.Errorf("error load torrent files")
|
|
||||||
}
|
|
||||||
cf := fdb.Cursor()
|
|
||||||
for fn, _ := cf.First(); fn != nil; fn, _ = cf.Next() {
|
|
||||||
file := File{Name: string(fn)}
|
|
||||||
ffdb := fdb.Bucket(fn)
|
|
||||||
if ffdb == nil {
|
|
||||||
return fmt.Errorf("error load torrent files")
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := ffdb.Get([]byte("Id"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent file")
|
|
||||||
}
|
|
||||||
file.Id = int(b2i(tmp))
|
|
||||||
|
|
||||||
tmp = ffdb.Get([]byte("Size"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent file")
|
|
||||||
}
|
|
||||||
file.Size = b2i(tmp)
|
|
||||||
|
|
||||||
torr.Files = append(torr.Files, file)
|
|
||||||
}
|
|
||||||
SortFiles(torr.Files)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return torr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadTorrentsDB() ([]*Torrent, error) {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
torrs := make([]*Torrent, 0)
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
tdb := tx.Bucket(dbTorrentsName)
|
|
||||||
c := tdb.Cursor()
|
|
||||||
for h, _ := c.First(); h != nil; h, _ = c.Next() {
|
|
||||||
hdb := tdb.Bucket(h)
|
|
||||||
if hdb != nil {
|
|
||||||
torr := new(Torrent)
|
|
||||||
torr.Hash = string(h)
|
|
||||||
tmp := hdb.Get([]byte("Name"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Name = string(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Link"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Magnet = string(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Size"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Size = b2i(tmp)
|
|
||||||
|
|
||||||
tmp = hdb.Get([]byte("Timestamp"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent")
|
|
||||||
}
|
|
||||||
torr.Timestamp = b2i(tmp)
|
|
||||||
|
|
||||||
fdb := hdb.Bucket([]byte("Files"))
|
|
||||||
if fdb == nil {
|
|
||||||
return fmt.Errorf("error load torrent files")
|
|
||||||
}
|
|
||||||
cf := fdb.Cursor()
|
|
||||||
for fn, _ := cf.First(); fn != nil; fn, _ = cf.Next() {
|
|
||||||
file := File{Name: string(fn)}
|
|
||||||
ffdb := fdb.Bucket(fn)
|
|
||||||
if ffdb == nil {
|
|
||||||
return fmt.Errorf("error load torrent files")
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := ffdb.Get([]byte("Id"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent file")
|
|
||||||
}
|
|
||||||
file.Id = int(b2i(tmp))
|
|
||||||
|
|
||||||
tmp = ffdb.Get([]byte("Size"))
|
|
||||||
if tmp == nil {
|
|
||||||
return fmt.Errorf("error load torrent file")
|
|
||||||
}
|
|
||||||
file.Size = b2i(tmp)
|
|
||||||
|
|
||||||
torr.Files = append(torr.Files, file)
|
|
||||||
}
|
|
||||||
SortFiles(torr.Files)
|
|
||||||
torrs = append(torrs, torr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return torrs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func i2b(v int64) []byte {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(b, uint64(v))
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func b2i(v []byte) int64 {
|
|
||||||
return int64(binary.BigEndian.Uint64(v))
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
uFiles = map[string]interface{}{
|
|
||||||
".3g2": nil,
|
|
||||||
".3gp": nil,
|
|
||||||
".aaf": nil,
|
|
||||||
".asf": nil,
|
|
||||||
".avchd": nil,
|
|
||||||
".avi": nil,
|
|
||||||
".drc": nil,
|
|
||||||
".flv": 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,
|
|
||||||
".ts": nil,
|
|
||||||
".m2ts": nil,
|
|
||||||
".mts": nil,
|
|
||||||
".qt": nil,
|
|
||||||
".rm": nil,
|
|
||||||
".rmvb": nil,
|
|
||||||
".roq": nil,
|
|
||||||
".svi": nil,
|
|
||||||
".vob": nil,
|
|
||||||
".webm": nil,
|
|
||||||
".wmv": nil,
|
|
||||||
".yuv": nil,
|
|
||||||
|
|
||||||
".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 SortFiles(files []File) {
|
|
||||||
sort.Slice(files, func(i, j int) bool {
|
|
||||||
if haveUsable(files[i].Name) && !haveUsable(files[j].Name) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !haveUsable(files[i].Name) && haveUsable(files[j].Name) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return files[i].Name < files[j].Name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func haveUsable(name string) bool {
|
|
||||||
ext := strings.ToLower(filepath.Ext(name))
|
|
||||||
_, ok := uFiles[ext]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetViewed(hash, filename string) error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
dbt, err := tx.CreateBucketIfNotExists(dbViewedName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save viewed %v", err)
|
|
||||||
}
|
|
||||||
hdb, err := dbt.CreateBucketIfNotExists([]byte(hash))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save viewed %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hdb.Put([]byte(filename), []byte{1})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error save viewed %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemTorrentViewed(hash string) error {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
|
||||||
dbt := tx.Bucket(dbViewedName)
|
|
||||||
if dbt == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = dbt.DeleteBucket([]byte(hash))
|
|
||||||
if err == nil || err == bolt.ErrBucketNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetViewed(hash, filename string) bool {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
viewed := false
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
hdb := tx.Bucket(dbViewedName)
|
|
||||||
if hdb == nil {
|
|
||||||
return fmt.Errorf("error get viewed")
|
|
||||||
}
|
|
||||||
hdb = hdb.Bucket([]byte(hash))
|
|
||||||
if hdb != nil {
|
|
||||||
vw := hdb.Get([]byte(filename))
|
|
||||||
viewed = vw != nil && vw[0] == 1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return viewed
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetViewedList() map[string][]string {
|
|
||||||
err := openDB()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = make(map[string][]string)
|
|
||||||
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
rdb := tx.Bucket(dbViewedName)
|
|
||||||
if rdb == nil {
|
|
||||||
return fmt.Errorf("could not find torrent")
|
|
||||||
}
|
|
||||||
|
|
||||||
rdb.ForEach(func(hash, _ []byte) error {
|
|
||||||
hdb := rdb.Bucket(hash)
|
|
||||||
fdb := hdb.Bucket([]byte("Files"))
|
|
||||||
fdb.ForEach(func(fileName, _ []byte) error {
|
|
||||||
vw := fdb.Get(fileName)
|
|
||||||
if vw != nil && vw[0] == 1 {
|
|
||||||
list[string(hash)] = append(list[string(hash)], string(fileName))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
179
src/server/settings/db.go
Normal file
179
src/server/settings/db.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
"server/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TDB struct {
|
||||||
|
Path string
|
||||||
|
ReadOnly bool
|
||||||
|
db *bolt.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTDB(path string, readOnly bool) *TDB {
|
||||||
|
db, err := bolt.Open(filepath.Join(path, "config.db"), 0666, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tdb := new(TDB)
|
||||||
|
tdb.db = db
|
||||||
|
tdb.Path = path
|
||||||
|
tdb.ReadOnly = readOnly
|
||||||
|
return tdb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TDB) CloseDB() {
|
||||||
|
if v.db != nil {
|
||||||
|
v.db.Close()
|
||||||
|
v.db = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TDB) Get(xpath, name string) []byte {
|
||||||
|
spath := strings.Split(xpath, "/")
|
||||||
|
if len(spath) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret []byte
|
||||||
|
err := v.db.View(func(tx *bolt.Tx) error {
|
||||||
|
buckt := tx.Bucket([]byte(spath[0]))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range spath {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buckt = buckt.Bucket([]byte(p))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = buckt.Get([]byte(name))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln("Error get sets", xpath+"/"+name, ", error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TDB) Set(xpath, name string, value []byte) {
|
||||||
|
if v.ReadOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spath := strings.Split(xpath, "/")
|
||||||
|
if len(spath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := v.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
buckt, err := tx.CreateBucketIfNotExists([]byte(spath[0]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range spath {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buckt, err = buckt.CreateBucketIfNotExists([]byte(p))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buckt.Put([]byte(name), value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln("Error put sets", xpath+"/"+name, ", error:", err)
|
||||||
|
log.TLogln("value:", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TDB) List(xpath string) []string {
|
||||||
|
spath := strings.Split(xpath, "/")
|
||||||
|
if len(spath) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret []string
|
||||||
|
err := v.db.View(func(tx *bolt.Tx) error {
|
||||||
|
buckt := tx.Bucket([]byte(spath[0]))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range spath {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buckt = buckt.Bucket([]byte(p))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buckt.ForEach(func(_, v []byte) error {
|
||||||
|
if len(v) > 0 {
|
||||||
|
ret = append(ret, string(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln("Error list sets", xpath, ", error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TDB) Rem(xpath, name string) {
|
||||||
|
if v.ReadOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spath := strings.Split(xpath, "/")
|
||||||
|
if len(spath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := v.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
buckt := tx.Bucket([]byte(spath[0]))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range spath {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buckt = buckt.Bucket([]byte(p))
|
||||||
|
if buckt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buckt.Delete([]byte(name))
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln("Error rem sets", xpath+"/"+name, ", error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
16
src/server/settings/settings.go
Normal file
16
src/server/settings/settings.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
var (
|
||||||
|
tdb *TDB
|
||||||
|
Path string
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitSets(path string, readOnly bool) {
|
||||||
|
Path = path
|
||||||
|
tdb = NewTDB(path, readOnly)
|
||||||
|
loadBTSets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseDB() {
|
||||||
|
tdb.CloseDB()
|
||||||
|
}
|
||||||
77
src/server/settings/torrbt.go
Normal file
77
src/server/settings/torrbt.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"server/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BTSets struct {
|
||||||
|
CacheSize int64 // in byte, def 200 mb
|
||||||
|
PreloadBufferSize int64 // in byte, buffer for preload
|
||||||
|
|
||||||
|
SaveOnDisk bool // save on disk?
|
||||||
|
ContentPath string // path to save content
|
||||||
|
|
||||||
|
RetrackersMode int // 0 - don`t add, 1 - add retrackers (def), 2 - remove retrackers 3 - replace retrackers
|
||||||
|
TorrentDisconnectTimeout int // in seconds
|
||||||
|
EnableDebug bool // print logs
|
||||||
|
|
||||||
|
// BT Config
|
||||||
|
EnableIPv6 bool
|
||||||
|
DisableTCP bool
|
||||||
|
DisableUTP bool
|
||||||
|
DisableUPNP bool
|
||||||
|
DisableDHT bool
|
||||||
|
DisableUpload bool
|
||||||
|
Encryption int // ???? 0 - Enable, 1 - disable, 2 - force
|
||||||
|
DownloadRateLimit int // in kb, 0 - inf
|
||||||
|
UploadRateLimit int // in kb, 0 - inf
|
||||||
|
ConnectionsLimit int
|
||||||
|
DhtConnectionLimit int // 0 - inf
|
||||||
|
PeersListenPort int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BTSets) String() string {
|
||||||
|
buf, _ := json.Marshal(v)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
BTsets = loadBTSets()
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetBTSets(sets *BTSets) {
|
||||||
|
if tdb.ReadOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
BTsets = sets
|
||||||
|
buf, err := json.Marshal(BTsets)
|
||||||
|
if err != nil {
|
||||||
|
log.TLogln("Error marshal btsets", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tdb.Set("Settings", "BitTorr", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBTSets() *BTSets {
|
||||||
|
buf := tdb.Get("Settings", "BitTorr")
|
||||||
|
if len(buf) > 0 {
|
||||||
|
err := json.Unmarshal(buf, &BTsets)
|
||||||
|
if err == nil {
|
||||||
|
return BTsets
|
||||||
|
}
|
||||||
|
log.TLogln("Error unmarshal btsets", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sets := new(BTSets)
|
||||||
|
sets.EnableDebug = false
|
||||||
|
|
||||||
|
sets.CacheSize = 200 * 1024 * 1024 // 200mb
|
||||||
|
sets.PreloadBufferSize = 20 * 1024 * 1024
|
||||||
|
sets.ConnectionsLimit = 20
|
||||||
|
sets.DhtConnectionLimit = 500
|
||||||
|
sets.RetrackersMode = 1
|
||||||
|
sets.TorrentDisconnectTimeout = 30
|
||||||
|
return sets
|
||||||
|
}
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
package torr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/torr/storage/memcache"
|
|
||||||
"server/torr/storage/state"
|
|
||||||
"server/utils"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent"
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BTServer struct {
|
|
||||||
config *torrent.ClientConfig
|
|
||||||
client *torrent.Client
|
|
||||||
|
|
||||||
storage *memcache.Storage
|
|
||||||
|
|
||||||
torrents map[metainfo.Hash]*Torrent
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
wmu sync.Mutex
|
|
||||||
|
|
||||||
watching bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBTS() *BTServer {
|
|
||||||
bts := new(BTServer)
|
|
||||||
bts.torrents = make(map[metainfo.Hash]*Torrent)
|
|
||||||
return bts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) Connect() error {
|
|
||||||
bt.mu.Lock()
|
|
||||||
defer bt.mu.Unlock()
|
|
||||||
var err error
|
|
||||||
bt.configure()
|
|
||||||
bt.client, err = torrent.NewClient(bt.config)
|
|
||||||
bt.torrents = make(map[metainfo.Hash]*Torrent)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) Disconnect() {
|
|
||||||
bt.mu.Lock()
|
|
||||||
defer bt.mu.Unlock()
|
|
||||||
if bt.client != nil {
|
|
||||||
bt.client.Close()
|
|
||||||
bt.client = nil
|
|
||||||
utils.FreeOSMemGC()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) Reconnect() error {
|
|
||||||
bt.Disconnect()
|
|
||||||
return bt.Connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) configure() {
|
|
||||||
bt.storage = memcache.NewStorage(settings.Get().CacheSize)
|
|
||||||
|
|
||||||
//blocklist, _ := iplist.MMapPackedFile(filepath.Join(settings.Path, "blocklist"))
|
|
||||||
blocklist, _ := utils.ReadBlockedIP()
|
|
||||||
|
|
||||||
userAgent := "uTorrent/3.5.5"
|
|
||||||
peerID := "-UT3550-"
|
|
||||||
cliVers := "µTorrent 3.5.5"
|
|
||||||
|
|
||||||
bt.config = torrent.NewDefaultClientConfig()
|
|
||||||
|
|
||||||
bt.config.Debug = settings.Get().EnableDebug
|
|
||||||
bt.config.DisableIPv6 = settings.Get().EnableIPv6 == false
|
|
||||||
bt.config.DisableTCP = settings.Get().DisableTCP
|
|
||||||
bt.config.DisableUTP = settings.Get().DisableUTP
|
|
||||||
bt.config.NoDefaultPortForwarding = settings.Get().DisableUPNP
|
|
||||||
bt.config.NoDHT = settings.Get().DisableDHT
|
|
||||||
bt.config.NoUpload = settings.Get().DisableUpload
|
|
||||||
bt.config.EncryptionPolicy = torrent.EncryptionPolicy{
|
|
||||||
DisableEncryption: settings.Get().Encryption == 1,
|
|
||||||
ForceEncryption: settings.Get().Encryption == 2,
|
|
||||||
}
|
|
||||||
bt.config.IPBlocklist = blocklist
|
|
||||||
bt.config.DefaultStorage = bt.storage
|
|
||||||
bt.config.Bep20 = peerID
|
|
||||||
bt.config.PeerID = utils.PeerIDRandom(peerID)
|
|
||||||
bt.config.HTTPUserAgent = userAgent
|
|
||||||
bt.config.ExtendedHandshakeClientVersion = cliVers
|
|
||||||
bt.config.EstablishedConnsPerTorrent = settings.Get().ConnectionsLimit
|
|
||||||
if settings.Get().DhtConnectionLimit > 0 {
|
|
||||||
bt.config.ConnTracker.SetMaxEntries(settings.Get().DhtConnectionLimit)
|
|
||||||
}
|
|
||||||
if settings.Get().DownloadRateLimit > 0 {
|
|
||||||
bt.config.DownloadRateLimiter = utils.Limit(settings.Get().DownloadRateLimit * 1024)
|
|
||||||
}
|
|
||||||
if settings.Get().UploadRateLimit > 0 {
|
|
||||||
bt.config.UploadRateLimiter = utils.Limit(settings.Get().UploadRateLimit * 1024)
|
|
||||||
}
|
|
||||||
if settings.Get().PeersListenPort > 0 {
|
|
||||||
bt.config.ListenPort = settings.Get().PeersListenPort
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Configure client:", settings.Get())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) AddTorrent(magnet metainfo.Magnet, infobytes []byte, onAdd func(*Torrent)) (*Torrent, error) {
|
|
||||||
torr, err := NewTorrent(magnet, infobytes, bt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if onAdd != nil {
|
|
||||||
go func() {
|
|
||||||
if torr.GotInfo() {
|
|
||||||
onAdd(torr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
go torr.GotInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
return torr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) List() []*Torrent {
|
|
||||||
bt.mu.Lock()
|
|
||||||
defer bt.mu.Unlock()
|
|
||||||
list := make([]*Torrent, 0)
|
|
||||||
for _, t := range bt.torrents {
|
|
||||||
list = append(list, t)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) GetTorrent(hash metainfo.Hash) *Torrent {
|
|
||||||
bt.mu.Lock()
|
|
||||||
defer bt.mu.Unlock()
|
|
||||||
|
|
||||||
if t, ok := bt.torrents[hash]; ok {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) RemoveTorrent(hash torrent.InfoHash) {
|
|
||||||
if torr, ok := bt.torrents[hash]; ok {
|
|
||||||
torr.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
for _, dht := range bt.client.DhtServers() {
|
|
||||||
btState.DHTs = append(btState.DHTs, dht)
|
|
||||||
}
|
|
||||||
for _, t := range bt.torrents {
|
|
||||||
btState.Torrents = append(btState.Torrents, t)
|
|
||||||
}
|
|
||||||
return btState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) CacheState(hash metainfo.Hash) *state.CacheState {
|
|
||||||
st := bt.GetTorrent(hash)
|
|
||||||
if st == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheState := bt.storage.GetStats(hash)
|
|
||||||
return cacheState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) WriteState(w io.Writer) {
|
|
||||||
bt.client.WriteStatus(w)
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package torr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/utils"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/anacrolix/missinggo/httptoo"
|
|
||||||
"github.com/anacrolix/torrent"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bt *BTServer) View(torr *Torrent, file *torrent.File, c echo.Context) error {
|
|
||||||
go settings.SetViewed(torr.Hash().HexString(), file.Path())
|
|
||||||
reader := torr.NewReader(file, 0)
|
|
||||||
|
|
||||||
log.Println("Connect client")
|
|
||||||
c.Response().Header().Set("Connection", "close")
|
|
||||||
c.Response().Header().Set("ETag", httptoo.EncodeQuotedString(fmt.Sprintf("%s/%s", torr.Hash().HexString(), file.Path())))
|
|
||||||
|
|
||||||
http.ServeContent(c.Response(), c.Request(), file.Path(), time.Time{}, reader)
|
|
||||||
|
|
||||||
log.Println("Disconnect client")
|
|
||||||
torr.CloseReader(reader)
|
|
||||||
return c.NoContent(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BTServer) Play(torr *Torrent, file *torrent.File, preload int64, c echo.Context) error {
|
|
||||||
if torr.status == TorrentAdded {
|
|
||||||
if !torr.GotInfo() {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "torrent closed befor get info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if torr.status == TorrentGettingInfo {
|
|
||||||
if !torr.WaitInfo() {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "torrent closed befor get info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if torr.PreloadedBytes == 0 {
|
|
||||||
torr.Preload(file, preload)
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl := c.Scheme() + "://" + c.Request().Host + "/torrent/view/" + torr.Hash().HexString() + "/" + utils.CleanFName(file.Path())
|
|
||||||
return c.Redirect(http.StatusFound, redirectUrl)
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package torr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/anacrolix/dht"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BTState struct {
|
|
||||||
LocalPort int
|
|
||||||
PeerID string
|
|
||||||
BannedIPs int
|
|
||||||
DHTs []*dht.Server
|
|
||||||
|
|
||||||
Torrents []*Torrent
|
|
||||||
}
|
|
||||||
|
|
||||||
type TorrentStats struct {
|
|
||||||
Name string
|
|
||||||
Hash string
|
|
||||||
|
|
||||||
TorrentStatus TorrentStatus
|
|
||||||
TorrentStatusString string
|
|
||||||
|
|
||||||
LoadedSize int64
|
|
||||||
TorrentSize int64
|
|
||||||
|
|
||||||
PreloadedBytes int64
|
|
||||||
PreloadSize int64
|
|
||||||
|
|
||||||
DownloadSpeed float64
|
|
||||||
UploadSpeed float64
|
|
||||||
|
|
||||||
TotalPeers int
|
|
||||||
PendingPeers int
|
|
||||||
ActivePeers int
|
|
||||||
ConnectedSeeders int
|
|
||||||
HalfOpenPeers int
|
|
||||||
|
|
||||||
BytesWritten int64
|
|
||||||
BytesWrittenData int64
|
|
||||||
BytesRead int64
|
|
||||||
BytesReadData int64
|
|
||||||
BytesReadUsefulData int64
|
|
||||||
ChunksWritten int64
|
|
||||||
ChunksRead int64
|
|
||||||
ChunksReadUseful int64
|
|
||||||
ChunksReadWasted int64
|
|
||||||
PiecesDirtiedGood int64
|
|
||||||
PiecesDirtiedBad int64
|
|
||||||
|
|
||||||
FileStats []TorrentFileStat
|
|
||||||
}
|
|
||||||
|
|
||||||
type TorrentFileStat struct {
|
|
||||||
Id int
|
|
||||||
Path string
|
|
||||||
Length int64
|
|
||||||
}
|
|
||||||
106
src/server/torr/btserver.go
Normal file
106
src/server/torr/btserver.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package torr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"server/settings"
|
||||||
|
"server/torr/storage/torrstor"
|
||||||
|
"server/torr/utils"
|
||||||
|
|
||||||
|
"github.com/anacrolix/torrent"
|
||||||
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BTServer struct {
|
||||||
|
config *torrent.ClientConfig
|
||||||
|
client *torrent.Client
|
||||||
|
|
||||||
|
storage *torrstor.Storage
|
||||||
|
|
||||||
|
torrents map[metainfo.Hash]*Torrent
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBTS() *BTServer {
|
||||||
|
bts := new(BTServer)
|
||||||
|
bts.torrents = make(map[metainfo.Hash]*Torrent)
|
||||||
|
return bts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BTServer) Connect() error {
|
||||||
|
bt.mu.Lock()
|
||||||
|
defer bt.mu.Unlock()
|
||||||
|
var err error
|
||||||
|
bt.configure()
|
||||||
|
bt.client, err = torrent.NewClient(bt.config)
|
||||||
|
bt.torrents = make(map[metainfo.Hash]*Torrent)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BTServer) Disconnect() {
|
||||||
|
bt.mu.Lock()
|
||||||
|
defer bt.mu.Unlock()
|
||||||
|
if bt.client != nil {
|
||||||
|
bt.client.Close()
|
||||||
|
bt.client = nil
|
||||||
|
utils.FreeOSMemGC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BTServer) Reconnect() error {
|
||||||
|
bt.Disconnect()
|
||||||
|
return bt.Connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BTServer) configure() {
|
||||||
|
bt.storage = torrstor.NewStorage(settings.BTsets.CacheSize)
|
||||||
|
|
||||||
|
blocklist, _ := utils.ReadBlockedIP()
|
||||||
|
|
||||||
|
userAgent := "uTorrent/3.5.5"
|
||||||
|
peerID := "-UT3550-"
|
||||||
|
cliVers := "µTorrent 3.5.5"
|
||||||
|
|
||||||
|
bt.config = torrent.NewDefaultClientConfig()
|
||||||
|
|
||||||
|
bt.config.Debug = settings.BTsets.EnableDebug
|
||||||
|
bt.config.DisableIPv6 = settings.BTsets.EnableIPv6 == false
|
||||||
|
bt.config.DisableTCP = settings.BTsets.DisableTCP
|
||||||
|
bt.config.DisableUTP = settings.BTsets.DisableUTP
|
||||||
|
bt.config.NoDefaultPortForwarding = settings.BTsets.DisableUPNP
|
||||||
|
bt.config.NoDHT = settings.BTsets.DisableDHT
|
||||||
|
bt.config.NoUpload = settings.BTsets.DisableUpload
|
||||||
|
// bt.config.EncryptionPolicy = torrent.EncryptionPolicy{
|
||||||
|
// DisableEncryption: settings.BTsets.Encryption == 1,
|
||||||
|
// ForceEncryption: settings.BTsets.Encryption == 2,
|
||||||
|
// }
|
||||||
|
bt.config.IPBlocklist = blocklist
|
||||||
|
bt.config.DefaultStorage = bt.storage
|
||||||
|
bt.config.Bep20 = peerID
|
||||||
|
bt.config.PeerID = utils.PeerIDRandom(peerID)
|
||||||
|
bt.config.HTTPUserAgent = userAgent
|
||||||
|
bt.config.ExtendedHandshakeClientVersion = cliVers
|
||||||
|
bt.config.EstablishedConnsPerTorrent = settings.BTsets.ConnectionsLimit
|
||||||
|
if settings.BTsets.DhtConnectionLimit > 0 {
|
||||||
|
bt.config.ConnTracker.SetMaxEntries(settings.BTsets.DhtConnectionLimit)
|
||||||
|
}
|
||||||
|
if settings.BTsets.DownloadRateLimit > 0 {
|
||||||
|
bt.config.DownloadRateLimiter = utils.Limit(settings.BTsets.DownloadRateLimit * 1024)
|
||||||
|
}
|
||||||
|
if settings.BTsets.UploadRateLimit > 0 {
|
||||||
|
bt.config.UploadRateLimiter = utils.Limit(settings.BTsets.UploadRateLimit * 1024)
|
||||||
|
}
|
||||||
|
if settings.BTsets.PeersListenPort > 0 {
|
||||||
|
bt.config.ListenPort = settings.BTsets.PeersListenPort
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Configure client:", settings.BTsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bt *BTServer) RemoveTorrent(hash torrent.InfoHash) {
|
||||||
|
if torr, ok := bt.torrents[hash]; ok {
|
||||||
|
torr.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package reader
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anacrolix/torrent"
|
"github.com/anacrolix/torrent"
|
||||||
|
"server/torr"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,10 +14,16 @@ type Reader struct {
|
|||||||
file *torrent.File
|
file *torrent.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(file *torrent.File) *Reader {
|
func NewReader(torr *torr.Torrent, file *torrent.File, readahead int64) *Reader {
|
||||||
r := new(Reader)
|
r := new(Reader)
|
||||||
r.file = file
|
r.file = file
|
||||||
r.Reader = file.NewReader()
|
r.Reader = file.NewReader()
|
||||||
|
|
||||||
|
if readahead <= 0 {
|
||||||
|
readahead = torr.Torrent.Info().PieceLength
|
||||||
|
}
|
||||||
|
r.SetReadahead(readahead)
|
||||||
|
torr.GetCache().AddReader(r)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
130
src/server/torr/state.go
Normal file
130
src/server/torr/state.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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
|
||||||
|
Hash string
|
||||||
|
|
||||||
|
TorrentStatus TorrentStatus
|
||||||
|
TorrentStatusString string
|
||||||
|
|
||||||
|
LoadedSize int64
|
||||||
|
TorrentSize int64
|
||||||
|
|
||||||
|
PreloadedBytes int64
|
||||||
|
PreloadSize int64
|
||||||
|
|
||||||
|
DownloadSpeed float64
|
||||||
|
UploadSpeed float64
|
||||||
|
|
||||||
|
TotalPeers int
|
||||||
|
PendingPeers int
|
||||||
|
ActivePeers int
|
||||||
|
ConnectedSeeders int
|
||||||
|
HalfOpenPeers int
|
||||||
|
|
||||||
|
BytesWritten int64
|
||||||
|
BytesWrittenData int64
|
||||||
|
BytesRead int64
|
||||||
|
BytesReadData int64
|
||||||
|
BytesReadUsefulData int64
|
||||||
|
ChunksWritten int64
|
||||||
|
ChunksRead int64
|
||||||
|
ChunksReadUseful int64
|
||||||
|
ChunksReadWasted int64
|
||||||
|
PiecesDirtiedGood int64
|
||||||
|
PiecesDirtiedBad int64
|
||||||
|
|
||||||
|
FileStats []TorrentFileStat
|
||||||
|
}
|
||||||
|
|
||||||
|
type TorrentFileStat struct {
|
||||||
|
Id int
|
||||||
|
Path string
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
package filecache
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package filecache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/torr/storage"
|
|
||||||
"server/torr/storage/state"
|
|
||||||
|
|
||||||
"github.com/anacrolix/missinggo/filecache"
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
|
||||||
storage2 "github.com/anacrolix/torrent/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Storage struct {
|
|
||||||
storage.Storage
|
|
||||||
|
|
||||||
caches map[metainfo.Hash]*filecache.Cache
|
|
||||||
capacity int64
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorage(capacity int64) storage.Storage {
|
|
||||||
stor := new(Storage)
|
|
||||||
stor.capacity = capacity
|
|
||||||
stor.caches = make(map[metainfo.Hash]*filecache.Cache)
|
|
||||||
return stor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (storage2.TorrentImpl, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
path := filepath.Join(settings.Path, "cache", infoHash.String())
|
|
||||||
cache, err := filecache.NewCache(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cache.SetCapacity(s.capacity)
|
|
||||||
s.caches[infoHash] = cache
|
|
||||||
return storage2.NewResourcePieces(cache.AsResourceProvider()).OpenTorrent(info, infoHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) GetStats(hash metainfo.Hash) *state.CacheState {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) Clean() {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) CloseHash(hash metainfo.Hash) {
|
|
||||||
if s.caches == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) Close() error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package state
|
|
||||||
|
|
||||||
type CacheState struct {
|
|
||||||
Hash string
|
|
||||||
Capacity int64
|
|
||||||
Filled int64
|
|
||||||
PiecesLength int64
|
|
||||||
PiecesCount int
|
|
||||||
Pieces map[int]ItemState
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemState struct {
|
|
||||||
Id int
|
|
||||||
Accessed int64
|
|
||||||
BufferSize int64
|
|
||||||
Completed bool
|
|
||||||
Hash string
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"server/torr/storage/state"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"github.com/anacrolix/torrent/storage"
|
"github.com/anacrolix/torrent/storage"
|
||||||
)
|
)
|
||||||
@@ -10,6 +8,5 @@ import (
|
|||||||
type Storage interface {
|
type Storage interface {
|
||||||
storage.ClientImpl
|
storage.ClientImpl
|
||||||
|
|
||||||
GetStats(hash metainfo.Hash) *state.CacheState
|
|
||||||
CloseHash(hash metainfo.Hash)
|
CloseHash(hash metainfo.Hash)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package memcache
|
package torrstor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -53,7 +53,6 @@ func (b *BufferPool) GetBuffer(p *Piece) (buff []byte, index int) {
|
|||||||
buff = buf.buf
|
buff = buf.buf
|
||||||
index = id
|
index = id
|
||||||
b.frees--
|
b.frees--
|
||||||
//fmt.Printf("Get buffer: %v %v %v %p\n", id, p.Id, b.frees, buff)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package memcache
|
package torrstor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
@@ -7,8 +7,7 @@ import (
|
|||||||
|
|
||||||
"server/settings"
|
"server/settings"
|
||||||
"server/torr/reader"
|
"server/torr/reader"
|
||||||
"server/torr/storage/state"
|
"server/torr/utils"
|
||||||
"server/utils"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"github.com/anacrolix/torrent/storage"
|
"github.com/anacrolix/torrent/storage"
|
||||||
@@ -101,30 +100,6 @@ func (c *Cache) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) GetState() state.CacheState {
|
|
||||||
cState := state.CacheState{}
|
|
||||||
cState.Capacity = c.capacity
|
|
||||||
cState.PiecesLength = c.pieceLength
|
|
||||||
cState.PiecesCount = c.pieceCount
|
|
||||||
cState.Hash = c.hash.HexString()
|
|
||||||
|
|
||||||
stats := make(map[int]state.ItemState, 0)
|
|
||||||
c.muPiece.Lock()
|
|
||||||
var fill int64 = 0
|
|
||||||
for _, value := range c.pieces {
|
|
||||||
stat := value.Stat()
|
|
||||||
if stat.BufferSize > 0 {
|
|
||||||
fill += stat.BufferSize
|
|
||||||
stats[stat.Id] = stat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.filled = fill
|
|
||||||
c.muPiece.Unlock()
|
|
||||||
cState.Filled = c.filled
|
|
||||||
cState.Pieces = stats
|
|
||||||
return cState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) cleanPieces() {
|
func (c *Cache) cleanPieces() {
|
||||||
if c.isRemove {
|
if c.isRemove {
|
||||||
return
|
return
|
||||||
@@ -220,7 +195,7 @@ func (c *Cache) ReadersLen() int {
|
|||||||
func (c *Cache) AdjustRA(readahead int64) {
|
func (c *Cache) AdjustRA(readahead int64) {
|
||||||
c.muReader.Lock()
|
c.muReader.Lock()
|
||||||
defer c.muReader.Unlock()
|
defer c.muReader.Unlock()
|
||||||
if settings.Get().CacheSize == 0 {
|
if settings.BTsets.CacheSize == 0 {
|
||||||
c.capacity = readahead * 3
|
c.capacity = readahead * 3
|
||||||
}
|
}
|
||||||
for r, _ := range c.readers {
|
for r, _ := range c.readers {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package memcache
|
package torrstor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"server/torr/storage/state"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/storage"
|
"github.com/anacrolix/torrent/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,13 +101,10 @@ func (p *Piece) Release() {
|
|||||||
p.complete = false
|
p.complete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Piece) Stat() state.ItemState {
|
func WriteToDisk(b []byte, off int64) (n int, err error) {
|
||||||
itm := state.ItemState{
|
return 0, nil
|
||||||
Id: p.Id,
|
|
||||||
Hash: p.Hash,
|
|
||||||
Accessed: p.accessed,
|
|
||||||
Completed: p.complete,
|
|
||||||
BufferSize: p.Size,
|
|
||||||
}
|
}
|
||||||
return itm
|
|
||||||
|
func ReadFromDisk(b []byte, off int64) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package memcache
|
package torrstor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"server/torr/storage"
|
"server/torr/storage"
|
||||||
"server/torr/storage/state"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
storage2 "github.com/anacrolix/torrent/storage"
|
storage2 "github.com/anacrolix/torrent/storage"
|
||||||
@@ -34,16 +33,6 @@ func (s *Storage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (stor
|
|||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) GetStats(hash metainfo.Hash) *state.CacheState {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
if c, ok := s.caches[hash]; ok {
|
|
||||||
st := c.GetState()
|
|
||||||
return &st
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) CloseHash(hash metainfo.Hash) {
|
func (s *Storage) CloseHash(hash metainfo.Hash) {
|
||||||
if s.caches == nil {
|
if s.caches == nil {
|
||||||
return
|
return
|
||||||
31
src/server/torr/stream.go
Normal file
31
src/server/torr/stream.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package torr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/anacrolix/missinggo/httptoo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Torrent) Stream(fileIndex int, req *http.Request, resp http.ResponseWriter) error {
|
||||||
|
files := t.Files()
|
||||||
|
if fileIndex < 0 || fileIndex >= len(files) {
|
||||||
|
return errors.New("file index out of range")
|
||||||
|
}
|
||||||
|
file := files[fileIndex]
|
||||||
|
reader := t.NewReader(file, 0)
|
||||||
|
|
||||||
|
log.Println("Connect client")
|
||||||
|
|
||||||
|
resp.Header().Set("Connection", "close")
|
||||||
|
resp.Header().Set("ETag", httptoo.EncodeQuotedString(fmt.Sprintf("%s/%s", t.Hash().HexString(), file.Path())))
|
||||||
|
|
||||||
|
http.ServeContent(resp, req, file.Path(), time.Time{}, reader)
|
||||||
|
|
||||||
|
log.Println("Disconnect client")
|
||||||
|
t.CloseReader(reader)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,15 +3,15 @@ package torr
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"server/settings"
|
"server/settings"
|
||||||
"server/utils"
|
"server/torr/utils"
|
||||||
|
utils2 "server/utils"
|
||||||
|
|
||||||
"server/torr/reader"
|
"server/torr/reader"
|
||||||
"server/torr/storage/memcache"
|
"server/torr/storage/torrstor"
|
||||||
|
|
||||||
"github.com/anacrolix/torrent"
|
"github.com/anacrolix/torrent"
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
@@ -51,10 +51,9 @@ type Torrent struct {
|
|||||||
status TorrentStatus
|
status TorrentStatus
|
||||||
|
|
||||||
muTorrent sync.Mutex
|
muTorrent sync.Mutex
|
||||||
muReader sync.Mutex
|
|
||||||
|
|
||||||
bt *BTServer
|
bt *BTServer
|
||||||
cache *memcache.Cache
|
cache *torrstor.Cache
|
||||||
|
|
||||||
lastTimeSpeed time.Time
|
lastTimeSpeed time.Time
|
||||||
DownloadSpeed float64
|
DownloadSpeed float64
|
||||||
@@ -74,29 +73,25 @@ type Torrent struct {
|
|||||||
progressTicker *time.Ticker
|
progressTicker *time.Ticker
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTorrent(magnet metainfo.Magnet, infobytes []byte, bt *BTServer) (*Torrent, error) {
|
func NewTorrent(spec *torrent.TorrentSpec, bt *BTServer) (*Torrent, error) {
|
||||||
switch settings.Get().RetrackersMode {
|
|
||||||
case 1:
|
|
||||||
magnet.Trackers = append(magnet.Trackers, utils.GetDefTrackers()...)
|
|
||||||
case 2:
|
|
||||||
magnet.Trackers = nil
|
|
||||||
case 3:
|
|
||||||
magnet.Trackers = utils.GetDefTrackers()
|
|
||||||
}
|
|
||||||
goTorrent, _, err := bt.client.AddTorrentSpec(&torrent.TorrentSpec{
|
|
||||||
InfoBytes: infobytes,
|
|
||||||
Trackers: [][]string{magnet.Trackers},
|
|
||||||
DisplayName: magnet.DisplayName,
|
|
||||||
InfoHash: magnet.InfoHash,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
switch settings.BTsets.RetrackersMode {
|
||||||
|
case 1:
|
||||||
|
spec.Trackers = append(spec.Trackers, [][]string{utils.GetDefTrackers()}...)
|
||||||
|
case 2:
|
||||||
|
spec.Trackers = nil
|
||||||
|
case 3:
|
||||||
|
spec.Trackers = [][]string{utils.GetDefTrackers()}
|
||||||
|
}
|
||||||
|
|
||||||
|
goTorrent, _, err := bt.client.AddTorrentSpec(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bt.mu.Lock()
|
bt.mu.Lock()
|
||||||
defer bt.mu.Unlock()
|
defer bt.mu.Unlock()
|
||||||
if tor, ok := bt.torrents[magnet.InfoHash]; ok {
|
if tor, ok := bt.torrents[spec.InfoHash]; ok {
|
||||||
return tor, nil
|
return tor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,12 +100,12 @@ func NewTorrent(magnet metainfo.Magnet, infobytes []byte, bt *BTServer) (*Torren
|
|||||||
torr.status = TorrentAdded
|
torr.status = TorrentAdded
|
||||||
torr.lastTimeSpeed = time.Now()
|
torr.lastTimeSpeed = time.Now()
|
||||||
torr.bt = bt
|
torr.bt = bt
|
||||||
torr.hash = magnet.InfoHash
|
torr.hash = spec.InfoHash
|
||||||
torr.closed = goTorrent.Closed()
|
torr.closed = goTorrent.Closed()
|
||||||
|
|
||||||
go torr.watch()
|
go torr.watch()
|
||||||
|
|
||||||
bt.torrents[magnet.InfoHash] = torr
|
bt.torrents[spec.InfoHash] = torr
|
||||||
return torr, nil
|
return torr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +114,7 @@ func (t *Torrent) WaitInfo() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close torrent if not info while 10 minutes
|
||||||
tm := time.NewTimer(time.Minute * 10)
|
tm := time.NewTimer(time.Minute * 10)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -185,17 +181,22 @@ func (t *Torrent) progressEvent() {
|
|||||||
t.UploadSpeed = 0
|
t.UploadSpeed = 0
|
||||||
}
|
}
|
||||||
t.muTorrent.Unlock()
|
t.muTorrent.Unlock()
|
||||||
|
|
||||||
t.lastTimeSpeed = time.Now()
|
t.lastTimeSpeed = time.Now()
|
||||||
if t.BytesReadUsefulData > settings.Get().PreloadBufferSize {
|
t.updateRA()
|
||||||
adj := int64((int(t.cache.GetState().PiecesLength) * t.Torrent.Stats().ActivePeers) / (1 + t.cache.ReadersLen()))
|
}
|
||||||
|
|
||||||
|
func (t *Torrent) updateRA() {
|
||||||
|
if t.BytesReadUsefulData > settings.BTsets.PreloadBufferSize {
|
||||||
|
pieceLen := t.Torrent.Info().PieceLength
|
||||||
|
adj := pieceLen * int64(t.Torrent.Stats().ActivePeers) / int64(1+t.cache.ReadersLen())
|
||||||
switch {
|
switch {
|
||||||
case adj < t.cache.GetState().PiecesLength:
|
case adj < pieceLen:
|
||||||
adj = t.cache.GetState().PiecesLength
|
adj = pieceLen
|
||||||
case adj > t.cache.GetState().PiecesLength*4:
|
case adj > pieceLen*4:
|
||||||
adj = t.cache.GetState().PiecesLength * 4
|
adj = pieceLen * 4
|
||||||
}
|
}
|
||||||
t.cache.AdjustRA(adj)
|
t.cache.AdjustRA(adj)
|
||||||
//log.Println("Status:", t.Name(), "S:", fmt.Sprintf("%8s", utils.Format(t.DownloadSpeed)), "P:", fmt.Sprintf("%2d", t.Torrent.Stats().ActivePeers), "/", fmt.Sprintf("%2d", t.Torrent.Stats().TotalPeers), "R:", t.cache.ReadersLen(), "RA:", utils.Format(float64(adj)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,31 +228,20 @@ func (t *Torrent) Length() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) NewReader(file *torrent.File, readahead int64) *reader.Reader {
|
func (t *Torrent) NewReader(file *torrent.File, readahead int64) *reader.Reader {
|
||||||
t.muReader.Lock()
|
|
||||||
|
|
||||||
if t.status == TorrentClosed {
|
if t.status == TorrentClosed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
reader := reader.NewReader(t, file, readahead)
|
||||||
defer t.muReader.Unlock()
|
|
||||||
reader := reader.NewReader(file)
|
|
||||||
if readahead <= 0 {
|
|
||||||
readahead = t.cache.GetState().PiecesLength
|
|
||||||
}
|
|
||||||
reader.SetReadahead(readahead)
|
|
||||||
t.cache.AddReader(reader)
|
|
||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) CloseReader(reader *reader.Reader) {
|
func (t *Torrent) CloseReader(reader *reader.Reader) {
|
||||||
t.muReader.Lock()
|
|
||||||
reader.Close()
|
reader.Close()
|
||||||
t.cache.RemReader(reader)
|
t.cache.RemReader(reader)
|
||||||
t.expiredTime = time.Now().Add(time.Second * time.Duration(settings.Get().TorrentDisconnectTimeout))
|
t.expiredTime = time.Now().Add(time.Second * time.Duration(settings.BTsets.TorrentDisconnectTimeout))
|
||||||
t.muReader.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) GetCache() *memcache.Cache {
|
func (t *Torrent) GetCache() *torrstor.Cache {
|
||||||
return t.cache
|
return t.cache
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,6 +252,8 @@ func (t *Torrent) Preload(file *torrent.File, size int64) {
|
|||||||
|
|
||||||
if t.status == TorrentGettingInfo {
|
if t.status == TorrentGettingInfo {
|
||||||
t.WaitInfo()
|
t.WaitInfo()
|
||||||
|
// wait change status
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.muTorrent.Lock()
|
t.muTorrent.Lock()
|
||||||
@@ -271,7 +263,7 @@ func (t *Torrent) Preload(file *torrent.File, size int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = settings.Get().PreloadBufferSize
|
size = settings.BTsets.PreloadBufferSize
|
||||||
}
|
}
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
t.muTorrent.Unlock()
|
t.muTorrent.Unlock()
|
||||||
@@ -325,7 +317,7 @@ func (t *Torrent) Preload(file *torrent.File, size int64) {
|
|||||||
for t.status == TorrentPreload {
|
for t.status == 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(), bytes.Format(t.PreloadedBytes), "/", bytes.Format(t.PreloadSize), "Speed:", utils.Format(t.DownloadSpeed), "Peers:[", t.Torrent.Stats().ConnectedSeeders, "]", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers)
|
log.Println("Preload:", file.Torrent().InfoHash().HexString(), bytes.Format(t.PreloadedBytes), "/", bytes.Format(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
|
||||||
}
|
}
|
||||||
@@ -357,66 +349,9 @@ func (t *Torrent) Close() {
|
|||||||
t.bt.mu.Lock()
|
t.bt.mu.Lock()
|
||||||
defer t.bt.mu.Unlock()
|
defer t.bt.mu.Unlock()
|
||||||
|
|
||||||
t.muReader.Lock()
|
|
||||||
defer t.muReader.Unlock()
|
|
||||||
|
|
||||||
if _, ok := t.bt.torrents[t.hash]; ok {
|
if _, ok := t.bt.torrents[t.hash]; ok {
|
||||||
delete(t.bt.torrents, t.hash)
|
delete(t.bt.torrents, t.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.drop()
|
t.drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
15
src/server/torr/utils/freemem.go
Normal file
15
src/server/torr/utils/freemem.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FreeOSMem() {
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FreeOSMemGC() {
|
||||||
|
runtime.GC()
|
||||||
|
debug.FreeOSMemory()
|
||||||
|
}
|
||||||
@@ -2,16 +2,11 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent"
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,30 +90,6 @@ func PeerIDRandom(peer string) string {
|
|||||||
return peer + base32.StdEncoding.EncodeToString(randomBytes)[:20-len(peer)]
|
return peer + base32.StdEncoding.EncodeToString(randomBytes)[:20-len(peer)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func GotInfo(t *torrent.Torrent, timeout int) error {
|
|
||||||
gi := t.GotInfo()
|
|
||||||
select {
|
|
||||||
case <-gi:
|
|
||||||
return nil
|
|
||||||
case <-time.Tick(time.Second * time.Duration(timeout)):
|
|
||||||
return errors.New("timeout load torrent info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetReadahead() int64 {
|
|
||||||
readahead := settings.Get().CacheSize - (138412032) //132mb
|
|
||||||
if readahead < 69206016 { //66mb
|
|
||||||
readahead = int64(float64(settings.Get().CacheSize) * 0.33)
|
|
||||||
if readahead < 66*1024*1024 {
|
|
||||||
readahead = int64(settings.Get().CacheSize)
|
|
||||||
if readahead > 66*1024*1024 {
|
|
||||||
readahead = 66 * 1024 * 1024
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return readahead
|
|
||||||
}
|
|
||||||
|
|
||||||
func Limit(i int) *rate.Limiter {
|
func Limit(i int) *rate.Limiter {
|
||||||
l := rate.NewLimiter(rate.Inf, 0)
|
l := rate.NewLimiter(rate.Inf, 0)
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@@ -3,8 +3,6 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -18,15 +16,6 @@ func CleanFName(file string) string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func FreeOSMem() {
|
|
||||||
debug.FreeOSMemory()
|
|
||||||
}
|
|
||||||
|
|
||||||
func FreeOSMemGC() {
|
|
||||||
runtime.GC()
|
|
||||||
debug.FreeOSMemory()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
|
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
|
||||||
KB
|
KB
|
||||||
@@ -68,13 +57,3 @@ func Format(b float64) string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%.2f%s", value, multiple)
|
return fmt.Sprintf("%.2f%s", value, multiple)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//func IsCyrillic(str string) bool {
|
|
||||||
// for _, r := range str {
|
|
||||||
// if unicode.Is(unicode.Cyrillic, r) {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
//}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
const Version = "1.1.77"
|
|
||||||
const VerInt = 77
|
|
||||||
4
src/server/version/version.go
Normal file
4
src/server/version/version.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
const Version = "1.2.78"
|
||||||
|
const VerInt = 78
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
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 # Nodes: %d (%d good, %d banned)<br>\n", dhtStats.Nodes, dhtStats.GoodNodes, dhtStats.BadNodes)
|
|
||||||
msg += fmt.Sprintf("\t Server ID: %x<br>\n", dht.ID())
|
|
||||||
msg += fmt.Sprintf("\t Announces: %d<br>\n", dhtStats.SuccessfulOutboundAnnouncePeerQueries)
|
|
||||||
msg += fmt.Sprintf("\t 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 TotalPeers: %v<br>\n", st.TotalPeers)
|
|
||||||
msg += fmt.Sprintf("\t PendingPeers: %v<br>\n", st.PendingPeers)
|
|
||||||
msg += fmt.Sprintf("\t ActivePeers: %v<br>\n", st.ActivePeers)
|
|
||||||
msg += fmt.Sprintf("\t ConnectedSeeders: %v<br>\n", st.ConnectedSeeders)
|
|
||||||
msg += fmt.Sprintf("\t HalfOpenPeers: %v<br>\n", st.HalfOpenPeers)
|
|
||||||
|
|
||||||
msg += fmt.Sprintf("\t BytesWritten: %v (%v)<br>\n", st.BytesWritten, bytes.Format(st.BytesWritten))
|
|
||||||
msg += fmt.Sprintf("\t BytesWrittenData: %v (%v)<br>\n", st.BytesWrittenData, bytes.Format(st.BytesWrittenData))
|
|
||||||
msg += fmt.Sprintf("\t BytesRead: %v (%v)<br>\n", st.BytesRead, bytes.Format(st.BytesRead))
|
|
||||||
msg += fmt.Sprintf("\t BytesReadData: %v (%v)<br>\n", st.BytesReadData, bytes.Format(st.BytesReadData))
|
|
||||||
msg += fmt.Sprintf("\t BytesReadUsefulData: %v (%v)<br>\n", st.BytesReadUsefulData, bytes.Format(st.BytesReadUsefulData))
|
|
||||||
msg += fmt.Sprintf("\t ChunksWritten: %v<br>\n", st.ChunksWritten)
|
|
||||||
msg += fmt.Sprintf("\t ChunksRead: %v<br>\n", st.ChunksRead)
|
|
||||||
msg += fmt.Sprintf("\t ChunksReadUseful: %v<br>\n", st.ChunksReadUseful)
|
|
||||||
msg += fmt.Sprintf("\t ChunksReadWasted: %v<br>\n", st.ChunksReadWasted)
|
|
||||||
msg += fmt.Sprintf("\t PiecesDirtiedGood: %v<br>\n", st.PiecesDirtiedGood)
|
|
||||||
msg += fmt.Sprintf("\t PiecesDirtiedBad: %v<br>\n<br>\n", st.PiecesDirtiedBad)
|
|
||||||
if len(st.FileStats) > 0 {
|
|
||||||
msg += fmt.Sprintf("\t Files:<br>\n")
|
|
||||||
for _, f := range st.FileStats {
|
|
||||||
msg += fmt.Sprintf("\t \t %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 Piece: %v\t  Access: %v\t  Buffer size: %d(%s)\t  Complete: %v\t  Hash: %s\n<br>", p.Id, p.Accessed, 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)
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/torr"
|
|
||||||
"server/version"
|
|
||||||
"server/web/mods"
|
|
||||||
"server/web/templates"
|
|
||||||
|
|
||||||
"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())
|
|
||||||
server.Use(ServerHeaderSet)
|
|
||||||
|
|
||||||
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.ApiJS)
|
|
||||||
|
|
||||||
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 ServerHeaderSet(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
c.Response().Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,720 +0,0 @@
|
|||||||
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.POST("/torrent/viewed/add", torrentViewedAdd)
|
|
||||||
e.POST("/torrent/viewed/remove", torrentViewedRem)
|
|
||||||
e.GET("/torrent/viewed/list", torrentViewedList)
|
|
||||||
|
|
||||||
e.GET("/torrent/restart", torrentRestart)
|
|
||||||
|
|
||||||
e.GET("/torrent/playlist.m3u", torrentPlayListAll)
|
|
||||||
|
|
||||||
e.GET("/torrent/play", torrentPlay)
|
|
||||||
e.HEAD("/torrent/play", torrentPlay)
|
|
||||||
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
|
|
||||||
Play 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, infoBytes, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
tor := bts.GetTorrent(magnet.InfoHash)
|
|
||||||
if tor != nil {
|
|
||||||
return c.String(http.StatusOK, magnet.InfoHash.HexString())
|
|
||||||
}
|
|
||||||
|
|
||||||
torDb, err := settings.LoadTorrentDB(magnet.InfoHash.HexString())
|
|
||||||
if err == nil && torDb != nil {
|
|
||||||
infoBytes = torDb.InfoBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
err = helpers.Add(bts, *magnet, infoBytes, !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() {
|
|
||||||
settings.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"]
|
|
||||||
magnets := map[*metainfo.Magnet][]byte{}
|
|
||||||
|
|
||||||
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[&magnet] = mi.InfoBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]string, 0)
|
|
||||||
for magnet, infobytes := range magnets {
|
|
||||||
er := helpers.Add(bts, *magnet, infobytes, !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()) // Handle R/O DB
|
|
||||||
}
|
|
||||||
|
|
||||||
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, torrDb.InfoBytes, 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 torrentViewedAdd(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = settings.SetViewed(jreq.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
||||||
}
|
|
||||||
return c.NoContent(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func torrentViewedRem(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = settings.RemTorrentViewed(jreq.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
||||||
}
|
|
||||||
return c.NoContent(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func torrentViewedList(c echo.Context) error {
|
|
||||||
err = settings.List(jreq.Hash)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
||||||
}
|
|
||||||
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("Connection", "close")
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
if (settings.Get().EnableDebug) {
|
|
||||||
fmt.Println("Play:", c.QueryParams()) // mute log flood on play
|
|
||||||
}
|
|
||||||
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, infoBytes, 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, infoBytes, 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 {
|
|
||||||
torrDb.InfoBytes = infoBytes
|
|
||||||
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.Now(), 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.StatusBadRequest, fmt.Sprint("File index ", 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, torrDb.InfoBytes, 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()
|
|
||||||
|
|
||||||
st := getTorPlayState(t)
|
|
||||||
|
|
||||||
for _, f := range st.FileStats {
|
|
||||||
tf := settings.File{
|
|
||||||
Id: f.Id,
|
|
||||||
Name: f.Path,
|
|
||||||
Size: f.Length,
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
mag.Trackers = []string{} // remove retrackers for small link size
|
|
||||||
mag.DisplayName = "" // clear dn from link - long query params may fail in QueryParam("link")
|
|
||||||
js.Magnet = tor.Magnet
|
|
||||||
js.Hash = tor.Hash
|
|
||||||
js.AddTime = tor.Timestamp
|
|
||||||
js.Length = tor.Size
|
|
||||||
js.Playlist = "/torrent/play?link=" + url.QueryEscape(mag.String()) + "&m3u=true"
|
|
||||||
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),
|
|
||||||
Play: "/torrent/play/" + utils.CleanFName(f.Name) + "?link=" + url.QueryEscape(mag.String()) + "&file=" + fmt.Sprint(f.Id),
|
|
||||||
Preload: "/torrent/play/" + utils.CleanFName(f.Name) + "?link=" + url.QueryEscape(mag.String()) + "&file=" + fmt.Sprint(f.Id) + "&preload=true",
|
|
||||||
Size: f.Size,
|
|
||||||
Viewed: settings.GetViewed(tor.Hash, f.Name),
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package helpers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
"server/torr"
|
|
||||||
"server/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MakeM3ULists(torrents []*settings.Torrent, host string) string {
|
|
||||||
m3u := "#EXTM3U\n"
|
|
||||||
|
|
||||||
for _, t := range torrents {
|
|
||||||
m3u += "#EXTINF:0 type=\"playlist\"," + t.Name + "\n"
|
|
||||||
|
|
||||||
magnet := t.Magnet
|
|
||||||
mag, _, err := GetMagnet(magnet)
|
|
||||||
if err == nil {
|
|
||||||
mag.Trackers = []string{} // remove retrackers for small link size
|
|
||||||
mag.DisplayName = "" // clear dn from link - long query params may fail in QueryParam("link")
|
|
||||||
magnet = mag.String()
|
|
||||||
}
|
|
||||||
m3u += host + "/torrent/play?link=" + url.QueryEscape(magnet) + "&m3u=true\n"
|
|
||||||
}
|
|
||||||
return m3u
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeM3UPlayList(tor torr.TorrentStats, magnet string, host string) string {
|
|
||||||
m3u := "#EXTM3U\n"
|
|
||||||
|
|
||||||
mag, _, err := GetMagnet(magnet)
|
|
||||||
if err == nil {
|
|
||||||
mag.Trackers = []string{} //Remove retrackers for small link size
|
|
||||||
mag.DisplayName = "" //Remove dn from link (useless)
|
|
||||||
magnet = mag.String()
|
|
||||||
}
|
|
||||||
magnet = url.QueryEscape(magnet)
|
|
||||||
|
|
||||||
for _, f := range tor.FileStats {
|
|
||||||
if GetMimeType(f.Path) != "*/*" {
|
|
||||||
fn := filepath.Base(f.Path)
|
|
||||||
if fn == "" {
|
|
||||||
fn = f.Path
|
|
||||||
}
|
|
||||||
m3u += "#EXTINF:0," + fn + "\n"
|
|
||||||
m3u += host + "/torrent/play/" + url.QueryEscape(utils.CleanFName(f.Path)) + "?link=" + magnet + "&file=" + fmt.Sprint(f.Id) + "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m3u
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package helpers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"server/settings"
|
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetMagnet(link string) (*metainfo.Magnet, []byte, error) {
|
|
||||||
url, err := url.Parse(link)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var mag *metainfo.Magnet
|
|
||||||
var infoBytes []byte
|
|
||||||
switch strings.ToLower(url.Scheme) {
|
|
||||||
case "magnet":
|
|
||||||
mag, err = getMag(url.String())
|
|
||||||
if err == nil {
|
|
||||||
torDb, err := settings.LoadTorrentDB(mag.InfoHash.HexString())
|
|
||||||
if err == nil && torDb != nil {
|
|
||||||
infoBytes = torDb.InfoBytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "http", "https":
|
|
||||||
mag, infoBytes, err = getMagFromHttp(url.String())
|
|
||||||
case "":
|
|
||||||
mag, err = getMag("magnet:?xt=urn:btih:" + url.Path)
|
|
||||||
case "file":
|
|
||||||
mag, infoBytes, err = getMagFromFile(url.Path)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unknown scheme:", url, url.Scheme)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mag, infoBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMag(link string) (*metainfo.Magnet, error) {
|
|
||||||
mag, err := metainfo.ParseMagnetURI(link)
|
|
||||||
return &mag, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMagFromHttp(url string) (*metainfo.Magnet, []byte, error) {
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, nil, errors.New(resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
minfo, err := metainfo.Load(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
info, err := minfo.UnmarshalInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
|
|
||||||
return &mag, minfo.InfoBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMagFromFile(path string) (*metainfo.Magnet, []byte, error) {
|
|
||||||
if runtime.GOOS == "windows" && strings.HasPrefix(path, "/") {
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
|
||||||
}
|
|
||||||
minfo, err := metainfo.LoadFromFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
info, err := minfo.UnmarshalInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
|
|
||||||
return &mag, minfo.InfoBytes, nil
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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, infobytes []byte, save bool) error {
|
|
||||||
fmt.Println("Adding torrent", magnet.String())
|
|
||||||
_, err := bts.AddTorrent(magnet, infobytes, func(torr *torr.Torrent) {
|
|
||||||
if torr, _ := settings.LoadTorrentDB(magnet.InfoHash.HexString()); torr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
torDb := new(settings.Torrent)
|
|
||||||
torDb.Name = torr.Name()
|
|
||||||
torDb.Hash = torr.Hash().HexString()
|
|
||||||
torDb.Size = torr.Length()
|
|
||||||
torDb.Magnet = magnet.String()
|
|
||||||
torDb.InfoBytes = infobytes
|
|
||||||
torDb.Timestamp = time.Now().Unix()
|
|
||||||
files := torr.Stats().FileStats
|
|
||||||
sort.Slice(files, func(i, j int) bool {
|
|
||||||
return files[i].Path < files[j].Path
|
|
||||||
})
|
|
||||||
for _, f := range files {
|
|
||||||
ff := settings.File{
|
|
||||||
Id: f.Id,
|
|
||||||
Name: f.Path,
|
|
||||||
Size: f.Length,
|
|
||||||
}
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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>`)
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
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>
|
|
||||||
<i class="inline">Telegram: </i>
|
|
||||||
<a target="_blank" class="inline" href="https://t.me/TorrServe">@TorrServe</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)
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
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 ApiJS(c echo.Context) error {
|
|
||||||
http.ServeContent(c.Response(), c.Request(), "api.js", settings.StartTime, helpers.NewSeekingBuffer(apijs))
|
|
||||||
return c.NoContent(http.StatusOK)
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
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 {
|
|
||||||
padding-left:6px;
|
|
||||||
padding-right:2px;
|
|
||||||
line-height:11px
|
|
||||||
}
|
|
||||||
.piece {
|
|
||||||
width:10px;
|
|
||||||
height:10px;
|
|
||||||
background-color:#eef2f4;
|
|
||||||
border:1px solid #dee2e5;
|
|
||||||
display:inline-block;
|
|
||||||
margin-right:1px
|
|
||||||
}
|
|
||||||
.piece-complete{
|
|
||||||
background-color:#b8dd69;
|
|
||||||
border-color:#b8dd69
|
|
||||||
}
|
|
||||||
.piece-loading{
|
|
||||||
background-color:#66cbff;
|
|
||||||
border-color:#66cbff
|
|
||||||
}
|
|
||||||
</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, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 piece = st.Pieces[i];
|
|
||||||
if (piece){
|
|
||||||
if (piece.Completed && piece.BufferSize >= st.PiecesLength){
|
|
||||||
$("#p"+i).addClass("piece-complete");
|
|
||||||
$("#p"+i).removeClass("piece-loading");
|
|
||||||
}else if (!piece.Completed && piece.BufferSize > 0){
|
|
||||||
$("#p"+i).removeClass("piece-complete");
|
|
||||||
$("#p"+i).addClass("piece-loading");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!piece || piece.Completed && piece.BufferSize == 0){
|
|
||||||
$("#p"+i).removeClass("piece-complete");
|
|
||||||
$("#p"+i).removeClass("piece-loading");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},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+'" title="'+i+'" ></span>';
|
|
||||||
}
|
|
||||||
cache.html(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
@@ -1,300 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
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="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<div class="input-group-text">Таймаут отключения торрента</div>
|
|
||||||
</div>
|
|
||||||
<input id="TorrentDisconnectTimeout" class="form-control" type="number" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">Время (секунд) после которого закроется торрент, после отключения соединения</small>
|
|
||||||
<br>
|
|
||||||
<div class="form-check">
|
|
||||||
<input id="EnableIPv6" class="form-check-input" type="checkbox" autocomplete="off">
|
|
||||||
<label for="EnableIPv6">Включить IPv6</label>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
<div class="form-check">
|
|
||||||
<input id="EnableDebug" class="form-check-input" type="checkbox" autocomplete="off">
|
|
||||||
<label for="EnableDebug">Режим отладки (только для разработчиков)</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="PeersListenPort" 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>
|
|
||||||
<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">Количество соединений DHT (0 - не ограничивать)</div>
|
|
||||||
</div>
|
|
||||||
<input id="DhtConnectionLimit" 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.TorrentDisconnectTimeout = Number($('#TorrentDisconnectTimeout').val());
|
|
||||||
|
|
||||||
data.EnableIPv6 = $('#EnableIPv6').prop('checked');
|
|
||||||
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.EnableDebug = $('#EnableDebug').prop('checked');
|
|
||||||
data.Encryption = Number($('#Encryption').val());
|
|
||||||
|
|
||||||
data.ConnectionsLimit = Number($('#ConnectionsLimit').val());
|
|
||||||
data.DhtConnectionLimit = Number($('#DhtConnectionLimit').val());
|
|
||||||
|
|
||||||
data.DownloadRateLimit = Number($('#DownloadRateLimit').val());
|
|
||||||
data.UploadRateLimit = Number($('#UploadRateLimit').val());
|
|
||||||
|
|
||||||
data.RetrackersMode = Number($('#RetrackersMode').val());
|
|
||||||
data.PeersListenPort = Number($('#PeersListenPort').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));
|
|
||||||
$('#TorrentDisconnectTimeout').val(data.TorrentDisconnectTimeout);
|
|
||||||
|
|
||||||
$('#EnableIPv6').prop('checked', data.EnableIPv6);
|
|
||||||
$('#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);
|
|
||||||
$('#DhtConnectionLimit').val(data.DhtConnectionLimit);
|
|
||||||
|
|
||||||
$('#DownloadRateLimit').val(data.DownloadRateLimit);
|
|
||||||
$('#UploadRateLimit').val(data.UploadRateLimit);
|
|
||||||
|
|
||||||
$('#RetrackersMode').val(data.RetrackersMode);
|
|
||||||
$('#PeersListenPort').val(data.PeersListenPort);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
refreshSettings();
|
|
||||||
navigator.registerProtocolHandler("magnet",
|
|
||||||
"http://localhost:8090/torrent/play?link=%s&save=true&stat=true",
|
|
||||||
"TorrServer");
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on("wheel", "input[type=number]", function (e) {
|
|
||||||
$(this).blur();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
func (t *Template) parseSettingsPage() {
|
|
||||||
parsePage(t, "settingsPage", settingsPage)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user