From 3420fa44ba5365b86eb76a597061318929f1261f Mon Sep 17 00:00:00 2001
From: YouROK <8yourok8@mail.ru>
Date: Wed, 25 Aug 2021 22:18:14 +0300
Subject: [PATCH 1/3] add dlna
---
server/dlna/dlna.go | 133 ++++++++++++++++
server/dlna/list.go | 150 ++++++++++++++++++
server/dlna/utils.go | 19 +++
server/go.mod | 7 +-
server/go.sum | 7 +
server/server.go | 5 +-
server/settings/btsets.go | 7 +-
server/settings/settings.go | 1 +
server/web/api/settings.go | 6 +
server/web/server.go | 6 +
.../Settings/SecondarySettingsComponent.jsx | 17 +-
11 files changed, 341 insertions(+), 17 deletions(-)
create mode 100644 server/dlna/dlna.go
create mode 100644 server/dlna/list.go
create mode 100644 server/dlna/utils.go
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/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/server.go b/server/web/server.go
index 2e655c4..6aefd27 100644
--- a/server/web/server.go
+++ b/server/web/server.go
@@ -6,6 +6,8 @@ import (
"github.com/gin-contrib/cors"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
+ "server/dlna"
+ "server/settings"
"server/log"
"server/torr"
@@ -51,6 +53,9 @@ func Start(port string) {
api.SetupRoute(&route.RouterGroup)
pages.SetupRoute(&route.RouterGroup)
}
+ if settings.BTsets.EnableDLNA {
+ dlna.Start()
+ }
log.TLogln("Start web server at port", port)
waitChan <- route.Run(":" + port)
}
@@ -60,6 +65,7 @@ func Wait() error {
}
func Stop() {
+ dlna.Stop()
BTS.Disconnect()
waitChan <- nil
}
diff --git a/web/src/components/Settings/SecondarySettingsComponent.jsx b/web/src/components/Settings/SecondarySettingsComponent.jsx
index b226827..af55b8c 100644
--- a/web/src/components/Settings/SecondarySettingsComponent.jsx
+++ b/web/src/components/Settings/SecondarySettingsComponent.jsx
@@ -10,6 +10,7 @@ export default function SecondarySettingsComponent({ settings, inputForm }) {
const {
RetrackersMode,
TorrentDisconnectTimeout,
+ EnableDLNA,
EnableIPv6,
ForceEncrypt,
DisableTCP,
@@ -21,7 +22,6 @@ export default function SecondarySettingsComponent({ settings, inputForm }) {
DownloadRateLimit,
UploadRateLimit,
ConnectionsLimit,
- DhtConnectionLimit,
PeersListenPort,
} = settings || {}
@@ -85,16 +85,6 @@ export default function SecondarySettingsComponent({ settings, inputForm }) {
label={t('SettingsDialog.DHT')}
labelPlacement='start'
/>
-
+ }
+ label='DLNA'
+ labelPlacement='start'
+ />
{t('SettingsDialog.RetrackersMode')}