init 1.2.x

This commit is contained in:
YouROK
2020-11-06 15:40:58 +03:00
parent ce87ecabcb
commit a1e17b1cf3
57 changed files with 670 additions and 4003 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ pkg/
bin/ bin/
dist/ dist/
toolchains/ toolchains/
/src/crawshaw.io/
/src/go.etcd.io/

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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
View File

@@ -0,0 +1,9 @@
package log
import (
"log"
)
func TLogln(v ...interface{}) {
log.Println(v...)
}

26
src/server/server.go Normal file
View 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()
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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
View 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
}

View 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()
}

View 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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
View 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()
}
}

View File

@@ -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
View 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
}

View File

@@ -1 +0,0 @@
package filecache

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
} }

View File

@@ -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
} }
} }

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -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
View 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
}

View File

@@ -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
}

View File

@@ -0,0 +1,15 @@
package utils
import (
"runtime"
"runtime/debug"
)
func FreeOSMem() {
debug.FreeOSMemory()
}
func FreeOSMemGC() {
runtime.GC()
debug.FreeOSMemory()
}

View File

@@ -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 {

View File

@@ -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
//}

View File

@@ -1,4 +0,0 @@
package version
const Version = "1.1.77"
const VerInt = 77

View File

@@ -0,0 +1,4 @@
package version
const Version = "1.2.78"
const VerInt = 78

View File

@@ -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)
}

View File

@@ -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&emsp;# Nodes: %d (%d good, %d banned)<br>\n", dhtStats.Nodes, dhtStats.GoodNodes, dhtStats.BadNodes)
msg += fmt.Sprintf("\t&emsp;Server ID: %x<br>\n", dht.ID())
msg += fmt.Sprintf("\t&emsp;Announces: %d<br>\n", dhtStats.SuccessfulOutboundAnnouncePeerQueries)
msg += fmt.Sprintf("\t&emsp;Outstanding transactions: %d<br>\n", dhtStats.OutstandingTransactions)
}
sort.Slice(state.Torrents, func(i, j int) bool {
return state.Torrents[i].Hash().HexString() < state.Torrents[j].Hash().HexString()
})
msg += "Torrents:<br>\n"
for _, t := range state.Torrents {
st := t.Stats()
msg += fmt.Sprintf("Name: %v<br>\n", st.Name)
msg += fmt.Sprintf("Hash: %v<br>\n", st.Hash)
msg += fmt.Sprintf("Status: %v<br>\n", st.TorrentStatus)
msg += fmt.Sprintf("Loaded Size: %v<br>\n", bytes.Format(st.LoadedSize))
msg += fmt.Sprintf("Torrent Size: %v<br>\n<br>\n", bytes.Format(st.TorrentSize))
msg += fmt.Sprintf("Preloaded Bytes: %v<br>\n", bytes.Format(st.PreloadedBytes))
msg += fmt.Sprintf("Preload Size: %v<br>\n<br>\n", bytes.Format(st.PreloadSize))
msg += fmt.Sprintf("Download Speed: %v/Sec<br>\n", utils.Format(st.DownloadSpeed))
msg += fmt.Sprintf("Upload Speed: %v/Sec<br>\n<br>\n", utils.Format(st.UploadSpeed))
msg += fmt.Sprintf("\t&emsp;TotalPeers: %v<br>\n", st.TotalPeers)
msg += fmt.Sprintf("\t&emsp;PendingPeers: %v<br>\n", st.PendingPeers)
msg += fmt.Sprintf("\t&emsp;ActivePeers: %v<br>\n", st.ActivePeers)
msg += fmt.Sprintf("\t&emsp;ConnectedSeeders: %v<br>\n", st.ConnectedSeeders)
msg += fmt.Sprintf("\t&emsp;HalfOpenPeers: %v<br>\n", st.HalfOpenPeers)
msg += fmt.Sprintf("\t&emsp;BytesWritten: %v (%v)<br>\n", st.BytesWritten, bytes.Format(st.BytesWritten))
msg += fmt.Sprintf("\t&emsp;BytesWrittenData: %v (%v)<br>\n", st.BytesWrittenData, bytes.Format(st.BytesWrittenData))
msg += fmt.Sprintf("\t&emsp;BytesRead: %v (%v)<br>\n", st.BytesRead, bytes.Format(st.BytesRead))
msg += fmt.Sprintf("\t&emsp;BytesReadData: %v (%v)<br>\n", st.BytesReadData, bytes.Format(st.BytesReadData))
msg += fmt.Sprintf("\t&emsp;BytesReadUsefulData: %v (%v)<br>\n", st.BytesReadUsefulData, bytes.Format(st.BytesReadUsefulData))
msg += fmt.Sprintf("\t&emsp;ChunksWritten: %v<br>\n", st.ChunksWritten)
msg += fmt.Sprintf("\t&emsp;ChunksRead: %v<br>\n", st.ChunksRead)
msg += fmt.Sprintf("\t&emsp;ChunksReadUseful: %v<br>\n", st.ChunksReadUseful)
msg += fmt.Sprintf("\t&emsp;ChunksReadWasted: %v<br>\n", st.ChunksReadWasted)
msg += fmt.Sprintf("\t&emsp;PiecesDirtiedGood: %v<br>\n", st.PiecesDirtiedGood)
msg += fmt.Sprintf("\t&emsp;PiecesDirtiedBad: %v<br>\n<br>\n", st.PiecesDirtiedBad)
if len(st.FileStats) > 0 {
msg += fmt.Sprintf("\t&emsp;Files:<br>\n")
for _, f := range st.FileStats {
msg += fmt.Sprintf("\t&emsp;\t&emsp;%v Size:%v<br>\n", f.Path, bytes.Format(f.Length))
}
}
hash := metainfo.NewHashFromHex(st.Hash)
cState := bts.CacheState(hash)
if cState != nil {
msg += fmt.Sprintf("CacheType:<br>\n")
msg += fmt.Sprintf("Capacity: %v<br>\n", bytes.Format(cState.Capacity))
msg += fmt.Sprintf("Filled: %v<br>\n", bytes.Format(cState.Filled))
msg += fmt.Sprintf("PiecesLength: %v<br>\n", bytes.Format(cState.PiecesLength))
msg += fmt.Sprintf("PiecesCount: %v<br>\n", cState.PiecesCount)
for _, p := range cState.Pieces {
msg += fmt.Sprintf("\t&emsp;Piece: %v\t&emsp; Access: %v\t&emsp; Buffer size: %d(%s)\t&emsp; Complete: %v\t&emsp; 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)
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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]
}

View File

@@ -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>`)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}