Skip to content

Commit 4a78d27

Browse files
authored
Control access to recordings and timers (#260)
* Control access to recordings and timers Provide options to access Live TV only (no recording or timer access) or to disable timers only, disable recordings and disable recordings delete * Add backend timer rules NextPVR allows timeslot and also episode timers, pvr.nextpvr didn't support all episode single channel timers. Legacy backend code was defaulting the daily timeslot recording to an all episode recording. Added logic for backend type 2 and type 3 timers to handle these situations.
1 parent c66afa5 commit 4a78d27

File tree

9 files changed

+217
-66
lines changed

9 files changed

+217
-66
lines changed

pvr.nextpvr/addon.xml.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<addon
33
id="pvr.nextpvr"
4-
version="21.0.3"
4+
version="21.1.0"
55
name="NextPVR PVR Client"
66
provider-name="Graeme Blackley">
77
<requires>@ADDON_DEPENDS@

pvr.nextpvr/changelog.txt

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v21.0.4
2+
- Allow control of recording and timers access
3+
- Support all episode single channel recordings
4+
- Force Daily recordings to daily timeslot recordings
5+
16
v21.0.3
27
- Translations updates from Weblate
38
- de_de, es_es, fi_fi, it_it, ru_ru

pvr.nextpvr/resources/instance-settings.xml

+41-26
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@
8282
<default>2</default>
8383
<constraints>
8484
<options>
85-
<option label="Real Time">2</option>
86-
<option label="Timeshift">4</option>
87-
<option label="Transcoded">3</option>
85+
<option label="30210">2</option>
86+
<option label="30211">4</option>
87+
<option label="30212">3</option>
8888
</options>
8989
</constraints>
9090
<control format="string" type="spinner"/>
@@ -203,6 +203,13 @@
203203
<default>false</default>
204204
<control type="toggle"/>
205205
</setting>
206+
<setting help="30694" id="genrestring" label="30194" type="boolean">
207+
<level>1</level>
208+
<default>false</default>
209+
<control type="toggle"/>
210+
</setting>
211+
</group>
212+
<group id="13">
206213
<setting help="30680" id="flattenrecording" label="30180" type="boolean">
207214
<level>2</level>
208215
<default>false</default>
@@ -228,23 +235,16 @@
228235
<default>Default</default>
229236
<constraints>
230237
<options>
231-
<option>No</option>
232-
<option>Default</option>
233-
<option>Span</option>
238+
<option label="106">No</option>
239+
<option label="571">Default</option>
240+
<option label="30213">Span</option>
234241
</options>
235242
<allowempty>false</allowempty>
236243
</constraints>
237244
<control type="list" format="string">
238245
<heading>30198</heading>
239246
</control>
240247
</setting>
241-
<setting help="30694" id="genrestring" label="30194" type="boolean">
242-
<level>1</level>
243-
<default>false</default>
244-
<control type="toggle"/>
245-
</setting>
246-
</group>
247-
<group id="13">
248248
<setting help="30699" id="ignorepadding" label="30199" type="boolean">
249249
<level>2</level>
250250
<default>true</default>
@@ -279,6 +279,21 @@
279279
<default>false</default>
280280
<control type="toggle"/>
281281
</setting>
282+
<setting id="accesscontrol" type="integer" label="30209" help="30709">
283+
<level>3</level>
284+
<default>7</default>
285+
<constraints>
286+
<options>
287+
<option label="30214">7</option>
288+
<option label="30215">3</option>
289+
<option label="30216">1</option>
290+
<option label="30217">0</option>
291+
</options>
292+
</constraints>
293+
<control type="list" format="string">
294+
<heading>30709</heading>
295+
</control>
296+
</setting>
282297
<setting help="30688" id="showradio" label="30188" type="boolean">
283298
<level>0</level>
284299
<default>true</default>
@@ -292,19 +307,19 @@
292307
</group>
293308
<group id="15">
294309
<setting id="heartbeat" type="integer" label="30207" help="30707">
295-
<level>2</level>
296-
<default>0</default>
297-
<constraints>
298-
<options>
299-
<option label="13278">0</option>
300-
<option label="30208">1</option>
301-
<option label="33036">2</option>
302-
<option label="1223">3</option>
303-
</options>
304-
</constraints>
305-
<control type="list" format="string">
306-
<heading>32009</heading>
307-
</control>
310+
<level>2</level>
311+
<default>0</default>
312+
<constraints>
313+
<options>
314+
<option label="13278">0</option>
315+
<option label="30208">1</option>
316+
<option label="33036">2</option>
317+
<option label="1223">3</option>
318+
</options>
319+
</constraints>
320+
<control type="list" format="string">
321+
<heading>32009</heading>
322+
</control>
308323
</setting>
309324
</group>
310325
</category>

pvr.nextpvr/resources/language/resource.language.en_gb/strings.po

+44-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,50 @@ msgctxt "#30707"
394394
msgid "Interval to check for backend changes made outside this Kodi client"
395395
msgstr ""
396396

397-
398397
msgctxt "#30208"
399398
msgid "Every 5 minutes"
400399
msgstr ""
400+
401+
msgctxt "#30209"
402+
msgid "Recording and timer control"
403+
msgstr ""
404+
405+
msgctxt "#30709"
406+
msgid "Manage user rights for recordings and timers"
407+
msgstr ""
408+
409+
msgctxt "#30210"
410+
msgid "Real Time"
411+
msgstr ""
412+
413+
msgctxt "#30211"
414+
msgid "Timeshift"
415+
msgstr ""
416+
417+
msgctxt "#30212"
418+
msgid "Transcoded"
419+
msgstr ""
420+
421+
msgctxt "#30213"
422+
msgid "Spann"
423+
msgstr ""
424+
425+
msgctxt "#30214"
426+
msgid "Full access to recordings and timers"
427+
msgstr ""
428+
429+
msgctxt "#30215"
430+
msgid "Play and delete recordings with no timers"
431+
msgstr ""
432+
433+
msgctxt "#30216"
434+
msgid "Play recordings with no timers"
435+
msgstr ""
436+
437+
msgctxt "#30217"
438+
msgid "Live TV only"
439+
msgstr ""
440+
441+
msgctxt "#30218"
442+
msgid "Repeating (all episodes)"
443+
msgstr ""

src/InstanceSettings.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ void InstanceSettings::ReadFromAddon()
7979

8080
m_ignorePadding = ReadBoolSetting("ignorepadding", true);
8181

82-
m_resolution = ReadStringSetting("resolution", "720");
82+
m_resolution = ReadStringSetting("resolution", "720");
83+
84+
m_accessLevel = ReadIntSetting("accesscontrol", ACCESS_RECORDINGS | ACCESS_RECORDINGS_DELETE | ACCESS_RECORDINGS_DELETE);
8385

8486
m_showRadio = ReadBoolSetting("showradio", true);
8587

@@ -134,6 +136,8 @@ void InstanceSettings::ReadFromAddon()
134136
else if (m_heartbeat == eHeartbeat::None)
135137
m_heartbeatInterval = std::numeric_limits<time_t>::max();
136138

139+
if (m_accessLevel == ACCESS_NONE)
140+
m_heartbeatInterval = std::numeric_limits<time_t>::max();
137141

138142
/* Log the current settings for debugging purposes */
139143
kodi::Log(ADDON_LOG_DEBUG, "settings: host='%s', port=%i, instance=%d, mac=%4.4s...", m_hostname.c_str(), m_port, m_instanceNumber, m_hostMACAddress.c_str());
@@ -295,6 +299,8 @@ ADDON_STATUS InstanceSettings::SetValue(const std::string& settingName, const ko
295299
return SetStringSetting<ADDON_STATUS>(settingName, settingValue, m_PIN, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
296300
else if (settingName == "remoteaccess")
297301
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_remoteAccess, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
302+
else if (settingName == "accesscontrol")
303+
return SetSetting<int, ADDON_STATUS>(settingName, settingValue, m_accessLevel, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
298304
else if (settingName == "showradio")
299305
return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_showRadio, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
300306
else if (settingName == "backendresume")

src/InstanceSettings.h

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ namespace NextPVR
5050
constexpr eStreamingMethod DEFAULT_LIVE_STREAM = RealTime;
5151
constexpr time_t DEFAULT_HEARTBEAT = 60;
5252

53+
const int ACCESS_NONE = 0;
54+
const int ACCESS_RECORDINGS = (1 << 0);
55+
const int ACCESS_RECORDINGS_DELETE = (1 << 1);
56+
const int ACCESS_TIMERS = (1 << 2);
57+
5358
class ATTR_DLL_LOCAL InstanceSettings
5459
{
5560
public:
@@ -84,6 +89,7 @@ namespace NextPVR
8489
enum eHeartbeat m_heartbeat;
8590
time_t m_heartbeatInterval;
8691
bool m_instancePriority = true;
92+
int m_accessLevel = ACCESS_RECORDINGS | ACCESS_RECORDINGS_DELETE | ACCESS_RECORDINGS_DELETE;
8793

8894
//Channel
8995
bool m_showRadio = true;

src/Timers.cpp

+82-15
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ PVR_ERROR Timers::GetTimers(kodi::addon::PVRTimersResultSet& results)
117117
tag.SetEndTime(TIMER_DATE_MIN);
118118
tag.SetStartAnyTime(true);
119119
tag.SetEndAnyTime(true);
120+
if (recordingType == 2)
121+
{
122+
tag.SetTimerType(TIMER_REPEATING_EPG_ALL_EPISODES);
123+
}
120124
}
121125
else
122126
{
@@ -390,6 +394,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vector<kodi::addon::PVRTimerType>& types)
390394
static const int MSG_REPEATING_CHILD = 30144;
391395
static const int MSG_REPEATING_KEYWORD = 30145;
392396
static const int MSG_REPEATING_ADVANCED = 30171;
397+
static const int MSG_REPEATING_ALL_EPISODES = 30218;
393398

394399
static const int MSG_KEEPALL = 30150;
395400
static const int MSG_KEEP1 = 30151;
@@ -532,7 +537,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vector<kodi::addon::PVRTimerType>& types)
532537
types.emplace_back(*t);
533538
delete t;
534539

535-
/* Repeating epg based Parent*/
540+
/* Repeating epg based Parent timeslot */
536541
t = new TimerType(
537542
/* Type id. */
538543
TIMER_REPEATING_EPG,
@@ -545,6 +550,21 @@ PVR_ERROR Timers::GetTimerTypes(std::vector<kodi::addon::PVRTimerType>& types)
545550
types.emplace_back(*t);
546551
delete t;
547552

553+
/* Repeating epg based all episode*/
554+
t = new TimerType(
555+
/* Type id. */
556+
TIMER_REPEATING_EPG_ALL_EPISODES,
557+
/* Attributes. */
558+
TIMER_EPG_ATTRIBS | TIMER_REPEATING_EPG_ATTRIBS & ~(PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS),
559+
/* Description. */
560+
GetTimerDescription(MSG_REPEATING_ALL_EPISODES), // "Repeating (All episodes)"
561+
/* Values definitions for attributes. */
562+
recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
563+
types.emplace_back(*t);
564+
delete t;
565+
566+
567+
548568
/* Read-only one-shot for timers generated by timerec */
549569
t = new TimerType(
550570
/* Type id. */
@@ -669,6 +689,7 @@ PVR_ERROR Timers::AddTimer(const kodi::addon::PVRTimer& timer)
669689
const std::string encodedName = UriEncode(timer.GetTitle());
670690
const std::string encodedKeyword = UriEncode(timer.GetEPGSearchString());
671691
const std::string days = GetDayString(timer.GetWeekdays());
692+
672693
const std::string directory = UriEncode(m_settings->m_recordingDirectories[timer.GetRecordingGroup()]);
673694

674695
int epgOid = 0;
@@ -689,7 +710,18 @@ PVR_ERROR Timers::AddTimer(const kodi::addon::PVRTimer& timer)
689710
marginEnd = m_settings->m_defaultPostPadding;
690711
}
691712

692-
switch (timer.GetTimerType())
713+
int timerType = timer.GetTimerType();
714+
size_t countDays = std::count(days.begin(), days.end(), ':');
715+
if (timerType == TIMER_REPEATING_EPG)
716+
{
717+
if (countDays > 1 && countDays < 7)
718+
{
719+
// Backend doesn't support mixed days change to type 2 any episode
720+
timerType = TIMER_REPEATING_EPG_ALL_EPISODES;
721+
}
722+
}
723+
724+
switch (timerType)
693725
{
694726
case TIMER_ONCE_MANUAL:
695727
kodi::Log(ADDON_LOG_DEBUG, "TIMER_ONCE_MANUAL");
@@ -735,7 +767,7 @@ PVR_ERROR Timers::AddTimer(const kodi::addon::PVRTimer& timer)
735767
if (timer.GetEPGSearchString() == TYPE_7_TITLE)
736768
{
737769
kodi::Log(ADDON_LOG_DEBUG, "TIMER_REPEATING_EPG ANY CHANNEL - TYPE 7");
738-
request = kodi::tools::StringUtils::Format("recording.recurring.save&type=7&recurring_id=%d&start_time=%d&end_time=%d&keep=%d&pre_padding=%d&post_padding=%d&day_mask=%s&directory_id=%s%s",
770+
request = kodi::tools::StringUtils::Format("recording.recurring.save&recurring_type=7&recurring_id=%d&start_time=%d&end_time=%d&keep=%d&pre_padding=%d&post_padding=%d&day_mask=%s&directory_id=%s%s",
739771
timer.GetClientIndex(),
740772
static_cast<int>(timer.GetStartTime()),
741773
static_cast<int>(timer.GetEndTime()),
@@ -769,21 +801,56 @@ PVR_ERROR Timers::AddTimer(const kodi::addon::PVRTimer& timer)
769801
else
770802
{
771803
kodi::Log(ADDON_LOG_DEBUG, "TIMER_REPEATING_EPG");
772-
// build recurring recording request
773-
request = kodi::tools::StringUtils::Format("recording.recurring.save&recurring_id=%d&channel_id=%d&event_id=%d&keep=%d&pre_padding=%d&post_padding=%d&day_mask=%s&directory_id=%s&only_new=%s%s",
774-
timer.GetClientIndex(),
775-
timer.GetClientChannelUid(),
776-
epgOid,
777-
timer.GetMaxRecordings(),
778-
marginStart,
779-
marginEnd,
780-
days.c_str(),
781-
directory.c_str(),
782-
preventDuplicates,
783-
enabled.c_str()
804+
if (countDays == 7)
805+
{
806+
// build recurring type 3 request for a daily request not automatic timeslot
807+
request = kodi::tools::StringUtils::Format("recording.recurring.save&recurring_type=3&recurring_id=%d&channel_id=%d&event_id=%d&keep=%d&pre_padding=%d&post_padding=%d&directory_id=%s&only_new=%s%s",
808+
timer.GetClientIndex(),
809+
timer.GetClientChannelUid(),
810+
epgOid,
811+
timer.GetMaxRecordings(),
812+
marginStart,
813+
marginEnd,
814+
directory.c_str(),
815+
preventDuplicates,
816+
enabled.c_str()
817+
);
818+
}
819+
else
820+
{
821+
// NextPVR saves DAY, WEEKEND and WEEKDAY as timeslot recordings
822+
request = kodi::tools::StringUtils::Format("recording.recurring.save&recurring_id=%d&channel_id=%d&event_id=%d&keep=%d&pre_padding=%d&post_padding=%d&day_mask=%s&directory_id=%s&only_new=%s%s",
823+
timer.GetClientIndex(),
824+
timer.GetClientChannelUid(),
825+
epgOid,
826+
timer.GetMaxRecordings(),
827+
marginStart,
828+
marginEnd,
829+
days.c_str(),
830+
directory.c_str(),
831+
preventDuplicates,
832+
enabled.c_str()
784833
);
834+
}
785835
}
786836
break;
837+
case TIMER_REPEATING_EPG_ALL_EPISODES:
838+
// NextPVR doesn't support daymask but pass it anyway.
839+
kodi::Log(ADDON_LOG_DEBUG, "TIMER_REPEATING_EPG_ALL_EPISODES");
840+
// build recurring type 2 request
841+
request = kodi::tools::StringUtils::Format("recording.recurring.save&recurring_type=2&recurring_id=%d&channel_id=%d&event_id=%d&keep=%d&pre_padding=%d&post_padding=%d&day_mask=%s&directory_id=%s&only_new=%s%s",
842+
timer.GetClientIndex(),
843+
timer.GetClientChannelUid(),
844+
epgOid,
845+
timer.GetMaxRecordings(),
846+
marginStart,
847+
marginEnd,
848+
days.c_str(),
849+
directory.c_str(),
850+
preventDuplicates,
851+
enabled.c_str()
852+
);
853+
break;
787854

788855
case TIMER_REPEATING_MANUAL:
789856
kodi::Log(ADDON_LOG_DEBUG, "TIMER_REPEATING_MANUAL");

src/Timers.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ namespace NextPVR
3333
constexpr unsigned int TIMER_REPEATING_MIN = TIMER_MANUAL_MAX + 1;
3434
constexpr unsigned int TIMER_REPEATING_MANUAL = TIMER_REPEATING_MIN;
3535
constexpr unsigned int TIMER_REPEATING_EPG = TIMER_REPEATING_MIN + 1;
36-
constexpr unsigned int TIMER_REPEATING_KEYWORD = TIMER_REPEATING_MIN + 2;
37-
constexpr unsigned int TIMER_REPEATING_ADVANCED = TIMER_REPEATING_MIN + 3;
38-
constexpr unsigned int TIMER_REPEATING_MAX = TIMER_REPEATING_MIN + 3;
36+
constexpr unsigned int TIMER_REPEATING_EPG_ALL_EPISODES = TIMER_REPEATING_MIN + 2;
37+
constexpr unsigned int TIMER_REPEATING_KEYWORD = TIMER_REPEATING_MIN + 3;
38+
constexpr unsigned int TIMER_REPEATING_ADVANCED = TIMER_REPEATING_MIN + 4;
39+
constexpr unsigned int TIMER_REPEATING_MAX = TIMER_REPEATING_MIN + 4;
3940

4041
class ATTR_DLL_LOCAL Timers
4142
{

0 commit comments

Comments
 (0)