diff --git a/.dockerignore b/.dockerignore
index e0187c7..247b853 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -16,3 +16,10 @@ agent.md
skill.md
*.md
!changelog-beta.md
+
+# Web UI toolchain. The compiled bundle is committed under html/js and is
+# embedded by the Go build, so the TypeScript sources and npm tree are not
+# needed in the build context. html/ itself must stay in the context.
+node_modules/
+package.json
+package-lock.json
diff --git a/.drone.yml b/.drone.yml
index 0b43d02..5afeab0 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -8,6 +8,8 @@ steps:
commands:
- go mod download
- go vet ./...
+ - test -z "$(gofmt -l .)" || (echo "gofmt needed:" && gofmt -l . && exit 1)
+ - go run honnef.co/go/tools/cmd/staticcheck@v0.8.1 ./...
- go test ./...
- name: webui-check
diff --git a/Dockerfile b/Dockerfile
index dbd3b26..7d28b2c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,14 +17,18 @@ RUN XTEVE_VERSION="$(grep -m1 '^#### ' changelog-beta.md | cut -d' ' -f2 | sed '
&& CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
go build -trimpath -ldflags="-s -w -X main.Version=$XTEVE_VERSION" -o /out/xteve ./xteve.go
-FROM mwader/static-ffmpeg:latest AS ffmpeg
+# Static ffmpeg/ffprobe binaries. To bump, pick a tag from
+# https://hub.docker.com/r/mwader/static-ffmpeg/tags and change it here.
+FROM mwader/static-ffmpeg:7.1.1 AS ffmpeg
FROM alpine:3.23
+# Build-time defaults for the xteve user. The entrypoint can change them at
+# runtime with PUID/PGID, so these only matter when neither is set.
ARG XTEVE_UID=1000
ARG XTEVE_GID=1000
-RUN apk add --no-cache ca-certificates tzdata \
+RUN apk add --no-cache ca-certificates tzdata su-exec \
&& addgroup -S -g "${XTEVE_GID}" xteve \
&& adduser -S -D -H -u "${XTEVE_UID}" -G xteve xteve \
&& mkdir -p /xteve/config \
@@ -37,13 +41,16 @@ COPY --from=ffmpeg /ffmpeg /usr/local/bin/ffmpeg
COPY --from=ffmpeg /ffprobe /usr/local/bin/ffprobe
COPY docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
-USER xteve
+# The entrypoint starts as root so it can apply PUID/PGID and fix ownership of
+# the config directory, then drops to the xteve user with su-exec.
EXPOSE 34400/tcp
ENV XTEVE_CONFIG=/xteve/config
ENV XTEVE_PORT=34400
+VOLUME ["/xteve/config"]
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -qO- "http://127.0.0.1:${XTEVE_PORT}/healthz" > /dev/null || exit 1
diff --git a/README-DEV.md b/README-DEV.md
index fb3893e..bc9bb49 100644
--- a/README-DEV.md
+++ b/README-DEV.md
@@ -55,3 +55,7 @@ Drone (`.drone.yml`) runs vet, tests, the bundle check, hadolint and compose val
## Endpoints useful for operations
* `GET /healthz` returns `{"status":"ok","version":...,"scanInProgress":...}` with no authentication. The Docker healthcheck uses it.
+
+## Container image
+
+`Dockerfile` builds the binary in a `golang:1.27.1-alpine` stage, copies static `ffmpeg`/`ffprobe` from a pinned `mwader/static-ffmpeg` tag (bump the tag in the Dockerfile; there is no other place to change) and runs on `alpine`. The runtime stage installs `su-exec` and does not set `USER`: `docker/entrypoint.sh` starts as root, applies `PUID`/`PGID` to the `xteve` user by editing `/etc/passwd` and `/etc/group`, fixes ownership of the config directory, and execs `su-exec xteve:xteve xteve`. With `--user` it skips all of that. `ARG XTEVE_UID`/`XTEVE_GID` remain the build-time defaults so Drone's build args still work. Check the script with `sh -n` and `shellcheck -s sh` after editing.
diff --git a/README.md b/README.md
index 44e9de8..9810401 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,27 @@
# xTeVe
-## M3U Proxy for Plex DVR and Emby Live TV.
+## M3U Proxy for Plex DVR and Emby Live TV.
Documentation for setup and configuration is [here](https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md).
-#### Donation
-* **Bitcoin:** 1c1iCe4CJPfNUXtqxKBbW2Qd2EtqRPWme
-
+## About this fork
+
+The upstream xTeVe project has been inactive since 2021. This fork is maintained privately for a single trusted LAN. It is Docker-only and built for amd64 only. There are no release archives and no self-update.
+
+What it adds over upstream:
+
+* Web UI redesign with a mobile and accessibility pass (responsive navigation, keyboard flow, focus visibility, ARIA announcements, contrast).
+* Plex API guide refresh: after a lineup or XEPG update xTeVe can ask Plex to reload the DVR guide. New settings: `use_plexAPI`, `plex.url`, `plex.token`.
+* Strict or relaxed handling of channels whose EPG source went missing in XEPG, with automatic re-mapping.
+* A wizard-completed flag so the setup wizard is not shown again on restart.
+* First-party Docker image with a static ffmpeg, runtime `PUID`/`PGID`, and a healthcheck.
+* Drone CI: vet, tests, web bundle check, Dockerfile lint, compose validation, image publishing.
+* Web UI embedded in the binary with `go:embed`.
+* Unauthenticated `GET /healthz` endpoint for container health checks.
+* The self-updater and the GitHub branch switching are removed.
+
+The plan for further work is in `tasks/improvement-plan.md`.
## Requirements
### Plex
@@ -23,7 +37,7 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
* Emby Client with Live-TV support
* Emby Premiere
----
+---
## Features
@@ -48,164 +62,82 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
---
-## Project Analysis (UI + Operations)
+## Running the container
-The core architecture is strong: a Go backend with websocket-driven UI updates, filesystem-based state, and very low runtime overhead.
-The weakest points are mostly operational and UX-focused:
+### Image
-* UI was historically utility-first and desktop-biased, with limited responsive behavior and visual hierarchy.
-* Container usage was documented externally but there was no first-party Dockerfile/compose setup in this repository.
-* Static web assets are generated into `src/webUI.go`, which works, but creates large diffs and a heavier edit/build cycle.
+The image is built by Drone and pushed to a private registry:
-### Recommended next technical improvements
-
-1. Replace generated `src/webUI.go` with Go `embed` for simpler static asset management and cleaner PR diffs.
-2. Add CI checks (`go test ./...`, build on Linux/arm64/amd64, docker build smoke test).
-3. Add a dedicated health endpoint (for example `/healthz`) to decouple health checks from HDHomeRun endpoints.
-4. Add integration tests around websocket commands that mutate settings/files to reduce regression risk.
-
----
-
-## Container-First Run (Included In This Repo)
-
-### Build image
-```bash
-docker build -t xteve:local .
+```
+registry.coadcorp.com/nathan/xteve
```
-### Run with Docker Compose (bridge mode)
+Tags:
+
+* `latest` and `` from the `master` branch.
+* `` and `` from every other branch.
+
+```bash
+docker pull registry.coadcorp.com/nathan/xteve:latest
+```
+
+The image is linux/amd64 only.
+
+### Start with Docker Compose
+
+Bridge mode (default). The web UI is on port 34400.
+
```bash
docker compose up -d
```
-Compose file: `docker-compose.yml`
-Persistent config volume: `./docker-data/config:/xteve/config`
+Host networking. Use this if you want Plex or Emby to discover the tuner by SSDP/DLNA. Multicast does not cross the Docker bridge, so publishing UDP 1900 in bridge mode is not enough on its own. Linux only.
-### Run with Docker Compose (host networking, Linux recommended for discovery)
```bash
docker compose -f docker-compose.host.yml up -d
```
-Host networking improves LAN discovery behavior (SSDP/DLNA) for Plex/Emby in many setups.
+In bridge mode you can still add the tuner manually in Plex or Emby with `http://:34400`.
-### Container environment variables
+Both compose files pull `registry.coadcorp.com/nathan/xteve:latest`. To build locally instead, uncomment the `build:` block in the compose file, or run `docker build -t xteve:local .`.
-* `XTEVE_CONFIG` (default: `/xteve/config`)
-* `XTEVE_PORT` (default: `34400`)
+### Environment variables
-### Image details
+| Variable | Default | Meaning |
+|---|---|---|
+| `XTEVE_CONFIG` | `/xteve/config` | Config directory inside the container. |
+| `XTEVE_PORT` | `34400` | HTTP port xTeVe listens on. |
+| `PUID` | `1000` | User id xTeVe runs as. Set it to the owner of the host config directory. |
+| `PGID` | `1000` | Group id xTeVe runs as. |
+| `TZ` | unset (UTC) | Time zone, for example `Australia/Sydney`. |
-* Multi-stage build (Go builder + minimal Alpine runtime)
-* Runs as non-root user (`xteve`)
-* Built-in healthcheck against `http://127.0.0.1:${XTEVE_PORT}/healthz`
+The container starts as root, sets the `xteve` user to `PUID`/`PGID`, fixes the ownership of the config directory if needed, and then drops to that user before starting xTeVe. If the container is started with `--user`, the ids are left alone and xTeVe runs as the given user. The old `XTEVE_UID`/`XTEVE_GID` variables are still accepted as aliases.
+
+### Volume
+
+The config lives in `/xteve/config` (declared as a volume in the image). The compose files bind it to `./docker-data/config`. It holds `settings.json`, the mapping and EPG data, backups and the cache, so it is small enough to back up as a whole.
+
+If an older container kept its config in `/config` or `/home/xteve/.xteve`, the entrypoint copies it to the new location on first start.
+
+### Healthcheck
+
+The image has a `HEALTHCHECK` that requests `http://127.0.0.1:${XTEVE_PORT}/healthz` every 30 seconds. The endpoint needs no authentication and returns the version and whether a scan is in progress. `docker ps` shows the state as `healthy` or `unhealthy`.
+
+### ffmpeg
+
+A static ffmpeg and ffprobe are in the image at `/usr/local/bin/ffmpeg` and `/usr/local/bin/ffprobe`, taken from the pinned `mwader/static-ffmpeg` image. In a container xTeVe defaults `ffmpeg.path` to that location. VLC is not included.
---
-## Downloads v2 | 64 Bit only
-#### 64 Bit Intel / AMD
+## Security notes
-* [Windows](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_windows_amd64.zip?raw=true)
-* [OS X](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_darwin_amd64.zip?raw=true)
-* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_amd64.zip?raw=true)
-* [FreeBSD](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_freebsd_amd64.zip?raw=true)
-
-#### 64 Bit ARM
-* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_arm64.zip?raw=true)
-
-#### Recommended Docker Image (Linux 64 Bit)
-Thanks to @alturismo and @LeeD for creating the Docker Images.
-
-**Created by alturismo:**
-[xTeVe](https://hub.docker.com/r/alturismo/xteve)
-[xTeVe / Guide2go](https://hub.docker.com/r/alturismo/xteve_guide2go)
-[xTeVe / Guide2go / owi2plex](https://hub.docker.com/r/alturismo/xteve_g2g_owi)
-
-Including:
-- Guide2go: XMLTV grabber for Schedules Direct
-- owi2plex: XMLTV file grabber for Enigma receivers
-
-**Created by LeeD:**
-[xTeVe / Guide2go / Zap2XML](https://hub.docker.com/r/dnsforge/xteve)
-
-Including:
-- Guide2go: XMLTV grabber for Schedules Direct
-- Zap2XML: Perl based zap2it XMLTV grabber
-- Bash: A Unix / Linux shell
-- Crond: Daemon to execute scheduled commands
-- Perl: Programming language
+* This fork is meant for a trusted LAN. Do not put it on the internet.
+* Web authentication is off by default. Turn it on under Settings > Authentication before you expose it any further than the LAN, for example through a reverse proxy.
+* `/healthz` is always unauthenticated. It reveals the version and scan state only.
+* The container starts as root to apply `PUID`/`PGID` and then drops privileges. Start it with `--user` if you prefer it never to run as root.
---
-### xTeVe Beta branch
-New features and bug fixes are only available in beta branch. Only after successful testing are they are merged into the master branch.
-
-**It is not recommended to use the beta version in a production system.**
-
-With the command line argument `branch` the Git Branch can be changed. xTeVe must be started via the terminal.
-
-#### Switch from master to beta branch:
-```
-xteve -branch beta
-
-...
-[xTeVe] GitHub: https://github.com/xteve-project
-[xTeVe] Git Branch: beta [xteve-project]
-...
-```
-
-#### Switch from beta to master branch:
-```
-xteve -branch master
-
-...
-[xTeVe] GitHub: https://github.com/xteve-project
-[xTeVe] Git Branch: master [xteve-project]
-...
-```
-
-When the branch is changed, an update is only performed if there is a new version and the update function is activated in the settings.
-
----
-
-## Build from source code [Go / Golang]
-
-#### Requirements
-* [Go](https://golang.org) (go1.16.2 or newer)
-
-#### Dependencies
-* [go-ssdp](https://github.com/koron/go-ssdp)
-* [websocket](https://github.com/gorilla/websocket)
-* [osext](https://github.com/kardianos/osext)
-
-#### Build
-1. Download source code
-2. Install dependencies
-```
-go get github.com/koron/go-ssdp
-go get github.com/gorilla/websocket
-go get github.com/kardianos/osext
-```
-3. Build xTeVe
-```
-go build xteve.go
-```
-
----
-
-## Fork without pull request :mega:
-When creating a fork, the xTeVe GitHub account must be changed from the source code or the update function disabled.
-Future updates of the xteve-project would update your fork. :wink:
-
-xteve.go - Line: 29
-```Go
-var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-Downloads", Update: true}
-
-/*
- Branch: GitHub Branch
- User: GitHub Username
- Repo: GitHub Repository
- Update: Automatic updates from the GitHub repository [true|false]
-*/
-
-```
+## Development
+See [README-DEV.md](README-DEV.md) for the layout, how to build the binary and the web UI, and how CI publishes the image.
diff --git a/docker-compose.host.yml b/docker-compose.host.yml
index eb58f26..3a2c3dd 100644
--- a/docker-compose.host.yml
+++ b/docker-compose.host.yml
@@ -1,18 +1,26 @@
+# Host-network compose file for the private xTeVe image.
+#
+# Host networking lets SSDP/DLNA multicast reach the container, so Plex and
+# Emby can discover the tuner automatically. Ports are not published in this
+# mode; xTeVe listens on XTEVE_PORT directly on the host. Linux only.
services:
xteve:
- build:
- context: .
- dockerfile: Dockerfile
- args:
- XTEVE_UID: ${XTEVE_UID:-1000}
- XTEVE_GID: ${XTEVE_GID:-1000}
+ image: registry.coadcorp.com/nathan/xteve:latest
+ # Uncomment to build locally instead of pulling from the registry.
+ # build:
+ # context: .
+ # dockerfile: Dockerfile
container_name: xteve
restart: unless-stopped
network_mode: host
environment:
XTEVE_CONFIG: /xteve/config
XTEVE_PORT: "34400"
- XTEVE_UID: ${XTEVE_UID:-1000}
- XTEVE_GID: ${XTEVE_GID:-1000}
+ # Owner of the config directory inside the container. Set these to the
+ # uid/gid that owns ./docker-data/config on the host.
+ PUID: ${PUID:-1000}
+ PGID: ${PGID:-1000}
+ # Time zone for log timestamps and the EPG, for example Australia/Sydney.
+ TZ: ${TZ:-UTC}
volumes:
- ./docker-data/config:/xteve/config
diff --git a/docker-compose.yml b/docker-compose.yml
index bf25840..dbeb3a3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,18 +1,29 @@
+# Bridge-mode compose file for the private xTeVe image.
+#
+# SSDP/DLNA discovery (how Plex and Emby find the tuner automatically) uses
+# multicast on UDP 1900. Multicast does not cross the Docker bridge, so
+# publishing 1900/udp below is not enough on its own. If you want discovery
+# to work, use host networking instead: docker-compose.host.yml. In bridge
+# mode you can still add the tuner in Plex/Emby by entering
+# http://:34400 manually.
services:
xteve:
- build:
- context: .
- dockerfile: Dockerfile
- args:
- XTEVE_UID: ${XTEVE_UID:-1000}
- XTEVE_GID: ${XTEVE_GID:-1000}
+ image: registry.coadcorp.com/nathan/xteve:latest
+ # Uncomment to build locally instead of pulling from the registry.
+ # build:
+ # context: .
+ # dockerfile: Dockerfile
container_name: xteve
restart: unless-stopped
environment:
XTEVE_CONFIG: /xteve/config
XTEVE_PORT: "34400"
- XTEVE_UID: ${XTEVE_UID:-1000}
- XTEVE_GID: ${XTEVE_GID:-1000}
+ # Owner of the config directory inside the container. Set these to the
+ # uid/gid that owns ./docker-data/config on the host.
+ PUID: ${PUID:-1000}
+ PGID: ${PGID:-1000}
+ # Time zone for log timestamps and the EPG, for example Australia/Sydney.
+ TZ: ${TZ:-UTC}
ports:
- "34400:34400/tcp"
- "1900:1900/udp"
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index 58280b2..f857a2e 100755
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -2,7 +2,16 @@
set -eu
DEFAULT_CONFIG_DIR="/xteve/config"
-LEGACY_CONFIG_DIRS="/config /xteve /home/xteve/.xteve"
+# Older images and other Docker images kept the config in one of these.
+# /xteve itself is not listed because it is the parent of the default dir.
+LEGACY_CONFIG_DIRS="/config /home/xteve/.xteve"
+
+RUN_USER="xteve"
+XTEVE_BIN="/usr/local/bin/xteve"
+
+log() {
+ echo "[entrypoint] $*"
+}
resolve_config_dir() {
if [ -n "${XTEVE_CONFIG:-}" ] && [ "${XTEVE_CONFIG}" != "${DEFAULT_CONFIG_DIR}" ]; then
@@ -48,11 +57,75 @@ settings_initialized() {
return 1
}
+is_number() {
+ case "$1" in
+ ''|*[!0-9]*) return 1 ;;
+ *) return 0 ;;
+ esac
+}
+
+# Apply PUID/PGID to the xteve user and group. Only called when running as root.
+# Falls back to XTEVE_UID/XTEVE_GID (the old variable names), then to the ids
+# baked into the image at build time.
+apply_ids() {
+ current_uid="$(id -u "${RUN_USER}")"
+ current_gid="$(id -g "${RUN_USER}")"
+
+ want_uid="${PUID:-${XTEVE_UID:-${current_uid}}}"
+ want_gid="${PGID:-${XTEVE_GID:-${current_gid}}}"
+
+ if ! is_number "${want_uid}" || ! is_number "${want_gid}"; then
+ log "ERROR: PUID/PGID must be numeric (got PUID=${want_uid} PGID=${want_gid})" >&2
+ exit 1
+ fi
+
+ if [ "${want_gid}" != "${current_gid}" ]; then
+ # Edit /etc/group in place. deluser/delgroup on busybox refuse to remove
+ # a group that still has members, so sed is the safest option.
+ sed -i "s/^${RUN_USER}:\([^:]*\):${current_gid}:/${RUN_USER}:\1:${want_gid}:/" /etc/group
+ fi
+
+ if [ "${want_uid}" != "${current_uid}" ] || [ "${want_gid}" != "${current_gid}" ]; then
+ sed -i "s/^${RUN_USER}:\([^:]*\):${current_uid}:${current_gid}:/${RUN_USER}:\1:${want_uid}:${want_gid}:/" /etc/passwd
+ fi
+
+ new_uid="$(id -u "${RUN_USER}")"
+ new_gid="$(id -g "${RUN_USER}")"
+ if [ "${new_uid}" != "${want_uid}" ] || [ "${new_gid}" != "${want_gid}" ]; then
+ log "ERROR: failed to set ${RUN_USER} to UID:GID ${want_uid}:${want_gid} (now ${new_uid}:${new_gid})" >&2
+ exit 1
+ fi
+}
+
+# Make sure the config directory belongs to the xteve user. The directory is
+# small (settings, cache, backups), so a recursive chown is acceptable, but it
+# is skipped when the ownership is already right.
+fix_config_owner() {
+ dir="$1"
+ uid="$2"
+ gid="$3"
+
+ owner="$(stat -c '%u:%g' "${dir}" 2>/dev/null || echo "")"
+ if [ "${owner}" != "${uid}:${gid}" ] || [ "${MIGRATED}" = "1" ]; then
+ log "Setting ownership of ${dir} to ${uid}:${gid}"
+ chown -R "${uid}:${gid}" "${dir}" || log "WARNING: chown of ${dir} failed; continuing"
+ fi
+}
+
CONFIG_DIR="$(resolve_config_dir)"
PORT="${XTEVE_PORT:-34400}"
+# The binary uses XTEVE_CONFIG to detect that it runs in a container and to
+# default ffmpeg.path to /usr/local/bin/ffmpeg. Keep it in sync with the
+# resolved directory so both agree.
+export XTEVE_CONFIG="${CONFIG_DIR}"
+
mkdir -p "${CONFIG_DIR}"
+# Set to 1 when files were copied from a legacy directory. When that happens as
+# root the copies are root-owned, so the ownership fix below must not be skipped.
+MIGRATED=0
+
if ! settings_initialized "${CONFIG_DIR}/settings.json"; then
for legacy_dir in ${LEGACY_CONFIG_DIRS}; do
if [ "${legacy_dir}" = "${CONFIG_DIR}" ]; then
@@ -60,7 +133,7 @@ if ! settings_initialized "${CONFIG_DIR}/settings.json"; then
fi
if settings_initialized "${legacy_dir}/settings.json"; then
- echo "[entrypoint] Migrating existing configuration from ${legacy_dir} to ${CONFIG_DIR}"
+ log "Migrating existing configuration from ${legacy_dir} to ${CONFIG_DIR}"
for file in authentication.json pms.json settings.json xepg.json urls.json; do
if [ "${file}" = "settings.json" ] || [ "${file}" = "xepg.json" ] || [ "${file}" = "urls.json" ]; then
@@ -74,24 +147,55 @@ if ! settings_initialized "${CONFIG_DIR}/settings.json"; then
copy_if_missing "${legacy_dir}/${dir_name}" "${CONFIG_DIR}/${dir_name}"
done
+ MIGRATED=1
break
fi
done
fi
+if [ "$(id -u)" = "0" ]; then
+ # Started as root (the default): apply PUID/PGID, fix ownership, drop privileges.
+ apply_ids
+ RUN_UID="$(id -u "${RUN_USER}")"
+ RUN_GID="$(id -g "${RUN_USER}")"
+ fix_config_owner "${CONFIG_DIR}" "${RUN_UID}" "${RUN_GID}"
+
+ if ! su-exec "${RUN_USER}:${RUN_USER}" touch "${CONFIG_DIR}/.xteve-write-test" 2>/dev/null; then
+ log "ERROR: Config directory is not writable: ${CONFIG_DIR}" >&2
+ log "xteve would run as UID:GID ${RUN_UID}:${RUN_GID}" >&2
+ ls -ld "${CONFIG_DIR}" >&2 || true
+ log "Hint: set PUID/PGID to the owner of the host directory, or fix its ownership/permissions on the host." >&2
+ exit 1
+ fi
+ rm -f "${CONFIG_DIR}/.xteve-write-test"
+
+ log "Using config directory: ${CONFIG_DIR}"
+ log "Running as UID:GID ${RUN_UID}:${RUN_GID} (PUID/PGID)"
+ if [ -f "${CONFIG_DIR}/settings.json" ]; then
+ log "settings.json details: $(ls -l "${CONFIG_DIR}/settings.json" | awk '{print $1, $3, $4, $5, $9}')"
+ fi
+
+ exec su-exec "${RUN_USER}:${RUN_USER}" "${XTEVE_BIN}" -config "${CONFIG_DIR}" -port "${PORT}" "$@"
+fi
+
+# Started with --user: keep the given identity, no id or ownership changes.
+if [ -n "${PUID:-}${PGID:-}${XTEVE_UID:-}${XTEVE_GID:-}" ]; then
+ log "Container started as non-root; PUID/PGID are ignored"
+fi
+
if ! touch "${CONFIG_DIR}/.xteve-write-test" 2>/dev/null; then
- echo "[entrypoint] ERROR: Config directory is not writable: ${CONFIG_DIR}" >&2
- echo "[entrypoint] Running as UID:GID $(id -u):$(id -g)" >&2
+ log "ERROR: Config directory is not writable: ${CONFIG_DIR}" >&2
+ log "Running as UID:GID $(id -u):$(id -g)" >&2
ls -ld "${CONFIG_DIR}" >&2 || true
- echo "[entrypoint] Hint: ensure host path ownership/permissions allow this UID:GID to write, or set matching container UID/GID at build time." >&2
+ log "Hint: ensure the host path ownership/permissions allow this UID:GID to write, or drop --user and set PUID/PGID instead." >&2
exit 1
fi
rm -f "${CONFIG_DIR}/.xteve-write-test"
-echo "[entrypoint] Using config directory: ${CONFIG_DIR}"
-echo "[entrypoint] Running as UID:GID $(id -u):$(id -g)"
+log "Using config directory: ${CONFIG_DIR}"
+log "Running as UID:GID $(id -u):$(id -g)"
if [ -f "${CONFIG_DIR}/settings.json" ]; then
- echo "[entrypoint] settings.json details: $(ls -l "${CONFIG_DIR}/settings.json" | awk '{print $1, $3, $4, $5, $9}')"
+ log "settings.json details: $(ls -l "${CONFIG_DIR}/settings.json" | awk '{print $1, $3, $4, $5, $9}')"
fi
-exec /usr/local/bin/xteve -config "${CONFIG_DIR}" -port "${PORT}" "$@"
+exec "${XTEVE_BIN}" -config "${CONFIG_DIR}" -port "${PORT}" "$@"
diff --git a/src/authentication.go b/src/authentication.go
index ebfee34..7720f29 100644
--- a/src/authentication.go
+++ b/src/authentication.go
@@ -63,7 +63,7 @@ func createFirstUserForAuthentication(username, password string) (token string,
func tokenAuthentication(token string) (newToken string, err error) {
- if System.ConfigurationWizard == true {
+ if System.ConfigurationWizard {
return
}
@@ -109,7 +109,7 @@ func urlAuth(r *http.Request, requestType string) (err error) {
case "m3u":
level = "authentication.m3u"
- if Settings.AuthenticationM3U == true {
+ if Settings.AuthenticationM3U {
token, err = authentication.UserAuthentication(username, password)
if err != nil {
return
@@ -119,7 +119,7 @@ func urlAuth(r *http.Request, requestType string) (err error) {
case "xml":
level = "authentication.xml"
- if Settings.AuthenticationXML == true {
+ if Settings.AuthenticationXML {
token, err = authentication.UserAuthentication(username, password)
if err != nil {
return
@@ -150,18 +150,22 @@ func checkAuthorizationLevel(token, level string) (err error) {
if v, ok := userData[level].(bool); ok {
- if v == false {
+ if !v {
err = errors.New("No authorization")
}
} else {
userData[level] = false
- err = authentication.WriteUserData(userID, userData)
+ if err = authentication.WriteUserData(userID, userData); err != nil {
+ ShowError(err, 0)
+ }
err = errors.New("No authorization")
}
} else {
- err = authentication.WriteUserData(userID, userData)
+ if err = authentication.WriteUserData(userID, userData); err != nil {
+ ShowError(err, 0)
+ }
err = errors.New("No authorization")
}
diff --git a/src/backup.go b/src/backup.go
index 437fbcb..10c6102 100644
--- a/src/backup.go
+++ b/src/backup.go
@@ -1,274 +1,273 @@
package src
import (
- b64 "encoding/base64"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "time"
+ b64 "encoding/base64"
+ "errors"
+ "fmt"
+ "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
+ 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
- }
+ if len(Settings.BackupPath) > 0 {
+ System.Folder.Backup = Settings.BackupPath
+ }
- showInfo("Backup Path:" + System.Folder.Backup)
+ showInfo("Backup Path:" + System.Folder.Backup)
- err = checkFolder(System.Folder.Backup)
- if err != nil {
- ShowError(err, 1070)
- return
- }
+ err = checkFolder(System.Folder.Backup)
+ if err != nil {
+ ShowError(err, 1070)
+ return
+ }
- // Alte Backups löschen
- files, err := ioutil.ReadDir(System.Folder.Backup)
+ // Alte Backups löschen
+ files, err := os.ReadDir(System.Folder.Backup)
- if err == nil {
+ if err == nil {
- for _, file := range files {
+ for _, file := range files {
- if filepath.Ext(file.Name()) == ".zip" && strings.Contains(file.Name(), "xteve_auto_backup") {
- oldBackupFiles = append(oldBackupFiles, file.Name())
- }
+ 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
- }
+ // 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++ {
+ 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)
+ os.RemoveAll(System.Folder.Backup + oldBackupFiles[i])
+ debug = fmt.Sprintf("Delete backup file:%s", oldBackupFiles[i])
+ showDebug(debug, 1)
- }
+ }
- if Settings.BackupKeep == 0 {
- return
- }
+ if Settings.BackupKeep == 0 {
+ return
+ }
- } else {
+ } else {
- return
+ return
- }
+ }
- // Backup erstellen
- if err == nil {
+ // Backup erstellen
+ if err == nil {
- target = System.Folder.Backup + archiv
+ target = System.Folder.Backup + archiv
- for _, i := range SystemFiles {
- sourceFiles = append(sourceFiles, System.Folder.Config+i)
- }
+ for _, i := range SystemFiles {
+ sourceFiles = append(sourceFiles, System.Folder.Config+i)
+ }
- sourceFiles = append(sourceFiles, System.Folder.ImagesUpload)
+ sourceFiles = append(sourceFiles, System.Folder.ImagesUpload)
- err = zipFiles(sourceFiles, target)
+ err = zipFiles(sourceFiles, target)
- if err == nil {
+ if err == nil {
- debug = fmt.Sprintf("Create backup file:%s", target)
- showDebug(debug, 1)
+ debug = fmt.Sprintf("Create backup file:%s", target)
+ showDebug(debug, 1)
- showInfo("Backup file:" + target)
+ showInfo("Backup file:" + target)
- }
+ }
- }
+ }
- return
+ return
}
func xteveBackup() (archiv string, err error) {
- err = checkFolder(System.Folder.Temp)
- if err != nil {
- return
- }
+ err = checkFolder(System.Folder.Temp)
+ if err != nil {
+ return
+ }
- archiv = "xteve_backup_" + time.Now().Format("20060102_1504") + ".zip"
+ archiv = "xteve_backup_" + time.Now().Format("20060102_1504") + ".zip"
- var target = System.Folder.Temp + archiv
- var sourceFiles = make([]string, 0)
+ var target = System.Folder.Temp + archiv
+ var sourceFiles = make([]string, 0)
- for _, i := range SystemFiles {
- sourceFiles = append(sourceFiles, System.Folder.Config+i)
- }
+ for _, i := range SystemFiles {
+ sourceFiles = append(sourceFiles, System.Folder.Config+i)
+ }
- sourceFiles = append(sourceFiles, System.Folder.Data)
+ sourceFiles = append(sourceFiles, System.Folder.Data)
- err = zipFiles(sourceFiles, target)
- if err != nil {
- ShowError(err, 0)
- return
- }
+ err = zipFiles(sourceFiles, target)
+ if err != nil {
+ ShowError(err, 0)
+ return
+ }
- return
+ return
}
func xteveRestore(archive string) (newWebURL string, err error) {
- var newPort, oldPort, backupVersion, tmpRestore string
+ var newPort, oldPort, backupVersion, tmpRestore string
- tmpRestore = System.Folder.Temp + "restore" + string(os.PathSeparator)
+ tmpRestore = System.Folder.Temp + "restore" + string(os.PathSeparator)
- err = checkFolder(tmpRestore)
- if err != nil {
- return
- }
+ err = checkFolder(tmpRestore)
+ if err != nil {
+ return
+ }
- // Zip Archiv in tmp entpacken
- err = extractZIP(archive, tmpRestore)
- if err != nil {
- return
- }
+ // Zip Archiv in tmp entpacken
+ err = extractZIP(archive, tmpRestore)
+ if err != nil {
+ return
+ }
- // Neue Config laden um den Port und die Version zu überprüfen
- newConfig, err := loadJSONFileToMap(tmpRestore + "settings.json")
- if err != nil {
- ShowError(err, 0)
- return
- }
+ // Neue Config laden um den Port und die Version zu überprüfen
+ newConfig, err := loadJSONFileToMap(tmpRestore + "settings.json")
+ if err != nil {
+ ShowError(err, 0)
+ return
+ }
- backupVersion = newConfig["version"].(string)
- if backupVersion < System.Compatibility {
- err = errors.New(getErrMsg(1013))
- return
- }
+ backupVersion = newConfig["version"].(string)
+ if backupVersion < System.Compatibility {
+ err = errors.New(getErrMsg(1013))
+ return
+ }
- // Zip Archiv in den Config Ordner entpacken
- err = extractZIP(archive, System.Folder.Config)
- if err != nil {
- return
- }
+ // Zip Archiv in den Config Ordner entpacken
+ err = extractZIP(archive, System.Folder.Config)
+ if err != nil {
+ return
+ }
- // Neue Config laden um den Port und die Version zu überprüfen
- newConfig, err = loadJSONFileToMap(System.Folder.Config + "settings.json")
- if err != nil {
- ShowError(err, 0)
- return
- }
+ // Neue Config laden um den Port und die Version 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
+ newPort = newConfig["port"].(string)
+ oldPort = Settings.Port
- if newPort == oldPort {
+ if newPort == oldPort {
- if err != nil {
- ShowError(err, 0)
- }
+ if err != nil {
+ ShowError(err, 0)
+ }
- loadSettings()
+ loadSettings()
- err := Init()
- if err != nil {
- ShowError(err, 0)
- return "", err
- }
+ err := Init()
+ if err != nil {
+ ShowError(err, 0)
+ return "", err
+ }
- err = StartSystem(true)
- if err != nil {
- ShowError(err, 0)
- return "", err
- }
+ err = StartSystem(true)
+ if err != nil {
+ ShowError(err, 0)
+ return "", err
+ }
- return "", err
- }
+ return "", err
+ }
- var url = System.URLBase + "/web/"
- newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
+ var url = System.URLBase + "/web/"
+ newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
- os.RemoveAll(tmpRestore)
+ os.RemoveAll(tmpRestore)
- return
+ return
}
func xteveRestoreFromWeb(input string) (newWebURL string, err error) {
- // Base64 Json String in base64 umwandeln
- b64data := input[strings.IndexByte(input, ',')+1:]
+ // Base64 Json String in base64 umwandeln
+ b64data := input[strings.IndexByte(input, ',')+1:]
- // Base64 in bytes umwandeln und speichern
- sDec, err := b64.StdEncoding.DecodeString(b64data)
+ // Base64 in bytes umwandeln und speichern
+ sDec, err := b64.StdEncoding.DecodeString(b64data)
- if err != nil {
- return
- }
+ if err != nil {
+ return
+ }
- var archive = System.Folder.Temp + "restore.zip"
+ var archive = System.Folder.Temp + "restore.zip"
- err = writeByteToFile(archive, sDec)
- if err != nil {
- return
- }
+ err = writeByteToFile(archive, sDec)
+ if err != nil {
+ return
+ }
- newWebURL, err = xteveRestore(archive)
+ newWebURL, err = xteveRestore(archive)
- return
+ return
}
// XteveRestoreFromCLI : Wiederherstellung über die Kommandozeile
func XteveRestoreFromCLI(archive string) (err error) {
- var confirm string
+ var confirm string
- println()
- showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
- showInfo(fmt.Sprintf("Backup File:%s", archive))
- showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
- println()
+ println()
+ showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
+ showInfo(fmt.Sprintf("Backup File:%s", archive))
+ showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
+ println()
- fmt.Print("All data will be replaced with those from the backup. Should the files be restored? [yes|no]:")
+ fmt.Print("All data will be replaced with those from the backup. Should the files be restored? [yes|no]:")
- fmt.Scanln(&confirm)
+ fmt.Scanln(&confirm)
- switch strings.ToLower(confirm) {
+ switch strings.ToLower(confirm) {
- case "yes":
- break
+ case "yes":
+ break
- case "no":
- return
+ case "no":
+ return
- default:
- fmt.Println("Invalid input")
- return
+ default:
+ fmt.Println("Invalid input")
+ return
- }
+ }
- if len(System.Folder.Config) > 0 {
+ if len(System.Folder.Config) > 0 {
- err = checkFilePermission(System.Folder.Config)
- if err != nil {
- return
- }
+ err = checkFilePermission(System.Folder.Config)
+ if err != nil {
+ return
+ }
- _, err = xteveRestore(archive)
- if err != nil {
- return
- }
+ _, err = xteveRestore(archive)
+ if err != nil {
+ return
+ }
- showHighlight(fmt.Sprintf("Restor:Backup was successfully restored. %s can now be started normally", System.Name))
+ showHighlight(fmt.Sprintf("Restor:Backup was successfully restored. %s can now be started normally", System.Name))
- }
- return
+ }
+ return
}
diff --git a/src/buffer.go b/src/buffer.go
index edd4b9d..68fff80 100644
--- a/src/buffer.go
+++ b/src/buffer.go
@@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"net/http"
"net/url"
"os"
@@ -146,7 +145,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
}
// Neuer Stream bei einer bereits aktiven Playlist
- if newStream == true {
+ if newStream {
// Prüfen ob die Playlist noch einen weiteren Stream erlaubt (Tuner)
if len(playlist.Streams) >= playlist.Tuner {
@@ -193,7 +192,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
}
// Überprüfen ob der Stream breits von einem anderen Client abgespielt wird
- if playlist.Streams[streamID].Status == false && newStream == true {
+ if !playlist.Streams[streamID].Status && newStream {
// Neuer Buffer wird benötigt
stream = playlist.Streams[streamID]
@@ -235,7 +234,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
if stream, ok := playlist.Streams[streamID]; ok {
- if stream.Status == false {
+ if !stream.Status {
timeOut++
@@ -261,6 +260,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
// HTTP Clientverbindung überwachen
+ //lint:ignore SA1019 replaced by request context in Phase 2
cn, ok := w.(http.CloseNotifier)
if ok {
@@ -310,10 +310,11 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
var fileName = stream.Folder + f
file, err := os.Open(fileName)
- defer file.Close()
if err == nil {
+ defer file.Close()
+
l, err := file.Stat()
if err == nil {
@@ -327,7 +328,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
file.Seek(0, 0)
- if streaming == false {
+ if !streaming {
contentType := http.DetectContentType(buffer)
_ = contentType
@@ -406,7 +407,7 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
if _, err := os.Stat(tmpFolder); !os.IsNotExist(err) {
- files, err := ioutil.ReadDir(getPlatformPath(tmpFolder))
+ files, err := os.ReadDir(getPlatformPath(tmpFolder))
if err != nil {
ShowError(err, 000)
return
@@ -455,7 +456,7 @@ func killClientConnection(streamID int, playlistID string, force bool) {
var playlist = p.(Playlist)
- if force == true {
+ if force {
delete(playlist.Streams, streamID)
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner))
return
@@ -531,6 +532,21 @@ func clientConnection(stream ThisStream) (status bool) {
return
}
+// addErrorToStream : Fehler an die Clientverbindung des Streams weitergeben
+func addErrorToStream(playlist Playlist, streamID int, playlistID string, err error) {
+
+ var stream = playlist.Streams[streamID]
+
+ if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
+
+ var clients = c.(ClientConnection)
+ clients.Error = err
+ BufferClients.Store(playlistID+stream.MD5, clients)
+
+ }
+
+}
+
func connectToStreamingServer(streamID int, playlistID string) {
if p, ok := BufferInformation.Load(playlistID); ok {
@@ -575,26 +591,12 @@ func connectToStreamingServer(streamID int, playlistID string) {
}
- var addErrorToStream = func(err error) {
-
- var stream = playlist.Streams[streamID]
-
- if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
-
- var clients = c.(ClientConnection)
- clients.Error = err
- BufferClients.Store(playlistID+stream.MD5, clients)
-
- }
-
- }
-
os.RemoveAll(getPlatformPath(tmpFolder))
err := checkFolder(tmpFolder)
if err != nil {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
@@ -609,11 +611,11 @@ func connectToStreamingServer(streamID int, playlistID string) {
return
}
- var stream ThisStream = playlist.Streams[streamID]
+ var stream = playlist.Streams[streamID]
- if stream.Status == false {
+ if !stream.Status {
- if strings.Index(stream.URL, ".m3u8") != -1 {
+ if strings.Contains(stream.URL, ".m3u8") {
showInfo("Streaming Type:" + "[HLS / M3U8]")
} else {
showInfo("Streaming Type:" + "[TS]")
@@ -631,7 +633,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
for {
- if clientConnection(stream) == false {
+ if !clientConnection(stream) {
return
}
@@ -654,6 +656,14 @@ func connectToStreamingServer(streamID int, playlistID string) {
Redirect:
req, err := http.NewRequest("GET", currentURL, nil)
+ if err != nil {
+ ShowError(err, 0)
+ addErrorToStream(playlist, streamID, playlistID, err)
+ killClientConnection(streamID, playlistID, true)
+ clientConnection(stream)
+ return
+ }
+
req.Header.Set("User-Agent", getUserAgent())
req.Header.Set("Connection", "close")
//req.Header.Set("Range", "bytes=0-")
@@ -679,7 +689,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
fmt.Println("Current URL:", currentURL)
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
killClientConnection(streamID, playlistID, true)
clientConnection(stream)
@@ -708,7 +718,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
err = errors.New("Streaming server")
ShowError(err, 4002)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
defer resp.Body.Close()
@@ -719,7 +729,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
} else {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
defer resp.Body.Close()
@@ -749,7 +759,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
showDebug(debug, 1)
BufferInformation.Store(playlist.PlaylistID, playlist)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
killClientConnection(streamID, playlistID, true)
clientConnection(stream)
@@ -759,7 +769,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
}
// Informationen über den Streamingserver auslesen
- if stream.Status == false {
+ if !stream.Status {
if len(stream.URLStreamingServer) == 0 {
@@ -802,10 +812,10 @@ func connectToStreamingServer(streamID int, playlistID string) {
// M3U8 Playlist
case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl", "audio/x-mpegurl":
- body, err := ioutil.ReadAll(resp.Body)
+ body, err := io.ReadAll(resp.Body)
if err != nil {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
}
stream.Body = string(body)
@@ -815,7 +825,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
err = parseM3U8(&stream)
if err != nil {
ShowError(err, 4050)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
}
// Video Stream (TS)
@@ -835,7 +845,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
var tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
- if clientConnection(stream) == false {
+ if !clientConnection(stream) {
resp.Body.Close()
return
}
@@ -843,7 +853,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
bufferFile, err := os.Create(tmpFile)
if err != nil {
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
bufferFile.Close()
resp.Body.Close()
return
@@ -866,7 +876,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
if err != nil && err != io.EOF {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
resp.Body.Close()
return
@@ -877,7 +887,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
if _, err := bufferFile.Write(buffer[:n]); err != nil {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
resp.Body.Close()
return
@@ -887,7 +897,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
fileSize = fileSize + n
- if clientConnection(stream) == false {
+ if !clientConnection(stream) {
resp.Body.Close()
bufferFile.Close()
@@ -929,7 +939,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
- if clientConnection(stream) == false {
+ if !clientConnection(stream) {
bufferFile.Close()
resp.Body.Close()
@@ -944,7 +954,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
bufferFile, err = os.Create(tmpFile)
if err != nil {
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
resp.Body.Close()
return
}
@@ -970,7 +980,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
err = errors.New("Streaming error")
ShowError(err, 4003)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
resp.Body.Close()
return
}
@@ -978,7 +988,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
s++
// Wartezeit für den Download das nächste Segments berechnen
- if stream.HLS == true {
+ if stream.HLS {
var sleep float64
@@ -1143,14 +1153,13 @@ func parseM3U8(stream *ThisStream) (err error) {
}
- return
}
if strings.Contains(stream.Body, "#EXTM3U") {
var lines = strings.Split(strings.Replace(stream.Body, "\r\n", "\n", -1), "\n")
- if stream.DynamicBandwidth == false {
+ if !stream.DynamicBandwidth {
stream.DynamicStream = make(map[int]DynamicStream)
}
@@ -1173,7 +1182,7 @@ func parseM3U8(stream *ThisStream) (err error) {
}
// M3U8 enthällt mehrere Links zu weiteren M3U8 Wiedergabelisten (Bandbreitenoption)
- if segment.Info == true && len(line) > 0 && line[0:1] != "#" {
+ if segment.Info && len(line) > 0 && line[0:1] != "#" {
var dynamicStream DynamicStream
@@ -1217,7 +1226,7 @@ func parseM3U8(stream *ThisStream) (err error) {
noNewSegment = true
- if stream.Status == false {
+ if !stream.Status {
if len(m3u8Segments) >= 2 {
m3u8Segments = m3u8Segments[0 : len(m3u8Segments)-1]
@@ -1229,7 +1238,7 @@ func parseM3U8(stream *ThisStream) (err error) {
segment = s
- if stream.Status == false {
+ if !stream.Status {
noNewSegment = false
stream.LastSequence = segment.Sequence
@@ -1255,9 +1264,9 @@ func parseM3U8(stream *ThisStream) (err error) {
}
- if noNewSegment == false {
+ if !noNewSegment {
- if stream.DynamicBandwidth == true {
+ if stream.DynamicBandwidth {
switchBandwidth(stream)
} else {
stream.Segment = append(stream.Segment, segment)
@@ -1265,7 +1274,7 @@ func parseM3U8(stream *ThisStream) (err error) {
}
- if noNewSegment == true {
+ if noNewSegment {
var sleep = lastSegmentDuration * 0.5
@@ -1371,33 +1380,19 @@ func thirdPartyBuffer(streamID int, playlistID string) {
return
}
- var addErrorToStream = func(err error) {
-
- var stream = playlist.Streams[streamID]
-
- if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
-
- var clients = c.(ClientConnection)
- clients.Error = err
- BufferClients.Store(playlistID+stream.MD5, clients)
-
- }
-
- }
-
os.RemoveAll(getPlatformPath(tmpFolder))
err := checkFolder(tmpFolder)
if err != nil {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
err = checkFile(path)
if err != nil {
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
@@ -1409,7 +1404,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
f, err := os.Create(tmpFile)
f.Close()
if err != nil {
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
@@ -1461,7 +1456,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
ShowError(err, 0)
cmd.Process.Kill()
cmd.Wait()
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
@@ -1471,11 +1466,11 @@ func thirdPartyBuffer(streamID int, playlistID string) {
ShowError(err, 0)
cmd.Process.Kill()
cmd.Wait()
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
return
}
- if len(buf.Bytes()) == 0 && stream.Status == false {
+ if len(buf.Bytes()) == 0 && !stream.Status {
showInfo(bufferType + ":Processing data")
}
@@ -1543,7 +1538,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
cmd.Process.Kill()
err = errors.New("Timout")
ShowError(err, 4006)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
cmd.Wait()
f.Close()
return
@@ -1553,11 +1548,11 @@ func thirdPartyBuffer(streamID int, playlistID string) {
}
- if fileSize == 0 && stream.Status == false {
+ if fileSize == 0 && !stream.Status {
showInfo("Streaming Status:Receive data from " + bufferType)
}
- if clientConnection(stream) == false {
+ if !clientConnection(stream) {
cmd.Process.Kill()
f.Close()
cmd.Wait()
@@ -1574,7 +1569,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
if _, err := f.Write(buffer[:n]); err != nil {
cmd.Process.Kill()
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
cmd.Wait()
f.Close()
return
@@ -1582,7 +1577,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
if fileSize >= bufferSize/2 {
- if tmpSegment == 1 && stream.Status == false {
+ if tmpSegment == 1 && !stream.Status {
close(t)
close(streamStatus)
showInfo(fmt.Sprintf("Streaming Status:Buffering data from %s", bufferType))
@@ -1591,7 +1586,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
f.Close()
tmpSegment++
- if stream.Status == false {
+ if !stream.Status {
Lock.Lock()
stream.Status = true
playlist.Streams[streamID] = stream
@@ -1603,13 +1598,11 @@ func thirdPartyBuffer(streamID int, playlistID string) {
fileSize = 0
- var errCreate, errOpen error
- f, errCreate = os.Create(tmpFile)
- f, errOpen = os.OpenFile(tmpFile, os.O_APPEND|os.O_WRONLY, 0600)
- if errCreate != nil || errOpen != nil {
+ f, err = os.OpenFile(tmpFile, os.O_CREATE|os.O_TRUNC|os.O_APPEND|os.O_WRONLY, 0666)
+ if err != nil {
cmd.Process.Kill()
ShowError(err, 0)
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
cmd.Wait()
f.Close()
return
@@ -1623,7 +1616,7 @@ func thirdPartyBuffer(streamID int, playlistID string) {
cmd.Wait()
err = errors.New(bufferType + " error")
- addErrorToStream(err)
+ addErrorToStream(playlist, streamID, playlistID, err)
ShowError(err, 1204)
time.Sleep(time.Duration(500) * time.Millisecond)
@@ -1694,7 +1687,6 @@ func debugRequest(req *http.Request) {
debug = "Request:* * * * * * END HTTP(S) REQUEST * * * * * *"
showDebug(debug, debugLevel)
- return
}
func debugResponse(resp *http.Response) {
@@ -1739,5 +1731,4 @@ func debugResponse(resp *http.Response) {
debug = "Pesponse:* * * * * * END RESPONSE * * * * * * "
showDebug(debug, debugLevel)
- return
}
diff --git a/src/config.go b/src/config.go
index bfea24b..6c819fe 100644
--- a/src/config.go
+++ b/src/config.go
@@ -152,8 +152,9 @@ func Init() (err error) {
// Berechtigung aller Ordner überprüfen
err = checkFilePermission(System.Folder.Config)
- if err == nil {
- err = checkFilePermission(System.Folder.Temp)
+ if err != nil {
+ ShowError(err, 0)
+ return
}
// Separaten tmp Ordner für jede Instanz
@@ -165,6 +166,12 @@ func Init() (err error) {
return
}
+ err = checkFilePermission(System.Folder.Temp)
+ if err != nil {
+ ShowError(err, 0)
+ return
+ }
+
err = removeChildItems(getPlatformPath(System.Folder.Temp))
if err != nil {
return
@@ -173,7 +180,7 @@ func Init() (err error) {
// Branch festlegen
System.Branch = Settings.Branch
- if System.Dev == true {
+ if System.Dev {
System.Branch = "Development"
}
@@ -217,7 +224,7 @@ func StartSystem(updateProviderFiles bool) (err error) {
showInfo(fmt.Sprintf("Unfiltered Chan. Limit:%d", System.UnfilteredChannelLimit))
// Providerdaten aktualisieren
- if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
+ if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate || updateProviderFiles {
err = xTeVeAutoBackup()
if err != nil {
diff --git a/src/data.go b/src/data.go
index 71448aa..a5871d1 100644
--- a/src/data.go
+++ b/src/data.go
@@ -73,10 +73,6 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
}
- if len(newUpdateTimes) == 0 {
- //newUpdateTimes = append(newUpdateTimes, "0000")
- }
-
value = newUpdateTimes
case "cache.images":
@@ -179,7 +175,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
return
}
- if Settings.AuthenticationWEB == false {
+ if !Settings.AuthenticationWEB {
Settings.AuthenticationAPI = false
Settings.AuthenticationM3U = false
@@ -221,7 +217,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
settings = Settings
- if reloadData == true {
+ if reloadData {
err = buildDatabaseDVR()
if err != nil {
@@ -232,7 +228,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
}
- if cacheImages == true {
+ if cacheImages {
if Settings.EpgSource == "XEPG" && System.ImageCachingInProgress == 0 {
@@ -271,7 +267,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
}
- if createXEPGFiles == true {
+ if createXEPGFiles {
go func() {
createXMLTVFile()
@@ -280,7 +276,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStruct, err e
}
- if triggerPlexGuideReload == true {
+ if triggerPlexGuideReload {
queuePlexGuideRefresh("settings change")
}
@@ -378,7 +374,7 @@ func saveFiles(request RequestStruct, fileType string) (err error) {
return
}
- if reloadData == true {
+ if reloadData {
err = buildDatabaseDVR()
if err != nil {
@@ -452,7 +448,6 @@ func deleteLocalProviderFiles(dataID, fileType string) {
os.RemoveAll(System.Folder.Data + dataID + fileExtension)
}
- return
}
// Filtereinstellungen speichern (WebUI)
@@ -505,7 +500,7 @@ func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
if len(filter) == 0 {
err = errors.New(getErrMsg(1014))
- if newFilter == true {
+ if newFilter {
delete(filterMap, dataID)
}
@@ -573,7 +568,7 @@ func saveXEpgMapping(request RequestStruct) (err error) {
// Wenn während des erstellen der Datanbank das Mapping erneut gespeichert wird, wird die Datenbank erst später erneut aktualisiert.
go func() {
- if System.BackgroundProcess == true {
+ if System.BackgroundProcess {
return
}
@@ -591,7 +586,7 @@ func saveXEpgMapping(request RequestStruct) (err error) {
cleanupXEPG()
System.ScanInProgress = 0
buildXEPG(false)
- showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
+ showInfo("XEPG:" + "Ready to use")
System.BackgroundProcess = false
@@ -632,7 +627,7 @@ func saveUserData(request RequestStruct) (err error) {
return
}
- if request.DeleteUser == true {
+ if request.DeleteUser {
err = authentication.RemoveUser(userID)
return
}
@@ -859,7 +854,7 @@ func buildDatabaseDVR() (err error) {
for n, i := range playlistFile {
var channels []any
- var groupTitle, tvgID, uuid int = 0, 0, 0
+ var groupTitle, tvgID, uuid = 0, 0, 0
var keys = []string{"group-title", "tvg-id", "uuid"}
var compatibility = make(map[string]int)
@@ -905,11 +900,7 @@ func buildDatabaseDVR() (err error) {
if value, ok := s[key]; ok {
if len(value) > 0 {
- if _, ok := tmpGroupsM3U[value]; ok {
- tmpGroupsM3U[value]++
- } else {
- tmpGroupsM3U[value] = 1
- }
+ tmpGroupsM3U[value]++
groupTitle++
}
@@ -985,7 +976,7 @@ func buildDatabaseDVR() (err error) {
for group, count := range tmpGroupsM3U {
var text = fmt.Sprintf("%s (%d)", group, count)
- var value = fmt.Sprintf("%s", group)
+ var value = 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)
}
diff --git a/src/hdhr.go b/src/hdhr.go
index d98eca0..2cce878 100644
--- a/src/hdhr.go
+++ b/src/hdhr.go
@@ -154,7 +154,7 @@ func getLineup() (jsonContent []byte, err error) {
return
}
- if xepgChannel.XActive == true {
+ if xepgChannel.XActive {
var stream LineupStream
stream.GuideName = xepgChannel.XName
stream.GuideNumber = xepgChannel.XChannelID
diff --git a/src/images.go b/src/images.go
index 04dd256..2f33445 100644
--- a/src/images.go
+++ b/src/images.go
@@ -1,30 +1,30 @@
package src
import (
- b64 "encoding/base64"
- "fmt"
- "strings"
+ b64 "encoding/base64"
+ "fmt"
+ "strings"
)
func uploadLogo(input, filename string) (logoURL string, err error) {
- b64data := input[strings.IndexByte(input, ',')+1:]
+ b64data := input[strings.IndexByte(input, ',')+1:]
- // BAse64 in bytes umwandeln un speichern
- sDec, err := b64.StdEncoding.DecodeString(b64data)
- if err != nil {
- return
- }
+ // 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)
+ var file = fmt.Sprintf("%s%s", System.Folder.ImagesUpload, filename)
- err = writeByteToFile(file, sDec)
- if err != nil {
- return
- }
+ err = writeByteToFile(file, sDec)
+ if err != nil {
+ return
+ }
- logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.XML, System.Domain, filename)
+ logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.XML, System.Domain, filename)
- return
+ return
}
diff --git a/src/info.go b/src/info.go
index 6d09ee6..db913dc 100644
--- a/src/info.go
+++ b/src/info.go
@@ -20,45 +20,41 @@ func ShowSystemInfo() {
fmt.Println("OK")
println()
- fmt.Println(fmt.Sprintf("Version: %s %s.%s", System.Name, System.Version, System.Build))
- fmt.Println(fmt.Sprintf("Branch: %s", System.Branch))
- fmt.Println(fmt.Sprintf("Folder (config): %s", System.Folder.Config))
+ fmt.Printf("Version: %s %s.%s\n", System.Name, System.Version, System.Build)
+ fmt.Printf("Branch: %s\n", System.Branch)
+ fmt.Printf("Folder (config): %s\n", System.Folder.Config)
- fmt.Println(fmt.Sprintf("Streams: %d / %d", len(Data.Streams.Active), len(Data.Streams.All)))
- fmt.Println(fmt.Sprintf("Filter: %d", len(Data.Filter)))
- fmt.Println(fmt.Sprintf("XEPG Chanels: %d", int(Data.XEPG.XEPGCount)))
+ fmt.Printf("Streams: %d / %d\n", len(Data.Streams.Active), len(Data.Streams.All))
+ fmt.Printf("Filter: %d\n", len(Data.Filter))
+ fmt.Printf("XEPG Chanels: %d\n", int(Data.XEPG.XEPGCount))
println()
- fmt.Println(fmt.Sprintf("IPv4 Addresses:"))
+ fmt.Println("IPv4 Addresses:")
for i, ipv4 := range System.IPAddressesV4 {
switch count := i; {
case count < 10:
- fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
- break
+ fmt.Printf(" %d. %s\n", count, ipv4)
case count < 100:
- fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
- break
+ fmt.Printf(" %d. %s\n", count, ipv4)
}
}
println()
- fmt.Println(fmt.Sprintf("IPv6 Addresses:"))
+ fmt.Println("IPv6 Addresses:")
for i, ipv4 := range System.IPAddressesV6 {
switch count := i; {
case count < 10:
- fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
- break
+ fmt.Printf(" %d. %s\n", count, ipv4)
case count < 100:
- fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
- break
+ fmt.Printf(" %d. %s\n", count, ipv4)
}
@@ -67,33 +63,33 @@ func ShowSystemInfo() {
println("---")
fmt.Println("Settings [General]")
- fmt.Println(fmt.Sprintf("UUID: %s", Settings.UUID))
- fmt.Println(fmt.Sprintf("Tuner (Plex / Emby): %d", Settings.Tuner))
- fmt.Println(fmt.Sprintf("EPG Source: %s", Settings.EpgSource))
+ fmt.Printf("UUID: %s\n", Settings.UUID)
+ fmt.Printf("Tuner (Plex / Emby): %d\n", Settings.Tuner)
+ fmt.Printf("EPG Source: %s\n", Settings.EpgSource)
println("---")
fmt.Println("Settings [Files]")
- fmt.Println(fmt.Sprintf("Schedule: %s", strings.Join(Settings.Update, ",")))
- fmt.Println(fmt.Sprintf("Files Update: %t", Settings.FilesUpdate))
- fmt.Println(fmt.Sprintf("Folder (tmp): %s", Settings.TempPath))
- fmt.Println(fmt.Sprintf("Image Chaching: %t", Settings.CacheImages))
- fmt.Println(fmt.Sprintf("Missing EPG Mode: %s", Settings.XepgMissingEPGMode))
- fmt.Println(fmt.Sprintf("Replace EPG Image: %t", Settings.XepgReplaceMissingImages))
+ fmt.Printf("Schedule: %s\n", strings.Join(Settings.Update, ","))
+ fmt.Printf("Files Update: %t\n", Settings.FilesUpdate)
+ fmt.Printf("Folder (tmp): %s\n", Settings.TempPath)
+ fmt.Printf("Image Chaching: %t\n", Settings.CacheImages)
+ fmt.Printf("Missing EPG Mode: %s\n", Settings.XepgMissingEPGMode)
+ fmt.Printf("Replace EPG Image: %t\n", Settings.XepgReplaceMissingImages)
println("---")
fmt.Println("Settings [Streaming]")
- fmt.Println(fmt.Sprintf("Buffer: %s", Settings.Buffer))
- fmt.Println(fmt.Sprintf("UDPxy: %s", Settings.UDPxy))
- fmt.Println(fmt.Sprintf("Buffer Size: %d KB", Settings.BufferSize))
- fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout)))
- fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent))
+ fmt.Printf("Buffer: %s\n", Settings.Buffer)
+ fmt.Printf("UDPxy: %s\n", Settings.UDPxy)
+ fmt.Printf("Buffer Size: %d KB\n", Settings.BufferSize)
+ fmt.Printf("Timeout: %d ms\n", int(Settings.BufferTimeout))
+ fmt.Printf("User Agent: %s\n", Settings.UserAgent)
println("---")
fmt.Println("Settings [Backup]")
- fmt.Println(fmt.Sprintf("Folder (backup): %s", Settings.BackupPath))
- fmt.Println(fmt.Sprintf("Backup Keep: %d", Settings.BackupKeep))
+ fmt.Printf("Folder (backup): %s\n", Settings.BackupPath)
+ fmt.Printf("Backup Keep: %d\n", Settings.BackupKeep)
}
diff --git a/src/internal/authentication/authentication.go b/src/internal/authentication/authentication.go
index a908394..0fc0870 100755
--- a/src/internal/authentication/authentication.go
+++ b/src/internal/authentication/authentication.go
@@ -3,7 +3,6 @@ package authentication
import (
"encoding/json"
"errors"
- "io/ioutil"
"net/http"
"os"
"path/filepath"
@@ -44,90 +43,6 @@ type Cookie struct {
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
@@ -457,7 +372,7 @@ func CheckTheValidityOfTheTokenFromHTTPHeader(w http.ResponseWriter, r *http.Req
// Framework tools
func checkInit() (err error) {
- if initAuthentication == false {
+ if !initAuthentication {
err = createError(000)
}
@@ -472,7 +387,7 @@ func saveDatabase(tmpMap any) (err error) {
return
}
- err = ioutil.WriteFile(database, []byte(jsonString), 0600)
+ err = os.WriteFile(database, []byte(jsonString), 0600)
if err != nil {
return
}
@@ -481,7 +396,7 @@ func saveDatabase(tmpMap any) (err error) {
}
func loadDatabase() (err error) {
- jsonString, err := ioutil.ReadFile(database)
+ jsonString, err := os.ReadFile(database)
if err != nil {
return
}
@@ -587,14 +502,6 @@ loopToken:
return
}
-func mapToJSON(tmpMap any) 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))
diff --git a/src/internal/imgcache/cache.go b/src/internal/imgcache/cache.go
index 0337dcc..94a5451 100644
--- a/src/internal/imgcache/cache.go
+++ b/src/internal/imgcache/cache.go
@@ -1,178 +1,177 @@
package imgcache
import (
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "path/filepath"
- "strings"
- "sync"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
)
// Cache : Cache strcut
type Cache struct {
- path string
- cacheURL string
- caching bool
- images map[string]string
- Queue []string
- Cache []string
- Image imageFunc
- sync.RWMutex
+ path string
+ cacheURL string
+ caching bool
+ images map[string]string
+ Queue []string
+ Cache []string
+ Image imageFunc
+ sync.RWMutex
}
type imageFunc struct {
- GetURL func(string) string
- Caching func()
- Remove func()
+ GetURL func(string) string
+ Caching func()
+ Remove func()
}
// New : New cahce
func New(path, chacheURL string, caching bool) (c *Cache, err error) {
- c = &Cache{}
+ c = &Cache{}
- c.images = make(map[string]string)
- c.path = path
- c.cacheURL = chacheURL
- c.caching = caching
- c.Queue = []string{}
- c.Cache = []string{}
+ c.images = make(map[string]string)
+ c.path = path
+ c.cacheURL = chacheURL
+ c.caching = caching
+ c.Queue = []string{}
+ c.Cache = []string{}
- var queue []string
+ var queue []string
- c.Image.GetURL = func(src string) (cacheURL string) {
+ c.Image.GetURL = func(src string) (cacheURL string) {
- c.Lock()
- defer c.Unlock()
+ c.Lock()
+ defer c.Unlock()
- src = strings.Trim(src, "\r\n")
+ src = strings.Trim(src, "\r\n")
- if c.caching == false {
- return src
- }
+ if !c.caching {
+ return src
+ }
- u, err := url.Parse(src)
- if err != nil || len(filepath.Ext(u.Path)) == 0 {
- return src
- }
+ u, err := url.Parse(src)
+ if err != nil || len(filepath.Ext(u.Path)) == 0 {
+ return src
+ }
- var filename = fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))
- if cacheURL, ok := c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))]; ok {
- return cacheURL
- }
+ var filename = fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))
+ if cacheURL, ok := c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))]; ok {
+ return cacheURL
+ }
- if indexOfString(filename, c.Cache) == -1 {
+ if indexOfString(filename, c.Cache) == -1 {
- if indexOfString(src, c.Queue) == -1 {
- c.Queue = append(c.Queue, src)
- }
+ if indexOfString(src, c.Queue) == -1 {
+ c.Queue = append(c.Queue, src)
+ }
- } else {
- c.images[filename] = c.cacheURL + filename
- src = c.cacheURL + filename
- }
+ } else {
+ c.images[filename] = c.cacheURL + filename
+ src = c.cacheURL + filename
+ }
- /*
- if _, err := os.Stat(c.path + filename); err != nil {
- //c.images[filename] = c.cacheURL + filename
- if indexOfString(src, c.Queue) == -1 {
- c.Queue = append(c.Queue, src)
- }
- } else {
- c.images[filename] = c.cacheURL + filename
- }
- */
+ /*
+ if _, err := os.Stat(c.path + filename); err != nil {
+ //c.images[filename] = c.cacheURL + filename
+ if indexOfString(src, c.Queue) == -1 {
+ c.Queue = append(c.Queue, src)
+ }
+ } else {
+ c.images[filename] = c.cacheURL + filename
+ }
+ */
- return src
- }
+ return src
+ }
- c.Image.Caching = func() {
+ c.Image.Caching = func() {
- c.Lock()
- defer c.Unlock()
+ c.Lock()
+ defer c.Unlock()
- var filename string
+ var filename string
- for _, src := range c.Queue {
+ for _, src := range c.Queue {
- resp, err := http.Get(src)
- if err != nil {
- continue
- }
- defer resp.Body.Close()
+ resp, err := http.Get(src)
+ if err != nil {
+ continue
+ }
+ defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- continue
- }
+ if resp.StatusCode != http.StatusOK {
+ continue
+ }
- filename = fmt.Sprintf("%s%s%s%s", c.path, string(os.PathSeparator), strToMD5(src), filepath.Ext(src))
+ filename = fmt.Sprintf("%s%s%s%s", c.path, string(os.PathSeparator), strToMD5(src), filepath.Ext(src))
- file, err := os.Create(filename)
- if err != nil {
- continue
- }
+ file, err := os.Create(filename)
+ if err != nil {
+ continue
+ }
- defer file.Close()
+ defer file.Close()
- _, err = io.Copy(file, resp.Body)
- if err != nil {
- continue
- }
+ _, err = io.Copy(file, resp.Body)
+ if err != nil {
+ continue
+ }
- u, err := url.Parse(src)
- if err == nil {
- c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))] = c.cacheURL + filename
- }
+ u, err := url.Parse(src)
+ if err == nil {
+ c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))] = c.cacheURL + filename
+ }
- queue = append(queue, src)
+ queue = append(queue, src)
- }
+ }
- for _, q := range queue {
- c.Queue = removeStringFromSlice(q, c.Queue)
- }
+ for _, q := range queue {
+ c.Queue = removeStringFromSlice(q, c.Queue)
+ }
- }
+ }
- c.Image.Remove = func() {
+ c.Image.Remove = func() {
- c.Lock()
- defer c.Unlock()
+ c.Lock()
+ defer c.Unlock()
- files, err := ioutil.ReadDir(c.path)
- if err != nil {
- return
- }
+ files, err := os.ReadDir(c.path)
+ if err != nil {
+ return
+ }
- for _, file := range files {
+ for _, file := range files {
- switch c.caching {
+ switch c.caching {
- case true:
- if _, ok := c.images[file.Name()]; !ok {
- os.RemoveAll(c.path + file.Name())
- }
+ case true:
+ if _, ok := c.images[file.Name()]; !ok {
+ os.RemoveAll(c.path + file.Name())
+ }
- case false:
- os.RemoveAll(c.path + file.Name())
- }
+ case false:
+ os.RemoveAll(c.path + file.Name())
+ }
- }
+ }
- }
+ }
- files, err := ioutil.ReadDir(c.path)
- if err != nil {
- return
- }
+ files, err := os.ReadDir(c.path)
+ if err != nil {
+ return
+ }
- for _, file := range files {
- c.Cache = append(c.Cache, file.Name())
- }
+ for _, file := range files {
+ c.Cache = append(c.Cache, file.Name())
+ }
- return
+ return
}
diff --git a/src/internal/imgcache/tools.go b/src/internal/imgcache/tools.go
index 9d4b622..f30e277 100644
--- a/src/internal/imgcache/tools.go
+++ b/src/internal/imgcache/tools.go
@@ -1,34 +1,34 @@
package imgcache
import (
- "crypto/md5"
- "encoding/hex"
+ "crypto/md5"
+ "encoding/hex"
)
func strToMD5(str string) string {
- md5Hasher := md5.New()
- md5Hasher.Write([]byte(str))
- return hex.EncodeToString(md5Hasher.Sum(nil))
+ md5Hasher := md5.New()
+ md5Hasher.Write([]byte(str))
+ return hex.EncodeToString(md5Hasher.Sum(nil))
}
func indexOfString(str string, slice []string) int {
- for i, v := range slice {
- if str == v {
- return i
- }
- }
+ for i, v := range slice {
+ if str == v {
+ return i
+ }
+ }
- return -1
+ return -1
}
func removeStringFromSlice(str string, slice []string) []string {
- var i = indexOfString(str, slice)
+ var i = indexOfString(str, slice)
- if i != -1 {
- slice = append(slice[:i], slice[i+1:]...)
- }
+ if i != -1 {
+ slice = append(slice[:i], slice[i+1:]...)
+ }
- return slice
+ return slice
}
diff --git a/src/internal/m3u-parser/m3u-parser_test.go b/src/internal/m3u-parser/m3u-parser_test.go
index c59cba7..38c352d 100644
--- a/src/internal/m3u-parser/m3u-parser_test.go
+++ b/src/internal/m3u-parser/m3u-parser_test.go
@@ -3,18 +3,18 @@ package m3u
import (
"encoding/json"
"fmt"
- "io/ioutil"
"log"
+ "os"
"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"`
+ GroupTitle string `json:"group-title"`
+ Name string `json:"name"`
+ TvgID string `json:"tvg-id"`
+ TvgLogo string `json:"tvg-logo"`
+ TvgName string `json:"tvg-name"`
+ URL string `json:"url"`
UUIDKey string `json:"_uuid.key,omitempty"`
UUIDValue string `json:"_uuid.value,omitempty"`
}
@@ -22,7 +22,7 @@ type M3UStream struct {
func TestStream1(t *testing.T) {
var file = "test_list_1.m3u"
- var content, err = ioutil.ReadFile(file)
+ var content, err = os.ReadFile(file)
if err != nil {
t.Error(err)
return
@@ -58,26 +58,26 @@ func checkStream(streamInterface []any) (err error) {
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))
+ log.Printf("Stream: %d", i)
+ log.Printf("Name*: %s", m3uStream.Name)
+ log.Printf("URL*: %s", m3uStream.URL)
+ log.Printf("tvg-name: %s", m3uStream.TvgName)
+ log.Printf("tvg-id**: %s", m3uStream.TvgID)
+ log.Printf("tvg-logo: %s", m3uStream.TvgLogo)
+ log.Printf("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))
+ log.Printf("UUID key***: %s", m3uStream.UUIDKey)
+ log.Printf("UUID value: %s", m3uStream.UUIDValue)
} else {
- log.Print(fmt.Sprintf("UUID key: false"))
+ log.Print("UUID key: false")
}
}
}
- log.Println(fmt.Sprintf("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -"))
+ log.Println("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -")
}
return
diff --git a/src/internal/m3u-parser/xteve_m3uParser.go b/src/internal/m3u-parser/xteve_m3uParser.go
index d8dc05f..c7aea4d 100755
--- a/src/internal/m3u-parser/xteve_m3uParser.go
+++ b/src/internal/m3u-parser/xteve_m3uParser.go
@@ -2,7 +2,6 @@ package m3u
import (
"errors"
- "fmt"
"log"
"net/url"
"regexp"
@@ -124,7 +123,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []any, err error) {
if strings.Contains(strings.ToLower(key), "id") {
if indexOfString(value, uuids) != -1 {
- log.Println(fmt.Sprintf("Channel: %s - %s = %s ", stream["name"], key, value))
+ log.Printf("Channel: %s - %s = %s ", stream["name"], key, value)
break
}
diff --git a/src/m3u.go b/src/m3u.go
index 3ae48ab..8d5d81c 100644
--- a/src/m3u.go
+++ b/src/m3u.go
@@ -114,18 +114,18 @@ func filterThisStream(s any) (status bool) {
}
}
- if match == true {
+ if match {
if len(exclude) > 0 {
var status = checkConditions(search, exclude, "exclude")
- if status == false {
+ if !status {
return false
}
}
if len(include) > 0 {
var status = checkConditions(search, include, "include")
- if status == false {
+ if !status {
return false
}
}
@@ -191,7 +191,7 @@ func buildM3U(groups []string) (m3u string, err error) {
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
if err == nil {
- if xepgChannel.XActive == true {
+ if xepgChannel.XActive {
if len(groups) > 0 {
diff --git a/src/maintenance.go b/src/maintenance.go
index b915205..5c86314 100644
--- a/src/maintenance.go
+++ b/src/maintenance.go
@@ -1,15 +1,12 @@
package src
import (
- "math/rand"
"time"
)
// InitMaintenance : Wartungsprozess initialisieren
func InitMaintenance() (err error) {
- rand.Seed(time.Now().Unix())
-
go maintenance()
return
@@ -50,7 +47,7 @@ func maintenance() {
ShowError(err, 000)
}
- if Settings.CacheImages == false && System.ImageCachingInProgress == 0 {
+ if !Settings.CacheImages && System.ImageCachingInProgress == 0 {
removeChildItems(System.Folder.ImagesCache)
}
@@ -68,8 +65,3 @@ func maintenance() {
}
}
-
-func randomTime(min, max int) int {
- rand.Seed(time.Now().Unix())
- return rand.Intn(max-min) + min
-}
diff --git a/src/migrate.go b/src/migrate.go
index a1e7c47..4124e4d 100644
--- a/src/migrate.go
+++ b/src/migrate.go
@@ -131,6 +131,9 @@ func convertToNewFilter(oldFilter []any) (newFilterMap map[int]any) {
func setValueForUUID() (err error) {
xepg, err := loadJSONFileToMap(System.File.XEPG)
+ if err != nil {
+ return
+ }
for _, c := range xepg {
diff --git a/src/plex_api.go b/src/plex_api.go
index 7b80901..e1169f7 100644
--- a/src/plex_api.go
+++ b/src/plex_api.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"net/url"
"strconv"
@@ -26,7 +26,7 @@ var plexRefreshState = struct {
// queuePlexGuideRefresh schedules a debounced Plex DVR guide refresh.
func queuePlexGuideRefresh(reason string) {
- if Settings.UsePlexAPI == false {
+ if !Settings.UsePlexAPI {
return
}
@@ -36,7 +36,7 @@ func queuePlexGuideRefresh(reason string) {
plexRefreshState.reason = reason
}
- if plexRefreshState.scheduled == true || plexRefreshState.inProgress == true {
+ if plexRefreshState.scheduled || plexRefreshState.inProgress {
plexRefreshState.queued = true
plexRefreshState.Unlock()
return
@@ -78,13 +78,13 @@ func runPlexGuideRefresh(initialDelay time.Duration) {
plexRefreshState.inProgress = false
nextDelay := time.Duration(0)
- if runAgain == true && Settings.UsePlexAPI == true {
+ if runAgain && Settings.UsePlexAPI {
plexRefreshState.scheduled = true
nextDelay = plexRefreshDelayLocked()
}
plexRefreshState.Unlock()
- if runAgain == false || Settings.UsePlexAPI == false {
+ if !runAgain || !Settings.UsePlexAPI {
return
}
@@ -98,7 +98,7 @@ func plexRefreshDelayLocked() (delay time.Duration) {
const minDelay = 2 * time.Second
const cooldown = 20 * time.Second
- if plexRefreshState.lastRun.IsZero() == true {
+ if plexRefreshState.lastRun.IsZero() {
return minDelay
}
@@ -113,7 +113,7 @@ func plexRefreshDelayLocked() (delay time.Duration) {
func refreshPlexGuide(reason string) (err error) {
baseURL, token, ready := getPlexConfig()
- if ready == false {
+ if !ready {
return nil
}
@@ -178,7 +178,7 @@ func getPlexConfig() (baseURL, token string, ready bool) {
baseURL = strings.TrimSpace(Settings.PlexURL)
token = strings.TrimSpace(Settings.PlexToken)
- if strings.HasPrefix(baseURL, "http://") == false && strings.HasPrefix(baseURL, "https://") == false && len(baseURL) > 0 {
+ if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") && len(baseURL) > 0 {
baseURL = "http://" + baseURL
}
@@ -189,7 +189,7 @@ func getPlexConfig() (baseURL, token string, ready bool) {
if len(baseURL) == 0 || len(token) == 0 {
- if plexRefreshState.missingConfigLogged == false {
+ if !plexRefreshState.missingConfigLogged {
showInfo("Plex API:Skipped refresh because plex.url or plex.token is empty")
plexRefreshState.missingConfigLogged = true
}
@@ -221,14 +221,14 @@ func discoverPlexDVRs(baseURL, token string) (dvrs []map[string]any, err error)
}
mediaContainer, ok := payload["MediaContainer"].(map[string]any)
- if ok == false {
+ if !ok {
err = errors.New("Plex API response missing MediaContainer")
return
}
for _, key := range []string{"Dvr", "DVR", "Directory", "Metadata"} {
- if raw, found := mediaContainer[key]; found == true {
- if list, ok := raw.([]any); ok == true {
+ if raw, found := mediaContainer[key]; found {
+ if list, ok := raw.([]any); ok {
dvrs = convertToPlexMapSlice(list)
if len(dvrs) > 0 {
return
@@ -246,7 +246,7 @@ func convertToPlexMapSlice(list []any) (dvrs []map[string]any) {
dvrs = make([]map[string]any, 0, len(list))
for _, item := range list {
- if dvr, ok := item.(map[string]any); ok == true {
+ if dvr, ok := item.(map[string]any); ok {
dvrs = append(dvrs, dvr)
}
}
@@ -264,10 +264,10 @@ func getPlexReloadEndpoints(dvr map[string]any) (endpoints []string) {
if len(endpoint) == 0 {
return
}
- if strings.HasPrefix(endpoint, "/") == false {
+ if !strings.HasPrefix(endpoint, "/") {
endpoint = "/" + endpoint
}
- if added[endpoint] == true {
+ if added[endpoint] {
return
}
added[endpoint] = true
@@ -275,7 +275,7 @@ func getPlexReloadEndpoints(dvr map[string]any) (endpoints []string) {
}
if key := getPlexMapString(dvr, "key"); len(key) > 0 {
- if strings.HasPrefix(key, "/") == true {
+ if strings.HasPrefix(key, "/") {
add(strings.TrimRight(key, "/") + "/reloadGuide")
} else {
add("/livetv/dvrs/" + url.PathEscape(key) + "/reloadGuide")
@@ -294,7 +294,7 @@ func getPlexReloadEndpoints(dvr map[string]any) (endpoints []string) {
func getPlexMapString(data map[string]any, field string) string {
for key, value := range data {
- if strings.EqualFold(key, field) == false {
+ if !strings.EqualFold(key, field) {
continue
}
@@ -354,7 +354,7 @@ func doPlexRequest(method, baseURL, endpoint, token string) (status int, body []
defer resp.Body.Close()
status = resp.StatusCode
- body, err = ioutil.ReadAll(resp.Body)
+ body, err = io.ReadAll(resp.Body)
if err != nil {
return
}
diff --git a/src/provider.go b/src/provider.go
index d985cf1..d7cf508 100644
--- a/src/provider.go
+++ b/src/provider.go
@@ -2,7 +2,7 @@ package src
import (
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"net/url"
"strings"
@@ -152,7 +152,7 @@ func getProviderData(fileType, fileID string) (err error) {
}
// 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 len(fileID) > 0 && !newProvider {
if dataID != fileID {
goto Done
}
@@ -204,7 +204,7 @@ func getProviderData(fileType, fileID string) (err error) {
ShowError(err, 000)
var downloadErr = err
- if newProvider == false {
+ if !newProvider {
// Prüfen ob ältere Datei vorhanden ist
var file = System.Folder.Data + dataID + fileExtension
@@ -236,7 +236,7 @@ func getProviderData(fileType, fileID string) (err error) {
}
// Berechnen der Fehlerquote
- if newProvider == false {
+ if !newProvider {
if value, ok := dataMap[dataID].(map[string]any); ok {
@@ -322,7 +322,7 @@ func downloadFileFromServer(providerURL string) (filename string, body []byte, e
}
- body, err = ioutil.ReadAll(resp.Body)
+ body, err = io.ReadAll(resp.Body)
if err != nil {
return
}
diff --git a/src/screen.go b/src/screen.go
index 64bf5ce..55c6ec7 100644
--- a/src/screen.go
+++ b/src/screen.go
@@ -12,7 +12,7 @@ import (
func showInfo(str string) {
- if System.Flag.Info == true {
+ if System.Flag.Info {
return
}
@@ -39,7 +39,6 @@ func showInfo(str string) {
}
- return
}
func showDebug(str string, level int) {
@@ -73,7 +72,6 @@ func showDebug(str string, level int) {
}
- return
}
func showHighlight(str string) {
@@ -105,7 +103,6 @@ func showHighlight(str string) {
addNotification(notification)
- return
}
func showWarning(errCode int) {
@@ -121,7 +118,6 @@ func showWarning(errCode int) {
WebScreenLog.Warnings++
mutex.Unlock()
- return
}
// ShowError : Zeigt die Fehlermeldungen in der Konsole
@@ -139,7 +135,6 @@ func ShowError(err error, errCode int) {
WebScreenLog.Errors++
mutex.Unlock()
- return
}
func printLogOnScreen(logMsg string, logType string) {
@@ -211,7 +206,6 @@ func logCleanUp() {
WebScreenLog.Log = logs
- return
}
// Fehlercodes
@@ -224,79 +218,79 @@ func getErrMsg(errCode int) (errMsg string) {
// Errors
case 1001:
- errMsg = fmt.Sprintf("Web server could not be started.")
+ errMsg = "Web server could not be started."
case 1002:
- errMsg = fmt.Sprintf("No local IP address found.")
+ errMsg = "No local IP address found."
case 1003:
- errMsg = fmt.Sprintf("Invalid xml")
+ errMsg = "Invalid xml"
case 1004:
- errMsg = fmt.Sprintf("File not found")
+ errMsg = "File not found"
case 1005:
- errMsg = fmt.Sprintf("Invalid M3U file, an extended M3U file is required.")
+ errMsg = "Invalid M3U file, an extended M3U file is required."
case 1006:
- errMsg = fmt.Sprintf("No playlist!")
+ errMsg = "No playlist!"
case 1007:
- errMsg = fmt.Sprintf("XEPG requires an XMLTV file.")
+ errMsg = "XEPG requires an XMLTV file."
case 1010:
- errMsg = fmt.Sprintf("Invalid file compression")
+ errMsg = "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")
+ errMsg = "Invalid formatting of the time"
case 1013:
errMsg = fmt.Sprintf("Invalid settings file (settings.json), file must be at least version %s", System.Compatibility)
case 1014:
- errMsg = fmt.Sprintf("Invalid filter rule")
+ errMsg = "Invalid filter rule"
case 1020:
- errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
+ errMsg = "Data could not be saved, invalid keyword"
// Datenbank Update
case 1030:
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
case 1031:
- errMsg = fmt.Sprintf("Database error. The database version of your settings is not compatible with this version.")
+ errMsg = "Database error. The database version of your settings is not compatible with this version."
// M3U Parser
case 1050:
- errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
+ errMsg = "Invalid duration specification in the M3U8 playlist."
case 1060:
- errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
+ errMsg = "Invalid characters found in the tvg parameters, streams with invalid parameters were skipped."
// Dateisystem
case 1070:
- errMsg = fmt.Sprintf("Folder could not be created.")
+ errMsg = "Folder could not be created."
case 1071:
- errMsg = fmt.Sprintf("File could not be created")
+ errMsg = "File could not be created"
case 1072:
- errMsg = fmt.Sprintf("File not found")
+ errMsg = "File not found"
// Backup
case 1090:
- errMsg = fmt.Sprintf("Automatic backup failed")
+ errMsg = "Automatic backup failed"
// Websockets
case 1100:
- errMsg = fmt.Sprintf("WebUI build error")
+ errMsg = "WebUI build error"
case 1101:
- errMsg = fmt.Sprintf("WebUI request error")
+ errMsg = "WebUI request error"
case 1102:
- errMsg = fmt.Sprintf("WebUI response error")
+ errMsg = "WebUI response error"
// PMS Guide Numbers
case 1200:
- errMsg = fmt.Sprintf("Could not create file")
+ errMsg = "Could not create file"
// Stream URL Fehler
case 1201:
- errMsg = fmt.Sprintf("Plex stream error")
+ errMsg = "Plex stream error"
case 1202:
- errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
+ errMsg = "Steaming URL could not be found in any playlist"
case 1203:
- errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
+ errMsg = "Steaming URL could not be found in any playlist"
case 1204:
- errMsg = fmt.Sprintf("Streaming was stopped by third party transcoder (FFmpeg / VLC)")
+ errMsg = "Streaming was stopped by third party transcoder (FFmpeg / VLC)"
// Warnings
case 2000:
@@ -304,86 +298,86 @@ func getErrMsg(errCode int) (errMsg string) {
case 2001:
errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.UnfilteredChannelLimit)
case 2002:
- errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
+ errMsg = "PMS can not play m3u8 streams"
case 2003:
- errMsg = fmt.Sprintf("PMS can not play streams over RTSP.")
+ errMsg = "PMS can not play streams over RTSP."
case 2004:
- errMsg = fmt.Sprintf("Buffer is disabled for this stream.")
+ errMsg = "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.")
+ errMsg = "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")
+ errMsg = "No valid streaming URL"
case 2020:
- errMsg = fmt.Sprintf("FFmpeg binary was not found. Check the FFmpeg binary path in the xTeVe settings.")
+ errMsg = "FFmpeg binary was not found. Check the FFmpeg binary path in the xTeVe settings."
case 2021:
- errMsg = fmt.Sprintf("VLC binary was not found. Check the VLC path binary in the xTeVe settings.")
+ errMsg = "VLC binary was not found. Check the VLC path binary in the xTeVe settings."
case 2099:
- errMsg = fmt.Sprintf("Updates have been disabled by the developer")
+ errMsg = "Updates have been disabled by the developer"
// Tuner
case 2105:
errMsg = "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")
+ errMsg = "This function is only available with XEPG as EPG source"
case 2110:
- errMsg = fmt.Sprintf("Don't run this as Root!")
+ errMsg = "Don't run this as Root!"
case 2300:
- errMsg = fmt.Sprintf("No channel logo found in the XMLTV or M3U file.")
+ errMsg = "No channel logo found in the XMLTV or M3U file."
case 2301:
- errMsg = fmt.Sprintf("XMLTV file no longer available, channel has been deactivated.")
+ errMsg = "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.")
+ errMsg = "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.")
+ errMsg = "Database for user authentication could not be initialized."
case 3001:
- errMsg = fmt.Sprintf("The user has no authorization to load the channels.")
+ errMsg = "The user has no authorization to load the channels."
// Buffer
case 4000:
- errMsg = fmt.Sprintf("Connection to streaming source was interrupted.")
+ errMsg = "Connection to streaming source was interrupted."
case 4001:
- errMsg = fmt.Sprintf("Too many errors connecting to the provider. Streaming is canceled.")
+ errMsg = "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")
+ errMsg = "New URL for the redirect to the streaming server is missing"
case 4003:
- errMsg = fmt.Sprintf("Server sends an incompatible content-type")
+ errMsg = "Server sends an incompatible content-type"
case 4004:
- errMsg = fmt.Sprintf("This error message comes from the provider")
+ errMsg = "This error message comes from the provider"
case 4005:
- errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
+ errMsg = "Temporary buffer files could not be deleted"
case 4006:
- errMsg = fmt.Sprintf("Server connection timeout")
+ errMsg = "Server connection timeout"
// Buffer (M3U8)
case 4050:
- errMsg = fmt.Sprintf("Invalid M3U8 file")
+ errMsg = "Invalid M3U8 file"
case 4051:
- errMsg = fmt.Sprintf("#EXTM3U header is missing")
+ errMsg = "#EXTM3U header is missing"
// Caching
case 4100:
- errMsg = fmt.Sprintf("Unknown content type for downloaded image")
+ errMsg = "Unknown content type for downloaded image"
case 4101:
- errMsg = fmt.Sprintf("Invalid URL, original URL is used for this image")
+ errMsg = "Invalid URL, original URL is used for this image"
// API
case 5000:
- errMsg = fmt.Sprintf("Invalid API command")
+ errMsg = "Invalid API command"
// Update Server
case 6001:
- errMsg = fmt.Sprintf("Ivalid key")
+ errMsg = "Ivalid key"
case 6002:
- errMsg = fmt.Sprintf("Update failed")
+ errMsg = "Update failed"
case 6003:
- errMsg = fmt.Sprintf("Update server not available")
+ errMsg = "Update server not available"
case 6004:
- errMsg = fmt.Sprintf("xTeVe update available")
+ errMsg = "xTeVe update available"
default:
errMsg = fmt.Sprintf("Unknown error / warning (%d)", errCode)
diff --git a/src/ssdp.go b/src/ssdp.go
index 63ce77c..85e38f2 100644
--- a/src/ssdp.go
+++ b/src/ssdp.go
@@ -1,72 +1,72 @@
package src
import (
- "fmt"
- "log"
- "os"
- "os/signal"
- "time"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "time"
- "github.com/koron/go-ssdp"
+ "github.com/koron/go-ssdp"
)
// SSDP : SSPD / DLNA Server
func SSDP() (err error) {
- if Settings.SSDP == false || System.Flag.Info == true {
- return
- }
+ if !Settings.SSDP || System.Flag.Info {
+ return
+ }
- showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
+ showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
- quit := make(chan os.Signal, 1)
- signal.Notify(quit, os.Interrupt)
+ quit := make(chan os.Signal, 1)
+ signal.Notify(quit, os.Interrupt)
- ad, err := ssdp.Advertise(
- fmt.Sprintf("upnp:rootdevice"), // send as "ST"
- fmt.Sprintf("uuid:%s::upnp:rootdevice", System.DeviceID), // send as "USN"
- fmt.Sprintf("%s/device.xml", System.URLBase), // send as "LOCATION"
- System.AppName, // send as "SERVER"
- 1800) // send as "maxAge" in "CACHE-CONTROL"
+ ad, err := ssdp.Advertise(
+ "upnp:rootdevice", // send as "ST"
+ fmt.Sprintf("uuid:%s::upnp:rootdevice", System.DeviceID), // send as "USN"
+ fmt.Sprintf("%s/device.xml", System.URLBase), // send as "LOCATION"
+ System.AppName, // send as "SERVER"
+ 1800) // send as "maxAge" in "CACHE-CONTROL"
- if err != nil {
- return
- }
+ if err != nil {
+ return
+ }
- // Debug SSDP
- if System.Flag.Debug == 3 {
- ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
- }
+ // Debug SSDP
+ if System.Flag.Debug == 3 {
+ ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
+ }
- go func(adv *ssdp.Advertiser) {
+ go func(adv *ssdp.Advertiser) {
- aliveTick := time.Tick(300 * time.Second)
+ aliveTick := time.Tick(300 * time.Second)
- loop:
- for {
+ loop:
+ for {
- select {
+ select {
- case <-aliveTick:
- err = adv.Alive()
- if err != nil {
- ShowError(err, 0)
- adv.Bye()
- adv.Close()
- break loop
- }
+ case <-aliveTick:
+ err = adv.Alive()
+ if err != nil {
+ ShowError(err, 0)
+ adv.Bye()
+ adv.Close()
+ break loop
+ }
- case <-quit:
- adv.Bye()
- adv.Close()
- os.Exit(0)
- break loop
+ case <-quit:
+ adv.Bye()
+ adv.Close()
+ os.Exit(0)
+ break loop
- }
+ }
- }
+ }
- }(ad)
+ }(ad)
- return
+ return
}
diff --git a/src/struct-buffer.go b/src/struct-buffer.go
index b56e2d5..68332d6 100644
--- a/src/struct-buffer.go
+++ b/src/struct-buffer.go
@@ -108,147 +108,3 @@ type BandwidthCalculation struct {
Stop time.Time
TimeDiff float64
}
-
-/*
-var args = "-hide_banner -loglevel panic -re -i " + url + " -codec copy -f mpegts pipe:1"
- //var args = "-re -i " + url + " -codec copy -f mpegts pipe:1"
- cmd := exec.Command("/usr/local/bin/ffmpeg", strings.Split(args, " ")...)
-
- //run := exec.Command("/usr/local/bin/ffmpeg", "-hide_banner", "-loglevel", "panic", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
- //run := exec.Command("/usr/local/bin/ffmpeg", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
-
- stderr, _ := cmd.StderrPipe()
- cmd.Start()
-
- scanner := bufio.NewScanner(stderr)
- scanner.Split(bufio.ScanLines)
- for scanner.Scan() {
- m := scanner.Text()
- fmt.Println(m)
- }
- cmd.Wait()
-
- os.Exit(0)
-*/
-
-/*
-
-ffmpegOut, _ := run.StderrPipe()
- //run.Start()
-
- scanner = bufio.NewScanner(ffmpegOut)
- scanner.Split(bufio.ScanLines)
- for scanner.Scan() {
- m := scanner.Text()
- fmt.Println(m)
- }
-
- ffmpegOut, err = run.StdoutPipe()
- if err != nil {
- ShowError(err, 0)
- return
- }
-
- stderr, stderrErr := run.StderrPipe()
- if stderrErr != nil {
- fmt.Println(stderrErr)
- }
-
- _ = stderr
-
- if startErr := run.Start(); startErr != nil {
- fmt.Println(startErr)
-
- return
- }
-
- n, err := ffmpegOut.Read(buffer)
- _ = n
- _ = stream
- _ = fileSize
-
- if err != nil && err != io.EOF {
-
- ShowError(err, 0)
- addErrorToStream(err)
- return
-
- }
-
- defer bufferFile.Close()
-
- scanner = bufio.NewScanner(ffmpegOut)
-
- for scanner.Scan() {
- //fmt.Printf("%s\n", scanner.Text())
- //fmt.Println(scanner)
- thisLine := scanner.Text()
- line := make([]byte, len(thisLine))
-
- buffer = append(buffer, line...)
-
- fmt.Println(len(buffer))
-
- if len(buffer) > tmpFileSize {
-
- if _, err := bufferFile.Write(buffer[:]); err != nil {
-
- ShowError(err, 0)
- addErrorToStream(err)
- run.Process.Kill()
- return
-
- }
-
- buffer = make([]byte, 1024*Settings.BufferSize*2)
-
- debug = fmt.Sprintf("Buffer Status:Done (%s)", tmpFile)
- showDebug(debug, 2)
-
- bufferFile.Close()
-
- stream.Status = true
- playlist.Streams[streamID] = stream
- BufferInformation.Store(playlistID, playlist)
-
- tmpSegment++
-
- tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
-
- if clientConnection(stream) == false {
-
- bufferFile.Close()
- run.Process.Kill()
-
- err = os.RemoveAll(stream.Folder)
- if err != nil {
- ShowError(err, 4005)
- }
-
- return
- }
-
- bufferFile, err = os.Create(tmpFile)
- if err != nil {
- addErrorToStream(err)
- run.Process.Kill()
- return
- }
-
- fileSize = 0
-
- if n == 0 {
- bufferFile.Close()
- run.Process.Kill()
- break
- }
-
- os.Exit(0)
-
- }
-
-
-
- }
-
-*/
diff --git a/src/struct-system.go b/src/struct-system.go
index b384ced..61f1f1f 100644
--- a/src/struct-system.go
+++ b/src/struct-system.go
@@ -155,81 +155,81 @@ type Filter struct {
// 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"`
+ FileM3UID string `json:"_file.m3u.id"`
+ FileM3UName string `json:"_file.m3u.name"`
+ FileM3UPath string `json:"_file.m3u.path"`
+ GroupTitle string `json:"group-title"`
+ Name string `json:"name"`
+ TvgID string `json:"tvg-id"`
+ TvgLogo string `json:"tvg-logo"`
+ TvgName string `json:"tvg-name"`
+ URL string `json:"url"`
+ UUIDKey string `json:"_uuid.key"`
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"`
- XDescription string `json:"x-description,required"`
+ Values string `json:"_values"`
+ XActive bool `json:"x-active"`
+ XCategory string `json:"x-category"`
+ XChannelID string `json:"x-channelID"`
+ XEPG string `json:"x-epg"`
+ XGroupTitle string `json:"x-group-title"`
+ XMapping string `json:"x-mapping"`
+ XmltvFile string `json:"x-xmltv-file"`
+ XName string `json:"x-name"`
+ XUpdateChannelIcon bool `json:"x-update-channel-icon"`
+ XUpdateChannelName bool `json:"x-update-channel-name"`
+ XDescription string `json:"x-description"`
}
// 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"`
+ FileM3UID string `json:"_file.m3u.id"`
+ FileM3UName string `json:"_file.m3u.name"`
+ FileM3UPath string `json:"_file.m3u.path"`
+ GroupTitle string `json:"group-title"`
+ Name string `json:"name"`
+ TvgID string `json:"tvg-id"`
+ TvgLogo string `json:"tvg-logo"`
+ TvgName string `json:"tvg-name"`
+ URL string `json:"url"`
+ UUIDKey string `json:"_uuid.key"`
+ UUIDValue string `json:"_uuid.value"`
+ Values string `json:"_values"`
}
// 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"`
+ Active bool `json:"active"`
+ CaseSensitive bool `json:"caseSensitive"`
+ Description string `json:"description"`
+ Exclude string `json:"exclude"`
+ Filter string `json:"filter"`
+ Include string `json:"include"`
+ Name string `json:"name"`
Rule string `json:"rule,omitempty"`
- Type string `json:"type,required"`
+ Type string `json:"type"`
}
// StreamingURLS : Informationen zu allen streaming URL's
type StreamingURLS struct {
- Streams map[string]StreamInfo `json:"channels,required"`
+ Streams map[string]StreamInfo `json:"channels"`
}
// 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"`
+ ChannelNumber string `json:"channelNumber"`
+ Name string `json:"name"`
+ PlaylistID string `json:"playlistID"`
+ URL string `json:"url"`
+ URLid string `json:"urlID"`
}
// 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"`
+ Headline string `json:"headline"`
+ Message string `json:"message"`
+ New bool `json:"new"`
+ Time string `json:"time"`
+ Type string `json:"type"`
}
// SettingsStruct : Inhalt der settings.json
@@ -283,4 +283,3 @@ type SettingsStruct struct {
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
WizardCompleted bool `json:"wizard.completed"`
}
-
diff --git a/src/struct-webserver.go b/src/struct-webserver.go
index e0bde99..e6f7cb2 100644
--- a/src/struct-webserver.go
+++ b/src/struct-webserver.go
@@ -3,7 +3,7 @@ package src
// RequestStruct : Anfragen über die Websocket Schnittstelle
type RequestStruct struct {
// Befehle an xTeVe
- Cmd string `json:"cmd,required"`
+ Cmd string `json:"cmd"`
// Benutzer
DeleteUser bool `json:"deleteUser,omitempty"`
@@ -79,46 +79,46 @@ type ResponseStruct struct {
DVR string `json:"DVR"`
EpgSource string `json:"epgSource"`
Errors int `json:"errors"`
- M3U string `json:"m3u-url,required"`
+ M3U string `json:"m3u-url"`
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"`
+ XML string `json:"xepg-url"`
} `json:"clientInfo"`
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"`
+ Text []string `json:"text"`
+ Value []string `json:"value"`
+ } `json:"groups"`
+ } `json:"m3u"`
+ } `json:"playlist"`
StreamPreviewUI struct {
- Active []string `json:"activeStreams,required"`
- Inactive []string `json:"inactiveStreams,required"`
+ Active []string `json:"activeStreams"`
+ Inactive []string `json:"inactiveStreams"`
}
- } `json:"data,required"`
+ } `json:"data"`
Alert string `json:"alert,omitempty"`
- ConfigurationWizard bool `json:"configurationWizard,required"`
+ ConfigurationWizard bool `json:"configurationWizard"`
Error string `json:"err,omitempty"`
- Log WebScreenLogStruct `json:"log,required"`
+ Log WebScreenLogStruct `json:"log"`
LogoURL string `json:"logoURL,omitempty"`
OpenLink string `json:"openLink,omitempty"`
OpenMenu string `json:"openMenu,omitempty"`
Reload bool `json:"reload,omitempty"`
- Settings SettingsStruct `json:"settings,required"`
- Status bool `json:"status,required"`
+ Settings SettingsStruct `json:"settings"`
+ Status bool `json:"status"`
Token string `json:"token,omitempty"`
Users map[string]any `json:"users,omitempty"`
Wizard int `json:"wizard,omitempty"`
- XEPG map[string]any `json:"xepg,required"`
+ XEPG map[string]any `json:"xepg"`
Notification map[string]Notification `json:"notification,omitempty"`
}
@@ -135,7 +135,7 @@ type APIRequestStruct struct {
type APIResponseStruct struct {
EpgSource string `json:"epg.source,omitempty"`
Error string `json:"err,omitempty"`
- Status bool `json:"status,required"`
+ Status bool `json:"status"`
StreamsActive int64 `json:"streams.active,omitempty"`
StreamsAll int64 `json:"streams.all,omitempty"`
StreamsXepg int64 `json:"streams.xepg,omitempty"`
@@ -149,7 +149,7 @@ type APIResponseStruct struct {
// 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"`
+ Errors int `json:"errors"`
+ Log []string `json:"log"`
+ Warnings int `json:"warnings"`
}
diff --git a/src/struct-xml.go b/src/struct-xml.go
index c79f385..b10b3a0 100644
--- a/src/struct-xml.go
+++ b/src/struct-xml.go
@@ -66,7 +66,7 @@ type SubTitle struct {
Value string `xml:",chardata"`
}
-//Desc : Programmbeschreibung
+// Desc : Programmbeschreibung
type Desc struct {
Lang string `xml:"lang,attr"`
Value string `xml:",chardata"`
diff --git a/src/system.go b/src/system.go
index 5d17a77..6db7c7b 100644
--- a/src/system.go
+++ b/src/system.go
@@ -13,7 +13,7 @@ import (
// Entwicklerinfos anzeigen
func showDevInfo() {
- if System.Dev == true {
+ if System.Dev {
fmt.Print("\033[31m")
fmt.Println("* * * * * D E V M O D E * * * * *")
@@ -25,7 +25,6 @@ func showDevInfo() {
}
- return
}
// Alle Systemordner erstellen
@@ -261,7 +260,7 @@ func saveSettings(settings SettingsStruct) (err error) {
Settings = settings
- if System.Dev == true {
+ if System.Dev {
Settings.UUID = "2019-01-DEV-xTeVe!"
}
@@ -301,7 +300,6 @@ func setGlobalDomain(domain string) {
System.Addresses.XML = getErrMsg(2106)
}
- return
}
// UUID generieren
@@ -323,7 +321,6 @@ func setDeviceID() {
System.DeviceID = fmt.Sprintf("%s:%d", id, Settings.Tuner)
}
- return
}
// Provider Streaming-URL zu xTeVe Streaming-URL konvertieren
diff --git a/src/toolchain.go b/src/toolchain.go
index de34794..baffa25 100644
--- a/src/toolchain.go
+++ b/src/toolchain.go
@@ -7,7 +7,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
- "io/ioutil"
+ "io"
"net"
"os"
"os/exec"
@@ -59,11 +59,8 @@ func checkFile(filename string) (err error) {
return err
}
- switch mode := fi.Mode(); {
- case mode.IsDir():
+ if fi.Mode().IsDir() {
err = fmt.Errorf("%s: %s", file, getErrMsg(1072))
- case mode.IsRegular():
- break
}
return
@@ -97,7 +94,7 @@ func checkFilePermission(dir string) (err error) {
var filename = dir + "permission.test"
- err = ioutil.WriteFile(filename, []byte(""), 0644)
+ err = os.WriteFile(filename, []byte(""), 0644)
if err == nil {
err = os.RemoveAll(filename)
}
@@ -125,12 +122,6 @@ 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)
-}
-
// Sucht eine Datei im OS
func searchFileInOS(file string) (path string) {
@@ -197,14 +188,6 @@ func jsonToMap(content string) map[string]any {
return (tmpMap)
}
-func jsonToMapInt64(content string) map[int64]any {
-
- var tmpMap = make(map[int64]any)
- json.Unmarshal([]byte(content), &tmpMap)
-
- return (tmpMap)
-}
-
func jsonToInterface(content string) (tmpMap any, err error) {
err = json.Unmarshal([]byte(content), &tmpMap)
@@ -221,7 +204,7 @@ func saveMapToJSONFile(file string, tmpMap any) error {
return err
}
- err = ioutil.WriteFile(filename, []byte(jsonString), 0644)
+ err = os.WriteFile(filename, []byte(jsonString), 0644)
if err != nil {
return err
}
@@ -232,16 +215,17 @@ func saveMapToJSONFile(file string, tmpMap any) error {
func loadJSONFileToMap(file string) (tmpMap map[string]any, err error) {
f, err := os.Open(getPlatformFile(file))
+ if err != nil {
+ return
+ }
defer f.Close()
- content, err := ioutil.ReadAll(f)
+ content, err := io.ReadAll(f)
if err == nil {
err = json.Unmarshal([]byte(content), &tmpMap)
}
- f.Close()
-
return
}
@@ -249,10 +233,12 @@ func loadJSONFileToMap(file string) (tmpMap map[string]any, err error) {
func readByteFromFile(file string) (content []byte, err error) {
f, err := os.Open(getPlatformFile(file))
+ if err != nil {
+ return
+ }
defer f.Close()
- content, err = ioutil.ReadAll(f)
- f.Close()
+ content, err = io.ReadAll(f)
return
}
@@ -260,7 +246,7 @@ func readByteFromFile(file string) (content []byte, err error) {
func writeByteToFile(file string, data []byte) (err error) {
var filename = getPlatformFile(file)
- err = ioutil.WriteFile(filename, data, 0644)
+ err = os.WriteFile(filename, data, 0644)
return
}
@@ -275,7 +261,7 @@ func readStringFromFile(file string) (str string, err error) {
return
}
- content, err = ioutil.ReadFile(filename)
+ content, err = os.ReadFile(filename)
if err != nil {
ShowError(err, 0)
return
@@ -395,17 +381,6 @@ func indexOfFloat64(element float64, data []float64) int {
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()
diff --git a/src/webserver.go b/src/webserver.go
index f23c874..15a467b 100644
--- a/src/webserver.go
+++ b/src/webserver.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"net/http"
"os"
"strconv"
@@ -40,8 +40,6 @@ func StartWebserver() (err error) {
http.HandleFunc("/data_images/", DataImages)
http.HandleFunc("/healthz", Healthz)
- //http.HandleFunc("/auto/", Auto)
-
showInfo("DVR IP:" + System.IPAddress + ":" + Settings.Port)
var ips = len(System.IPAddressesV4) + len(System.IPAddressesV6) - 1
@@ -90,7 +88,7 @@ func Index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
case "/lineup.json":
- if Settings.AuthenticationPMS == true {
+ if Settings.AuthenticationPMS {
_, err := basicAuth(r, "authentication.pms")
if err != nil {
@@ -123,7 +121,6 @@ func Index(w http.ResponseWriter, r *http.Request) {
httpStatusError(w, r, 500)
- return
}
// Stream : Web Server /stream/
@@ -151,12 +148,12 @@ func Stream(w http.ResponseWriter, r *http.Request) {
showInfo(fmt.Sprintf("Buffer:false [%s]", Settings.Buffer))
case "xteve":
- if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
+ if strings.Contains(streamInfo.URL, "rtsp://") || strings.Contains(streamInfo.URL, "rtp://") {
err = errors.New("RTSP and RTP streams are not supported")
ShowError(err, 2004)
showInfo("Streaming URL:" + streamInfo.URL)
- http.Redirect(w, r, streamInfo.URL, 302)
+ http.Redirect(w, r, streamInfo.URL, http.StatusFound)
showInfo("Streaming Info:URL was passed to the client")
return
@@ -181,7 +178,7 @@ func Stream(w http.ResponseWriter, r *http.Request) {
case "-":
showInfo("Streaming URL:" + streamInfo.URL)
- http.Redirect(w, r, streamInfo.URL, 302)
+ http.Redirect(w, r, streamInfo.URL, http.StatusFound)
showInfo("Streaming Info:URL was passed to the client.")
showInfo("Streaming Info:xTeVe is no longer involved, the client connects directly to the streaming server.")
@@ -191,32 +188,6 @@ func Stream(w http.ResponseWriter, r *http.Request) {
}
- return
-}
-
-// Auto : HDHR routing (wird derzeit nicht benutzt)
-func Auto(w http.ResponseWriter, r *http.Request) {
-
- var channelID = strings.Replace(r.RequestURI, "/auto/v", "", 1)
- fmt.Println(channelID)
-
- /*
- switch Settings.Buffer {
-
- case true:
- var playlistID, streamURL, err = getStreamByChannelID(channelID)
- if err == nil {
- bufferingStream(playlistID, streamURL, w, r)
- } else {
- httpStatusError(w, r, 404)
- }
-
- case false:
- httpStatusError(w, r, 423)
- }
- */
-
- return
}
// xTeVe : Web Server /xmltv/ und /m3u/
@@ -250,7 +221,7 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
requestType = "m3u"
groupTitle = r.URL.Query().Get("group-title")
- if System.Dev == false {
+ if !System.Dev {
// false: Dateiname wird im Header gesetzt
// true: M3U wird direkt im Browser angezeigt
w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(path))
@@ -286,7 +257,6 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(content))
}
- return
}
// Images : Image Cache /images/
@@ -306,7 +276,6 @@ func Images(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write(content)
- return
}
// DataImages : Image Pfad für Logos / Bilder die hochgeladen wurden /data_images/
@@ -326,7 +295,6 @@ func DataImages(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write(content)
- return
}
// WS : Web Sockets /ws/
@@ -362,7 +330,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
return
}
- if System.ConfigurationWizard == false {
+ if !System.ConfigurationWizard {
switch Settings.AuthenticationWEB {
@@ -425,7 +393,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
response.OpenMenu = strconv.Itoa(indexOfString("settings", System.WEB.Menu))
- if Settings.AuthenticationWEB == true && authenticationUpdate == false {
+ if Settings.AuthenticationWEB && !authenticationUpdate {
response.Reload = true
}
@@ -568,7 +536,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
var requestMap = make(map[string]any) // Debug
_ = requestMap
- if System.Dev == true {
+ if System.Dev {
fmt.Println(mapToJSON(requestMap))
}
@@ -581,7 +549,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
}
response = setDefaultResponseData(response, true)
- if System.ConfigurationWizard == true {
+ if System.ConfigurationWizard {
response.ConfigurationWizard = System.ConfigurationWizard
}
@@ -593,7 +561,6 @@ func WS(w http.ResponseWriter, r *http.Request) {
}
- return
}
// Healthz : Liveness endpoint for container healthchecks and monitoring.
@@ -629,7 +596,7 @@ func Web(w http.ResponseWriter, r *http.Request) {
if System.ScanInProgress == 0 {
- if Settings.WizardCompleted == false && len(Settings.Files.M3U) == 0 && len(Settings.Files.HDHR) == 0 {
+ if !Settings.WizardCompleted && len(Settings.Files.M3U) == 0 && len(Settings.Files.HDHR) == 0 {
System.ConfigurationWizard = true
}
@@ -675,7 +642,7 @@ func Web(w http.ResponseWriter, r *http.Request) {
}
// Redirect, damit die Daten aus dem Browser gelöscht werden.
w = authentication.SetCookieToken(w, token)
- http.Redirect(w, r, "/web", 301)
+ http.Redirect(w, r, "/web", http.StatusMovedPermanently)
return
}
@@ -691,11 +658,11 @@ func Web(w http.ResponseWriter, r *http.Request) {
}
w = authentication.SetCookieToken(w, token)
- http.Redirect(w, r, "/web", 301) // Redirect, damit die Daten aus dem Browser gelöscht werden.
+ http.Redirect(w, r, "/web", http.StatusMovedPermanently) // Redirect, damit die Daten aus dem Browser gelöscht werden.
} else {
w = authentication.SetCookieToken(w, "-")
- http.Redirect(w, r, "/web", 301) // Redirect, damit die Daten aus dem Browser gelöscht werden.
+ http.Redirect(w, r, "/web", http.StatusMovedPermanently) // Redirect, damit die Daten aus dem Browser gelöscht werden.
}
return
@@ -723,7 +690,7 @@ func Web(w http.ResponseWriter, r *http.Request) {
return
}
- if len(allUserData) == 0 && Settings.AuthenticationWEB == true {
+ if len(allUserData) == 0 && Settings.AuthenticationWEB {
file = "create-first-user.html"
}
@@ -828,13 +795,12 @@ func API(w http.ResponseWriter, r *http.Request) {
response.Status = false
response.Error = err.Error()
w.Write([]byte(mapToJSON(response)))
- return
}
response.Status = true
- if Settings.API == false {
+ if !Settings.API {
httpStatusError(w, r, 423)
return
}
@@ -844,7 +810,7 @@ func API(w http.ResponseWriter, r *http.Request) {
return
}
- b, err := ioutil.ReadAll(r.Body)
+ b, err := io.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
httpStatusError(w, r, 400)
@@ -860,7 +826,7 @@ func API(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json")
- if Settings.AuthenticationAPI == true {
+ if Settings.AuthenticationAPI {
var token string
switch len(request.Token) {
case 0:
@@ -957,7 +923,6 @@ func API(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(mapToJSON(response)))
- return
}
// Download : Datei Download
@@ -975,7 +940,6 @@ func Download(w http.ResponseWriter, r *http.Request) {
os.RemoveAll(System.Folder.Temp + getFilenameFromPath(path))
w.Write([]byte(content))
- return
}
func setDefaultResponseData(response ResponseStruct, data bool) (defaults ResponseStruct) {
@@ -999,7 +963,7 @@ func setDefaultResponseData(response ResponseStruct, data bool) (defaults Respon
switch System.Branch {
case "master":
- defaults.ClientInfo.Version = fmt.Sprintf("%s", System.Version)
+ defaults.ClientInfo.Version = System.Version
default:
defaults.ClientInfo.Version = fmt.Sprintf("%s (%s)", System.Version, System.Build)
@@ -1007,7 +971,7 @@ func setDefaultResponseData(response ResponseStruct, data bool) (defaults Respon
}
- if data == true {
+ if data {
defaults.Users, _ = authentication.GetAllUserData()
//defaults.DVR = System.DVRAddress
@@ -1048,7 +1012,6 @@ func setDefaultResponseData(response ResponseStruct, data bool) (defaults Respon
func httpStatusError(w http.ResponseWriter, r *http.Request, httpStatusCode int) {
http.Error(w, fmt.Sprintf("%s [%d]", http.StatusText(httpStatusCode), httpStatusCode), httpStatusCode)
- return
}
func getContentType(filename string) (contentType string) {
diff --git a/src/xepg.go b/src/xepg.go
index f098b2e..b3eb318 100644
--- a/src/xepg.go
+++ b/src/xepg.go
@@ -5,7 +5,7 @@ import (
"encoding/xml"
"errors"
"fmt"
- "io/ioutil"
+ "os"
"path"
"runtime"
"sort"
@@ -71,28 +71,9 @@ func buildXEPG(background bool) {
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
- showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
+ showInfo("XEPG:" + "Ready to use")
- if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
-
- go func() {
-
- System.ImageCachingInProgress = 1
- showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
-
- Data.Cache.Images.Image.Caching()
- Data.Cache.Images.Image.Remove()
- showInfo("Image Caching:Done")
-
- createXMLTVFile()
- createM3UFile()
- queuePlexGuideRefresh("xepg image cache refresh")
-
- System.ImageCachingInProgress = 0
-
- }()
-
- }
+ cacheImagesInBackground()
System.ScanInProgress = 0
@@ -118,28 +99,9 @@ func buildXEPG(background bool) {
createM3UFile()
queuePlexGuideRefresh("xepg rebuild")
- if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
+ cacheImagesInBackground()
- go func() {
-
- System.ImageCachingInProgress = 1
- showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
-
- Data.Cache.Images.Image.Caching()
- Data.Cache.Images.Image.Remove()
- showInfo("Image Caching:Done")
-
- createXMLTVFile()
- createM3UFile()
- queuePlexGuideRefresh("xepg image cache refresh")
-
- System.ImageCachingInProgress = 0
-
- }()
-
- }
-
- showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
+ showInfo("XEPG:" + "Ready to use")
System.ScanInProgress = 0
@@ -161,52 +123,30 @@ func buildXEPG(background bool) {
}
-// XEPG Daten aktualisieren
-func updateXEPG(background bool) {
+// cacheImagesInBackground : Bilder im Hintergrund cachen, danach XMLTV- und M3U-Datei neu erstellen
+func cacheImagesInBackground() {
- if System.ScanInProgress == 1 {
+ if !Settings.CacheImages || System.ImageCachingInProgress != 0 {
return
}
- System.ScanInProgress = 1
+ go func() {
- if Settings.EpgSource == "XEPG" {
+ System.ImageCachingInProgress = 1
+ showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
- switch background {
+ Data.Cache.Images.Image.Caching()
+ Data.Cache.Images.Image.Remove()
+ showInfo("Image Caching:Done")
- case false:
+ createXMLTVFile()
+ createM3UFile()
+ queuePlexGuideRefresh("xepg image cache refresh")
- createXEPGDatabase()
- mapping()
- cleanupXEPG()
+ System.ImageCachingInProgress = 0
- go func() {
+ }()
- createXMLTVFile()
- createM3UFile()
- queuePlexGuideRefresh("xepg update")
- 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
@@ -219,7 +159,6 @@ func createXEPGMapping() {
var friendlyDisplayName = func(channel Channel) (displayName string) {
var dn = channel.DisplayName
- displayName = dn[0].Value
switch len(dn) {
case 1:
@@ -277,11 +216,10 @@ func createXEPGMapping() {
}
Data.XMLTV.Mapping = tmpMap
- tmpMap = make(map[string]any)
} else {
- if System.ConfigurationWizard == false {
+ if !System.ConfigurationWizard {
showWarning(1007)
}
@@ -304,7 +242,6 @@ func createXEPGMapping() {
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
- return
}
// XEPG Datenbank erstellen / aktualisieren
@@ -339,7 +276,7 @@ func createXEPGDatabase() (err error) {
sort.Float64s(allChannelNumbers)
- var firstFreeNumber float64 = Settings.MappingFirstChannel
+ var firstFreeNumber = Settings.MappingFirstChannel
for {
@@ -469,14 +406,14 @@ func createXEPGDatabase() (err error) {
xepgChannel.Name = m3uChannel.Name
// Kanalname aktualisieren, nur mit Kanal ID's möglich
- if channelHasUUID == true {
- if xepgChannel.XUpdateChannelName == true {
+ if channelHasUUID {
+ if xepgChannel.XUpdateChannelName {
xepgChannel.XName = m3uChannel.Name
}
}
// Kanallogo aktualisieren. Wird bei vorhandenem Logo in der XMLTV Datei wieder überschrieben
- if xepgChannel.XUpdateChannelIcon == true {
+ if xepgChannel.XUpdateChannelIcon {
xepgChannel.TvgLogo = m3uChannel.TvgLogo
}
@@ -602,7 +539,7 @@ func findXEPGReplacementChannel(xmltvChannels map[string]any, xepgChannel XEPGCh
for id, data := range xmltvChannels {
xmltvChannel, castOK := data.(map[string]any)
- if castOK == false {
+ if !castOK {
continue
}
@@ -615,7 +552,7 @@ func findXEPGReplacementChannel(xmltvChannels map[string]any, xepgChannel XEPGCh
}
displayName, hasDisplayName := xmltvChannel["display-name"].(string)
- if hasDisplayName == false {
+ if !hasDisplayName {
continue
}
@@ -660,7 +597,7 @@ func mapping() (err error) {
}
// 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 {
+ if !xepgChannel.XActive {
// Werte kann "-" sein, deswegen len < 1
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XMapping) < 1 {
@@ -704,7 +641,7 @@ func mapping() (err error) {
}
// Überprüfen, ob die zugeordneten XMLTV Dateien und Kanäle noch existieren.
- if xepgChannel.XActive == true {
+ if xepgChannel.XActive {
var mapping = xepgChannel.XMapping
var file = xepgChannel.XmltvFile
@@ -718,7 +655,7 @@ func mapping() (err error) {
// Kanallogo aktualisieren
if logo, ok := channel["icon"].(string); ok {
- if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
+ if xepgChannel.XUpdateChannelIcon && len(logo) > 0 {
xepgChannel.TvgLogo = logo
}
@@ -729,7 +666,7 @@ func mapping() (err error) {
xepgChannel.XMapping = channelID
if logo, ok := replacementChannel["icon"].(string); ok {
- if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
+ if xepgChannel.XUpdateChannelIcon && len(logo) > 0 {
xepgChannel.TvgLogo = logo
}
}
@@ -752,7 +689,7 @@ func mapping() (err error) {
name = xepg
}
- if strictMissingEPGMode == true {
+ if strictMissingEPGMode {
missingEPGCount++
appendXEPGIssueSample(missingEPGSamples, name)
xepgChannel.XActive = false
@@ -772,7 +709,7 @@ func mapping() (err error) {
providerName = file
}
- if strictMissingEPGMode == true {
+ if strictMissingEPGMode {
missingXMLTVCount++
appendXEPGIssueSample(missingXMLTVSamples, providerName)
xepgChannel.XActive = false
@@ -849,7 +786,7 @@ func createXMLTVFile() (err error) {
Data.Cache.ImagesURLS = []string{}
Data.Cache.ImagesCache = []string{}
- files, err := ioutil.ReadDir(System.Folder.ImagesCache)
+ files, err := os.ReadDir(System.Folder.ImagesCache)
if err == nil {
for _, file := range files {
@@ -887,7 +824,7 @@ func createXMLTVFile() (err error) {
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
if err == nil {
- if xepgChannel.XActive == true {
+ if xepgChannel.XActive {
// Kanäle
var channel Channel
@@ -902,9 +839,7 @@ func createXMLTVFile() (err error) {
*tmpProgram, err = getProgramData(xepgChannel)
if err == nil {
- for _, program := range tmpProgram.Program {
- xepgXML.Program = append(xepgXML.Program, program)
- }
+ xepgXML.Program = append(xepgXML.Program, tmpProgram.Program...)
}
@@ -947,7 +882,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
err = getLocalXMLTV(xmltvFile, &xmltv)
if err != nil {
- if relaxedMissingEPGMode == true {
+ if relaxedMissingEPGMode {
fallbackToDummy()
err = nil
}
@@ -1024,7 +959,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
}
- if len(xepgXML.Program) == 0 && relaxedMissingEPGMode == true && xmltvFile != System.Folder.Data+"xTeVe Dummy" {
+ if len(xepgXML.Program) == 0 && relaxedMissingEPGMode && xmltvFile != System.Folder.Data+"xTeVe Dummy" {
fallbackToDummy()
}
@@ -1072,7 +1007,7 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
epg.Desc = append(epg.Desc, &Desc{Value: xepgChannel.XDescription, Lang: "en"})
}
- if Settings.XepgReplaceMissingImages == true {
+ if Settings.XepgReplaceMissingImages {
poster.Src = imgc.Image.GetURL(xepgChannel.TvgLogo)
epg.Poster = append(epg.Poster, poster)
}
@@ -1114,7 +1049,6 @@ func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChanne
}
- return
}
// Programm Poster Cover aus der XMLTV Datei laden
@@ -1127,7 +1061,7 @@ func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelS
program.Poster = append(program.Poster, poster)
}
- if Settings.XepgReplaceMissingImages == true {
+ if Settings.XepgReplaceMissingImages {
if len(xmltvProgram.Poster) == 0 {
var poster Poster
@@ -1161,7 +1095,6 @@ func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChan
}
- return
}
// Videoparameter erstellen (createXMLTVFile)
@@ -1187,7 +1120,6 @@ func getVideo(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelSt
program.Video = video
- return
}
// Lokale Provider XMLTV Datei laden
@@ -1236,7 +1168,6 @@ func createM3UFile() {
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
- return
}
// XEPG Datenbank bereinigen
@@ -1254,7 +1185,7 @@ func cleanupXEPG() {
sourceIDs = append(sourceIDs, source)
}
- showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
+ showInfo("XEPG:" + "Cleanup database")
Data.XEPG.XEPGCount = 0
for id, dxc := range Data.XEPG.Channels {
@@ -1266,7 +1197,7 @@ func cleanupXEPG() {
if indexOfString(xepgChannel.Name+xepgChannel.FileM3UID, Data.Cache.Streams.Active) == -1 {
delete(Data.XEPG.Channels, id)
} else {
- if xepgChannel.XActive == true {
+ if xepgChannel.XActive {
Data.XEPG.XEPGCount++
}
}
@@ -1291,34 +1222,4 @@ func cleanupXEPG() {
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
}
diff --git a/staticcheck.conf b/staticcheck.conf
new file mode 100644
index 0000000..79554e4
--- /dev/null
+++ b/staticcheck.conf
@@ -0,0 +1,4 @@
+# Style checks deliberately kept out:
+# ST1005 - error strings are shown verbatim in the web UI; wording is intentional
+# ST1000/ST1003/ST1016/ST1020/ST1021/ST1022 - staticcheck's own defaults
+checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
diff --git a/tasks/todo.md b/tasks/todo.md
index 2ccc765..e301ee0 100644
--- a/tasks/todo.md
+++ b/tasks/todo.md
@@ -46,13 +46,13 @@ Status: Phase 0 committed on branch `improvements` 2026-09-26.
- [x] `//go:embed` via `html/embed.go`; deleted `webUI.go`, `html-build.go`, `cmd/webui-gen`; ETag + Cache-Control on static assets
- [x] i18n dropped: 254 placeholders inlined, `en.json` deleted, only HTML pages templated (`authenticationErr`)
- [x] `package.json` + `ts/tsconfig.json` (tsc 5.9.3, ES2020, single outFile `html/js/app.js`, committed, CI-verified); 10 dead JS files deleted; 6 tsc errors fixed incl. a real ASI bug in the search shortcut
-- [ ] CI: vet (done), lint, gofmt, `test -race`; bundle freshness check (done)
+- [x] CI: vet, gofmt check, staticcheck v0.8.1 (config in `staticcheck.conf`), bundle freshness check. `go test -race` still to add once tests exist
- [ ] Version-tagged images (amd64 only)
-- [ ] PUID/PGID via su-exec; pin static-ffmpeg; `VOLUME`; fix `LEGACY_CONFIG_DIRS`
+- [x] PUID/PGID via su-exec at runtime; static-ffmpeg pinned to 7.1.1; `VOLUME /xteve/config`; `/xteve` removed from `LEGACY_CONFIG_DIRS`; compose files pull from the registry and document SSDP/host networking
- [x] `/healthz` endpoint; Dockerfile healthcheck uses it
-- [ ] `README-DEV.md` (done) and fork section in `README.md`
-- [ ] Deduplicate and delete dead code; drop `ioutil`, `osext`, `rand.Seed` (after go:embed)
-- [ ] Resolve missing `docs/design-system/` referenced by `agent.md`
+- [x] `README-DEV.md` and fork-specific `README.md` (about, container usage, env vars, security notes)
+- [x] staticcheck 399 -> 0; dead code and duplicates removed; `ioutil`/`rand.Seed` gone. Real bugs fixed on the way: leaked file handle per ffmpeg segment, unchecked `http.NewRequest`, migration writing `null` xepg.json, silent user-write failures, unwritable config/temp dir now fatal at start
+- [ ] Resolve missing `docs/design-system/` referenced by `agent.md` (user decision: agent.md is a personal, git-ignored file)
## Phase 4: Data model and performance
Not planned (lineup is ~170 channels, decided 2026-09-25). See plan for the reference list.