diff --git a/server/dlna/dlna.go b/server/dlna/dlna.go new file mode 100644 index 0000000..9002eba --- /dev/null +++ b/server/dlna/dlna.go @@ -0,0 +1,133 @@ +package dlna + +import ( + "bytes" + "fmt" + "net" + "os" + "os/user" + "path/filepath" + "time" + + "github.com/anacrolix/dms/dlna/dms" + + "server/log" + "server/web/pages/template" +) + +var dmsServer *dms.Server + +func Start() { + dmsServer = &dms.Server{ + Interfaces: func() (ifs []net.Interface) { + var err error + ifs, err = net.Interfaces() + if err != nil { + log.TLogln(err) + os.Exit(1) + } + return + }(), + HTTPConn: func() net.Listener { + conn, err := net.Listen("tcp", ":9080") + if err != nil { + log.TLogln(err) + os.Exit(1) + } + return conn + }(), + FriendlyName: getDefaultFriendlyName(), + NoTranscode: true, + NoProbe: true, + Icons: []dms.Icon{ + dms.Icon{ + Width: 192, + Height: 192, + Depth: 32, + Mimetype: "image/png", + ReadSeeker: bytes.NewReader(template.Androidchrome192x192png), + }, + dms.Icon{ + Width: 32, + Height: 32, + Depth: 32, + Mimetype: "image/png", + ReadSeeker: bytes.NewReader(template.Favicon32x32png), + }, + }, + NotifyInterval: 30 * time.Second, + AllowedIpNets: func() []*net.IPNet { + var nets []*net.IPNet + _, ipnet, _ := net.ParseCIDR("0.0.0.0/0") + nets = append(nets, ipnet) + _, ipnet, _ = net.ParseCIDR("::/0") + nets = append(nets, ipnet) + return nets + }(), + OnBrowseDirectChildren: onBrowse, + OnBrowseMetadata: onBrowseMeta, + } + + if err := dmsServer.Init(); err != nil { + log.TLogln("error initing dms server: %v", err) + os.Exit(1) + } + go func() { + if err := dmsServer.Run(); err != nil { + log.TLogln(err) + os.Exit(1) + } + }() +} + +func Stop() { + if dmsServer != nil { + dmsServer.Close() + dmsServer = nil + } +} + +func onBrowse(path, rootObjectPath, host, userAgent string) (ret []interface{}, err error) { + if path == "/" { + ret = getTorrents() + return + } else if isHashPath(path) { + ret = getTorrent(path, host) + return + } else if filepath.Base(path) == "Load Torrent" { + ret = loadTorrent(path, host) + } + return +} + +func onBrowseMeta(path string, rootObjectPath string, host, userAgent string) (ret interface{}, err error) { + err = fmt.Errorf("not implemented") + return +} + +func getDefaultFriendlyName() string { + ret := "TorrServer" + userName := "" + user, err := user.Current() + if err != nil { + log.TLogln("getDefaultFriendlyName could not get username: %s", err) + } else { + userName = user.Name + } + host, err := os.Hostname() + if err != nil { + log.TLogln("getDefaultFriendlyName could not get hostname: %s", err) + } + + if userName == "" && host == "" { + return ret + } + + if userName != "" && host != "" { + if userName == host { + return ret + ": " + userName + } + return ret + ": " + userName + " on " + host + } + return ret + ": " + userName + host +} diff --git a/server/dlna/list.go b/server/dlna/list.go new file mode 100644 index 0000000..aaf1020 --- /dev/null +++ b/server/dlna/list.go @@ -0,0 +1,150 @@ +package dlna + +import ( + "fmt" + "net/url" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/anacrolix/dms/dlna" + "github.com/anacrolix/dms/dlna/dms" + "github.com/anacrolix/dms/upnpav" + + "server/log" + "server/settings" + "server/torr" + "server/torr/state" +) + +func getTorrents() (ret []interface{}) { + torrs := torr.ListTorrent() + for _, t := range torrs { + obj := upnpav.Object{ + ID: "%2F" + t.TorrentSpec.InfoHash.HexString(), + Restricted: 1, + ParentID: "0", + Class: "object.container.storageFolder", + Title: t.Title, + Icon: t.Poster, + AlbumArtURI: t.Poster, + } + cnt := upnpav.Container{Object: obj} + ret = append(ret, cnt) + } + return +} + +func getTorrent(path, host string) (ret []interface{}) { + // find torrent without load + torrs := torr.ListTorrent() + var torr *torr.Torrent + for _, t := range torrs { + if strings.Contains(path, t.TorrentSpec.InfoHash.HexString()) { + torr = t + break + } + } + if torr == nil { + return nil + } + parent := "%2F" + torr.TorrentSpec.InfoHash.HexString() + // get content from torrent + + // if torrent not loaded, get button for load + if torr.Files() == nil { + obj := upnpav.Object{ + ID: parent + "%2FLoad Torrent", + Restricted: 1, + ParentID: parent, + Class: "object.container.storageFolder", + Title: "Load Torrent", + } + cnt := upnpav.Container{Object: obj} + ret = append(ret, cnt) + return + } + + ret = loadTorrent(path, host) + return +} + +func loadTorrent(path, host string) (ret []interface{}) { + hash := filepath.Base(filepath.Dir(path)) + if hash == "/" { + hash = filepath.Base(path) + } + if len(hash) != 40 { + return + } + + tor := torr.GetTorrent(hash) + if tor == nil { + log.TLogln("Dlna error get info from torrent", hash) + return + } + if len(tor.Files()) == 0 { + time.Sleep(time.Millisecond * 200) + timeout := time.Now().Add(time.Second * 60) + for { + tor = torr.GetTorrent(hash) + if len(tor.Files()) > 0 { + break + } + time.Sleep(time.Millisecond * 200) + if time.Now().After(timeout) { + return + } + } + } + parent := "%2F" + tor.TorrentSpec.InfoHash.HexString() + files := tor.Status().FileStats + for _, f := range files { + obj := getObjFromTorrent(path, parent, host, tor, f) + if obj != nil { + ret = append(ret, obj) + } + } + return +} + +func getLink(host, path string) string { + if !strings.HasPrefix(host, "http") { + host = "http://" + host + } + pos := strings.LastIndex(host, ":") + if pos > 7 { + host = host[:pos] + } + return host + ":" + settings.Port + "/" + path +} + +func getObjFromTorrent(path, parent, host string, torr *torr.Torrent, file *state.TorrentFileStat) (ret interface{}) { + mime, err := dms.MimeTypeByPath(file.Path) + if err != nil { + return + } + + obj := upnpav.Object{ + ID: parent + "%2" + file.Path, + Restricted: 1, + ParentID: parent, + Class: "object.item." + mime.Type() + "Item", + Title: file.Path, + } + + item := upnpav.Item{ + Object: obj, + Res: make([]upnpav.Resource, 0, 1), + } + pathPlay := "stream/" + url.PathEscape(file.Path) + "?link=" + torr.TorrentSpec.InfoHash.HexString() + "&play&index=" + strconv.Itoa(file.Id) + item.Res = append(item.Res, upnpav.Resource{ + URL: getLink(host, pathPlay), + ProtocolInfo: fmt.Sprintf("http-get:*:%s:%s", mime, dlna.ContentFeatures{ + SupportRange: true, + }.String()), + Size: uint64(file.Length), + }) + return item +} diff --git a/server/dlna/utils.go b/server/dlna/utils.go new file mode 100644 index 0000000..decf282 --- /dev/null +++ b/server/dlna/utils.go @@ -0,0 +1,19 @@ +package dlna + +import ( + "path/filepath" +) + +func isHashPath(path string) bool { + base := filepath.Base(path) + if len(base) == 40 { + data := []byte(base) + for _, v := range data { + if !(v >= 48 && v <= 57 || v >= 65 && v <= 70 || v >= 97 && v <= 102) { + return false + } + } + return true + } + return false +} diff --git a/server/go.mod b/server/go.mod index 7804872..180b839 100644 --- a/server/go.mod +++ b/server/go.mod @@ -2,12 +2,16 @@ module server go 1.17 -replace github.com/anacrolix/torrent v1.30.3 => github.com/tsynik/torrent v1.2.7-0.20210825105145-c21425452ac8 +replace ( + github.com/anacrolix/dms v1.2.2 => github.com/yourok/dms v0.0.0-20210726184814-0838f1936b67 + github.com/anacrolix/torrent v1.30.3 => github.com/tsynik/torrent v1.2.7-0.20210825105145-c21425452ac8 +) exclude github.com/willf/bitset v1.2.0 require ( github.com/alexflint/go-arg v1.3.0 + github.com/anacrolix/dms v1.2.2 github.com/anacrolix/missinggo v1.3.0 github.com/anacrolix/torrent v1.30.3 github.com/gin-contrib/cors v1.3.1 @@ -25,6 +29,7 @@ require ( github.com/anacrolix/chansync v0.1.0 // indirect github.com/anacrolix/confluence v1.8.0 // indirect github.com/anacrolix/dht/v2 v2.10.3 // indirect + github.com/anacrolix/ffprobe v1.0.0 // indirect github.com/anacrolix/log v0.9.0 // indirect github.com/anacrolix/missinggo/perf v1.0.0 // indirect github.com/anacrolix/missinggo/v2 v2.5.2 // indirect diff --git a/server/go.sum b/server/go.sum index a303f67..5cfd69d 100644 --- a/server/go.sum +++ b/server/go.sum @@ -66,6 +66,8 @@ github.com/anacrolix/envpprof v1.0.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAK github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.1.1 h1:sHQCyj7HtiSfaZAzL2rJrQdyS7odLqlwO6nhk/tG/j8= github.com/anacrolix/envpprof v1.1.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= +github.com/anacrolix/ffprobe v1.0.0 h1:j8fGLBsXejwdXd0pkA9iR3Dt1XwMFv5wjeYWObcue8A= +github.com/anacrolix/ffprobe v1.0.0/go.mod h1:BIw+Bjol6CWjm/CRWrVLk2Vy+UYlkgmBZ05vpSYqZPw= github.com/anacrolix/go-libutp v0.0.0-20180522111405-6baeb806518d/go.mod h1:beQSaSxwH2d9Eeu5ijrEnHei5Qhk+J6cDm1QkWFru4E= github.com/anacrolix/go-libutp v1.0.2/go.mod h1:uIH0A72V++j0D1nnmTjjZUiH/ujPkFxYWkxQ02+7S0U= github.com/anacrolix/go-libutp v1.0.4/go.mod h1:8vSGX5g0b4eebsDBNVQHUXSCwYaN18Lnkse0hUW8/5w= @@ -457,6 +459,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -677,6 +680,8 @@ github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNT github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA= github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yourok/dms v0.0.0-20210726184814-0838f1936b67 h1:so0N7cyMaBB3Qauspq9MabJJofSy7VjwfrB6rK3ReD8= +github.com/yourok/dms v0.0.0-20210726184814-0838f1936b67/go.mod h1:1GqMUla/yTV3GFjpKMpmdntkTl6aslGK3jfIksEwIdI= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -766,6 +771,7 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= @@ -835,6 +841,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/server/server.go b/server/server.go index 9a4b20d..b9ebab8 100644 --- a/server/server.go +++ b/server/server.go @@ -25,8 +25,9 @@ func Start(port string, roSets bool) { log.TLogln("Port", port, "already in use! Abort") os.Exit(1) } else { - go cleanCache() - web.Start(port) + go cleanCache() + settings.Port = port + web.Start(port) } } diff --git a/server/settings/btsets.go b/server/settings/btsets.go index 1ea76c5..49a3f08 100644 --- a/server/settings/btsets.go +++ b/server/settings/btsets.go @@ -27,6 +27,9 @@ type BTSets struct { TorrentDisconnectTimeout int // in seconds EnableDebug bool // print logs + // DLNA + EnableDLNA bool + // BT Config EnableIPv6 bool DisableTCP bool @@ -38,8 +41,7 @@ type BTSets struct { DownloadRateLimit int // in kb, 0 - inf UploadRateLimit int // in kb, 0 - inf ConnectionsLimit int - DhtConnectionLimit int // 0 - inf - PeersListenPort int + PeersListenPort int } func (v *BTSets) String() string { @@ -123,7 +125,6 @@ func SetDefault() { sets.CacheSize = 96 * 1024 * 1024 // 96 MB sets.PreloadCache = 0 sets.ConnectionsLimit = 23 - sets.DhtConnectionLimit = 500 sets.RetrackersMode = 1 sets.TorrentDisconnectTimeout = 30 sets.ReaderReadAHead = 95 // 95% preload diff --git a/server/settings/settings.go b/server/settings/settings.go index bdff98a..a08693a 100644 --- a/server/settings/settings.go +++ b/server/settings/settings.go @@ -10,6 +10,7 @@ import ( var ( tdb *TDB Path string + Port string ReadOnly bool HttpAuth bool ) diff --git a/server/torr/btserver.go b/server/torr/btserver.go index e205901..5ebc76e 100644 --- a/server/torr/btserver.go +++ b/server/torr/btserver.go @@ -85,9 +85,6 @@ func (bt *BTServer) configure() { ForceEncryption: settings.BTsets.ForceEncrypt, } - 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) } diff --git a/server/web/api/settings.go b/server/web/api/settings.go index 0269dcc..afdeb30 100644 --- a/server/web/api/settings.go +++ b/server/web/api/settings.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "github.com/pkg/errors" + "server/dlna" sets "server/settings" "server/torr" @@ -29,10 +30,15 @@ func settings(c *gin.Context) { return } else if req.Action == "set" { torr.SetSettings(req.Sets) + dlna.Stop() + if req.Sets.EnableDLNA { + dlna.Start() + } c.Status(200) return } else if req.Action == "def" { torr.SetDefSettings() + dlna.Stop() c.Status(200) return } diff --git a/server/web/pages/template/html.go b/server/web/pages/template/html.go index 5849dc6..5ef4eac 100644 --- a/server/web/pages/template/html.go +++ b/server/web/pages/template/html.go @@ -49,24 +49,24 @@ var Mstile150x150png []byte var Sitewebmanifest []byte -//go:embed pages/static/js/2.4daf62d1.chunk.js -var Staticjs24daf62d1chunkjs []byte +//go:embed pages/static/js/2.ed783265.chunk.js +var Staticjs2ed783265chunkjs []byte -//go:embed pages/static/js/2.4daf62d1.chunk.js.LICENSE.txt -var Staticjs24daf62d1chunkjsLICENSEtxt []byte +//go:embed pages/static/js/2.ed783265.chunk.js.LICENSE.txt +var Staticjs2ed783265chunkjsLICENSEtxt []byte -//go:embed pages/static/js/2.4daf62d1.chunk.js.map -var Staticjs24daf62d1chunkjsmap []byte +//go:embed pages/static/js/2.ed783265.chunk.js.map +var Staticjs2ed783265chunkjsmap []byte -//go:embed pages/static/js/main.ed4c2969.chunk.js -var Staticjsmained4c2969chunkjs []byte +//go:embed pages/static/js/main.0baba7bd.chunk.js +var Staticjsmain0baba7bdchunkjs []byte -//go:embed pages/static/js/main.ed4c2969.chunk.js.map -var Staticjsmained4c2969chunkjsmap []byte +//go:embed pages/static/js/main.0baba7bd.chunk.js.map +var Staticjsmain0baba7bdchunkjsmap []byte //go:embed pages/static/js/runtime-main.8bda5920.js diff --git a/server/web/pages/template/pages/asset-manifest.json b/server/web/pages/template/pages/asset-manifest.json index bb96b65..a54f5b9 100644 --- a/server/web/pages/template/pages/asset-manifest.json +++ b/server/web/pages/template/pages/asset-manifest.json @@ -1,17 +1,17 @@ { "files": { - "main.js": "/static/js/main.ed4c2969.chunk.js", - "main.js.map": "/static/js/main.ed4c2969.chunk.js.map", + "main.js": "/static/js/main.0baba7bd.chunk.js", + "main.js.map": "/static/js/main.0baba7bd.chunk.js.map", "runtime-main.js": "/static/js/runtime-main.8bda5920.js", "runtime-main.js.map": "/static/js/runtime-main.8bda5920.js.map", - "static/js/2.4daf62d1.chunk.js": "/static/js/2.4daf62d1.chunk.js", - "static/js/2.4daf62d1.chunk.js.map": "/static/js/2.4daf62d1.chunk.js.map", + "static/js/2.ed783265.chunk.js": "/static/js/2.ed783265.chunk.js", + "static/js/2.ed783265.chunk.js.map": "/static/js/2.ed783265.chunk.js.map", "index.html": "/index.html", - "static/js/2.4daf62d1.chunk.js.LICENSE.txt": "/static/js/2.4daf62d1.chunk.js.LICENSE.txt" + "static/js/2.ed783265.chunk.js.LICENSE.txt": "/static/js/2.ed783265.chunk.js.LICENSE.txt" }, "entrypoints": [ "static/js/runtime-main.8bda5920.js", - "static/js/2.4daf62d1.chunk.js", - "static/js/main.ed4c2969.chunk.js" + "static/js/2.ed783265.chunk.js", + "static/js/main.0baba7bd.chunk.js" ] } \ No newline at end of file diff --git a/server/web/pages/template/pages/index.html b/server/web/pages/template/pages/index.html index 9c77fb9..bb32a8c 100644 --- a/server/web/pages/template/pages/index.html +++ b/server/web/pages/template/pages/index.html @@ -1 +1 @@ -