MythTV master
data_mythtv.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2# smolt - Fedora hardware profiler
3#
4# Copyright (C) 2011 Raymond Wagner <raymond@wagnerrp.com>
5# Copyright (C) 2011 james meyer <james.meyer@operamail.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20
21
22from builtins import range
23from builtins import object
24import os
25import re
26from i18n import _
27from datetime import timedelta
28
29from distros.mythtv_data.orddict import OrdDict
30from distros.mythtv_data.uuiddb import UuidDb
31
32import MythTV
33
34_DB = MythTV.MythDB()
35_BE = MythTV.MythBE(db=_DB)
36_SETTINGS = _DB.settings[_DB.gethostname()]
37prefix = 'mythtv'
38
39class _Mythtv_data(object):
40 def __init__(self, gate):
41 self.get_data(gate)
42
43 def isBackend(self):
44 return (_SETTINGS.BackendServerIP is not None)
45
46 def ProcessVersion(self):
47 data = OrdDict()
48 for name in ['version', 'branch', 'protocol', 'libapi', 'qtversion']:
49 data[name] = 'Unknown'
50
51 executables = ('mythutil', 'mythbackend', 'mythfrontend')
52
53 for executable in executables:
54 execpath = os.path.join(MythTV.static.INSTALL_PREFIX,
55 'bin', executable)
56 try:
57 cmd = MythTV.System(execpath)
58 res = cmd.command('--version')
59 break
60 except:
61 continue
62 else:
63 return data
64
65 names = {'MythTV Version' : 'version',
66 'MythTV Branch' : 'branch',
67 'Network Protocol': 'protocol',
68 'Library API' : 'libapi',
69 'QT Version' : 'qtversion'}
70
71 for line in res.decode().split('\n'):
72 try:
73 prop, val = line.split(':')
74 data[names[prop.strip()]] = val.strip()
75 except:
76 pass
77 return data
78
79 def td_to_secs(self,td): return td.days*86400 + td.seconds
80
81 def ProcessPrograms(self):
82 rdata = OrdDict()
83 edata = OrdDict()
84 ldata = OrdDict()
85 udata = OrdDict()
86
87 data = {'scheduled': rdata,
88 'expireable': edata,
89 'livetv': ldata,
90 'upcoming': udata}
91
92 progs = list(MythTV.Recorded.getAllEntries())
93 upcoming = list(_BE.getUpcomingRecordings())
94 livetv = [prog for prog in progs if prog.recgroup == 'LiveTV']
95 recs = [prog for prog in progs if prog.recgroup != 'LiveTV']
96 expireable = [prog for prog in recs if prog.autoexpire]
97
98 times = []
99 for dataset in (recs, expireable, livetv, upcoming):
100 if len(dataset):
101 try:
102 deltas = [rec.endtime-rec.starttime for rec in dataset]
103 except:
104 deltas = [rec.recendts-rec.recstartts for rec in dataset]
105 secs = self.td_to_secs(deltas.pop())
106 for delta in deltas:
107 secs += self.td_to_secs(delta)
108 times.append(secs)
109 else:
110 times.append(0)
111
112 rdata.count = len(recs)
113 rdata.size = sum([rec.filesize for rec in recs])
114 rdata.time = times[0]
115
116 edata.count = len(expireable)
117 edata.size = sum([rec.filesize for rec in expireable])
118 edata.time = times[1]
119
120 ldata.count = len(livetv)
121 ldata.size = sum([rec.filesize for rec in livetv])
122 ldata.time = times[2]
123
124 udata.count = len(upcoming)
125 udata.time = times[3]
126
127 return {'recordings': data}
128
130 data = OrdDict()
131 data.db_age = 0
132 data.rectime = 0
133 data.reccount = 0
134 data.showcount = 0
135
136 recs = list(MythTV.OldRecorded.getAllEntries())
137 if len(recs) == 0:
138 return data
139
140 oldest = recs[0]
141 shows = []
142
143 maxage = MythTV.utility.datetime(2001,1,1,0,0)
144
145 for rec in recs:
146 if (rec.starttime < oldest.starttime) and (rec.starttime > maxage):
147 oldest = rec
148 data.rectime += self.td_to_secs(rec.endtime - rec.starttime)
149 if rec.recstatus == -3:
150 shows.append(rec.title)
151 data.reccount += 1
152
153 data.db_age = self.td_to_secs(MythTV.utility.datetime.now() - oldest.starttime)
154 data.showcount = len(set(shows))
155 return {'historical': data}
156
157 def ProcessSource(self):
158 class CaptureCard( MythTV.database.DBData ): pass
159 class VideoSource( MythTV.database.DBData ): pass
160
161 data = OrdDict()
162 usedsources = list(set([inp.sourceid for inp in CaptureCard.getAllEntries()]))
163 grabbers = list(set([VideoSource(id).xmltvgrabber for id in usedsources]))
164 data.sourcecount = len(usedsources)
165 data.grabbers = grabbers
166 return data
167
169 data = OrdDict()
170 data.timezone = 'Unknown'
171 data.tzoffset = 0
172
173 res = _BE.backendCommand('QUERY_TIME_ZONE').split('[]:[]')
174 data.timezone = res[0]
175 data.tzoffset = int(res[1])
176 return data
177
178 def ProcessStorage(self):
179 def processdirs(paths):
180 total = 0
181 free = 0
182
183 for path in paths:
184 try:
185 stat = os.statvfs(path)
186 bsize = stat.f_frsize
187 total += stat.f_blocks*bsize
188 free += stat.f_bfree*bsize
189 except OSError:
190 pass
191
192 return (total, free)
193
194 data = OrdDict()
195 data.rectotal = 0
196 data.recfree = 0
197 data.videototal = 0
198 data.videofree = 0
199
200 if not self.isBackend():
201 return data
202
203 sgnames = [rec.storagegroup for rec in MythTV.Recorded.getAllEntries()]
204 sgnames += [rec.storagegroup for rec in MythTV.Record.getAllEntries()]
205 sgnames = list(set(sgnames))
206
207 sgs = []
208 for host in set([_DB.gethostname(), _BE.hostname]):
209 for sgname in sgnames:
210 for sg in _DB.getStorageGroup(sgname, host):
211 if sg.local:
212 sgs.append(sg.dirname)
213 data.rectotal, data.recfree = processdirs(sgs)
214
215 sgs = [sg.dirname for sg in _DB.getStorageGroup('Videos', _DB.gethostname()) if sg.local]
216 data.videototal, data.videofree = processdirs(sgs)
217
218 return {'storage': data}
219
220 def ProcessAudio(self):
221 def _bool(val):
222 if val is None:
223 return False
224 return bool(int(val))
225
226 def _read_file(filename):
227 firstline=[]
228 try:
229 with open(filename,'r') as f:
230 line = f.readline()
231 firstline = line.split()
232 except:
233 pass
234 return firstline
235
236
237 def _oss_alsa():
238 snd_type = "unknown"
239 version = "unknown"
240 alsasnd = "/proc/asound/version"
241 osssnd = "/dev/sndstat"
242
243 if os.path.exists(alsasnd):
244 snd_type = "ALSA"
245 version = _read_file(alsasnd)[-1].rstrip(".")
246
247 elif os.path.exists(osssnd):
248 version = _read_file(osssnd)[1]
249 snd_type = "OSS"
250
251 return snd_type , version
252
253 def _process_search(processname):
254 foundit = False
255 for line in os.popen("ps xa"):
256 fields = line.split()
257 pid = fields[0]
258 process = fields[4].split("/")
259 if processname in process :
260 foundit = True
261 break
262 return foundit
263
264 def _jack():
265 if _process_search("jackd") or _process_search("jackdbus"):
266 foundit = 1
267 else:
268 foundit = 0
269 return foundit
270
271 def _pulse():
272 if _process_search("pulseaudio"):
273 foundit = 1
274 else:
275 foundit = 0
276 return foundit
277
278
279 data = OrdDict()
280 data.device = _SETTINGS.AudioOutputDevice
281 data.passthrudevice = _SETTINGS.PassThruOutputDevice
282 data.passthruoverride = _bool(_SETTINGS.PassThruDeviceOverride)
283 data.stereopcm = _bool(_SETTINGS.StereoPCM)
284 data.sr_override = _bool(_SETTINGS.Audio48kOverride)
285 data.maxchannels = _SETTINGS.MaxChannels
286 data.defaultupmix = _bool(_SETTINGS.AudioDefaultUpmix)
287 data.upmixtype = _SETTINGS.AudioUpmixType
288 p = []
289 for k,v in (('AC3PassThru', 'ac3'), ('DTSPassThru', 'dts'),
290 ('HBRPassthru', 'hbr'), ('EAC3PassThru', 'eac3'),
291 ('TrueHDPassThru', 'truehd'), ('DTSHDPassThru', 'dtshd')):
292 if _bool(_SETTINGS[k]):
293 p.append(v)
294 data.passthru = p
295 data.volcontrol = _bool(_SETTINGS.MythControlsVolume)
296 data.mixerdevice = _SETTINGS.MixerDevice
297 data.mixercontrol = _SETTINGS.MixerControl
298 data.jack = _jack()
299 data.pulse = _pulse()
300 data.audio_sys, data.audio_sys_version = _oss_alsa()
301
302 return {'audio': data}
303
305 class DisplayProfileGroups( MythTV.database.DBData ): pass
306 class DisplayProfiles( OrdDict ):
307 def __new__(cls, *args, **kwargs):
308 inst = super(DisplayProfiles, cls).__new__(cls, *args, **kwargs)
309 inst.__dict__['_profilegroupid'] = None
310 inst.__dict__['_profileid'] = None
311 inst.__dict__['_db'] = None
312 return inst
313
314 def __init__(self, profilegroupid, profileid, db=None):
315 self._db = MythTV.database.DBCache(db=db)
316 self._profilegroupid = profilegroupid
317 self._profileid = profileid
318 with db as cursor:
319 cursor.execute("""SELECT value,data FROM displayprofiles
320 WHERE profilegroupid=%s
321 AND profileid=%s""",
322 (profilegroupid, profileid))
323 for k,v in cursor.fetchall():
324 self[k] = v
325
326 @classmethod
327 def fromProfileGroup(cls, profilegroupid, db=None):
328 db = MythTV.database.DBCache(db=db)
329 with db as cursor:
330 cursor.execute("""SELECT DISTINCT(profileid)
331 FROM displayprofiles
332 WHERE profilegroupid=%s""",
333 profilegroupid)
334 for profileid in cursor.fetchall():
335 yield cls(profilegroupid, profileid[0], db)
336
337 data = OrdDict()
338 data.name = _SETTINGS.DefaultVideoPlaybackProfile
339 data.profiles = []
340
341 profilegroupid = DisplayProfileGroups((data.name, _DB.gethostname()), _DB)\
342 .profilegroupid
343 for profile in DisplayProfiles.fromProfileGroup(profilegroupid, _DB):
344 d = OrdDict()
345 d.decoder = profile.pref_decoder
346 d.deint_pri = profile.pref_deint0
347 d.deint_sec = profile.pref_deint1
348 d.renderer = profile.pref_videorenderer
349 d.filters = profile.pref_filters
350 data.profiles.append(d)
351
352 return {'playbackprofile':data}
353
354 def ProcessMySQL(self):
355 data = OrdDict()
356
357 c = _DB.cursor()
358 c.execute("""SELECT VERSION()""")
359 data.version = c.fetchone()[0]
360
361 c.execute("""SHOW ENGINES""")
362 data.engines = [r[0] for r in c.fetchall()]
363
364 c.execute("""SHOW TABLE STATUS WHERE NAME='settings'""")
365 data.usedengine = c.fetchone()[1]
366
367 data.schema = OrdDict()
368 c.execute("""SELECT value,data FROM settings
369 WHERE value LIKE '%SchemaVer'""")
370 for k,v in c.fetchall():
371 data.schema[k] = v
372
373 return {'database':data}
374
376 def stddev(data):
377 avg = sum(data)/len(data)
378 return avg, (sum([(d-avg)**2 for d in data])/len(data))**.5
379
380 data = OrdDict()
381 data.count = 0
382 data.match_avg = 0
383 data.match_stddev = 0
384 data.place_avg = 0
385 data.place_stddev = 0
386
387 r = re.compile('Scheduled ([0-9]*) items in [0-9.]* = ([0-9.]*) match \+ ([0-9.]*) place')
388 data = OrdDict()
389
390 c = _DB.cursor()
391 c.execute("""SELECT details FROM mythlog
392 WHERE module='scheduler'
393 AND message='Scheduled items'""")
394
395 runs = [r.match(d[0]).groups() for d in c.fetchall()]
396
397 if len(runs) == 0:
398 return {'scheduler': data}
399
400 a,s = stddev([float(r[2]) for r in runs])
401 for i,r in reversed(list(enumerate(runs))):
402 if abs(float(r[2]) - a) > (3*s):
403 runs.pop(i)
404
405 data = OrdDict()
406
407 count = [float(r[0]) for r in runs]
408 match = [float(r[1]) for r in runs]
409 place = [float(r[2]) for r in runs]
410
411 data.count = int(sum(count)/len(count))
412 data.match_avg, data.match_stddev = stddev(match)
413 data.place_avg, data.place_stddev = stddev(place)
414
415 return {'scheduler': data}
416
417
418 def Processtuners(self):
419 class CaptureCard( MythTV.database.DBData ): pass
420
421 cardtypes = {}
422 virtual = [0,0]
423 virtualtypes = ('DVB', 'HDHOMERUN', 'SATIP', 'ASI')
424
425 for card in CaptureCard.getAllEntries(db=_DB):
426 isvirt = (card.cardtype in virtualtypes)
427 loc = card.videodevice+'@'+card.hostname
428 if card.cardtype not in cardtypes:
429 cardtypes[card.cardtype] = [loc]
430 virtual[0] += isvirt
431 else:
432 if loc not in cardtypes[card.cardtype]:
433 cardtypes[card.cardtype].append(loc)
434 virtual[0] += isvirt
435 else:
436 virtual[1] += isvirt
437
438 data = {'tuners':dict([(k,len(v)) for k,v in list(cardtypes.items())])}
439 if virtual[0]:
440 data['vtpertuner'] = sum(virtual)/float(virtual[0])
441 return data
442
444 smoltfile=os.path.expanduser('~') +"/.mythtv/smolt.info"
445 config = {}
446 try:
447 config_file= open(smoltfile)
448 for line in config_file:
449 line = line.strip()
450 if line and line[0] != "#" and line[-1] != "=":
451 var,val = line.rsplit("=",1)
452 config[var.strip()] = val.strip("\"")
453 except:
454 pass
455
456 try:
457 myth_systemrole = config["systemtype" ]
458 except:
459 myth_systemrole = '0'
460
461 try:
462 mythremote = config["remote" ]
463 except:
464 mythremote = 'unknown'
465
466
467 return myth_systemrole , mythremote
468
470 c = _DB.cursor()
471 c.execute("""SELECT level,count(level) FROM logging GROUP BY level""")
472 levels = ('EMERG', 'ALERT', 'CRIT', 'ERR',
473 'WARNING', 'NOTICE', 'INFO') # ignore debugging from totals
474 counts = {}
475 total = 0.
476 for level,count in c.fetchall():
477 if level in range(len(levels)):
478 counts[levels[level]] = count
479 total += count
480 for k,v in list(counts.items()):
481 counts[k] = v/total
482 return {'logurgency':counts}
483
484 def get_data(self,gate):
485 self._data = OrdDict()
486 for func in (self.ProcessVersion,
487 self.ProcessPrograms,
489 self.ProcessSource,
490 self.ProcessTimeZone,
491 self.ProcessStorage,
492 self.ProcessAudio,
494 self.ProcessMySQL,
495 self.ProcessScheduler,
496 self.Processtuners,
497 self.ProcessLogUrgency):
498 try:
499 self._data.update(func())
500 except:
501 pass
502
503 self._data.theme = _SETTINGS.Theme
504 if _DB.settings.NULL.country is not None:
505 self._data.country = _DB.settings.NULL.country
506 else:
507 self._data.country = _SETTINGS.Country
508 self._data.channel_count = len([c for c in MythTV.Channel.getAllEntries() if c.visible])
509 if _DB.settings.NULL.Language is not None:
510 self._data.language = _DB.settings.NULL.Language.lower()
511 elif _SETTINGS.Language is not None:
512 self._data.language = _SETTINGS.Language.lower()
513 else:
514 # something is wrong when you have no language set at all
515 self._data.language = 'not set'
516 self._data.mythtype, self._data.remote = self.ProcessSmoltInfo()
517
518 if _DB.settings.NULL.SystemUUID is None:
519 _DB.settings.NULL.SystemUUID = UuidDb().gen_uuid()
520 self._data.uuid = _DB.settings.NULL.SystemUUID
521
522
523
524
525 def serialize(self):
526 res = self._data
527 return res
528
529 def _dump_rst_section(self, lines, title, data, line_break=True):
530 lines.append(title)
531 for k,v in sorted(data.items()):
532 lines.append('- %s:\n %s \n' %(k,v))
533
534 if line_break:
535 lines.append('')
536
537 def dump_rst(self, lines):
538 serialized = self.serialize()
539 lines.append('MythTV Features')
540 lines.append('-----------------------------')
541 self._dump_rst_section(lines, '', serialized)
542
543
544 def _dump(self):
545 lines = []
546 self.dump_rst(lines)
547 print('\n'.join(lines))
548 print()
549
550
552 return _Mythtv_data(gate)
553
554
555
556if __name__ == '__main__':
557 import pprint
558 pp = pprint.PrettyPrinter()
559 pp.pprint(get_data())
def _dump_rst_section(self, lines, title, data, line_break=True)
Definition: data_mythtv.py:529
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)