many updates
This commit is contained in:
50
src/data.go
50
src/data.go
@@ -23,6 +23,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
||||
var reloadData = false
|
||||
var cacheImages = false
|
||||
var createXEPGFiles = false
|
||||
var triggerPlexGuideReload = false
|
||||
var debug string
|
||||
|
||||
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
|
||||
@@ -36,6 +37,21 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
||||
case "tuner":
|
||||
showWarning(2105)
|
||||
|
||||
case "use_plexAPI":
|
||||
triggerPlexGuideReload = true
|
||||
|
||||
case "plex.url":
|
||||
if v, ok := value.(string); ok {
|
||||
value = strings.TrimRight(strings.TrimSpace(v), "/")
|
||||
}
|
||||
triggerPlexGuideReload = true
|
||||
|
||||
case "plex.token":
|
||||
if v, ok := value.(string); ok {
|
||||
value = strings.TrimSpace(v)
|
||||
}
|
||||
triggerPlexGuideReload = true
|
||||
|
||||
case "epgSource":
|
||||
reloadData = true
|
||||
|
||||
@@ -119,22 +135,26 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
||||
|
||||
oldSettings[key] = value
|
||||
|
||||
switch fmt.Sprintf("%T", value) {
|
||||
if key == "plex.token" {
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: ******** (%T)", key, value)
|
||||
} else {
|
||||
switch fmt.Sprintf("%T", value) {
|
||||
|
||||
case "bool":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value)
|
||||
case "bool":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value)
|
||||
|
||||
case "string":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value)
|
||||
case "string":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value)
|
||||
|
||||
case "[]interface {}":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value)
|
||||
case "[]interface {}":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value)
|
||||
|
||||
case "float64":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value)
|
||||
case "float64":
|
||||
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value)
|
||||
|
||||
default:
|
||||
debug = fmt.Sprintf("%T", value)
|
||||
default:
|
||||
debug = fmt.Sprintf("%T", value)
|
||||
}
|
||||
}
|
||||
|
||||
showDebug(debug, 1)
|
||||
@@ -250,6 +270,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
|
||||
|
||||
}
|
||||
|
||||
if triggerPlexGuideReload == true {
|
||||
queuePlexGuideRefresh("settings change")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
@@ -980,6 +1004,10 @@ func buildDatabaseDVR() (err error) {
|
||||
sort.Strings(Data.StreamPreviewUI.Active)
|
||||
sort.Strings(Data.StreamPreviewUI.Inactive)
|
||||
|
||||
if Settings.EpgSource != "XEPG" {
|
||||
queuePlexGuideRefresh("lineup update")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
363
src/plex_api.go
Normal file
363
src/plex_api.go
Normal file
@@ -0,0 +1,363 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var plexRefreshState = struct {
|
||||
sync.Mutex
|
||||
lastRun time.Time
|
||||
scheduled bool
|
||||
inProgress bool
|
||||
queued bool
|
||||
reason string
|
||||
missingConfigLogged bool
|
||||
}{}
|
||||
|
||||
// queuePlexGuideRefresh schedules a debounced Plex DVR guide refresh.
|
||||
func queuePlexGuideRefresh(reason string) {
|
||||
|
||||
if Settings.UsePlexAPI == false {
|
||||
return
|
||||
}
|
||||
|
||||
plexRefreshState.Lock()
|
||||
|
||||
if len(strings.TrimSpace(reason)) > 0 {
|
||||
plexRefreshState.reason = reason
|
||||
}
|
||||
|
||||
if plexRefreshState.scheduled == true || plexRefreshState.inProgress == true {
|
||||
plexRefreshState.queued = true
|
||||
plexRefreshState.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
plexRefreshState.scheduled = true
|
||||
delay := plexRefreshDelayLocked()
|
||||
plexRefreshState.Unlock()
|
||||
|
||||
go runPlexGuideRefresh(delay)
|
||||
}
|
||||
|
||||
func runPlexGuideRefresh(initialDelay time.Duration) {
|
||||
|
||||
if initialDelay > 0 {
|
||||
time.Sleep(initialDelay)
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
plexRefreshState.Lock()
|
||||
reason := strings.TrimSpace(plexRefreshState.reason)
|
||||
if len(reason) == 0 {
|
||||
reason = "update"
|
||||
}
|
||||
plexRefreshState.scheduled = false
|
||||
plexRefreshState.inProgress = true
|
||||
plexRefreshState.queued = false
|
||||
plexRefreshState.Unlock()
|
||||
|
||||
err := refreshPlexGuide(reason)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
plexRefreshState.Lock()
|
||||
plexRefreshState.lastRun = time.Now()
|
||||
runAgain := plexRefreshState.queued
|
||||
plexRefreshState.inProgress = false
|
||||
nextDelay := time.Duration(0)
|
||||
|
||||
if runAgain == true && Settings.UsePlexAPI == true {
|
||||
plexRefreshState.scheduled = true
|
||||
nextDelay = plexRefreshDelayLocked()
|
||||
}
|
||||
plexRefreshState.Unlock()
|
||||
|
||||
if runAgain == false || Settings.UsePlexAPI == false {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(nextDelay)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func plexRefreshDelayLocked() (delay time.Duration) {
|
||||
|
||||
const minDelay = 2 * time.Second
|
||||
const cooldown = 20 * time.Second
|
||||
|
||||
if plexRefreshState.lastRun.IsZero() == true {
|
||||
return minDelay
|
||||
}
|
||||
|
||||
since := time.Since(plexRefreshState.lastRun)
|
||||
if since >= cooldown {
|
||||
return minDelay
|
||||
}
|
||||
|
||||
return cooldown - since
|
||||
}
|
||||
|
||||
func refreshPlexGuide(reason string) (err error) {
|
||||
|
||||
baseURL, token, ready := getPlexConfig()
|
||||
if ready == false {
|
||||
return nil
|
||||
}
|
||||
|
||||
dvrs, err := discoverPlexDVRs(baseURL, token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var refreshed int
|
||||
var failed int
|
||||
|
||||
for _, dvr := range dvrs {
|
||||
|
||||
endpoints := getPlexReloadEndpoints(dvr)
|
||||
if len(endpoints) == 0 {
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
var endpointErr error
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
status, _, requestErr := doPlexRequest("POST", baseURL, endpoint, token)
|
||||
if requestErr != nil {
|
||||
endpointErr = requestErr
|
||||
continue
|
||||
}
|
||||
|
||||
if status >= 200 && status < 300 {
|
||||
refreshed++
|
||||
endpointErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
endpointErr = fmt.Errorf("Plex API returned HTTP %d for %s", status, endpoint)
|
||||
}
|
||||
|
||||
if endpointErr != nil {
|
||||
failed++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if refreshed == 0 {
|
||||
if failed == 0 {
|
||||
return errors.New("Plex API guide refresh failed")
|
||||
}
|
||||
|
||||
return fmt.Errorf("Plex API guide refresh failed for %d DVR(s)", failed)
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("Plex API:Guide reload requested for %d DVR(s) (%s)", refreshed, reason))
|
||||
if failed > 0 {
|
||||
showInfo(fmt.Sprintf("Plex API:Guide reload failed for %d DVR(s)", failed))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPlexConfig() (baseURL, token string, ready bool) {
|
||||
|
||||
baseURL = strings.TrimSpace(Settings.PlexURL)
|
||||
token = strings.TrimSpace(Settings.PlexToken)
|
||||
|
||||
if strings.HasPrefix(baseURL, "http://") == false && strings.HasPrefix(baseURL, "https://") == false && len(baseURL) > 0 {
|
||||
baseURL = "http://" + baseURL
|
||||
}
|
||||
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
|
||||
plexRefreshState.Lock()
|
||||
defer plexRefreshState.Unlock()
|
||||
|
||||
if len(baseURL) == 0 || len(token) == 0 {
|
||||
|
||||
if plexRefreshState.missingConfigLogged == false {
|
||||
showInfo("Plex API:Skipped refresh because plex.url or plex.token is empty")
|
||||
plexRefreshState.missingConfigLogged = true
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
plexRefreshState.missingConfigLogged = false
|
||||
|
||||
return baseURL, token, true
|
||||
}
|
||||
|
||||
func discoverPlexDVRs(baseURL, token string) (dvrs []map[string]interface{}, err error) {
|
||||
|
||||
status, body, err := doPlexRequest("GET", baseURL, "/livetv/dvrs", token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if status < 200 || status >= 300 {
|
||||
err = fmt.Errorf("Plex API returned HTTP %d for /livetv/dvrs", status)
|
||||
return
|
||||
}
|
||||
|
||||
var payload = make(map[string]interface{})
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mediaContainer, ok := payload["MediaContainer"].(map[string]interface{})
|
||||
if ok == false {
|
||||
err = errors.New("Plex API response missing MediaContainer")
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range []string{"Dvr", "DVR", "Directory", "Metadata"} {
|
||||
if raw, found := mediaContainer[key]; found == true {
|
||||
if list, ok := raw.([]interface{}); ok == true {
|
||||
dvrs = convertToPlexMapSlice(list)
|
||||
if len(dvrs) > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = errors.New("Plex API returned no DVR entries")
|
||||
return
|
||||
}
|
||||
|
||||
func convertToPlexMapSlice(list []interface{}) (dvrs []map[string]interface{}) {
|
||||
|
||||
dvrs = make([]map[string]interface{}, 0, len(list))
|
||||
|
||||
for _, item := range list {
|
||||
if dvr, ok := item.(map[string]interface{}); ok == true {
|
||||
dvrs = append(dvrs, dvr)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPlexReloadEndpoints(dvr map[string]interface{}) (endpoints []string) {
|
||||
|
||||
endpoints = make([]string, 0, 6)
|
||||
added := make(map[string]bool)
|
||||
|
||||
add := func(endpoint string) {
|
||||
endpoint = strings.TrimSpace(endpoint)
|
||||
if len(endpoint) == 0 {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(endpoint, "/") == false {
|
||||
endpoint = "/" + endpoint
|
||||
}
|
||||
if added[endpoint] == true {
|
||||
return
|
||||
}
|
||||
added[endpoint] = true
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
|
||||
if key := getPlexMapString(dvr, "key"); len(key) > 0 {
|
||||
if strings.HasPrefix(key, "/") == true {
|
||||
add(strings.TrimRight(key, "/") + "/reloadGuide")
|
||||
} else {
|
||||
add("/livetv/dvrs/" + url.PathEscape(key) + "/reloadGuide")
|
||||
}
|
||||
}
|
||||
|
||||
for _, field := range []string{"uuid", "identifier", "machineIdentifier", "clientIdentifier", "id"} {
|
||||
if value := getPlexMapString(dvr, field); len(value) > 0 {
|
||||
add("/livetv/dvrs/" + url.PathEscape(value) + "/reloadGuide")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getPlexMapString(data map[string]interface{}, field string) string {
|
||||
|
||||
for key, value := range data {
|
||||
if strings.EqualFold(key, field) == false {
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
|
||||
case float64:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func doPlexRequest(method, baseURL, endpoint, token string) (status int, body []byte, err error) {
|
||||
|
||||
requestURL := strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(endpoint, "/")
|
||||
|
||||
req, err := http.NewRequest(method, requestURL, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
if len(token) > 0 {
|
||||
query.Set("X-Plex-Token", token)
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("X-Plex-Token", token)
|
||||
|
||||
if len(System.DeviceID) > 0 {
|
||||
req.Header.Set("X-Plex-Client-Identifier", System.DeviceID)
|
||||
}
|
||||
|
||||
if len(System.Name) > 0 {
|
||||
req.Header.Set("X-Plex-Product", System.Name)
|
||||
req.Header.Set("X-Plex-Device-Name", System.Name)
|
||||
}
|
||||
|
||||
if len(System.Version) > 0 {
|
||||
req.Header.Set("X-Plex-Version", System.Version)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
status = resp.StatusCode
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -254,6 +254,9 @@ type Notification struct {
|
||||
// SettingsStruct : Inhalt der settings.json
|
||||
type SettingsStruct struct {
|
||||
API bool `json:"api"`
|
||||
UsePlexAPI bool `json:"use_plexAPI"`
|
||||
PlexURL string `json:"plex.url"`
|
||||
PlexToken string `json:"plex.token"`
|
||||
AuthenticationAPI bool `json:"authentication.api"`
|
||||
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||
AuthenticationPMS bool `json:"authentication.pms"`
|
||||
|
||||
@@ -18,6 +18,9 @@ type RequestStruct struct {
|
||||
// Neue Werte für die Einstellungen (settings.json)
|
||||
Settings struct {
|
||||
API *bool `json:"api,omitempty"`
|
||||
UsePlexAPI *bool `json:"use_plexAPI,omitempty"`
|
||||
PlexURL *string `json:"plex.url,omitempty"`
|
||||
PlexToken *string `json:"plex.token,omitempty"`
|
||||
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
|
||||
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
|
||||
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`
|
||||
|
||||
@@ -106,6 +106,9 @@ func loadSettings() (settings SettingsStruct, err error) {
|
||||
dataMap["hdhr"] = make(map[string]interface{})
|
||||
|
||||
defaults["api"] = false
|
||||
defaults["use_plexAPI"] = false
|
||||
defaults["plex.url"] = ""
|
||||
defaults["plex.token"] = ""
|
||||
defaults["authentication.api"] = false
|
||||
defaults["authentication.m3u"] = false
|
||||
defaults["authentication.pms"] = false
|
||||
|
||||
@@ -68,6 +68,7 @@ func buildXEPG(background bool) {
|
||||
cleanupXEPG()
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
queuePlexGuideRefresh("xepg rebuild")
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
@@ -84,6 +85,7 @@ func buildXEPG(background bool) {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
queuePlexGuideRefresh("xepg image cache refresh")
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
@@ -113,6 +115,7 @@ func buildXEPG(background bool) {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
queuePlexGuideRefresh("xepg rebuild")
|
||||
|
||||
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
|
||||
|
||||
@@ -127,6 +130,7 @@ func buildXEPG(background bool) {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
queuePlexGuideRefresh("xepg image cache refresh")
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
@@ -179,6 +183,7 @@ func updateXEPG(background bool) {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
queuePlexGuideRefresh("xepg update")
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
Reference in New Issue
Block a user