v2.0.0.0000
This commit is contained in:
169
src/authentication.go
Normal file
169
src/authentication.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"../src/internal/authentication"
|
||||
)
|
||||
|
||||
func activatedSystemAuthentication() (err error) {
|
||||
|
||||
err = authentication.Init(System.Folder.Config, 60)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var defaults = make(map[string]interface{})
|
||||
defaults["authentication.web"] = false
|
||||
defaults["authentication.pms"] = false
|
||||
defaults["authentication.xml"] = false
|
||||
defaults["authentication.api"] = false
|
||||
err = authentication.SetDefaultUserData(defaults)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func createFirstUserForAuthentication(username, password string) (token string, err error) {
|
||||
|
||||
var authenticationErr = func(err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = authentication.CreateDefaultUser(username, password)
|
||||
authenticationErr(err)
|
||||
|
||||
token, err = authentication.UserAuthentication(username, password)
|
||||
authenticationErr(err)
|
||||
|
||||
token, err = authentication.CheckTheValidityOfTheToken(token)
|
||||
authenticationErr(err)
|
||||
|
||||
var userData = make(map[string]interface{})
|
||||
userData["username"] = username
|
||||
userData["authentication.web"] = true
|
||||
userData["authentication.pms"] = true
|
||||
userData["authentication.m3u"] = true
|
||||
userData["authentication.xml"] = true
|
||||
userData["authentication.api"] = false
|
||||
userData["defaultUser"] = true
|
||||
|
||||
userID, err := authentication.GetUserID(token)
|
||||
authenticationErr(err)
|
||||
|
||||
err = authentication.WriteUserData(userID, userData)
|
||||
authenticationErr(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func tokenAuthentication(token string) (newToken string, err error) {
|
||||
|
||||
if System.ConfigurationWizard == true {
|
||||
return
|
||||
}
|
||||
|
||||
newToken, err = authentication.CheckTheValidityOfTheToken(token)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func basicAuth(r *http.Request, level string) (username string, err error) {
|
||||
|
||||
err = errors.New("User authentication failed")
|
||||
|
||||
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||
|
||||
if len(auth) != 2 || auth[0] != "Basic" {
|
||||
return
|
||||
}
|
||||
|
||||
payload, _ := base64.StdEncoding.DecodeString(auth[1])
|
||||
pair := strings.SplitN(string(payload), ":", 2)
|
||||
|
||||
username = pair[0]
|
||||
var password = pair[1]
|
||||
|
||||
token, err := authentication.UserAuthentication(username, password)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = checkAuthorizationLevel(token, level)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func urlAuth(r *http.Request, requestType string) (err error) {
|
||||
var level, token string
|
||||
|
||||
var username = r.URL.Query().Get("username")
|
||||
var password = r.URL.Query().Get("password")
|
||||
|
||||
switch requestType {
|
||||
|
||||
case "m3u":
|
||||
level = "authentication.m3u"
|
||||
if Settings.AuthenticationM3U == true {
|
||||
token, err = authentication.UserAuthentication(username, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = checkAuthorizationLevel(token, level)
|
||||
}
|
||||
|
||||
case "xml":
|
||||
level = "authentication.xml"
|
||||
if Settings.AuthenticationXML == true {
|
||||
token, err = authentication.UserAuthentication(username, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = checkAuthorizationLevel(token, level)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkAuthorizationLevel(token, level string) (err error) {
|
||||
|
||||
var authenticationErr = func(err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
userID, err := authentication.GetUserID(token)
|
||||
authenticationErr(err)
|
||||
|
||||
userData, err := authentication.ReadUserData(userID)
|
||||
authenticationErr(err)
|
||||
|
||||
if len(userData) > 0 {
|
||||
|
||||
if v, ok := userData[level].(bool); ok {
|
||||
|
||||
if v == false {
|
||||
err = errors.New("No authorization")
|
||||
}
|
||||
|
||||
} else {
|
||||
userData[level] = false
|
||||
err = authentication.WriteUserData(userID, userData)
|
||||
err = errors.New("No authorization")
|
||||
}
|
||||
|
||||
} else {
|
||||
err = authentication.WriteUserData(userID, userData)
|
||||
err = errors.New("No authorization")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
191
src/backup.go
Normal file
191
src/backup.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func xTeVeAutoBackup() (err error) {
|
||||
|
||||
var archiv = "xteve_auto_backup_" + time.Now().Format("20060102_1504") + ".zip"
|
||||
var target string
|
||||
var sourceFiles = make([]string, 0)
|
||||
var oldBackupFiles = make([]string, 0)
|
||||
var debug string
|
||||
|
||||
if len(Settings.BackupPath) > 0 {
|
||||
System.Folder.Backup = Settings.BackupPath
|
||||
}
|
||||
|
||||
showInfo("Backup Path:" + System.Folder.Backup)
|
||||
|
||||
err = checkFolder(System.Folder.Backup)
|
||||
if err != nil {
|
||||
ShowError(err, 1070)
|
||||
return
|
||||
}
|
||||
|
||||
// Alte Backups löschen
|
||||
files, err := ioutil.ReadDir(System.Folder.Backup)
|
||||
|
||||
if err == nil {
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
if filepath.Ext(file.Name()) == ".zip" && strings.Contains(file.Name(), "xteve_auto_backup") {
|
||||
oldBackupFiles = append(oldBackupFiles, file.Name())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Alle Backups löschen
|
||||
var end int
|
||||
switch Settings.BackupKeep {
|
||||
case 0:
|
||||
end = 0
|
||||
default:
|
||||
end = Settings.BackupKeep - 1
|
||||
}
|
||||
|
||||
for i := 0; i < len(oldBackupFiles)-end; i++ {
|
||||
|
||||
os.RemoveAll(System.Folder.Backup + oldBackupFiles[i])
|
||||
debug = fmt.Sprintf("Delete backup file:%s", oldBackupFiles[i])
|
||||
showDebug(debug, 1)
|
||||
|
||||
}
|
||||
|
||||
if Settings.BackupKeep == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// Backup erstellen
|
||||
if err == nil {
|
||||
|
||||
target = System.Folder.Backup + archiv
|
||||
|
||||
for _, i := range SystemFiles {
|
||||
sourceFiles = append(sourceFiles, System.Folder.Config+i)
|
||||
}
|
||||
|
||||
sourceFiles = append(sourceFiles, System.Folder.ImagesUpload)
|
||||
|
||||
err = zipFiles(sourceFiles, target)
|
||||
|
||||
if err == nil {
|
||||
|
||||
debug = fmt.Sprintf("Create backup file:%s", target)
|
||||
showDebug(debug, 1)
|
||||
|
||||
showInfo("Backup file:" + target)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func xteveBackup() (archiv string, err error) {
|
||||
|
||||
err = checkFolder(System.Folder.Temp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
archiv = "xteve_backup_" + time.Now().Format("20060102_1504") + ".zip"
|
||||
|
||||
var target = System.Folder.Temp + archiv
|
||||
var sourceFiles = make([]string, 0)
|
||||
|
||||
for _, i := range SystemFiles {
|
||||
sourceFiles = append(sourceFiles, System.Folder.Config+i)
|
||||
}
|
||||
|
||||
sourceFiles = append(sourceFiles, System.Folder.Data)
|
||||
|
||||
err = zipFiles(sourceFiles, target)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func xteveRestore(input string) (newWebURL string, err error) {
|
||||
|
||||
var newPort, oldPort string
|
||||
|
||||
// Base64 Json String in base64 umwandeln
|
||||
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||
|
||||
// Base64 in bytes umwandeln und speichern
|
||||
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var archive = System.Folder.Temp + "restore.zip"
|
||||
|
||||
err = writeByteToFile(archive, sDec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Zip Archiv entpacken
|
||||
err = extractZIP(archive, System.Folder.Config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Neue Config laden um den Port zu überprüfen
|
||||
newConfig, err := loadJSONFileToMap(System.Folder.Config + "settings.json")
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
newPort = newConfig["port"].(string)
|
||||
oldPort = Settings.Port
|
||||
|
||||
if newPort == oldPort {
|
||||
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
loadSettings()
|
||||
|
||||
err := Init()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = StartSystem(true)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
var url = System.URLBase + "/web/"
|
||||
newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
|
||||
|
||||
return
|
||||
}
|
||||
1405
src/buffer.go
Normal file
1405
src/buffer.go
Normal file
File diff suppressed because it is too large
Load Diff
148
src/compression.go
Normal file
148
src/compression.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func zipFiles(sourceFiles []string, target string) error {
|
||||
|
||||
zipfile, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipfile.Close()
|
||||
|
||||
archive := zip.NewWriter(zipfile)
|
||||
defer archive.Close()
|
||||
|
||||
for _, source := range sourceFiles {
|
||||
|
||||
info, err := os.Stat(source)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var baseDir string
|
||||
if info.IsDir() {
|
||||
baseDir = filepath.Base(System.Folder.Data)
|
||||
}
|
||||
|
||||
filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if baseDir != "" {
|
||||
header.Name = filepath.Join(strings.TrimPrefix(path, System.Folder.Config))
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += string(os.PathSeparator)
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
writer, err := archive.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(writer, file)
|
||||
|
||||
return err
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func extractZIP(archive, target string) (err error) {
|
||||
|
||||
reader, err := zip.OpenReader(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range reader.File {
|
||||
|
||||
path := filepath.Join(target, file.Name)
|
||||
if file.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, file.Mode())
|
||||
continue
|
||||
}
|
||||
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
if _, err := io.Copy(targetFile, fileReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) {
|
||||
|
||||
var b = bytes.NewBuffer(gzipBody)
|
||||
|
||||
var r io.Reader
|
||||
r, err = gzip.NewReader(b)
|
||||
if err != nil {
|
||||
// Keine gzip Datei
|
||||
body = gzipBody
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
showInfo("Extract gzip:" + fileSource)
|
||||
|
||||
var resB bytes.Buffer
|
||||
_, err = resB.ReadFrom(r)
|
||||
if err != nil {
|
||||
body = gzipBody
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
body = resB.Bytes()
|
||||
return
|
||||
}
|
||||
242
src/config.go
Normal file
242
src/config.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// System : Beinhaltet alle Systeminformationen
|
||||
var System SystemStruct
|
||||
|
||||
// WebScreenLog : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt
|
||||
var WebScreenLog WebScreenLogStruct
|
||||
|
||||
// Settings : Inhalt der settings.json
|
||||
var Settings SettingsStrcut
|
||||
|
||||
// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||
var Data DataStruct
|
||||
|
||||
// SystemFiles : Alle Systemdateien
|
||||
var SystemFiles = []string{"authentication.json", "pms.json", "settings.json", "xepg.json", "urls.json"}
|
||||
|
||||
// BufferInformation : Informationen über den Buffer (aktive Streams, maximale Streams)
|
||||
var BufferInformation sync.Map
|
||||
|
||||
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
|
||||
var BufferClients sync.Map
|
||||
|
||||
// Init : Systeminitialisierung
|
||||
func Init() (err error) {
|
||||
|
||||
var debug string
|
||||
|
||||
// System Einstellungen
|
||||
System.AppName = strings.ToLower(System.Name)
|
||||
System.ARCH = runtime.GOARCH
|
||||
System.OS = runtime.GOOS
|
||||
System.ServerProtocol.API = "http"
|
||||
System.ServerProtocol.DVR = "http"
|
||||
System.ServerProtocol.M3U = "http"
|
||||
System.ServerProtocol.WEB = "http"
|
||||
System.ServerProtocol.XML = "http"
|
||||
System.DVRLimit = 480
|
||||
System.Compatibility = "1.4.4"
|
||||
|
||||
// Default Logeinträge, wird später von denen aus der settings.json überschrieben. Muss gemacht werden, damit die ersten Einträge auch im Log (webUI aangezeigt werden)
|
||||
Settings.LogEntriesRAM = 500
|
||||
|
||||
// Variablen für den Update Prozess
|
||||
//System.Update.Git = "https://github.com/xteve-project/xTeVe-Downloads/blob"
|
||||
System.Update.Git = fmt.Sprintf("https://github.com/%s/%s/blob", System.GitHub.User, System.GitHub.Repo)
|
||||
System.Update.Name = "xteve_2"
|
||||
|
||||
// Ordnerpfade festlegen
|
||||
var tempFolder = os.TempDir() + string(os.PathSeparator) + System.AppName + string(os.PathSeparator)
|
||||
tempFolder = getPlatformPath(strings.Replace(tempFolder, "//", "/", -1))
|
||||
|
||||
if len(System.Folder.Config) == 0 {
|
||||
System.Folder.Config = GetUserHomeDirectory() + string(os.PathSeparator) + "." + System.AppName + string(os.PathSeparator)
|
||||
} else {
|
||||
System.Folder.Config = strings.TrimRight(System.Folder.Config, string(os.PathSeparator)) + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
System.Folder.Config = getPlatformPath(System.Folder.Config)
|
||||
|
||||
System.Folder.Backup = System.Folder.Config + "backup" + string(os.PathSeparator)
|
||||
System.Folder.Data = System.Folder.Config + "data" + string(os.PathSeparator)
|
||||
System.Folder.Cache = System.Folder.Config + "cache" + string(os.PathSeparator)
|
||||
System.Folder.ImagesCache = System.Folder.Cache + "images" + string(os.PathSeparator)
|
||||
System.Folder.ImagesUpload = System.Folder.Data + "images" + string(os.PathSeparator)
|
||||
System.Folder.Temp = tempFolder
|
||||
|
||||
// Dev Info
|
||||
showDevInfo()
|
||||
|
||||
// System Ordner erstellen
|
||||
err = createSystemFolders()
|
||||
if err != nil {
|
||||
ShowError(err, 1070)
|
||||
return
|
||||
}
|
||||
|
||||
System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName))
|
||||
System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName))
|
||||
|
||||
err = activatedSystemAuthentication()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resolveHostIP()
|
||||
if err != nil {
|
||||
ShowError(err, 1002)
|
||||
}
|
||||
|
||||
// Menü für das Webinterface
|
||||
System.WEB.Menu = []string{"playlist", "filter", "xmltv", "mapping", "users", "settings", "log", "logout"}
|
||||
|
||||
fmt.Println("For help run: " + getPlatformFile(os.Args[0]) + " -h")
|
||||
fmt.Println()
|
||||
|
||||
// Überprüfen ob xTeVe als root läuft
|
||||
if os.Geteuid() == 0 {
|
||||
showWarning(2010)
|
||||
}
|
||||
|
||||
if System.Flag.Debug > 0 {
|
||||
debug = fmt.Sprintf("Debug Level:%d", System.Flag.Debug)
|
||||
showDebug(debug, 1)
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
|
||||
showInfo(fmt.Sprintf("System IP Addresses:IPv4: %d | IPv6: %d", len(System.IPAddressesV4), len(System.IPAddressesV6)))
|
||||
showInfo("Hostname:" + System.Hostname)
|
||||
showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
|
||||
|
||||
// Systemdateien erstellen (Falls nicht vorhanden)
|
||||
err = createSystemFiles()
|
||||
if err != nil {
|
||||
ShowError(err, 1071)
|
||||
return
|
||||
}
|
||||
|
||||
// Bedingte Update Änderungen durchführen
|
||||
err = conditionalUpdateChanges()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Einstellungen laden (settings.json)
|
||||
showInfo(fmt.Sprintf("Load Settings:%s", System.File.Settings))
|
||||
|
||||
_, err = loadSettings()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Berechtigung aller Ordner überprüfen
|
||||
err = checkFilePermission(System.Folder.Config)
|
||||
if err == nil {
|
||||
err = checkFilePermission(System.Folder.Temp)
|
||||
}
|
||||
|
||||
// Separaten tmp Ordner für jede Instanz
|
||||
//System.Folder.Temp = System.Folder.Temp + Settings.UUID + string(os.PathSeparator)
|
||||
showInfo(fmt.Sprintf("Temporary Folder:%s", getPlatformPath(System.Folder.Temp)))
|
||||
|
||||
err = checkFolder(System.Folder.Temp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = removeChildItems(getPlatformPath(System.Folder.Temp))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// DLNA Server starten
|
||||
go SSDP()
|
||||
|
||||
// Branch festlegen
|
||||
System.Branch = Settings.Branch
|
||||
|
||||
if System.Dev == true {
|
||||
System.Branch = "Development"
|
||||
}
|
||||
|
||||
if len(System.Branch) == 0 {
|
||||
System.Branch = "master"
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("GitHub:https://github.com/%s", System.GitHub.User))
|
||||
showInfo(fmt.Sprintf("Git Branch:%s [%s]", System.Branch, System.GitHub.User))
|
||||
|
||||
// Domainnamen setzten
|
||||
setGlobalDomain(fmt.Sprintf("%s:%s", System.IPAddress, Settings.Port))
|
||||
|
||||
System.URLBase = fmt.Sprintf("%s://%s:%s", System.ServerProtocol.WEB, System.IPAddress, Settings.Port)
|
||||
|
||||
// HTML Dateien erstellen, mit dev == true werden die lokalen HTML Dateien verwendet
|
||||
if System.Dev == true {
|
||||
|
||||
HTMLInit("webUI", "src", "html"+string(os.PathSeparator), "src"+string(os.PathSeparator)+"webUI.go")
|
||||
err = BuildGoFile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loadHTMLMap()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// StartSystem : System wird gestartet
|
||||
func StartSystem(updateProviderFiles bool) (err error) {
|
||||
|
||||
setDeviceID()
|
||||
|
||||
if System.ScanInProgress == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Systeminformationen in der Konsole ausgeben
|
||||
showInfo(fmt.Sprintf("UUID:%s", Settings.UUID))
|
||||
showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner))
|
||||
showInfo(fmt.Sprintf("EPG Source:%s", Settings.EpgSource))
|
||||
showInfo(fmt.Sprintf("Plex Channel Limit:%d", System.DVRLimit))
|
||||
|
||||
// Providerdaten aktualisieren
|
||||
if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
|
||||
|
||||
err = xTeVeAutoBackup()
|
||||
if err != nil {
|
||||
ShowError(err, 1090)
|
||||
}
|
||||
|
||||
getProviderData("m3u", "")
|
||||
getProviderData("hdhr", "")
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
getProviderData("xmltv", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
return
|
||||
}
|
||||
953
src/data.go
Normal file
953
src/data.go
Normal file
@@ -0,0 +1,953 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"../src/internal/authentication"
|
||||
)
|
||||
|
||||
// Einstellungen ändern (WebUI)
|
||||
func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
|
||||
var oldSettings = jsonToMap(mapToJSON(Settings))
|
||||
var newSettings = jsonToMap(mapToJSON(request.Settings))
|
||||
var reloadData = false
|
||||
var cacheImages = false
|
||||
var createXEPGFiles = false
|
||||
var debug string
|
||||
|
||||
for key, value := range newSettings {
|
||||
|
||||
if _, ok := oldSettings[key]; ok {
|
||||
|
||||
switch key {
|
||||
|
||||
case "tuner":
|
||||
showWarning(2105)
|
||||
|
||||
case "epgSource":
|
||||
reloadData = true
|
||||
|
||||
case "update":
|
||||
// Die Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
||||
for _, i := range newSettings[key].([]interface{}) {
|
||||
|
||||
_, err := time.Parse("1504", i.(string))
|
||||
if err != nil {
|
||||
ShowError(err, 1012)
|
||||
return Settings, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case "cache.images":
|
||||
cacheImages = true
|
||||
|
||||
case "xepg.replace.missing.images":
|
||||
createXEPGFiles = true
|
||||
|
||||
case "backup.path":
|
||||
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
||||
err = checkFolder(value.(string))
|
||||
if err == nil {
|
||||
|
||||
err = checkFilePermission(value.(string))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "temp.path":
|
||||
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
||||
err = checkFolder(value.(string))
|
||||
if err == nil {
|
||||
|
||||
err = checkFilePermission(value.(string))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
oldSettings[key] = value
|
||||
|
||||
switch fmt.Sprintf("%T", 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 "[]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)
|
||||
|
||||
default:
|
||||
debug = fmt.Sprintf("%T", value)
|
||||
}
|
||||
|
||||
showDebug(debug, 1)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Einstellungen aktualisieren
|
||||
err = json.Unmarshal([]byte(mapToJSON(oldSettings)), &Settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if Settings.AuthenticationWEB == false {
|
||||
|
||||
Settings.AuthenticationAPI = false
|
||||
Settings.AuthenticationM3U = false
|
||||
Settings.AuthenticationPMS = false
|
||||
Settings.AuthenticationWEB = false
|
||||
Settings.AuthenticationXML = false
|
||||
|
||||
}
|
||||
|
||||
err = saveSettings(Settings)
|
||||
if err == nil {
|
||||
|
||||
settings = Settings
|
||||
|
||||
if reloadData == true {
|
||||
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
}
|
||||
|
||||
if cacheImages == true {
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
|
||||
go func() {
|
||||
|
||||
if Settings.CacheImages == true {
|
||||
|
||||
createXMLTVFile()
|
||||
cachingImages()
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
} else {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if createXEPGFiles == true {
|
||||
|
||||
go func() {
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Providerdaten speichern (WebUI)
|
||||
func saveFiles(request RequestStruct, fileType string) (err error) {
|
||||
|
||||
var filesMap = make(map[string]interface{})
|
||||
var newData = make(map[string]interface{})
|
||||
var indicator string
|
||||
var reloadData = false
|
||||
|
||||
switch fileType {
|
||||
case "m3u":
|
||||
filesMap = Settings.Files.M3U
|
||||
newData = request.Files.M3U
|
||||
indicator = "M"
|
||||
|
||||
case "hdhr":
|
||||
filesMap = Settings.Files.HDHR
|
||||
newData = request.Files.HDHR
|
||||
indicator = "H"
|
||||
|
||||
case "xmltv":
|
||||
filesMap = Settings.Files.XMLTV
|
||||
newData = request.Files.XMLTV
|
||||
indicator = "X"
|
||||
}
|
||||
|
||||
if len(filesMap) == 0 {
|
||||
filesMap = make(map[string]interface{})
|
||||
}
|
||||
|
||||
for dataID, data := range newData {
|
||||
|
||||
if dataID == "-" {
|
||||
|
||||
// Neue Providerdatei
|
||||
dataID = indicator + randomString(19)
|
||||
data.(map[string]interface{})["new"] = true
|
||||
filesMap[dataID] = data
|
||||
|
||||
} else {
|
||||
|
||||
// Bereits vorhandene Providerdatei
|
||||
for key, value := range data.(map[string]interface{}) {
|
||||
|
||||
var oldData = filesMap[dataID].(map[string]interface{})
|
||||
oldData[key] = value
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
Settings.Files.M3U = filesMap
|
||||
|
||||
case "hdhr":
|
||||
Settings.Files.HDHR = filesMap
|
||||
|
||||
case "xmltv":
|
||||
Settings.Files.XMLTV = filesMap
|
||||
|
||||
}
|
||||
|
||||
// Neue Providerdatei
|
||||
if _, ok := data.(map[string]interface{})["new"]; ok {
|
||||
|
||||
reloadData = true
|
||||
err = getProviderData(fileType, dataID)
|
||||
delete(data.(map[string]interface{}), "new")
|
||||
|
||||
if err != nil {
|
||||
delete(filesMap, dataID)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||
|
||||
deleteLocalProviderFiles(dataID, fileType)
|
||||
reloadData = true
|
||||
|
||||
}
|
||||
|
||||
err = saveSettings(Settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if reloadData == true {
|
||||
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
}
|
||||
|
||||
Settings, _ = loadSettings()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Providerdaten manuell aktualisieren (WebUI)
|
||||
func updateFile(request RequestStruct, fileType string) (err error) {
|
||||
|
||||
var updateData = make(map[string]interface{})
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
updateData = request.Files.M3U
|
||||
|
||||
case "hdhr":
|
||||
updateData = request.Files.HDHR
|
||||
|
||||
case "xmltv":
|
||||
updateData = request.Files.XMLTV
|
||||
}
|
||||
|
||||
for dataID := range updateData {
|
||||
|
||||
err = getProviderData(fileType, dataID)
|
||||
if err == nil {
|
||||
err = buildDatabaseDVR()
|
||||
buildXEPG(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Providerdaten löschen (WebUI)
|
||||
func deleteLocalProviderFiles(dataID, fileType string) {
|
||||
|
||||
var removeData = make(map[string]interface{})
|
||||
var fileExtension string
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
removeData = Settings.Files.M3U
|
||||
fileExtension = ".m3u"
|
||||
|
||||
case "hdhr":
|
||||
removeData = Settings.Files.HDHR
|
||||
fileExtension = ".json"
|
||||
|
||||
case "xmltv":
|
||||
removeData = Settings.Files.XMLTV
|
||||
fileExtension = ".xml"
|
||||
}
|
||||
|
||||
if _, ok := removeData[dataID]; ok {
|
||||
delete(removeData, dataID)
|
||||
os.RemoveAll(System.Folder.Data + dataID + fileExtension)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filtereinstellungen speichern (WebUI)
|
||||
func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
|
||||
var filterMap = make(map[int64]interface{})
|
||||
var newData = make(map[int64]interface{})
|
||||
var defaultFilter FilterStruct
|
||||
|
||||
defaultFilter.Active = true
|
||||
defaultFilter.CaseSensitive = false
|
||||
|
||||
filterMap = Settings.Filter
|
||||
newData = request.Filter
|
||||
|
||||
var createNewID = func() (id int64) {
|
||||
|
||||
newID:
|
||||
if _, ok := filterMap[id]; ok {
|
||||
id++
|
||||
goto newID
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
for dataID, data := range newData {
|
||||
|
||||
if dataID == -1 {
|
||||
|
||||
// Neuer Filter
|
||||
dataID = createNewID()
|
||||
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
|
||||
|
||||
}
|
||||
|
||||
// Filter aktualisieren / löschen
|
||||
for key, value := range data.(map[string]interface{}) {
|
||||
|
||||
var oldData = filterMap[dataID].(map[string]interface{})
|
||||
oldData[key] = value
|
||||
|
||||
// Filter löschen
|
||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||
|
||||
delete(filterMap, dataID)
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveSettings(Settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
settings = Settings
|
||||
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// XEPG Mapping speichern
|
||||
func saveXEpgMapping(request RequestStruct) (err error) {
|
||||
|
||||
var tmp = Data.XEPG
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(request.EpgMapping)), &tmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = saveMapToJSONFile(System.File.XEPG, request.EpgMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Data.XEPG.Channels = request.EpgMapping
|
||||
|
||||
cleanupXEPG()
|
||||
buildXEPG(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Benutzerdaten speichern (WebUI)
|
||||
func saveUserData(request RequestStruct) (err error) {
|
||||
|
||||
var userData = request.UserData
|
||||
|
||||
var newCredentials = func(userID string, newUserData map[string]interface{}) (err error) {
|
||||
|
||||
var newUsername, newPassword string
|
||||
if username, ok := newUserData["username"].(string); ok {
|
||||
newUsername = username
|
||||
}
|
||||
|
||||
if password, ok := newUserData["password"].(string); ok {
|
||||
newPassword = password
|
||||
}
|
||||
|
||||
if len(newUsername) > 0 {
|
||||
err = authentication.ChangeCredentials(userID, newUsername, newPassword)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for userID, newUserData := range userData {
|
||||
|
||||
err = newCredentials(userID, newUserData.(map[string]interface{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if request.DeleteUser == true {
|
||||
err = authentication.RemoveUser(userID)
|
||||
return
|
||||
}
|
||||
|
||||
delete(newUserData.(map[string]interface{}), "password")
|
||||
delete(newUserData.(map[string]interface{}), "confirm")
|
||||
|
||||
if _, ok := newUserData.(map[string]interface{})["delete"]; ok {
|
||||
|
||||
authentication.RemoveUser(userID)
|
||||
|
||||
} else {
|
||||
|
||||
err = authentication.WriteUserData(userID, newUserData.(map[string]interface{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Neuen Benutzer anlegen (WebUI)
|
||||
func saveNewUser(request RequestStruct) (err error) {
|
||||
|
||||
var data = request.UserData
|
||||
var username = data["username"].(string)
|
||||
var password = data["password"].(string)
|
||||
|
||||
delete(data, "password")
|
||||
delete(data, "confirm")
|
||||
|
||||
userID, err := authentication.CreateNewUser(username, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = authentication.WriteUserData(userID, data)
|
||||
return
|
||||
}
|
||||
|
||||
// Wizard (WebUI)
|
||||
func saveWizard(request RequestStruct) (nextStep int, err error) {
|
||||
|
||||
var wizard = jsonToMap(mapToJSON(request.Wizard))
|
||||
|
||||
for key, value := range wizard {
|
||||
|
||||
switch key {
|
||||
|
||||
case "tuner":
|
||||
Settings.Tuner = int(value.(float64))
|
||||
nextStep = 1
|
||||
|
||||
case "epgSource":
|
||||
Settings.EpgSource = value.(string)
|
||||
nextStep = 2
|
||||
|
||||
case "m3u", "xmltv":
|
||||
|
||||
var filesMap = make(map[string]interface{})
|
||||
var data = make(map[string]interface{})
|
||||
var indicator, dataID string
|
||||
|
||||
filesMap = make(map[string]interface{})
|
||||
|
||||
data["type"] = key
|
||||
data["new"] = true
|
||||
|
||||
switch key {
|
||||
|
||||
case "m3u":
|
||||
filesMap = Settings.Files.M3U
|
||||
data["name"] = "M3U"
|
||||
indicator = "M"
|
||||
|
||||
case "xmltv":
|
||||
filesMap = Settings.Files.XMLTV
|
||||
data["name"] = "XMLTV"
|
||||
indicator = "X"
|
||||
|
||||
}
|
||||
|
||||
dataID = indicator + randomString(19)
|
||||
data["file.source"] = value.(string)
|
||||
|
||||
filesMap[dataID] = data
|
||||
|
||||
switch key {
|
||||
case "m3u":
|
||||
Settings.Files.M3U = filesMap
|
||||
nextStep = 3
|
||||
|
||||
err = getProviderData(key, dataID)
|
||||
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
delete(filesMap, dataID)
|
||||
return
|
||||
}
|
||||
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
delete(filesMap, dataID)
|
||||
return
|
||||
}
|
||||
|
||||
if Settings.EpgSource == "PMS" {
|
||||
nextStep = 10
|
||||
}
|
||||
|
||||
case "xmltv":
|
||||
Settings.Files.XMLTV = filesMap
|
||||
nextStep = 10
|
||||
|
||||
err = getProviderData(key, dataID)
|
||||
|
||||
if err != nil {
|
||||
|
||||
ShowError(err, 000)
|
||||
delete(filesMap, dataID)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveSettings(Settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filterregeln erstellen
|
||||
func createFilterRules() (err error) {
|
||||
|
||||
Data.Filter = nil
|
||||
var dataFilter Filter
|
||||
|
||||
for _, f := range Settings.Filter {
|
||||
|
||||
var filter FilterStruct
|
||||
|
||||
var exclude, include string
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(f)), &filter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch filter.Type {
|
||||
|
||||
case "custom-filter":
|
||||
dataFilter.CaseSensitive = filter.CaseSensitive
|
||||
dataFilter.Rule = filter.Filter
|
||||
dataFilter.Type = filter.Type
|
||||
|
||||
Data.Filter = append(Data.Filter, dataFilter)
|
||||
|
||||
case "group-title":
|
||||
if len(filter.Include) > 0 {
|
||||
include = fmt.Sprintf(" {%s}", filter.Include)
|
||||
}
|
||||
|
||||
if len(filter.Exclude) > 0 {
|
||||
exclude = fmt.Sprintf(" !{%s}", filter.Exclude)
|
||||
}
|
||||
|
||||
dataFilter.CaseSensitive = filter.CaseSensitive
|
||||
dataFilter.Rule = fmt.Sprintf("%s%s%s", filter.Filter, include, exclude)
|
||||
dataFilter.Type = filter.Type
|
||||
|
||||
Data.Filter = append(Data.Filter, dataFilter)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Datenbank für das DVR System erstellen
|
||||
func buildDatabaseDVR() (err error) {
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
Data.Streams.All = make([]interface{}, 0)
|
||||
Data.Streams.Active = make([]interface{}, 0)
|
||||
Data.Streams.Inactive = make([]interface{}, 0)
|
||||
Data.Playlist.M3U.Groups.Text = []string{}
|
||||
Data.Playlist.M3U.Groups.Value = []string{}
|
||||
Data.StreamPreviewUI.Active = []string{}
|
||||
Data.StreamPreviewUI.Inactive = []string{}
|
||||
|
||||
var availableFileTypes = []string{"m3u", "hdhr"}
|
||||
|
||||
var tmpGroupsM3U = make(map[string]int64)
|
||||
|
||||
err = createFilterRules()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, fileType := range availableFileTypes {
|
||||
|
||||
var playlistFile = getLocalProviderFiles(fileType)
|
||||
|
||||
for n, i := range playlistFile {
|
||||
|
||||
var channels []interface{}
|
||||
var groupTitle, tvgID, uuid int = 0, 0, 0
|
||||
var keys = []string{"group-title", "tvg-id", "uuid"}
|
||||
var compatibility = make(map[string]int)
|
||||
|
||||
var id = strings.TrimSuffix(getFilenameFromPath(i), path.Ext(getFilenameFromPath(i)))
|
||||
var playlistName = getProviderParameter(id, fileType, "name")
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
channels, err = parsePlaylist(i, fileType)
|
||||
case "hdhr":
|
||||
channels, err = parsePlaylist(i, fileType)
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ShowError(err, 1005)
|
||||
err = errors.New(playlistName + ": Local copy of the file no longer exists")
|
||||
ShowError(err, 0)
|
||||
playlistFile = append(playlistFile[:n], playlistFile[n+1:]...)
|
||||
}
|
||||
|
||||
// Streams analysieren
|
||||
for _, stream := range channels {
|
||||
|
||||
var s = stream.(map[string]string)
|
||||
s["_file.m3u.path"] = i
|
||||
s["_file.m3u.name"] = playlistName
|
||||
s["_file.m3u.id"] = id
|
||||
|
||||
// Kompatibilität berechnen
|
||||
for _, key := range keys {
|
||||
|
||||
switch key {
|
||||
case "uuid":
|
||||
if value, ok := s["_uuid.key"]; ok {
|
||||
if len(value) > 0 {
|
||||
uuid++
|
||||
}
|
||||
}
|
||||
|
||||
case "group-title":
|
||||
if value, ok := s[key]; ok {
|
||||
if len(value) > 0 {
|
||||
|
||||
if _, ok := tmpGroupsM3U[value]; ok {
|
||||
tmpGroupsM3U[value]++
|
||||
} else {
|
||||
tmpGroupsM3U[value] = 1
|
||||
}
|
||||
|
||||
groupTitle++
|
||||
}
|
||||
}
|
||||
|
||||
case "tvg-id":
|
||||
if value, ok := s[key]; ok {
|
||||
if len(value) > 0 {
|
||||
tvgID++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Data.Streams.All = append(Data.Streams.All, stream)
|
||||
|
||||
// Neuer Filter ab Version 1.3.0
|
||||
var preview string
|
||||
var status = filterThisStream(stream)
|
||||
|
||||
if name, ok := s["name"]; ok {
|
||||
var group string
|
||||
|
||||
if v, ok := s["group-title"]; ok {
|
||||
group = v
|
||||
}
|
||||
|
||||
preview = fmt.Sprintf("%s [%s]", name, group)
|
||||
|
||||
}
|
||||
|
||||
switch status {
|
||||
|
||||
case true:
|
||||
Data.StreamPreviewUI.Active = append(Data.StreamPreviewUI.Active, preview)
|
||||
Data.Streams.Active = append(Data.Streams.Active, stream)
|
||||
|
||||
case false:
|
||||
Data.StreamPreviewUI.Inactive = append(Data.StreamPreviewUI.Inactive, preview)
|
||||
Data.Streams.Inactive = append(Data.Streams.Inactive, stream)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if tvgID == 0 {
|
||||
compatibility["tvg.id"] = 0
|
||||
} else {
|
||||
compatibility["tvg.id"] = int(tvgID * 100 / len(channels))
|
||||
}
|
||||
|
||||
if groupTitle == 0 {
|
||||
compatibility["group.title"] = 0
|
||||
} else {
|
||||
compatibility["group.title"] = int(groupTitle * 100 / len(channels))
|
||||
}
|
||||
|
||||
if uuid == 0 {
|
||||
compatibility["stream.id"] = 0
|
||||
} else {
|
||||
compatibility["stream.id"] = int(uuid * 100 / len(channels))
|
||||
}
|
||||
|
||||
compatibility["streams"] = len(channels)
|
||||
|
||||
setProviderCompatibility(id, fileType, compatibility)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for group, count := range tmpGroupsM3U {
|
||||
var text = fmt.Sprintf("%s (%d)", group, count)
|
||||
var value = fmt.Sprintf("%s", group)
|
||||
Data.Playlist.M3U.Groups.Text = append(Data.Playlist.M3U.Groups.Text, text)
|
||||
Data.Playlist.M3U.Groups.Value = append(Data.Playlist.M3U.Groups.Value, value)
|
||||
}
|
||||
|
||||
sort.Strings(Data.Playlist.M3U.Groups.Text)
|
||||
sort.Strings(Data.Playlist.M3U.Groups.Value)
|
||||
|
||||
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.DVRLimit && len(Settings.Filter) == 0 {
|
||||
Data.Streams.Active = Data.Streams.All
|
||||
Data.Streams.Inactive = make([]interface{}, 0)
|
||||
|
||||
Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Inactive
|
||||
Data.StreamPreviewUI.Inactive = []string{}
|
||||
|
||||
}
|
||||
|
||||
if len(Data.Streams.Active) > System.DVRLimit {
|
||||
showWarning(2000)
|
||||
}
|
||||
|
||||
if len(Settings.Filter) == 0 && len(Data.Streams.All) > System.DVRLimit {
|
||||
showWarning(2001)
|
||||
}
|
||||
|
||||
System.ScanInProgress = 0
|
||||
showInfo(fmt.Sprintf("All streams:%d", len(Data.Streams.All)))
|
||||
showInfo(fmt.Sprintf("Active streams:%d", len(Data.Streams.Active)))
|
||||
showInfo(fmt.Sprintf("Filter:%d", len(Data.Filter)))
|
||||
|
||||
sort.Strings(Data.StreamPreviewUI.Active)
|
||||
sort.Strings(Data.StreamPreviewUI.Inactive)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Speicherort aller lokalen Providerdateien laden, immer für eine Dateityp (M3U, XMLTV usw.)
|
||||
func getLocalProviderFiles(fileType string) (localFiles []string) {
|
||||
|
||||
var fileExtension string
|
||||
var dataMap = make(map[string]interface{})
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
fileExtension = ".m3u"
|
||||
dataMap = Settings.Files.M3U
|
||||
|
||||
case "hdhr":
|
||||
fileExtension = ".json"
|
||||
dataMap = Settings.Files.HDHR
|
||||
|
||||
case "xmltv":
|
||||
fileExtension = ".xml"
|
||||
dataMap = Settings.Files.XMLTV
|
||||
|
||||
}
|
||||
|
||||
for dataID := range dataMap {
|
||||
localFiles = append(localFiles, System.Folder.Data+dataID+fileExtension)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Providerparameter anhand von dem Key ausgeben
|
||||
func getProviderParameter(id, fileType, key string) (s string) {
|
||||
|
||||
var dataMap = make(map[string]interface{})
|
||||
|
||||
switch fileType {
|
||||
case "m3u":
|
||||
dataMap = Settings.Files.M3U
|
||||
|
||||
case "hdhr":
|
||||
dataMap = Settings.Files.HDHR
|
||||
|
||||
case "xmltv":
|
||||
dataMap = Settings.Files.XMLTV
|
||||
}
|
||||
|
||||
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
||||
|
||||
if v, ok := data[key].(string); ok {
|
||||
s = v
|
||||
}
|
||||
|
||||
if v, ok := data[key].(float64); ok {
|
||||
s = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Provider Statistiken Kompatibilität aktualisieren
|
||||
func setProviderCompatibility(id, fileType string, compatibility map[string]int) {
|
||||
|
||||
var dataMap = make(map[string]interface{})
|
||||
|
||||
switch fileType {
|
||||
case "m3u":
|
||||
dataMap = Settings.Files.M3U
|
||||
|
||||
case "hdhr":
|
||||
dataMap = Settings.Files.HDHR
|
||||
|
||||
case "xmltv":
|
||||
dataMap = Settings.Files.XMLTV
|
||||
}
|
||||
|
||||
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
||||
|
||||
data["compatibility"] = compatibility
|
||||
|
||||
switch fileType {
|
||||
case "m3u":
|
||||
Settings.Files.M3U = dataMap
|
||||
case "hdhr":
|
||||
Settings.Files.HDHR = dataMap
|
||||
case "xmltv":
|
||||
Settings.Files.XMLTV = dataMap
|
||||
}
|
||||
|
||||
saveSettings(Settings)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
236
src/hdhr.go
Normal file
236
src/hdhr.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []interface{}, err error) {
|
||||
|
||||
var hdhrData []interface{}
|
||||
|
||||
err = json.Unmarshal(content, &hdhrData)
|
||||
if err == nil {
|
||||
|
||||
for _, d := range hdhrData {
|
||||
|
||||
var channel = make(map[string]string)
|
||||
var data = d.(map[string]interface{})
|
||||
|
||||
channel["group-title"] = playlistName
|
||||
channel["name"] = data["GuideName"].(string)
|
||||
channel["tvg-id"] = data["GuideName"].(string)
|
||||
channel["url"] = data["URL"].(string)
|
||||
channel["ID-"+id] = data["GuideNumber"].(string)
|
||||
channel["_uuid.key"] = "ID-" + id
|
||||
channel["_values"] = playlistName + " " + channel["name"]
|
||||
|
||||
channels = append(channels, channel)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getCapability() (xmlContent []byte, err error) {
|
||||
|
||||
var capability Capability
|
||||
var buffer bytes.Buffer
|
||||
|
||||
capability.Xmlns = "urn:schemas-upnp-org:device-1-0"
|
||||
capability.URLBase = System.ServerProtocol.WEB + "://" + System.Domain
|
||||
|
||||
capability.SpecVersion.Major = 1
|
||||
capability.SpecVersion.Minor = 0
|
||||
|
||||
capability.Device.DeviceType = "urn:schemas-upnp-org:device:MediaServer:1"
|
||||
capability.Device.FriendlyName = System.Name
|
||||
capability.Device.Manufacturer = "Silicondust"
|
||||
capability.Device.ModelName = "HDTC-2US"
|
||||
capability.Device.ModelNumber = "HDTC-2US"
|
||||
capability.Device.SerialNumber = ""
|
||||
capability.Device.UDN = "uuid:" + System.DeviceID
|
||||
|
||||
output, err := xml.MarshalIndent(capability, " ", " ")
|
||||
if err != nil {
|
||||
ShowError(err, 1003)
|
||||
}
|
||||
|
||||
buffer.Write([]byte(xml.Header))
|
||||
buffer.Write([]byte(output))
|
||||
xmlContent = buffer.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getDiscover() (jsonContent []byte, err error) {
|
||||
|
||||
var discover Discover
|
||||
|
||||
discover.BaseURL = System.ServerProtocol.WEB + "://" + System.Domain
|
||||
discover.DeviceAuth = System.AppName
|
||||
discover.DeviceID = System.DeviceID
|
||||
discover.FirmwareName = "bin_" + System.Version
|
||||
discover.FirmwareVersion = System.Version
|
||||
discover.FriendlyName = System.Name
|
||||
|
||||
discover.LineupURL = fmt.Sprintf("%s://%s/lineup.json", System.ServerProtocol.DVR, System.Domain)
|
||||
discover.Manufacturer = "Golang"
|
||||
discover.ModelNumber = System.Version
|
||||
discover.TunerCount = Settings.Tuner
|
||||
|
||||
jsonContent, err = json.MarshalIndent(discover, "", " ")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getLineupStatus() (jsonContent []byte, err error) {
|
||||
|
||||
var lineupStatus LineupStatus
|
||||
|
||||
lineupStatus.ScanInProgress = System.ScanInProgress
|
||||
lineupStatus.ScanPossible = 0
|
||||
lineupStatus.Source = "Cable"
|
||||
lineupStatus.SourceList = []string{"IPTV", "Cable"}
|
||||
|
||||
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getLineup() (jsonContent []byte, err error) {
|
||||
|
||||
var lineup Lineup
|
||||
|
||||
switch Settings.EpgSource {
|
||||
|
||||
case "PMS":
|
||||
for i, dsa := range Data.Streams.Active {
|
||||
|
||||
var m3uChannel M3UChannelStructXEPG
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var stream LineupStream
|
||||
stream.GuideName = m3uChannel.Name
|
||||
switch len(m3uChannel.UUIDValue) {
|
||||
|
||||
case 0:
|
||||
stream.GuideNumber = fmt.Sprintf("%d", i+1000)
|
||||
guideNumber, err := getGuideNumberPMS(stream.GuideName)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
stream.GuideNumber = guideNumber
|
||||
|
||||
default:
|
||||
stream.GuideNumber = m3uChannel.UUIDValue
|
||||
|
||||
}
|
||||
|
||||
stream.URL, err = createStreamingURL("DVR", m3uChannel.FileM3UID, stream.GuideNumber, m3uChannel.Name, m3uChannel.URL)
|
||||
if err == nil {
|
||||
lineup = append(lineup, stream)
|
||||
} else {
|
||||
ShowError(err, 1202)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case "XEPG":
|
||||
for _, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if xepgChannel.XActive == true {
|
||||
var stream LineupStream
|
||||
stream.GuideName = xepgChannel.XName
|
||||
stream.GuideNumber = xepgChannel.XChannelID
|
||||
//stream.URL = fmt.Sprintf("%s://%s/stream/%s-%s", System.ServerProtocol.DVR, System.Domain, xepgChannel.FileM3UID, base64.StdEncoding.EncodeToString([]byte(xepgChannel.URL)))
|
||||
stream.URL, err = createStreamingURL("DVR", xepgChannel.FileM3UID, xepgChannel.XChannelID, xepgChannel.XName, xepgChannel.URL)
|
||||
if err == nil {
|
||||
lineup = append(lineup, stream)
|
||||
} else {
|
||||
ShowError(err, 1202)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jsonContent, err = json.MarshalIndent(lineup, "", " ")
|
||||
|
||||
Data.Cache.PMS = nil
|
||||
|
||||
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getGuideNumberPMS(channelName string) (pmsID string, err error) {
|
||||
|
||||
if len(Data.Cache.PMS) == 0 {
|
||||
|
||||
Data.Cache.PMS = make(map[string]string)
|
||||
|
||||
pms, err := loadJSONFileToMap(System.File.PMS)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for key, value := range pms {
|
||||
Data.Cache.PMS[key] = value.(string)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var getNewID = func(channelName string) (id string) {
|
||||
|
||||
var i int
|
||||
|
||||
newID:
|
||||
|
||||
var ids []string
|
||||
id = fmt.Sprintf("id-%d", i)
|
||||
|
||||
for _, v := range Data.Cache.PMS {
|
||||
ids = append(ids, v)
|
||||
}
|
||||
|
||||
if indexOfString(id, ids) != -1 {
|
||||
i++
|
||||
goto newID
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := Data.Cache.PMS[channelName]; ok {
|
||||
|
||||
pmsID = value
|
||||
|
||||
} else {
|
||||
|
||||
pmsID = getNewID(channelName)
|
||||
Data.Cache.PMS[channelName] = pmsID
|
||||
saveMapToJSONFile(System.File.PMS, Data.Cache.PMS)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
147
src/html-build.go
Normal file
147
src/html-build.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var htmlFolder string
|
||||
var goFile string
|
||||
var mapName string
|
||||
var packageName string
|
||||
|
||||
var blankMap = make(map[string]interface{})
|
||||
|
||||
// HTMLInit : Dateipfade festlegen
|
||||
// mapName = Name der zu erstellenden map
|
||||
// htmlFolder: Ordner der HTML Dateien
|
||||
// packageName: Name des package
|
||||
func HTMLInit(name, pkg, folder, file string) {
|
||||
|
||||
htmlFolder = folder
|
||||
goFile = file
|
||||
mapName = name
|
||||
packageName = pkg
|
||||
|
||||
}
|
||||
|
||||
// BuildGoFile : Erstellt das GO Dokument
|
||||
func BuildGoFile() error {
|
||||
|
||||
var err = checkHTMLFile(htmlFolder)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var content string
|
||||
content += `package ` + packageName + "\n\n"
|
||||
content += `var ` + mapName + ` = make(map[string]interface{})` + "\n\n"
|
||||
content += "func loadHTMLMap() {" + "\n\n"
|
||||
|
||||
content += createMapFromFiles(htmlFolder) + "\n"
|
||||
|
||||
content += "}" + "\n\n"
|
||||
writeStringToFile(goFile, content)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHTMLString : base64 -> string
|
||||
func GetHTMLString(base string) string {
|
||||
content, _ := base64.StdEncoding.DecodeString(base)
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func createMapFromFiles(folder string) string {
|
||||
|
||||
var path = getLocalPath(folder)
|
||||
|
||||
err := filepath.Walk(path, readFilesToMap)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
var content string
|
||||
|
||||
for key := range blankMap {
|
||||
var newKey = key
|
||||
content += ` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n"
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func readFilesToMap(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if info.IsDir() == false {
|
||||
var base64Str = fileToBase64(getLocalPath(path))
|
||||
blankMap[path] = base64Str
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileToBase64(file string) string {
|
||||
|
||||
imgFile, _ := os.Open(file)
|
||||
defer imgFile.Close()
|
||||
|
||||
// create a new buffer base on file size
|
||||
fInfo, _ := imgFile.Stat()
|
||||
var size = fInfo.Size()
|
||||
buf := make([]byte, int64(size))
|
||||
|
||||
// read file content into buffer
|
||||
fReader := bufio.NewReader(imgFile)
|
||||
fReader.Read(buf)
|
||||
|
||||
imgBase64Str := base64.StdEncoding.EncodeToString(buf)
|
||||
|
||||
return imgBase64Str
|
||||
}
|
||||
|
||||
func getLocalPath(filename string) string {
|
||||
|
||||
path, file := filepath.Split(filename)
|
||||
var newPath = filepath.Dir(path)
|
||||
|
||||
var newFileName = newPath + "/" + file
|
||||
|
||||
return newFileName
|
||||
}
|
||||
|
||||
func writeStringToFile(filename, content string) error {
|
||||
|
||||
err := ioutil.WriteFile(getPlatformFile(filename), []byte(content), 0644)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkHTMLFile(filename string) error {
|
||||
|
||||
if _, err := os.Stat(getLocalPath(filename)); os.IsNotExist(err) {
|
||||
fmt.Println(filename)
|
||||
checkErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
log.Println("ERROR: [", err, "] in ", file, line)
|
||||
}
|
||||
}
|
||||
153
src/images.go
Normal file
153
src/images.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getCacheImageURL(url string) (cacheImageURL string) {
|
||||
|
||||
url = strings.Trim(url, "\r\n")
|
||||
|
||||
var urlMD5 = getMD5(url)
|
||||
var fileExtension = filepath.Ext(url)
|
||||
|
||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
||||
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
||||
}
|
||||
|
||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
||||
return url
|
||||
}
|
||||
|
||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
|
||||
|
||||
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.WEB, System.Domain, urlMD5, fileExtension)
|
||||
|
||||
} else {
|
||||
|
||||
if strings.Contains(url, System.Domain+"/images/") == false {
|
||||
|
||||
if indexOfString(url, Data.Cache.ImagesURLS) == -1 {
|
||||
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, url)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cacheImageURL = url
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cachingImages() {
|
||||
|
||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
System.ImageCachingInProgress = 1
|
||||
|
||||
showInfo("Image Caching:Images are cached")
|
||||
|
||||
for _, url := range Data.Cache.ImagesURLS {
|
||||
|
||||
if len(url) > 0 {
|
||||
cacheImage(url)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showInfo("Image Caching:Done")
|
||||
|
||||
// Bilder die nicht mehr verwendet werden, werden gelöscht
|
||||
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
if indexOfString(file.Name(), Data.Cache.ImagesFiles) == -1 {
|
||||
|
||||
var debug = fmt.Sprintf("Image Caching:Remove file: %s %s %d", System.Folder.ImagesCache+file.Name(), file.Name(), len(file.Name()))
|
||||
showDebug(debug, 1)
|
||||
err := os.RemoveAll(System.Folder.ImagesCache + file.Name())
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cacheImage(url string) {
|
||||
|
||||
var debug string
|
||||
var urlMD5 = getMD5(url)
|
||||
var fileExtension = filepath.Ext(url)
|
||||
|
||||
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, url)
|
||||
showDebug(debug, 1)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
var filePath = System.Folder.ImagesCache + urlMD5 + fileExtension
|
||||
|
||||
// Datei speichern
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func uploadLogo(input, filename string) (logoURL string, err error) {
|
||||
|
||||
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||
|
||||
// BAse64 in bytes umwandeln un speichern
|
||||
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var file = fmt.Sprintf("%s%s", System.Folder.ImagesUpload, filename)
|
||||
|
||||
err = writeByteToFile(file, sDec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.WEB, System.Domain, filename)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
592
src/internal/authentication/authentication.go
Executable file
592
src/internal/authentication/authentication.go
Executable file
@@ -0,0 +1,592 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
|
||||
"time"
|
||||
//"fmt"
|
||||
//"log"
|
||||
)
|
||||
|
||||
const tokenLength = 40
|
||||
const saltLength = 20
|
||||
const idLength = 10
|
||||
|
||||
var tokenValidity int
|
||||
var database string
|
||||
|
||||
var databaseFile = "authentication.json"
|
||||
|
||||
var data = make(map[string]interface{})
|
||||
var tokens = make(map[string]interface{})
|
||||
|
||||
var initAuthentication = false
|
||||
|
||||
// Cookie : cookie
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
}
|
||||
|
||||
// Framework examples
|
||||
|
||||
/*
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
var checkErr = func(err error) {
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = Init("", 10) // Path to save the data, Validity of tokens in minutes | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
err = CreateDefaultUser("admin", "123")
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
err = CreateNewUser("xteve", "xteve") // Username, Password | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
err, token := UserAuthentication("xteve", "xteve") // Username, Password | (error, token)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("UserAuthentication()")
|
||||
fmt.Println("Token:", token)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err, newToken := CheckTheValidityOfTheToken(token) // Current token | (error, new token)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("CheckTheValidityOfTheToken()")
|
||||
fmt.Println("New Token:", newToken)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err, userID := GetUserID(newToken) // Current token | (error, user id)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("GetUserID()")
|
||||
fmt.Println("User ID:", userID)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
|
||||
var userData = make(map[string]interface{})
|
||||
userData["type"] = "Administrator"
|
||||
err = WriteUserData(userID, userData) // User id, user data | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
err, userData = ReadUserData(userID) // User id | (error, userData)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("ReadUserData()")
|
||||
fmt.Println("User data:", userData)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err = RemoveUser(userID)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// Init : databasePath = Path to authentication.json
|
||||
func Init(databasePath string, validity int) (err error) {
|
||||
database = filepath.Dir(databasePath) + string(os.PathSeparator) + databaseFile
|
||||
|
||||
// Check if the database already exists
|
||||
if _, err = os.Stat(database); os.IsNotExist(err) {
|
||||
// Create an empty database
|
||||
var defaults = make(map[string]interface{})
|
||||
defaults["dbVersion"] = "1.0"
|
||||
defaults["hash"] = "sha256"
|
||||
defaults["users"] = make(map[string]interface{})
|
||||
|
||||
if saveDatabase(defaults) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Loading the database
|
||||
err = loadDatabase()
|
||||
|
||||
// Set Token Validity
|
||||
tokenValidity = validity
|
||||
initAuthentication = true
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDefaultUser = created efault user
|
||||
func CreateDefaultUser(username, password string) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
// Check if the default user exists
|
||||
if len(users) > 0 {
|
||||
err = createError(001)
|
||||
return
|
||||
}
|
||||
|
||||
var defaults = defaultsForNewUser(username, password)
|
||||
users[defaults["_id"].(string)] = defaults
|
||||
saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateNewUser : create new user
|
||||
func CreateNewUser(username, password string) (userID string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var checkIfTheUserAlreadyExists = func(username string, userData map[string]interface{}) (err error) {
|
||||
var salt = userData["_salt"].(string)
|
||||
var loginUsername = userData["_username"].(string)
|
||||
|
||||
if SHA256(username, salt) == loginUsername {
|
||||
err = createError(020)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
for _, userData := range users {
|
||||
err = checkIfTheUserAlreadyExists(username, userData.(map[string]interface{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var defaults = defaultsForNewUser(username, password)
|
||||
userID = defaults["_id"].(string)
|
||||
users[userID] = defaults
|
||||
|
||||
saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UserAuthentication : user authentication
|
||||
func UserAuthentication(username, password string) (token string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var login = func(username, password string, loginData map[string]interface{}) (err error) {
|
||||
err = createError(010)
|
||||
|
||||
var salt = loginData["_salt"].(string)
|
||||
var loginUsername = loginData["_username"].(string)
|
||||
var loginPassword = loginData["_password"].(string)
|
||||
|
||||
if SHA256(username, salt) == loginUsername {
|
||||
if SHA256(password, salt) == loginPassword {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
for id, loginData := range users {
|
||||
err = login(username, password, loginData.(map[string]interface{}))
|
||||
if err == nil {
|
||||
token = setToken(id, "-")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTheValidityOfTheToken : check token
|
||||
func CheckTheValidityOfTheToken(token string) (newToken string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(011)
|
||||
|
||||
if v, ok := tokens[token]; ok {
|
||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||
var userID = v.(map[string]interface{})["id"].(string)
|
||||
|
||||
if expires.Sub(time.Now().Local()) < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newToken = setToken(userID, token)
|
||||
|
||||
err = nil
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserID : get user ID
|
||||
func GetUserID(token string) (userID string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(002)
|
||||
|
||||
if v, ok := tokens[token]; ok {
|
||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||
userID = v.(map[string]interface{})["id"].(string)
|
||||
|
||||
if expires.Sub(time.Now().Local()) < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WriteUserData : save user date
|
||||
func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(030)
|
||||
|
||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||
|
||||
v["data"] = userData
|
||||
err = saveDatabase(data)
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadUserData : load user date
|
||||
func ReadUserData(userID string) (userData map[string]interface{}, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(031)
|
||||
|
||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||
userData = v["data"].(map[string]interface{})
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveUser : remove user
|
||||
func RemoveUser(userID string) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(032)
|
||||
|
||||
if _, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||
|
||||
delete(data["users"].(map[string]interface{}), userID)
|
||||
err = saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetDefaultUserData : set default user data
|
||||
func SetDefaultUserData(defaults map[string]interface{}) (err error) {
|
||||
|
||||
allUserData, err := GetAllUserData()
|
||||
|
||||
for _, d := range allUserData {
|
||||
var data = d.(map[string]interface{})["data"].(map[string]interface{})
|
||||
var userID = d.(map[string]interface{})["_id"].(string)
|
||||
|
||||
for k, v := range defaults {
|
||||
if _, ok := data[k]; ok {
|
||||
// Key exist
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
err = WriteUserData(userID, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeCredentials : change credentials
|
||||
func ChangeCredentials(userID, username, password string) (err error) {
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(032)
|
||||
|
||||
if userData, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||
//var userData = tmp.(map[string]interface{})
|
||||
var salt = userData.(map[string]interface{})["_salt"].(string)
|
||||
|
||||
if len(username) > 0 {
|
||||
userData.(map[string]interface{})["_username"] = SHA256(username, salt)
|
||||
}
|
||||
|
||||
if len(password) > 0 {
|
||||
userData.(map[string]interface{})["_password"] = SHA256(password, salt)
|
||||
}
|
||||
|
||||
err = saveDatabase(data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllUserData : get all user data
|
||||
func GetAllUserData() (allUserData map[string]interface{}, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
var defaults = make(map[string]interface{})
|
||||
defaults["dbVersion"] = "1.0"
|
||||
defaults["hash"] = "sha256"
|
||||
defaults["users"] = make(map[string]interface{})
|
||||
saveDatabase(defaults)
|
||||
data = defaults
|
||||
}
|
||||
|
||||
allUserData = data["users"].(map[string]interface{})
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTheValidityOfTheTokenFromHTTPHeader : get token from HTTP header
|
||||
func CheckTheValidityOfTheTokenFromHTTPHeader(w http.ResponseWriter, r *http.Request) (writer http.ResponseWriter, newToken string, err error) {
|
||||
err = createError(011)
|
||||
for _, cookie := range r.Cookies() {
|
||||
if cookie.Name == "Token" {
|
||||
var token string
|
||||
token, err = CheckTheValidityOfTheToken(cookie.Value)
|
||||
//fmt.Println("T", token, err)
|
||||
writer = SetCookieToken(w, token)
|
||||
newToken = token
|
||||
}
|
||||
}
|
||||
//fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Framework tools
|
||||
|
||||
func checkInit() (err error) {
|
||||
if initAuthentication == false {
|
||||
err = createError(000)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func saveDatabase(tmpMap interface{}) (err error) {
|
||||
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(database, []byte(jsonString), 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadDatabase() (err error) {
|
||||
jsonString, err := ioutil.ReadFile(database)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(jsonString), &data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SHA256 : password + salt = sha256 string
|
||||
func SHA256(secret, salt string) string {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte("_remote_db"))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func randomString(n int) string {
|
||||
const alphanum = "-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789aBcDeFgHiJkLmNoPqRsTuVwXyZ_"
|
||||
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func randomID(n int) string {
|
||||
const alphanum = "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func createError(errCode int) (err error) {
|
||||
var errMsg string
|
||||
switch errCode {
|
||||
case 000:
|
||||
errMsg = "Authentication has not yet been initialized"
|
||||
case 001:
|
||||
errMsg = "Default user already exists"
|
||||
case 002:
|
||||
errMsg = "No user id found for this token"
|
||||
case 010:
|
||||
errMsg = "User authentication failed"
|
||||
case 011:
|
||||
errMsg = "Session has expired"
|
||||
case 020:
|
||||
errMsg = "User already exists"
|
||||
case 030:
|
||||
errMsg = "User data could not be saved"
|
||||
case 031:
|
||||
errMsg = "User data could not be read"
|
||||
case 032:
|
||||
errMsg = "User ID was not found"
|
||||
}
|
||||
|
||||
err = errors.New(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
func defaultsForNewUser(username, password string) map[string]interface{} {
|
||||
var defaults = make(map[string]interface{})
|
||||
var salt = randomString(saltLength)
|
||||
defaults["_username"] = SHA256(username, salt)
|
||||
defaults["_password"] = SHA256(password, salt)
|
||||
defaults["_salt"] = salt
|
||||
defaults["_id"] = "id-" + randomID(idLength)
|
||||
//defaults["_one.time.token"] = randomString(tokenLength)
|
||||
defaults["data"] = make(map[string]interface{})
|
||||
|
||||
return defaults
|
||||
}
|
||||
|
||||
func setToken(id, oldToken string) (newToken string) {
|
||||
delete(tokens, oldToken)
|
||||
|
||||
loopToken:
|
||||
newToken = randomString(tokenLength)
|
||||
if _, ok := tokens[newToken]; ok {
|
||||
goto loopToken
|
||||
}
|
||||
|
||||
var tmp = make(map[string]interface{})
|
||||
tmp["id"] = id
|
||||
tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
||||
|
||||
tokens[newToken] = tmp
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mapToJSON(tmpMap interface{}) string {
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(jsonString)
|
||||
}
|
||||
|
||||
// SetCookieToken : set cookie
|
||||
func SetCookieToken(w http.ResponseWriter, token string) http.ResponseWriter {
|
||||
expiration := time.Now().Add(time.Minute * time.Duration(tokenValidity))
|
||||
cookie := http.Cookie{Name: "Token", Value: token, Expires: expiration}
|
||||
http.SetCookie(w, &cookie)
|
||||
return w
|
||||
}
|
||||
84
src/internal/m3u-parser/m3u-parser_test.go
Normal file
84
src/internal/m3u-parser/m3u-parser_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package m3u
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type M3UStream struct {
|
||||
GroupTitle string `json:"group-title,required"`
|
||||
Name string `json:"name,required"`
|
||||
TvgID string `json:"tvg-id,required"`
|
||||
TvgLogo string `json:"tvg-logo,required"`
|
||||
TvgName string `json:"tvg-name,required"`
|
||||
URL string `json:"url,required"`
|
||||
UUIDKey string `json:"_uuid.key,omitempty"`
|
||||
UUIDValue string `json:"_uuid.value,omitempty"`
|
||||
}
|
||||
|
||||
func TestStream1(t *testing.T) {
|
||||
|
||||
var file = "test_list_1.m3u"
|
||||
var content, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
streams, err := MakeInterfaceFromM3U(content)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = checkStream(streams)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fmt.Println("Streams:", len(streams))
|
||||
t.Log(streams)
|
||||
|
||||
}
|
||||
|
||||
func checkStream(streamInterface []interface{}) (err error) {
|
||||
|
||||
for i, s := range streamInterface {
|
||||
|
||||
var stream = s.(map[string]string)
|
||||
var m3uStream M3UStream
|
||||
|
||||
jsonString, err := json.MarshalIndent(stream, "", " ")
|
||||
|
||||
if err == nil {
|
||||
|
||||
err = json.Unmarshal(jsonString, &m3uStream)
|
||||
if err == nil {
|
||||
|
||||
log.Print(fmt.Sprintf("Stream: %d", i))
|
||||
log.Print(fmt.Sprintf("Name*: %s", m3uStream.Name))
|
||||
log.Print(fmt.Sprintf("URL*: %s", m3uStream.URL))
|
||||
log.Print(fmt.Sprintf("tvg-name: %s", m3uStream.TvgName))
|
||||
log.Print(fmt.Sprintf("tvg-id**: %s", m3uStream.TvgID))
|
||||
log.Print(fmt.Sprintf("tvg-logo: %s", m3uStream.TvgLogo))
|
||||
log.Print(fmt.Sprintf("group-title**: %s", m3uStream.GroupTitle))
|
||||
|
||||
if len(m3uStream.UUIDKey) > 0 {
|
||||
log.Print(fmt.Sprintf("UUID key***: %s", m3uStream.UUIDKey))
|
||||
log.Print(fmt.Sprintf("UUID value: %s", m3uStream.UUIDValue))
|
||||
} else {
|
||||
log.Print(fmt.Sprintf("UUID key: false"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Println(fmt.Sprintf("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
7
src/internal/m3u-parser/test_list_1.m3u
Normal file
7
src/internal/m3u-parser/test_list_1.m3u
Normal file
@@ -0,0 +1,7 @@
|
||||
#EXTM3U url-tvg="http://example.com/file.xml" x-tvg-url="http://example.com/xteve.xml"
|
||||
#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1",Channel 1
|
||||
http://example.com/stream/1
|
||||
|
||||
#EXTINF:0 channelID="2" tvg-chno="2" tvg-name="Channel.2" tvg-id="tvg.id.2" tvg-logo="https://example/logo.png" group-title="Group 2",Channel 2
|
||||
#123
|
||||
http://example.com/stream/2
|
||||
267
src/internal/m3u-parser/xteve_m3uParser.go
Executable file
267
src/internal/m3u-parser/xteve_m3uParser.go
Executable file
@@ -0,0 +1,267 @@
|
||||
package m3u
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MakeInterfaceFromM3U :
|
||||
func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err error) {
|
||||
|
||||
var content = string(byteStream)
|
||||
var channelName string
|
||||
|
||||
var parseMetaData = func(channel string) (stream map[string]string) {
|
||||
|
||||
stream = make(map[string]string)
|
||||
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||
var exceptForChannelName = `,([^\n]*|,[^\r]*)`
|
||||
|
||||
var lines = strings.Split(strings.Replace(channel, "\r\n", "\n", -1), "\n")
|
||||
|
||||
// Zeilen mit # und leerer Zeilen entfernen
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
|
||||
if len(lines[i]) == 0 || lines[i][0:1] == "#" {
|
||||
lines = append(lines[:i], lines[i+1:]...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(lines) >= 2 {
|
||||
|
||||
for _, line := range lines {
|
||||
|
||||
_, err := url.ParseRequestURI(line)
|
||||
|
||||
switch err {
|
||||
|
||||
case nil:
|
||||
stream["url"] = strings.Trim(line, "\r\n")
|
||||
|
||||
default:
|
||||
|
||||
var value string
|
||||
// Alle Parameter parsen
|
||||
var p = regexp.MustCompile(exceptForParameter)
|
||||
var streamParameter = p.FindAllString(line, -1)
|
||||
|
||||
for _, p := range streamParameter {
|
||||
|
||||
line = strings.Replace(line, p, "", 1)
|
||||
|
||||
p = strings.Replace(p, `"`, "", -1)
|
||||
var parameter = strings.Split(p, "=")
|
||||
|
||||
if len(parameter) == 2 {
|
||||
|
||||
// TVG Key als Kleinbuchstaben speichern
|
||||
switch strings.Contains(parameter[0], "tvg") {
|
||||
|
||||
case true:
|
||||
stream[strings.ToLower(parameter[0])] = parameter[1]
|
||||
case false:
|
||||
stream[parameter[0]] = parameter[1]
|
||||
|
||||
}
|
||||
|
||||
// URL's nicht an die Filterfunktion übergeben
|
||||
if !strings.Contains(parameter[1], "://") && len(parameter[1]) > 0 {
|
||||
value = value + parameter[1] + " "
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Kanalnamen parsen
|
||||
n := regexp.MustCompile(exceptForChannelName)
|
||||
var name = n.FindAllString(line, 1)
|
||||
|
||||
if len(name) > 0 {
|
||||
channelName = name[0]
|
||||
channelName = strings.Replace(channelName, `,`, "", 1)
|
||||
channelName = strings.TrimRight(channelName, "\r\n")
|
||||
channelName = strings.TrimRight(channelName, " ")
|
||||
}
|
||||
|
||||
if len(channelName) == 0 {
|
||||
|
||||
if v, ok := stream["tvg-name"]; ok {
|
||||
channelName = v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
channelName = strings.TrimRight(channelName, " ")
|
||||
|
||||
// Kanäle ohne Namen werden augelassen
|
||||
if len(channelName) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stream["name"] = channelName
|
||||
value = value + channelName
|
||||
|
||||
stream["_values"] = value
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Nach eindeutiger ID im Stream suchen
|
||||
for key, value := range stream {
|
||||
|
||||
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||
|
||||
if strings.Contains(strings.ToLower(key), "id") {
|
||||
|
||||
stream["_uuid.key"] = key
|
||||
stream["_uuid.value"] = value
|
||||
//os.Exit(0)
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println(content)
|
||||
|
||||
if strings.Contains(content, "#EXTM3U") {
|
||||
|
||||
var channels = strings.Split(content, "#EXTINF")
|
||||
|
||||
channels = append(channels[:0], channels[1:]...)
|
||||
|
||||
for _, channel := range channels {
|
||||
|
||||
var stream = parseMetaData(channel)
|
||||
|
||||
if len(stream) > 0 && stream != nil {
|
||||
allChannels = append(allChannels, stream)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
err = errors.New("No valid m3u file")
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MakeInterfaceFromM3U2 :
|
||||
func MakeInterfaceFromM3U2(byteStream []byte) (allChannels []interface{}, err error) {
|
||||
var content = string(byteStream)
|
||||
//var allChannels = make([]interface{}, 0)
|
||||
|
||||
var channels = strings.Split(content, "#EXTINF")
|
||||
|
||||
var parseMetaData = func(metaData string) map[string]string {
|
||||
var values string // Save all values in a key
|
||||
var channel = make(map[string]string)
|
||||
|
||||
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||
//var exceptForChannelName = `(,[^.$\n]*|,[^.$\r]*)`
|
||||
var exceptForChannelName = `(,[^\n]*|,[^\r]*)`
|
||||
|
||||
var exceptForStreamingURL = `(\n.*?\n|\r.*?\r|\n.*?\z|\r.*?\z)`
|
||||
//var exceptForStreamingURL = `^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?`
|
||||
|
||||
// Parse all parameters
|
||||
p := regexp.MustCompile(exceptForParameter)
|
||||
var parameter = p.FindAllString(metaData, -1)
|
||||
//fmt.Println(parameter)
|
||||
for _, i := range parameter {
|
||||
var remove = i
|
||||
i = strings.Replace(i, `"`, "", -1)
|
||||
if strings.Contains(i, "=") {
|
||||
var item = strings.Split(i, "=")
|
||||
switch strings.Contains(item[0], "tvg") {
|
||||
case true:
|
||||
channel[strings.ToLower(item[0])] = item[1]
|
||||
case false:
|
||||
channel[item[0]] = item[1]
|
||||
}
|
||||
|
||||
switch strings.Contains(item[1], "://") {
|
||||
case false:
|
||||
values = values + item[1] + " "
|
||||
}
|
||||
|
||||
}
|
||||
metaData = strings.Replace(metaData, remove, "", 1)
|
||||
}
|
||||
|
||||
// Parse channel name (after the comma)
|
||||
n := regexp.MustCompile(exceptForChannelName)
|
||||
var name = n.FindAllString(metaData, 1)
|
||||
//name[len(name) - 1] = strings.Replace(name[len(name) - 1], `\r`, "", -1)
|
||||
|
||||
var channelName string
|
||||
if len(name) == 0 {
|
||||
if v, ok := channel["tvg-name"]; ok {
|
||||
channelName = v
|
||||
}
|
||||
} else {
|
||||
channelName = name[len(name)-1][1:len(name[len(name)-1])]
|
||||
}
|
||||
|
||||
channelName = strings.Replace(channelName, `"`, "", -1)
|
||||
|
||||
var replacer = strings.NewReplacer("\n", "", "\r", "")
|
||||
channel["name"] = replacer.Replace(channelName)
|
||||
|
||||
values = values + channelName + " "
|
||||
|
||||
// Parse streaming URL
|
||||
u := regexp.MustCompile(exceptForStreamingURL)
|
||||
var streamingURL = u.FindAllString(metaData, -1)
|
||||
var url = strings.Replace(streamingURL[0], "\n", "", -1)
|
||||
url = strings.Replace(url, "\r", "", -1)
|
||||
url = strings.Trim(url, "\r\n")
|
||||
channel["url"] = url
|
||||
|
||||
channel["_values"] = values
|
||||
|
||||
// Search for a unique ID
|
||||
|
||||
for key, value := range channel {
|
||||
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||
if strings.Contains(strings.ToLower(key), "id") {
|
||||
channel["_uuid.key"] = key
|
||||
channel["_uuid.value"] = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
if strings.Contains(channels[0], "#EXTM3U") {
|
||||
|
||||
for _, thisStream := range channels {
|
||||
if !strings.Contains(thisStream, "#EXTM3U") {
|
||||
var channel = parseMetaData(thisStream)
|
||||
allChannels = append(allChannels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
err = errors.New("No valid m3u file")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
129
src/internal/up2date/client/client.go
Executable file
129
src/internal/up2date/client/client.go
Executable file
@@ -0,0 +1,129 @@
|
||||
package up2date
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientInfo : Information about the key (NAME OS, ARCH, UUID, KEY)
|
||||
type ClientInfo struct {
|
||||
Arch string `json:"arch,required"`
|
||||
Branch string `json:"branch,required"`
|
||||
CMD string `json:"cmd,omitempty"`
|
||||
Name string `json:"name,required"`
|
||||
OS string `json:"os,required"`
|
||||
URL string `json:"url,required"`
|
||||
|
||||
Response ServerResponse `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
//ServerResponse : Response from server after client request
|
||||
type ServerResponse struct {
|
||||
Status bool `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
UpdateBIN string `json:"update.url.bin,omitempty"`
|
||||
UpdateZIP string `json:"update.url.zip,omitempty"`
|
||||
Filename string `json:"filename.bin,omitempty"`
|
||||
}
|
||||
|
||||
// Updater : Client infos
|
||||
var Updater ClientInfo
|
||||
|
||||
// UpdateURL : URL for the new binary
|
||||
var UpdateURL string
|
||||
|
||||
// Init : Init
|
||||
func Init() {
|
||||
Updater.OS = runtime.GOOS
|
||||
Updater.Arch = runtime.GOARCH
|
||||
}
|
||||
|
||||
// GetVersion : Information about the latest version
|
||||
func GetVersion() (err error) {
|
||||
|
||||
Updater.CMD = "getVersion"
|
||||
err = serverRequest()
|
||||
return
|
||||
}
|
||||
|
||||
func serverRequest() (err error) {
|
||||
|
||||
var serverResponse ServerResponse
|
||||
jsonByte, err := json.MarshalIndent(Updater, "", " ")
|
||||
if err == nil {
|
||||
|
||||
// Serververbindung prüfen
|
||||
u, err := url.Parse(Updater.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var server = u.Host
|
||||
|
||||
timeout := time.Duration(1 * time.Second)
|
||||
_, err = net.DialTimeout("tcp", server, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check redirect 301 <---> 308
|
||||
redirect, err := http.NewRequest("POST", Updater.URL, nil)
|
||||
|
||||
client := &http.Client{}
|
||||
client.CheckRedirect = func(redirect *http.Request, via []*http.Request) error {
|
||||
return errors.New("Redirect")
|
||||
}
|
||||
|
||||
resp, err := client.Do(redirect)
|
||||
|
||||
if err != nil {
|
||||
// Redirect
|
||||
if resp.StatusCode >= 301 && resp.StatusCode <= 308 { //status code 301 <---> 308
|
||||
Updater.URL = resp.Header.Get("Location")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// ---
|
||||
|
||||
req, err := http.NewRequest("POST", Updater.URL, bytes.NewBuffer(jsonByte))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client = &http.Client{}
|
||||
resp, err = client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
//fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD)
|
||||
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL))
|
||||
return err
|
||||
}
|
||||
|
||||
Updater.CMD = ""
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
err = json.Unmarshal(body, &serverResponse)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Updater.Response = serverResponse
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
271
src/internal/up2date/client/update.go
Executable file
271
src/internal/up2date/client/update.go
Executable file
@@ -0,0 +1,271 @@
|
||||
package up2date
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
// DoUpdate : Update binary
|
||||
func DoUpdate(fileType, filenameBIN string) (err error) {
|
||||
|
||||
var url string
|
||||
switch fileType {
|
||||
case "bin":
|
||||
url = Updater.Response.UpdateBIN
|
||||
case "zip":
|
||||
url = Updater.Response.UpdateZIP
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
filenameBIN = filenameBIN + ".exe"
|
||||
}
|
||||
|
||||
if len(url) > 0 {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "New version ("+Updater.Name+"):", Updater.Response.Version)
|
||||
|
||||
// Download new binary
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...")
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...OK")
|
||||
return fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Change binary filename to .filename
|
||||
binary, err := osext.Executable()
|
||||
var filename = getFilenameFromPath(binary)
|
||||
var path = getPlatformPath(binary)
|
||||
var oldBinary = path + "_old_" + filename
|
||||
var newBinary = binary
|
||||
|
||||
// ZIP
|
||||
var tmpFolder = path + "tmp"
|
||||
var tmpFile = tmpFolder + string(os.PathSeparator) + filenameBIN
|
||||
|
||||
//fmt.Println(binary, path+"."+filename)
|
||||
os.Rename(newBinary, oldBinary)
|
||||
|
||||
// Save the new binary with the old file name
|
||||
out, err := os.Create(binary)
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update as a ZIP file
|
||||
if fileType == "zip" {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Update file:", filenameBIN)
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...")
|
||||
err = extractZIP(binary, tmpFolder)
|
||||
|
||||
binary = newBinary
|
||||
|
||||
if err != nil {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...ERROR")
|
||||
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
|
||||
return err
|
||||
} else {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...OK")
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...")
|
||||
|
||||
err = copyFile(tmpFile, binary)
|
||||
if err == nil {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...OK")
|
||||
} else {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...ERROR")
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
os.RemoveAll(tmpFolder)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set the permission
|
||||
err = os.Chmod(binary, 0755)
|
||||
|
||||
// Close the new file !Windows
|
||||
out.Close()
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Update Successful")
|
||||
|
||||
// Restart binary (Windows)
|
||||
if runtime.GOOS == "windows" {
|
||||
|
||||
bin, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
|
||||
var pid = os.Getpid()
|
||||
var process, _ = os.FindProcess(pid)
|
||||
|
||||
if proc, err := start(bin); err == nil {
|
||||
|
||||
os.RemoveAll(oldBinary)
|
||||
process.Kill()
|
||||
proc.Wait()
|
||||
|
||||
} else {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Restart binary (Linux and UNIX)
|
||||
file, _ := osext.Executable()
|
||||
os.RemoveAll(oldBinary)
|
||||
err = syscall.Exec(file, os.Args, os.Environ())
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func start(args ...string) (p *os.Process, err error) {
|
||||
|
||||
if args[0], err = exec.LookPath(args[0]); err == nil {
|
||||
//fmt.Println(args[0])
|
||||
var procAttr os.ProcAttr
|
||||
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
p, err := os.StartProcess(args[0], args, &procAttr)
|
||||
|
||||
if err == nil {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func restorOldBinary(oldBinary, newBinary string) {
|
||||
os.RemoveAll(newBinary)
|
||||
os.Rename(oldBinary, newBinary)
|
||||
}
|
||||
|
||||
func getPlatformFile(filename string) string {
|
||||
|
||||
path, file := filepath.Split(filename)
|
||||
var newPath = filepath.Dir(path)
|
||||
var newFileName = newPath + string(os.PathSeparator) + file
|
||||
|
||||
return newFileName
|
||||
}
|
||||
|
||||
func getFilenameFromPath(path string) string {
|
||||
|
||||
file := filepath.Base(path)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func getPlatformPath(path string) string {
|
||||
|
||||
var newPath = filepath.Dir(path) + string(os.PathSeparator)
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func extractZIP(archive, target string) (err error) {
|
||||
|
||||
reader, err := zip.OpenReader(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range reader.File {
|
||||
|
||||
path := filepath.Join(target, file.Name)
|
||||
if file.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, file.Mode())
|
||||
continue
|
||||
}
|
||||
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
if _, err := io.Copy(targetFile, fileReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
238
src/m3u.go
Normal file
238
src/m3u.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
m3u "../src/internal/m3u-parser"
|
||||
)
|
||||
|
||||
// Playlisten parsen
|
||||
func parsePlaylist(filename, fileType string) (channels []interface{}, err error) {
|
||||
|
||||
content, err := readByteFromFile(filename)
|
||||
var id = strings.TrimSuffix(getFilenameFromPath(filename), path.Ext(getFilenameFromPath(filename)))
|
||||
var playlistName = getProviderParameter(id, fileType, "name")
|
||||
|
||||
if err == nil {
|
||||
|
||||
switch fileType {
|
||||
case "m3u":
|
||||
channels, err = m3u.MakeInterfaceFromM3U(content)
|
||||
case "hdhr":
|
||||
channels, err = makeInteraceFromHDHR(content, playlistName, id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Streams filtern
|
||||
func filterThisStream(s interface{}) (status bool) {
|
||||
|
||||
status = false
|
||||
var stream = s.(map[string]string)
|
||||
var regexpYES = `[{]+[^.]+[}]`
|
||||
var regexpNO = `!+[{]+[^.]+[}]`
|
||||
|
||||
for _, filter := range Data.Filter {
|
||||
|
||||
var group, name, search string
|
||||
var exclude, include string
|
||||
var match = false
|
||||
|
||||
var streamValues = strings.Replace(stream["_values"], "\r", "", -1)
|
||||
|
||||
if v, ok := stream["group-title"]; ok {
|
||||
group = v
|
||||
}
|
||||
|
||||
if v, ok := stream["name"]; ok {
|
||||
name = v
|
||||
}
|
||||
|
||||
// Unerwünschte Streams !{DEU}
|
||||
r := regexp.MustCompile(regexpNO)
|
||||
val := r.FindStringSubmatch(filter.Rule)
|
||||
|
||||
if len(val) == 1 {
|
||||
|
||||
exclude = val[0][2 : len(val[0])-1]
|
||||
filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1)
|
||||
filter.Rule = strings.Replace(filter.Rule, val[0], "", -1)
|
||||
|
||||
}
|
||||
|
||||
// Muss zusätzlich erfüllt sein {DEU}
|
||||
r = regexp.MustCompile(regexpYES)
|
||||
val = r.FindStringSubmatch(filter.Rule)
|
||||
|
||||
if len(val) == 1 {
|
||||
|
||||
include = val[0][1 : len(val[0])-1]
|
||||
filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1)
|
||||
filter.Rule = strings.Replace(filter.Rule, val[0], "", -1)
|
||||
|
||||
}
|
||||
|
||||
switch filter.CaseSensitive {
|
||||
|
||||
case false:
|
||||
|
||||
streamValues = strings.ToLower(streamValues)
|
||||
filter.Rule = strings.ToLower(filter.Rule)
|
||||
exclude = strings.ToLower(exclude)
|
||||
include = strings.ToLower(include)
|
||||
group = strings.ToLower(group)
|
||||
name = strings.ToLower(name)
|
||||
|
||||
}
|
||||
|
||||
switch filter.Type {
|
||||
|
||||
case "group-title":
|
||||
search = name
|
||||
|
||||
if group == filter.Rule {
|
||||
match = true
|
||||
}
|
||||
|
||||
case "custom-filter":
|
||||
search = streamValues
|
||||
if strings.Contains(search, filter.Rule) {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
if match == true {
|
||||
|
||||
if len(exclude) > 0 {
|
||||
var status = checkConditions(search, exclude, "exclude")
|
||||
if status == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(include) > 0 {
|
||||
var status = checkConditions(search, include, "include")
|
||||
if status == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Bedingungen für den Filter
|
||||
func checkConditions(streamValues, conditions, coType string) (status bool) {
|
||||
|
||||
switch coType {
|
||||
|
||||
case "exclude":
|
||||
status = true
|
||||
|
||||
case "include":
|
||||
status = false
|
||||
|
||||
}
|
||||
|
||||
conditions = strings.Replace(conditions, ", ", ",", -1)
|
||||
conditions = strings.Replace(conditions, " ,", ",", -1)
|
||||
|
||||
var keys = strings.Split(conditions, ",")
|
||||
|
||||
for _, key := range keys {
|
||||
|
||||
if strings.Contains(streamValues, key) {
|
||||
|
||||
switch coType {
|
||||
|
||||
case "exclude":
|
||||
return false
|
||||
|
||||
case "include":
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// xTeVe M3U Datei erstellen
|
||||
func buildM3U(groups []string) (m3u string, err error) {
|
||||
|
||||
var m3uChannels = make(map[float64]XEPGChannelStruct)
|
||||
var channelNumbers []float64
|
||||
|
||||
for _, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err == nil {
|
||||
|
||||
if xepgChannel.XActive == true {
|
||||
|
||||
if len(groups) > 0 {
|
||||
|
||||
if indexOfString(xepgChannel.XGroupTitle, groups) == -1 {
|
||||
goto Done
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var channelNumber, err = strconv.ParseFloat(strings.TrimSpace(xepgChannel.XChannelID), 64)
|
||||
|
||||
if err == nil {
|
||||
m3uChannels[channelNumber] = xepgChannel
|
||||
channelNumbers = append(channelNumbers, channelNumber)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Done:
|
||||
}
|
||||
|
||||
// M3U Inhalt erstellen
|
||||
sort.Float64s(channelNumbers)
|
||||
|
||||
var xmltvURL = fmt.Sprintf("%s://%s/xmltv/xteve.xml", System.ServerProtocol.XML, System.Domain)
|
||||
m3u = fmt.Sprintf(`#EXTM3U url-tvg="%s" x-tvg-url="%s"`+"\n", xmltvURL, xmltvURL)
|
||||
|
||||
for _, channelNumber := range channelNumbers {
|
||||
|
||||
var channel = m3uChannels[channelNumber]
|
||||
var parameter = fmt.Sprintf(`#EXTINF:0 channelID="%s" tvg-chno="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s" group-title="%s",%s`+"\n", channel.XEPG, channel.XChannelID, channel.XName, channel.XChannelID, getCacheImageURL(channel.TvgLogo), channel.XGroupTitle, channel.XName)
|
||||
var stream, err = createStreamingURL("M3U", channel.FileM3UID, channel.XChannelID, channel.XName, channel.URL)
|
||||
if err == nil {
|
||||
m3u = m3u + parameter + stream + "\n"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
|
||||
var filename = System.Folder.Data + "xteve.m3u"
|
||||
err = writeByteToFile(filename, []byte(m3u))
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
84
src/maintenance.go
Normal file
84
src/maintenance.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InitMaintenance : Wartungsprozess initialisieren
|
||||
func InitMaintenance() (err error) {
|
||||
|
||||
rand.Seed(time.Now().Unix())
|
||||
System.TimeForAutoUpdate = fmt.Sprintf("0%d%d", randomTime(0, 2), randomTime(10, 59))
|
||||
|
||||
go maintenance()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func maintenance() {
|
||||
|
||||
for {
|
||||
|
||||
var t = time.Now()
|
||||
|
||||
// Aktualisierung der Playlist und XMLTV Dateien
|
||||
if System.ScanInProgress == 0 {
|
||||
|
||||
for _, schedule := range Settings.Update {
|
||||
|
||||
if schedule == t.Format("1504") {
|
||||
|
||||
showInfo("Update:" + schedule)
|
||||
|
||||
// Backup erstellen
|
||||
err := xTeVeAutoBackup()
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
}
|
||||
|
||||
// Playlist und XMLTV Dateien aktualisieren
|
||||
getProviderData("m3u", "")
|
||||
getProviderData("hdhr", "")
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
getProviderData("xmltv", "")
|
||||
}
|
||||
|
||||
// Datenbank für DVR erstellen
|
||||
err = buildDatabaseDVR()
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
}
|
||||
|
||||
if Settings.CacheImages == false && System.ImageCachingInProgress == 0 {
|
||||
removeChildItems(System.Folder.ImagesCache)
|
||||
}
|
||||
|
||||
// XEPG Dateien erstellen
|
||||
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||
buildXEPG(false)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update xTeVe (Binary)
|
||||
if System.TimeForAutoUpdate == t.Format("1504") {
|
||||
BinaryUpdate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
time.Sleep(60 * time.Second)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func randomTime(min, max int) int {
|
||||
rand.Seed(time.Now().Unix())
|
||||
return rand.Intn(max-min) + min
|
||||
}
|
||||
323
src/provider.go
Normal file
323
src/provider.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
m3u "../src/internal/m3u-parser"
|
||||
)
|
||||
|
||||
// fileType: Welcher Dateityp soll aktualisiert werden (m3u, hdhr, xml) | fileID: Update einer bestimmten Datei (Provider ID)
|
||||
func getProviderData(fileType, fileID string) (err error) {
|
||||
|
||||
var fileExtension, serverFileName string
|
||||
var body = make([]byte, 0)
|
||||
var newProvider = false
|
||||
var dataMap = make(map[string]interface{})
|
||||
|
||||
var saveDateFromProvider = func(fileSource, serverFileName, id string, body []byte) (err error) {
|
||||
|
||||
var data = make(map[string]interface{})
|
||||
|
||||
if value, ok := dataMap[id].(map[string]interface{}); ok {
|
||||
data = value
|
||||
} else {
|
||||
data["id.provider"] = id
|
||||
dataMap[id] = data
|
||||
}
|
||||
|
||||
// Default keys für die Providerdaten
|
||||
var keys = []string{"name", "description", "type", "file." + System.AppName, "file.source", "tuner", "last.update", "compatibility", "counter.error", "counter.download", "provider.availability"}
|
||||
|
||||
for _, key := range keys {
|
||||
|
||||
if _, ok := data[key]; !ok {
|
||||
|
||||
switch key {
|
||||
|
||||
case "name":
|
||||
data[key] = serverFileName
|
||||
|
||||
case "description":
|
||||
data[key] = ""
|
||||
|
||||
case "type":
|
||||
data[key] = fileType
|
||||
|
||||
case "file." + System.AppName:
|
||||
data[key] = id + fileExtension
|
||||
|
||||
case "file.source":
|
||||
data[key] = fileSource
|
||||
|
||||
case "last.update":
|
||||
data[key] = time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
case "tuner":
|
||||
if fileType == "m3u" || fileType == "hdhr" {
|
||||
if _, ok := data[key].(float64); !ok {
|
||||
data[key] = 1
|
||||
}
|
||||
}
|
||||
|
||||
case "compatibility":
|
||||
data[key] = make(map[string]interface{})
|
||||
|
||||
case "counter.download":
|
||||
data[key] = 0.0
|
||||
|
||||
case "counter.error":
|
||||
data[key] = 0.0
|
||||
|
||||
case "provider.availability":
|
||||
data[key] = 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if _, ok := data["id.provider"]; !ok {
|
||||
data["id.provider"] = id
|
||||
}
|
||||
|
||||
// Datei extrahieren
|
||||
body, err = extractGZIP(body, fileSource)
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
return
|
||||
}
|
||||
|
||||
// Daten überprüfen
|
||||
showInfo("Check File:" + fileSource)
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
_, err = m3u.MakeInterfaceFromM3U(body)
|
||||
|
||||
case "hdhr":
|
||||
_, err = jsonToInterface(string(body))
|
||||
|
||||
case "xmltv":
|
||||
err = checkXMLCompatibility(id, body)
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var filePath = System.Folder.Data + data["file."+System.AppName].(string)
|
||||
|
||||
err = writeByteToFile(filePath, body)
|
||||
|
||||
if err == nil {
|
||||
data["last.update"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
data["counter.download"] = data["counter.download"].(float64) + 1
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
dataMap = Settings.Files.M3U
|
||||
fileExtension = ".m3u"
|
||||
|
||||
case "hdhr":
|
||||
dataMap = Settings.Files.HDHR
|
||||
fileExtension = ".json"
|
||||
|
||||
case "xmltv":
|
||||
dataMap = Settings.Files.XMLTV
|
||||
fileExtension = ".xml"
|
||||
|
||||
}
|
||||
|
||||
for dataID, d := range dataMap {
|
||||
|
||||
var data = d.(map[string]interface{})
|
||||
var fileSource = data["file.source"].(string)
|
||||
newProvider = false
|
||||
|
||||
if _, ok := data["new"]; ok {
|
||||
newProvider = true
|
||||
delete(data, "new")
|
||||
}
|
||||
|
||||
// Wenn eine ID vorhanden ist und nicht mit der aus der Datanbank übereinstimmt, wird die Aktualisierung übersprungen (goto)
|
||||
if len(fileID) > 0 && newProvider == false {
|
||||
if dataID != fileID {
|
||||
goto Done
|
||||
}
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "hdhr":
|
||||
|
||||
// Laden vom HDHomeRun Tuner
|
||||
showInfo("Tuner:" + fileSource)
|
||||
var tunerURL = "http://" + fileSource + "/lineup.json"
|
||||
serverFileName, body, err = downloadFileFromServer(tunerURL)
|
||||
|
||||
default:
|
||||
|
||||
if strings.Contains(fileSource, "http://") || strings.Contains(fileSource, "https://") {
|
||||
|
||||
// Laden vom Remote Server
|
||||
showInfo("Download:" + fileSource)
|
||||
serverFileName, body, err = downloadFileFromServer(fileSource)
|
||||
|
||||
} else {
|
||||
|
||||
// Laden einer lokalen Datei
|
||||
showInfo("Open:" + fileSource)
|
||||
|
||||
err = checkFile(fileSource)
|
||||
if err == nil {
|
||||
body, err = readByteFromFile(fileSource)
|
||||
serverFileName = getFilenameFromPath(fileSource)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
|
||||
err = saveDateFromProvider(fileSource, serverFileName, dataID, body)
|
||||
if err == nil {
|
||||
showInfo("Save File:" + fileSource + " [ID: " + dataID + "]")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
ShowError(err, 000)
|
||||
var downloadErr = err
|
||||
|
||||
if newProvider == false {
|
||||
|
||||
// Prüfen ob ältere Datei vorhanden ist
|
||||
var file = System.Folder.Data + dataID + fileExtension
|
||||
|
||||
err = checkFile(file)
|
||||
if err == nil {
|
||||
|
||||
if len(fileID) == 0 {
|
||||
showWarning(1011)
|
||||
}
|
||||
|
||||
err = downloadErr
|
||||
}
|
||||
|
||||
// Fehler Counter um 1 erhöhen
|
||||
var data = make(map[string]interface{})
|
||||
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
||||
|
||||
data = value
|
||||
data["counter.error"] = data["counter.error"].(float64) + 1
|
||||
data["counter.download"] = data["counter.download"].(float64) + 1
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
return downloadErr
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Berechnen der Fehlerquote
|
||||
if newProvider == false {
|
||||
|
||||
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
||||
|
||||
var data = make(map[string]interface{})
|
||||
data = value
|
||||
|
||||
if data["counter.error"].(float64) == 0 {
|
||||
data["provider.availability"] = 100
|
||||
} else {
|
||||
data["provider.availability"] = int(data["counter.error"].(float64)*100/data["counter.download"].(float64)*-1 + 100)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
|
||||
case "m3u":
|
||||
Settings.Files.M3U = dataMap
|
||||
|
||||
case "hdhr":
|
||||
Settings.Files.HDHR = dataMap
|
||||
|
||||
case "xmltv":
|
||||
Settings.Files.XMLTV = dataMap
|
||||
delete(Data.Cache.XMLTV, System.Folder.Data+dataID+fileExtension)
|
||||
|
||||
}
|
||||
|
||||
saveSettings(Settings)
|
||||
|
||||
Done:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func downloadFileFromServer(providerURL string) (filename string, body []byte, err error) {
|
||||
|
||||
_, err = url.ParseRequestURI(providerURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := http.Get(providerURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Header.Set("User-Agent", Settings.UserAgent)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf(fmt.Sprintf("%d: %s "+http.StatusText(resp.StatusCode), resp.StatusCode, providerURL))
|
||||
return
|
||||
}
|
||||
|
||||
// Dateiname aus dem Header holen
|
||||
var index = strings.Index(resp.Header.Get("Content-Disposition"), "filename")
|
||||
|
||||
if index > -1 {
|
||||
|
||||
var headerFilename = resp.Header.Get("Content-Disposition")[index:len(resp.Header.Get("Content-Disposition"))]
|
||||
var value = strings.Split(headerFilename, `=`)
|
||||
var f = strings.Replace(value[1], `"`, "", -1)
|
||||
|
||||
f = strings.Replace(f, `;`, "", -1)
|
||||
filename = f
|
||||
showInfo("Header filename:" + filename)
|
||||
|
||||
} else {
|
||||
|
||||
var cleanFilename = strings.SplitN(getFilenameFromPath(providerURL), "?", 2)
|
||||
filename = cleanFilename[0]
|
||||
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
404
src/screen.go
Normal file
404
src/screen.go
Normal file
@@ -0,0 +1,404 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func showInfo(str string) {
|
||||
|
||||
var max = 22
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
|
||||
if len(msg) == 2 {
|
||||
|
||||
for i := length; i < max; i++ {
|
||||
space = space + " "
|
||||
}
|
||||
|
||||
msg[0] = msg[0] + ":" + space
|
||||
|
||||
var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1])
|
||||
|
||||
printLogOnScreen(logMsg, "info")
|
||||
|
||||
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
logCleanUp()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func showDebug(str string, level int) {
|
||||
|
||||
if System.Flag.Debug < level {
|
||||
return
|
||||
}
|
||||
|
||||
var max = 22
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
if len(msg) == 2 {
|
||||
|
||||
for i := length; i < max; i++ {
|
||||
space = space + " "
|
||||
}
|
||||
msg[0] = msg[0] + ":" + space
|
||||
|
||||
var logMsg = fmt.Sprintf("[DEBUG] %s%s", msg[0], msg[1])
|
||||
|
||||
printLogOnScreen(logMsg, "debug")
|
||||
|
||||
mutex.Lock()
|
||||
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
logCleanUp()
|
||||
mutex.Unlock()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func showHighlight(str string) {
|
||||
|
||||
var max = 22
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
|
||||
var notification Notification
|
||||
notification.Type = "info"
|
||||
|
||||
if len(msg) == 2 {
|
||||
|
||||
for i := length; i < max; i++ {
|
||||
space = space + " "
|
||||
}
|
||||
|
||||
msg[0] = msg[0] + ":" + space
|
||||
|
||||
var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1])
|
||||
|
||||
printLogOnScreen(logMsg, "highlight")
|
||||
|
||||
}
|
||||
|
||||
notification.Type = "info"
|
||||
notification.Message = msg[1]
|
||||
|
||||
addNotification(notification)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func showWarning(errCode int) {
|
||||
|
||||
var errMsg = getErrMsg(errCode)
|
||||
var logMsg = fmt.Sprintf("[%s] [WARNING] %s", System.Name, errMsg)
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
printLogOnScreen(logMsg, "warning")
|
||||
|
||||
mutex.Lock()
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
WebScreenLog.Warnings++
|
||||
mutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ShowError : Zeigt die Fehlermeldungen in der Konsole
|
||||
func ShowError(err error, errCode int) {
|
||||
|
||||
var mutex = sync.RWMutex{}
|
||||
|
||||
var errMsg = getErrMsg(errCode)
|
||||
var logMsg = fmt.Sprintf("[%s] [ERROR] %s (%s) - EC: %d", System.Name, err, errMsg, errCode)
|
||||
|
||||
printLogOnScreen(logMsg, "error")
|
||||
|
||||
mutex.Lock()
|
||||
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||
WebScreenLog.Errors++
|
||||
mutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printLogOnScreen(logMsg string, logType string) {
|
||||
|
||||
var color string
|
||||
|
||||
switch logType {
|
||||
|
||||
case "info":
|
||||
color = "\033[0m"
|
||||
|
||||
case "debug":
|
||||
color = "\033[35m"
|
||||
|
||||
case "highlight":
|
||||
color = "\033[32m"
|
||||
|
||||
case "warning":
|
||||
color = "\033[33m"
|
||||
|
||||
case "error":
|
||||
color = "\033[31m"
|
||||
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
|
||||
case "windows":
|
||||
log.Println(logMsg)
|
||||
|
||||
default:
|
||||
fmt.Print(color)
|
||||
log.Println(logMsg)
|
||||
fmt.Print("\033[0m")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func logCleanUp() {
|
||||
|
||||
var logEntriesRAM = Settings.LogEntriesRAM
|
||||
var logs = WebScreenLog.Log
|
||||
|
||||
WebScreenLog.Warnings = 0
|
||||
WebScreenLog.Errors = 0
|
||||
|
||||
if len(logs) > logEntriesRAM {
|
||||
|
||||
var tmp = make([]string, 0)
|
||||
for i := len(logs) - logEntriesRAM; i < logEntriesRAM; i++ {
|
||||
tmp = append(tmp, logs[i])
|
||||
}
|
||||
|
||||
logs = tmp
|
||||
}
|
||||
|
||||
for _, log := range logs {
|
||||
|
||||
if strings.Contains(log, "WARNING") {
|
||||
WebScreenLog.Warnings++
|
||||
}
|
||||
|
||||
if strings.Contains(log, "ERROR") {
|
||||
WebScreenLog.Errors++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WebScreenLog.Log = logs
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Fehlercodes
|
||||
func getErrMsg(errCode int) (errMsg string) {
|
||||
|
||||
switch errCode {
|
||||
|
||||
case 0:
|
||||
return
|
||||
|
||||
// Errors
|
||||
case 1001:
|
||||
errMsg = fmt.Sprintf("Web server could not be started.")
|
||||
case 1002:
|
||||
errMsg = fmt.Sprintf("No local IP address found.")
|
||||
case 1003:
|
||||
errMsg = fmt.Sprintf("Invalid xml")
|
||||
case 1004:
|
||||
errMsg = fmt.Sprintf("File not found")
|
||||
case 1005:
|
||||
errMsg = fmt.Sprintf("Invalide m3u")
|
||||
case 1006:
|
||||
errMsg = fmt.Sprintf("No playlist!")
|
||||
case 1007:
|
||||
errMsg = fmt.Sprintf("XEPG requires an XMLTV file.")
|
||||
case 1010:
|
||||
errMsg = fmt.Sprintf("Invalid file compression")
|
||||
case 1011:
|
||||
errMsg = fmt.Sprintf("Data is corrupt or unavailable, %s now uses an older version of this file", System.Name)
|
||||
case 1012:
|
||||
errMsg = fmt.Sprintf("Invalid formatting of the time")
|
||||
case 1013:
|
||||
errMsg = fmt.Sprintf("Invalid settings file (%s), file must be at least version %s", System.File.Settings, System.Compatibility)
|
||||
|
||||
case 1020:
|
||||
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
|
||||
|
||||
// Datenbank Update
|
||||
case 1030:
|
||||
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
|
||||
|
||||
// M3U Parser
|
||||
case 1050:
|
||||
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
|
||||
|
||||
// M3U Parser
|
||||
case 1060:
|
||||
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
|
||||
|
||||
// Dateisystem
|
||||
case 1070:
|
||||
errMsg = fmt.Sprintf("Folder could not be created.")
|
||||
case 1071:
|
||||
errMsg = fmt.Sprintf("File could not be created")
|
||||
|
||||
// Backup
|
||||
case 1090:
|
||||
errMsg = fmt.Sprintf("Automatic backup failed")
|
||||
|
||||
// Websockets
|
||||
case 1100:
|
||||
errMsg = fmt.Sprintf("WebUI build error")
|
||||
case 1101:
|
||||
errMsg = fmt.Sprintf("WebUI request error")
|
||||
case 1102:
|
||||
errMsg = fmt.Sprintf("WebUI response error")
|
||||
|
||||
// PMS Guide Numbers
|
||||
case 1200:
|
||||
errMsg = fmt.Sprintf("Could not create file")
|
||||
|
||||
// Stream URL Fehler
|
||||
case 1201:
|
||||
errMsg = fmt.Sprintf("Plex stream error")
|
||||
case 1202:
|
||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||
case 1203:
|
||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||
|
||||
// Warnings
|
||||
case 2000:
|
||||
errMsg = fmt.Sprintf("Plex can not handle more than %d streams. If you do not use Plex, you can ignore this warning.", System.DVRLimit)
|
||||
case 2001:
|
||||
errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.DVRLimit)
|
||||
case 2002:
|
||||
errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
|
||||
case 2003:
|
||||
errMsg = fmt.Sprintf("PMS can not play streams over RTSP.")
|
||||
case 2004:
|
||||
errMsg = fmt.Sprintf("Buffer is disabled for this stream.")
|
||||
case 2005:
|
||||
errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
|
||||
case 2010:
|
||||
errMsg = fmt.Sprintf("No valid streaming URL")
|
||||
|
||||
case 2099:
|
||||
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
|
||||
|
||||
// Tuner
|
||||
case 2105:
|
||||
errMsg = fmt.Sprintf("The number of tuners has changed, you have to delete " + System.Name + " in Plex / Emby HDHR and set it up again.")
|
||||
case 2106:
|
||||
errMsg = fmt.Sprintf("This function is only available with XEPG as EPG source")
|
||||
|
||||
case 2110:
|
||||
errMsg = fmt.Sprintf("Don't run this as Root!")
|
||||
|
||||
case 2300:
|
||||
errMsg = fmt.Sprintf("No channel logo found in the XMLTV or M3U file.")
|
||||
case 2301:
|
||||
errMsg = fmt.Sprintf("XMLTV file no longer available, channel has been deactivated.")
|
||||
case 2302:
|
||||
errMsg = fmt.Sprintf("Channel ID in the XMLTV file has changed. Channel has been deactivated.")
|
||||
|
||||
// Benutzerauthentifizierung
|
||||
case 3000:
|
||||
errMsg = fmt.Sprintf("Database for user authentication could not be initialized.")
|
||||
case 3001:
|
||||
errMsg = fmt.Sprintf("The user has no authorization to load the channels.")
|
||||
|
||||
// Buffer
|
||||
case 4000:
|
||||
errMsg = fmt.Sprintf("Connection to streaming source was interrupted.")
|
||||
case 4001:
|
||||
errMsg = fmt.Sprintf("Too many errors connecting to the provider. Streaming is canceled.")
|
||||
case 4002:
|
||||
errMsg = fmt.Sprintf("New URL for the redirect to the streaming server is missing")
|
||||
case 4003:
|
||||
errMsg = fmt.Sprintf("Server sends an incompatible content-type")
|
||||
case 4004:
|
||||
errMsg = fmt.Sprintf("This error message comes from the provider")
|
||||
case 4005:
|
||||
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
|
||||
|
||||
// Buffer (M3U8)
|
||||
case 4050:
|
||||
errMsg = fmt.Sprintf("Invalid M3U8 file")
|
||||
case 4051:
|
||||
errMsg = fmt.Sprintf("#EXTM3U header is missing")
|
||||
|
||||
// Caching
|
||||
case 4100:
|
||||
errMsg = fmt.Sprintf("Unknown content type for downloaded image")
|
||||
|
||||
// API
|
||||
case 5000:
|
||||
errMsg = fmt.Sprintf("Invalid API command")
|
||||
|
||||
// Update Server
|
||||
case 6001:
|
||||
errMsg = fmt.Sprintf("Ivalid key")
|
||||
case 6002:
|
||||
errMsg = fmt.Sprintf("Update failed")
|
||||
case 6003:
|
||||
errMsg = fmt.Sprintf("Server not available")
|
||||
case 6004:
|
||||
errMsg = fmt.Sprintf("xTeVe update available")
|
||||
|
||||
default:
|
||||
errMsg = fmt.Sprintf("Unknown error / warning (%d)", errCode)
|
||||
}
|
||||
|
||||
return errMsg
|
||||
}
|
||||
|
||||
func addNotification(notification Notification) (err error) {
|
||||
|
||||
var i int
|
||||
var t = time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
|
||||
notification.Time = strconv.FormatInt(t, 10)
|
||||
notification.New = true
|
||||
|
||||
if len(notification.Headline) == 0 {
|
||||
notification.Headline = strings.ToUpper(notification.Type)
|
||||
}
|
||||
|
||||
if len(System.Notification) == 0 {
|
||||
System.Notification = make(map[string]Notification)
|
||||
}
|
||||
|
||||
System.Notification[notification.Time] = notification
|
||||
|
||||
for key := range System.Notification {
|
||||
|
||||
if i < len(System.Notification)-10 {
|
||||
delete(System.Notification, key)
|
||||
}
|
||||
|
||||
i++
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
69
src/ssdp.go
Normal file
69
src/ssdp.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/koron/go-ssdp"
|
||||
)
|
||||
|
||||
// SSDP : SSPD / DLNA Server
|
||||
func SSDP() {
|
||||
|
||||
showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
|
||||
|
||||
if Settings.SSDP == false {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
ad, err := ssdp.Advertise(
|
||||
"upnp:"+System.AppName, // send as "ST"
|
||||
System.DeviceID+"::upnp:"+System.AppName, // send as "USN"
|
||||
System.URLBase+"/device.xml", // send as "LOCATION"
|
||||
System.AppName, // send as "SERVER"
|
||||
1800) // send as "maxAge" in "CACHE-CONTROL"
|
||||
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
}
|
||||
|
||||
// Debug SSDP
|
||||
if System.Flag.Debug == 3 {
|
||||
ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
|
||||
}
|
||||
|
||||
var aliveTick <-chan time.Time
|
||||
var ai = 10
|
||||
|
||||
if ai > 0 {
|
||||
aliveTick = time.Tick(time.Duration(ai) * time.Second)
|
||||
} else {
|
||||
aliveTick = make(chan time.Time)
|
||||
}
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
|
||||
loop:
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
|
||||
case <-aliveTick:
|
||||
ad.Alive()
|
||||
case <-quit:
|
||||
os.Exit(0)
|
||||
break loop
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ad.Bye()
|
||||
ad.Close()
|
||||
}
|
||||
109
src/struct-buffer.go
Normal file
109
src/struct-buffer.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package src
|
||||
|
||||
import "time"
|
||||
|
||||
// Playlist : Enthält allen Playlistinformationen, die der Buffer benötigr
|
||||
type Playlist struct {
|
||||
Folder string
|
||||
PlaylistID string
|
||||
PlaylistName string
|
||||
Tuner int
|
||||
|
||||
Clients map[int]ThisClient
|
||||
Streams map[int]ThisStream
|
||||
}
|
||||
|
||||
// ThisClient : Clientinfos
|
||||
type ThisClient struct {
|
||||
Connection int
|
||||
}
|
||||
|
||||
// ThisStream : Enthält Informationen zu dem abzuspielenden Stream einer Playlist
|
||||
type ThisStream struct {
|
||||
ChannelName string
|
||||
Error string
|
||||
Folder string
|
||||
MD5 string
|
||||
NetworkBandwidth int
|
||||
PlaylistID string
|
||||
PlaylistName string
|
||||
Status bool
|
||||
URL string
|
||||
|
||||
Segment []Segment
|
||||
|
||||
// Serverinformationen
|
||||
Location string
|
||||
URLFile string
|
||||
URLHost string
|
||||
URLPath string
|
||||
URLRedirect string
|
||||
URLScheme string
|
||||
URLStreamingServer string
|
||||
|
||||
// Wird nur für HLS / M3U8 verwendet
|
||||
Body string
|
||||
Difference float64
|
||||
Duration float64
|
||||
DynamicBandwidth bool
|
||||
FirstSequence int64
|
||||
HLS bool
|
||||
LastSequence int64
|
||||
M3U8URL string
|
||||
NewSegCount int
|
||||
OldSegCount int
|
||||
Sequence int64
|
||||
TimeDiff float64
|
||||
TimeEnd time.Time
|
||||
TimeSegDuration float64
|
||||
TimeStart time.Time
|
||||
Version int
|
||||
Wait float64
|
||||
|
||||
DynamicStream map[int]DynamicStream
|
||||
|
||||
// Lokale Temp Datein
|
||||
OldSegments []string
|
||||
}
|
||||
|
||||
// Segment : URL Segmente (HLS / M3U8)
|
||||
type Segment struct {
|
||||
Duration float64
|
||||
Info bool
|
||||
Sequence int64
|
||||
URL string
|
||||
Version int
|
||||
Wait float64
|
||||
|
||||
StreamInf struct {
|
||||
AverageBandwidth int
|
||||
Bandwidth int
|
||||
Framerate float64
|
||||
Resolution string
|
||||
SegmentURL string
|
||||
}
|
||||
}
|
||||
|
||||
// DynamicStream : Streaminformationen bei dynamischer Bandbreite
|
||||
type DynamicStream struct {
|
||||
AverageBandwidth int
|
||||
Bandwidth int
|
||||
Framerate float64
|
||||
Resolution string
|
||||
URL string
|
||||
}
|
||||
|
||||
// ClientConnection : Client Verbindungen
|
||||
type ClientConnection struct {
|
||||
Connection int
|
||||
Error error
|
||||
}
|
||||
|
||||
// BandwidthCalculation : Bandbreitenberechnung für den Stream
|
||||
type BandwidthCalculation struct {
|
||||
NetworkBandwidth int
|
||||
Size int
|
||||
Start time.Time
|
||||
Stop time.Time
|
||||
TimeDiff float64
|
||||
}
|
||||
61
src/struct-hdhr.go
Normal file
61
src/struct-hdhr.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package src
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// Capability : HDHR Capability XML
|
||||
type Capability struct {
|
||||
URLBase string `xml:"URLBase"`
|
||||
XMLName xml.Name `xml:"root"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
|
||||
SpecVersion struct {
|
||||
Major int `xml:"major"`
|
||||
Minor int `xml:"minor"`
|
||||
} `xml:"specVersion"`
|
||||
|
||||
Device struct {
|
||||
DeviceType string `xml:"deviceType"`
|
||||
FriendlyName string `xml:"friendlyName"`
|
||||
Manufacturer string `xml:"manufacturer"`
|
||||
ModelName string `xml:"modelName"`
|
||||
ModelNumber string `xml:"modelNumber"`
|
||||
SerialNumber string `xml:"serialNumber"`
|
||||
UDN string `xml:"UDN"`
|
||||
} `xml:"device"`
|
||||
}
|
||||
|
||||
// Discover : HDHR Discover /discover.json
|
||||
type Discover struct {
|
||||
BaseURL string `json:"BaseURL"`
|
||||
DeviceAuth string `json:"DeviceAuth"`
|
||||
DeviceID string `json:"DeviceID"`
|
||||
FirmwareName string `json:"FirmwareName"`
|
||||
FirmwareVersion string `json:"FirmwareVersion"`
|
||||
FriendlyName string `json:"FriendlyName"`
|
||||
LineupURL string `json:"LineupURL"`
|
||||
Manufacturer string `json:"Manufacturer"`
|
||||
ModelNumber string `json:"ModelNumber"`
|
||||
TunerCount int `json:"TunerCount"`
|
||||
}
|
||||
|
||||
// LineupStatus : HDHR Lineup status /lineup_status.json
|
||||
type LineupStatus struct {
|
||||
ScanInProgress int `json:"ScanInProgress"`
|
||||
ScanPossible int `json:"ScanPossible"`
|
||||
Source string `json:"Source"`
|
||||
SourceList []string `json:"SourceList"`
|
||||
}
|
||||
|
||||
// Lineup : HDHR Lineup /lineup.json
|
||||
type Lineup []interface {
|
||||
//GuideName string `json:"GuideName"`
|
||||
//GuideNumber string `json:"GuideNumber"`
|
||||
//URL string `json:"URL"`
|
||||
}
|
||||
|
||||
// LineupStream : HDHR einzelner Stream im Lineup
|
||||
type LineupStream struct {
|
||||
GuideName string `json:"GuideName"`
|
||||
GuideNumber string `json:"GuideNumber"`
|
||||
URL string `json:"URL"`
|
||||
}
|
||||
280
src/struct-system.go
Normal file
280
src/struct-system.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package src
|
||||
|
||||
// SystemStruct : Beinhaltet alle Systeminformationen
|
||||
type SystemStruct struct {
|
||||
Addresses struct {
|
||||
DVR string
|
||||
M3U string
|
||||
XML string
|
||||
}
|
||||
|
||||
APIVersion string
|
||||
AppName string
|
||||
ARCH string
|
||||
Branch string
|
||||
Build string
|
||||
Compatibility string
|
||||
ConfigurationWizard bool
|
||||
Dev bool
|
||||
DeviceID string
|
||||
Domain string
|
||||
DVRLimit int
|
||||
|
||||
File struct {
|
||||
Authentication string
|
||||
M3U string
|
||||
PMS string
|
||||
Settings string
|
||||
URLS string
|
||||
XEPG string
|
||||
XML string
|
||||
}
|
||||
|
||||
Flag struct {
|
||||
Branch string
|
||||
Debug int
|
||||
Port string
|
||||
SSDP bool
|
||||
}
|
||||
|
||||
Folder struct {
|
||||
Backup string
|
||||
Cache string
|
||||
Config string
|
||||
Data string
|
||||
ImagesCache string
|
||||
ImagesUpload string
|
||||
Temp string
|
||||
}
|
||||
|
||||
Hostname string
|
||||
ImageCachingInProgress int
|
||||
IPAddress string
|
||||
IPAddressesList []string
|
||||
IPAddressesV4 []string
|
||||
IPAddressesV6 []string
|
||||
Name string
|
||||
OS string
|
||||
ScanInProgress int
|
||||
TimeForAutoUpdate string
|
||||
|
||||
Notification map[string]Notification
|
||||
|
||||
ServerProtocol struct {
|
||||
API string
|
||||
DVR string
|
||||
M3U string
|
||||
WEB string
|
||||
XML string
|
||||
}
|
||||
|
||||
GitHub struct {
|
||||
Branch string
|
||||
Repo string
|
||||
Update bool
|
||||
User string
|
||||
}
|
||||
|
||||
Update struct {
|
||||
Git string
|
||||
Name string
|
||||
}
|
||||
|
||||
URLBase string
|
||||
Version string
|
||||
WEB struct {
|
||||
Menu []string
|
||||
}
|
||||
}
|
||||
|
||||
// GitStruct : Updateinformationen von GitHub
|
||||
type GitStruct struct {
|
||||
Filename string `json:"filename"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||
type DataStruct struct {
|
||||
Cache struct {
|
||||
ImagesCache []string
|
||||
ImagesFiles []string
|
||||
ImagesURLS []string
|
||||
PMS map[string]string
|
||||
|
||||
StreamingURLS map[string]StreamInfo
|
||||
XMLTV map[string]XMLTV
|
||||
|
||||
Streams struct {
|
||||
Active []string
|
||||
}
|
||||
}
|
||||
|
||||
Filter []Filter
|
||||
|
||||
Playlist struct {
|
||||
M3U struct {
|
||||
Groups struct {
|
||||
Text []string
|
||||
Value []string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StreamPreviewUI struct {
|
||||
Active []string
|
||||
Inactive []string
|
||||
}
|
||||
|
||||
Streams struct {
|
||||
Active []interface{}
|
||||
All []interface{}
|
||||
Inactive []interface{}
|
||||
}
|
||||
|
||||
XMLTV struct {
|
||||
Files []string
|
||||
Mapping map[string]interface{}
|
||||
}
|
||||
|
||||
XEPG struct {
|
||||
Channels map[string]interface{}
|
||||
XEPGCount int64
|
||||
}
|
||||
}
|
||||
|
||||
// Filter : Wird für die Filterregeln verwendet
|
||||
type Filter struct {
|
||||
CaseSensitive bool
|
||||
Rule string
|
||||
Type string
|
||||
}
|
||||
|
||||
// XEPGChannelStruct : XEPG Struktur
|
||||
type XEPGChannelStruct struct {
|
||||
FileM3UID string `json:"_file.m3u.id,required"`
|
||||
FileM3UName string `json:"_file.m3u.name,required"`
|
||||
FileM3UPath string `json:"_file.m3u.path,required"`
|
||||
GroupTitle string `json:"group-title,required"`
|
||||
Name string `json:"name,required"`
|
||||
TvgID string `json:"tvg-id,required"`
|
||||
TvgLogo string `json:"tvg-logo,required"`
|
||||
TvgName string `json:"tvg-name,required"`
|
||||
URL string `json:"url,required"`
|
||||
UUIDKey string `json:"_uuid.key,required"`
|
||||
UUIDValue string `json:"_uuid.value,omitempty"`
|
||||
Values string `json:"_values,required"`
|
||||
XActive bool `json:"x-active,required"`
|
||||
XCategory string `json:"x-category,required"`
|
||||
XChannelID string `json:"x-channelID,required"`
|
||||
XEPG string `json:"x-epg,required"`
|
||||
XGroupTitle string `json:"x-group-title,required"`
|
||||
XMapping string `json:"x-mapping,required"`
|
||||
XmltvFile string `json:"x-xmltv-file,required"`
|
||||
XName string `json:"x-name,required"`
|
||||
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
|
||||
XUpdateChannelName bool `json:"x-update-channel-name,required"`
|
||||
}
|
||||
|
||||
// M3UChannelStructXEPG : M3U Struktur für XEPG
|
||||
type M3UChannelStructXEPG struct {
|
||||
FileM3UID string `json:"_file.m3u.id,required"`
|
||||
FileM3UName string `json:"_file.m3u.name,required"`
|
||||
FileM3UPath string `json:"_file.m3u.path,required"`
|
||||
GroupTitle string `json:"group-title,required"`
|
||||
Name string `json:"name,required"`
|
||||
TvgID string `json:"tvg-id,required"`
|
||||
TvgLogo string `json:"tvg-logo,required"`
|
||||
TvgName string `json:"tvg-name,required"`
|
||||
URL string `json:"url,required"`
|
||||
UUIDKey string `json:"_uuid.key,required"`
|
||||
UUIDValue string `json:"_uuid.value,required"`
|
||||
Values string `json:"_values,required"`
|
||||
}
|
||||
|
||||
// FilterStruct : Filter Struktur
|
||||
type FilterStruct struct {
|
||||
Active bool `json:"active,required"`
|
||||
CaseSensitive bool `json:"caseSensitive,required"`
|
||||
Description string `json:"description,required"`
|
||||
Exclude string `json:"exclude,required"`
|
||||
Filter string `json:"filter,required"`
|
||||
Include string `json:"include,required"`
|
||||
Name string `json:"name,required"`
|
||||
Rule string `json:"rule,omitempty"`
|
||||
Type string `json:"type,required"`
|
||||
}
|
||||
|
||||
// StreamingURLS : Informationen zu allen streaming URL's
|
||||
type StreamingURLS struct {
|
||||
Streams map[string]StreamInfo `json:"channels,required"`
|
||||
}
|
||||
|
||||
// StreamInfo : Informationen zum Kanal für die streaming URL
|
||||
type StreamInfo struct {
|
||||
ChannelNumber string `json:"channelNumber,required"`
|
||||
Name string `json:"name,required"`
|
||||
PlaylistID string `json:"playlistID,required"`
|
||||
URL string `json:"url,required"`
|
||||
URLid string `json:"urlID,required"`
|
||||
}
|
||||
|
||||
// Notification : Notifikationen im Webinterface
|
||||
type Notification struct {
|
||||
Headline string `json:"headline,required"`
|
||||
Message string `json:"message,required"`
|
||||
New bool `json:"new,required"`
|
||||
Time string `json:"time,required"`
|
||||
Type string `json:"type,required"`
|
||||
}
|
||||
|
||||
// SettingsStrcut : Inhalt der settings.json
|
||||
type SettingsStrcut struct {
|
||||
API bool `json:"api"`
|
||||
AuthenticationAPI bool `json:"authentication.api"`
|
||||
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||
AuthenticationPMS bool `json:"authentication.pms"`
|
||||
AuthenticationWEB bool `json:"authentication.web"`
|
||||
AuthenticationXML bool `json:"authentication.xml"`
|
||||
BackupKeep int `json:"backup.keep"`
|
||||
BackupPath string `json:"backup.path"`
|
||||
Branch string `json:"git.branch,omitempty"`
|
||||
Buffer bool `json:"buffer"`
|
||||
BufferSize int `json:"buffer.size.kb"`
|
||||
BufferTimeout float64 `json:"buffer.timeout"`
|
||||
CacheImages bool `json:"cache.images"`
|
||||
EpgSource string `json:"epgSource"`
|
||||
FileM3U []string `json:"file,omitempty"` // Beim Wizard wird die M3U in ein Slice gespeichert
|
||||
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
||||
|
||||
Files struct {
|
||||
HDHR map[string]interface{} `json:"hdhr"`
|
||||
M3U map[string]interface{} `json:"m3u"`
|
||||
XMLTV map[string]interface{} `json:"xmltv"`
|
||||
} `json:"files"`
|
||||
|
||||
FilesUpdate bool `json:"files.update"`
|
||||
Filter map[int64]interface{} `json:"filter"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Language string `json:"language"`
|
||||
LogEntriesRAM int `json:"log.entries.ram"`
|
||||
M3U8AdaptiveBandwidthMBPS int `json:"m3u8.adaptive.bandwidth.mbps"`
|
||||
MappingFirstChannel float64 `json:"mapping.first.channel"`
|
||||
Port string `json:"port"`
|
||||
SSDP bool `json:"ssdp"`
|
||||
TempPath string `json:"temp.path"`
|
||||
Tuner int `json:"tuner"`
|
||||
Update []string `json:"update"`
|
||||
UpdateURL string `json:"update.url,omitempty"`
|
||||
UserAgent string `json:"user.agent"`
|
||||
UUID string `json:"uuid"`
|
||||
Version string `json:"version"`
|
||||
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
||||
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
||||
}
|
||||
|
||||
// LanguageUI : Sprache für das WebUI
|
||||
type LanguageUI struct {
|
||||
Login struct {
|
||||
Failed string
|
||||
}
|
||||
}
|
||||
145
src/struct-webserver.go
Normal file
145
src/struct-webserver.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package src
|
||||
|
||||
// RequestStruct : Anfragen über die Websocket Schnittstelle
|
||||
type RequestStruct struct {
|
||||
// Befehle an xTeVe
|
||||
Cmd string `json:"cmd,required"`
|
||||
|
||||
// Benutzer
|
||||
DeleteUser bool `json:"deleteUser,omitempty"`
|
||||
UserData map[string]interface{} `json:"userData,omitempty"`
|
||||
|
||||
// Mapping
|
||||
EpgMapping map[string]interface{} `json:"epgMapping,omitempty"`
|
||||
|
||||
// Restore
|
||||
Base64 string `json:"base64,omitempty"`
|
||||
|
||||
// Neue Werte für die Einstellungen (settings.json)
|
||||
Settings struct {
|
||||
API *bool `json:"api,omitempty"`
|
||||
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
|
||||
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
|
||||
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`
|
||||
AuthenticationWEP *bool `json:"authentication.web,omitempty"`
|
||||
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
|
||||
BackupKeep *int `json:"backup.keep,omitempty"`
|
||||
BackupPath *string `json:"backup.path,omitempty"`
|
||||
Buffer *bool `json:"buffer,omitempty"`
|
||||
BufferSize *int `json:"buffer.size.kb, omitempty"`
|
||||
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
|
||||
CacheImages *bool `json:"cache.images,omitempty"`
|
||||
EpgSource *string `json:"epgSource,omitempty"`
|
||||
FilesUpdate *bool `json:"files.update,omitempty"`
|
||||
TempPath *string `json:"temp.path,omitempty"`
|
||||
Tuner *int `json:"tuner,omitempty"`
|
||||
Update *[]string `json:"update,omitempty"`
|
||||
UserAgent *string `json:"user.agent,omitempty"`
|
||||
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
||||
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
||||
} `json:"settings,omitempty"`
|
||||
|
||||
// Upload Logo
|
||||
Filename string `json:"filename,omitempty"`
|
||||
|
||||
// Filter
|
||||
Filter map[int64]interface{} `json:"filter,omitempty"`
|
||||
|
||||
// Dateien (M3U, HDHR, XMLTV)
|
||||
Files struct {
|
||||
HDHR map[string]interface{} `json:"hdhr,omitempty"`
|
||||
M3U map[string]interface{} `json:"m3u,omitempty"`
|
||||
XMLTV map[string]interface{} `json:"xmltv,omitempty"`
|
||||
} `json:"files,omitempty"`
|
||||
|
||||
// Wizard
|
||||
Wizard struct {
|
||||
EpgSource *string `json:"epgSource,omitempty"`
|
||||
M3U *string `json:"m3u,omitempty"`
|
||||
Tuner *int `json:"tuner,omitempty"`
|
||||
XMLTV *string `json:"xmltv,omitempty"`
|
||||
} `json:"wizard,omitempty"`
|
||||
}
|
||||
|
||||
// ResponseStruct : Antworten an den Client (WEB)
|
||||
type ResponseStruct struct {
|
||||
ClientInfo struct {
|
||||
ARCH string `json:"arch"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
DVR string `json:"DVR"`
|
||||
EpgSource string `json:"epgSource"`
|
||||
Errors int `json:"errors"`
|
||||
M3U string `json:"m3u-url,required"`
|
||||
OS string `json:"os"`
|
||||
Streams string `json:"streams"`
|
||||
UUID string `json:"uuid"`
|
||||
Version string `json:"version"`
|
||||
Warnings int `json:"warnings"`
|
||||
XEPGCount int64 `json:"xepg"`
|
||||
XML string `json:"xepg-url,required"`
|
||||
} `json:"clientInfo,omitempty"`
|
||||
|
||||
Data struct {
|
||||
Playlist struct {
|
||||
M3U struct {
|
||||
Groups struct {
|
||||
Text []string `json:"text,required"`
|
||||
Value []string `json:"value,required"`
|
||||
} `json:"groups,required"`
|
||||
} `json:"m3u,required"`
|
||||
} `json:"playlist,required"`
|
||||
|
||||
StreamPreviewUI struct {
|
||||
Active []string `json:"activeStreams,required"`
|
||||
Inactive []string `json:"inactiveStreams,required"`
|
||||
}
|
||||
} `json:"data,required"`
|
||||
|
||||
Alert string `json:"alert,omitempty"`
|
||||
ConfigurationWizard bool `json:"configurationWizard,required"`
|
||||
Error string `json:"err,omitempty"`
|
||||
Log WebScreenLogStruct `json:"log,required"`
|
||||
LogoURL string `json:"logoURL,omitempty"`
|
||||
OpenLink string `json:"openLink,omitempty"`
|
||||
OpenMenu string `json:"openMenu,omitempty"`
|
||||
Reload bool `json:"reload,omitempty"`
|
||||
Settings SettingsStrcut `json:"settings,required"`
|
||||
Status bool `json:"status,required"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Users map[string]interface{} `json:"users,omitempty"`
|
||||
Wizard int `json:"wizard,omitempty"`
|
||||
XEPG map[string]interface{} `json:"xepg,required"`
|
||||
|
||||
Notification map[string]Notification `json:"notification,omitempty"`
|
||||
}
|
||||
|
||||
// APIRequestStruct : Anfrage über die API Schnittstelle
|
||||
type APIRequestStruct struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Password string `json:"password"`
|
||||
Token string `json:"token"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// APIResponseStruct : Antwort an den Client (API)
|
||||
type APIResponseStruct struct {
|
||||
EpgSource string `json:"epg.source,omitempty"`
|
||||
Error string `json:"err,omitempty"`
|
||||
Status bool `json:"status,required"`
|
||||
StreamsActive int64 `json:"streams.active,omitempty"`
|
||||
StreamsAll int64 `json:"streams.all,omitempty"`
|
||||
StreamsXepg int64 `json:"streams.xepg,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
URLDvr string `json:"url.dvr,omitempty"`
|
||||
URLM3U string `json:"url.m3u,omitempty"`
|
||||
URLXepg string `json:"url.xepg,omitempty"`
|
||||
VersionAPI string `json:"version.api,omitempty"`
|
||||
VersionXteve string `json:"version.xteve,omitempty"`
|
||||
}
|
||||
|
||||
// WebScreenLogStruct : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt
|
||||
type WebScreenLogStruct struct {
|
||||
Errors int `json:"errors,required"`
|
||||
Log []string `json:"log,required"`
|
||||
Warnings int `json:"warnings,required"`
|
||||
}
|
||||
123
src/struct-xml.go
Normal file
123
src/struct-xml.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package src
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
// XMLTV : XMLTV Datei
|
||||
type XMLTV struct {
|
||||
Generator string `xml:"generator-info-name,attr"`
|
||||
Source string `xml:"source-info-name,attr"`
|
||||
XMLName xml.Name `xml:"tv"`
|
||||
|
||||
Channel []*Channel `xml:"channel"`
|
||||
Program []*Program `xml:"programme"`
|
||||
}
|
||||
|
||||
// Channel : Kanäle
|
||||
type Channel struct {
|
||||
ID string `xml:"id,attr"`
|
||||
DisplayName []DisplayName `xml:"display-name"`
|
||||
Icon Icon `xml:"icon"`
|
||||
}
|
||||
|
||||
// DisplayName : Kanalname
|
||||
type DisplayName struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Icon : Senderlogo
|
||||
type Icon struct {
|
||||
Src string `xml:"src,attr"`
|
||||
}
|
||||
|
||||
// Program : Programme
|
||||
type Program struct {
|
||||
Channel string `xml:"channel,attr"`
|
||||
Start string `xml:"start,attr"`
|
||||
Stop string `xml:"stop,attr"`
|
||||
|
||||
Title []*Title `xml:"title"`
|
||||
SubTitle []*SubTitle `xml:"sub-title"`
|
||||
Desc []*Desc `xml:"desc"`
|
||||
Category []*Category `xml:"category"`
|
||||
Country []*Country `xml:"country"`
|
||||
EpisodeNum []*EpisodeNum `xml:"episode-num"`
|
||||
Poster []Poster `xml:"icon"`
|
||||
Language []*Language `xml:"language"`
|
||||
Video Video `xml:"video"`
|
||||
Date string `xml:"date"`
|
||||
PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
|
||||
New *New `xml:"new"`
|
||||
Live *Live `xml:"live"`
|
||||
}
|
||||
|
||||
// Title : Programmtitel
|
||||
type Title struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// SubTitle : Kurzbeschreibung
|
||||
type SubTitle struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
//Desc : Programmbeschreibung
|
||||
type Desc struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Category : Kategorien
|
||||
type Category struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Language : Sprachen
|
||||
type Language struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Country : Länder
|
||||
type Country struct {
|
||||
Lang string `xml:"lang,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// EpisodeNum : Episodennummerierung
|
||||
type EpisodeNum struct {
|
||||
System string `xml:"system,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Poster : Programmposter / Cover
|
||||
type Poster struct {
|
||||
Height string `xml:"height,attr"`
|
||||
Src string `xml:"src,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
Width string `xml:"width,attr"`
|
||||
}
|
||||
|
||||
// Video : Video Metadaten
|
||||
type Video struct {
|
||||
Aspect string `xml:"aspect,omitempty"`
|
||||
Colour string `xml:"colour,omitempty"`
|
||||
Present string `xml:"present,omitempty"`
|
||||
Quality string `xml:"quality,omitempty"`
|
||||
}
|
||||
|
||||
// PreviouslyShown : Widerholung bzw. Erstausstrahlung
|
||||
type PreviouslyShown struct {
|
||||
Start string `xml:"start,attr"`
|
||||
}
|
||||
|
||||
// New : Sendung als neu deklarieren
|
||||
type New struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Live : Sendung als Liveübertragung deklarieren
|
||||
type Live struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
323
src/system.go
Normal file
323
src/system.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Entwicklerinfos anzeigen
|
||||
func showDevInfo() {
|
||||
|
||||
if System.Dev == true {
|
||||
|
||||
fmt.Print("\033[31m")
|
||||
fmt.Println("* * * * * D E V M O D E * * * * *")
|
||||
fmt.Println("Version: ", System.Version)
|
||||
fmt.Println("Build: ", System.Build)
|
||||
fmt.Println("* * * * * * * * * * * * * * * * * *")
|
||||
fmt.Print("\033[0m")
|
||||
fmt.Println()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Alle Systemordner erstellen
|
||||
func createSystemFolders() (err error) {
|
||||
|
||||
e := reflect.ValueOf(&System.Folder).Elem()
|
||||
|
||||
for i := 0; i < e.NumField(); i++ {
|
||||
|
||||
var folder = e.Field(i).Interface().(string)
|
||||
|
||||
err = checkFolder(folder)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Alle Systemdateien erstellen
|
||||
func createSystemFiles() (err error) {
|
||||
|
||||
var debug string
|
||||
for _, file := range SystemFiles {
|
||||
|
||||
var filename = getPlatformFile(System.Folder.Config + file)
|
||||
|
||||
err = checkFile(filename)
|
||||
if err != nil {
|
||||
// Datei existiert nicht, wird jetzt erstellt
|
||||
err = saveMapToJSONFile(filename, make(map[string]interface{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug = fmt.Sprintf("Create File:%s", filename)
|
||||
showDebug(debug, 1)
|
||||
|
||||
}
|
||||
|
||||
switch file {
|
||||
|
||||
case "authentication.json":
|
||||
System.File.Authentication = filename
|
||||
case "pms.json":
|
||||
System.File.PMS = filename
|
||||
case "settings.json":
|
||||
System.File.Settings = filename
|
||||
case "xepg.json":
|
||||
System.File.XEPG = filename
|
||||
case "urls.json":
|
||||
System.File.URLS = filename
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Einstellungen laden und default Werte setzen (xTeVe)
|
||||
func loadSettings() (settings SettingsStrcut, err error) {
|
||||
|
||||
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Deafult Werte setzten
|
||||
var defaults = make(map[string]interface{})
|
||||
var dataMap = make(map[string]interface{})
|
||||
|
||||
dataMap["xmltv"] = make(map[string]interface{})
|
||||
dataMap["m3u"] = make(map[string]interface{})
|
||||
dataMap["hdhr"] = make(map[string]interface{})
|
||||
|
||||
defaults["api"] = false
|
||||
defaults["authentication.api"] = false
|
||||
defaults["authentication.m3u"] = false
|
||||
defaults["authentication.pms"] = false
|
||||
defaults["authentication.web"] = false
|
||||
defaults["authentication.xml"] = false
|
||||
defaults["backup.keep"] = 10
|
||||
defaults["backup.path"] = System.Folder.Backup
|
||||
defaults["buffer"] = false
|
||||
defaults["buffer.size.kb"] = 1024
|
||||
defaults["buffer.timeout"] = 500
|
||||
defaults["cache.images"] = false
|
||||
defaults["epgSource"] = "XEPG"
|
||||
defaults["files"] = dataMap
|
||||
defaults["files.update"] = true
|
||||
defaults["filter"] = make(map[string]interface{})
|
||||
defaults["git.branch"] = System.Branch
|
||||
defaults["language"] = "en"
|
||||
defaults["log.entries.ram"] = 500
|
||||
defaults["mapping.first.channel"] = 1000
|
||||
defaults["xepg.replace.missing.images"] = true
|
||||
defaults["m3u8.adaptive.bandwidth.mbps"] = 10
|
||||
defaults["port"] = "34400"
|
||||
defaults["ssdp"] = true
|
||||
defaults["tuner"] = 1
|
||||
defaults["update"] = []string{"0000"}
|
||||
defaults["user.agent"] = System.Name
|
||||
defaults["uuid"] = createUUID()
|
||||
defaults["version"] = System.Version
|
||||
defaults["xteveAutoUpdate"] = true
|
||||
defaults["temp.path"] = System.Folder.Temp
|
||||
|
||||
// Default Werte setzen
|
||||
for key, value := range defaults {
|
||||
if _, ok := settingsMap[key]; !ok {
|
||||
settingsMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(settingsMap)), &settings)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Einstellungen von den Flags übernehmen
|
||||
if len(System.Flag.Port) > 0 {
|
||||
settings.Port = System.Flag.Port
|
||||
}
|
||||
|
||||
if len(System.Flag.Branch) > 0 {
|
||||
settings.Branch = System.Flag.Branch
|
||||
showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch))
|
||||
}
|
||||
|
||||
err = saveSettings(settings)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Einstellungen speichern (xTeVe)
|
||||
func saveSettings(settings SettingsStrcut) (err error) {
|
||||
|
||||
if settings.BackupKeep == 0 {
|
||||
settings.BackupKeep = 10
|
||||
}
|
||||
|
||||
if len(settings.BackupPath) == 0 {
|
||||
settings.BackupPath = System.Folder.Backup
|
||||
}
|
||||
|
||||
if settings.BufferTimeout < 0 {
|
||||
settings.BufferTimeout = 0
|
||||
}
|
||||
|
||||
System.Folder.Temp = settings.TempPath + settings.UUID + string(os.PathSeparator)
|
||||
|
||||
err = writeByteToFile(System.File.Settings, []byte(mapToJSON(settings)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Settings = settings
|
||||
|
||||
if System.Dev == true {
|
||||
Settings.UUID = "2019-01-DEV-xTeVe!"
|
||||
}
|
||||
|
||||
setDeviceID()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Zugriff über die Domain ermöglichen
|
||||
func setGlobalDomain(domain string) {
|
||||
|
||||
System.Domain = domain
|
||||
|
||||
switch Settings.AuthenticationPMS {
|
||||
case true:
|
||||
System.Addresses.DVR = "username:password@" + System.Domain
|
||||
case false:
|
||||
System.Addresses.DVR = System.Domain
|
||||
}
|
||||
|
||||
switch Settings.AuthenticationM3U {
|
||||
case true:
|
||||
System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u?username=xxx&password=yyy<br>(Specific groups: [http://...&group-title=foo,bar])"
|
||||
case false:
|
||||
System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u (Specific groups: [http://...?group-title=foo,bar])"
|
||||
}
|
||||
|
||||
switch Settings.AuthenticationXML {
|
||||
case true:
|
||||
System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml?username=xxx&password=yyy"
|
||||
case false:
|
||||
System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml"
|
||||
}
|
||||
|
||||
if Settings.EpgSource != "XEPG" {
|
||||
System.Addresses.M3U = getErrMsg(2106)
|
||||
System.Addresses.XML = getErrMsg(2106)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UUID generieren
|
||||
func createUUID() (uuid string) {
|
||||
uuid = time.Now().Format("2006-01") + "-" + randomString(4) + "-" + randomString(6)
|
||||
return
|
||||
}
|
||||
|
||||
// Eindeutige Geräte ID für Plex generieren
|
||||
func setDeviceID() {
|
||||
|
||||
var id = Settings.UUID
|
||||
|
||||
switch Settings.Tuner {
|
||||
case 1:
|
||||
System.DeviceID = id
|
||||
|
||||
default:
|
||||
System.DeviceID = fmt.Sprintf("%s:%d", id, Settings.Tuner)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Provider Streaming-URL zu xTeVe Streaming-URL konvertieren
|
||||
func createStreamingURL(streamingType, playlistID, channelNumber, channelName, url string) (streamingURL string, err error) {
|
||||
|
||||
var streamInfo StreamInfo
|
||||
var serverProtocol string
|
||||
|
||||
if len(Data.Cache.StreamingURLS) == 0 {
|
||||
Data.Cache.StreamingURLS = make(map[string]StreamInfo)
|
||||
}
|
||||
|
||||
var urlID = getMD5(fmt.Sprintf("%s-%s", playlistID, url))
|
||||
|
||||
if s, ok := Data.Cache.StreamingURLS[urlID]; ok {
|
||||
|
||||
streamInfo = s
|
||||
|
||||
} else {
|
||||
|
||||
streamInfo.URL = url
|
||||
streamInfo.Name = channelName
|
||||
streamInfo.PlaylistID = playlistID
|
||||
streamInfo.ChannelNumber = channelNumber
|
||||
streamInfo.URLid = urlID
|
||||
|
||||
Data.Cache.StreamingURLS[urlID] = streamInfo
|
||||
|
||||
}
|
||||
|
||||
switch streamingType {
|
||||
|
||||
case "DVR":
|
||||
serverProtocol = System.ServerProtocol.DVR
|
||||
|
||||
case "M3U":
|
||||
serverProtocol = System.ServerProtocol.M3U
|
||||
|
||||
}
|
||||
|
||||
streamingURL = fmt.Sprintf("%s://%s/stream/%s", serverProtocol, System.Domain, streamInfo.URLid)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getStreamInfo(urlID string) (streamInfo StreamInfo, err error) {
|
||||
|
||||
if len(Data.Cache.StreamingURLS) == 0 {
|
||||
|
||||
tmp, err := loadJSONFileToMap(System.File.URLS)
|
||||
if err != nil {
|
||||
return streamInfo, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(tmp)), &Data.Cache.StreamingURLS)
|
||||
if err != nil {
|
||||
return streamInfo, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if s, ok := Data.Cache.StreamingURLS[urlID]; ok {
|
||||
streamInfo = s
|
||||
streamInfo.URL = strings.Trim(streamInfo.URL, "\r\n")
|
||||
} else {
|
||||
err = errors.New("Streaming error")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
356
src/toolchain.go
Normal file
356
src/toolchain.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// --- System Tools ---
|
||||
|
||||
// Prüft ob der Ordner existiert, falls nicht, wir der Ordner erstellt
|
||||
func checkFolder(path string) (err error) {
|
||||
|
||||
var debug string
|
||||
_, err = os.Stat(filepath.Dir(path))
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// Ordner existiert nicht, wird jetzt erstellt
|
||||
|
||||
err = os.MkdirAll(getPlatformPath(path), 0755)
|
||||
if err == nil {
|
||||
|
||||
debug = fmt.Sprintf("Create Folder:%s", path)
|
||||
showDebug(debug, 1)
|
||||
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prüft ob die datei im Dateisystem existiert
|
||||
func checkFile(filename string) (err error) {
|
||||
|
||||
var file = getPlatformFile(filename)
|
||||
|
||||
if _, err = os.Stat(file); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserHomeDirectory : Benutzer Homer Verzeichnis
|
||||
func GetUserHomeDirectory() (userHomeDirectory string) {
|
||||
|
||||
usr, err := user.Current()
|
||||
|
||||
if err != nil {
|
||||
|
||||
for _, name := range []string{"HOME", "USERPROFILE"} {
|
||||
|
||||
if dir := os.Getenv(name); dir != "" {
|
||||
userHomeDirectory = dir
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
userHomeDirectory = usr.HomeDir
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkFilePermission(dir string) (err error) {
|
||||
|
||||
var filename = dir + "permission.test"
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte(""), 0644)
|
||||
if err == nil {
|
||||
err = os.RemoveAll(filename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Ordnerpfad für das laufende OS generieren
|
||||
func getPlatformPath(path string) string {
|
||||
return filepath.Dir(path) + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
// Dateipfad für das laufende OS generieren
|
||||
func getPlatformFile(filename string) (osFilePath string) {
|
||||
|
||||
path, file := filepath.Split(filename)
|
||||
var newPath = filepath.Dir(path)
|
||||
osFilePath = newPath + string(os.PathSeparator) + file
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Dateinamen aus dem Dateipfad ausgeben
|
||||
func getFilenameFromPath(path string) (file string) {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
// Nicht mehr verwendete Systemdaten löschen
|
||||
func removeOldSystemData() {
|
||||
// Temporären Ordner löschen
|
||||
os.RemoveAll(System.Folder.Temp)
|
||||
}
|
||||
|
||||
//
|
||||
func removeChildItems(dir string) error {
|
||||
|
||||
files, err := filepath.Glob(filepath.Join(dir, "*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
err = os.RemoveAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSON
|
||||
func mapToJSON(tmpMap interface{}) string {
|
||||
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
return string(jsonString)
|
||||
}
|
||||
|
||||
func jsonToMap(content string) map[string]interface{} {
|
||||
|
||||
var tmpMap = make(map[string]interface{})
|
||||
json.Unmarshal([]byte(content), &tmpMap)
|
||||
|
||||
return (tmpMap)
|
||||
}
|
||||
|
||||
func jsonToMapInt64(content string) map[int64]interface{} {
|
||||
|
||||
var tmpMap = make(map[int64]interface{})
|
||||
json.Unmarshal([]byte(content), &tmpMap)
|
||||
|
||||
return (tmpMap)
|
||||
}
|
||||
|
||||
func jsonToInterface(content string) (tmpMap interface{}, err error) {
|
||||
|
||||
err = json.Unmarshal([]byte(content), &tmpMap)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func saveMapToJSONFile(file string, tmpMap interface{}) error {
|
||||
|
||||
var filename = getPlatformFile(file)
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte(jsonString), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadJSONFileToMap(file string) (tmpMap map[string]interface{}, err error) {
|
||||
|
||||
f, err := os.Open(getPlatformFile(file))
|
||||
defer f.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(f)
|
||||
|
||||
if err == nil {
|
||||
err = json.Unmarshal([]byte(content), &tmpMap)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Binary
|
||||
func readByteFromFile(file string) (content []byte, err error) {
|
||||
|
||||
f, err := os.Open(getPlatformFile(file))
|
||||
defer f.Close()
|
||||
|
||||
content, err = ioutil.ReadAll(f)
|
||||
f.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeByteToFile(file string, data []byte) (err error) {
|
||||
|
||||
var filename = getPlatformFile(file)
|
||||
err = ioutil.WriteFile(filename, data, 0644)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readStringFromFile(file string) (str string, err error) {
|
||||
|
||||
var content []byte
|
||||
var filename = getPlatformFile(file)
|
||||
|
||||
err = checkFile(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content, err = ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
str = string(content)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Netzwerk
|
||||
func resolveHostIP() (err error) {
|
||||
|
||||
netInterfaceAddresses, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, netInterfaceAddress := range netInterfaceAddresses {
|
||||
|
||||
networkIP, ok := netInterfaceAddress.(*net.IPNet)
|
||||
System.IPAddressesList = append(System.IPAddressesList, networkIP.IP.String())
|
||||
|
||||
if ok {
|
||||
|
||||
var ip = networkIP.IP.String()
|
||||
|
||||
if networkIP.IP.To4() != nil {
|
||||
|
||||
System.IPAddressesV4 = append(System.IPAddressesV4, ip)
|
||||
|
||||
if !networkIP.IP.IsLoopback() && ip[0:7] != "169.254" {
|
||||
System.IPAddress = ip
|
||||
}
|
||||
|
||||
} else {
|
||||
System.IPAddressesV6 = append(System.IPAddressesV6, ip)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.Hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sonstiges
|
||||
func randomString(n int) string {
|
||||
|
||||
const alphanum = "AB1CD2EF3GH4IJ5KL6MN7OP8QR9ST0UVWXYZ"
|
||||
|
||||
var bytes = make([]byte, n)
|
||||
|
||||
rand.Read(bytes)
|
||||
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func parseTemplate(content string, tmpMap map[string]interface{}) (result string) {
|
||||
|
||||
t := template.Must(template.New("template").Parse(content))
|
||||
|
||||
var tpl bytes.Buffer
|
||||
|
||||
if err := t.Execute(&tpl, tmpMap); err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
result = tpl.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func indexOfString(element string, data []string) int {
|
||||
|
||||
for k, v := range data {
|
||||
if element == v {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func indexOfFloat64(element float64, data []float64) int {
|
||||
|
||||
for k, v := range data {
|
||||
if element == v {
|
||||
return (k)
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func indexOfInt(element int, data []int) int {
|
||||
|
||||
for k, v := range data {
|
||||
if element == v {
|
||||
return (k)
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func getMD5(str string) string {
|
||||
|
||||
md5Hasher := md5.New()
|
||||
md5Hasher.Write([]byte(str))
|
||||
|
||||
return hex.EncodeToString(md5Hasher.Sum(nil))
|
||||
}
|
||||
273
src/update.go
Normal file
273
src/update.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
up2date "../src/internal/up2date/client"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// BinaryUpdate : Binary Update Prozess. Git Branch master und beta wird von GitHub geladen.
|
||||
func BinaryUpdate() (err error) {
|
||||
|
||||
if System.GitHub.Update == false {
|
||||
showWarning(2099)
|
||||
return
|
||||
}
|
||||
|
||||
var debug string
|
||||
|
||||
var updater = &up2date.Updater
|
||||
updater.Name = System.Update.Name
|
||||
updater.Branch = System.Branch
|
||||
|
||||
up2date.Init()
|
||||
|
||||
switch System.Branch {
|
||||
|
||||
// Update von GitHub
|
||||
case "master", "beta":
|
||||
|
||||
var gitInfo = fmt.Sprintf("%s/%s/info.json?raw=true", System.Update.Git, System.Branch)
|
||||
var zipFile = fmt.Sprintf("%s/%s/%s_%s_%s.zip?raw=true", System.Update.Git, System.Branch, System.AppName, System.OS, System.ARCH)
|
||||
var body []byte
|
||||
|
||||
var git GitStruct
|
||||
|
||||
resp, err := http.Get(gitInfo)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
err = fmt.Errorf(fmt.Sprintf("Update Server: %s (%s)", http.StatusText(resp.StatusCode), gitInfo))
|
||||
ShowError(err, 6003)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), gitInfo))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
|
||||
err = json.Unmarshal(body, &git)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updater.Response.Status = true
|
||||
updater.Response.UpdateZIP = zipFile
|
||||
updater.Response.Version = git.Version
|
||||
updater.Response.Filename = git.Filename
|
||||
|
||||
// Update vom eigenen Server
|
||||
default:
|
||||
|
||||
updater.URL = Settings.UpdateURL
|
||||
|
||||
if len(updater.URL) == 0 {
|
||||
showInfo(fmt.Sprintf("Update URL:No server URL specified, update will not be performed. Branch: %s", System.Branch))
|
||||
return
|
||||
}
|
||||
|
||||
showInfo("Update URL:" + updater.URL)
|
||||
fmt.Println("-----------------")
|
||||
|
||||
// Versionsinformationen vom Server laden
|
||||
err = up2date.GetVersion()
|
||||
if err != nil {
|
||||
|
||||
debug = fmt.Sprintf(err.Error())
|
||||
showDebug(debug, 1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(updater.Response.Reason) > 0 {
|
||||
|
||||
err = fmt.Errorf(fmt.Sprintf("Update Server: %s", updater.Response.Reason))
|
||||
ShowError(err, 6002)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var currentVersion = System.Version + "." + System.Build
|
||||
|
||||
// Versionsnummer überprüfen
|
||||
if updater.Response.Version > currentVersion && updater.Response.Status == true {
|
||||
|
||||
if Settings.XteveAutoUpdate == true {
|
||||
// Update durchführen
|
||||
var fileType, url string
|
||||
|
||||
showInfo(fmt.Sprintf("Update Available:Version: %s", updater.Response.Version))
|
||||
|
||||
switch System.Branch {
|
||||
|
||||
// Update von GitHub
|
||||
case "master", "beta":
|
||||
showInfo(fmt.Sprintf("Update Server:GitHub"))
|
||||
|
||||
// Update vom eigenen Server
|
||||
default:
|
||||
showInfo(fmt.Sprintf("Update Server:%s", Settings.UpdateURL))
|
||||
|
||||
}
|
||||
|
||||
showInfo(fmt.Sprintf("Start Update:Branch: %s", updater.Branch))
|
||||
|
||||
// Neue Version als BIN Datei herunterladen
|
||||
if len(updater.Response.UpdateBIN) > 0 {
|
||||
url = updater.Response.UpdateBIN
|
||||
fileType = "bin"
|
||||
}
|
||||
|
||||
// Neue Version als ZIP Datei herunterladen
|
||||
if len(updater.Response.UpdateZIP) > 0 {
|
||||
url = updater.Response.UpdateZIP
|
||||
fileType = "zip"
|
||||
}
|
||||
|
||||
if len(url) > 0 {
|
||||
|
||||
err = up2date.DoUpdate(fileType, updater.Response.Filename)
|
||||
if err != nil {
|
||||
ShowError(err, 6002)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// Hinweis ausgeben
|
||||
showWarning(6004)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func conditionalUpdateChanges() (err error) {
|
||||
|
||||
checkVersion:
|
||||
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||
if err != nil || len(settingsMap) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if settingsVersion, ok := settingsMap["version"].(string); ok {
|
||||
|
||||
// Letzte Kompatible Version (1.4.4)
|
||||
if settingsVersion < System.Compatibility {
|
||||
err = errors.New(getErrMsg(1013))
|
||||
return
|
||||
}
|
||||
|
||||
switch settingsVersion {
|
||||
|
||||
case "1.4.4":
|
||||
// UUID Wert in xepg.json setzen
|
||||
err = setValueForUUID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Neuer Filter (WebUI). Alte Filtereinstellungen werden konvertiert
|
||||
if oldFilter, ok := settingsMap["filter"].([]interface{}); ok {
|
||||
var newFilterMap = convertToNewFilter(oldFilter)
|
||||
settingsMap["filter"] = newFilterMap
|
||||
|
||||
settingsMap["version"] = "1.9.0"
|
||||
|
||||
err = saveMapToJSONFile(System.File.Settings, settingsMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
goto checkVersion
|
||||
|
||||
} else {
|
||||
err = errors.New(getErrMsg(1030))
|
||||
return
|
||||
}
|
||||
|
||||
case "1.9.0":
|
||||
// Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
|
||||
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// settings.json ist zu alt (älter als Version 1.4.4)
|
||||
err = errors.New(getErrMsg(1013))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func convertToNewFilter(oldFilter []interface{}) (newFilterMap map[int]interface{}) {
|
||||
|
||||
newFilterMap = make(map[int]interface{})
|
||||
|
||||
switch reflect.TypeOf(oldFilter).Kind() {
|
||||
|
||||
case reflect.Slice:
|
||||
s := reflect.ValueOf(oldFilter)
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
|
||||
var newFilter FilterStruct
|
||||
newFilter.Active = true
|
||||
newFilter.Name = fmt.Sprintf("Custom filter %d", i+1)
|
||||
newFilter.Filter = s.Index(i).Interface().(string)
|
||||
newFilter.Type = "custom-filter"
|
||||
newFilter.CaseSensitive = false
|
||||
|
||||
newFilterMap[i] = newFilter
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func setValueForUUID() (err error) {
|
||||
|
||||
xepg, err := loadJSONFileToMap(System.File.XEPG)
|
||||
|
||||
for _, c := range xepg {
|
||||
|
||||
var xepgChannel = c.(map[string]interface{})
|
||||
|
||||
if uuidKey, ok := xepgChannel["_uuid.key"].(string); ok {
|
||||
|
||||
if value, ok := xepgChannel[uuidKey].(string); ok {
|
||||
|
||||
if len(value) > 0 {
|
||||
xepgChannel["_uuid.value"] = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveMapToJSONFile(System.File.XEPG, xepg)
|
||||
|
||||
return
|
||||
}
|
||||
54
src/webUI.go
Normal file
54
src/webUI.go
Normal file
File diff suppressed because one or more lines are too long
1050
src/webserver.go
Normal file
1050
src/webserver.go
Normal file
File diff suppressed because it is too large
Load Diff
967
src/xepg.go
Normal file
967
src/xepg.go
Normal file
@@ -0,0 +1,967 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Provider XMLTV Datei überprüfen
|
||||
func checkXMLCompatibility(id string, body []byte) (err error) {
|
||||
|
||||
var xmltv XMLTV
|
||||
var compatibility = make(map[string]int)
|
||||
|
||||
err = xml.Unmarshal(body, &xmltv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
compatibility["xmltv.channels"] = len(xmltv.Channel)
|
||||
compatibility["xmltv.programs"] = len(xmltv.Program)
|
||||
|
||||
setProviderCompatibility(id, "xmltv", compatibility)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// XEPG Daten erstellen
|
||||
func buildXEPG(background bool) {
|
||||
|
||||
if System.ScanInProgress == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
|
||||
switch background {
|
||||
|
||||
case true:
|
||||
|
||||
go func() {
|
||||
|
||||
createXEPGMapping()
|
||||
createXEPGDatabase()
|
||||
mapping()
|
||||
cleanupXEPG()
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
go cachingImages()
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
// Cache löschen
|
||||
/*
|
||||
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||
Data.Cache.XMLTV = nil
|
||||
*/
|
||||
runtime.GC()
|
||||
|
||||
}()
|
||||
|
||||
case false:
|
||||
|
||||
createXEPGMapping()
|
||||
createXEPGDatabase()
|
||||
mapping()
|
||||
cleanupXEPG()
|
||||
|
||||
go func() {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
go cachingImages()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
// Cache löschen
|
||||
//Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||
//Data.Cache.XMLTV = nil
|
||||
runtime.GC()
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
getLineup()
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// XEPG Daten aktualisieren
|
||||
func updateXEPG(background bool) {
|
||||
|
||||
if System.ScanInProgress == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
|
||||
switch background {
|
||||
|
||||
case false:
|
||||
|
||||
createXEPGDatabase()
|
||||
mapping()
|
||||
cleanupXEPG()
|
||||
|
||||
go func() {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}()
|
||||
|
||||
case true:
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}
|
||||
|
||||
// Cache löschen
|
||||
//Data.Cache.XMLTV = nil //make(map[string]XMLTV)
|
||||
//Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Mapping Menü für die XMLTV Dateien erstellen
|
||||
func createXEPGMapping() {
|
||||
|
||||
Data.XMLTV.Files = getLocalProviderFiles("xmltv")
|
||||
Data.XMLTV.Mapping = make(map[string]interface{})
|
||||
|
||||
var tmpMap = make(map[string]interface{})
|
||||
|
||||
var friendlyDisplayName = func(channel Channel) (displayName string) {
|
||||
var dn = channel.DisplayName
|
||||
displayName = dn[0].Value
|
||||
|
||||
switch len(dn) {
|
||||
case 1:
|
||||
displayName = dn[0].Value
|
||||
default:
|
||||
displayName = fmt.Sprintf("%s (%s)", dn[1].Value, dn[0].Value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(Data.XMLTV.Files) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := len(Data.XMLTV.Files) - 1; i >= 0; i-- {
|
||||
|
||||
var file = Data.XMLTV.Files[i]
|
||||
|
||||
var err error
|
||||
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
||||
showInfo("XEPG:" + "Parse XMLTV file: " + getProviderParameter(fileID, "xmltv", "name"))
|
||||
|
||||
//xmltv, err = getLocalXMLTV(file)
|
||||
var xmltv XMLTV
|
||||
|
||||
err = getLocalXMLTV(file, &xmltv)
|
||||
if err != nil {
|
||||
Data.XMLTV.Files = append(Data.XMLTV.Files, Data.XMLTV.Files[i+1:]...)
|
||||
var errMsg = err.Error()
|
||||
err = errors.New(getProviderParameter(fileID, "xmltv", "name") + ": " + errMsg)
|
||||
ShowError(err, 000)
|
||||
}
|
||||
|
||||
// XML Parsen (Provider Datei)
|
||||
if err == nil {
|
||||
|
||||
// Daten aus der XML Datei in eine temporäre Map schreiben
|
||||
var xmltvMap = make(map[string]interface{})
|
||||
|
||||
for _, c := range xmltv.Channel {
|
||||
var channel = make(map[string]interface{})
|
||||
|
||||
channel["id"] = c.ID
|
||||
channel["display-name"] = friendlyDisplayName(*c)
|
||||
channel["icon"] = c.Icon.Src
|
||||
|
||||
xmltvMap[c.ID] = channel
|
||||
|
||||
}
|
||||
|
||||
tmpMap[getFilenameFromPath(file)] = xmltvMap
|
||||
Data.XMLTV.Mapping[getFilenameFromPath(file)] = xmltvMap
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Auswahl für den Dummy erstellen
|
||||
var dummy = make(map[string]interface{})
|
||||
var times = []string{"30", "60", "90", "120"}
|
||||
|
||||
for _, i := range times {
|
||||
|
||||
var dummyChannel = make(map[string]string)
|
||||
dummyChannel["display-name"] = i + " Minutes"
|
||||
dummyChannel["id"] = i + "_Minutes"
|
||||
dummyChannel["icon"] = ""
|
||||
|
||||
dummy[dummyChannel["id"]] = dummyChannel
|
||||
|
||||
}
|
||||
|
||||
Data.XMLTV.Mapping = tmpMap
|
||||
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
|
||||
|
||||
tmpMap = make(map[string]interface{})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// XEPG Datenbank erstellen / aktualisieren
|
||||
func createXEPGDatabase() (err error) {
|
||||
|
||||
var allChannelNumbers []float64
|
||||
Data.Cache.Streams.Active = []string{}
|
||||
|
||||
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
||||
if err != nil {
|
||||
ShowError(err, 1004)
|
||||
return err
|
||||
}
|
||||
|
||||
var createNewID = func() (xepg string) {
|
||||
|
||||
var firstID = 0 //len(Data.XEPG.Channels)
|
||||
|
||||
newXEPGID:
|
||||
|
||||
if _, ok := Data.XEPG.Channels["x-ID."+strconv.FormatInt(int64(firstID), 10)]; ok {
|
||||
firstID++
|
||||
goto newXEPGID
|
||||
}
|
||||
|
||||
xepg = "x-ID." + strconv.FormatInt(int64(firstID), 10)
|
||||
return
|
||||
}
|
||||
|
||||
var getFreeChannelNumber = func() (xChannelID string) {
|
||||
|
||||
var firstFreeNumber float64 = Settings.MappingFirstChannel
|
||||
|
||||
newNumber:
|
||||
|
||||
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
||||
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
||||
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
||||
} else {
|
||||
firstFreeNumber++
|
||||
goto newNumber
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + "Update database")
|
||||
|
||||
// Kanal mit fehlenden Kanalnummern löschen
|
||||
for id, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(xepgChannel.XChannelID) == 0 {
|
||||
fmt.Println(mapToJSON(xepgChannel))
|
||||
delete(Data.XEPG.Channels, id)
|
||||
}
|
||||
|
||||
if xChannelID, err := strconv.ParseFloat(xepgChannel.XChannelID, 64); err == nil {
|
||||
allChannelNumbers = append(allChannelNumbers, xChannelID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, dsa := range Data.Streams.Active {
|
||||
|
||||
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll.
|
||||
var channelHasUUID = false // Überprüft, ob der Kanal (Stream) eindeutige ID's besitzt
|
||||
var currentXEPGID string // Aktuelle Datenbank ID (XEPG). Wird verwendet, um den Kanal in der Datenbank mit dem Stream der M3u zu aktualisieren
|
||||
var m3uChannel M3UChannelStructXEPG
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
||||
|
||||
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
||||
for xepg, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank
|
||||
if len(xepgChannel.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 {
|
||||
|
||||
if xepgChannel.UUIDValue == m3uChannel.UUIDValue && xepgChannel.UUIDKey == m3uChannel.UUIDKey {
|
||||
|
||||
channelExists = true
|
||||
channelHasUUID = true
|
||||
currentXEPGID = xepg
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens
|
||||
//fmt.Println(xepgChannel.Name, xepgChannel.UUIDKey, xepgChannel.UUIDValue)
|
||||
if xepgChannel.Name == m3uChannel.Name {
|
||||
channelExists = true
|
||||
currentXEPGID = xepg
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//os.Exit(0)
|
||||
|
||||
switch channelExists {
|
||||
case true:
|
||||
// Bereits vorhandener Kanal
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(Data.XEPG.Channels[currentXEPGID])), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Streaming URL aktualisieren
|
||||
xepgChannel.URL = m3uChannel.URL
|
||||
|
||||
// Name aktualisieren, anhand des Names wird überprüft ob der Kanal noch in einer Playlist verhanden. Funktion: cleanupXEPG
|
||||
xepgChannel.Name = m3uChannel.Name
|
||||
|
||||
// Kanalname aktualisieren, nur mit Kanal ID's möglich
|
||||
if channelHasUUID == true {
|
||||
if xepgChannel.XUpdateChannelName == true {
|
||||
xepgChannel.XName = m3uChannel.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Kanallogo aktualisieren. Wird bei vorhandenem Logo in der XMLTV Datei wieder überschrieben
|
||||
if xepgChannel.XUpdateChannelIcon == true {
|
||||
xepgChannel.TvgLogo = m3uChannel.TvgLogo
|
||||
}
|
||||
|
||||
Data.XEPG.Channels[currentXEPGID] = xepgChannel
|
||||
|
||||
case false:
|
||||
// Neuer Kanal
|
||||
var xepg = createNewID()
|
||||
var xChannelID = getFreeChannelNumber()
|
||||
|
||||
var newChannel XEPGChannelStruct
|
||||
newChannel.FileM3UID = m3uChannel.FileM3UID
|
||||
newChannel.FileM3UName = m3uChannel.FileM3UName
|
||||
newChannel.FileM3UPath = m3uChannel.FileM3UPath
|
||||
newChannel.Values = m3uChannel.Values
|
||||
newChannel.GroupTitle = m3uChannel.GroupTitle
|
||||
newChannel.Name = m3uChannel.Name
|
||||
newChannel.TvgID = m3uChannel.TvgID
|
||||
newChannel.TvgLogo = m3uChannel.TvgLogo
|
||||
newChannel.TvgName = m3uChannel.TvgName
|
||||
newChannel.URL = m3uChannel.URL
|
||||
newChannel.XmltvFile = ""
|
||||
newChannel.XMapping = ""
|
||||
|
||||
if len(m3uChannel.UUIDKey) > 0 {
|
||||
newChannel.UUIDKey = m3uChannel.UUIDKey
|
||||
newChannel.UUIDValue = m3uChannel.UUIDValue
|
||||
}
|
||||
|
||||
newChannel.XName = m3uChannel.Name
|
||||
newChannel.XGroupTitle = m3uChannel.GroupTitle
|
||||
newChannel.XEPG = xepg
|
||||
newChannel.XChannelID = xChannelID
|
||||
|
||||
Data.XEPG.Channels[xepg] = newChannel
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Kanäle automatisch zuordnen und das Mapping überprüfen
|
||||
func mapping() (err error) {
|
||||
showInfo("XEPG:" + "Map channels")
|
||||
|
||||
for xepg, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Automatische Mapping für neue Kanäle. Wird nur ausgeführt, wenn der Kanal deaktiviert ist und keine XMLTV Datei und kein XMLTV Kanal zugeordnet ist.
|
||||
if xepgChannel.XActive == false {
|
||||
|
||||
// Werte kann "-" sein, deswegen len < 1
|
||||
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XmltvFile) < 1 {
|
||||
|
||||
var tvgID = xepgChannel.TvgID
|
||||
|
||||
// Default für neuen Kanal setzen
|
||||
xepgChannel.XmltvFile = "-"
|
||||
xepgChannel.XMapping = "-"
|
||||
|
||||
Data.XEPG.Channels[xepg] = xepgChannel
|
||||
|
||||
for file, xmltvChannels := range Data.XMLTV.Mapping {
|
||||
|
||||
if channel, ok := xmltvChannels.(map[string]interface{})[tvgID]; ok {
|
||||
|
||||
if channelID, ok := channel.(map[string]interface{})["id"].(string); ok {
|
||||
|
||||
xepgChannel.XmltvFile = file
|
||||
xepgChannel.XMapping = channelID
|
||||
xepgChannel.XActive = true
|
||||
|
||||
// Falls in der XMLTV Datei ein Logo existiert, wird dieses verwendet. Falls nicht, dann das Logo aus der M3U Datei
|
||||
if icon, ok := channel.(map[string]interface{})["icon"].(string); ok {
|
||||
if len(icon) > 0 {
|
||||
xepgChannel.TvgLogo = icon
|
||||
}
|
||||
}
|
||||
|
||||
Data.XEPG.Channels[xepg] = xepgChannel
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Überprüfen, ob die zugeordneten XMLTV Dateien und Kanäle noch existieren.
|
||||
if xepgChannel.XActive == true {
|
||||
|
||||
var mapping = xepgChannel.XMapping
|
||||
var file = xepgChannel.XmltvFile
|
||||
|
||||
if file != "xTeVe Dummy" {
|
||||
|
||||
if value, ok := Data.XMLTV.Mapping[file].(map[string]interface{}); ok {
|
||||
|
||||
if channel, ok := value[mapping].(map[string]interface{}); ok {
|
||||
|
||||
// Kanallogo aktualisieren
|
||||
if logo, ok := channel["icon"].(string); ok {
|
||||
|
||||
if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
|
||||
xepgChannel.TvgLogo = logo
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ShowError(fmt.Errorf(fmt.Sprintf("Missing EPG data: %s", xepgChannel.Name)), 0)
|
||||
showWarning(2302)
|
||||
xepgChannel.XActive = false
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
||||
|
||||
ShowError(fmt.Errorf("Missing XMLTV file: %s", getProviderParameter(fileID, "xmltv", "name")), 0)
|
||||
showWarning(2301)
|
||||
xepgChannel.XActive = false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(xepgChannel.XmltvFile) == 0 {
|
||||
xepgChannel.XmltvFile = "-"
|
||||
xepgChannel.XActive = false
|
||||
}
|
||||
|
||||
if len(xepgChannel.XMapping) == 0 {
|
||||
xepgChannel.XMapping = "-"
|
||||
xepgChannel.XActive = false
|
||||
}
|
||||
|
||||
Data.XEPG.Channels[xepg] = xepgChannel
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// XMLTV Datei erstellen
|
||||
func createXMLTVFile() (err error) {
|
||||
|
||||
Data.Cache.ImagesFiles = []string{}
|
||||
Data.Cache.ImagesURLS = []string{}
|
||||
Data.Cache.ImagesCache = []string{}
|
||||
|
||||
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
|
||||
if err == nil {
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
if indexOfString(file.Name(), Data.Cache.ImagesCache) == -1 {
|
||||
Data.Cache.ImagesCache = append(Data.Cache.ImagesCache, file.Name())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(Data.XMLTV.Files) == 0 && len(Data.Streams.Active) == 0 {
|
||||
Data.XEPG.Channels = make(map[string]interface{})
|
||||
return
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Create XMLTV file (%s)", System.File.XML))
|
||||
|
||||
var xepgXML XMLTV
|
||||
|
||||
xepgXML.Generator = System.Name
|
||||
xepgXML.Source = fmt.Sprintf("%s - %s", System.Name, System.Version)
|
||||
|
||||
var tmpProgram = &XMLTV{}
|
||||
|
||||
for _, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err == nil {
|
||||
|
||||
if xepgChannel.XActive == true {
|
||||
|
||||
// Kanäle
|
||||
var channel Channel
|
||||
channel.ID = xepgChannel.XChannelID
|
||||
channel.Icon = Icon{Src: getCacheImageURL(xepgChannel.TvgLogo)}
|
||||
channel.DisplayName = append(channel.DisplayName, DisplayName{Value: xepgChannel.XName})
|
||||
|
||||
xepgXML.Channel = append(xepgXML.Channel, &channel)
|
||||
|
||||
// Programme
|
||||
|
||||
*tmpProgram, err = getProgramData(xepgChannel)
|
||||
if err == nil {
|
||||
|
||||
for _, program := range tmpProgram.Program {
|
||||
xepgXML.Program = append(xepgXML.Program, program)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var content, _ = xml.MarshalIndent(xepgXML, " ", " ")
|
||||
var xmlOutput = []byte(xml.Header + string(content))
|
||||
writeByteToFile(System.File.XML, xmlOutput)
|
||||
|
||||
xepgXML = XMLTV{}
|
||||
|
||||
//saveMapToJSONFile(System.File.Images, Data.Cache.ImageCache)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Programmdaten erstellen (createXMLTVFile)
|
||||
func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
||||
|
||||
var xmltvFile = System.Folder.Data + xepgChannel.XmltvFile
|
||||
var channelID = xepgChannel.XMapping
|
||||
|
||||
var xmltv XMLTV
|
||||
|
||||
if xmltvFile == System.Folder.Data+"xTeVe Dummy" {
|
||||
xmltv = createDummyProgram(xepgChannel)
|
||||
} else {
|
||||
|
||||
err = getLocalXMLTV(xmltvFile, &xmltv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, xmltvProgram := range xmltv.Program {
|
||||
|
||||
if xmltvProgram.Channel == channelID {
|
||||
//fmt.Println(&channelID)
|
||||
var program = &Program{}
|
||||
|
||||
// Channel ID
|
||||
program.Channel = xepgChannel.XChannelID
|
||||
program.Start = xmltvProgram.Start
|
||||
program.Stop = xmltvProgram.Stop
|
||||
|
||||
// Title
|
||||
program.Title = xmltvProgram.Title
|
||||
|
||||
// Sub title (Untertitel)
|
||||
program.SubTitle = xmltvProgram.SubTitle
|
||||
|
||||
// Description (Beschreibung)
|
||||
program.Desc = xmltvProgram.Desc
|
||||
|
||||
// Category (Kategorie)
|
||||
getCategory(program, xmltvProgram, xepgChannel)
|
||||
|
||||
// Country (Länder)
|
||||
program.Country = xmltvProgram.Country
|
||||
|
||||
// Program icon (Poster / Cover)
|
||||
getPoster(program, xmltvProgram, xepgChannel)
|
||||
|
||||
// Language (Sprache)
|
||||
program.Language = xmltvProgram.Language
|
||||
|
||||
// Episodes numbers (Episodennummern)
|
||||
getEpisodeNum(program, xmltvProgram, xepgChannel)
|
||||
|
||||
// Video (Videoparameter)
|
||||
getVideo(program, xmltvProgram, xepgChannel)
|
||||
|
||||
// Date (Datum)
|
||||
program.Date = xmltvProgram.Date
|
||||
|
||||
// Previously shown (Wiederholung)
|
||||
program.PreviouslyShown = xmltvProgram.PreviouslyShown
|
||||
|
||||
// New (Neu)
|
||||
program.New = xmltvProgram.New
|
||||
|
||||
// Live
|
||||
program.Live = xmltvProgram.Live
|
||||
|
||||
xepgXML.Program = append(xepgXML.Program, program)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Dummy Daten erstellen (createXMLTVFile)
|
||||
func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
||||
|
||||
var currentTime = time.Now()
|
||||
var dateArray = strings.Fields(currentTime.String())
|
||||
var offset = " " + dateArray[2]
|
||||
var currentDay = currentTime.Format("20060102")
|
||||
var startTime, _ = time.Parse("20060102150405", currentDay+"000000")
|
||||
|
||||
showInfo("Create Dummy Guide:" + "Time offset" + offset + " - " + xepgChannel.XName)
|
||||
|
||||
var dl = strings.Split(xepgChannel.XMapping, "_")
|
||||
dummyLength, err := strconv.Atoi(dl[0])
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
return
|
||||
}
|
||||
|
||||
for d := 0; d < 4; d++ {
|
||||
|
||||
var epgStartTime = startTime.Add(time.Hour * time.Duration(d*24))
|
||||
|
||||
for t := dummyLength; t <= 1440; t = t + dummyLength {
|
||||
|
||||
var epgStopTime = epgStartTime.Add(time.Minute * time.Duration(dummyLength))
|
||||
|
||||
var epg Program
|
||||
poster := Poster{}
|
||||
|
||||
epg.Channel = xepgChannel.XMapping
|
||||
epg.Start = epgStartTime.Format("20060102150405") + offset
|
||||
epg.Stop = epgStopTime.Format("20060102150405") + offset
|
||||
epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"})
|
||||
epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"})
|
||||
|
||||
if Settings.XepgReplaceMissingImages == true {
|
||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||
epg.Poster = append(epg.Poster, poster)
|
||||
}
|
||||
|
||||
epg.EpisodeNum = append(epg.EpisodeNum, &EpisodeNum{Value: epgStartTime.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||
|
||||
epg.New = &New{Value: ""}
|
||||
|
||||
dummyXMLTV.Program = append(dummyXMLTV.Program, &epg)
|
||||
epgStartTime = epgStopTime
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Kategorien erweitern (createXMLTVFile)
|
||||
func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||
|
||||
for _, i := range xmltvProgram.Category {
|
||||
|
||||
category := &Category{}
|
||||
category.Value = i.Value
|
||||
category.Lang = i.Lang
|
||||
program.Category = append(program.Category, category)
|
||||
|
||||
}
|
||||
|
||||
if len(xepgChannel.XCategory) > 0 {
|
||||
|
||||
category := &Category{}
|
||||
category.Value = xepgChannel.XCategory
|
||||
category.Lang = "en"
|
||||
program.Category = append(program.Category, category)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Programm Poster Cover aus der XMLTV Datei laden
|
||||
func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||
|
||||
for _, poster := range xmltvProgram.Poster {
|
||||
poster.Src = getCacheImageURL(poster.Src)
|
||||
program.Poster = append(program.Poster, poster)
|
||||
}
|
||||
|
||||
if Settings.XepgReplaceMissingImages == true {
|
||||
|
||||
if len(xmltvProgram.Poster) == 0 {
|
||||
var poster Poster
|
||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||
program.Poster = append(program.Poster, poster)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Episodensystem übernehmen, falls keins vorhanden ist und eine Kategorie im Mapping eingestellt wurden, wird eine Episode erstellt
|
||||
func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||
|
||||
program.EpisodeNum = xmltvProgram.EpisodeNum
|
||||
|
||||
if len(xepgChannel.XCategory) > 0 {
|
||||
|
||||
if len(xmltvProgram.EpisodeNum) == 0 {
|
||||
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: time.Now().Format("2006-01-02"), System: "original-air-date"})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Videoparameter erstellen (createXMLTVFile)
|
||||
func getVideo(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||
|
||||
var video Video
|
||||
video.Present = xmltvProgram.Video.Present
|
||||
video.Colour = xmltvProgram.Video.Colour
|
||||
video.Aspect = xmltvProgram.Video.Aspect
|
||||
video.Quality = xmltvProgram.Video.Quality
|
||||
|
||||
if len(xmltvProgram.Video.Quality) == 0 {
|
||||
|
||||
if strings.Contains(strings.ToUpper(xepgChannel.XName), " HD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " FHD") {
|
||||
video.Quality = "HDTV"
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToUpper(xepgChannel.XName), " UHD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " 4K") {
|
||||
video.Quality = "UHDTV"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
program.Video = video
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Lokale Provider XMLTV Datei laden
|
||||
func getLocalXMLTV(file string, xmltv *XMLTV) (err error) {
|
||||
|
||||
if _, ok := Data.Cache.XMLTV[file]; !ok {
|
||||
|
||||
// Cache initialisieren
|
||||
if len(Data.Cache.XMLTV) == 0 {
|
||||
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||
}
|
||||
|
||||
// XML Daten lesen
|
||||
content, err := readByteFromFile(file)
|
||||
|
||||
// Lokale XML Datei existiert nicht im Ordner: data
|
||||
if err != nil {
|
||||
ShowError(err, 1004)
|
||||
err = errors.New("Local copy of the file no longer exists")
|
||||
return err
|
||||
}
|
||||
|
||||
// XML Datei parsen
|
||||
err = xml.Unmarshal(content, &xmltv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Data.Cache.XMLTV[file] = *xmltv
|
||||
|
||||
} else {
|
||||
*xmltv = Data.Cache.XMLTV[file]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// M3U Datei erstellen
|
||||
func createM3UFile() {
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Create M3U file (%s)", System.File.M3U))
|
||||
_, err := buildM3U([]string{})
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
}
|
||||
|
||||
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// XEPG Datenbank bereinigen
|
||||
func cleanupXEPG() {
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
|
||||
Data.XEPG.XEPGCount = 0
|
||||
|
||||
for id, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err == nil {
|
||||
|
||||
if indexOfString(xepgChannel.Name, Data.Cache.Streams.Active) == -1 {
|
||||
delete(Data.XEPG.Channels, id)
|
||||
} else {
|
||||
if xepgChannel.XActive == true {
|
||||
Data.XEPG.XEPGCount++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err := saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||
if err != nil {
|
||||
ShowError(err, 000)
|
||||
return
|
||||
}
|
||||
|
||||
showInfo("XEPG Channels:" + fmt.Sprintf("%d", Data.XEPG.XEPGCount))
|
||||
|
||||
if len(Data.Streams.Active) > 0 && Data.XEPG.XEPGCount == 0 {
|
||||
showWarning(2005)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Streaming URL für die Channels App generieren
|
||||
func getStreamByChannelID(channelID string) (playlistID, streamURL string, err error) {
|
||||
|
||||
err = errors.New("Channel not found")
|
||||
|
||||
for _, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
|
||||
fmt.Println(xepgChannel.XChannelID)
|
||||
|
||||
if err == nil {
|
||||
|
||||
if channelID == xepgChannel.XChannelID {
|
||||
|
||||
playlistID = xepgChannel.FileM3UID
|
||||
streamURL = xepgChannel.URL
|
||||
|
||||
return playlistID, streamURL, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user