Compare commits

...

75 Commits

Author SHA1 Message Date
Kai Sommerfeld
419998ff98 Merge pull request #66 from phunkyfish/fix-multiline-workflows-nexus
Fix github workflows Nexus
2024-02-25 22:32:00 +01:00
phunkyfish
2ff3523183 [github] fix workflows nexus 2024-02-22 20:21:43 +00:00
Kai Sommerfeld
bf39731a50 Merge pull request #63 from phunkyfish/update-workflow-nexus
Update deprecated github workflows nexus
2024-02-17 13:48:24 +01:00
phunkyfish
f62c9b9cd5 [github] Updated deprecated workflows actions 2024-02-13 17:17:13 +00:00
Kai Sommerfeld
656912b9f1 Merge pull request #61 from ksooo/ci-fixes-nexus
[Nexus] CI fixes/updates
2023-11-07 17:41:22 +01:00
ksooo
39dc489001 CI fixes/updates 2023-11-07 12:50:37 +01:00
Kai Sommerfeld
c2a3daa691 Merge pull request #57 from AlwinEsch/Nexus-changes
[Nexus] increase version to 20.3.0 (API related)
2022-09-16 16:01:46 +02:00
Alwin Esch
51cd4c4c90 increase version to 20.3.0 (API related) 2022-09-16 11:45:19 +02:00
Kai Sommerfeld
23b4f3eecf Merge pull request #56 from AlwinEsch/Nexus-change
[Nexus] API related update
2022-01-01 19:38:39 +01:00
Alwin Esch
2a4230567c increase version to 20.2.0 2022-01-01 19:19:06 +01:00
Alwin Esch
567232b5fb API related update 2021-12-28 13:27:10 +01:00
Kai Sommerfeld
fb17e25ef6 Merge pull request #55 from AlwinEsch/Nexus-change
[Nexus] Update for PVR API 8.0.0
2021-10-04 16:00:27 +02:00
Alwin Esch
41a9829054 increase version to 20.1.0 (API update related) 2021-09-20 05:30:34 +02:00
Kai Sommerfeld
046acf1636 Merge pull request #54 from AlwinEsch/Nexus-change
[Nexus] initial release change for new Kodi 20 Nexus version
2021-09-19 10:33:47 +02:00
Alwin Esch
df13aef650 increase version to 20.0.0 (see note below)
With start of Kodi 20 Nexus, takes addon as major the same version number as Kodi.
This done to know easier to which Kodi the addon works.

For here now used by Kodi 20 Nexus.
2021-09-19 09:56:56 +02:00
Alwin Esch
bacefe5194 change test builds to 'Kodi Nexus' 2021-09-19 09:56:27 +02:00
Kai Sommerfeld
94d6b5fce7 Merge pull request #52 from AlwinEsch/Matrix-change
[Matrix/Nexus] some cleanup
2021-09-18 20:18:43 +02:00
Alwin Esch
07ef28ed18 udpate appveyor.yml build script 2021-09-18 19:52:20 +02:00
Alwin Esch
2d5e55b05d remove tabs on addon.xml.in
Not sure if the language script works correct if them as tab.
2021-09-18 19:52:01 +02:00
Alwin Esch
7fea489b8a update copyright year to 2021 2021-09-18 19:51:57 +02:00
Alwin Esch
ae2525dc2b [debian] improve packaging 2021-09-18 19:51:53 +02:00
Alwin Esch
52f9a04da6 cleanup and fix language files (on addon.xml was set as en_US) 2021-09-18 19:51:46 +02:00
Alwin Esch
e00fc3b4ea allow OS build test via github workflows 2021-09-18 19:51:38 +02:00
Alwin Esch
012ff40c25 Add Weblate workflows 2021-09-18 19:24:17 +02:00
Alwin Esch
7ec048377c remove travis CI status badge as no more used 2021-09-18 19:20:33 +02:00
Kai Sommerfeld
2325d7ff2d Merge pull request #50 from phunkyfish/release-only
Worflow to release using current changelog and version
2021-09-18 13:34:39 +02:00
Kai Sommerfeld
0e5c8d37f0 Merge pull request #49 from phunkyfish/scripts-actions
Scripts actions
2021-09-18 13:34:13 +02:00
phunkyfish
ee2648a2c2 Add change and release workflow for update automation 2021-07-24 09:44:31 +01:00
phunkyfish
524eb385b5 Worflow to release using current changelog and version 2021-07-24 09:42:24 +01:00
Kai Sommerfeld
3b3e8e4a1a Merge pull request #48 from phunkyfish/epg-timeframe-api
PVR API 7.1.0 - changelog and version v4.1.0
2021-01-09 14:21:35 +01:00
phunkyfish
e76a37eb1d changelog and version v4.1.0 2021-01-08 13:50:29 +00:00
Kai Sommerfeld
43b2467d35 Merge pull request #47 from ksooo/fix-windows-build
Fix windows build
2020-10-31 00:34:12 +01:00
Kai Sommerfeld
18fc3a3339 Fix windows build 2020-10-31 00:25:34 +01:00
Kai Sommerfeld
11b8d0b076 Merge pull request #44 from AlwinEsch/Matrix-change
[Matrix] API update
2020-10-30 23:49:03 +01:00
Alwin Esch
479502987a increase version to 4.0.0 (API related) 2020-10-28 02:18:46 +01:00
Kai Sommerfeld
495dfe137a Merge pull request #45 from phunkyfish/traviscpp17
Travis changes for cpp17 and debian
2020-10-17 17:02:14 +02:00
phunkyfish
5a228cc805 Travis changes for cpp17 and debian 2020-10-17 15:30:32 +01:00
Kai Sommerfeld
81520e3104 Merge pull request #43 from AlwinEsch/interface-change
[Matrix] change to new C++ PVR interface way
2020-06-24 22:52:18 +02:00
Alwin Esch
1d1cede442 increase version to 3.0.0 2020-06-07 19:14:38 +02:00
Alwin Esch
1736207a46 change settings.xml to new style 2020-06-07 19:14:08 +02:00
Alwin Esch
e7449d9537 change to new C++ addon interface 2020-06-07 19:06:40 +02:00
Alwin Esch
fc5b149f2c clang code cleanup 2020-06-07 16:56:16 +02:00
Alwin Esch
76259ba352 change point=xbmc.pvrclient to point=kodi.pvrclient 2020-06-05 19:03:08 +02:00
Kai Sommerfeld
525f1101c6 Merge pull request #42 from AlwinEsch/Matrix-change
[Matrix] API change update
2020-05-22 21:59:05 +02:00
Alwin Esch
2a9d84fa8e increase version to 2.0.0 (API related) 2020-05-19 00:23:26 +02:00
Alwin Esch
c537ad2337 api related updated 2020-05-16 21:35:34 +02:00
Kai Sommerfeld
b01e24b0a2 Merge pull request #41 from AlwinEsch/Matrix-change
[Matrix] API change updates
2020-04-30 23:15:16 +02:00
Alwin Esch
5f9342abdd increase version to 1.2.6 2020-04-30 19:06:50 +02:00
Alwin Esch
cc34e52369 [API related] change SignalStatus and GetDescrambleInfo (channelUid added) 2020-04-29 20:47:22 +02:00
Alwin Esch
360fb03145 [API related] rename GetAddonCapabilities to GetCapabilities 2020-04-29 20:44:00 +02:00
Alwin Esch
ace4891f78 [API related] change PVR_PROPERTIES to AddonProperties_PVR 2020-04-29 20:43:46 +02:00
Alwin Esch
3d47c712fd remove not used GUI addon interface include 2020-04-27 11:16:50 +02:00
Kai Sommerfeld
3f04265d51 Merge pull request #40 from AlwinEsch/Matrix-change
[Matrix] API change update / cleanups
2020-04-24 19:09:49 +02:00
Alwin Esch
d4d6628d57 increase version to 1.2.5 2020-04-24 18:10:56 +02:00
Alwin Esch
cbef814f8f correct license on addon.xml 2020-03-31 05:38:50 +02:00
Kai Sommerfeld
50386e13b2 Merge pull request #39 from phunkyfish/pvr-api
PVR API 6.3.0
2020-03-26 21:15:37 +01:00
phunkyfish
cd1c5d4171 PVR API 6.3.0 2020-03-26 19:43:26 +00:00
Kai Sommerfeld
83fbcfb360 Merge pull request #38 from AlwinEsch/Matrix-change
[Matrix] cleanups, addon.xml update and LICENSE add / update
2020-03-25 22:29:16 +01:00
Alwin Esch
bed39d0575 increase version to 1.2.3 2020-03-25 20:58:12 +01:00
Alwin Esch
0bdcb49d69 update .gitignore 2020-03-25 20:58:07 +01:00
Alwin Esch
5024fd99c1 add license and source url to addon.xml 2020-03-25 20:56:17 +01:00
Alwin Esch
a19fef8af8 update source license to use SPDX 2020-03-25 20:53:37 +01:00
Alwin Esch
38f8f54c9f add GPL2 as LICENSE.md to addon 2020-03-25 20:53:25 +01:00
Alwin Esch
01c41d8ea1 update debian build code 2020-03-25 05:36:06 +01:00
Kai Sommerfeld
237362e65a Merge pull request #37 from phunkyfish/season-invalid
Update PVR API 6.2.0
2020-03-17 18:19:46 +01:00
phunkyfish
c3f4d61b38 Update PVR API 6.2.0 2020-03-17 09:47:45 +00:00
Kai Sommerfeld
c37cede8dc Merge pull request #36 from AlwinEsch/Matrix-change
add p8-platform to addon itself as depends
2020-02-26 13:18:25 +01:00
Alwin Esch
aa4f7c0b0d add p8-platform to addon itself as depends
This is intended to significantly speed up the construction times for those who do not use these dependencies (most addons without this).

This will also happen to all other PVRs that need it and also allows you to add your own versions if necessary.

After converting this will be removed from Kodi.
Only used on the Matrix branches, the others remain with Kodi.
2020-02-23 07:21:06 +01:00
Kai Sommerfeld
8032f085fd Merge pull request #33 from AlwinEsch/Matrix-change
[Matrix] update CMakeLists.txt to 3.5 and change include way
2019-09-23 12:21:19 +02:00
Alwin Esch
0f6cd141c4 increase version to 1.2.1 2019-09-21 00:43:42 +02:00
Alwin Esch
d5ec59e00e change header include to start with "kodi/"
This needed for reworked header checks by Kodi's addon build system.
2019-09-21 00:42:34 +02:00
Alwin Esch
e2a73af36e update .travis.yml 2019-09-21 00:42:34 +02:00
Alwin Esch
a55552e768 update CMakeLists.txt to 3.5 2019-09-21 00:42:34 +02:00
Kai Sommerfeld
14d3e9d225 Merge pull request #35 from phunkyfish/api-change
Recompile for 6.1.0 PVR Addon API compatibility
2019-09-03 19:58:50 +02:00
phunkyfish
cf9ee98727 Recompile for 6.1.0 PVR Addon API compatibility 2019-09-03 16:56:33 +01:00
32 changed files with 2284 additions and 1640 deletions

88
.clang-format Normal file
View 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
...

54
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
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-latest
CC: gcc
CXX: g++
DEBIAN_BUILD: true
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@v4
with:
repository: xbmc/xbmc
ref: Nexus
path: xbmc
- name: Checkout add-on repo
uses: actions/checkout@v4
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/Nexus/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

View 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@v4
with:
fetch-depth: 0
path: ${{ github.event.repository.name }}
# Checkout the required scripts from xbmc/binary-addon-scripts into the 'scripts' directory
- name: Checkout Scripts
uses: actions/checkout@v4
with:
fetch-depth: 0
repository: xbmc/binary-addon-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 update
sudo apt-get install libxml2-utils xmlstarlet
# Setup python version 3.9
- name: Set up Python
uses: actions/setup-python@v5
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'
# Note: we use a random EOF for 'changes' as is best practice for for multiline variables
- 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
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "changes<<$EOF" >> $GITHUB_OUTPUT
echo "$changes" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)')
echo "version=$version" >> $GITHUB_OUTPUT
branch=$(echo ${GITHUB_REF#refs/heads/})
echo "branch=$branch" >> $GITHUB_OUTPUT
echo "today=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
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 }}

63
.github/workflows/increment-version.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
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@v4
with:
fetch-depth: 0
path: ${{ github.event.repository.name }}
- name: Checkout Scripts
uses: actions/checkout@v4
with:
fetch-depth: 0
repository: xbmc/weblate-supplementary-scripts
path: scripts
- name: Set up Python
uses: actions/setup-python@v5
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 update
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 "version=$version" >> $GITHUB_OUTPUT
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
View 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@v4
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 update
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
# Note: we use a random EOF for 'changes' as is best practice for for multiline variables
- 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
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "changes<<$EOF" >> $GITHUB_OUTPUT
echo "$changes" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
version=$(xmlstarlet fo -R "$(find . -name addon.xml.in)" | xmlstarlet sel -t -v 'string(/addon/@version)')
echo "version=$version" >> $GITHUB_OUTPUT
branch=$(echo ${GITHUB_REF#refs/heads/})
echo "branch=$branch" >> $GITHUB_OUTPUT
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 }}

View File

@@ -0,0 +1,58 @@
name: Sync addon metadata translations
on:
push:
branches: [ Matrix, Nexus ]
paths:
- '**addon.xml.in'
- '**resource.language.**strings.po'
jobs:
default:
if: github.repository == 'kodi-pvr/pvr.octonet'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ 3.9 ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
path: project
- name: Checkout sync_addon_metadata_translations repository
uses: actions/checkout@v4
with:
repository: xbmc/sync_addon_metadata_translations
path: sync_addon_metadata_translations
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
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
reviewers: gade01

52
.gitignore vendored
View File

@@ -1,2 +1,50 @@
/build
pvr.octonet/addon.xml
# build artifacts
build/
pvr.*/addon.xml
# Debian build files
debian/changelog
debian/files
debian/*.log
debian/*.substvars
debian/.debhelper/
debian/tmp/
debian/kodi-pvr-*/
obj-x86_64-linux-gnu/
# commonly used editors
# vim
*.swp
# Eclipse
*.project
*.cproject
.classpath
*.sublime-*
.settings/
# KDevelop 4
*.kdev4
# gedit
*~
# CLion
/.idea
# clion
.idea/
# to prevent add after a "git format-patch VALUE" and "git add ." call
/*.patch
# Visual Studio Code
.vscode
# to prevent add if project code opened by Visual Studio over CMake file
.vs/
# General MacOS
.DS_Store
.AppleDouble
.LSOverride

View File

@@ -1,45 +0,0 @@
language: cpp
#
# Define the build matrix
#
# 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++.
#
matrix:
include:
- os: linux
dist: trusty
sudo: required
compiler: gcc
- os: linux
dist: trusty
sudo: required
compiler: clang
- os: osx
osx_image: xcode7.3
- os: osx
osx_image: xcode6.1
#
# Some of the OS X images don't have cmake, contrary to what people
# on the Internet say
#
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then which cmake || brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then which cmake || brew install cmake ; fi
#
# The addon source is automatically checked out in $TRAVIS_BUILD_DIR,
# we'll put the Kodi source on the same level
#
before_script:
- cd $TRAVIS_BUILD_DIR/..
- git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git
- mkdir -p xbmc/cmake/addons/addons/pvr.octonet
- echo "pvr.octonet https://github.com/DigitalDevices/pvr.octonet master" > xbmc/cmake/addons/addons/pvr.octonet/pvr.octonet.txt
- cd $TRAVIS_BUILD_DIR && mkdir build && cd build
- cmake -DADDONS_TO_BUILD=pvr.octonet -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons
script: make

View File

@@ -1,42 +1,37 @@
cmake_minimum_required(VERSION 3.5)
project(pvr.octonet)
cmake_minimum_required(VERSION 2.6)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
enable_language(CXX)
find_package(Kodi REQUIRED)
find_package(p8-platform REQUIRED)
find_package(JsonCpp REQUIRED)
include_directories(
${p8-platform_INCLUDE_DIRS}
${KODI_INCLUDE_DIR}
${JSONCPP_INCLUDE_DIRS})
include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Kodi cmake rework to match right include ways
${JSONCPP_INCLUDE_DIRS})
set(DEPLIBS
${p8-platform_LIBRARIES}
${JSONCPP_LIBRARIES})
set(DEPLIBS ${JSONCPP_LIBRARIES})
set(OCTONET_SOURCES
src/OctonetData.cpp
src/client.cpp
src/Socket.cpp
src/rtsp_client.cpp)
set(OCTONET_SOURCES src/addon.cpp
src/OctonetData.cpp
src/Socket.cpp
src/rtsp_client.cpp)
set(OCTONET_HEADERS
src/client.h
src/OctonetData.h
src/Socket.h)
set(OCTONET_HEADERS src/addon.h
src/OctonetData.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)
if(WIN32)
if(NOT CMAKE_SYSTEM_NAME STREQUAL WindowsStore)
target_link_libraries(pvr.octonet wsock32 ws2_32)
else()
target_link_libraries(pvr.octonet ws2_32)
endif()
if(NOT CMAKE_SYSTEM_NAME STREQUAL WindowsStore)
target_link_libraries(pvr.octonet wsock32 ws2_32)
else()
target_link_libraries(pvr.octonet ws2_32)
endif()
endif()
include(CPack)

2
Jenkinsfile vendored
View File

@@ -1 +1 @@
buildPlugin(version: "Matrix")
buildPlugin(version: "Nexus")

264
LICENSE.md Normal file
View File

@@ -0,0 +1,264 @@
The GNU General Public License, Version 2, June 1991 (GPLv2)
============================================================
> Copyright (C) 1989, 1991 Free Software Foundation, Inc.
> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
--------
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public License is intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users. This General Public License applies to most
of the Free Software Foundation's software and to any other program whose
authors commit to using it. (Some other Free Software Foundation software is
covered by the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for this service if you wish),
that you receive source code or can get it if you want it, that you can change
the software or use pieces of it in new free programs; and that you know you can
do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny
you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of the
software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for a
fee, you must give the recipients all the rights that you have. You must make
sure that they, too, receive or can get the source code. And you must show them
these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer
you this license which gives you legal permission to copy, distribute and/or
modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If the
software is modified by someone else and passed on, we want its recipients to
know that what they have is not the original, so that any problems introduced by
others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We wish
to avoid the danger that redistributors of a free program will individually
obtain patent licenses, in effect making the program proprietary. To prevent
this, we have made it clear that any patent must be licensed for everyone's free
use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
Terms And Conditions For Copying, Distribution And Modification
---------------------------------------------------------------
**0.** This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms of
this General Public License. The "Program", below, refers to any such program or
work, and a "work based on the Program" means either the Program or any
derivative work under copyright law: that is to say, a work containing the
Program or a portion of it, either verbatim or with modifications and/or
translated into another language. (Hereinafter, translation is included without
limitation in the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered by
this License; they are outside its scope. The act of running the Program is not
restricted, and the output from the Program is covered only if its contents
constitute a work based on the Program (independent of having been made by
running the Program). Whether that is true depends on what the Program does.
**1.** You may copy and distribute verbatim copies of the Program's source code
as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this License
and to the absence of any warranty; and give any other recipients of the Program
a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may at
your option offer warranty protection in exchange for a fee.
**2.** You may modify your copy or copies of the Program or any portion of it,
thus forming a work based on the Program, and copy and distribute such
modifications or work under the terms of Section 1 above, provided that you also
meet all of these conditions:
* **a)** You must cause the modified files to carry prominent notices stating
that you changed the files and the date of any change.
* **b)** You must cause any work that you distribute or publish, that in whole
or in part contains or is derived from the Program or any part thereof, to
be licensed as a whole at no charge to all third parties under the terms of
this License.
* **c)** If the modified program normally reads commands interactively when
run, you must cause it, when started running for such interactive use in the
most ordinary way, to print or display an announcement including an
appropriate copyright notice and a notice that there is no warranty (or
else, saying that you provide a warranty) and that users may redistribute
the program under these conditions, and telling the user how to view a copy
of this License. (Exception: if the Program itself is interactive but does
not normally print such an announcement, your work based on the Program is
not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License, and
its terms, do not apply to those sections when you distribute them as separate
works. But when you distribute the same sections as part of a whole which is a
work based on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the entire whole,
and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise the
right to control the distribution of derivative or collective works based on the
Program.
In addition, mere aggregation of another work not based on the Program with the
Program (or with a work based on the Program) on a volume of a storage or
distribution medium does not bring the other work under the scope of this
License.
**3.** You may copy and distribute the Program (or a work based on it, under
Section 2) in object code or executable form under the terms of Sections 1 and 2
above provided that you also do one of the following:
* **a)** Accompany it with the complete corresponding machine-readable source
code, which must be distributed under the terms of Sections 1 and 2 above on
a medium customarily used for software interchange; or,
* **b)** Accompany it with a written offer, valid for at least three years, to
give any third party, for a charge no more than your cost of physically
performing source distribution, a complete machine-readable copy of the
corresponding source code, to be distributed under the terms of Sections 1
and 2 above on a medium customarily used for software interchange; or,
* **c)** Accompany it with the information you received as to the offer to
distribute corresponding source code. (This alternative is allowed only for
noncommercial distribution and only if you received the program in object
code or executable form with such an offer, in accord with Subsection b
above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all the
source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and installation
of the executable. However, as a special exception, the source code distributed
need not include anything that is normally distributed (in either source or
binary form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component itself
accompanies the executable.
If distribution of executable or object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the source code
from the same place counts as distribution of the source code, even though third
parties are not compelled to copy the source along with the object code.
**4.** You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies, or
rights, from you under this License will not have their licenses terminated so
long as such parties remain in full compliance.
**5.** You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Program or its derivative works. These actions are prohibited by law if you do
not accept this License. Therefore, by modifying or distributing the Program (or
any work based on the Program), you indicate your acceptance of this License to
do so, and all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
**6.** Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the original
licensor to copy, distribute or modify the Program subject to these terms and
conditions. You may not impose any further restrictions on the recipients'
exercise of the rights granted herein. You are not responsible for enforcing
compliance by third parties to this License.
**7.** If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues), conditions
are imposed on you (whether by court order, agreement or otherwise) that
contradict the conditions of this License, they do not excuse you from the
conditions of this License. If you cannot distribute so as to satisfy
simultaneously your obligations under this License and any other pertinent
obligations, then as a consequence you may not distribute the Program at all.
For example, if a patent license would not permit royalty-free redistribution of
the Program by all those who receive copies directly or indirectly through you,
then the only way you could satisfy both it and this License would be to refrain
entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and the
section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or
other property right claims or to contest validity of any such claims; this
section has the sole purpose of protecting the integrity of the free software
distribution system, which is implemented by public license practices. Many
people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose that
choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
**8.** If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Program under this License may add an explicit
geographical distribution limitation excluding those countries, so that
distribution is permitted only in or among countries not thus excluded. In such
case, this License incorporates the limitation as if written in the body of this
License.
**9.** The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be similar
in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Program does not specify a version number of this License, you may choose any
version ever published by the Free Software Foundation.
**10.** If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software Foundation,
write to the Free Software Foundation; we sometimes make exceptions for this.
Our decision will be guided by the two goals of preserving the free status of
all derivatives of our free software and of promoting the sharing and reuse of
software generally.
No Warranty
-----------
**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

View File

@@ -1,10 +1,9 @@
# Octonet PVR
Digital Devices [Octonet](http://www.digital-devices.eu/shop/de/netzwerk-tv/) PVR client addon for [Kodi](http://kodi.tv)
| Platform | Status |
|----------|--------|
| Linux + OS X (Travis) | [![Build Status](https://travis-ci.org/julianscheel/pvr.octonet.svg?branch=master)](https://travis-ci.org/julianscheel/pvr.octonet) |
| Windows (AppVeyor) | [![Build status](https://ci.appveyor.com/api/projects/status/m7dhmpmuf5coir5h?svg=true)](https://ci.appveyor.com/project/julianscheel/pvr-octonet) |
[![License: GPL-2.0-or-later](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](LICENSE.md)
[![Build and run tests](https://github.com/DigitalDevices/pvr.octonet/actions/workflows/build.yml/badge.svg?branch=Nexus)](https://github.com/DigitalDevices/pvr.octonet/actions/workflows/build.yml)
[![Build Status](https://jenkins.kodi.tv/view/Addons/job/DigitalDevices/job/pvr.octonet/job/Nexus/badge/icon)](https://jenkins.kodi.tv/blue/organizations/jenkins/DigitalDevices%2Fpvr.octonet/branches/)
# Building

View File

@@ -1,40 +0,0 @@
version: BuildNr.{build}
init:
- ps: $commit = $env:appveyor_repo_commit.SubString(0,7)
- ps: $timestamp = $env:appveyor_repo_commit_timestamp.SubString(0,10)
- ps: Update-AppveyorBuild -Version ("{0}-{1}-{2}" -f $env:appveyor_repo_branch, $commit, $timestamp)
# clone directory
clone_folder: c:\projects\pvr.octonet
# fetch repository as zip archive
shallow_clone: true # default is "false"
environment:
ADDON: pvr.octonet
matrix:
#- GENERATOR: "Visual Studio 14"
# CONFIG: Debug
- GENERATOR: "Visual Studio 14"
CONFIG: Release
artifacts:
- path: build/install/
name: pvr.octonet
type: zip
build_script:
- cd ..
- set ROOT=%cd%
- git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git
- mkdir xbmc\cmake\addons\addons\pvr.octonet
- echo pvr.octonet https://github.com/DigitalDevices/pvr.octonet master > xbmc\cmake\addons\addons\pvr.octonet\pvr.octonet.txt
- cd %ADDON%
- mkdir build
- cd build
# Must use absolute path for cmake to build depends correctly
- 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
- cmake --build . --config %CONFIG%

2
debian/control vendored
View File

@@ -3,7 +3,7 @@ Priority: extra
Maintainer: Julian Scheel <julian@jusst.de>
Build-Depends: debhelper (>= 9.0.0), cmake, libjsoncpp-dev,
libp8-platform-dev, kodi-addon-dev
Standards-Version: 3.9.4
Standards-Version: 4.1.2
Section: libs
Homepage: https://github.com/DigitalDevices/pvr.octonet

5
debian/copyright vendored
View File

@@ -5,7 +5,7 @@ Source: https://github.com/DigitalDevices/pvr.octonet
Files: *
Copyright: 2015-2016 Julian Scheel
2015-2016 jusst technologies GmbH
2005-2013 Team XBMC
2005-2021 Team Kodi
License: GPL-2+
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
@@ -24,7 +24,8 @@ License: GPL-2+
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
Files: debian/*
Copyright: 2016 Julian Scheel <julian@jusst.de>
Copyright: 2020-2021 Team Kodi
2016 Julian Scheel <julian@jusst.de>
2015 Jean-Luc Barriere
2015 wsnipex <wsnipex@a1.net>
License: GPL-2+

7
debian/rules vendored
View File

@@ -10,14 +10,11 @@
#export DH_VERBOSE=1
%:
dh $@
dh $@
override_dh_auto_configure:
# USE_LTO breaks build
dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=1
override_dh_strip:
dh_strip -pkodi-pvr-octonet --dbg-package=kodi-pvr-octonet-dbg
dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=1
override_dh_installdocs:
dh_installdocs --link-doc=kodi-pvr-octonet

View File

@@ -1 +1 @@
3.0 (quilt)
3.0 (native)

View File

@@ -1,7 +1,6 @@
cmake_minimum_required(VERSION 3.5)
project(jsoncpp)
cmake_minimum_required(VERSION 3.1)
enable_language(CXX)
SET(CMAKE_CXX_STANDARD 11)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)
SET(CMAKE_CXX_EXTENSIONS OFF)

View File

@@ -1,16 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="pvr.octonet"
version="1.1.0"
name="Digital Devices Octopus NET Client"
provider-name="digitaldevices">
<requires>@ADDON_DEPENDS@</requires>
<extension
point="xbmc.pvrclient"
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
<extension point="xbmc.addon.metadata">
<summary lang="de_DE">Kodi PVR Addon für Digital Devices Octopus NET Streams</summary>
<summary lang="en_US">Kodi PVR Addon for Digital Devices Octopus NET Streams</summary>
<platform>@PLATFORM@</platform>
</extension>
id="pvr.octonet"
version="20.3.0"
name="Digital Devices Octopus NET Client"
provider-name="digitaldevices">
<requires>@ADDON_DEPENDS@</requires>
<extension
point="kodi.pvrclient"
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
<extension point="xbmc.addon.metadata">
<platform>@PLATFORM@</platform>
<license>GPL-2.0-or-later</license>
<source>https://github.com/DigitalDevices/pvr.octonet</source>
<news>
</news>
<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>

View File

@@ -5,17 +5,21 @@
msgid ""
msgstr ""
"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"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Language: de_DE\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"
msgid "Octonet Server Address"
msgstr ""

View File

@@ -5,17 +5,21 @@
msgid ""
msgstr ""
"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"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\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"
msgid "Octonet Server Address"
msgstr ""

View File

@@ -1,5 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>
<!-- Octonet Server Address -->
<setting id="octonetAddress" type="text" label="30000" default="" />
<settings version="1">
<section id="pvr.octonet">
<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>

View File

@@ -1,369 +1,457 @@
/*
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSE.md for more information.
*
*/
#include "OctonetData.h"
#include "rtsp_client.hpp"
#include <json/json.h>
#include <kodi/Filesystem.h>
#include <kodi/General.h>
#include <sstream>
#include <string>
#include <json/json.h>
#include "OctonetData.h"
#ifdef __WINDOWS__
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define timegm _mkgmtime
#endif
using namespace ADDON;
OctonetData::OctonetData()
OctonetData::OctonetData(const std::string& octonetAddress,
const kodi::addon::IInstanceInfo& instance)
: kodi::addon::CInstancePVRClient(instance)
{
serverAddress = octonetAddress;
channels.clear();
groups.clear();
lastEpgLoad = 0;
m_serverAddress = octonetAddress;
m_channels.clear();
m_groups.clear();
m_lastEpgLoad = 0;
if (!loadChannelList())
libKodi->QueueNotification(QUEUE_ERROR, libKodi->GetLocalizedString(30001), channels.size());
if (!LoadChannelList())
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)
{
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;
int64_t nativeId = hash_fn(id);
capabilities.SetSupportsTV(true);
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;
void *f = libKodi->OpenFile(("http://" + serverAddress + "/channellist.lua?select=json").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 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;
name = "Digital Devices Octopus NET Client";
return PVR_ERROR_NO_ERROR;
}
OctonetChannel* OctonetData::findChannel(int64_t nativeId)
PVR_ERROR OctonetData::GetBackendVersion(std::string& version)
{
std::vector<OctonetChannel>::iterator it;
for (it = channels.begin(); it < channels.end(); ++it) {
if (it->nativeId == nativeId)
return &*it;
}
return NULL;
version = STR(OCTONET_VERSION);
return PVR_ERROR_NO_ERROR;
}
time_t OctonetData::parseDateTime(std::string date)
PVR_ERROR OctonetData::GetConnectionString(std::string& connection)
{
struct tm timeinfo;
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);
connection = "connected"; // FIXME: translate?
return PVR_ERROR_NO_ERROR;
}
bool OctonetData::loadEPG(void)
PVR_ERROR OctonetData::GetBackendHostname(std::string& hostname)
{
/* Reload at most every 30 seconds */
if (lastEpgLoad + 30 > time(NULL))
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;
hostname = m_serverAddress;
return PVR_ERROR_NO_ERROR;
}
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++)
{
OctonetChannel &channel = channels.at(i);
if (channel.radio == bRadio)
{
PVR_CHANNEL chan;
memset(&chan, 0, sizeof(PVR_CHANNEL));
std::hash<std::string> hash_fn;
int64_t nativeId = hash_fn(id);
chan.iUniqueId = channel.id;
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;
return nativeId;
}
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++)
{
OctonetChannel &chan = channels.at(i);
if (iChannelUid != chan.id)
continue;
std::string jsonContent;
kodi::vfs::CFile f;
if (!f.OpenFile("http://" + m_serverAddress + "/channellist.lua?select=json", 0))
return false;
if(chan.epg.empty()) {
loadEPG();
}
char buf[1024];
while (int read = f.Read(buf, 1024))
jsonContent.append(buf, read);
// FIXME: Check if reload is needed!?
f.Close();
std::vector<OctonetEpgEntry>::iterator it;
time_t last_end = 0;
for (it = chan.epg.begin(); it != chan.epg.end(); ++it) {
if (it->end > last_end)
last_end = it->end;
Json::Value root;
Json::Reader reader;
if (it->end < start || it->start > end) {
continue;
}
if (!reader.parse(jsonContent, root, false))
return false;
EPG_TAG entry;
memset(&entry, 0, sizeof(EPG_TAG));
const Json::Value groupList = root["GroupList"];
for (unsigned int i = 0; i < groupList.size(); i++)
{
const Json::Value channelList = groupList[i]["ChannelList"];
OctonetGroup group;
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;
group.name = groupList[i]["Title"].asString();
group.radio = group.name.compare(0, 5, "Radio") ? false : true;
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)
loadEPG();
chan.name = channel["Title"].asString();
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) {
if (it->end < start || it->start > end) {
continue;
}
chan.id = 1000 + m_channels.size();
group.members.push_back(m_channels.size());
m_channels.push_back(chan);
}
m_groups.push_back(group);
}
EPG_TAG entry;
memset(&entry, 0, sizeof(EPG_TAG));
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;
return true;
}
const std::string& OctonetData::getUrl(int id) const {
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)
OctonetChannel* OctonetData::FindChannel(int64_t nativeId)
{
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++)
{
OctonetGroup &group = groups.at(i);
if (group.radio == bRadio)
{
PVR_CHANNEL_GROUP g;
memset(&g, 0, sizeof(PVR_CHANNEL_GROUP));
struct tm timeinfo;
g.iPosition = 0;
g.bIsRadio = group.radio;
strncpy(g.strGroupName, group.name.c_str(), strlen(group.name.c_str()));
memset(&timeinfo, 0, sizeof(timeinfo));
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);
if (g == NULL)
return PVR_ERROR_UNKNOWN;
/* Reload at most every 30 seconds */
if (m_lastEpgLoad + 30 > time(nullptr))
return false;
for (unsigned int i = 0; i < g->members.size(); i++)
{
OctonetChannel &channel = channels.at(g->members[i]);
PVR_CHANNEL_GROUP_MEMBER m;
memset(&m, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER));
std::string jsonContent;
kodi::vfs::CFile f;
if (!f.OpenFile("http://" + m_serverAddress + "/epg.lua?;#|encoding=gzip", 0))
return false;
strncpy(m.strGroupName, group.strGroupName, strlen(group.strGroupName));
m.iChannelUniqueId = channel.id;
m.iChannelNumber = channel.id;
char buf[1024];
while (int read = f.Read(buf, 1024))
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++)
{
if (groups.at(i).name == name)
return &groups.at(i);
}
return NULL;
return;
}
PVR_ERROR OctonetData::GetChannelsAmount(int& amount)
{
amount = m_channels.size();
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();
}

View File

@@ -1,92 +1,101 @@
#pragma once
/*
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSE.md for more information.
*
*/
#include <vector>
#pragma once
#include "p8-platform/threads/threads.h"
#include "client.h"
#include <atomic>
#include <kodi/addon-instance/PVR.h>
#include <thread>
#include <vector>
struct OctonetEpgEntry
{
int64_t channelId;
time_t start;
time_t end;
int id;
std::string title;
std::string subtitle;
int64_t channelId;
time_t start;
time_t end;
int id;
std::string title;
std::string subtitle;
};
struct OctonetChannel
{
int64_t nativeId;
std::string name;
std::string url;
bool radio;
int id;
int64_t nativeId;
std::string name;
std::string url;
bool radio;
int id;
std::vector<OctonetEpgEntry> epg;
std::vector<OctonetEpgEntry> epg;
};
struct OctonetGroup
{
std::string name;
bool radio;
std::vector<int> members;
std::string name;
bool radio;
std::vector<int> members;
};
class OctonetData : public P8PLATFORM::CThread
class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient
{
public:
OctonetData(void);
virtual ~OctonetData(void);
public:
OctonetData(const std::string& octonetAddress,
const kodi::addon::IInstanceInfo& instance);
~OctonetData() override;
virtual int getChannelCount(void);
virtual PVR_ERROR getChannels(ADDON_HANDLE handle, bool bRadio);
PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override;
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);
virtual PVR_ERROR getGroups(ADDON_HANDLE handle, bool bRadio);
virtual PVR_ERROR getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group);
PVR_ERROR OnSystemSleep() override;
PVR_ERROR OnSystemWake() override;
virtual PVR_ERROR getEPG(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end);
const std::string& getUrl(int id) const;
const std::string& getName(int id) const;
PVR_ERROR GetChannelsAmount(int& amount) override;
PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results) override;
protected:
virtual bool loadChannelList(void);
virtual bool loadEPG(void);
virtual OctonetGroup* findGroup(const std::string &name);
PVR_ERROR GetChannelGroupsAmount(int& amount) override;
PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override;
PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
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);
time_t parseDateTime(std::string date);
int64_t parseID(std::string id);
bool OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) override;
int ReadLiveStream(unsigned char* buffer, unsigned int size) override;
void CloseLiveStream() override;
private:
std::string serverAddress;
std::vector<OctonetChannel> channels;
std::vector<OctonetGroup> groups;
protected:
void Process();
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;
};

View File

@@ -1,30 +1,17 @@
/*
* Copyright (C) 2005-2011 Team XBMC
* http://www.xbmc.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSE.md for more information.
*/
#include "libXBMC_addon.h"
#include <string>
#include "p8-platform/os.h"
#include "client.h"
#include "Socket.h"
#include <cstdio>
#include <kodi/General.h>
#include <string>
using namespace std;
using namespace ADDON;
namespace OCTO
{
@@ -32,28 +19,31 @@ namespace OCTO
/* Master defines for client control */
#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;
_family = family;
_domain = domain;
_type = type;
_protocol = protocol;
_port = 0;
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
m_sd = INVALID_SOCKET;
m_family = family;
m_domain = domain;
m_type = type;
m_protocol = protocol;
m_port = 0;
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
}
Socket::Socket()
{
// Default constructor, default settings
_sd = INVALID_SOCKET;
_family = af_inet;
_domain = pf_inet;
_type = sock_stream;
_protocol = tcp;
_port = 0;
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
m_sd = INVALID_SOCKET;
m_family = af_inet;
m_domain = pf_inet;
m_type = sock_stream;
m_protocol = tcp;
m_port = 0;
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
}
@@ -65,7 +55,7 @@ Socket::~Socket()
bool Socket::setHostname(const std::string& host)
{
_hostname = host;
m_hostname = host;
return true;
}
@@ -73,9 +63,9 @@ bool Socket::close()
{
if (is_valid())
{
if (_sd != SOCKET_ERROR)
closesocket(_sd);
_sd = INVALID_SOCKET;
if (m_sd != SOCKET_ERROR)
closesocket(m_sd);
m_sd = INVALID_SOCKET;
return true;
}
return false;
@@ -85,7 +75,7 @@ bool Socket::create()
{
close();
if(!osInit())
if (!osInit())
{
return false;
}
@@ -94,25 +84,25 @@ bool Socket::create()
}
bool Socket::bind ( const unsigned short port )
bool Socket::bind(const unsigned short port)
{
if (is_valid())
{
close();
close();
}
_sd = socket(_family, _type, _protocol);
_port = port;
_sockaddr.sin_family = (sa_family_t) _family;
_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
_sockaddr.sin_port = htons( _port );
m_sd = socket(m_family, m_type, m_protocol);
m_port = port;
m_sockaddr.sin_family = (sa_family_t)m_family;
m_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
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;
}
@@ -128,13 +118,13 @@ bool Socket::listen() const
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.
//linux 128//MAXCONNECTIONS =1
if (listen_return == -1)
{
errormessage( getLastError(), "Socket::listen" );
errormessage(getLastError(), "Socket::listen");
return false;
}
@@ -142,23 +132,24 @@ bool Socket::listen() const
}
bool Socket::accept ( Socket& new_socket ) const
bool Socket::accept(Socket& new_socket) const
{
if (!is_valid())
{
return false;
}
socklen_t addr_length = sizeof( _sockaddr );
new_socket._sd = ::accept(_sd, const_cast<sockaddr*>( (const sockaddr*) &_sockaddr), &addr_length );
socklen_t addr_length = sizeof(m_sockaddr);
new_socket.m_sd =
::accept(m_sd, const_cast<sockaddr*>((const sockaddr*)&m_sockaddr), &addr_length);
#ifdef TARGET_WINDOWS
if (new_socket._sd == INVALID_SOCKET)
if (new_socket.m_sd == INVALID_SOCKET)
#else
if (new_socket._sd <= 0)
if (new_socket.m_sd <= 0)
#endif
{
errormessage( getLastError(), "Socket::accept" );
errormessage(getLastError(), "Socket::accept");
return false;
}
@@ -166,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;
struct timeval tv;
int result;
int result;
if (!is_valid())
{
@@ -184,35 +175,35 @@ int Socket::send ( const char* data, const unsigned int len )
}
// fill with new data
tv.tv_sec = 0;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&set_w);
FD_ZERO(&set_e);
FD_SET(_sd, &set_w);
FD_SET(_sd, &set_e);
FD_SET(m_sd, &set_w);
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)
{
libKodi->Log(LOG_ERROR, "Socket::send - select failed");
kodi::Log(ADDON_LOG_ERROR, "Socket::send - select failed");
close();
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();
return 0;
}
int status = ::send(_sd, data, len, 0 );
int status = ::send(m_sd, data, len, 0);
if (status == -1)
{
errormessage( getLastError(), "Socket::send");
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
errormessage(getLastError(), "Socket::send");
kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data");
close();
return 0;
}
@@ -220,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 i;
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)
{
errormessage( getLastError(), "Socket::sendto");
errormessage(getLastError(), "Socket::sendto");
osCleanup();
return i;
}
sentbytes += i;
} while ( (sentbytes < (int) size) && (sendcompletebuffer == true));
} while ((sentbytes < (int)size) && (sendcompletebuffer == true));
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;
if (!is_valid())
@@ -252,10 +243,10 @@ int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
return 0;
}
buf = new char [ minpacketsize + 1 ];
memset ( buf, 0, minpacketsize + 1 );
buf = new char[minpacketsize + 1];
memset(buf, 0, minpacketsize + 1);
status = receive( buf, minpacketsize, minpacketsize );
status = receive(buf, minpacketsize, minpacketsize);
data = buf;
@@ -265,12 +256,12 @@ int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
//Receive until error or \n
bool Socket::ReadLine (string& line)
bool Socket::ReadLine(string& line)
{
fd_set set_r, set_e;
timeval timeout;
int retries = 6;
char buffer[2048];
fd_set set_r, set_e;
timeval timeout;
int retries = 6;
char buffer[2048];
if (!is_valid())
return false;
@@ -284,20 +275,20 @@ bool Socket::ReadLine (string& line)
return true;
}
timeout.tv_sec = RECEIVE_TIMEOUT;
timeout.tv_sec = RECEIVE_TIMEOUT;
timeout.tv_usec = 0;
// fill with new data
FD_ZERO(&set_r);
FD_ZERO(&set_e);
FD_SET(_sd, &set_r);
FD_SET(_sd, &set_e);
int result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout);
FD_SET(m_sd, &set_r);
FD_SET(m_sd, &set_e);
int result = select(FD_SETSIZE, &set_r, nullptr, &set_e, &timeout);
if (result < 0)
{
libKodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__);
errormessage(getLastError(), __FUNCTION__);
kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__);
errormessage(getLastError(), __func__);
close();
return false;
}
@@ -306,20 +297,24 @@ bool Socket::ReadLine (string& line)
{
if (retries != 0)
{
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries);
retries--;
kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__,
retries);
retries--;
continue;
} else {
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__);
return false;
}
else
{
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)
{
libKodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__);
errormessage(getLastError(), __FUNCTION__);
kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__);
errormessage(getLastError(), __func__);
close();
return false;
}
@@ -332,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];
int status = 0;
if ( !is_valid() )
if (!is_valid())
{
return 0;
}
memset ( buf, 0, MAXRECV + 1 );
status = receive( buf, MAXRECV, 0 );
memset(buf, 0, MAXRECV + 1);
status = receive(buf, MAXRECV, 0);
data = buf;
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;
if ( !is_valid() )
if (!is_valid())
{
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;
}
@@ -375,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;
}
bool Socket::connect ( const std::string& host, const unsigned short port )
bool Socket::connect(const std::string& host, const unsigned short port)
{
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;
}
_port = port;
m_port = port;
char strPort[15];
snprintf(strPort, 15, "%hu", port);
struct addrinfo hints;
struct addrinfo* result = NULL;
struct addrinfo *address = NULL;
struct addrinfo* result = nullptr;
struct addrinfo* address = nullptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = _family;
hints.ai_socktype = _type;
hints.ai_protocol = _protocol;
hints.ai_family = m_family;
hints.ai_socktype = m_type;
hints.ai_protocol = m_protocol;
int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
if (retval != 0)
@@ -412,18 +412,18 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
return false;
}
for (address = result; address != NULL; address = address->ai_next)
for (address = result; address != nullptr; address = address->ai_next)
{
// 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");
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)
{
close();
@@ -436,9 +436,9 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
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");
close();
return false;
@@ -449,123 +449,124 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
bool Socket::reconnect()
{
if ( is_valid() )
if (is_valid())
{
return true;
}
return connect(_hostname, _port);
return connect(m_hostname, m_port);
}
bool Socket::is_valid() const
{
return (_sd != INVALID_SOCKET);
return (m_sd != INVALID_SOCKET);
}
#if defined(TARGET_WINDOWS)
bool Socket::set_non_blocking ( const bool b )
bool Socket::set_non_blocking(const bool b)
{
u_long iMode;
if ( b )
iMode = 1; // enable non_blocking
if (b)
iMode = 1; // enable non_blocking
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 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)
{
case WSANOTINITIALISED:
errmsg = "A successful WSAStartup call must occur before using this function.";
break;
case WSAENETDOWN:
errmsg = "The network subsystem or the associated service provider has failed";
break;
case WSA_NOT_ENOUGH_MEMORY:
errmsg = "Insufficient memory available";
break;
case WSA_INVALID_PARAMETER:
errmsg = "One or more parameters are invalid";
break;
case WSA_OPERATION_ABORTED:
errmsg = "Overlapped operation aborted";
break;
case WSAEINTR:
errmsg = "Interrupted function call";
break;
case WSAEBADF:
errmsg = "File handle is not valid";
break;
case WSAEACCES:
errmsg = "Permission denied";
break;
case WSAEFAULT:
errmsg = "Bad address";
break;
case WSAEINVAL:
errmsg = "Invalid argument";
break;
case WSAENOTSOCK:
errmsg = "Socket operation on nonsocket";
break;
case WSAEDESTADDRREQ:
errmsg = "Destination address required";
break;
case WSAEMSGSIZE:
errmsg = "Message too long";
break;
case WSAEPROTOTYPE:
errmsg = "Protocol wrong type for socket";
break;
case WSAENOPROTOOPT:
errmsg = "Bad protocol option";
break;
case WSAEPFNOSUPPORT:
errmsg = "Protocol family not supported";
break;
case WSAEAFNOSUPPORT:
errmsg = "Address family not supported by protocol family";
break;
case WSAEADDRINUSE:
errmsg = "Address already in use";
break;
case WSAECONNRESET:
errmsg = "Connection reset by peer";
break;
case WSAHOST_NOT_FOUND:
errmsg = "Authoritative answer host not found";
break;
case WSATRY_AGAIN:
errmsg = "Nonauthoritative host not found, or server failure";
break;
case WSAEISCONN:
errmsg = "Socket is already connected";
break;
case WSAETIMEDOUT:
errmsg = "Connection timed out";
break;
case WSAECONNREFUSED:
errmsg = "Connection refused";
break;
case WSANO_DATA:
errmsg = "Valid name, no data record of requested type";
break;
default:
errmsg = "WSA Error";
case WSANOTINITIALISED:
errmsg = "A successful WSAStartup call must occur before using this function.";
break;
case WSAENETDOWN:
errmsg = "The network subsystem or the associated service provider has failed";
break;
case WSA_NOT_ENOUGH_MEMORY:
errmsg = "Insufficient memory available";
break;
case WSA_INVALID_PARAMETER:
errmsg = "One or more parameters are invalid";
break;
case WSA_OPERATION_ABORTED:
errmsg = "Overlapped operation aborted";
break;
case WSAEINTR:
errmsg = "Interrupted function call";
break;
case WSAEBADF:
errmsg = "File handle is not valid";
break;
case WSAEACCES:
errmsg = "Permission denied";
break;
case WSAEFAULT:
errmsg = "Bad address";
break;
case WSAEINVAL:
errmsg = "Invalid argument";
break;
case WSAENOTSOCK:
errmsg = "Socket operation on nonsocket";
break;
case WSAEDESTADDRREQ:
errmsg = "Destination address required";
break;
case WSAEMSGSIZE:
errmsg = "Message too long";
break;
case WSAEPROTOTYPE:
errmsg = "Protocol wrong type for socket";
break;
case WSAENOPROTOOPT:
errmsg = "Bad protocol option";
break;
case WSAEPFNOSUPPORT:
errmsg = "Protocol family not supported";
break;
case WSAEAFNOSUPPORT:
errmsg = "Address family not supported by protocol family";
break;
case WSAEADDRINUSE:
errmsg = "Address already in use";
break;
case WSAECONNRESET:
errmsg = "Connection reset by peer";
break;
case WSAHOST_NOT_FOUND:
errmsg = "Authoritative answer host not found";
break;
case WSATRY_AGAIN:
errmsg = "Nonauthoritative host not found, or server failure";
break;
case WSAEISCONN:
errmsg = "Socket is already connected";
break;
case WSAETIMEDOUT:
errmsg = "Connection timed out";
break;
case WSAECONNREFUSED:
errmsg = "Connection refused";
break;
case WSANO_DATA:
errmsg = "Valid name, no data record of requested type";
break;
default:
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
@@ -579,15 +580,15 @@ bool Socket::osInit()
{
win_usage_count++;
// initialize winsock:
if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0)
if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0)
{
return false;
}
WORD wVersionRequested = MAKEWORD(2,2);
WORD wVersionRequested = MAKEWORD(2, 2);
// check version
if (_wsaData.wVersion != wVersionRequested)
if (m_wsaData.wVersion != wVersionRequested)
{
return false;
}
@@ -598,42 +599,42 @@ bool Socket::osInit()
void Socket::osCleanup()
{
win_usage_count--;
if(win_usage_count == 0)
if (win_usage_count == 0)
{
WSACleanup();
}
}
#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;
opts = fcntl(_sd, F_GETFL);
opts = fcntl(m_sd, F_GETFL);
if ( opts < 0 )
if (opts < 0)
{
return false;
}
if ( b )
opts = ( opts | O_NONBLOCK );
if (b)
opts = (opts | O_NONBLOCK);
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 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
errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block";
@@ -660,7 +661,8 @@ void Socket::errormessage( int errnum, const char* functionname) const
errmsg = "ENOTSOCK: The argument is not a valid socket";
break;
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;
case ENOBUFS:
errmsg = "ENOBUFS: The output queue for a network interface was full";
@@ -672,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";
break;
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;
case EAFNOSUPPORT:
errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family";
@@ -684,13 +687,16 @@ void Socket::errormessage( int errnum, const char* functionname) const
errmsg = "EMFILE: Process file table overflow";
break;
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;
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;
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;
//case E:
// errmsg = "";
@@ -699,7 +705,7 @@ void Socket::errormessage( int errnum, const char* functionname) const
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

View File

@@ -1,83 +1,72 @@
/*
* Copyright (C) 2005-2011 Team XBMC
* http://www.xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* 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 platform specific datatypes, header files, defines and constants:
#if defined TARGET_WINDOWS
#define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
#pragma warning(disable:4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma warning(default:4005)
#include <windows.h>
#define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
#pragma warning(disable : 4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
#include <WS2tcpip.h>
#include <winsock2.h>
#pragma warning(default : 4005)
#include <windows.h>
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif
#ifndef socklen_t
typedef int socklen_t;
#endif
#ifndef ipaddr_t
typedef unsigned long ipaddr_t;
#endif
#ifndef port_t
typedef unsigned short port_t;
#endif
#ifndef sa_family_t
#define sa_family_t ADDRESS_FAMILY
#endif
#ifndef socklen_t
typedef int socklen_t;
#endif
#ifndef ipaddr_t
typedef unsigned long ipaddr_t;
#endif
#ifndef port_t
typedef unsigned short port_t;
#endif
#ifndef sa_family_t
#define sa_family_t ADDRESS_FAMILY
#endif
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
#ifdef SOCKADDR_IN
#undef SOCKADDR_IN
#endif
#include <sys/types.h> /* for socket,connect */
#include <sys/socket.h> /* for socket,connect */
#include <sys/un.h> /* for Unix socket */
#include <arpa/inet.h> /* for inet_pton */
#include <netdb.h> /* for gethostbyname */
#include <netinet/in.h> /* for htons */
#include <unistd.h> /* for read, write, close */
#include <errno.h>
#include <fcntl.h>
#include <arpa/inet.h> /* for inet_pton */
#include <errno.h>
#include <fcntl.h>
#include <netdb.h> /* for gethostbyname */
#include <netinet/in.h> /* for htons */
#include <sys/socket.h> /* for socket,connect */
#include <sys/types.h> /* for socket,connect */
#include <sys/un.h> /* for Unix socket */
#include <unistd.h> /* for read, write, close */
typedef int SOCKET;
typedef sockaddr SOCKADDR;
typedef sockaddr_in SOCKADDR_IN;
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (-1)
#endif
#define SOCKET_ERROR (-1)
typedef int SOCKET;
typedef sockaddr SOCKADDR;
typedef sockaddr_in SOCKADDR_IN;
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (-1)
#endif
#define SOCKET_ERROR (-1)
#define closesocket(sd) ::close(sd)
#define closesocket(sd) ::close(sd)
#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
#include <string>
#include <vector>
namespace OCTO
{
#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
#define MAXRECV 1500 ///< Maximum packet size
#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
#define MAXRECV 1500 ///< Maximum packet size
enum SocketFamily
{
@@ -88,10 +77,10 @@ enum SocketFamily
enum SocketDomain
{
#if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
pf_unix = PF_UNIX,
pf_local = PF_LOCAL,
#endif
#if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
pf_unix = PF_UNIX,
pf_local = PF_LOCAL,
#endif
pf_inet = PF_INET
};
@@ -109,197 +98,187 @@ enum SocketProtocol
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();
/*!
* 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 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
* \param family Can be af_inet or af_inet6. Default: af_inet
*/
void setFamily(const enum SocketFamily family)
{
_family = family;
};
/*!
* Socket setDomain
* \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
*/
void setDomain(const enum SocketDomain domain) { m_domain = domain; };
/*!
* Socket setDomain
* \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
*/
void setDomain(const enum SocketDomain domain)
{
_domain = domain;
};
/*!
* Socket setType
* \param type Can be sock_stream or sock_dgram. Default: sock_stream.
*/
void setType(const enum SocketType type) { m_type = type; };
/*!
* Socket setType
* \param type Can be sock_stream or sock_dgram. Default: sock_stream.
*/
void setType(const enum SocketType type)
{
_type = type;
};
/*!
* Socket setProtocol
* \param protocol Can be tcp or udp. Default: tcp.
*/
void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; };
/*!
* Socket setProtocol
* \param protocol Can be tcp or udp. Default: tcp.
*/
void setProtocol(const enum SocketProtocol protocol)
{
_protocol = protocol;
};
/*!
* Socket setPort
* \param port port number for socket communication
*/
void setPort(const unsigned short port) { m_sockaddr.sin_port = htons(port); };
/*!
* 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);
bool setHostname ( const std::string& host );
// Server initialization
// Server initialization
/*!
* Socket create
* Create a new socket
* \return True if succesful
*/
bool create();
/*!
* Socket create
* Create a new socket
* \return True if succesful
*/
bool create();
/*!
* Socket close
* Close the socket
* \return True if succesful
*/
bool close();
/*!
* Socket close
* Close the socket
* \return True if succesful
*/
bool close();
/*!
* Socket bind
*/
bool bind(const unsigned short port);
bool listen() const;
bool accept(Socket& socket) const;
/*!
* Socket bind
*/
bool bind ( const unsigned short port );
bool listen() const;
bool accept ( Socket& socket ) const;
// Client initialization
bool connect(const std::string& host, const unsigned short port);
// Client initialization
bool connect ( const std::string& host, const unsigned short port );
bool reconnect();
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
*
* \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
*
* \param data Pointer to a character array of size 'size' with the data to transmit
* \param size Length of the data to transmit
* \return Number of bytes send or -1 in case of an error
*/
int send(const char* data, const unsigned int size);
/*!
* Socket send function
*
* \param data Pointer to a character array of size 'size' with the data to transmit
* \param size Length of the data to transmit
* \return Number of bytes send or -1 in case of an error
*/
int send ( const char* data, const unsigned int size );
/*!
* Socket sendto function
*
* \param data Reference to a std::string with the data to transmit
* \param size Length of the data to transmit
* \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 sendto(const char* data, unsigned int size, bool sendcompletebuffer = false);
// Data Receive
/*!
* Socket sendto function
*
* \param data Reference to a std::string with the data to transmit
* \param size Length of the data to transmit
* \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 sendto ( const char* data, unsigned int size, bool sendcompletebuffer = false);
// Data Receive
/*!
* Socket receive function
*
* \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
*/
int receive(std::string& data, unsigned int minpacketsize) const;
/*!
* Socket receive function
*
* \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
*/
int receive ( std::string& data, unsigned int minpacketsize ) const;
/*!
* Socket receive function
*
* \param data Reference to a std::string for storage of the received data.
* \return Number of bytes received or SOCKET_ERROR
*/
int receive(std::string& data) const;
/*!
* Socket receive function
*
* \param data Reference to a std::string for storage of the received data.
* \return Number of bytes received or SOCKET_ERROR
*/
int receive ( std::string& data ) const;
/*!
* Socket receive 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 minpacketsize Specifies the minimum number of bytes that need to be received before returning
* \return Number of bytes received or SOCKET_ERROR
*/
int receive(char* data, const unsigned int buffersize, const unsigned int minpacketsize) const;
/*!
* Socket receive 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 minpacketsize Specifies the minimum number of bytes that need to be received before returning
* \return Number of bytes received or SOCKET_ERROR
*/
int receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const;
/*!
* 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 = nullptr,
socklen_t* fromlen = nullptr) const;
/*!
* 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 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
SOCKADDR_IN _sockaddr; ///< Socket Address
//struct addrinfo* _addrinfo; ///< Socket address info
std::string _hostname; ///< Hostname
unsigned short _port; ///< Port number
#ifdef TARGET_WINDOWS
WSADATA m_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
enum SocketFamily _family; ///< Socket Address Family
enum SocketProtocol _protocol; ///< Socket Protocol
enum SocketType _type; ///< Socket Type
enum SocketDomain _domain; ///< Socket domain
#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();
void errormessage(int errornum, const char* functionname = nullptr) const;
int getLastError(void) const;
bool osInit();
void osCleanup();
};
} //namespace OCTO

58
src/addon.cpp Normal file
View 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
View 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;
};

View File

@@ -1,286 +0,0 @@
/*
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*/
#include "client.h"
#include <xbmc_pvr_dll.h>
#include <libXBMC_addon.h>
#include <p8-platform/util/util.h>
#include <libKODI_guilib.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, void* props)
{
if (callbacks == NULL || props == NULL)
return ADDON_STATUS_UNKNOWN;
PVR_PROPERTIES *pvrprops = (PVR_PROPERTIES*)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 GetAddonCapabilities(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 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 SignalStatus(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(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();
}
}

View File

@@ -1,35 +0,0 @@
#pragma once
/*
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*/
#include "libXBMC_addon.h"
#include "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;

View File

@@ -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 "Socket.h"
#include <algorithm>
#include <cctype>
#include <iterator>
#include "Socket.h"
#include "client.h"
#include <p8-platform/util/util.h>
#include <libXBMC_addon.h>
#include <cstring>
#include <iterator>
#include <sstream>
#if defined(_WIN32) || defined(_WIN64)
#define strtok_r strtok_s
#define strncasecmp _strnicmp
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))
return -1;
return vsprintf(*sptr, fmt, argv);
int vasprintf(char** sptr, char* fmt, va_list argv)
{
int wanted = vsnprintf(*sptr = nullptr, 0, fmt, argv);
if ((wanted < 0) || ((*sptr = (char*)malloc(1 + wanted)) == nullptr))
return -1;
return vsprintf(*sptr, fmt, argv);
}
int asprintf(char **sptr, char *fmt, ...) {
int retval;
va_list argv;
va_start(argv, fmt);
retval = vasprintf(sptr, fmt, argv);
va_end(argv);
return retval;
int asprintf(char** sptr, char* fmt, ...)
{
int retval;
va_list argv;
va_start(argv, fmt);
retval = vasprintf(sptr, fmt, argv);
va_end(argv);
return retval;
}
#endif
@@ -40,441 +48,501 @@ int asprintf(char **sptr, char *fmt, ...) {
#define RTCP_BUFFER_SIZE 1024
using namespace std;
using namespace ADDON;
using namespace OCTO;
enum rtsp_state {
RTSP_IDLE,
RTSP_DESCRIBE,
RTSP_SETUP,
RTSP_PLAY,
RTSP_RUNNING
enum rtsp_state
{
RTSP_IDLE,
RTSP_DESCRIBE,
RTSP_SETUP,
RTSP_PLAY,
RTSP_RUNNING
};
enum rtsp_result {
RTSP_RESULT_OK = 200,
enum rtsp_result
{
RTSP_RESULT_OK = 200,
};
struct rtsp_client {
char *content_base;
char *control;
char session_id[64];
uint16_t stream_id;
int keepalive_interval;
struct rtsp_client
{
char* content_base;
char* control;
char session_id[64];
uint16_t stream_id;
int keepalive_interval;
char udp_address[UDP_ADDRESS_LEN];
uint16_t udp_port;
char udp_address[UDP_ADDRESS_LEN];
uint16_t udp_port;
Socket tcp_sock;
Socket udp_sock;
Socket rtcp_sock;
Socket tcp_sock;
Socket udp_sock;
Socket rtcp_sock;
enum rtsp_state state;
int cseq;
enum rtsp_state state;
int cseq;
size_t fifo_size;
uint16_t last_seq_nr;
size_t fifo_size;
uint16_t last_seq_nr;
string name;
int level;
int quality;
string name;
int level;
int quality;
};
struct url {
string protocol;
string host;
int port;
string path;
struct url
{
string protocol;
string host;
int port;
string path;
};
struct rtcp_app {
uint8_t subtype;
uint8_t pt;
uint16_t len;
uint32_t ssrc;
char name[4];
uint16_t identifier;
uint16_t string_len;
struct rtcp_app
{
uint8_t subtype;
uint8_t pt;
uint16_t len;
uint32_t ssrc;
char name[4];
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 const string prot_end = "://";
static const string host_end = "/";
url result;
static url parse_url(const std::string& str)
{
static const string prot_end = "://";
static const string host_end = "/";
url result;
string::const_iterator begin = str.begin();
string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
result.protocol.reserve(distance(begin, end));
transform(begin, end, back_inserter(result.protocol), ::tolower);
advance(end, prot_end.size());
begin = end;
string::const_iterator begin = str.begin();
string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
result.protocol.reserve(distance(begin, end));
transform(begin, end, back_inserter(result.protocol), ::tolower);
advance(end, prot_end.size());
begin = end;
end = search(begin, str.end(), host_end.begin(), host_end.end());
result.host.reserve(distance(begin, end));
transform(begin, end, back_inserter(result.host), ::tolower);
advance(end, host_end.size());
begin = end;
end = search(begin, str.end(), host_end.begin(), host_end.end());
result.host.reserve(distance(begin, end));
transform(begin, end, back_inserter(result.host), ::tolower);
advance(end, host_end.size());
begin = end;
result.port = RTSP_DEFAULT_PORT;
result.port = RTSP_DEFAULT_PORT;
result.path.reserve(distance(begin, str.end()));
transform(begin, str.end(), back_inserter(result.path), ::tolower);
result.path.reserve(distance(begin, str.end()));
transform(begin, str.end(), back_inserter(result.path), ::tolower);
return result;
return result;
}
void split_string(const string& s, char delim, vector<string>& elems) {
stringstream ss;
ss.str(s);
void split_string(const string& s, char delim, vector<string>& elems)
{
stringstream ss;
ss.str(s);
string item;
while(getline(ss, item, delim)) {
elems.push_back(item);
}
string item;
while (getline(ss, item, delim))
{
elems.push_back(item);
}
}
static int tcp_sock_read_line(string &line) {
static string buf;
static int tcp_sock_read_line(string& line)
{
static string buf;
while(true) {
string::size_type pos = buf.find("\r\n");
if(pos != string::npos) {
line = buf.substr(0, pos);
buf.erase(0, pos + 2);
return 0;
}
while (true)
{
string::size_type pos = buf.find("\r\n");
if (pos != string::npos)
{
line = buf.substr(0, pos);
buf.erase(0, pos + 2);
return 0;
}
char tmp_buf[2048];
int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
if(size <= 0) {
return 1;
}
char tmp_buf[2048];
int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
if (size <= 0)
{
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)
{
stringstream res;
res << u.protocol << "://" << u.host;
if (u.port > 0)
res << ":" << u.port;
res << "/" << u.path;
stringstream res;
res << u.protocol << "://" << u.host;
if (u.port > 0)
res << ":" << u.port;
res << "/" << u.path;
return res.str();
return res.str();
}
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)
static void parse_session(char* request_line, char* session, unsigned max, int* timeout)
{
int p = atoi(str);
if (p < 0 || p > UINT16_MAX)
return -1;
char* state;
char* tok;
*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) {
char *state;
char *tok;
int err;
static int parse_port(char* str, uint16_t* port)
{
int p = atoi(str);
if (p < 0 || p > UINT16_MAX)
return -1;
tok = strtok_r(request_line, ";", &state);
if (tok == NULL || strncmp(tok, "RTP/AVP", 7) != 0)
return -1;
*port = p;
tok = strtok_r(NULL, ";", &state);
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;
return 0;
}
#define skip_whitespace(x) while(*x == ' ') x++
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;
static int parse_transport(char* request_line)
{
char* state;
char* tok;
int err;
/* Parse header */
while (!have_header) {
if (tcp_sock_read_line(in_str) < 0)
break;
in = const_cast<char *>(in_str.c_str());
tok = strtok_r(request_line, ";", &state);
if (tok == nullptr || strncmp(tok, "RTP/AVP", 7) != 0)
return -1;
if (strncmp(in, "RTSP/1.0 ", 9) == 0) {
rtsp_result = atoi(in + 9);
} else if (strncmp(in, "Content-Base:", 13) == 0) {
free(rtsp->content_base);
tok = strtok_r(nullptr, ";", &state);
if (tok == nullptr || strncmp(tok, "multicast", 9) != 0)
return 0;
val = in + 13;
skip_whitespace(val);
while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
{
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);
} else if (strncmp(in, "Content-Length:", 15) == 0) {
val = in + 16;
skip_whitespace(val);
memset(port, 0x00, 6);
strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5));
if ((end = strstr(port, "-")) != nullptr)
*end = '\0';
err = parse_port(port, &rtsp->udp_port);
if (err)
return err;
}
}
content_length = atoi(val);
} else if (strncmp("Session:", in, 8) == 0) {
val = in + 8;
skip_whitespace(val);
return 0;
}
parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval);
} else if (strncmp("Transport:", in, 10) == 0) {
val = in + 10;
skip_whitespace(val);
#define skip_whitespace(x) \
while (*x == ' ') \
x++
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) {
rtsp_result = -1;
break;
}
} else if (strncmp("com.ses.streamID:", in, 17) == 0) {
val = in + 17;
skip_whitespace(val);
/* Parse header */
while (!have_header)
{
if (tcp_sock_read_line(in_str) < 0)
break;
in = const_cast<char*>(in_str.c_str());
rtsp->stream_id = atoi(val);
} else if (in[0] == '\0') {
have_header = true;
}
}
if (strncmp(in, "RTSP/1.0 ", 9) == 0)
{
rtsp_result = atoi(in + 9);
}
else if (strncmp(in, "Content-Base:", 13) == 0)
{
free(rtsp->content_base);
/* Discard further content */
while (content_length > 0 &&
(read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer), min(sizeof(buffer), content_length))))
content_length -= read;
val = in + 13;
skip_whitespace(val);
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)
{
string setup_url_str;
const char *psz_setup_url;
stringstream setup_ss;
stringstream play_ss;
url setup_url;
string setup_url_str;
const char* psz_setup_url;
stringstream setup_ss;
stringstream play_ss;
url setup_url;
rtsp_close();
rtsp = new rtsp_client();
if (rtsp == NULL)
return false;
rtsp_close();
rtsp = new rtsp_client();
if (rtsp == nullptr)
return false;
rtsp->name = name;
rtsp->level = 0;
rtsp->quality = 0;
rtsp->name = name;
rtsp->level = 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);
libKodi->Log(LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
url dst = parse_url(url_str);
kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
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;
}
if (!rtsp->tcp_sock.connect(dst.host, dst.port))
{
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(),
dst.port) < 0) {
rtsp->content_base = NULL;
goto error;
}
if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), dst.port) < 0)
{
rtsp->content_base = nullptr;
goto error;
}
rtsp->last_seq_nr = 0;
rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN);
rtsp->last_seq_nr = 0;
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
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) {
setup_url.protocol = "rtsp";
}
// reverse the satip protocol trick, as SAT>IP believes to be RTSP
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5))
{
setup_url.protocol = "rtsp";
}
setup_url_str = compose_url(setup_url);
psz_setup_url = setup_url_str.c_str();
setup_url_str = compose_url(setup_url);
psz_setup_url = setup_url_str.c_str();
// TODO: Find available port
rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
rtsp->udp_port = 6785;
if(!rtsp->udp_sock.bind(rtsp->udp_port)) {
goto error;
}
// TODO: Find available port
rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
rtsp->udp_port = 6785;
if (!rtsp->udp_sock.bind(rtsp->udp_port))
{
goto error;
}
setup_ss << "SETUP " << setup_url_str<< " RTSP/1.0\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";
rtsp->tcp_sock.send(setup_ss.str());
setup_ss << "SETUP " << setup_url_str << " RTSP/1.0\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";
rtsp->tcp_sock.send(setup_ss.str());
if (rtsp_handle() != RTSP_RESULT_OK) {
libKodi->Log(LOG_ERROR, "Failed to setup RTSP session");
goto error;
}
if (rtsp_handle() != RTSP_RESULT_OK)
{
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) {
rtsp->control = NULL;
goto error;
}
if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0)
{
rtsp->control = nullptr;
goto error;
}
play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
rtsp->tcp_sock.send(play_ss.str());
play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
rtsp->tcp_sock.send(play_ss.str());
if (rtsp_handle() != RTSP_RESULT_OK) {
libKodi->Log(LOG_ERROR, "Failed to play RTSP session");
goto error;
}
if (rtsp_handle() != RTSP_RESULT_OK)
{
kodi::Log(ADDON_LOG_ERROR, "Failed to play RTSP session");
goto error;
}
rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
if(!rtsp->rtcp_sock.bind(rtsp->udp_port + 1)) {
goto error;
}
if(!rtsp->rtcp_sock.set_non_blocking(true)) {
goto error;
}
rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1))
{
goto error;
}
if (!rtsp->rtcp_sock.set_non_blocking(true))
{
goto error;
}
return true;
return true;
error:
rtsp_close();
return false;
rtsp_close();
return false;
}
static void parse_rtcp(const char *buf, int size) {
int offset = 0;
while(size > 4) {
const rtcp_app *app = reinterpret_cast<const rtcp_app *>(buf + offset);
uint16_t len = 4 * (ntohs(app->len) + 1);
static void parse_rtcp(const char* buf, int size)
{
int offset = 0;
while (size > 4)
{
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)) {
size -= len;
offset += len;
continue;
}
if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0))
{
size -= len;
offset += len;
continue;
}
uint16_t string_len = ntohs(app->string_len);
string app_data(&buf[offset + sizeof(rtcp_app)], string_len);
uint16_t string_len = ntohs(app->string_len);
string app_data(&buf[offset + sizeof(rtcp_app)], string_len);
vector<string> elems;
split_string(app_data, ';', elems);
if(elems.size() != 4) {
return;
}
vector<string> elems;
split_string(app_data, ';', elems);
if (elems.size() != 4)
{
return;
}
vector<string> tuner;
split_string(elems[2], ',', tuner);
if(tuner.size() < 4) {
return;
}
vector<string> tuner;
split_string(elems[2], ',', tuner);
if (tuner.size() < 4)
{
return;
}
rtsp->level = atoi(tuner[1].c_str());
rtsp->quality = atoi(tuner[3].c_str());
rtsp->level = atoi(tuner[1].c_str());
rtsp->quality = atoi(tuner[3].c_str());
return;
}
return;
}
}
int rtsp_read(void *buf, unsigned buf_size) {
sockaddr addr;
socklen_t addr_len = sizeof(addr);
int ret = rtsp->udp_sock.recvfrom((char *)buf, buf_size, (sockaddr *)&addr, &addr_len);
int rtsp_read(void* buf, unsigned buf_size)
{
sockaddr addr;
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];
int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr *)&addr, &addr_len);
parse_rtcp(rtcp_buf, rtcp_len);
char rtcp_buf[RTCP_BUFFER_SIZE];
int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr*)&addr, &addr_len);
parse_rtcp(rtcp_buf, rtcp_len);
// TODO: check ip
// TODO: check ip
return ret;
return ret;
}
static void rtsp_teardown() {
if(!rtsp->tcp_sock.is_valid()) {
return;
}
static void rtsp_teardown()
{
if (!rtsp->tcp_sock.is_valid())
{
return;
}
if (rtsp->session_id[0] > 0) {
char *msg;
int len;
stringstream ss;
if (rtsp->session_id[0] > 0)
{
char* msg;
int len;
stringstream ss;
rtsp->udp_sock.close();
rtsp->udp_sock.close();
ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
ss << "CSeq: " << rtsp->cseq++ << "\r\n";
ss << "Session: " << rtsp->session_id << "\r\n\r\n";
rtsp->tcp_sock.send(ss.str());
ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
ss << "CSeq: " << rtsp->cseq++ << "\r\n";
ss << "Session: " << rtsp->session_id << "\r\n\r\n";
rtsp->tcp_sock.send(ss.str());
if (rtsp_handle() != RTSP_RESULT_OK) {
libKodi->Log(LOG_ERROR, "Failed to teardown RTSP session");
return;
}
}
if (rtsp_handle() != RTSP_RESULT_OK)
{
kodi::Log(ADDON_LOG_ERROR, "Failed to teardown RTSP session");
return;
}
}
}
void rtsp_close()
{
if(rtsp) {
rtsp_teardown();
rtsp->tcp_sock.close();
rtsp->udp_sock.close();
rtsp->rtcp_sock.close();
delete rtsp;
rtsp = NULL;
}
if (rtsp)
{
rtsp_teardown();
rtsp->tcp_sock.close();
rtsp->udp_sock.close();
rtsp->rtcp_sock.close();
delete rtsp;
rtsp = nullptr;
}
}
void rtsp_fill_signal_status(PVR_SIGNAL_STATUS& signal_status) {
if(rtsp) {
strncpy(signal_status.strServiceName, rtsp->name.c_str(), PVR_ADDON_NAME_STRING_LENGTH - 1);
signal_status.iSNR = 0x1111 * rtsp->quality;
signal_status.iSignal = 0x101 * rtsp->level;
}
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status)
{
if (rtsp)
{
signal_status.SetAdapterName(rtsp->name);
signal_status.SetSNR(0x1111 * rtsp->quality);
signal_status.SetSignal(0x101 * rtsp->level);
}
}

View File

@@ -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 <xbmc_pvr_types.h>
bool rtsp_open(const std::string& name, const std::string& url_str);
void rtsp_close();
int rtsp_read(void *buf, unsigned buf_size);
void rtsp_fill_signal_status(PVR_SIGNAL_STATUS& signal_status);
#endif
int rtsp_read(void* buf, unsigned buf_size);
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status);