forked from b-jesch/service.rpm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.py
340 lines (268 loc) · 12.5 KB
/
service.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
import xbmc
from resources.lib.settings import *
from resources.lib.tools import *
import time
import xbmcvfs
import socket
import threading
import stat
osv = release()
if osv.get('ARCH', 'not detected') not in ['i386', 'i686', 'x86_64']:
messageOk(loc(30045), loc(30046))
query = {'method': 'Addons.SetAddonEnabled', 'params': {'addonid': addonid, 'enabled': False}}
if jsonrpc(query) == 'OK':
log('Addon disabled due hardware incompatibility: %s' % osv.get('ARCH', 'not detected'), xbmc.LOGINFO)
else:
log('Could not disable Addon (%s not compatible)' % osv.get('ARCH', 'not detected'), xbmc.LOGERROR)
exit(0)
SHUTDOWN_CMD = xbmcvfs.translatePath(os.path.join(addonpath, 'resources', 'lib', 'shutdown.sh'))
EXTGRABBER = xbmcvfs.translatePath(os.path.join(addonpath, 'resources', 'lib', 'epggrab_ext.sh'))
# set permissions for SHUTDOWN_CMD/EXTGRABBER, required after installation or update
_sts = os.stat(SHUTDOWN_CMD).st_mode
_stg = os.stat(EXTGRABBER).st_mode
if not (_sts & stat.S_IXOTH): os.chmod(SHUTDOWN_CMD, _sts | stat.S_IXOTH)
if not (_stg & stat.S_IXOTH): os.chmod(EXTGRABBER, _stg | stat.S_IXOTH)
Mon = Monitor()
Mon.settings = addon_settings
Mon.getAddonSettings()
setProperty('poweroff', False)
setProperty('observe', False)
setProperty('epg_exec_done', False)
osv = release()
log('OS ID is {} {}'.format(osv['ID'], osv['VERSION_ID']), xbmc.LOGINFO)
if osv['ID'].lower() in ['libreelec', 'openelec'] and Mon.setting['sudo']:
Mon.setSetting('sudo', False)
log('Reset wrong setting \'sudo\' to False')
Mon.settingsChanged = False
Mon.logSettings()
class EpgThread(threading.Thread):
def __init__(self, mode=None):
threading.Thread.__init__(self)
self.mode = mode
def run(self):
initialize = time.time()
if self.mode == 0:
xbmc.sleep(Mon.setting['epgtimer_duration'] * 60000)
elif self.mode == 1:
runExtEpg(Mon.setting['epg_script'], Mon.setting['epg_socket'])
elif self.mode == 2:
copy2Socket(Mon.setting['epg_file'], Mon.setting['epg_socket'])
else:
log('wrong or missing threading parameter: {}'.format(self.mode), xbmc.LOGERROR)
setProperty('epg_exec_done', True)
log('EPG thread took {} secs'.format(int(time.time() - initialize)), xbmc.LOGINFO)
def countDown():
if not Mon.observe:
pbar = ProgressBar(loc(30030), loc(30011).format(addonname), reverse=True)
else:
pbar = ProgressBar(loc(30010), loc(30011).format(addonname),
Mon.setting['notification_time'], Mon.setting['notification_time'], reverse=True)
return not pbar.show_progress()
def copy2Socket(source, tvhsocket):
if xbmcvfs.exists(source) and xbmcvfs.exists(tvhsocket):
s = xbmcvfs.File(source)
transfer = True
chunks = 0
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(tvhsocket)
while transfer:
chunk = s.readBytes(4096)
if not chunk:
log('{} chunks transmitted'.format(chunks))
break
sock.send(chunk)
chunks += 1
except socket.error as e:
log('couldn\'t write to socket: {}'.format(e), xbmc.LOGERROR)
finally:
sock.close()
s.close()
return True
else:
log('couldn\'t copy EPG XML source to socket', xbmc.LOGERROR)
log('Source file or socket doesn\'t exist. Check your settings', xbmc.LOGERROR)
return False
def runExtEpg(script, tvhsocket):
if osv['PLATFORM'] == 'Linux' and os.path.isfile(script) and os.path.isfile(tvhsocket):
try:
_comm = subprocess.Popen('%s %s' % (script, tvhsocket),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
shell=True, universal_newlines=True)
while _comm.poll() is None:
xbmc.sleep(1000)
except subprocess.SubprocessError as e:
log('Could not start external script: {}'.format(e), xbmc.LOGERROR)
def getPvrStatus():
Mon.checkPvrPresence(quiet=True)
if not Mon.hasPVR: return isUSR
# check for recordings and timers
Mon.nextTimer = 0
query = {'method': 'PVR.GetTimers', 'params': {'properties': ['starttime', 'startmargin', 'istimerrule', 'state']}}
response = jsonrpc(query)
if response and response.get('timers', False):
for timer in response.get('timers'):
if timer['istimerrule'] or timer['state'] == 'disabled':
continue
if timer['state'] == 'recording':
return isREC
elif time.mktime(time.strptime(timer['starttime'], JSON_TIME_FORMAT)) - \
Mon.setting['margin_start'] - \
Mon.setting['margin_stop'] - \
Mon.setting['notification_time'] - \
(timer['startmargin'] * 60) + TIME_OFFSET < int(time.time()):
return isREC
else:
Mon.nextTimer = int(time.mktime(time.strptime(timer['starttime'], JSON_TIME_FORMAT)) -
Mon.setting['margin_start'] - (timer['startmargin'] * 60) + TIME_OFFSET)
break
return isUSR
def getEpgStatus():
# Check for EPG update
Mon.nextEPG = 0
if Mon.setting['epgtimer_interval'] > 0:
__next = time.mktime(time.localtime()) + (int(time.strftime('%j')) % Mon.setting['epgtimer_interval']) * 86400
__n = datetime.fromtimestamp(__next).replace(hour=Mon.setting['epgtimer_time'],
minute=0, second=0, microsecond=0)
if not str2bool(getProperty('epg_exec_done')):
if datetime.timestamp(__n) - Mon.setting['margin_start'] - \
Mon.setting['margin_stop'] < \
time.mktime(time.localtime()) < datetime.timestamp(__n) + \
(Mon.setting['epgtimer_duration'] * 60):
return isEPG
Mon.nextEPG = int(datetime.timestamp(__n) - Mon.setting['margin_start'])
if Mon.nextEPG + Mon.setting['epgtimer_duration'] * 60 < time.mktime(time.localtime()):
Mon.nextEPG += int(Mon.setting['epgtimer_interval'] * 86400)
return isUSR
def getProcessStatus():
# check for running processes
if Mon.setting['check_postprocesses']:
processes = Mon.setting['monitored_processes'].split(',')
for proc in processes:
if getProcessPID(proc.strip()):
return isPRG
return isUSR
def getNetworkStatus():
# check for active network connection(s)
if Mon.setting['check_network']:
ports = Mon.setting['monitored_ports'].split(',')
ports = [p.strip() for p in ports]
if getManyPorts(ports):
return isNET
return isUSR
def getTimeFrameStatus():
# check for active time frame
if Mon.setting['main_activity']:
if Mon.setting['main_activity_start'] * 3600 < \
(datetime.now() - datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)).seconds < \
Mon.setting['main_activity_stop'] * 3600:
return isATF
return isUSR
def getPwrStatus(): # obsolete
return isPWR
def getStatusFlags(flags):
_flags = isUSR | getPvrStatus() | getEpgStatus() | getProcessStatus() | getNetworkStatus() | \
getTimeFrameStatus() | getPwrStatus()
if _flags ^ flags:
log('Status changed: {:06b} (PWR/ATF/NET/PRG/REC/EPG)'.format(_flags), xbmc.LOGINFO)
return _flags
def service():
flags = getStatusFlags(isUSR)
Mon.observe = True
# start EPG grabber threads
if flags & isEPG:
thread = EpgThread(Mon.setting['epg_mode'])
thread.start()
# ::MAIN LOOP::
idle_time = Mon.setting['idle_time']
idle_time_playing = Mon.setting['idle_time_playing']
cycle = idle_time
while not Mon.abortRequested():
immediateShutdown = False
walker = 0
while walker < cycle:
walker += 1
if Mon.abortRequested():
log('Shutdown requested by Kodi', xbmc.LOGINFO)
immediateShutdown = True
break
if Mon.settingsChanged:
Mon.getAddonSettings()
Mon.settingsChanged = False
# use the new idle times
idle_time = Mon.setting['idle_time']
idle_time_playing = Mon.setting['idle_time_playing']
# define check interval depending on addon mode and playback state
if xbmc.Player().isPlaying():
log('Playing, using idle_time_playing')
cycle = 60 * idle_time_playing
else:
log('Not playing, using idle_time')
cycle = idle_time
idle = xbmc.getGlobalIdleTime()
xbmc.sleep(1000)
# User activities will get detected during that sleep only!
# Processing time for the other instructions in this while loop is low compared to the sleep time.
# Therefore risc to miss a user activity is also low.
# check for user activity and power off required by user
if str2bool(getProperty('poweroff')):
log('Shutdown required by user', xbmc.LOGINFO)
Mon.observe = False
setProperty('poweroff', False)
break
# poll network status every second to avoid missing of DLNA/UPnP activity
if getNetworkStatus() == isNET:
log('Network activity detected, restart idle timer')
walker = 0
# check for user activity
if xbmc.getGlobalIdleTime() < idle: # getGlobalIdleTime resolution is 1 second
if Mon.setting['ignore_useractivity']:
log('Ignore user activity due to settings')
else:
log('User activity detected, restart idle timer')
walker = 0
flags = getStatusFlags(flags)
log('Idle time expired, flags={:06b} (PWR/ATF/NET/PRG/REC/EPG)'.format(flags), xbmc.LOGINFO)
if flags & isPWR:
if immediateShutdown or (not flags & (isREC | isEPG | isPRG | isNET | isATF)):
if not immediateShutdown:
# show count down and allow the user to retrigger the idle timer
if not countDown():
continue
# power off
_t = 0
if Mon.calcNextEvent():
_m, _t = Mon.calcNextEvent()
_ft = datetime.strftime(datetime.fromtimestamp(_t), LOCAL_TIME_FORMAT)
log('next schedule: {}'.format(_ft), xbmc.LOGINFO)
if Mon.setting['show_next_sched']:
notify(loc(30024), loc(_m).format(_ft))
else:
log('no schedules', xbmc.LOGINFO)
if Mon.setting['show_next_sched']:
notify(loc(30040), loc(30014))
if not immediateShutdown:
xbmc.sleep(5000)
log('set RTC to {}'.format(_t), xbmc.LOGINFO)
if osv['PLATFORM'] == 'Linux':
sudo = 'sudo ' if Mon.setting['sudo'] else ''
os.system('%s%s %s %s %s' % (sudo, SHUTDOWN_CMD, _t,
Mon.setting['shutdown_method'],
Mon.setting['shutdown_mode']))
if Mon.setting['shutdown_method'] == 0 or osv['PLATFORM'] == 'Windows':
xbmc.shutdown()
#if not Mon.observe:
if flags & isREC:
notify(loc(30015), loc(30020), icon=xbmcgui.NOTIFICATION_WARNING) # Notify 'Recording in progress'
elif flags & isEPG:
notify(loc(30015), loc(30021), icon=xbmcgui.NOTIFICATION_WARNING) # Notify 'EPG-Update'
elif flags & isPRG:
notify(loc(30015), loc(30022), icon=xbmcgui.NOTIFICATION_WARNING) # Notify 'Postprocessing'
elif flags & isNET:
notify(loc(30015), loc(30023), icon=xbmcgui.NOTIFICATION_WARNING) # Notify 'Network active'
elif flags & isATF:
notify(loc(30015), loc(30033), icon=xbmcgui.NOTIFICATION_WARNING) # Notify 'Time Frame active'
# Mon.observe = True
if __name__ == '__main__':
service()
log('Service finished', xbmc.LOGINFO)