mirror of https://github.com/stashapp/stash.git
354 lines
7.8 KiB
Go
354 lines
7.8 KiB
Go
package dlna
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/txn"
|
|
)
|
|
|
|
type Repository struct {
|
|
TxnManager models.TxnManager
|
|
|
|
SceneFinder SceneFinder
|
|
FileGetter models.FileGetter
|
|
StudioFinder StudioFinder
|
|
TagFinder TagFinder
|
|
PerformerFinder PerformerFinder
|
|
GroupFinder GroupFinder
|
|
}
|
|
|
|
func NewRepository(repo models.Repository) Repository {
|
|
return Repository{
|
|
TxnManager: repo.TxnManager,
|
|
FileGetter: repo.File,
|
|
SceneFinder: repo.Scene,
|
|
StudioFinder: repo.Studio,
|
|
TagFinder: repo.Tag,
|
|
PerformerFinder: repo.Performer,
|
|
GroupFinder: repo.Group,
|
|
}
|
|
}
|
|
|
|
func (r *Repository) WithReadTxn(ctx context.Context, fn txn.TxnFunc) error {
|
|
return txn.WithReadTxn(ctx, r.TxnManager, fn)
|
|
}
|
|
|
|
type Status struct {
|
|
Running bool `json:"running"`
|
|
// If not currently running, time until it will be started. If running, time until it will be stopped
|
|
Until *time.Time `json:"until"`
|
|
RecentIPAddresses []string `json:"recentIPAddresses"`
|
|
AllowedIPAddresses []*Dlnaip `json:"allowedIPAddresses"`
|
|
}
|
|
|
|
type Dlnaip struct {
|
|
IPAddress string `json:"ipAddress"`
|
|
// Time until IP will be no longer allowed/disallowed
|
|
Until *time.Time `json:"until"`
|
|
}
|
|
|
|
type dmsConfig struct {
|
|
Path string
|
|
IfNames []string
|
|
Http string
|
|
FriendlyName string
|
|
LogHeaders bool
|
|
StallEventSubscribe bool
|
|
NotifyInterval time.Duration
|
|
VideoSortOrder string
|
|
}
|
|
|
|
type sceneServer interface {
|
|
StreamSceneDirect(scene *models.Scene, w http.ResponseWriter, r *http.Request)
|
|
ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request)
|
|
}
|
|
|
|
type Config interface {
|
|
GetDLNAInterfaces() []string
|
|
GetDLNAServerName() string
|
|
GetDLNADefaultIPWhitelist() []string
|
|
GetVideoSortOrder() string
|
|
GetDLNAPortAsString() string
|
|
}
|
|
|
|
type Service struct {
|
|
repository Repository
|
|
config Config
|
|
sceneServer sceneServer
|
|
ipWhitelistMgr *ipWhitelistManager
|
|
|
|
server *Server
|
|
running bool
|
|
mutex sync.Mutex
|
|
|
|
startTimer *time.Timer
|
|
startTime *time.Time
|
|
stopTimer *time.Timer
|
|
stopTime *time.Time
|
|
}
|
|
|
|
func (s *Service) getInterfaces() ([]net.Interface, error) {
|
|
var ifs []net.Interface
|
|
var err error
|
|
ifNames := s.config.GetDLNAInterfaces()
|
|
|
|
if len(ifNames) == 0 {
|
|
ifs, err = net.Interfaces()
|
|
} else {
|
|
for _, n := range ifNames {
|
|
if_, err := net.InterfaceByName(n)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting interface for name %s: %s", n, err.Error())
|
|
}
|
|
|
|
if if_ != nil {
|
|
ifs = append(ifs, *if_)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var tmp []net.Interface
|
|
for _, if_ := range ifs {
|
|
if if_.Flags&net.FlagUp == 0 || if_.MTU <= 0 {
|
|
continue
|
|
}
|
|
tmp = append(tmp, if_)
|
|
}
|
|
ifs = tmp
|
|
return ifs, nil
|
|
}
|
|
|
|
func (s *Service) init() error {
|
|
friendlyName := s.config.GetDLNAServerName()
|
|
if friendlyName == "" {
|
|
friendlyName = "stash"
|
|
}
|
|
|
|
var dmsConfig = &dmsConfig{
|
|
Path: "",
|
|
IfNames: s.config.GetDLNADefaultIPWhitelist(),
|
|
Http: s.config.GetDLNAPortAsString(),
|
|
FriendlyName: friendlyName,
|
|
LogHeaders: false,
|
|
NotifyInterval: 30 * time.Second,
|
|
VideoSortOrder: s.config.GetVideoSortOrder(),
|
|
}
|
|
|
|
interfaces, err := s.getInterfaces()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.server = &Server{
|
|
repository: s.repository,
|
|
sceneServer: s.sceneServer,
|
|
ipWhitelistManager: s.ipWhitelistMgr,
|
|
Interfaces: interfaces,
|
|
HTTPConn: func() net.Listener {
|
|
conn, err := net.Listen("tcp", dmsConfig.Http)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
return conn
|
|
}(),
|
|
FriendlyName: dmsConfig.FriendlyName,
|
|
RootObjectPath: filepath.Clean(dmsConfig.Path),
|
|
LogHeaders: dmsConfig.LogHeaders,
|
|
// Icons: []Icon{
|
|
// {
|
|
// Width: 48,
|
|
// Height: 48,
|
|
// Depth: 8,
|
|
// Mimetype: "image/png",
|
|
// //ReadSeeker: readIcon(config.Config.Interfaces.DLNA.ServiceImage, 48),
|
|
// },
|
|
// {
|
|
// Width: 128,
|
|
// Height: 128,
|
|
// Depth: 8,
|
|
// Mimetype: "image/png",
|
|
// //ReadSeeker: readIcon(config.Config.Interfaces.DLNA.ServiceImage, 128),
|
|
// },
|
|
// },
|
|
StallEventSubscribe: dmsConfig.StallEventSubscribe,
|
|
NotifyInterval: dmsConfig.NotifyInterval,
|
|
VideoSortOrder: dmsConfig.VideoSortOrder,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// func getIconReader(fn string) (io.Reader, error) {
|
|
// b, err := assets.ReadFile("dlna/" + fn + ".png")
|
|
// return bytes.NewReader(b), err
|
|
// }
|
|
|
|
// func readIcon(path string, size uint) *bytes.Reader {
|
|
// r, err := getIconReader(path)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// imageData, _, err := image.Decode(r)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// return resizeImage(imageData, size)
|
|
// }
|
|
|
|
// func resizeImage(imageData image.Image, size uint) *bytes.Reader {
|
|
// img := resize.Resize(size, size, imageData, resize.Lanczos3)
|
|
// var buff bytes.Buffer
|
|
// png.Encode(&buff, img)
|
|
// return bytes.NewReader(buff.Bytes())
|
|
// }
|
|
|
|
// NewService initialises and returns a new DLNA service.
|
|
func NewService(repo Repository, cfg Config, sceneServer sceneServer) *Service {
|
|
ret := &Service{
|
|
repository: repo,
|
|
sceneServer: sceneServer,
|
|
config: cfg,
|
|
ipWhitelistMgr: &ipWhitelistManager{
|
|
config: cfg,
|
|
},
|
|
mutex: sync.Mutex{},
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Start starts the DLNA service. If duration is provided, then the service
|
|
// is stopped after the duration has elapsed.
|
|
func (s *Service) Start(duration *time.Duration) error {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if !s.running {
|
|
if err := s.init(); err != nil {
|
|
logger.Error(err)
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
logger.Info("Starting DLNA " + s.server.HTTPConn.Addr().String())
|
|
if err := s.server.Serve(); err != nil {
|
|
logger.Error(err)
|
|
}
|
|
}()
|
|
s.running = true
|
|
|
|
if s.startTimer != nil {
|
|
s.startTimer.Stop()
|
|
s.startTimer = nil
|
|
s.startTime = nil
|
|
}
|
|
}
|
|
|
|
if duration != nil {
|
|
// clear the existing stop timer
|
|
if s.stopTimer != nil {
|
|
s.stopTimer.Stop()
|
|
s.stopTime = nil
|
|
}
|
|
|
|
if s.stopTimer == nil {
|
|
s.stopTimer = time.AfterFunc(*duration, func() {
|
|
s.Stop(nil)
|
|
})
|
|
t := time.Now().Add(*duration)
|
|
s.stopTime = &t
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the DLNA service. If duration is provided, then the service
|
|
// is started after the duration has elapsed.
|
|
func (s *Service) Stop(duration *time.Duration) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.running {
|
|
logger.Info("Stopping DLNA")
|
|
err := s.server.Close()
|
|
if err != nil {
|
|
logger.Error(err)
|
|
}
|
|
s.running = false
|
|
|
|
if s.stopTimer != nil {
|
|
s.stopTimer.Stop()
|
|
s.stopTimer = nil
|
|
s.stopTime = nil
|
|
}
|
|
}
|
|
|
|
if duration != nil {
|
|
// clear the existing stop timer
|
|
if s.startTimer != nil {
|
|
s.startTimer.Stop()
|
|
}
|
|
|
|
if s.startTimer == nil {
|
|
s.startTimer = time.AfterFunc(*duration, func() {
|
|
if err := s.Start(nil); err != nil {
|
|
logger.Warnf("error restarting DLNA server: %v", err)
|
|
}
|
|
})
|
|
t := time.Now().Add(*duration)
|
|
s.startTime = &t
|
|
}
|
|
}
|
|
}
|
|
|
|
// IsRunning returns true if the DLNA service is running.
|
|
func (s *Service) IsRunning() bool {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
return s.running
|
|
}
|
|
|
|
func (s *Service) Status() *Status {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
ret := &Status{
|
|
Running: s.running,
|
|
RecentIPAddresses: s.ipWhitelistMgr.getRecent(),
|
|
AllowedIPAddresses: s.ipWhitelistMgr.getTempAllowed(),
|
|
}
|
|
|
|
if s.startTime != nil {
|
|
t := *s.startTime
|
|
ret.Until = &t
|
|
}
|
|
|
|
if s.stopTime != nil {
|
|
t := *s.stopTime
|
|
ret.Until = &t
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (s *Service) AddTempDLNAIP(pattern string, duration *time.Duration) {
|
|
s.ipWhitelistMgr.allowPattern(pattern, duration)
|
|
}
|
|
|
|
func (s *Service) RemoveTempDLNAIP(pattern string) bool {
|
|
return s.ipWhitelistMgr.removePattern(pattern)
|
|
}
|