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