mirror of
https://github.com/DigitalDevices/pvr.octonet.git
synced 2025-03-01 10:53:09 +00:00
Compare commits
35 Commits
2.0.0-Matr
...
20.2.0-Nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23b4f3eecf | ||
|
|
2a4230567c | ||
|
|
567232b5fb | ||
|
|
fb17e25ef6 | ||
|
|
41a9829054 | ||
|
|
046acf1636 | ||
|
|
df13aef650 | ||
|
|
bacefe5194 | ||
|
|
94d6b5fce7 | ||
|
|
07ef28ed18 | ||
|
|
2d5e55b05d | ||
|
|
7fea489b8a | ||
|
|
ae2525dc2b | ||
|
|
52f9a04da6 | ||
|
|
e00fc3b4ea | ||
|
|
012ff40c25 | ||
|
|
7ec048377c | ||
|
|
2325d7ff2d | ||
|
|
0e5c8d37f0 | ||
|
|
ee2648a2c2 | ||
|
|
524eb385b5 | ||
|
|
3b3e8e4a1a | ||
|
|
e76a37eb1d | ||
|
|
43b2467d35 | ||
|
|
18fc3a3339 | ||
|
|
11b8d0b076 | ||
|
|
479502987a | ||
|
|
495dfe137a | ||
|
|
5a228cc805 | ||
|
|
81520e3104 | ||
|
|
1d1cede442 | ||
|
|
1736207a46 | ||
|
|
e7449d9537 | ||
|
|
fc5b149f2c | ||
|
|
76259ba352 |
88
.clang-format
Normal file
88
.clang-format
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
# BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 100
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 2
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^<[a-z0-9_]+>$'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '^<'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '^["<](kodi|p8-platform)\/.*\.h[">]$'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
IncludeIsMainRegex: '$'
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 2
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: None
|
||||||
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60000
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SortIncludes: true
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Cpp11
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: Never
|
||||||
|
...
|
||||||
61
.github/workflows/build.yml
vendored
Normal file
61
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Build and run tests
|
||||||
|
on: [push, pull_request]
|
||||||
|
env:
|
||||||
|
app_id: pvr.octonet
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "Debian package test"
|
||||||
|
os: ubuntu-18.04
|
||||||
|
CC: gcc
|
||||||
|
CXX: g++
|
||||||
|
DEBIAN_BUILD: true
|
||||||
|
- os: ubuntu-18.04
|
||||||
|
CC: gcc
|
||||||
|
CXX: g++
|
||||||
|
- os: ubuntu-18.04
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
- os: macos-10.15
|
||||||
|
steps:
|
||||||
|
- name: Install needed ubuntu depends
|
||||||
|
env:
|
||||||
|
DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
|
||||||
|
run: |
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi
|
||||||
|
- name: Checkout Kodi repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: xbmc/xbmc
|
||||||
|
ref: master
|
||||||
|
path: xbmc
|
||||||
|
- name: Checkout pvr.argustv repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: ${{ env.app_id }}
|
||||||
|
- name: Configure
|
||||||
|
env:
|
||||||
|
CC: ${{ matrix.CC }}
|
||||||
|
CXX: ${{ matrix.CXX }}
|
||||||
|
DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
|
||||||
|
run: |
|
||||||
|
if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir -p build && cd build; fi
|
||||||
|
if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=${{ github.workspace }} -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/xbmc/addons -DPACKAGE_ZIP=1 ${{ github.workspace }}/xbmc/cmake/addons; fi
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/master/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep ${{ github.workspace }}/${app_id}; fi
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
CC: ${{ matrix.CC }}
|
||||||
|
CXX: ${{ matrix.CXX }}
|
||||||
|
DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
|
||||||
|
run: |
|
||||||
|
if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id}/build; fi
|
||||||
|
if [[ $DEBIAN_BUILD != true ]]; then make; fi
|
||||||
|
if [[ $DEBIAN_BUILD == true ]]; then ./debian-addon-package-test.sh ${{ github.workspace }}/${app_id}; fi
|
||||||
149
.github/workflows/changelog-and-release.yml
vendored
Normal file
149
.github/workflows/changelog-and-release.yml
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
name: Changelog and Release
|
||||||
|
# Update the changelog and news(optionally), bump the version, and create a release
|
||||||
|
#
|
||||||
|
# The release is created on the given branch, release and tag name format will be <version>-<branch> and
|
||||||
|
# the body of the release will be created from the changelog.txt or news element in the addon.xml.in
|
||||||
|
#
|
||||||
|
# options:
|
||||||
|
# - version_type: 'minor' / 'micro' # whether to do a minor or micro version bump
|
||||||
|
# - changelog_text: string to add to the changelog and news
|
||||||
|
# - update_news: 'true' / 'false' # whether to update the news in the addon.xml.in
|
||||||
|
# - add_date: 'true' / 'false' # Add date to version number in changelog and news. ie. v1.0.1 (2021-7-17)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version_type:
|
||||||
|
description: 'Create a ''minor'' or ''micro'' release?'
|
||||||
|
required: true
|
||||||
|
default: 'minor'
|
||||||
|
changelog_text:
|
||||||
|
description: 'Input the changes you''d like to add to the changelogs. Your text should be encapsulated in "''s with line feeds represented by literal \n''s. ie. "This is the first change\nThis is the second change"'
|
||||||
|
required: true
|
||||||
|
default: ''
|
||||||
|
update_news:
|
||||||
|
description: 'Update news in addon.xml.in? [true|false]'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
add_date:
|
||||||
|
description: 'Add date to version number in changelog and news. ie. "v1.0.1 (2021-7-17)" [true|false]'
|
||||||
|
required: true
|
||||||
|
default: 'false'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
default:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Changelog and Release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# Checkout the current repository into a directory (repositories name)
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
path: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Checkout the required scripts from kodi-pvr/pvr-scripts into the 'scripts' directory
|
||||||
|
- name: Checkout Scripts
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
repository: kodi-pvr/pvr-scripts
|
||||||
|
path: scripts
|
||||||
|
|
||||||
|
# Install all dependencies required by the following steps
|
||||||
|
# - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libxml2-utils xmlstarlet
|
||||||
|
|
||||||
|
# Setup python version 3.9
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
# Run the python script to increment the version, changelog and news
|
||||||
|
- name: Increment version and update changelogs
|
||||||
|
run: |
|
||||||
|
arguments=
|
||||||
|
if [[ ${{ github.event.inputs.update_news }} == true ]] ;
|
||||||
|
then
|
||||||
|
arguments=$(echo $arguments && echo --update-news)
|
||||||
|
fi
|
||||||
|
if [[ ${{ github.event.inputs.add_date }} == true ]] ;
|
||||||
|
then
|
||||||
|
arguments=$(echo $arguments && echo --add-date)
|
||||||
|
fi
|
||||||
|
python3 ../scripts/changelog_and_release.py ${{ github.event.inputs.version_type }} ${{ github.event.inputs.changelog_text }} $arguments
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Create the variables required by the following steps
|
||||||
|
# - steps.required-variables.outputs.changes: latest entry in the changelog.txt (if exists), or addon.xml.in news element
|
||||||
|
# - steps.required-variables.outputs.version: version element from addon.xml.in
|
||||||
|
# - steps.required-variables.outputs.branch: branch of the triggering ref
|
||||||
|
# - steps.required-variables.outputs.today: today's date in format '%Y-%m-%d'
|
||||||
|
- name: Get required variables
|
||||||
|
id: required-variables
|
||||||
|
run: |
|
||||||
|
changes=$(cat "$(find . -name changelog.txt)" | awk -v RS= 'NR==1')
|
||||||
|
if [ -z "$changes" ] ;
|
||||||
|
then
|
||||||
|
changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1')
|
||||||
|
fi
|
||||||
|
changes="${changes//'%'/'%25'}"
|
||||||
|
changes="${changes//$'\n'/'%0A'}"
|
||||||
|
changes="${changes//$'\r'/'%0D'}"
|
||||||
|
changes="${changes//$'\\n'/'%0A'}"
|
||||||
|
changes="${changes//$'\\r'/'%0D'}"
|
||||||
|
echo ::set-output name=changes::$changes
|
||||||
|
version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)')
|
||||||
|
echo ::set-output name=version::$version
|
||||||
|
branch=$(echo ${GITHUB_REF#refs/heads/})
|
||||||
|
echo ::set-output name=branch::$branch
|
||||||
|
echo ::set-output name=today::$(date +'%Y-%m-%d')
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Create a commit of the incremented version and changelog, news changes
|
||||||
|
# Commit message (add_date=false): changelog and version v{steps.required-variables.outputs.version}
|
||||||
|
# Commit message (add_date=true): changelog and version v{steps.required-variables.outputs.version} ({steps.required-variables.outputs.today})
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
commit_message="changelog and version v${{ steps.required-variables.outputs.version }}"
|
||||||
|
if [[ ${{ github.event.inputs.add_date }} == true ]] ;
|
||||||
|
then
|
||||||
|
commit_message="$commit_message (${{ steps.required-variables.outputs.today }})"
|
||||||
|
fi
|
||||||
|
git commit -m "$commit_message" -a
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Push the commit(s) created above to the triggering branch
|
||||||
|
- name: Push changes
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
branch: ${{ github.ref }}
|
||||||
|
directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Sleep for 60 seconds to allow for any delays in the push
|
||||||
|
- name: Sleep for 60 seconds
|
||||||
|
run: sleep 60s
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Create a release at {steps.required-variables.outputs.branch}
|
||||||
|
# - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 20.0.0-Nexus
|
||||||
|
# - release body: {steps.required-variables.outputs.changes}
|
||||||
|
- name: Create Release
|
||||||
|
id: create-release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.required-variables.outputs.version }}-${{ steps.required-variables.outputs.branch }}
|
||||||
|
release_name: ${{ steps.required-variables.outputs.version }}-${{ steps.required-variables.outputs.branch }}
|
||||||
|
body: ${{ steps.required-variables.outputs.changes }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
commitish: ${{ steps.required-variables.outputs.branch }}
|
||||||
62
.github/workflows/increment-version.yml
vendored
Normal file
62
.github/workflows/increment-version.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Increment version when languages are updated
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ Matrix, Nexus ]
|
||||||
|
paths:
|
||||||
|
- '**resource.language.**strings.po'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
default:
|
||||||
|
if: github.repository == 'DigitalDevices/pvr.octonet'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Increment add-on version when languages are updated
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
path: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
- name: Checkout Scripts
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
repository: xbmc/weblate-supplementary-scripts
|
||||||
|
path: scripts
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
uses: trilom/file-changes-action@v1.2.4
|
||||||
|
|
||||||
|
- name: Increment add-on version
|
||||||
|
run: |
|
||||||
|
python3 ../scripts/binary/increment_version.py $HOME/files.json -c -n
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libxml2-utils xmlstarlet
|
||||||
|
|
||||||
|
- name: Get required variables
|
||||||
|
id: required-variables
|
||||||
|
run: |
|
||||||
|
version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)')
|
||||||
|
echo ::set-output name=version::$version
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
- name: Create PR for incrementing add-on versions
|
||||||
|
uses: peter-evans/create-pull-request@v3.10.0
|
||||||
|
with:
|
||||||
|
commit-message: Add-on version incremented to ${{ steps.required-variables.outputs.version }} from Weblate
|
||||||
|
title: Add-on version incremented to ${{ steps.required-variables.outputs.version }} from Weblate
|
||||||
|
body: Add-on version incremented triggered by ${{ github.sha }}
|
||||||
|
branch: inc-ver
|
||||||
|
delete-branch: true
|
||||||
|
path: ./${{ github.event.repository.name }}
|
||||||
66
.github/workflows/release.yml
vendored
Normal file
66
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Make Release
|
||||||
|
# Create a release on the given branch
|
||||||
|
# Release and tag name format will be <version>-<branch>
|
||||||
|
# The body of the release will be created from the changelog.txt or news element in the addon.xml.in
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
default:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Make Release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# Checkout the current repository into a directory (repositories name)
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
path: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Install all dependencies required by the following steps
|
||||||
|
# - libxml2-utils, xmlstarlet: reading news and version from addon.xml.in
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libxml2-utils xmlstarlet
|
||||||
|
|
||||||
|
# Create the variables required by the following steps
|
||||||
|
# - steps.required-variables.outputs.changes: latest entry in the changelog.txt (if exists), or addon.xml.in news element
|
||||||
|
# - steps.required-variables.outputs.version: version element from addon.xml.in
|
||||||
|
# - steps.required-variables.outputs.branch: branch of the triggering ref
|
||||||
|
- name: Get required variables
|
||||||
|
id: required-variables
|
||||||
|
run: |
|
||||||
|
changes=$(cat "$(find . -name changelog.txt)" | awk -v RS= 'NR==1')
|
||||||
|
if [ -z "$changes" ] ;
|
||||||
|
then
|
||||||
|
changes=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/extension/news)' | awk -v RS= 'NR==1')
|
||||||
|
fi
|
||||||
|
changes="${changes//'%'/'%25'}"
|
||||||
|
changes="${changes//$'\n'/'%0A'}"
|
||||||
|
changes="${changes//$'\r'/'%0D'}"
|
||||||
|
changes="${changes//$'\\n'/'%0A'}"
|
||||||
|
changes="${changes//$'\\r'/'%0D'}"
|
||||||
|
echo ::set-output name=changes::$changes
|
||||||
|
version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)')
|
||||||
|
echo ::set-output name=version::$version
|
||||||
|
branch=$(echo ${GITHUB_REF#refs/heads/})
|
||||||
|
echo ::set-output name=branch::$branch
|
||||||
|
working-directory: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
# Create a release at {steps.required-variables.outputs.branch}
|
||||||
|
# - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 20.0.0-Nexus
|
||||||
|
# - release body: {steps.required-variables.outputs.changes}
|
||||||
|
- name: Create Release
|
||||||
|
id: create-release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.required-variables.outputs.version }}-${{ steps.required-variables.outputs.branch }}
|
||||||
|
release_name: ${{ steps.required-variables.outputs.version }}-${{ steps.required-variables.outputs.branch }}
|
||||||
|
body: ${{ steps.required-variables.outputs.changes }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
commitish: ${{ steps.required-variables.outputs.branch }}
|
||||||
57
.github/workflows/sync-addon-metadata-translations.yml
vendored
Normal file
57
.github/workflows/sync-addon-metadata-translations.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Sync addon metadata translations
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ Matrix, Nexus ]
|
||||||
|
paths:
|
||||||
|
- '**addon.xml.in'
|
||||||
|
- '**resource.language.**strings.po'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
default:
|
||||||
|
if: github.repository == 'DigitalDevices/pvr.octonet'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: [ 3.9 ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: project
|
||||||
|
|
||||||
|
- name: Checkout sync_addon_metadata_translations repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: xbmc/sync_addon_metadata_translations
|
||||||
|
path: sync_addon_metadata_translations
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install sync_addon_metadata_translations/
|
||||||
|
|
||||||
|
- name: Run sync-addon-metadata-translations
|
||||||
|
run: |
|
||||||
|
sync-addon-metadata-translations
|
||||||
|
working-directory: ./project
|
||||||
|
|
||||||
|
- name: Create PR for sync-addon-metadata-translations changes
|
||||||
|
uses: peter-evans/create-pull-request@v3.10.0
|
||||||
|
with:
|
||||||
|
commit-message: Sync of addon metadata translations
|
||||||
|
title: Sync of addon metadata translations
|
||||||
|
body: Sync of addon metadata translations triggered by ${{ github.sha }}
|
||||||
|
branch: amt-sync
|
||||||
|
delete-branch: true
|
||||||
|
path: ./project
|
||||||
43
.travis.yml
43
.travis.yml
@@ -1,11 +1,7 @@
|
|||||||
language: cpp
|
language: cpp
|
||||||
|
|
||||||
#
|
#
|
||||||
# Define the build matrix
|
# Define the builds to get up to date versions of cmake and gcc
|
||||||
#
|
|
||||||
# Travis defaults to building on Ubuntu Precise when building on
|
|
||||||
# Linux. We need Trusty in order to get up to date versions of
|
|
||||||
# cmake and g++.
|
|
||||||
#
|
#
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
@@ -14,26 +10,45 @@ env:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: xenial
|
dist: bionic
|
||||||
sudo: required
|
sudo: required
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: xenial
|
dist: bionic
|
||||||
sudo: required
|
sudo: required
|
||||||
compiler: clang
|
compiler: clang
|
||||||
|
- os: linux
|
||||||
|
dist: bionic
|
||||||
|
sudo: required
|
||||||
|
compiler: gcc
|
||||||
|
env: DEBIAN_BUILD=true
|
||||||
|
- os: linux
|
||||||
|
dist: focal
|
||||||
|
sudo: required
|
||||||
|
compiler: gcc
|
||||||
|
env: DEBIAN_BUILD=true
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode10.2
|
osx_image: xcode10.2
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# The addon source is automatically checked out in $TRAVIS_BUILD_DIR,
|
# The addon source is automatically checked out in $TRAVIS_BUILD_DIR,
|
||||||
# we'll put the Kodi source on the same level
|
# we'll put the Kodi source on the same level
|
||||||
#
|
#
|
||||||
before_script:
|
before_script:
|
||||||
- cd $TRAVIS_BUILD_DIR/..
|
- if [[ $DEBIAN_BUILD != true ]]; then cd $TRAVIS_BUILD_DIR/..; fi
|
||||||
- git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git
|
- if [[ $DEBIAN_BUILD != true ]]; then git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git; fi
|
||||||
- cd ${app_id} && mkdir build && cd build
|
- if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir build && cd build; fi
|
||||||
- mkdir -p definition/${app_id}
|
- if [[ $DEBIAN_BUILD != true ]]; then mkdir -p definition/${app_id}; fi
|
||||||
- echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt
|
- if [[ $DEBIAN_BUILD != true ]]; then echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt; fi
|
||||||
- cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons
|
- if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons; fi
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/master/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep $TRAVIS_BUILD_DIR; fi
|
||||||
|
|
||||||
script: make
|
script:
|
||||||
|
- if [[ $DEBIAN_BUILD != true ]]; then make; fi
|
||||||
|
- if [[ $DEBIAN_BUILD == true ]]; then ./debian-addon-package-test.sh $TRAVIS_BUILD_DIR; fi
|
||||||
|
|||||||
@@ -4,37 +4,34 @@ project(pvr.octonet)
|
|||||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
|
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
|
||||||
|
|
||||||
find_package(Kodi REQUIRED)
|
find_package(Kodi REQUIRED)
|
||||||
find_package(p8-platform REQUIRED)
|
|
||||||
find_package(JsonCpp REQUIRED)
|
find_package(JsonCpp REQUIRED)
|
||||||
|
|
||||||
include_directories(
|
include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Kodi cmake rework to match right include ways
|
||||||
${p8-platform_INCLUDE_DIRS}
|
${JSONCPP_INCLUDE_DIRS})
|
||||||
${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Kodi cmake rework to match right include ways
|
|
||||||
${JSONCPP_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
set(DEPLIBS
|
set(DEPLIBS ${JSONCPP_LIBRARIES})
|
||||||
${p8-platform_LIBRARIES}
|
|
||||||
${JSONCPP_LIBRARIES})
|
|
||||||
|
|
||||||
set(OCTONET_SOURCES
|
set(OCTONET_SOURCES src/addon.cpp
|
||||||
src/OctonetData.cpp
|
src/OctonetData.cpp
|
||||||
src/client.cpp
|
src/Socket.cpp
|
||||||
src/Socket.cpp
|
src/rtsp_client.cpp)
|
||||||
src/rtsp_client.cpp)
|
|
||||||
|
|
||||||
set(OCTONET_HEADERS
|
set(OCTONET_HEADERS src/addon.h
|
||||||
src/client.h
|
src/OctonetData.h
|
||||||
src/OctonetData.h
|
src/Socket.h
|
||||||
src/Socket.h)
|
src/rtsp_client.hpp)
|
||||||
|
|
||||||
|
addon_version(pvr.octonet OCTONET)
|
||||||
|
add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION})
|
||||||
|
|
||||||
build_addon(pvr.octonet OCTONET DEPLIBS)
|
build_addon(pvr.octonet OCTONET DEPLIBS)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL WindowsStore)
|
if(NOT CMAKE_SYSTEM_NAME STREQUAL WindowsStore)
|
||||||
target_link_libraries(pvr.octonet wsock32 ws2_32)
|
target_link_libraries(pvr.octonet wsock32 ws2_32)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(pvr.octonet ws2_32)
|
target_link_libraries(pvr.octonet ws2_32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|||||||
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -1 +1 @@
|
|||||||
buildPlugin(version: "Matrix")
|
buildPlugin(version: "Nexus")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Digital Devices [Octonet](http://www.digital-devices.eu/shop/de/netzwerk-tv/) PV
|
|||||||
|
|
||||||
| Platform | Status |
|
| Platform | Status |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
| Linux + OS X (Travis) | [](https://travis-ci.org/julianscheel/pvr.octonet) |
|
| Linux + OS X (github) | [](https://github.com/kodi-pvr/pvr.octonet/actions/workflows/build.yml) |
|
||||||
| Windows (AppVeyor) | [](https://ci.appveyor.com/project/julianscheel/pvr-octonet) |
|
| Windows (AppVeyor) | [](https://ci.appveyor.com/project/julianscheel/pvr-octonet) |
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|||||||
43
appveyor.yml
43
appveyor.yml
@@ -1,40 +1,33 @@
|
|||||||
version: BuildNr.{build}
|
version: BuildNr.{build}
|
||||||
|
|
||||||
init:
|
image: Visual Studio 2017
|
||||||
- ps: $commit = $env:appveyor_repo_commit.SubString(0,7)
|
|
||||||
- ps: $timestamp = $env:appveyor_repo_commit_timestamp.SubString(0,10)
|
shallow_clone: true
|
||||||
- ps: Update-AppveyorBuild -Version ("{0}-{1}-{2}" -f $env:appveyor_repo_branch, $commit, $timestamp)
|
|
||||||
|
|
||||||
# clone directory
|
|
||||||
clone_folder: c:\projects\pvr.octonet
|
clone_folder: c:\projects\pvr.octonet
|
||||||
|
|
||||||
# fetch repository as zip archive
|
|
||||||
shallow_clone: true # default is "false"
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
ADDON: pvr.octonet
|
app_id: pvr.octonet
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
#- GENERATOR: "Visual Studio 14"
|
- GENERATOR: "Visual Studio 15"
|
||||||
# CONFIG: Debug
|
|
||||||
|
|
||||||
- GENERATOR: "Visual Studio 14"
|
|
||||||
CONFIG: Release
|
CONFIG: Release
|
||||||
|
- GENERATOR: "Visual Studio 15 Win64"
|
||||||
artifacts:
|
CONFIG: Release
|
||||||
- path: build/install/
|
- GENERATOR: "Visual Studio 15 Win64"
|
||||||
name: pvr.octonet
|
CONFIG: Release
|
||||||
type: zip
|
WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0"
|
||||||
|
- GENERATOR: "Visual Studio 15 ARM"
|
||||||
|
CONFIG: Release
|
||||||
|
WINSTORE: -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0.17763.0"
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- cd ..
|
- cd ..
|
||||||
- set ROOT=%cd%
|
|
||||||
- git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git
|
- git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git
|
||||||
- mkdir xbmc\cmake\addons\addons\pvr.octonet
|
- cd %app_id%
|
||||||
- echo pvr.octonet https://github.com/DigitalDevices/pvr.octonet master > xbmc\cmake\addons\addons\pvr.octonet\pvr.octonet.txt
|
|
||||||
- cd %ADDON%
|
|
||||||
- mkdir build
|
- mkdir build
|
||||||
- cd build
|
- cd build
|
||||||
# Must use absolute path for cmake to build depends correctly
|
- mkdir -p definition\%app_id%
|
||||||
- cmake -G "%GENERATOR%" -DADDONS_TO_BUILD=%ADDON% -DCMAKE_BUILD_TYPE=%CONFIG% -DADDON_SRC_PREFIX=%ROOT% -DCMAKE_INSTALL_PREFIX=install -DPACKAGE_ZIP=1 %ROOT%\xbmc\cmake\addons
|
- echo %app_id% %APPVEYOR_BUILD_FOLDER% %APPVEYOR_REPO_COMMIT% > definition\%app_id%\%app_id%.txt
|
||||||
- cmake --build . --config %CONFIG%
|
- cmake -T host=x64 -G "%GENERATOR%" %WINSTORE% -DADDONS_TO_BUILD=%app_id% -DCMAKE_BUILD_TYPE=%CONFIG% -DADDONS_DEFINITION_DIR=%APPVEYOR_BUILD_FOLDER%/build/definition -DADDON_SRC_PREFIX=../.. -DCMAKE_INSTALL_PREFIX=../../xbmc/addons -DPACKAGE_ZIP=1 ../../xbmc/cmake/addons
|
||||||
|
- cmake --build . --config %CONFIG% --target %app_id%
|
||||||
|
|||||||
4
debian/copyright
vendored
4
debian/copyright
vendored
@@ -5,7 +5,7 @@ Source: https://github.com/DigitalDevices/pvr.octonet
|
|||||||
Files: *
|
Files: *
|
||||||
Copyright: 2015-2016 Julian Scheel
|
Copyright: 2015-2016 Julian Scheel
|
||||||
2015-2016 jusst technologies GmbH
|
2015-2016 jusst technologies GmbH
|
||||||
2005-2020 Team Kodi
|
2005-2021 Team Kodi
|
||||||
License: GPL-2+
|
License: GPL-2+
|
||||||
This package is free software; you can redistribute it and/or modify
|
This package is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,7 +24,7 @@ License: GPL-2+
|
|||||||
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
|
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
|
||||||
|
|
||||||
Files: debian/*
|
Files: debian/*
|
||||||
Copyright: 2020 Team Kodi
|
Copyright: 2020-2021 Team Kodi
|
||||||
2016 Julian Scheel <julian@jusst.de>
|
2016 Julian Scheel <julian@jusst.de>
|
||||||
2015 Jean-Luc Barriere
|
2015 Jean-Luc Barriere
|
||||||
2015 wsnipex <wsnipex@a1.net>
|
2015 wsnipex <wsnipex@a1.net>
|
||||||
|
|||||||
7
debian/rules
vendored
7
debian/rules
vendored
@@ -10,14 +10,11 @@
|
|||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
override_dh_auto_configure:
|
override_dh_auto_configure:
|
||||||
# USE_LTO breaks build
|
# USE_LTO breaks build
|
||||||
dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=1
|
dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=1
|
||||||
|
|
||||||
override_dh_strip:
|
|
||||||
dh_strip -pkodi-pvr-octonet --dbg-package=kodi-pvr-octonet-dbg
|
|
||||||
|
|
||||||
override_dh_installdocs:
|
override_dh_installdocs:
|
||||||
dh_installdocs --link-doc=kodi-pvr-octonet
|
dh_installdocs --link-doc=kodi-pvr-octonet
|
||||||
|
|||||||
2
debian/source/format
vendored
2
debian/source/format
vendored
@@ -1 +1 @@
|
|||||||
3.0 (quilt)
|
3.0 (native)
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
p8-platform https://github.com/xbmc/platform.git cee64e9dc0b69e8d286dc170a78effaabfa09c44
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
p8-platform https://github.com/afedchin/platform.git win10
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<addon
|
<addon
|
||||||
id="pvr.octonet"
|
id="pvr.octonet"
|
||||||
version="2.0.0"
|
version="20.2.0"
|
||||||
name="Digital Devices Octopus NET Client"
|
name="Digital Devices Octopus NET Client"
|
||||||
provider-name="digitaldevices">
|
provider-name="digitaldevices">
|
||||||
<requires>@ADDON_DEPENDS@</requires>
|
<requires>@ADDON_DEPENDS@</requires>
|
||||||
<extension
|
<extension
|
||||||
point="xbmc.pvrclient"
|
point="kodi.pvrclient"
|
||||||
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
|
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
|
||||||
<extension point="xbmc.addon.metadata">
|
<extension point="xbmc.addon.metadata">
|
||||||
<summary lang="de_DE">Kodi PVR Addon für Digital Devices Octopus NET Streams</summary>
|
<platform>@PLATFORM@</platform>
|
||||||
<summary lang="en_US">Kodi PVR Addon for Digital Devices Octopus NET Streams</summary>
|
<license>GPL-2.0-or-later</license>
|
||||||
<platform>@PLATFORM@</platform>
|
<source>https://github.com/DigitalDevices/pvr.octonet</source>
|
||||||
<license>GPL-2.0-or-later</license>
|
<news>
|
||||||
<source>https://github.com/DigitalDevices/pvr.octonet</source>
|
</news>
|
||||||
</extension>
|
<summary lang="de_DE">Kodi PVR Addon für Digital Devices Octopus NET Streams</summary>
|
||||||
|
<summary lang="en_GB">Kodi PVR Addon for Digital Devices Octopus NET Streams</summary>
|
||||||
|
</extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
|||||||
@@ -5,17 +5,21 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: KODI Main\n"
|
"Project-Id-Version: KODI Main\n"
|
||||||
"Report-Msgid-Bugs-To: http://trac.kodi.tv/\n"
|
"Report-Msgid-Bugs-To: https://github.com/DigitalDevices/pvr.octonet/issues\n"
|
||||||
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Kodi Translation Team\n"
|
"Last-Translator: Kodi Translation Team\n"
|
||||||
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/kodi-main/language/en_GB/)\n"
|
"Language-Team: German (Germany) (https://kodi.weblate.cloud/projects/kodi-add-ons-pvr-clients/pvr-octonet/de_de/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: en_GB\n"
|
"Language: de_DE\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgctxt "Addon Summary"
|
||||||
|
msgid "Kodi PVR Addon for Digital Devices Octopus NET Streams"
|
||||||
|
msgstr "Kodi PVR Addon für Digital Devices Octopus NET Streams"
|
||||||
|
|
||||||
msgctxt "#30000"
|
msgctxt "#30000"
|
||||||
msgid "Octonet Server Address"
|
msgid "Octonet Server Address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5,17 +5,21 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: KODI Main\n"
|
"Project-Id-Version: KODI Main\n"
|
||||||
"Report-Msgid-Bugs-To: http://trac.kodi.tv/\n"
|
"Report-Msgid-Bugs-To: https://github.com/DigitalDevices/pvr.octonet/issues\n"
|
||||||
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Kodi Translation Team\n"
|
"Last-Translator: Kodi Translation Team\n"
|
||||||
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/kodi-main/language/en_GB/)\n"
|
"Language-Team: English (United Kingdom) (https://kodi.weblate.cloud/projects/kodi-add-ons-pvr-clients/pvr-octonet/en_gb/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: en_GB\n"
|
"Language: en_GB\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgctxt "Addon Summary"
|
||||||
|
msgid "Kodi PVR Addon for Digital Devices Octopus NET Streams"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgctxt "#30000"
|
msgctxt "#30000"
|
||||||
msgid "Octonet Server Address"
|
msgid "Octonet Server Address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
<settings>
|
<settings version="1">
|
||||||
<!-- Octonet Server Address -->
|
<section id="pvr.octonet">
|
||||||
<setting id="octonetAddress" type="text" label="30000" default="" />
|
<category id="main" label="128" help="-1">
|
||||||
|
<group id="1" label="-1">
|
||||||
|
<!-- Octonet Server Address -->
|
||||||
|
<setting id="octonetAddress" type="string" label="30000" help="-1">
|
||||||
|
<level>0</level>
|
||||||
|
<default></default>
|
||||||
|
<constraints>
|
||||||
|
<allowempty>true</allowempty>
|
||||||
|
</constraints>
|
||||||
|
<control type="edit" format="string" />
|
||||||
|
</setting>
|
||||||
|
</group>
|
||||||
|
</category>
|
||||||
|
</section>
|
||||||
</settings>
|
</settings>
|
||||||
|
|||||||
@@ -8,356 +8,450 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "OctonetData.h"
|
||||||
|
|
||||||
|
#include "rtsp_client.hpp"
|
||||||
|
|
||||||
|
#include <json/json.h>
|
||||||
|
#include <kodi/Filesystem.h>
|
||||||
|
#include <kodi/General.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <json/json.h>
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
|
||||||
|
|
||||||
#include "OctonetData.h"
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
#define timegm _mkgmtime
|
#define timegm _mkgmtime
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace ADDON;
|
OctonetData::OctonetData(const std::string& octonetAddress,
|
||||||
|
const kodi::addon::IInstanceInfo& instance)
|
||||||
OctonetData::OctonetData()
|
: kodi::addon::CInstancePVRClient(instance)
|
||||||
{
|
{
|
||||||
serverAddress = octonetAddress;
|
m_serverAddress = octonetAddress;
|
||||||
channels.clear();
|
m_channels.clear();
|
||||||
groups.clear();
|
m_groups.clear();
|
||||||
lastEpgLoad = 0;
|
m_lastEpgLoad = 0;
|
||||||
|
|
||||||
if (!loadChannelList())
|
if (!LoadChannelList())
|
||||||
libKodi->QueueNotification(QUEUE_ERROR, libKodi->GetLocalizedString(30001), channels.size());
|
kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30001).c_str(),
|
||||||
|
m_channels.size());
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Currently unused, as thread was already present before with
|
||||||
|
// p8platform, by remove of them was it added as C++11 thread way.
|
||||||
|
kodi::Log(ADDON_LOG_INFO, "%s Starting separate client update thread...", __func__);
|
||||||
|
m_running = true;
|
||||||
|
m_thread = std::thread([&] { Process(); });
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
OctonetData::~OctonetData(void)
|
OctonetData::~OctonetData(void)
|
||||||
{
|
{
|
||||||
channels.clear();
|
/*
|
||||||
groups.clear();
|
m_running = false;
|
||||||
|
if (m_thread.joinable())
|
||||||
|
m_thread.join();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t OctonetData::parseID(std::string id)
|
PVR_ERROR OctonetData::GetCapabilities(kodi::addon::PVRCapabilities& capabilities)
|
||||||
{
|
{
|
||||||
std::hash<std::string> hash_fn;
|
capabilities.SetSupportsTV(true);
|
||||||
int64_t nativeId = hash_fn(id);
|
capabilities.SetSupportsRadio(true);
|
||||||
|
capabilities.SetSupportsChannelGroups(true);
|
||||||
|
capabilities.SetSupportsEPG(true);
|
||||||
|
capabilities.SetSupportsRecordings(false);
|
||||||
|
capabilities.SetSupportsRecordingsRename(false);
|
||||||
|
capabilities.SetSupportsRecordingsLifetimeChange(false);
|
||||||
|
capabilities.SetSupportsDescrambleInfo(false);
|
||||||
|
|
||||||
return nativeId;
|
return PVR_ERROR_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OctonetData::loadChannelList()
|
PVR_ERROR OctonetData::GetBackendName(std::string& name)
|
||||||
{
|
{
|
||||||
std::string jsonContent;
|
name = "Digital Devices Octopus NET Client";
|
||||||
void *f = libKodi->OpenFile(("http://" + serverAddress + "/channellist.lua?select=json").c_str(), 0);
|
return PVR_ERROR_NO_ERROR;
|
||||||
if (!f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
char buf[1024];
|
|
||||||
while (int read = libKodi->ReadFile(f, buf, 1024))
|
|
||||||
jsonContent.append(buf, read);
|
|
||||||
|
|
||||||
libKodi->CloseFile(f);
|
|
||||||
|
|
||||||
Json::Value root;
|
|
||||||
Json::Reader reader;
|
|
||||||
|
|
||||||
if (!reader.parse(jsonContent, root, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const Json::Value groupList = root["GroupList"];
|
|
||||||
for (unsigned int i = 0; i < groupList.size(); i++) {
|
|
||||||
const Json::Value channelList = groupList[i]["ChannelList"];
|
|
||||||
OctonetGroup group;
|
|
||||||
|
|
||||||
group.name = groupList[i]["Title"].asString();
|
|
||||||
group.radio = group.name.compare(0, 5, "Radio") ? false : true;
|
|
||||||
|
|
||||||
for (unsigned int j = 0; j < channelList.size(); j++) {
|
|
||||||
const Json::Value channel = channelList[j];
|
|
||||||
OctonetChannel chan;
|
|
||||||
|
|
||||||
chan.name = channel["Title"].asString();
|
|
||||||
chan.url = "rtsp://" + serverAddress + "/" + channel["Request"].asString();
|
|
||||||
chan.radio = group.radio;
|
|
||||||
chan.nativeId = parseID(channel["ID"].asString());
|
|
||||||
|
|
||||||
chan.id = 1000 + channels.size();
|
|
||||||
group.members.push_back(channels.size());
|
|
||||||
channels.push_back(chan);
|
|
||||||
}
|
|
||||||
groups.push_back(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OctonetChannel* OctonetData::findChannel(int64_t nativeId)
|
PVR_ERROR OctonetData::GetBackendVersion(std::string& version)
|
||||||
{
|
{
|
||||||
std::vector<OctonetChannel>::iterator it;
|
version = STR(OCTONET_VERSION);
|
||||||
for (it = channels.begin(); it < channels.end(); ++it) {
|
return PVR_ERROR_NO_ERROR;
|
||||||
if (it->nativeId == nativeId)
|
|
||||||
return &*it;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t OctonetData::parseDateTime(std::string date)
|
PVR_ERROR OctonetData::GetConnectionString(std::string& connection)
|
||||||
{
|
{
|
||||||
struct tm timeinfo;
|
connection = "connected"; // FIXME: translate?
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
memset(&timeinfo, 0, sizeof(timeinfo));
|
|
||||||
|
|
||||||
if (date.length() > 8) {
|
|
||||||
sscanf(date.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ",
|
|
||||||
&timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
|
|
||||||
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
|
||||||
timeinfo.tm_mon -= 1;
|
|
||||||
timeinfo.tm_year -= 1900;
|
|
||||||
} else {
|
|
||||||
sscanf(date.c_str(), "%02d:%02d:%02d",
|
|
||||||
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
|
||||||
timeinfo.tm_year = 70; // unix timestamps start 1970
|
|
||||||
timeinfo.tm_mday = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeinfo.tm_isdst = -1;
|
|
||||||
|
|
||||||
return timegm(&timeinfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OctonetData::loadEPG(void)
|
PVR_ERROR OctonetData::GetBackendHostname(std::string& hostname)
|
||||||
{
|
{
|
||||||
/* Reload at most every 30 seconds */
|
hostname = m_serverAddress;
|
||||||
if (lastEpgLoad + 30 > time(NULL))
|
return PVR_ERROR_NO_ERROR;
|
||||||
return false;
|
|
||||||
|
|
||||||
std::string jsonContent;
|
|
||||||
void *f = libKodi->OpenFile(("http://" + serverAddress + "/epg.lua?;#|encoding=gzip").c_str(), 0);
|
|
||||||
if (!f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
char buf[1024];
|
|
||||||
while (int read = libKodi->ReadFile(f, buf, 1024))
|
|
||||||
jsonContent.append(buf, read);
|
|
||||||
|
|
||||||
libKodi->CloseFile(f);
|
|
||||||
|
|
||||||
Json::Value root;
|
|
||||||
Json::Reader reader;
|
|
||||||
|
|
||||||
if (!reader.parse(jsonContent, root, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const Json::Value eventList = root["EventList"];
|
|
||||||
OctonetChannel *channel = NULL;
|
|
||||||
for (unsigned int i = 0; i < eventList.size(); i++) {
|
|
||||||
const Json::Value event = eventList[i];
|
|
||||||
OctonetEpgEntry entry;
|
|
||||||
|
|
||||||
entry.start = parseDateTime(event["Time"].asString());
|
|
||||||
entry.end = entry.start + parseDateTime(event["Duration"].asString());
|
|
||||||
entry.title = event["Name"].asString();
|
|
||||||
entry.subtitle = event["Text"].asString();
|
|
||||||
std::string channelId = event["ID"].asString();
|
|
||||||
std::string epgId = channelId.substr(channelId.rfind(":") + 1);
|
|
||||||
channelId = channelId.substr(0, channelId.rfind(":"));
|
|
||||||
|
|
||||||
entry.channelId = parseID(channelId);
|
|
||||||
entry.id = atoi(epgId.c_str());
|
|
||||||
|
|
||||||
if (channel == NULL || channel->nativeId != entry.channelId)
|
|
||||||
channel = findChannel(entry.channelId);
|
|
||||||
|
|
||||||
if (channel == NULL) {
|
|
||||||
libKodi->Log(LOG_ERROR, "EPG for unknown channel.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
channel->epg.push_back(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastEpgLoad = time(NULL);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void *OctonetData::Process(void)
|
PVR_ERROR OctonetData::OnSystemSleep()
|
||||||
{
|
{
|
||||||
return NULL;
|
kodi::Log(ADDON_LOG_INFO, "Received event: %s", __func__);
|
||||||
|
// FIXME: Disconnect?
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int OctonetData::getChannelCount(void)
|
PVR_ERROR OctonetData::OnSystemWake()
|
||||||
{
|
{
|
||||||
return channels.size();
|
kodi::Log(ADDON_LOG_INFO, "Received event: %s", __func__);
|
||||||
|
// FIXME:Reconnect?
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVR_ERROR OctonetData::getChannels(ADDON_HANDLE handle, bool bRadio)
|
int64_t OctonetData::ParseID(std::string id)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < channels.size(); i++)
|
std::hash<std::string> hash_fn;
|
||||||
{
|
int64_t nativeId = hash_fn(id);
|
||||||
OctonetChannel &channel = channels.at(i);
|
|
||||||
if (channel.radio == bRadio)
|
|
||||||
{
|
|
||||||
PVR_CHANNEL chan;
|
|
||||||
memset(&chan, 0, sizeof(PVR_CHANNEL));
|
|
||||||
|
|
||||||
chan.iUniqueId = channel.id;
|
return nativeId;
|
||||||
chan.bIsRadio = channel.radio;
|
|
||||||
chan.iChannelNumber = i;
|
|
||||||
strncpy(chan.strChannelName, channel.name.c_str(), strlen(channel.name.c_str()));
|
|
||||||
strcpy(chan.strInputFormat, "video/x-mpegts");
|
|
||||||
chan.bIsHidden = false;
|
|
||||||
|
|
||||||
pvr->TransferChannelEntry(handle, &chan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PVR_ERROR OctonetData::getEPG(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end)
|
bool OctonetData::LoadChannelList()
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < channels.size(); i++)
|
std::string jsonContent;
|
||||||
{
|
kodi::vfs::CFile f;
|
||||||
OctonetChannel &chan = channels.at(i);
|
if (!f.OpenFile("http://" + m_serverAddress + "/channellist.lua?select=json", 0))
|
||||||
if (iChannelUid != chan.id)
|
return false;
|
||||||
continue;
|
|
||||||
|
|
||||||
if(chan.epg.empty()) {
|
char buf[1024];
|
||||||
loadEPG();
|
while (int read = f.Read(buf, 1024))
|
||||||
}
|
jsonContent.append(buf, read);
|
||||||
|
|
||||||
// FIXME: Check if reload is needed!?
|
f.Close();
|
||||||
|
|
||||||
std::vector<OctonetEpgEntry>::iterator it;
|
Json::Value root;
|
||||||
time_t last_end = 0;
|
Json::Reader reader;
|
||||||
for (it = chan.epg.begin(); it != chan.epg.end(); ++it) {
|
|
||||||
if (it->end > last_end)
|
|
||||||
last_end = it->end;
|
|
||||||
|
|
||||||
if (it->end < start || it->start > end) {
|
if (!reader.parse(jsonContent, root, false))
|
||||||
continue;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
EPG_TAG entry;
|
const Json::Value groupList = root["GroupList"];
|
||||||
memset(&entry, 0, sizeof(EPG_TAG));
|
for (unsigned int i = 0; i < groupList.size(); i++)
|
||||||
entry.iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
{
|
||||||
entry.iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
const Json::Value channelList = groupList[i]["ChannelList"];
|
||||||
entry.iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
OctonetGroup group;
|
||||||
|
|
||||||
entry.iUniqueChannelId = chan.id;
|
group.name = groupList[i]["Title"].asString();
|
||||||
entry.iUniqueBroadcastId = it->id;
|
group.radio = group.name.compare(0, 5, "Radio") ? false : true;
|
||||||
entry.strTitle = it->title.c_str();
|
|
||||||
entry.strPlotOutline = it->subtitle.c_str();
|
|
||||||
entry.startTime = it->start;
|
|
||||||
entry.endTime = it->end;
|
|
||||||
|
|
||||||
pvr->TransferEpgEntry(handle, &entry);
|
for (unsigned int j = 0; j < channelList.size(); j++)
|
||||||
}
|
{
|
||||||
|
const Json::Value channel = channelList[j];
|
||||||
|
OctonetChannel chan;
|
||||||
|
|
||||||
if (last_end < end)
|
chan.name = channel["Title"].asString();
|
||||||
loadEPG();
|
chan.url = "rtsp://" + m_serverAddress + "/" + channel["Request"].asString();
|
||||||
|
chan.radio = group.radio;
|
||||||
|
chan.nativeId = ParseID(channel["ID"].asString());
|
||||||
|
|
||||||
for (it = chan.epg.begin(); it != chan.epg.end(); ++it) {
|
chan.id = 1000 + m_channels.size();
|
||||||
if (it->end < start || it->start > end) {
|
group.members.push_back(m_channels.size());
|
||||||
continue;
|
m_channels.push_back(chan);
|
||||||
}
|
}
|
||||||
|
m_groups.push_back(group);
|
||||||
|
}
|
||||||
|
|
||||||
EPG_TAG entry;
|
return true;
|
||||||
memset(&entry, 0, sizeof(EPG_TAG));
|
|
||||||
entry.iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
|
||||||
entry.iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
|
||||||
entry.iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
|
||||||
|
|
||||||
entry.iUniqueChannelId = chan.id;
|
|
||||||
entry.iUniqueBroadcastId = it->id;
|
|
||||||
entry.strTitle = it->title.c_str();
|
|
||||||
entry.strPlotOutline = it->subtitle.c_str();
|
|
||||||
entry.startTime = it->start;
|
|
||||||
entry.endTime = it->end;
|
|
||||||
|
|
||||||
pvr->TransferEpgEntry(handle, &entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& OctonetData::getUrl(int id) const {
|
OctonetChannel* OctonetData::FindChannel(int64_t nativeId)
|
||||||
for(std::vector<OctonetChannel>::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) {
|
|
||||||
if(iter->id == id) {
|
|
||||||
return iter->url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels[0].url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& OctonetData::getName(int id) const {
|
|
||||||
for(std::vector<OctonetChannel>::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) {
|
|
||||||
if(iter->id == id) {
|
|
||||||
return iter->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels[0].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
int OctonetData::getGroupCount(void)
|
|
||||||
{
|
{
|
||||||
return groups.size();
|
for (auto& channel : m_channels)
|
||||||
|
{
|
||||||
|
if (channel.nativeId == nativeId)
|
||||||
|
return &channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVR_ERROR OctonetData::getGroups(ADDON_HANDLE handle, bool bRadio)
|
time_t OctonetData::ParseDateTime(std::string date)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < groups.size(); i++)
|
struct tm timeinfo;
|
||||||
{
|
|
||||||
OctonetGroup &group = groups.at(i);
|
|
||||||
if (group.radio == bRadio)
|
|
||||||
{
|
|
||||||
PVR_CHANNEL_GROUP g;
|
|
||||||
memset(&g, 0, sizeof(PVR_CHANNEL_GROUP));
|
|
||||||
|
|
||||||
g.iPosition = 0;
|
memset(&timeinfo, 0, sizeof(timeinfo));
|
||||||
g.bIsRadio = group.radio;
|
|
||||||
strncpy(g.strGroupName, group.name.c_str(), strlen(group.name.c_str()));
|
|
||||||
|
|
||||||
pvr->TransferChannelGroup(handle, &g);
|
if (date.length() > 8)
|
||||||
}
|
{
|
||||||
}
|
sscanf(date.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &timeinfo.tm_year, &timeinfo.tm_mon,
|
||||||
|
&timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||||
|
timeinfo.tm_mon -= 1;
|
||||||
|
timeinfo.tm_year -= 1900;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sscanf(date.c_str(), "%02d:%02d:%02d", &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||||
|
timeinfo.tm_year = 70; // unix timestamps start 1970
|
||||||
|
timeinfo.tm_mday = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
timeinfo.tm_isdst = -1;
|
||||||
|
|
||||||
|
return timegm(&timeinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
PVR_ERROR OctonetData::getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group)
|
bool OctonetData::LoadEPG(void)
|
||||||
{
|
{
|
||||||
OctonetGroup *g = findGroup(group.strGroupName);
|
/* Reload at most every 30 seconds */
|
||||||
if (g == NULL)
|
if (m_lastEpgLoad + 30 > time(nullptr))
|
||||||
return PVR_ERROR_UNKNOWN;
|
return false;
|
||||||
|
|
||||||
for (unsigned int i = 0; i < g->members.size(); i++)
|
std::string jsonContent;
|
||||||
{
|
kodi::vfs::CFile f;
|
||||||
OctonetChannel &channel = channels.at(g->members[i]);
|
if (!f.OpenFile("http://" + m_serverAddress + "/epg.lua?;#|encoding=gzip", 0))
|
||||||
PVR_CHANNEL_GROUP_MEMBER m;
|
return false;
|
||||||
memset(&m, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER));
|
|
||||||
|
|
||||||
strncpy(m.strGroupName, group.strGroupName, strlen(group.strGroupName));
|
char buf[1024];
|
||||||
m.iChannelUniqueId = channel.id;
|
while (int read = f.Read(buf, 1024))
|
||||||
m.iChannelNumber = channel.id;
|
jsonContent.append(buf, read);
|
||||||
|
|
||||||
pvr->TransferChannelGroupMember(handle, &m);
|
f.Close();
|
||||||
}
|
|
||||||
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
Json::Value root;
|
||||||
|
Json::Reader reader;
|
||||||
|
|
||||||
|
if (!reader.parse(jsonContent, root, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const Json::Value eventList = root["EventList"];
|
||||||
|
OctonetChannel* channel = nullptr;
|
||||||
|
for (unsigned int i = 0; i < eventList.size(); i++)
|
||||||
|
{
|
||||||
|
const Json::Value event = eventList[i];
|
||||||
|
OctonetEpgEntry entry;
|
||||||
|
|
||||||
|
entry.start = ParseDateTime(event["Time"].asString());
|
||||||
|
entry.end = entry.start + ParseDateTime(event["Duration"].asString());
|
||||||
|
entry.title = event["Name"].asString();
|
||||||
|
entry.subtitle = event["Text"].asString();
|
||||||
|
std::string channelId = event["ID"].asString();
|
||||||
|
std::string epgId = channelId.substr(channelId.rfind(":") + 1);
|
||||||
|
channelId = channelId.substr(0, channelId.rfind(":"));
|
||||||
|
|
||||||
|
entry.channelId = ParseID(channelId);
|
||||||
|
entry.id = std::stoi(epgId);
|
||||||
|
|
||||||
|
if (channel == nullptr || channel->nativeId != entry.channelId)
|
||||||
|
channel = FindChannel(entry.channelId);
|
||||||
|
|
||||||
|
if (channel == nullptr)
|
||||||
|
{
|
||||||
|
kodi::Log(ADDON_LOG_ERROR, "EPG for unknown channel.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->epg.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastEpgLoad = time(nullptr);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
OctonetGroup* OctonetData::findGroup(const std::string &name)
|
void OctonetData::Process()
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < groups.size(); i++)
|
return;
|
||||||
{
|
}
|
||||||
if (groups.at(i).name == name)
|
|
||||||
return &groups.at(i);
|
PVR_ERROR OctonetData::GetChannelsAmount(int& amount)
|
||||||
}
|
{
|
||||||
|
amount = m_channels.size();
|
||||||
return NULL;
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < m_channels.size(); i++)
|
||||||
|
{
|
||||||
|
OctonetChannel& channel = m_channels.at(i);
|
||||||
|
if (channel.radio == radio)
|
||||||
|
{
|
||||||
|
kodi::addon::PVRChannel chan;
|
||||||
|
|
||||||
|
chan.SetUniqueId(channel.id);
|
||||||
|
chan.SetIsRadio(channel.radio);
|
||||||
|
chan.SetChannelNumber(i);
|
||||||
|
chan.SetChannelName(channel.name);
|
||||||
|
chan.SetMimeType("video/x-mpegts");
|
||||||
|
chan.SetIsHidden(false);
|
||||||
|
|
||||||
|
results.Add(chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVR_ERROR OctonetData::GetEPGForChannel(int channelUid,
|
||||||
|
time_t start,
|
||||||
|
time_t end,
|
||||||
|
kodi::addon::PVREPGTagsResultSet& results)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < m_channels.size(); i++)
|
||||||
|
{
|
||||||
|
OctonetChannel& chan = m_channels.at(i);
|
||||||
|
if (channelUid != chan.id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (chan.epg.empty())
|
||||||
|
{
|
||||||
|
LoadEPG();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Check if reload is needed!?
|
||||||
|
|
||||||
|
time_t last_end = 0;
|
||||||
|
for (const auto& epg : chan.epg)
|
||||||
|
{
|
||||||
|
if (epg.end > last_end)
|
||||||
|
last_end = epg.end;
|
||||||
|
|
||||||
|
if (epg.end < start || epg.start > end)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kodi::addon::PVREPGTag entry;
|
||||||
|
|
||||||
|
entry.SetUniqueChannelId(chan.id);
|
||||||
|
entry.SetUniqueBroadcastId(epg.id);
|
||||||
|
entry.SetTitle(epg.title);
|
||||||
|
entry.SetPlotOutline(epg.subtitle);
|
||||||
|
entry.SetStartTime(epg.start);
|
||||||
|
entry.SetEndTime(epg.end);
|
||||||
|
|
||||||
|
results.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_end < end)
|
||||||
|
LoadEPG();
|
||||||
|
|
||||||
|
for (const auto& epg : chan.epg)
|
||||||
|
{
|
||||||
|
if (epg.end < start || epg.start > end)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kodi::addon::PVREPGTag entry;
|
||||||
|
|
||||||
|
entry.SetUniqueChannelId(chan.id);
|
||||||
|
entry.SetUniqueBroadcastId(epg.id);
|
||||||
|
entry.SetTitle(epg.title);
|
||||||
|
entry.SetPlotOutline(epg.subtitle);
|
||||||
|
entry.SetStartTime(epg.start);
|
||||||
|
entry.SetEndTime(epg.end);
|
||||||
|
|
||||||
|
results.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& OctonetData::GetUrl(int id) const
|
||||||
|
{
|
||||||
|
for (const auto& channel : m_channels)
|
||||||
|
{
|
||||||
|
if (channel.id == id)
|
||||||
|
{
|
||||||
|
return channel.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_channels[0].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& OctonetData::GetName(int id) const
|
||||||
|
{
|
||||||
|
for (const auto& channel : m_channels)
|
||||||
|
{
|
||||||
|
if (channel.id == id)
|
||||||
|
{
|
||||||
|
return channel.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_channels[0].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVR_ERROR OctonetData::GetChannelGroupsAmount(int& amount)
|
||||||
|
{
|
||||||
|
amount = m_groups.size();
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVR_ERROR OctonetData::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results)
|
||||||
|
{
|
||||||
|
for (const auto& group : m_groups)
|
||||||
|
{
|
||||||
|
if (group.radio == radio)
|
||||||
|
{
|
||||||
|
kodi::addon::PVRChannelGroup g;
|
||||||
|
|
||||||
|
g.SetPosition(0);
|
||||||
|
g.SetIsRadio(group.radio);
|
||||||
|
g.SetGroupName(group.name);
|
||||||
|
|
||||||
|
results.Add(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVR_ERROR OctonetData::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
|
||||||
|
kodi::addon::PVRChannelGroupMembersResultSet& results)
|
||||||
|
{
|
||||||
|
const OctonetGroup* g = FindGroup(group.GetGroupName());
|
||||||
|
if (g == nullptr)
|
||||||
|
return PVR_ERROR_UNKNOWN;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < g->members.size(); i++)
|
||||||
|
{
|
||||||
|
OctonetChannel& channel = m_channels.at(g->members[i]);
|
||||||
|
kodi::addon::PVRChannelGroupMember m;
|
||||||
|
|
||||||
|
m.SetGroupName(group.GetGroupName());
|
||||||
|
m.SetChannelUniqueId(channel.id);
|
||||||
|
m.SetChannelNumber(channel.id);
|
||||||
|
|
||||||
|
results.Add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PVR_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
OctonetGroup* OctonetData::FindGroup(const std::string& name)
|
||||||
|
{
|
||||||
|
for (auto& group : m_groups)
|
||||||
|
{
|
||||||
|
if (group.name == name)
|
||||||
|
return &group;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PVR stream handling */
|
||||||
|
/* entirely unused, as we use standard RTSP+TS mux, which can be handlded by
|
||||||
|
* Kodi core */
|
||||||
|
bool OctonetData::OpenLiveStream(const kodi::addon::PVRChannel& channelinfo)
|
||||||
|
{
|
||||||
|
return rtsp_open(GetName(channelinfo.GetUniqueId()), GetUrl(channelinfo.GetUniqueId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int OctonetData::ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize)
|
||||||
|
{
|
||||||
|
return rtsp_read(pBuffer, iBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OctonetData::CloseLiveStream()
|
||||||
|
{
|
||||||
|
rtsp_close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,71 +10,92 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <kodi/addon-instance/PVR.h>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "p8-platform/threads/threads.h"
|
|
||||||
#include "client.h"
|
|
||||||
|
|
||||||
struct OctonetEpgEntry
|
struct OctonetEpgEntry
|
||||||
{
|
{
|
||||||
int64_t channelId;
|
int64_t channelId;
|
||||||
time_t start;
|
time_t start;
|
||||||
time_t end;
|
time_t end;
|
||||||
int id;
|
int id;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string subtitle;
|
std::string subtitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OctonetChannel
|
struct OctonetChannel
|
||||||
{
|
{
|
||||||
int64_t nativeId;
|
int64_t nativeId;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string url;
|
std::string url;
|
||||||
bool radio;
|
bool radio;
|
||||||
int id;
|
int id;
|
||||||
|
|
||||||
std::vector<OctonetEpgEntry> epg;
|
std::vector<OctonetEpgEntry> epg;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OctonetGroup
|
struct OctonetGroup
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
bool radio;
|
bool radio;
|
||||||
std::vector<int> members;
|
std::vector<int> members;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OctonetData : public P8PLATFORM::CThread
|
class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OctonetData(void);
|
OctonetData(const std::string& octonetAddress,
|
||||||
virtual ~OctonetData(void);
|
const kodi::addon::IInstanceInfo& instance);
|
||||||
|
~OctonetData() override;
|
||||||
|
|
||||||
virtual int getChannelCount(void);
|
PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override;
|
||||||
virtual PVR_ERROR getChannels(ADDON_HANDLE handle, bool bRadio);
|
PVR_ERROR GetBackendName(std::string& name) override;
|
||||||
|
PVR_ERROR GetBackendVersion(std::string& version) override;
|
||||||
|
PVR_ERROR GetConnectionString(std::string& connection) override;
|
||||||
|
PVR_ERROR GetBackendHostname(std::string& hostname) override;
|
||||||
|
|
||||||
virtual int getGroupCount(void);
|
PVR_ERROR OnSystemSleep() override;
|
||||||
virtual PVR_ERROR getGroups(ADDON_HANDLE handle, bool bRadio);
|
PVR_ERROR OnSystemWake() override;
|
||||||
virtual PVR_ERROR getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group);
|
|
||||||
|
|
||||||
virtual PVR_ERROR getEPG(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end);
|
PVR_ERROR GetChannelsAmount(int& amount) override;
|
||||||
const std::string& getUrl(int id) const;
|
PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results) override;
|
||||||
const std::string& getName(int id) const;
|
|
||||||
|
|
||||||
protected:
|
PVR_ERROR GetChannelGroupsAmount(int& amount) override;
|
||||||
virtual bool loadChannelList(void);
|
PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override;
|
||||||
virtual bool loadEPG(void);
|
PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
|
||||||
virtual OctonetGroup* findGroup(const std::string &name);
|
kodi::addon::PVRChannelGroupMembersResultSet& results) override;
|
||||||
|
|
||||||
virtual void *Process(void);
|
PVR_ERROR GetEPGForChannel(int channelUid,
|
||||||
|
time_t start,
|
||||||
|
time_t end,
|
||||||
|
kodi::addon::PVREPGTagsResultSet& results) override;
|
||||||
|
|
||||||
OctonetChannel* findChannel(int64_t nativeId);
|
bool OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) override;
|
||||||
time_t parseDateTime(std::string date);
|
int ReadLiveStream(unsigned char* buffer, unsigned int size) override;
|
||||||
int64_t parseID(std::string id);
|
void CloseLiveStream() override;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
std::string serverAddress;
|
void Process();
|
||||||
std::vector<OctonetChannel> channels;
|
|
||||||
std::vector<OctonetGroup> groups;
|
|
||||||
|
|
||||||
time_t lastEpgLoad;
|
const std::string& GetUrl(int id) const;
|
||||||
|
const std::string& GetName(int id) const;
|
||||||
|
|
||||||
|
bool LoadChannelList(void);
|
||||||
|
bool LoadEPG(void);
|
||||||
|
OctonetGroup* FindGroup(const std::string& name);
|
||||||
|
OctonetChannel* FindChannel(int64_t nativeId);
|
||||||
|
time_t ParseDateTime(std::string date);
|
||||||
|
int64_t ParseID(std::string id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_serverAddress;
|
||||||
|
std::vector<OctonetChannel> m_channels;
|
||||||
|
std::vector<OctonetGroup> m_groups;
|
||||||
|
|
||||||
|
time_t m_lastEpgLoad;
|
||||||
|
|
||||||
|
std::atomic<bool> m_running = {false};
|
||||||
|
std::thread m_thread;
|
||||||
};
|
};
|
||||||
|
|||||||
464
src/Socket.cpp
464
src/Socket.cpp
@@ -1,20 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2005-2020 Team Kodi
|
* Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
|
||||||
* https://kodi.tv
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
* See LICENSE.md for more information.
|
* See LICENSE.md for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "kodi/libXBMC_addon.h"
|
|
||||||
#include <string>
|
|
||||||
#include "p8-platform/os.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "Socket.h"
|
#include "Socket.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <kodi/General.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace ADDON;
|
|
||||||
|
|
||||||
namespace OCTO
|
namespace OCTO
|
||||||
{
|
{
|
||||||
@@ -22,28 +19,31 @@ namespace OCTO
|
|||||||
/* Master defines for client control */
|
/* Master defines for client control */
|
||||||
#define RECEIVE_TIMEOUT 6 //sec
|
#define RECEIVE_TIMEOUT 6 //sec
|
||||||
|
|
||||||
Socket::Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol)
|
Socket::Socket(const enum SocketFamily family,
|
||||||
|
const enum SocketDomain domain,
|
||||||
|
const enum SocketType type,
|
||||||
|
const enum SocketProtocol protocol)
|
||||||
{
|
{
|
||||||
_sd = INVALID_SOCKET;
|
m_sd = INVALID_SOCKET;
|
||||||
_family = family;
|
m_family = family;
|
||||||
_domain = domain;
|
m_domain = domain;
|
||||||
_type = type;
|
m_type = type;
|
||||||
_protocol = protocol;
|
m_protocol = protocol;
|
||||||
_port = 0;
|
m_port = 0;
|
||||||
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
|
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Socket::Socket()
|
Socket::Socket()
|
||||||
{
|
{
|
||||||
// Default constructor, default settings
|
// Default constructor, default settings
|
||||||
_sd = INVALID_SOCKET;
|
m_sd = INVALID_SOCKET;
|
||||||
_family = af_inet;
|
m_family = af_inet;
|
||||||
_domain = pf_inet;
|
m_domain = pf_inet;
|
||||||
_type = sock_stream;
|
m_type = sock_stream;
|
||||||
_protocol = tcp;
|
m_protocol = tcp;
|
||||||
_port = 0;
|
m_port = 0;
|
||||||
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
|
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ Socket::~Socket()
|
|||||||
|
|
||||||
bool Socket::setHostname(const std::string& host)
|
bool Socket::setHostname(const std::string& host)
|
||||||
{
|
{
|
||||||
_hostname = host;
|
m_hostname = host;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,9 +63,9 @@ bool Socket::close()
|
|||||||
{
|
{
|
||||||
if (is_valid())
|
if (is_valid())
|
||||||
{
|
{
|
||||||
if (_sd != SOCKET_ERROR)
|
if (m_sd != SOCKET_ERROR)
|
||||||
closesocket(_sd);
|
closesocket(m_sd);
|
||||||
_sd = INVALID_SOCKET;
|
m_sd = INVALID_SOCKET;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -75,7 +75,7 @@ bool Socket::create()
|
|||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
|
||||||
if(!osInit())
|
if (!osInit())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -84,25 +84,25 @@ bool Socket::create()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Socket::bind ( const unsigned short port )
|
bool Socket::bind(const unsigned short port)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (is_valid())
|
if (is_valid())
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
_sd = socket(_family, _type, _protocol);
|
m_sd = socket(m_family, m_type, m_protocol);
|
||||||
_port = port;
|
m_port = port;
|
||||||
_sockaddr.sin_family = (sa_family_t) _family;
|
m_sockaddr.sin_family = (sa_family_t)m_family;
|
||||||
_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
|
m_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
|
||||||
_sockaddr.sin_port = htons( _port );
|
m_sockaddr.sin_port = htons(m_port);
|
||||||
|
|
||||||
int bind_return = ::bind(_sd, (sockaddr*)(&_sockaddr), sizeof(_sockaddr));
|
int bind_return = ::bind(m_sd, (sockaddr*)(&m_sockaddr), sizeof(m_sockaddr));
|
||||||
|
|
||||||
if ( bind_return == -1 )
|
if (bind_return == -1)
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::bind" );
|
errormessage(getLastError(), "Socket::bind");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +118,13 @@ bool Socket::listen() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int listen_return = ::listen (_sd, SOMAXCONN);
|
int listen_return = ::listen(m_sd, SOMAXCONN);
|
||||||
//This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h.
|
//This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h.
|
||||||
//linux 128//MAXCONNECTIONS =1
|
//linux 128//MAXCONNECTIONS =1
|
||||||
|
|
||||||
if (listen_return == -1)
|
if (listen_return == -1)
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::listen" );
|
errormessage(getLastError(), "Socket::listen");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,23 +132,24 @@ bool Socket::listen() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Socket::accept ( Socket& new_socket ) const
|
bool Socket::accept(Socket& new_socket) const
|
||||||
{
|
{
|
||||||
if (!is_valid())
|
if (!is_valid())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
socklen_t addr_length = sizeof( _sockaddr );
|
socklen_t addr_length = sizeof(m_sockaddr);
|
||||||
new_socket._sd = ::accept(_sd, const_cast<sockaddr*>( (const sockaddr*) &_sockaddr), &addr_length );
|
new_socket.m_sd =
|
||||||
|
::accept(m_sd, const_cast<sockaddr*>((const sockaddr*)&m_sockaddr), &addr_length);
|
||||||
|
|
||||||
#ifdef TARGET_WINDOWS
|
#ifdef TARGET_WINDOWS
|
||||||
if (new_socket._sd == INVALID_SOCKET)
|
if (new_socket.m_sd == INVALID_SOCKET)
|
||||||
#else
|
#else
|
||||||
if (new_socket._sd <= 0)
|
if (new_socket.m_sd <= 0)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::accept" );
|
errormessage(getLastError(), "Socket::accept");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,17 +157,17 @@ bool Socket::accept ( Socket& new_socket ) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::send ( const std::string& data )
|
int Socket::send(const std::string& data)
|
||||||
{
|
{
|
||||||
return Socket::send( (const char*) data.c_str(), (const unsigned int) data.size());
|
return Socket::send((const char*)data.c_str(), (const unsigned int)data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::send ( const char* data, const unsigned int len )
|
int Socket::send(const char* data, const unsigned int len)
|
||||||
{
|
{
|
||||||
fd_set set_w, set_e;
|
fd_set set_w, set_e;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
if (!is_valid())
|
if (!is_valid())
|
||||||
{
|
{
|
||||||
@@ -174,35 +175,35 @@ int Socket::send ( const char* data, const unsigned int len )
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fill with new data
|
// fill with new data
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
FD_ZERO(&set_w);
|
FD_ZERO(&set_w);
|
||||||
FD_ZERO(&set_e);
|
FD_ZERO(&set_e);
|
||||||
FD_SET(_sd, &set_w);
|
FD_SET(m_sd, &set_w);
|
||||||
FD_SET(_sd, &set_e);
|
FD_SET(m_sd, &set_e);
|
||||||
|
|
||||||
result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv);
|
result = select(FD_SETSIZE, &set_w, nullptr, &set_e, &tv);
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::send - select failed");
|
kodi::Log(ADDON_LOG_ERROR, "Socket::send - select failed");
|
||||||
close();
|
close();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (FD_ISSET(_sd, &set_w))
|
if (FD_ISSET(m_sd, &set_w))
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
|
kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data");
|
||||||
close();
|
close();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = ::send(_sd, data, len, 0 );
|
int status = ::send(m_sd, data, len, 0);
|
||||||
|
|
||||||
if (status == -1)
|
if (status == -1)
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::send");
|
errormessage(getLastError(), "Socket::send");
|
||||||
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
|
kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data");
|
||||||
close();
|
close();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -210,31 +211,31 @@ int Socket::send ( const char* data, const unsigned int len )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffer)
|
int Socket::sendto(const char* data, unsigned int size, bool sendcompletebuffer)
|
||||||
{
|
{
|
||||||
int sentbytes = 0;
|
int sentbytes = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
i = ::sendto(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _sockaddr ) );
|
i = ::sendto(m_sd, data, size, 0, (const struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr));
|
||||||
|
|
||||||
if (i <= 0)
|
if (i <= 0)
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::sendto");
|
errormessage(getLastError(), "Socket::sendto");
|
||||||
osCleanup();
|
osCleanup();
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
sentbytes += i;
|
sentbytes += i;
|
||||||
} while ( (sentbytes < (int) size) && (sendcompletebuffer == true));
|
} while ((sentbytes < (int)size) && (sendcompletebuffer == true));
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
|
int Socket::receive(std::string& data, unsigned int minpacketsize) const
|
||||||
{
|
{
|
||||||
char * buf = NULL;
|
char* buf = nullptr;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
|
||||||
if (!is_valid())
|
if (!is_valid())
|
||||||
@@ -242,10 +243,10 @@ int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = new char [ minpacketsize + 1 ];
|
buf = new char[minpacketsize + 1];
|
||||||
memset ( buf, 0, minpacketsize + 1 );
|
memset(buf, 0, minpacketsize + 1);
|
||||||
|
|
||||||
status = receive( buf, minpacketsize, minpacketsize );
|
status = receive(buf, minpacketsize, minpacketsize);
|
||||||
|
|
||||||
data = buf;
|
data = buf;
|
||||||
|
|
||||||
@@ -255,12 +256,12 @@ int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
|
|||||||
|
|
||||||
|
|
||||||
//Receive until error or \n
|
//Receive until error or \n
|
||||||
bool Socket::ReadLine (string& line)
|
bool Socket::ReadLine(string& line)
|
||||||
{
|
{
|
||||||
fd_set set_r, set_e;
|
fd_set set_r, set_e;
|
||||||
timeval timeout;
|
timeval timeout;
|
||||||
int retries = 6;
|
int retries = 6;
|
||||||
char buffer[2048];
|
char buffer[2048];
|
||||||
|
|
||||||
if (!is_valid())
|
if (!is_valid())
|
||||||
return false;
|
return false;
|
||||||
@@ -274,20 +275,20 @@ bool Socket::ReadLine (string& line)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout.tv_sec = RECEIVE_TIMEOUT;
|
timeout.tv_sec = RECEIVE_TIMEOUT;
|
||||||
timeout.tv_usec = 0;
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
// fill with new data
|
// fill with new data
|
||||||
FD_ZERO(&set_r);
|
FD_ZERO(&set_r);
|
||||||
FD_ZERO(&set_e);
|
FD_ZERO(&set_e);
|
||||||
FD_SET(_sd, &set_r);
|
FD_SET(m_sd, &set_r);
|
||||||
FD_SET(_sd, &set_e);
|
FD_SET(m_sd, &set_e);
|
||||||
int result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout);
|
int result = select(FD_SETSIZE, &set_r, nullptr, &set_e, &timeout);
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__);
|
kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__);
|
||||||
errormessage(getLastError(), __FUNCTION__);
|
errormessage(getLastError(), __func__);
|
||||||
close();
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -296,20 +297,24 @@ bool Socket::ReadLine (string& line)
|
|||||||
{
|
{
|
||||||
if (retries != 0)
|
if (retries != 0)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries);
|
kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__,
|
||||||
retries--;
|
retries);
|
||||||
|
retries--;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
}
|
||||||
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__);
|
else
|
||||||
return false;
|
{
|
||||||
|
kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.",
|
||||||
|
__func__);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = recv(_sd, buffer, sizeof(buffer) - 1, 0);
|
result = recv(m_sd, buffer, sizeof(buffer) - 1, 0);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__);
|
kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__);
|
||||||
errormessage(getLastError(), __FUNCTION__);
|
errormessage(getLastError(), __func__);
|
||||||
close();
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -322,39 +327,41 @@ bool Socket::ReadLine (string& line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::receive ( std::string& data) const
|
int Socket::receive(std::string& data) const
|
||||||
{
|
{
|
||||||
char buf[MAXRECV + 1];
|
char buf[MAXRECV + 1];
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
|
||||||
if ( !is_valid() )
|
if (!is_valid())
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset ( buf, 0, MAXRECV + 1 );
|
memset(buf, 0, MAXRECV + 1);
|
||||||
status = receive( buf, MAXRECV, 0 );
|
status = receive(buf, MAXRECV, 0);
|
||||||
data = buf;
|
data = buf;
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Socket::receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const
|
int Socket::receive(char* data,
|
||||||
|
const unsigned int buffersize,
|
||||||
|
const unsigned int minpacketsize) const
|
||||||
{
|
{
|
||||||
unsigned int receivedsize = 0;
|
unsigned int receivedsize = 0;
|
||||||
|
|
||||||
if ( !is_valid() )
|
if (!is_valid())
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ( (receivedsize <= minpacketsize) && (receivedsize < buffersize) )
|
while ((receivedsize <= minpacketsize) && (receivedsize < buffersize))
|
||||||
{
|
{
|
||||||
int status = ::recv(_sd, data+receivedsize, (buffersize - receivedsize), 0 );
|
int status = ::recv(m_sd, data + receivedsize, (buffersize - receivedsize), 0);
|
||||||
|
|
||||||
if ( status == SOCKET_ERROR )
|
if (status == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
errormessage( getLastError(), "Socket::receive" );
|
errormessage(getLastError(), "Socket::receive");
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,35 +372,38 @@ int Socket::receive ( char* data, const unsigned int buffersize, const unsigned
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Socket::recvfrom ( char* data, const int buffersize, struct sockaddr* from, socklen_t* fromlen) const
|
int Socket::recvfrom(char* data,
|
||||||
|
const int buffersize,
|
||||||
|
struct sockaddr* from,
|
||||||
|
socklen_t* fromlen) const
|
||||||
{
|
{
|
||||||
int status = ::recvfrom(_sd, data, buffersize, 0, from, fromlen);
|
int status = ::recvfrom(m_sd, data, buffersize, 0, from, fromlen);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Socket::connect ( const std::string& host, const unsigned short port )
|
bool Socket::connect(const std::string& host, const unsigned short port)
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
|
||||||
if ( !setHostname( host ) )
|
if (!setHostname(host))
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
|
kodi::Log(ADDON_LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_port = port;
|
m_port = port;
|
||||||
|
|
||||||
char strPort[15];
|
char strPort[15];
|
||||||
snprintf(strPort, 15, "%hu", port);
|
snprintf(strPort, 15, "%hu", port);
|
||||||
|
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
struct addrinfo* result = NULL;
|
struct addrinfo* result = nullptr;
|
||||||
struct addrinfo *address = NULL;
|
struct addrinfo* address = nullptr;
|
||||||
memset(&hints, 0, sizeof(hints));
|
memset(&hints, 0, sizeof(hints));
|
||||||
hints.ai_family = _family;
|
hints.ai_family = m_family;
|
||||||
hints.ai_socktype = _type;
|
hints.ai_socktype = m_type;
|
||||||
hints.ai_protocol = _protocol;
|
hints.ai_protocol = m_protocol;
|
||||||
|
|
||||||
int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
|
int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
|
||||||
if (retval != 0)
|
if (retval != 0)
|
||||||
@@ -402,18 +412,18 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (address = result; address != NULL; address = address->ai_next)
|
for (address = result; address != nullptr; address = address->ai_next)
|
||||||
{
|
{
|
||||||
// Create the socket
|
// Create the socket
|
||||||
_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
m_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||||
|
|
||||||
if (_sd == INVALID_SOCKET)
|
if (m_sd == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
errormessage(getLastError(), "Socket::create");
|
errormessage(getLastError(), "Socket::create");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = ::connect(_sd, address->ai_addr, address->ai_addrlen);
|
int status = ::connect(m_sd, address->ai_addr, address->ai_addrlen);
|
||||||
if (status == SOCKET_ERROR)
|
if (status == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
@@ -426,9 +436,9 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
|||||||
|
|
||||||
freeaddrinfo(result);
|
freeaddrinfo(result);
|
||||||
|
|
||||||
if (address == NULL)
|
if (address == nullptr)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
|
kodi::Log(ADDON_LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
|
||||||
errormessage(getLastError(), "Socket::connect");
|
errormessage(getLastError(), "Socket::connect");
|
||||||
close();
|
close();
|
||||||
return false;
|
return false;
|
||||||
@@ -439,123 +449,124 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
|||||||
|
|
||||||
bool Socket::reconnect()
|
bool Socket::reconnect()
|
||||||
{
|
{
|
||||||
if ( is_valid() )
|
if (is_valid())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return connect(_hostname, _port);
|
return connect(m_hostname, m_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::is_valid() const
|
bool Socket::is_valid() const
|
||||||
{
|
{
|
||||||
return (_sd != INVALID_SOCKET);
|
return (m_sd != INVALID_SOCKET);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(TARGET_WINDOWS)
|
#if defined(TARGET_WINDOWS)
|
||||||
bool Socket::set_non_blocking ( const bool b )
|
bool Socket::set_non_blocking(const bool b)
|
||||||
{
|
{
|
||||||
u_long iMode;
|
u_long iMode;
|
||||||
|
|
||||||
if ( b )
|
if (b)
|
||||||
iMode = 1; // enable non_blocking
|
iMode = 1; // enable non_blocking
|
||||||
else
|
else
|
||||||
iMode = 0; // disable non_blocking
|
iMode = 0; // disable non_blocking
|
||||||
|
|
||||||
if (ioctlsocket(_sd, FIONBIO, &iMode) == -1)
|
if (ioctlsocket(m_sd, FIONBIO, &iMode) == -1)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", iMode);
|
kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i",
|
||||||
|
iMode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Socket::errormessage( int errnum, const char* functionname) const
|
void Socket::errormessage(int errnum, const char* functionname) const
|
||||||
{
|
{
|
||||||
const char* errmsg = NULL;
|
const char* errmsg = nullptr;
|
||||||
|
|
||||||
switch (errnum)
|
switch (errnum)
|
||||||
{
|
{
|
||||||
case WSANOTINITIALISED:
|
case WSANOTINITIALISED:
|
||||||
errmsg = "A successful WSAStartup call must occur before using this function.";
|
errmsg = "A successful WSAStartup call must occur before using this function.";
|
||||||
break;
|
break;
|
||||||
case WSAENETDOWN:
|
case WSAENETDOWN:
|
||||||
errmsg = "The network subsystem or the associated service provider has failed";
|
errmsg = "The network subsystem or the associated service provider has failed";
|
||||||
break;
|
break;
|
||||||
case WSA_NOT_ENOUGH_MEMORY:
|
case WSA_NOT_ENOUGH_MEMORY:
|
||||||
errmsg = "Insufficient memory available";
|
errmsg = "Insufficient memory available";
|
||||||
break;
|
break;
|
||||||
case WSA_INVALID_PARAMETER:
|
case WSA_INVALID_PARAMETER:
|
||||||
errmsg = "One or more parameters are invalid";
|
errmsg = "One or more parameters are invalid";
|
||||||
break;
|
break;
|
||||||
case WSA_OPERATION_ABORTED:
|
case WSA_OPERATION_ABORTED:
|
||||||
errmsg = "Overlapped operation aborted";
|
errmsg = "Overlapped operation aborted";
|
||||||
break;
|
break;
|
||||||
case WSAEINTR:
|
case WSAEINTR:
|
||||||
errmsg = "Interrupted function call";
|
errmsg = "Interrupted function call";
|
||||||
break;
|
break;
|
||||||
case WSAEBADF:
|
case WSAEBADF:
|
||||||
errmsg = "File handle is not valid";
|
errmsg = "File handle is not valid";
|
||||||
break;
|
break;
|
||||||
case WSAEACCES:
|
case WSAEACCES:
|
||||||
errmsg = "Permission denied";
|
errmsg = "Permission denied";
|
||||||
break;
|
break;
|
||||||
case WSAEFAULT:
|
case WSAEFAULT:
|
||||||
errmsg = "Bad address";
|
errmsg = "Bad address";
|
||||||
break;
|
break;
|
||||||
case WSAEINVAL:
|
case WSAEINVAL:
|
||||||
errmsg = "Invalid argument";
|
errmsg = "Invalid argument";
|
||||||
break;
|
break;
|
||||||
case WSAENOTSOCK:
|
case WSAENOTSOCK:
|
||||||
errmsg = "Socket operation on nonsocket";
|
errmsg = "Socket operation on nonsocket";
|
||||||
break;
|
break;
|
||||||
case WSAEDESTADDRREQ:
|
case WSAEDESTADDRREQ:
|
||||||
errmsg = "Destination address required";
|
errmsg = "Destination address required";
|
||||||
break;
|
break;
|
||||||
case WSAEMSGSIZE:
|
case WSAEMSGSIZE:
|
||||||
errmsg = "Message too long";
|
errmsg = "Message too long";
|
||||||
break;
|
break;
|
||||||
case WSAEPROTOTYPE:
|
case WSAEPROTOTYPE:
|
||||||
errmsg = "Protocol wrong type for socket";
|
errmsg = "Protocol wrong type for socket";
|
||||||
break;
|
break;
|
||||||
case WSAENOPROTOOPT:
|
case WSAENOPROTOOPT:
|
||||||
errmsg = "Bad protocol option";
|
errmsg = "Bad protocol option";
|
||||||
break;
|
break;
|
||||||
case WSAEPFNOSUPPORT:
|
case WSAEPFNOSUPPORT:
|
||||||
errmsg = "Protocol family not supported";
|
errmsg = "Protocol family not supported";
|
||||||
break;
|
break;
|
||||||
case WSAEAFNOSUPPORT:
|
case WSAEAFNOSUPPORT:
|
||||||
errmsg = "Address family not supported by protocol family";
|
errmsg = "Address family not supported by protocol family";
|
||||||
break;
|
break;
|
||||||
case WSAEADDRINUSE:
|
case WSAEADDRINUSE:
|
||||||
errmsg = "Address already in use";
|
errmsg = "Address already in use";
|
||||||
break;
|
break;
|
||||||
case WSAECONNRESET:
|
case WSAECONNRESET:
|
||||||
errmsg = "Connection reset by peer";
|
errmsg = "Connection reset by peer";
|
||||||
break;
|
break;
|
||||||
case WSAHOST_NOT_FOUND:
|
case WSAHOST_NOT_FOUND:
|
||||||
errmsg = "Authoritative answer host not found";
|
errmsg = "Authoritative answer host not found";
|
||||||
break;
|
break;
|
||||||
case WSATRY_AGAIN:
|
case WSATRY_AGAIN:
|
||||||
errmsg = "Nonauthoritative host not found, or server failure";
|
errmsg = "Nonauthoritative host not found, or server failure";
|
||||||
break;
|
break;
|
||||||
case WSAEISCONN:
|
case WSAEISCONN:
|
||||||
errmsg = "Socket is already connected";
|
errmsg = "Socket is already connected";
|
||||||
break;
|
break;
|
||||||
case WSAETIMEDOUT:
|
case WSAETIMEDOUT:
|
||||||
errmsg = "Connection timed out";
|
errmsg = "Connection timed out";
|
||||||
break;
|
break;
|
||||||
case WSAECONNREFUSED:
|
case WSAECONNREFUSED:
|
||||||
errmsg = "Connection refused";
|
errmsg = "Connection refused";
|
||||||
break;
|
break;
|
||||||
case WSANO_DATA:
|
case WSANO_DATA:
|
||||||
errmsg = "Valid name, no data record of requested type";
|
errmsg = "Valid name, no data record of requested type";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
errmsg = "WSA Error";
|
errmsg = "WSA Error";
|
||||||
}
|
}
|
||||||
libKodi->Log(LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
|
kodi::Log(ADDON_LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Socket::getLastError() const
|
int Socket::getLastError() const
|
||||||
@@ -569,15 +580,15 @@ bool Socket::osInit()
|
|||||||
{
|
{
|
||||||
win_usage_count++;
|
win_usage_count++;
|
||||||
// initialize winsock:
|
// initialize winsock:
|
||||||
if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0)
|
if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
WORD wVersionRequested = MAKEWORD(2,2);
|
WORD wVersionRequested = MAKEWORD(2, 2);
|
||||||
|
|
||||||
// check version
|
// check version
|
||||||
if (_wsaData.wVersion != wVersionRequested)
|
if (m_wsaData.wVersion != wVersionRequested)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -588,42 +599,42 @@ bool Socket::osInit()
|
|||||||
void Socket::osCleanup()
|
void Socket::osCleanup()
|
||||||
{
|
{
|
||||||
win_usage_count--;
|
win_usage_count--;
|
||||||
if(win_usage_count == 0)
|
if (win_usage_count == 0)
|
||||||
{
|
{
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
||||||
bool Socket::set_non_blocking ( const bool b )
|
bool Socket::set_non_blocking(const bool b)
|
||||||
{
|
{
|
||||||
int opts;
|
int opts;
|
||||||
|
|
||||||
opts = fcntl(_sd, F_GETFL);
|
opts = fcntl(m_sd, F_GETFL);
|
||||||
|
|
||||||
if ( opts < 0 )
|
if (opts < 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( b )
|
if (b)
|
||||||
opts = ( opts | O_NONBLOCK );
|
opts = (opts | O_NONBLOCK);
|
||||||
else
|
else
|
||||||
opts = ( opts & ~O_NONBLOCK );
|
opts = (opts & ~O_NONBLOCK);
|
||||||
|
|
||||||
if(fcntl (_sd , F_SETFL, opts) == -1)
|
if (fcntl(m_sd, F_SETFL, opts) == -1)
|
||||||
{
|
{
|
||||||
libKodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
|
kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Socket::errormessage( int errnum, const char* functionname) const
|
void Socket::errormessage(int errnum, const char* functionname) const
|
||||||
{
|
{
|
||||||
const char* errmsg = NULL;
|
const char* errmsg = nullptr;
|
||||||
|
|
||||||
switch ( errnum )
|
switch (errnum)
|
||||||
{
|
{
|
||||||
case EAGAIN: //same as EWOULDBLOCK
|
case EAGAIN: //same as EWOULDBLOCK
|
||||||
errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block";
|
errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block";
|
||||||
@@ -650,7 +661,8 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
|||||||
errmsg = "ENOTSOCK: The argument is not a valid socket";
|
errmsg = "ENOTSOCK: The argument is not a valid socket";
|
||||||
break;
|
break;
|
||||||
case EMSGSIZE:
|
case EMSGSIZE:
|
||||||
errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the message to be sent made this impossible";
|
errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the "
|
||||||
|
"message to be sent made this impossible";
|
||||||
break;
|
break;
|
||||||
case ENOBUFS:
|
case ENOBUFS:
|
||||||
errmsg = "ENOBUFS: The output queue for a network interface was full";
|
errmsg = "ENOBUFS: The output queue for a network interface was full";
|
||||||
@@ -662,7 +674,8 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
|||||||
errmsg = "EPIPE: The local end has been shut down on a connection oriented socket";
|
errmsg = "EPIPE: The local end has been shut down on a connection oriented socket";
|
||||||
break;
|
break;
|
||||||
case EPROTONOSUPPORT:
|
case EPROTONOSUPPORT:
|
||||||
errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported within this domain";
|
errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported "
|
||||||
|
"within this domain";
|
||||||
break;
|
break;
|
||||||
case EAFNOSUPPORT:
|
case EAFNOSUPPORT:
|
||||||
errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family";
|
errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family";
|
||||||
@@ -674,13 +687,16 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
|||||||
errmsg = "EMFILE: Process file table overflow";
|
errmsg = "EMFILE: Process file table overflow";
|
||||||
break;
|
break;
|
||||||
case EACCES:
|
case EACCES:
|
||||||
errmsg = "EACCES: Permission to create a socket of the specified type and/or protocol is denied";
|
errmsg =
|
||||||
|
"EACCES: Permission to create a socket of the specified type and/or protocol is denied";
|
||||||
break;
|
break;
|
||||||
case ECONNREFUSED:
|
case ECONNREFUSED:
|
||||||
errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically because it is not running the requested service)";
|
errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically "
|
||||||
|
"because it is not running the requested service)";
|
||||||
break;
|
break;
|
||||||
case ENOTCONN:
|
case ENOTCONN:
|
||||||
errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not been connected";
|
errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not "
|
||||||
|
"been connected";
|
||||||
break;
|
break;
|
||||||
//case E:
|
//case E:
|
||||||
// errmsg = "";
|
// errmsg = "";
|
||||||
@@ -689,7 +705,7 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
libKodi->Log(LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
|
kodi::Log(ADDON_LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Socket::getLastError() const
|
int Socket::getLastError() const
|
||||||
|
|||||||
411
src/Socket.h
411
src/Socket.h
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2005-2020 Team Kodi
|
* Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
|
||||||
* https://kodi.tv
|
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
* See LICENSE.md for more information.
|
* See LICENSE.md for more information.
|
||||||
@@ -10,64 +9,64 @@
|
|||||||
|
|
||||||
//Include platform specific datatypes, header files, defines and constants:
|
//Include platform specific datatypes, header files, defines and constants:
|
||||||
#if defined TARGET_WINDOWS
|
#if defined TARGET_WINDOWS
|
||||||
#define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
|
#define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
|
||||||
#pragma warning(disable:4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
|
#pragma warning(disable : 4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
|
||||||
#include <winsock2.h>
|
#include <WS2tcpip.h>
|
||||||
#include <WS2tcpip.h>
|
#include <winsock2.h>
|
||||||
#pragma warning(default:4005)
|
#pragma warning(default : 4005)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#ifndef NI_MAXHOST
|
#ifndef NI_MAXHOST
|
||||||
#define NI_MAXHOST 1025
|
#define NI_MAXHOST 1025
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef socklen_t
|
#ifndef socklen_t
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
#endif
|
#endif
|
||||||
#ifndef ipaddr_t
|
#ifndef ipaddr_t
|
||||||
typedef unsigned long ipaddr_t;
|
typedef unsigned long ipaddr_t;
|
||||||
#endif
|
#endif
|
||||||
#ifndef port_t
|
#ifndef port_t
|
||||||
typedef unsigned short port_t;
|
typedef unsigned short port_t;
|
||||||
#endif
|
#endif
|
||||||
#ifndef sa_family_t
|
#ifndef sa_family_t
|
||||||
#define sa_family_t ADDRESS_FAMILY
|
#define sa_family_t ADDRESS_FAMILY
|
||||||
#endif
|
#endif
|
||||||
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
||||||
#ifdef SOCKADDR_IN
|
#ifdef SOCKADDR_IN
|
||||||
#undef SOCKADDR_IN
|
#undef SOCKADDR_IN
|
||||||
#endif
|
#endif
|
||||||
#include <sys/types.h> /* for socket,connect */
|
#include <arpa/inet.h> /* for inet_pton */
|
||||||
#include <sys/socket.h> /* for socket,connect */
|
#include <errno.h>
|
||||||
#include <sys/un.h> /* for Unix socket */
|
#include <fcntl.h>
|
||||||
#include <arpa/inet.h> /* for inet_pton */
|
#include <netdb.h> /* for gethostbyname */
|
||||||
#include <netdb.h> /* for gethostbyname */
|
#include <netinet/in.h> /* for htons */
|
||||||
#include <netinet/in.h> /* for htons */
|
#include <sys/socket.h> /* for socket,connect */
|
||||||
#include <unistd.h> /* for read, write, close */
|
#include <sys/types.h> /* for socket,connect */
|
||||||
#include <errno.h>
|
#include <sys/un.h> /* for Unix socket */
|
||||||
#include <fcntl.h>
|
#include <unistd.h> /* for read, write, close */
|
||||||
|
|
||||||
typedef int SOCKET;
|
typedef int SOCKET;
|
||||||
typedef sockaddr SOCKADDR;
|
typedef sockaddr SOCKADDR;
|
||||||
typedef sockaddr_in SOCKADDR_IN;
|
typedef sockaddr_in SOCKADDR_IN;
|
||||||
#ifndef INVALID_SOCKET
|
#ifndef INVALID_SOCKET
|
||||||
#define INVALID_SOCKET (-1)
|
#define INVALID_SOCKET (-1)
|
||||||
#endif
|
#endif
|
||||||
#define SOCKET_ERROR (-1)
|
#define SOCKET_ERROR (-1)
|
||||||
|
|
||||||
#define closesocket(sd) ::close(sd)
|
#define closesocket(sd) ::close(sd)
|
||||||
#else
|
#else
|
||||||
#error Platform specific socket support is not yet available on this platform!
|
#error Platform specific socket support is not yet available on this platform!
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace OCTO
|
namespace OCTO
|
||||||
{
|
{
|
||||||
|
|
||||||
#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
|
#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
|
||||||
#define MAXRECV 1500 ///< Maximum packet size
|
#define MAXRECV 1500 ///< Maximum packet size
|
||||||
|
|
||||||
enum SocketFamily
|
enum SocketFamily
|
||||||
{
|
{
|
||||||
@@ -78,10 +77,10 @@ enum SocketFamily
|
|||||||
|
|
||||||
enum SocketDomain
|
enum SocketDomain
|
||||||
{
|
{
|
||||||
#if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
#if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
|
||||||
pf_unix = PF_UNIX,
|
pf_unix = PF_UNIX,
|
||||||
pf_local = PF_LOCAL,
|
pf_local = PF_LOCAL,
|
||||||
#endif
|
#endif
|
||||||
pf_inet = PF_INET
|
pf_inet = PF_INET
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,197 +98,187 @@ enum SocketProtocol
|
|||||||
|
|
||||||
class Socket
|
class Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/*!
|
||||||
|
* An unconnected socket may be created directly on the local
|
||||||
|
* machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and
|
||||||
|
* protocol may also be specified.
|
||||||
|
* If the socket cannot be created, an exception is thrown.
|
||||||
|
*
|
||||||
|
* \param family Socket family (IPv4 or IPv6)
|
||||||
|
* \param domain The domain parameter specifies a communications domain within which communication will take place;
|
||||||
|
* this selects the protocol family which should be used.
|
||||||
|
* \param type base type and protocol family of the socket.
|
||||||
|
* \param protocol specific protocol to apply.
|
||||||
|
*/
|
||||||
|
Socket(const enum SocketFamily family,
|
||||||
|
const enum SocketDomain domain,
|
||||||
|
const enum SocketType type,
|
||||||
|
const enum SocketProtocol protocol = tcp);
|
||||||
|
Socket(void);
|
||||||
|
virtual ~Socket();
|
||||||
|
|
||||||
/*!
|
//Socket settings
|
||||||
* An unconnected socket may be created directly on the local
|
|
||||||
* machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and
|
|
||||||
* protocol may also be specified.
|
|
||||||
* If the socket cannot be created, an exception is thrown.
|
|
||||||
*
|
|
||||||
* \param family Socket family (IPv4 or IPv6)
|
|
||||||
* \param domain The domain parameter specifies a communications domain within which communication will take place;
|
|
||||||
* this selects the protocol family which should be used.
|
|
||||||
* \param type base type and protocol family of the socket.
|
|
||||||
* \param protocol specific protocol to apply.
|
|
||||||
*/
|
|
||||||
Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol = tcp);
|
|
||||||
Socket(void);
|
|
||||||
virtual ~Socket();
|
|
||||||
|
|
||||||
//Socket settings
|
/*!
|
||||||
|
* Socket setFamily
|
||||||
|
* \param family Can be af_inet or af_inet6. Default: af_inet
|
||||||
|
*/
|
||||||
|
void setFamily(const enum SocketFamily family) { m_family = family; };
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket setFamily
|
* Socket setDomain
|
||||||
* \param family Can be af_inet or af_inet6. Default: af_inet
|
* \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
|
||||||
*/
|
*/
|
||||||
void setFamily(const enum SocketFamily family)
|
void setDomain(const enum SocketDomain domain) { m_domain = domain; };
|
||||||
{
|
|
||||||
_family = family;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket setDomain
|
* Socket setType
|
||||||
* \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
|
* \param type Can be sock_stream or sock_dgram. Default: sock_stream.
|
||||||
*/
|
*/
|
||||||
void setDomain(const enum SocketDomain domain)
|
void setType(const enum SocketType type) { m_type = type; };
|
||||||
{
|
|
||||||
_domain = domain;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket setType
|
* Socket setProtocol
|
||||||
* \param type Can be sock_stream or sock_dgram. Default: sock_stream.
|
* \param protocol Can be tcp or udp. Default: tcp.
|
||||||
*/
|
*/
|
||||||
void setType(const enum SocketType type)
|
void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; };
|
||||||
{
|
|
||||||
_type = type;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket setProtocol
|
* Socket setPort
|
||||||
* \param protocol Can be tcp or udp. Default: tcp.
|
* \param port port number for socket communication
|
||||||
*/
|
*/
|
||||||
void setProtocol(const enum SocketProtocol protocol)
|
void setPort(const unsigned short port) { m_sockaddr.sin_port = htons(port); };
|
||||||
{
|
|
||||||
_protocol = protocol;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
bool setHostname(const std::string& host);
|
||||||
* Socket setPort
|
|
||||||
* \param port port number for socket communication
|
|
||||||
*/
|
|
||||||
void setPort (const unsigned short port)
|
|
||||||
{
|
|
||||||
_sockaddr.sin_port = htons ( port );
|
|
||||||
};
|
|
||||||
|
|
||||||
bool setHostname ( const std::string& host );
|
// Server initialization
|
||||||
|
|
||||||
// Server initialization
|
/*!
|
||||||
|
* Socket create
|
||||||
|
* Create a new socket
|
||||||
|
* \return True if succesful
|
||||||
|
*/
|
||||||
|
bool create();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket create
|
* Socket close
|
||||||
* Create a new socket
|
* Close the socket
|
||||||
* \return True if succesful
|
* \return True if succesful
|
||||||
*/
|
*/
|
||||||
bool create();
|
bool close();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket close
|
* Socket bind
|
||||||
* Close the socket
|
*/
|
||||||
* \return True if succesful
|
bool bind(const unsigned short port);
|
||||||
*/
|
bool listen() const;
|
||||||
bool close();
|
bool accept(Socket& socket) const;
|
||||||
|
|
||||||
/*!
|
// Client initialization
|
||||||
* Socket bind
|
bool connect(const std::string& host, const unsigned short port);
|
||||||
*/
|
|
||||||
bool bind ( const unsigned short port );
|
|
||||||
bool listen() const;
|
|
||||||
bool accept ( Socket& socket ) const;
|
|
||||||
|
|
||||||
// Client initialization
|
bool reconnect();
|
||||||
bool connect ( const std::string& host, const unsigned short port );
|
|
||||||
|
|
||||||
bool reconnect();
|
// Data Transmission
|
||||||
|
|
||||||
// Data Transmission
|
/*!
|
||||||
|
* Socket send function
|
||||||
|
*
|
||||||
|
* \param data Reference to a std::string with the data to transmit
|
||||||
|
* \return Number of bytes send or -1 in case of an error
|
||||||
|
*/
|
||||||
|
int send(const std::string& data);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket send function
|
* Socket send function
|
||||||
*
|
*
|
||||||
* \param data Reference to a std::string with the data to transmit
|
* \param data Pointer to a character array of size 'size' with the data to transmit
|
||||||
* \return Number of bytes send or -1 in case of an error
|
* \param size Length of the data to transmit
|
||||||
*/
|
* \return Number of bytes send or -1 in case of an error
|
||||||
int send ( const std::string& data );
|
*/
|
||||||
|
int send(const char* data, const unsigned int size);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket send function
|
* Socket sendto function
|
||||||
*
|
*
|
||||||
* \param data Pointer to a character array of size 'size' with the data to transmit
|
* \param data Reference to a std::string with the data to transmit
|
||||||
* \param size Length of the data to transmit
|
* \param size Length of the data to transmit
|
||||||
* \return Number of bytes send or -1 in case of an error
|
* \param sendcompletebuffer If 'true': do not return until the complete buffer is transmitted
|
||||||
*/
|
* \return Number of bytes send or -1 in case of an error
|
||||||
int send ( const char* data, const unsigned int size );
|
*/
|
||||||
|
int sendto(const char* data, unsigned int size, bool sendcompletebuffer = false);
|
||||||
|
// Data Receive
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket sendto function
|
* Socket receive function
|
||||||
*
|
*
|
||||||
* \param data Reference to a std::string with the data to transmit
|
* \param data Reference to a std::string for storage of the received data.
|
||||||
* \param size Length of the data to transmit
|
* \param minpacketsize The minimum number of bytes that should be received before returning from this function
|
||||||
* \param sendcompletebuffer If 'true': do not return until the complete buffer is transmitted
|
* \return Number of bytes received or SOCKET_ERROR
|
||||||
* \return Number of bytes send or -1 in case of an error
|
*/
|
||||||
*/
|
int receive(std::string& data, unsigned int minpacketsize) const;
|
||||||
int sendto ( const char* data, unsigned int size, bool sendcompletebuffer = false);
|
|
||||||
// Data Receive
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket receive function
|
* Socket receive function
|
||||||
*
|
*
|
||||||
* \param data Reference to a std::string for storage of the received data.
|
* \param data Reference to a std::string for storage of the received data.
|
||||||
* \param minpacketsize The minimum number of bytes that should be received before returning from this function
|
* \return Number of bytes received or SOCKET_ERROR
|
||||||
* \return Number of bytes received or SOCKET_ERROR
|
*/
|
||||||
*/
|
int receive(std::string& data) const;
|
||||||
int receive ( std::string& data, unsigned int minpacketsize ) const;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket receive function
|
* Socket receive function
|
||||||
*
|
*
|
||||||
* \param data Reference to a std::string for storage of the received data.
|
* \param data Pointer to a character array of size buffersize. Used to store the received data.
|
||||||
* \return Number of bytes received or SOCKET_ERROR
|
* \param buffersize Size of the 'data' buffer
|
||||||
*/
|
* \param minpacketsize Specifies the minimum number of bytes that need to be received before returning
|
||||||
int receive ( std::string& data ) const;
|
* \return Number of bytes received or SOCKET_ERROR
|
||||||
|
*/
|
||||||
|
int receive(char* data, const unsigned int buffersize, const unsigned int minpacketsize) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Socket receive function
|
* Socket recvfrom function
|
||||||
*
|
*
|
||||||
* \param data Pointer to a character array of size buffersize. Used to store the received data.
|
* \param data Pointer to a character array of size buffersize. Used to store the received data.
|
||||||
* \param buffersize Size of the 'data' buffer
|
* \param buffersize Size of the 'data' buffer
|
||||||
* \param minpacketsize Specifies the minimum number of bytes that need to be received before returning
|
* \param from Optional: pointer to a sockaddr struct that will get the address from which the data is received
|
||||||
* \return Number of bytes received or SOCKET_ERROR
|
* \param fromlen Optional, only required if 'from' is given: length of from struct
|
||||||
*/
|
* \return Number of bytes received or SOCKET_ERROR
|
||||||
int receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const;
|
*/
|
||||||
|
int recvfrom(char* data,
|
||||||
|
const int buffersize,
|
||||||
|
struct sockaddr* from = nullptr,
|
||||||
|
socklen_t* fromlen = nullptr) const;
|
||||||
|
|
||||||
/*!
|
bool set_non_blocking(const bool);
|
||||||
* Socket recvfrom function
|
|
||||||
*
|
|
||||||
* \param data Pointer to a character array of size buffersize. Used to store the received data.
|
|
||||||
* \param buffersize Size of the 'data' buffer
|
|
||||||
* \param from Optional: pointer to a sockaddr struct that will get the address from which the data is received
|
|
||||||
* \param fromlen Optional, only required if 'from' is given: length of from struct
|
|
||||||
* \return Number of bytes received or SOCKET_ERROR
|
|
||||||
*/
|
|
||||||
int recvfrom ( char* data, const int buffersize, struct sockaddr* from = NULL, socklen_t* fromlen = NULL) const;
|
|
||||||
|
|
||||||
bool set_non_blocking ( const bool );
|
bool ReadLine(std::string& line);
|
||||||
|
|
||||||
bool ReadLine (std::string& line);
|
bool is_valid() const;
|
||||||
|
|
||||||
bool is_valid() const;
|
private:
|
||||||
|
SOCKET m_sd; ///< Socket Descriptor
|
||||||
|
SOCKADDR_IN m_sockaddr; ///< Socket Address
|
||||||
|
//struct addrinfo* m_addrinfo; ///< Socket address info
|
||||||
|
std::string m_hostname; ///< Hostname
|
||||||
|
unsigned short m_port; ///< Port number
|
||||||
|
|
||||||
private:
|
enum SocketFamily m_family; ///< Socket Address Family
|
||||||
|
enum SocketProtocol m_protocol; ///< Socket Protocol
|
||||||
|
enum SocketType m_type; ///< Socket Type
|
||||||
|
enum SocketDomain m_domain; ///< Socket domain
|
||||||
|
|
||||||
SOCKET _sd; ///< Socket Descriptor
|
#ifdef TARGET_WINDOWS
|
||||||
SOCKADDR_IN _sockaddr; ///< Socket Address
|
WSADATA m_wsaData; ///< Windows Socket data
|
||||||
//struct addrinfo* _addrinfo; ///< Socket address info
|
static int
|
||||||
std::string _hostname; ///< Hostname
|
win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used
|
||||||
unsigned short _port; ///< Port number
|
#endif
|
||||||
|
|
||||||
enum SocketFamily _family; ///< Socket Address Family
|
void errormessage(int errornum, const char* functionname = nullptr) const;
|
||||||
enum SocketProtocol _protocol; ///< Socket Protocol
|
int getLastError(void) const;
|
||||||
enum SocketType _type; ///< Socket Type
|
bool osInit();
|
||||||
enum SocketDomain _domain; ///< Socket domain
|
void osCleanup();
|
||||||
|
|
||||||
#ifdef TARGET_WINDOWS
|
|
||||||
WSADATA _wsaData; ///< Windows Socket data
|
|
||||||
static int win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void errormessage( int errornum, const char* functionname = NULL) const;
|
|
||||||
int getLastError(void) const;
|
|
||||||
bool osInit();
|
|
||||||
void osCleanup();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} //namespace OCTO
|
} //namespace OCTO
|
||||||
|
|||||||
58
src/addon.cpp
Normal file
58
src/addon.cpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||||
|
* Copyright (C) 2015 jusst technologies GmbH
|
||||||
|
* Copyright (C) 2015 Digital Devices GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* See LICENSE.md for more information.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "addon.h"
|
||||||
|
|
||||||
|
#include "OctonetData.h"
|
||||||
|
|
||||||
|
ADDON_STATUS COctonetAddon::SetSetting(const std::string& settingName,
|
||||||
|
const kodi::addon::CSettingValue& settingValue)
|
||||||
|
{
|
||||||
|
/* For simplicity do a full addon restart whenever settings are
|
||||||
|
* changed */
|
||||||
|
return ADDON_STATUS_NEED_RESTART;
|
||||||
|
}
|
||||||
|
|
||||||
|
ADDON_STATUS COctonetAddon::CreateInstance(const kodi::addon::IInstanceInfo& instance,
|
||||||
|
KODI_ADDON_INSTANCE_HDL& hdl)
|
||||||
|
{
|
||||||
|
if (instance.IsType(ADDON_INSTANCE_PVR))
|
||||||
|
{
|
||||||
|
kodi::Log(ADDON_LOG_DEBUG, "%s: Creating octonet pvr instance", __func__);
|
||||||
|
|
||||||
|
/* IP or hostname of the octonet to be connected to */
|
||||||
|
std::string octonetAddress = kodi::addon::GetSettingString("octonetAddress");
|
||||||
|
|
||||||
|
OctonetData* usedInstance = new OctonetData(octonetAddress, instance);
|
||||||
|
hdl = usedInstance;
|
||||||
|
|
||||||
|
m_usedInstances.emplace(instance.GetID(), usedInstance);
|
||||||
|
return ADDON_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ADDON_STATUS_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void COctonetAddon::DestroyInstance(const kodi::addon::IInstanceInfo& instance,
|
||||||
|
const KODI_ADDON_INSTANCE_HDL hdl)
|
||||||
|
{
|
||||||
|
if (instance.IsType(ADDON_INSTANCE_PVR))
|
||||||
|
{
|
||||||
|
kodi::Log(ADDON_LOG_DEBUG, "%s: Destoying octonet pvr instance", __func__);
|
||||||
|
|
||||||
|
const auto& it = m_usedInstances.find(instance.GetID());
|
||||||
|
if (it != m_usedInstances.end())
|
||||||
|
{
|
||||||
|
m_usedInstances.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ADDONCREATOR(COctonetAddon)
|
||||||
32
src/addon.h
Normal file
32
src/addon.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||||
|
* Copyright (C) 2015 jusst technologies GmbH
|
||||||
|
* Copyright (C) 2015 Digital Devices GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* See LICENSE.md for more information.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <kodi/AddonBase.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class OctonetData;
|
||||||
|
|
||||||
|
class ATTR_DLL_LOCAL COctonetAddon : public kodi::addon::CAddonBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
COctonetAddon() = default;
|
||||||
|
|
||||||
|
ADDON_STATUS SetSetting(const std::string& settingName,
|
||||||
|
const kodi::addon::CSettingValue& settingValue) override;
|
||||||
|
ADDON_STATUS CreateInstance(const kodi::addon::IInstanceInfo& instance,
|
||||||
|
KODI_ADDON_INSTANCE_HDL& hdl) override;
|
||||||
|
void DestroyInstance(const kodi::addon::IInstanceInfo& instance,
|
||||||
|
const KODI_ADDON_INSTANCE_HDL hdl) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, OctonetData*> m_usedInstances;
|
||||||
|
};
|
||||||
274
src/client.cpp
274
src/client.cpp
@@ -1,274 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
|
||||||
* Copyright (C) 2015 jusst technologies GmbH
|
|
||||||
* Copyright (C) 2015 Digital Devices GmbH
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
* See LICENSE.md for more information.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "client.h"
|
|
||||||
#include <kodi/xbmc_pvr_dll.h>
|
|
||||||
#include <kodi/libXBMC_addon.h>
|
|
||||||
#include <p8-platform/util/util.h>
|
|
||||||
|
|
||||||
#include "OctonetData.h"
|
|
||||||
#include "rtsp_client.hpp"
|
|
||||||
|
|
||||||
using namespace ADDON;
|
|
||||||
|
|
||||||
/* setting variables with defaults */
|
|
||||||
std::string octonetAddress = "";
|
|
||||||
|
|
||||||
/* internal state variables */
|
|
||||||
ADDON_STATUS addonStatus = ADDON_STATUS_UNKNOWN;
|
|
||||||
CHelper_libXBMC_addon *libKodi = NULL;
|
|
||||||
CHelper_libXBMC_pvr *pvr = NULL;
|
|
||||||
|
|
||||||
OctonetData *data = NULL;
|
|
||||||
|
|
||||||
/* KODI Core Addon functions
|
|
||||||
* see xbmc_addon_dll.h */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
void ADDON_ReadSettings(void)
|
|
||||||
{
|
|
||||||
char buffer[2048];
|
|
||||||
if (libKodi->GetSetting("octonetAddress", &buffer))
|
|
||||||
octonetAddress = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDON_STATUS ADDON_Create(void* callbacks, const char* globalApiVersion, void* props)
|
|
||||||
{
|
|
||||||
if (callbacks == NULL || props == NULL)
|
|
||||||
return ADDON_STATUS_UNKNOWN;
|
|
||||||
|
|
||||||
AddonProperties_PVR *pvrprops = (AddonProperties_PVR*)props;
|
|
||||||
libKodi = new CHelper_libXBMC_addon;
|
|
||||||
if (!libKodi->RegisterMe(callbacks)) {
|
|
||||||
libKodi->Log(LOG_ERROR, "%s: Failed to register octonet addon", __func__);
|
|
||||||
SAFE_DELETE(libKodi);
|
|
||||||
return ADDON_STATUS_PERMANENT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
pvr = new CHelper_libXBMC_pvr;
|
|
||||||
if (!pvr->RegisterMe(callbacks)) {
|
|
||||||
libKodi->Log(LOG_ERROR, "%s: Failed to register octonet pvr addon", __func__);
|
|
||||||
SAFE_DELETE(pvr);
|
|
||||||
SAFE_DELETE(libKodi);
|
|
||||||
return ADDON_STATUS_PERMANENT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
libKodi->Log(LOG_DEBUG, "%s: Creating octonet pvr addon", __func__);
|
|
||||||
ADDON_ReadSettings();
|
|
||||||
|
|
||||||
data = new OctonetData;
|
|
||||||
|
|
||||||
addonStatus = ADDON_STATUS_OK;
|
|
||||||
return addonStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ADDON_Destroy()
|
|
||||||
{
|
|
||||||
delete pvr;
|
|
||||||
delete libKodi;
|
|
||||||
addonStatus = ADDON_STATUS_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDON_STATUS ADDON_GetStatus()
|
|
||||||
{
|
|
||||||
return addonStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
ADDON_STATUS ADDON_SetSetting(const char *settingName, const void *settingValue)
|
|
||||||
{
|
|
||||||
/* For simplicity do a full addon restart whenever settings are
|
|
||||||
* changed */
|
|
||||||
return ADDON_STATUS_NEED_RESTART;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* KODI PVR Addon functions
|
|
||||||
* see xbmc_pvr_dll.h */
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
|
|
||||||
PVR_ERROR GetCapabilities(PVR_ADDON_CAPABILITIES *pCapabilities)
|
|
||||||
{
|
|
||||||
pCapabilities->bSupportsTV = true;
|
|
||||||
pCapabilities->bSupportsRadio = true;
|
|
||||||
pCapabilities->bSupportsChannelGroups = true;
|
|
||||||
pCapabilities->bSupportsEPG = true;
|
|
||||||
pCapabilities->bSupportsRecordings = false;
|
|
||||||
pCapabilities->bSupportsRecordingsRename = false;
|
|
||||||
pCapabilities->bSupportsRecordingsLifetimeChange = false;
|
|
||||||
pCapabilities->bSupportsDescrambleInfo = false;
|
|
||||||
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetBackendName(void)
|
|
||||||
{
|
|
||||||
return "Digital Devices Octopus NET Client";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetBackendVersion(void)
|
|
||||||
{
|
|
||||||
return STR(OCTONET_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetConnectionString(void)
|
|
||||||
{
|
|
||||||
return "connected"; // FIXME: translate?
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR GetDriveSpace(long long* iTotal, long long* iUsed) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR CallMenuHook(const PVR_MENUHOOK& menuhook, const PVR_MENUHOOK_DATA &item) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
void OnSystemSleep() {
|
|
||||||
libKodi->Log(LOG_INFO, "Received event: %s", __FUNCTION__);
|
|
||||||
// FIXME: Disconnect?
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnSystemWake() {
|
|
||||||
libKodi->Log(LOG_INFO, "Received event: %s", __FUNCTION__);
|
|
||||||
// FIXME:Reconnect?
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPowerSavingActivated() {}
|
|
||||||
void OnPowerSavingDeactivated() {}
|
|
||||||
|
|
||||||
/* EPG */
|
|
||||||
PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t iStart, time_t iEnd)
|
|
||||||
{
|
|
||||||
return data->getEPG(handle, iChannelUid, iStart, iEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR IsEPGTagRecordable(const EPG_TAG*, bool*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR IsEPGTagPlayable(const EPG_TAG*, bool*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
/* Channel groups */
|
|
||||||
int GetChannelGroupsAmount(void)
|
|
||||||
{
|
|
||||||
return data->getGroupCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR GetChannelGroups(ADDON_HANDLE handle, bool bRadio)
|
|
||||||
{
|
|
||||||
return data->getGroups(handle, bRadio);
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group)
|
|
||||||
{
|
|
||||||
return data->getGroupMembers(handle, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Channels */
|
|
||||||
PVR_ERROR OpenDialogChannelScan(void) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
int GetChannelsAmount(void)
|
|
||||||
{
|
|
||||||
return data->getChannelCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR GetChannels(ADDON_HANDLE handle, bool bRadio)
|
|
||||||
{
|
|
||||||
return data->getChannels(handle, bRadio);
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR DeleteChannel(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR RenameChannel(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR OpenDialogChannelSettings(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR OpenDialogChannelAdd(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
/* Recordings */
|
|
||||||
int GetRecordingsAmount(bool deleted) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetRecordings(ADDON_HANDLE handle, bool deleted) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR DeleteRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR UndeleteRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR DeleteAllRecordingsFromTrash() { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR RenameRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR SetRecordingLifetime(const PVR_RECORDING*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR SetRecordingPlayCount(const PVR_RECORDING& recording, int count) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR SetRecordingLastPlayedPosition(const PVR_RECORDING& recording, int lastplayedposition) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
int GetRecordingLastPlayedPosition(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetRecordingEdl(const PVR_RECORDING&, PVR_EDL_ENTRY edl[], int *size) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetRecordingSize(const PVR_RECORDING* recording, int64_t* sizeInBytes) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetTimerTypes(PVR_TIMER_TYPE types[], int *size) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
int GetTimersAmount(void) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetTimers(ADDON_HANDLE handle) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR AddTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR DeleteTimer(const PVR_TIMER& timer, bool bForceDelete) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR UpdateTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
/* PVR stream properties handling */
|
|
||||||
PVR_ERROR GetStreamReadChunkSize(int* chunksize) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetChannelStreamProperties(const PVR_CHANNEL*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetRecordingStreamProperties(const PVR_RECORDING*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetEPGTagStreamProperties(const EPG_TAG*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetEPGTagEdl(const EPG_TAG* epgTag, PVR_EDL_ENTRY edl[], int *size) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
/* PVR stream handling */
|
|
||||||
/* entirely unused, as we use standard RTSP+TS mux, which can be handlded by
|
|
||||||
* Kodi core */
|
|
||||||
bool OpenLiveStream(const PVR_CHANNEL& channel) {
|
|
||||||
return rtsp_open(data->getName(channel.iUniqueId), data->getUrl(channel.iUniqueId));
|
|
||||||
}
|
|
||||||
|
|
||||||
int ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) {
|
|
||||||
return rtsp_read(pBuffer, iBufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseLiveStream(void) {
|
|
||||||
rtsp_close();
|
|
||||||
}
|
|
||||||
|
|
||||||
long long SeekLiveStream(long long iPosition, int iWhence) { return -1; }
|
|
||||||
long long LengthLiveStream(void) { return -1; }
|
|
||||||
bool IsRealTimeStream(void) { return true; }
|
|
||||||
|
|
||||||
PVR_ERROR GetSignalStatus(int channelUid, PVR_SIGNAL_STATUS* signalStatus) {
|
|
||||||
memset(signalStatus, 0, sizeof(PVR_SIGNAL_STATUS));
|
|
||||||
rtsp_fill_signal_status(signalStatus);
|
|
||||||
return PVR_ERROR_NO_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
PVR_ERROR GetStreamTimes(PVR_STREAM_TIMES *times) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
PVR_ERROR GetDescrambleInfo(int, PVR_DESCRAMBLE_INFO*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
/* Recording stream handling */
|
|
||||||
bool OpenRecordedStream(const PVR_RECORDING& recording) { return false; }
|
|
||||||
void CloseRecordedStream(void) {}
|
|
||||||
int ReadRecordedStream(unsigned char* pBuffer, unsigned int iBufferSize) { return -1; }
|
|
||||||
long long SeekRecordedStream(long long iPosition, int iWhence) { return -1; }
|
|
||||||
long long LengthRecordedStream(void) { return -1; }
|
|
||||||
|
|
||||||
/* PVR demuxer */
|
|
||||||
/* entirey unused, as we use TS */
|
|
||||||
void DemuxReset(void) {}
|
|
||||||
void DemuxAbort(void) {}
|
|
||||||
void DemuxFlush(void) {}
|
|
||||||
DemuxPacket* DemuxRead(void) { return NULL; }
|
|
||||||
void FillBuffer(bool mode) {}
|
|
||||||
|
|
||||||
/* Various helper functions */
|
|
||||||
bool CanPauseStream() { return false; }
|
|
||||||
bool CanSeekStream() { return false; }
|
|
||||||
|
|
||||||
/* Callbacks */
|
|
||||||
void PauseStream(bool bPaused) {}
|
|
||||||
bool SeekTime(double time, bool backwards, double *startpts) { return false; }
|
|
||||||
void SetSpeed(int speed) {}
|
|
||||||
PVR_ERROR SetEPGTimeFrame(int) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
|
||||||
|
|
||||||
const char* GetBackendHostname()
|
|
||||||
{
|
|
||||||
return octonetAddress.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
24
src/client.h
24
src/client.h
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
|
||||||
* Copyright (C) 2015 jusst technologies GmbH
|
|
||||||
* Copyright (C) 2015 Digital Devices GmbH
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
* See LICENSE.md for more information.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "kodi/libXBMC_addon.h"
|
|
||||||
#include "kodi/libXBMC_pvr.h"
|
|
||||||
|
|
||||||
#ifndef __func__
|
|
||||||
#define __func__ __FUNCTION__
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern ADDON::CHelper_libXBMC_addon *libKodi;
|
|
||||||
extern CHelper_libXBMC_pvr *pvr;
|
|
||||||
|
|
||||||
/* IP or hostname of the octonet to be connected to */
|
|
||||||
extern std::string octonetAddress;
|
|
||||||
@@ -1,32 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* See LICENSE.md for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "rtsp_client.hpp"
|
#include "rtsp_client.hpp"
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <iterator>
|
|
||||||
#include "Socket.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include <p8-platform/util/util.h>
|
|
||||||
#include <kodi/libXBMC_addon.h>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
#define strtok_r strtok_s
|
#define strtok_r strtok_s
|
||||||
#define strncasecmp _strnicmp
|
#define strncasecmp _strnicmp
|
||||||
|
|
||||||
int vasprintf(char **sptr, char *fmt, va_list argv) {
|
int vasprintf(char** sptr, char* fmt, va_list argv)
|
||||||
int wanted = vsnprintf(*sptr = NULL, 0, fmt, argv);
|
{
|
||||||
if((wanted < 0) || ((*sptr = (char *)malloc(1 + wanted)) == NULL))
|
int wanted = vsnprintf(*sptr = nullptr, 0, fmt, argv);
|
||||||
return -1;
|
if ((wanted < 0) || ((*sptr = (char*)malloc(1 + wanted)) == nullptr))
|
||||||
return vsprintf(*sptr, fmt, argv);
|
return -1;
|
||||||
|
return vsprintf(*sptr, fmt, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
int asprintf(char **sptr, char *fmt, ...) {
|
int asprintf(char** sptr, char* fmt, ...)
|
||||||
int retval;
|
{
|
||||||
va_list argv;
|
int retval;
|
||||||
va_start(argv, fmt);
|
va_list argv;
|
||||||
retval = vasprintf(sptr, fmt, argv);
|
va_start(argv, fmt);
|
||||||
va_end(argv);
|
retval = vasprintf(sptr, fmt, argv);
|
||||||
return retval;
|
va_end(argv);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -40,441 +48,501 @@ int asprintf(char **sptr, char *fmt, ...) {
|
|||||||
#define RTCP_BUFFER_SIZE 1024
|
#define RTCP_BUFFER_SIZE 1024
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace ADDON;
|
|
||||||
using namespace OCTO;
|
using namespace OCTO;
|
||||||
|
|
||||||
enum rtsp_state {
|
enum rtsp_state
|
||||||
RTSP_IDLE,
|
{
|
||||||
RTSP_DESCRIBE,
|
RTSP_IDLE,
|
||||||
RTSP_SETUP,
|
RTSP_DESCRIBE,
|
||||||
RTSP_PLAY,
|
RTSP_SETUP,
|
||||||
RTSP_RUNNING
|
RTSP_PLAY,
|
||||||
|
RTSP_RUNNING
|
||||||
};
|
};
|
||||||
|
|
||||||
enum rtsp_result {
|
enum rtsp_result
|
||||||
RTSP_RESULT_OK = 200,
|
{
|
||||||
|
RTSP_RESULT_OK = 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rtsp_client {
|
struct rtsp_client
|
||||||
char *content_base;
|
{
|
||||||
char *control;
|
char* content_base;
|
||||||
char session_id[64];
|
char* control;
|
||||||
uint16_t stream_id;
|
char session_id[64];
|
||||||
int keepalive_interval;
|
uint16_t stream_id;
|
||||||
|
int keepalive_interval;
|
||||||
|
|
||||||
char udp_address[UDP_ADDRESS_LEN];
|
char udp_address[UDP_ADDRESS_LEN];
|
||||||
uint16_t udp_port;
|
uint16_t udp_port;
|
||||||
|
|
||||||
Socket tcp_sock;
|
Socket tcp_sock;
|
||||||
Socket udp_sock;
|
Socket udp_sock;
|
||||||
Socket rtcp_sock;
|
Socket rtcp_sock;
|
||||||
|
|
||||||
enum rtsp_state state;
|
enum rtsp_state state;
|
||||||
int cseq;
|
int cseq;
|
||||||
|
|
||||||
size_t fifo_size;
|
size_t fifo_size;
|
||||||
uint16_t last_seq_nr;
|
uint16_t last_seq_nr;
|
||||||
|
|
||||||
string name;
|
string name;
|
||||||
int level;
|
int level;
|
||||||
int quality;
|
int quality;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct url {
|
struct url
|
||||||
string protocol;
|
{
|
||||||
string host;
|
string protocol;
|
||||||
int port;
|
string host;
|
||||||
string path;
|
int port;
|
||||||
|
string path;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rtcp_app {
|
struct rtcp_app
|
||||||
uint8_t subtype;
|
{
|
||||||
uint8_t pt;
|
uint8_t subtype;
|
||||||
uint16_t len;
|
uint8_t pt;
|
||||||
uint32_t ssrc;
|
uint16_t len;
|
||||||
char name[4];
|
uint32_t ssrc;
|
||||||
uint16_t identifier;
|
char name[4];
|
||||||
uint16_t string_len;
|
uint16_t identifier;
|
||||||
|
uint16_t string_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
static rtsp_client *rtsp = NULL;
|
static rtsp_client* rtsp = nullptr;
|
||||||
|
|
||||||
static url parse_url(const std::string& str) {
|
static url parse_url(const std::string& str)
|
||||||
static const string prot_end = "://";
|
{
|
||||||
static const string host_end = "/";
|
static const string prot_end = "://";
|
||||||
url result;
|
static const string host_end = "/";
|
||||||
|
url result;
|
||||||
|
|
||||||
string::const_iterator begin = str.begin();
|
string::const_iterator begin = str.begin();
|
||||||
string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
|
string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
|
||||||
result.protocol.reserve(distance(begin, end));
|
result.protocol.reserve(distance(begin, end));
|
||||||
transform(begin, end, back_inserter(result.protocol), ::tolower);
|
transform(begin, end, back_inserter(result.protocol), ::tolower);
|
||||||
advance(end, prot_end.size());
|
advance(end, prot_end.size());
|
||||||
begin = end;
|
begin = end;
|
||||||
|
|
||||||
end = search(begin, str.end(), host_end.begin(), host_end.end());
|
end = search(begin, str.end(), host_end.begin(), host_end.end());
|
||||||
result.host.reserve(distance(begin, end));
|
result.host.reserve(distance(begin, end));
|
||||||
transform(begin, end, back_inserter(result.host), ::tolower);
|
transform(begin, end, back_inserter(result.host), ::tolower);
|
||||||
advance(end, host_end.size());
|
advance(end, host_end.size());
|
||||||
begin = end;
|
begin = end;
|
||||||
|
|
||||||
result.port = RTSP_DEFAULT_PORT;
|
result.port = RTSP_DEFAULT_PORT;
|
||||||
|
|
||||||
result.path.reserve(distance(begin, str.end()));
|
result.path.reserve(distance(begin, str.end()));
|
||||||
transform(begin, str.end(), back_inserter(result.path), ::tolower);
|
transform(begin, str.end(), back_inserter(result.path), ::tolower);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void split_string(const string& s, char delim, vector<string>& elems) {
|
void split_string(const string& s, char delim, vector<string>& elems)
|
||||||
stringstream ss;
|
{
|
||||||
ss.str(s);
|
stringstream ss;
|
||||||
|
ss.str(s);
|
||||||
|
|
||||||
string item;
|
string item;
|
||||||
while(getline(ss, item, delim)) {
|
while (getline(ss, item, delim))
|
||||||
elems.push_back(item);
|
{
|
||||||
}
|
elems.push_back(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tcp_sock_read_line(string &line) {
|
static int tcp_sock_read_line(string& line)
|
||||||
static string buf;
|
{
|
||||||
|
static string buf;
|
||||||
|
|
||||||
while(true) {
|
while (true)
|
||||||
string::size_type pos = buf.find("\r\n");
|
{
|
||||||
if(pos != string::npos) {
|
string::size_type pos = buf.find("\r\n");
|
||||||
line = buf.substr(0, pos);
|
if (pos != string::npos)
|
||||||
buf.erase(0, pos + 2);
|
{
|
||||||
return 0;
|
line = buf.substr(0, pos);
|
||||||
}
|
buf.erase(0, pos + 2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
char tmp_buf[2048];
|
char tmp_buf[2048];
|
||||||
int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
|
int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
|
||||||
if(size <= 0) {
|
if (size <= 0)
|
||||||
return 1;
|
{
|
||||||
}
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
buf.append(&tmp_buf[0], &tmp_buf[size]);
|
buf.append(&tmp_buf[0], &tmp_buf[size]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string compose_url(const url& u)
|
static string compose_url(const url& u)
|
||||||
{
|
{
|
||||||
stringstream res;
|
stringstream res;
|
||||||
res << u.protocol << "://" << u.host;
|
res << u.protocol << "://" << u.host;
|
||||||
if (u.port > 0)
|
if (u.port > 0)
|
||||||
res << ":" << u.port;
|
res << ":" << u.port;
|
||||||
res << "/" << u.path;
|
res << "/" << u.path;
|
||||||
|
|
||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parse_session(char *request_line, char *session, unsigned max, int *timeout) {
|
static void parse_session(char* request_line, char* session, unsigned max, int* timeout)
|
||||||
char *state;
|
|
||||||
char *tok;
|
|
||||||
|
|
||||||
tok = strtok_r(request_line, ";", &state);
|
|
||||||
if (tok == NULL)
|
|
||||||
return;
|
|
||||||
strncpy(session, tok, min(strlen(tok), (size_t)(max - 1)));
|
|
||||||
|
|
||||||
while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
|
|
||||||
if (strncmp(tok, "timeout=", 8) == 0) {
|
|
||||||
*timeout = atoi(tok + 8);
|
|
||||||
if (*timeout > 5)
|
|
||||||
*timeout -= KEEPALIVE_MARGIN;
|
|
||||||
else if (*timeout > 0)
|
|
||||||
*timeout = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_port(char *str, uint16_t *port)
|
|
||||||
{
|
{
|
||||||
int p = atoi(str);
|
char* state;
|
||||||
if (p < 0 || p > UINT16_MAX)
|
char* tok;
|
||||||
return -1;
|
|
||||||
|
|
||||||
*port = p;
|
tok = strtok_r(request_line, ";", &state);
|
||||||
|
if (tok == nullptr)
|
||||||
|
return;
|
||||||
|
strncpy(session, tok, min(strlen(tok), (size_t)(max - 1)));
|
||||||
|
|
||||||
return 0;
|
while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
|
||||||
|
{
|
||||||
|
if (strncmp(tok, "timeout=", 8) == 0)
|
||||||
|
{
|
||||||
|
*timeout = atoi(tok + 8);
|
||||||
|
if (*timeout > 5)
|
||||||
|
*timeout -= KEEPALIVE_MARGIN;
|
||||||
|
else if (*timeout > 0)
|
||||||
|
*timeout = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_transport(char *request_line) {
|
static int parse_port(char* str, uint16_t* port)
|
||||||
char *state;
|
{
|
||||||
char *tok;
|
int p = atoi(str);
|
||||||
int err;
|
if (p < 0 || p > UINT16_MAX)
|
||||||
|
return -1;
|
||||||
|
|
||||||
tok = strtok_r(request_line, ";", &state);
|
*port = p;
|
||||||
if (tok == NULL || strncmp(tok, "RTP/AVP", 7) != 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
tok = strtok_r(NULL, ";", &state);
|
return 0;
|
||||||
if (tok == NULL || strncmp(tok, "multicast", 9) != 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
|
|
||||||
if (strncmp(tok, "destination=", 12) == 0) {
|
|
||||||
strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1)));
|
|
||||||
} else if (strncmp(tok, "port=", 5) == 0) {
|
|
||||||
char port[6];
|
|
||||||
char *end;
|
|
||||||
|
|
||||||
memset(port, 0x00, 6);
|
|
||||||
strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5));
|
|
||||||
if ((end = strstr(port, "-")) != NULL)
|
|
||||||
*end = '\0';
|
|
||||||
err = parse_port(port, &rtsp->udp_port);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define skip_whitespace(x) while(*x == ' ') x++
|
static int parse_transport(char* request_line)
|
||||||
static enum rtsp_result rtsp_handle() {
|
{
|
||||||
uint8_t buffer[512];
|
char* state;
|
||||||
int rtsp_result = 0;
|
char* tok;
|
||||||
bool have_header = false;
|
int err;
|
||||||
size_t content_length = 0;
|
|
||||||
size_t read = 0;
|
|
||||||
char *in, *val;
|
|
||||||
string in_str;
|
|
||||||
|
|
||||||
/* Parse header */
|
tok = strtok_r(request_line, ";", &state);
|
||||||
while (!have_header) {
|
if (tok == nullptr || strncmp(tok, "RTP/AVP", 7) != 0)
|
||||||
if (tcp_sock_read_line(in_str) < 0)
|
return -1;
|
||||||
break;
|
|
||||||
in = const_cast<char *>(in_str.c_str());
|
|
||||||
|
|
||||||
if (strncmp(in, "RTSP/1.0 ", 9) == 0) {
|
tok = strtok_r(nullptr, ";", &state);
|
||||||
rtsp_result = atoi(in + 9);
|
if (tok == nullptr || strncmp(tok, "multicast", 9) != 0)
|
||||||
} else if (strncmp(in, "Content-Base:", 13) == 0) {
|
return 0;
|
||||||
free(rtsp->content_base);
|
|
||||||
|
|
||||||
val = in + 13;
|
while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
|
||||||
skip_whitespace(val);
|
{
|
||||||
|
if (strncmp(tok, "destination=", 12) == 0)
|
||||||
|
{
|
||||||
|
strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1)));
|
||||||
|
}
|
||||||
|
else if (strncmp(tok, "port=", 5) == 0)
|
||||||
|
{
|
||||||
|
char port[6];
|
||||||
|
char* end;
|
||||||
|
|
||||||
rtsp->content_base = strdup(val);
|
memset(port, 0x00, 6);
|
||||||
} else if (strncmp(in, "Content-Length:", 15) == 0) {
|
strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5));
|
||||||
val = in + 16;
|
if ((end = strstr(port, "-")) != nullptr)
|
||||||
skip_whitespace(val);
|
*end = '\0';
|
||||||
|
err = parse_port(port, &rtsp->udp_port);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content_length = atoi(val);
|
return 0;
|
||||||
} else if (strncmp("Session:", in, 8) == 0) {
|
}
|
||||||
val = in + 8;
|
|
||||||
skip_whitespace(val);
|
|
||||||
|
|
||||||
parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval);
|
#define skip_whitespace(x) \
|
||||||
} else if (strncmp("Transport:", in, 10) == 0) {
|
while (*x == ' ') \
|
||||||
val = in + 10;
|
x++
|
||||||
skip_whitespace(val);
|
static enum rtsp_result rtsp_handle()
|
||||||
|
{
|
||||||
|
uint8_t buffer[512];
|
||||||
|
int rtsp_result = 0;
|
||||||
|
bool have_header = false;
|
||||||
|
size_t content_length = 0;
|
||||||
|
size_t read = 0;
|
||||||
|
char *in, *val;
|
||||||
|
string in_str;
|
||||||
|
|
||||||
if (parse_transport(val) != 0) {
|
/* Parse header */
|
||||||
rtsp_result = -1;
|
while (!have_header)
|
||||||
break;
|
{
|
||||||
}
|
if (tcp_sock_read_line(in_str) < 0)
|
||||||
} else if (strncmp("com.ses.streamID:", in, 17) == 0) {
|
break;
|
||||||
val = in + 17;
|
in = const_cast<char*>(in_str.c_str());
|
||||||
skip_whitespace(val);
|
|
||||||
|
|
||||||
rtsp->stream_id = atoi(val);
|
if (strncmp(in, "RTSP/1.0 ", 9) == 0)
|
||||||
} else if (in[0] == '\0') {
|
{
|
||||||
have_header = true;
|
rtsp_result = atoi(in + 9);
|
||||||
}
|
}
|
||||||
}
|
else if (strncmp(in, "Content-Base:", 13) == 0)
|
||||||
|
{
|
||||||
|
free(rtsp->content_base);
|
||||||
|
|
||||||
/* Discard further content */
|
val = in + 13;
|
||||||
while (content_length > 0 &&
|
skip_whitespace(val);
|
||||||
(read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer), min(sizeof(buffer), content_length))))
|
|
||||||
content_length -= read;
|
|
||||||
|
|
||||||
return (enum rtsp_result)rtsp_result;
|
rtsp->content_base = strdup(val);
|
||||||
|
}
|
||||||
|
else if (strncmp(in, "Content-Length:", 15) == 0)
|
||||||
|
{
|
||||||
|
val = in + 16;
|
||||||
|
skip_whitespace(val);
|
||||||
|
|
||||||
|
content_length = atoi(val);
|
||||||
|
}
|
||||||
|
else if (strncmp("Session:", in, 8) == 0)
|
||||||
|
{
|
||||||
|
val = in + 8;
|
||||||
|
skip_whitespace(val);
|
||||||
|
|
||||||
|
parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval);
|
||||||
|
}
|
||||||
|
else if (strncmp("Transport:", in, 10) == 0)
|
||||||
|
{
|
||||||
|
val = in + 10;
|
||||||
|
skip_whitespace(val);
|
||||||
|
|
||||||
|
if (parse_transport(val) != 0)
|
||||||
|
{
|
||||||
|
rtsp_result = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strncmp("com.ses.streamID:", in, 17) == 0)
|
||||||
|
{
|
||||||
|
val = in + 17;
|
||||||
|
skip_whitespace(val);
|
||||||
|
|
||||||
|
rtsp->stream_id = atoi(val);
|
||||||
|
}
|
||||||
|
else if (in[0] == '\0')
|
||||||
|
{
|
||||||
|
have_header = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discard further content */
|
||||||
|
while (content_length > 0 && (read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer),
|
||||||
|
min(sizeof(buffer), content_length))))
|
||||||
|
content_length -= read;
|
||||||
|
|
||||||
|
return (enum rtsp_result)rtsp_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rtsp_open(const string& name, const string& url_str)
|
bool rtsp_open(const string& name, const string& url_str)
|
||||||
{
|
{
|
||||||
string setup_url_str;
|
string setup_url_str;
|
||||||
const char *psz_setup_url;
|
const char* psz_setup_url;
|
||||||
stringstream setup_ss;
|
stringstream setup_ss;
|
||||||
stringstream play_ss;
|
stringstream play_ss;
|
||||||
url setup_url;
|
url setup_url;
|
||||||
|
|
||||||
rtsp_close();
|
rtsp_close();
|
||||||
rtsp = new rtsp_client();
|
rtsp = new rtsp_client();
|
||||||
if (rtsp == NULL)
|
if (rtsp == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
rtsp->name = name;
|
rtsp->name = name;
|
||||||
rtsp->level = 0;
|
rtsp->level = 0;
|
||||||
rtsp->quality = 0;
|
rtsp->quality = 0;
|
||||||
|
|
||||||
libKodi->Log(LOG_DEBUG, "try to open '%s'", url_str.c_str());
|
kodi::Log(ADDON_LOG_DEBUG, "try to open '%s'", url_str.c_str());
|
||||||
|
|
||||||
url dst = parse_url(url_str);
|
url dst = parse_url(url_str);
|
||||||
libKodi->Log(LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
|
kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
|
||||||
|
|
||||||
if(!rtsp->tcp_sock.connect(dst.host, dst.port)) {
|
if (!rtsp->tcp_sock.connect(dst.host, dst.port))
|
||||||
libKodi->Log(LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(), dst.port);
|
{
|
||||||
goto error;
|
kodi::Log(ADDON_LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(),
|
||||||
}
|
dst.port);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: tcp keep alive?
|
// TODO: tcp keep alive?
|
||||||
|
|
||||||
if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(),
|
if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), dst.port) < 0)
|
||||||
dst.port) < 0) {
|
{
|
||||||
rtsp->content_base = NULL;
|
rtsp->content_base = nullptr;
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
rtsp->last_seq_nr = 0;
|
rtsp->last_seq_nr = 0;
|
||||||
rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN);
|
rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN);
|
||||||
|
|
||||||
setup_url = dst;
|
setup_url = dst;
|
||||||
|
|
||||||
// reverse the satip protocol trick, as SAT>IP believes to be RTSP
|
// reverse the satip protocol trick, as SAT>IP believes to be RTSP
|
||||||
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) {
|
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5))
|
||||||
setup_url.protocol = "rtsp";
|
{
|
||||||
}
|
setup_url.protocol = "rtsp";
|
||||||
|
}
|
||||||
|
|
||||||
setup_url_str = compose_url(setup_url);
|
setup_url_str = compose_url(setup_url);
|
||||||
psz_setup_url = setup_url_str.c_str();
|
psz_setup_url = setup_url_str.c_str();
|
||||||
|
|
||||||
// TODO: Find available port
|
// TODO: Find available port
|
||||||
rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
|
rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
|
||||||
rtsp->udp_port = 6785;
|
rtsp->udp_port = 6785;
|
||||||
if(!rtsp->udp_sock.bind(rtsp->udp_port)) {
|
if (!rtsp->udp_sock.bind(rtsp->udp_port))
|
||||||
goto error;
|
{
|
||||||
}
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
setup_ss << "SETUP " << setup_url_str<< " RTSP/1.0\r\n";
|
setup_ss << "SETUP " << setup_url_str << " RTSP/1.0\r\n";
|
||||||
setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
||||||
setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-" << (rtsp->udp_port + 1) << "\r\n\r\n";
|
setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-"
|
||||||
rtsp->tcp_sock.send(setup_ss.str());
|
<< (rtsp->udp_port + 1) << "\r\n\r\n";
|
||||||
|
rtsp->tcp_sock.send(setup_ss.str());
|
||||||
|
|
||||||
if (rtsp_handle() != RTSP_RESULT_OK) {
|
if (rtsp_handle() != RTSP_RESULT_OK)
|
||||||
libKodi->Log(LOG_ERROR, "Failed to setup RTSP session");
|
{
|
||||||
goto error;
|
kodi::Log(ADDON_LOG_ERROR, "Failed to setup RTSP session");
|
||||||
}
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0) {
|
if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0)
|
||||||
rtsp->control = NULL;
|
{
|
||||||
goto error;
|
rtsp->control = nullptr;
|
||||||
}
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
|
play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
|
||||||
play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
||||||
play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
||||||
rtsp->tcp_sock.send(play_ss.str());
|
rtsp->tcp_sock.send(play_ss.str());
|
||||||
|
|
||||||
if (rtsp_handle() != RTSP_RESULT_OK) {
|
if (rtsp_handle() != RTSP_RESULT_OK)
|
||||||
libKodi->Log(LOG_ERROR, "Failed to play RTSP session");
|
{
|
||||||
goto error;
|
kodi::Log(ADDON_LOG_ERROR, "Failed to play RTSP session");
|
||||||
}
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
|
rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
|
||||||
if(!rtsp->rtcp_sock.bind(rtsp->udp_port + 1)) {
|
if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1))
|
||||||
goto error;
|
{
|
||||||
}
|
goto error;
|
||||||
if(!rtsp->rtcp_sock.set_non_blocking(true)) {
|
}
|
||||||
goto error;
|
if (!rtsp->rtcp_sock.set_non_blocking(true))
|
||||||
}
|
{
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
rtsp_close();
|
rtsp_close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parse_rtcp(const char *buf, int size) {
|
static void parse_rtcp(const char* buf, int size)
|
||||||
int offset = 0;
|
{
|
||||||
while(size > 4) {
|
int offset = 0;
|
||||||
const rtcp_app *app = reinterpret_cast<const rtcp_app *>(buf + offset);
|
while (size > 4)
|
||||||
uint16_t len = 4 * (ntohs(app->len) + 1);
|
{
|
||||||
|
const rtcp_app* app = reinterpret_cast<const rtcp_app*>(buf + offset);
|
||||||
|
uint16_t len = 4 * (ntohs(app->len) + 1);
|
||||||
|
|
||||||
if((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0)) {
|
if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0))
|
||||||
size -= len;
|
{
|
||||||
offset += len;
|
size -= len;
|
||||||
continue;
|
offset += len;
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t string_len = ntohs(app->string_len);
|
uint16_t string_len = ntohs(app->string_len);
|
||||||
string app_data(&buf[offset + sizeof(rtcp_app)], string_len);
|
string app_data(&buf[offset + sizeof(rtcp_app)], string_len);
|
||||||
|
|
||||||
vector<string> elems;
|
vector<string> elems;
|
||||||
split_string(app_data, ';', elems);
|
split_string(app_data, ';', elems);
|
||||||
if(elems.size() != 4) {
|
if (elems.size() != 4)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
vector<string> tuner;
|
vector<string> tuner;
|
||||||
split_string(elems[2], ',', tuner);
|
split_string(elems[2], ',', tuner);
|
||||||
if(tuner.size() < 4) {
|
if (tuner.size() < 4)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rtsp->level = atoi(tuner[1].c_str());
|
rtsp->level = atoi(tuner[1].c_str());
|
||||||
rtsp->quality = atoi(tuner[3].c_str());
|
rtsp->quality = atoi(tuner[3].c_str());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int rtsp_read(void *buf, unsigned buf_size) {
|
int rtsp_read(void* buf, unsigned buf_size)
|
||||||
sockaddr addr;
|
{
|
||||||
socklen_t addr_len = sizeof(addr);
|
sockaddr addr;
|
||||||
int ret = rtsp->udp_sock.recvfrom((char *)buf, buf_size, (sockaddr *)&addr, &addr_len);
|
socklen_t addr_len = sizeof(addr);
|
||||||
|
int ret = rtsp->udp_sock.recvfrom((char*)buf, buf_size, (sockaddr*)&addr, &addr_len);
|
||||||
|
|
||||||
char rtcp_buf[RTCP_BUFFER_SIZE];
|
char rtcp_buf[RTCP_BUFFER_SIZE];
|
||||||
int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr *)&addr, &addr_len);
|
int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr*)&addr, &addr_len);
|
||||||
parse_rtcp(rtcp_buf, rtcp_len);
|
parse_rtcp(rtcp_buf, rtcp_len);
|
||||||
|
|
||||||
// TODO: check ip
|
// TODO: check ip
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtsp_teardown() {
|
static void rtsp_teardown()
|
||||||
if(!rtsp->tcp_sock.is_valid()) {
|
{
|
||||||
return;
|
if (!rtsp->tcp_sock.is_valid())
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rtsp->session_id[0] > 0) {
|
if (rtsp->session_id[0] > 0)
|
||||||
char *msg;
|
{
|
||||||
int len;
|
char* msg;
|
||||||
stringstream ss;
|
int len;
|
||||||
|
stringstream ss;
|
||||||
|
|
||||||
rtsp->udp_sock.close();
|
rtsp->udp_sock.close();
|
||||||
|
|
||||||
ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
|
ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
|
||||||
ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
ss << "CSeq: " << rtsp->cseq++ << "\r\n";
|
||||||
ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
||||||
rtsp->tcp_sock.send(ss.str());
|
rtsp->tcp_sock.send(ss.str());
|
||||||
|
|
||||||
if (rtsp_handle() != RTSP_RESULT_OK) {
|
if (rtsp_handle() != RTSP_RESULT_OK)
|
||||||
libKodi->Log(LOG_ERROR, "Failed to teardown RTSP session");
|
{
|
||||||
return;
|
kodi::Log(ADDON_LOG_ERROR, "Failed to teardown RTSP session");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtsp_close()
|
void rtsp_close()
|
||||||
{
|
{
|
||||||
if(rtsp) {
|
if (rtsp)
|
||||||
rtsp_teardown();
|
{
|
||||||
rtsp->tcp_sock.close();
|
rtsp_teardown();
|
||||||
rtsp->udp_sock.close();
|
rtsp->tcp_sock.close();
|
||||||
rtsp->rtcp_sock.close();
|
rtsp->udp_sock.close();
|
||||||
delete rtsp;
|
rtsp->rtcp_sock.close();
|
||||||
rtsp = NULL;
|
delete rtsp;
|
||||||
}
|
rtsp = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rtsp_fill_signal_status(PVR_SIGNAL_STATUS* signal_status) {
|
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status)
|
||||||
if(rtsp) {
|
{
|
||||||
strncpy(signal_status->strServiceName, rtsp->name.c_str(), PVR_ADDON_NAME_STRING_LENGTH - 1);
|
if (rtsp)
|
||||||
signal_status->iSNR = 0x1111 * rtsp->quality;
|
{
|
||||||
signal_status->iSignal = 0x101 * rtsp->level;
|
signal_status.SetAdapterName(rtsp->name);
|
||||||
}
|
signal_status.SetSNR(0x1111 * rtsp->quality);
|
||||||
|
signal_status.SetSignal(0x101 * rtsp->level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#ifndef _RTSP_CLIENT_HPP_
|
/*
|
||||||
#define _RTSP_CLIENT_HPP_
|
* Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
* See LICENSE.md for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <kodi/addon-instance/pvr/Channels.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <kodi/xbmc_pvr_types.h>
|
|
||||||
|
|
||||||
bool rtsp_open(const std::string& name, const std::string& url_str);
|
bool rtsp_open(const std::string& name, const std::string& url_str);
|
||||||
void rtsp_close();
|
void rtsp_close();
|
||||||
int rtsp_read(void *buf, unsigned buf_size);
|
int rtsp_read(void* buf, unsigned buf_size);
|
||||||
void rtsp_fill_signal_status(PVR_SIGNAL_STATUS* signal_status);
|
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status);
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
Reference in New Issue
Block a user