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 import os
23 import re
24 import statvfs
25 from i18n import _
26 from datetime import timedelta
27 
28 from orddict import OrdDict
29 from uuiddb import UuidDb
30 
31 import MythTV
32 from user import home
33 
34 _DB = MythTV.MythDB()
35 _BE = MythTV.MythBE(db=_DB)
36 _SETTINGS = _DB.settings[_DB.gethostname()]
37 prefix = 'mythtv'
38 
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.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 
129  def ProcessHistorical(self):
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 
168  def ProcessTimeZone(self):
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  stat = os.statvfs(path)
185  bsize = stat[statvfs.F_FRSIZE]
186  total += stat[statvfs.F_BLOCKS]*bsize
187  free += stat[statvfs.F_BFREE]*bsize
188 
189  return (total, free)
190 
191  data = OrdDict()
192  data.rectotal = 0
193  data.recfree = 0
194  data.videototal = 0
195  data.videofree = 0
196 
197  if not self.isBackend():
198  return data
199 
200  sgnames = [rec.storagegroup for rec in MythTV.Recorded.getAllEntries()]
201  sgnames += [rec.storagegroup for rec in MythTV.Record.getAllEntries()]
202  sgnames = list(set(sgnames))
203 
204  sgs = []
205  for host in set([_DB.gethostname(), _BE.hostname]):
206  for sgname in sgnames:
207  for sg in _DB.getStorageGroup(sgname, host):
208  if sg.local:
209  sgs.append(sg.dirname)
210  data.rectotal, data.recfree = processdirs(sgs)
211 
212  sgs = [sg.dirname for sg in _DB.getStorageGroup('Videos', _DB.gethostname()) if sg.local]
213  data.videototal, data.videofree = processdirs(sgs)
214 
215  return {'storage': data}
216 
217  def ProcessAudio(self):
218  def _bool(val):
219  if val is None:
220  return False
221  return bool(int(val))
222 
223  def _read_file(filename):
224  firstline=[]
225  try:
226  with open(filename,'r') as f:
227  line = f.readline()
228  firstline = line.split()
229  except:
230  pass
231  return firstline
232 
233 
234  def _oss_alsa():
235  snd_type = "unknown"
236  version = "unknown"
237  alsasnd = "/proc/asound/version"
238  osssnd = "/dev/sndstat"
239 
240  if os.path.exists(alsasnd):
241  snd_type = "ALSA"
242  version = _read_file(alsasnd)[-1].rstrip(".")
243 
244  elif os.path.exists(osssnd):
245  version = _read_file(osssnd)[1]
246  snd_type = "OSS"
247 
248  return snd_type , version
249 
250  def _process_search(processname):
251  foundit = False
252  for line in os.popen("ps xa"):
253  fields = line.split()
254  pid = fields[0]
255  process = fields[4].split("/")
256  if processname in process :
257  foundit = True
258  break
259  return foundit
260 
261  def _jack():
262  if _process_search("jackd") or _process_search("jackdbus"):
263  foundit = 1
264  else:
265  foundit = 0
266  return foundit
267 
268  def _pulse():
269  if _process_search("pulseaudio"):
270  foundit = 1
271  else:
272  foundit = 0
273  return foundit
274 
275 
276  data = OrdDict()
277  data.device = _SETTINGS.AudioOutputDevice
278  data.passthrudevice = _SETTINGS.PassThruOutputDevice
279  data.passthruoverride = _bool(_SETTINGS.PassThruDeviceOverride)
280  data.stereopcm = _bool(_SETTINGS.StereoPCM)
281  data.sr_override = _bool(_SETTINGS.Audio48kOverride)
282  data.maxchannels = _SETTINGS.MaxChannels
283  data.defaultupmix = _bool(_SETTINGS.AudioDefaultUpmix)
284  data.upmixtype = _SETTINGS.AudioUpmixType
285  p = []
286  for k,v in (('AC3PassThru', 'ac3'), ('DTSPassThru', 'dts'),
287  ('HBRPassthru', 'hbr'), ('EAC3PassThru', 'eac3'),
288  ('TrueHDPassThru', 'truehd'), ('DTSHDPassThru', 'dtshd')):
289  if _bool(_SETTINGS[k]):
290  p.append(v)
291  data.passthru = p
292  data.volcontrol = _bool(_SETTINGS.MythControlsVolume)
293  data.mixerdevice = _SETTINGS.MixerDevice
294  data.mixercontrol = _SETTINGS.MixerControl
295  data.jack = _jack()
296  data.pulse = _pulse()
297  data.audio_sys, data.audio_sys_version = _oss_alsa()
298 
299  return {'audio': data}
300 
302  class DisplayProfileGroups( MythTV.database.DBData ): pass
303  class DisplayProfiles( OrdDict ):
304  def __new__(cls, *args, **kwargs):
305  inst = super(DisplayProfiles, cls).__new__(cls, *args, **kwargs)
306  inst.__dict__['_profilegroupid'] = None
307  inst.__dict__['_profileid'] = None
308  inst.__dict__['_db'] = None
309  return inst
310 
311  def __init__(self, profilegroupid, profileid, db=None):
312  self._db = MythTV.database.DBCache(db=db)
313  self._profilegroupid = profilegroupid
314  self._profileid = profileid
315  with db as cursor:
316  cursor.execute("""SELECT value,data FROM displayprofiles
317  WHERE profilegroupid=%s
318  AND profileid=%s""",
319  (profilegroupid, profileid))
320  for k,v in cursor.fetchall():
321  self[k] = v
322 
323  @classmethod
324  def fromProfileGroup(cls, profilegroupid, db=None):
325  db = MythTV.database.DBCache(db=db)
326  with db as cursor:
327  cursor.execute("""SELECT DISTINCT(profileid)
328  FROM displayprofiles
329  WHERE profilegroupid=%s""",
330  profilegroupid)
331  for profileid in cursor.fetchall():
332  yield cls(profilegroupid, profileid[0], db)
333 
334  data = OrdDict()
335  data.name = _SETTINGS.DefaultVideoPlaybackProfile
336  data.profiles = []
337 
338  profilegroupid = DisplayProfileGroups((data.name, _DB.gethostname()), _DB)\
339  .profilegroupid
340  for profile in DisplayProfiles.fromProfileGroup(profilegroupid, _DB):
341  d = OrdDict()
342  d.decoder = profile.pref_decoder
343  d.deint_pri = profile.pref_deint0
344  d.deint_sec = profile.pref_deint1
345  d.renderer = profile.pref_videorenderer
346  d.filters = profile.pref_filters
347  data.profiles.append(d)
348 
349  return {'playbackprofile':data}
350 
351  def ProcessMySQL(self):
352  data = OrdDict()
353 
354  c = _DB.cursor()
355  c.execute("""SELECT VERSION()""")
356  data.version = c.fetchone()[0]
357 
358  c.execute("""SHOW ENGINES""")
359  data.engines = [r[0] for r in c.fetchall()]
360 
361  c.execute("""SHOW TABLE STATUS WHERE NAME='settings'""")
362  data.usedengine = c.fetchone()[1]
363 
364  data.schema = OrdDict()
365  c.execute("""SELECT value,data FROM settings
366  WHERE value LIKE '%SchemaVer'""")
367  for k,v in c.fetchall():
368  data.schema[k] = v
369 
370  return {'database':data}
371 
372  def ProcessScheduler(self):
373  def stddev(data):
374  avg = sum(data)/len(data)
375  return avg, (sum([(d-avg)**2 for d in data])/len(data))**.5
376 
377  data = OrdDict()
378  data.count = 0
379  data.match_avg = 0
380  data.match_stddev = 0
381  data.place_avg = 0
382  data.place_stddev = 0
383 
384  r = re.compile('Scheduled ([0-9]*) items in [0-9.]* = ([0-9.]*) match \+ ([0-9.]*) place')
385  data = OrdDict()
386 
387  c = _DB.cursor()
388  c.execute("""SELECT details FROM mythlog
389  WHERE module='scheduler'
390  AND message='Scheduled items'""")
391 
392  runs = [r.match(d[0]).groups() for d in c.fetchall()]
393 
394  if len(runs) == 0:
395  return {'scheduler': data}
396 
397  a,s = stddev([float(r[2]) for r in runs])
398  for i,r in reversed(list(enumerate(runs))):
399  if abs(float(r[2]) - a) > (3*s):
400  runs.pop(i)
401 
402  data = OrdDict()
403 
404  count = [float(r[0]) for r in runs]
405  match = [float(r[1]) for r in runs]
406  place = [float(r[2]) for r in runs]
407 
408  data.count = int(sum(count)/len(count))
409  data.match_avg, data.match_stddev = stddev(match)
410  data.place_avg, data.place_stddev = stddev(place)
411 
412  return {'scheduler': data}
413 
414 
415  def Processtuners(self):
416  class CaptureCard( MythTV.database.DBData ): pass
417 
418  cardtypes = {}
419  virtual = [0,0]
420  virtualtypes = ('DVB', 'HDHOMERUN', 'ASI')
421 
422  for card in CaptureCard.getAllEntries(db=_DB):
423  isvirt = (card.cardtype in virtualtypes)
424  loc = card.videodevice+'@'+card.hostname
425  if card.cardtype not in cardtypes:
426  cardtypes[card.cardtype] = [loc]
427  virtual[0] += isvirt
428  else:
429  if loc not in cardtypes[card.cardtype]:
430  cardtypes[card.cardtype].append(loc)
431  virtual[0] += isvirt
432  else:
433  virtual[1] += isvirt
434 
435  data = {'tuners':dict([(k,len(v)) for k,v in cardtypes.items()])}
436  if virtual[0]:
437  data['vtpertuner'] = sum(virtual)/float(virtual[0])
438  return data
439 
440  def ProcessSmoltInfo(self):
441  smoltfile=home+"/.mythtv/smolt.info"
442  config = {}
443  try:
444  config_file= open(smoltfile)
445  for line in config_file:
446  line = line.strip()
447  if line and line[0] is not "#" and line[-1] is not "=":
448  var,val = line.rsplit("=",1)
449  config[var.strip()] = val.strip("\"")
450  except:
451  pass
452 
453  try:
454  myth_systemrole = config["systemtype" ]
455  except:
456  myth_systemrole = '0'
457 
458  try:
459  mythremote = config["remote" ]
460  except:
461  mythremote = 'unknown'
462 
463 
464  return myth_systemrole , mythremote
465 
466  def ProcessLogUrgency(self):
467  c = _DB.cursor()
468  c.execute("""SELECT level,count(level) FROM logging GROUP BY level""")
469  levels = ('EMERG', 'ALERT', 'CRIT', 'ERR',
470  'WARNING', 'NOTICE', 'INFO') # ignore debugging from totals
471  counts = {}
472  total = 0.
473  for level,count in c.fetchall():
474  if level in range(len(levels)):
475  counts[levels[level]] = count
476  total += count
477  for k,v in counts.items():
478  counts[k] = v/total
479  return {'logurgency':counts}
480 
481  def get_data(self,gate):
482  self._data = OrdDict()
483  for func in (self.ProcessVersion,
484  self.ProcessPrograms,
485  self.ProcessHistorical,
486  self.ProcessSource,
487  self.ProcessTimeZone,
488  self.ProcessStorage,
489  self.ProcessAudio,
490  self.ProcessVideoProfile,
491  self.ProcessMySQL,
492  self.ProcessScheduler,
493  self.Processtuners,
494  self.ProcessLogUrgency):
495  try:
496  self._data.update(func())
497  except:
498  pass
499 
500  self._data.theme = _SETTINGS.Theme
501  if _DB.settings.NULL.country is not None:
502  self._data.country = _DB.settings.NULL.country
503  else:
504  self._data.country = _SETTINGS.Country
505  self._data.channel_count = len([c for c in MythTV.Channel.getAllEntries() if c.visible])
506  if _DB.settings.NULL.Language is not None:
507  self._data.language = _DB.settings.NULL.Language.lower()
508  elif _SETTINGS.Language is not None:
509  self._data.language = _SETTINGS.Language.lower()
510  else:
511  # something is wrong when you have no language set at all
512  self._data.language = 'not set'
513  self._data.mythtype, self._data.remote = self.ProcessSmoltInfo()
514 
515  if _DB.settings.NULL.SystemUUID is None:
516  _DB.settings.NULL.SystemUUID = UuidDb().gen_uuid()
517  self._data.uuid = _DB.settings.NULL.SystemUUID
518 
519 
520 
521 
522  def serialize(self):
523  res = self._data
524  return res
525 
526  def _dump_rst_section(self, lines, title, data, line_break=True):
527  lines.append(title)
528  for k,v in sorted(data.items()):
529  lines.append('- %s:\n %s \n' %(k,v))
530 
531  if line_break:
532  lines.append('')
533 
534  def dump_rst(self, lines):
535  serialized = self.serialize()
536  lines.append('MythTV Features')
537  lines.append('-----------------------------')
538  self._dump_rst_section(lines, '', serialized)
539 
540 
541  def _dump(self):
542  lines = []
543  self.dump_rst(lines)
544  print '\n'.join(lines)
545  print
546 
547 
549  return _Mythtv_data(gate)
550 
551 
552 
553 if __name__ == '__main__':
554  import pprint
555  pp = pprint.PrettyPrinter()
556  pp.pprint(get_data())
def _dump_rst_section(self, lines, title, data, line_break=True)
Definition: data_mythtv.py:526