00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 import os
00023 import re
00024 import statvfs
00025 from i18n import _
00026 from datetime import timedelta
00027
00028 from orddict import OrdDict
00029 from uuiddb import UuidDb
00030
00031 import MythTV
00032 from user import home
00033
00034 _DB = MythTV.MythDB()
00035 _BE = MythTV.MythBE(db=_DB)
00036 _SETTINGS = _DB.settings[_DB.gethostname()]
00037 prefix = 'mythtv'
00038
00039 class _Mythtv_data:
00040 def __init__(self, gate):
00041 self.get_data(gate)
00042
00043 def isBackend(self):
00044 return (_SETTINGS.BackendServerIP is not None)
00045
00046 def ProcessVersion(self):
00047 data = OrdDict()
00048 for name in ['version', 'branch', 'protocol', 'libapi', 'qtversion']:
00049 data[name] = 'Unknown'
00050
00051 try:
00052 mbe = MythTV.System(MythTV.static.INSTALL_PREFIX + '/bin/mythbackend')
00053 res = mbe.command('--version')
00054 except:
00055 return data
00056
00057 names = {'MythTV Version' : 'version',
00058 'MythTV Branch' : 'branch',
00059 'Network Protocol': 'protocol',
00060 'Library API' : 'libapi',
00061 'QT Version' : 'qtversion'}
00062
00063 for line in res.split('\n'):
00064 try:
00065 prop, val = line.split(':')
00066 data[names[prop.strip()]] = val.strip()
00067 except:
00068 pass
00069 return data
00070
00071 def td_to_secs(self,td): return td.days*86400 + td.seconds
00072
00073 def ProcessPrograms(self):
00074 rdata = OrdDict()
00075 edata = OrdDict()
00076 ldata = OrdDict()
00077 udata = OrdDict()
00078
00079 data = {'scheduled': rdata,
00080 'expireable': edata,
00081 'livetv': ldata,
00082 'upcoming': udata}
00083
00084 progs = list(MythTV.Recorded.getAllEntries())
00085 upcoming = list(_BE.getUpcomingRecordings())
00086 livetv = [prog for prog in progs if prog.recgroup == 'LiveTV']
00087 recs = [prog for prog in progs if prog.recgroup != 'LiveTV']
00088 expireable = [prog for prog in recs if prog.autoexpire]
00089
00090 times = []
00091 for dataset in (recs, expireable, livetv, upcoming):
00092 if len(dataset):
00093 try:
00094 deltas = [rec.endtime-rec.starttime for rec in dataset]
00095 except:
00096 deltas = [rec.recendts-rec.recstartts for rec in dataset]
00097 secs = self.td_to_secs(deltas.pop())
00098 for delta in deltas:
00099 secs += self.td_to_secs(delta)
00100 times.append(secs)
00101 else:
00102 times.append(0)
00103
00104 rdata.count = len(recs)
00105 rdata.size = sum([rec.filesize for rec in recs])
00106 rdata.time = times[0]
00107
00108 edata.count = len(expireable)
00109 edata.size = sum([rec.filesize for rec in expireable])
00110 edata.time = times[1]
00111
00112 ldata.count = len(livetv)
00113 ldata.size = sum([rec.filesize for rec in livetv])
00114 ldata.time = times[2]
00115
00116 udata.count = len(upcoming)
00117 udata.time = times[3]
00118
00119 return {'recordings': data}
00120
00121 def ProcessHistorical(self):
00122 data = OrdDict()
00123 data.db_age = 0
00124 data.rectime = 0
00125 data.reccount = 0
00126 data.showcount = 0
00127
00128 recs = list(MythTV.OldRecorded.getAllEntries())
00129 if len(recs) == 0:
00130 return data
00131
00132 oldest = recs[0]
00133 shows = []
00134
00135 for rec in recs:
00136 if rec.starttime < oldest.starttime:
00137 oldest = rec
00138 data.rectime += self.td_to_secs(rec.endtime - rec.starttime)
00139 if rec.recstatus == -3:
00140 shows.append(rec.title)
00141 data.reccount += 1
00142
00143 data.db_age = self.td_to_secs(MythTV.utility.datetime.now() - oldest.starttime)
00144 data.showcount = len(set(shows))
00145 return {'historical': data}
00146
00147 def ProcessSource(self):
00148 class CardInput( MythTV.database.DBData ): pass
00149 class VideoSource( MythTV.database.DBData ): pass
00150
00151 data = OrdDict()
00152 usedsources = list(set([inp.sourceid for inp in CardInput.getAllEntries()]))
00153 grabbers = list(set([VideoSource(id).xmltvgrabber for id in usedsources]))
00154 data.sourcecount = len(usedsources)
00155 data.grabbers = grabbers
00156 return data
00157
00158 def ProcessTimeZone(self):
00159 data = OrdDict()
00160 data.timezone = 'Unknown'
00161 data.tzoffset = 0
00162
00163 res = _BE.backendCommand('QUERY_TIME_ZONE').split('[]:[]')
00164 data.timezone = res[0]
00165 data.tzoffset = int(res[1])
00166 return data
00167
00168 def ProcessStorage(self):
00169 def processdirs(paths):
00170 total = 0
00171 free = 0
00172
00173 for path in paths:
00174 stat = os.statvfs(path)
00175 bsize = stat[statvfs.F_FRSIZE]
00176 total += stat[statvfs.F_BLOCKS]*bsize
00177 free += stat[statvfs.F_BFREE]*bsize
00178
00179 return (total, free)
00180
00181 data = OrdDict()
00182 data.rectotal = 0
00183 data.recfree = 0
00184 data.videototal = 0
00185 data.videofree = 0
00186
00187 if not self.isBackend():
00188 return data
00189
00190 sgnames = [rec.storagegroup for rec in MythTV.Recorded.getAllEntries()]
00191 sgnames += [rec.storagegroup for rec in MythTV.Record.getAllEntries()]
00192 sgnames = list(set(sgnames))
00193
00194 sgs = []
00195 for host in set([_DB.gethostname(), _BE.hostname]):
00196 for sgname in sgnames:
00197 for sg in _DB.getStorageGroup(sgname, host):
00198 if sg.local:
00199 sgs.append(sg.dirname)
00200 data.rectotal, data.recfree = processdirs(sgs)
00201
00202 sgs = [sg.dirname for sg in _DB.getStorageGroup('Videos', _DB.gethostname()) if sg.local]
00203 data.videototal, data.videofree = processdirs(sgs)
00204
00205 return {'storage': data}
00206
00207 def ProcessAudio(self):
00208 def _bool(val):
00209 if val is None:
00210 return False
00211 return bool(int(val))
00212
00213 def _read_file(filename):
00214 firstline=[]
00215 try:
00216 with open(filename,'r') as f:
00217 line = f.readline()
00218 firstline = line.split()
00219 except:
00220 pass
00221 return firstline
00222
00223
00224 def _oss_alsa():
00225 snd_type = "unknown"
00226 version = "unknown"
00227 alsasnd = "/proc/asound/version"
00228 osssnd = "/dev/sndstat"
00229
00230 if os.path.exists(alsasnd):
00231 snd_type = "ALSA"
00232 version = _read_file(alsasnd)[-1].rstrip(".")
00233
00234 elif os.path.exists(osssnd):
00235 version = _read_file(osssnd)[1]
00236 snd_type = "OSS"
00237
00238 return snd_type , version
00239
00240 def _process_search(processname):
00241 foundit = False
00242 for line in os.popen("ps xa"):
00243 fields = line.split()
00244 pid = fields[0]
00245 process = fields[4].split("/")
00246 if processname in process :
00247 foundit = True
00248 break
00249 return foundit
00250
00251 def _jack():
00252 if _process_search("jackd") or _process_search("jackdbus"):
00253 foundit = 1
00254 else:
00255 foundit = 0
00256 return foundit
00257
00258 def _pulse():
00259 if _process_search("pulseaudio"):
00260 foundit = 1
00261 else:
00262 foundit = 0
00263 return foundit
00264
00265
00266 data = OrdDict()
00267 data.device = _SETTINGS.AudioOutputDevice
00268 data.passthrudevice = _SETTINGS.PassThruOutputDevice
00269 data.passthruoverride = _bool(_SETTINGS.PassThruDeviceOverride)
00270 data.stereopcm = _bool(_SETTINGS.StereoPCM)
00271 data.sr_override = _bool(_SETTINGS.Audio48kOverride)
00272 data.maxchannels = _SETTINGS.MaxChannels
00273 data.defaultupmix = _bool(_SETTINGS.AudioDefaultUpmix)
00274 data.upmixtype = _SETTINGS.AudioUpmixType
00275 p = []
00276 for k,v in (('AC3PassThru', 'ac3'), ('DTSPassThru', 'dts'),
00277 ('HBRPassthru', 'hbr'), ('EAC3PassThru', 'eac3'),
00278 ('TrueHDPassThru', 'truehd'), ('DTSHDPassThru', 'dtshd')):
00279 if _bool(_SETTINGS[k]):
00280 p.append(v)
00281 data.passthru = p
00282 data.volcontrol = _bool(_SETTINGS.MythControlsVolume)
00283 data.mixerdevice = _SETTINGS.MixerDevice
00284 data.mixercontrol = _SETTINGS.MixerControl
00285 data.jack = _jack()
00286 data.pulse = _pulse()
00287 data.audio_sys, data.audio_sys_version = _oss_alsa()
00288
00289 return {'audio': data}
00290
00291 def ProcessVideoProfile(self):
00292 class DisplayProfileGroups( MythTV.database.DBData ): pass
00293 class DisplayProfiles( OrdDict ):
00294 def __new__(cls, *args, **kwargs):
00295 inst = super(DisplayProfiles, cls).__new__(cls, *args, **kwargs)
00296 inst.__dict__['_profilegroupid'] = None
00297 inst.__dict__['_profileid'] = None
00298 inst.__dict__['_db'] = None
00299 return inst
00300
00301 def __init__(self, profilegroupid, profileid, db=None):
00302 self._db = MythTV.database.DBCache(db=db)
00303 self._profilegroupid = profilegroupid
00304 self._profileid = profileid
00305 with db as cursor:
00306 cursor.execute("""SELECT value,data FROM displayprofiles
00307 WHERE profilegroupid=%s
00308 AND profileid=%s""",
00309 (profilegroupid, profileid))
00310 for k,v in cursor.fetchall():
00311 self[k] = v
00312
00313 @classmethod
00314 def fromProfileGroup(cls, profilegroupid, db=None):
00315 db = MythTV.database.DBCache(db=db)
00316 with db as cursor:
00317 cursor.execute("""SELECT DISTINCT(profileid)
00318 FROM displayprofiles
00319 WHERE profilegroupid=%s""",
00320 profilegroupid)
00321 for profileid in cursor.fetchall():
00322 yield cls(profilegroupid, profileid[0], db)
00323
00324 data = OrdDict()
00325 data.name = _SETTINGS.DefaultVideoPlaybackProfile
00326 data.profiles = []
00327
00328 profilegroupid = DisplayProfileGroups((data.name, _DB.gethostname()), _DB)\
00329 .profilegroupid
00330 for profile in DisplayProfiles.fromProfileGroup(profilegroupid, _DB):
00331 d = OrdDict()
00332 d.decoder = profile.pref_decoder
00333 d.deint_pri = profile.pref_deint0
00334 d.deint_sec = profile.pref_deint1
00335 d.renderer = profile.pref_videorenderer
00336 d.filters = profile.pref_filters
00337 data.profiles.append(d)
00338
00339 return {'playbackprofile':data}
00340
00341 def ProcessMySQL(self):
00342 data = OrdDict()
00343
00344 c = _DB.cursor()
00345 c.execute("""SELECT VERSION()""")
00346 data.version = c.fetchone()[0]
00347
00348 c.execute("""SHOW ENGINES""")
00349 data.engines = [r[0] for r in c.fetchall()]
00350
00351 c.execute("""SHOW TABLE STATUS WHERE NAME='settings'""")
00352 data.usedengine = c.fetchone()[1]
00353
00354 data.schema = OrdDict()
00355 c.execute("""SELECT value,data FROM settings
00356 WHERE value LIKE '%SchemaVer'""")
00357 for k,v in c.fetchall():
00358 data.schema[k] = v
00359
00360 return {'database':data}
00361
00362 def ProcessScheduler(self):
00363 def stddev(data):
00364 avg = sum(data)/len(data)
00365 return avg, (sum([(d-avg)**2 for d in data])/len(data))**.5
00366
00367 data = OrdDict()
00368 data.count = 0
00369 data.match_avg = 0
00370 data.match_stddev = 0
00371 data.place_avg = 0
00372 data.place_stddev = 0
00373
00374 r = re.compile('Scheduled ([0-9]*) items in [0-9.]* = ([0-9.]*) match \+ ([0-9.]*) place')
00375 data = OrdDict()
00376
00377 c = _DB.cursor()
00378 c.execute("""SELECT details FROM mythlog
00379 WHERE module='scheduler'
00380 AND message='Scheduled items'""")
00381
00382 runs = [r.match(d[0]).groups() for d in c.fetchall()]
00383
00384 if len(runs) == 0:
00385 return {'scheduler': data}
00386
00387 a,s = stddev([float(r[2]) for r in runs])
00388 for i,r in reversed(list(enumerate(runs))):
00389 if abs(float(r[2]) - a) > (3*s):
00390 runs.pop(i)
00391
00392 data = OrdDict()
00393
00394 count = [float(r[0]) for r in runs]
00395 match = [float(r[1]) for r in runs]
00396 place = [float(r[2]) for r in runs]
00397
00398 data.count = int(sum(count)/len(count))
00399 data.match_avg, data.match_stddev = stddev(match)
00400 data.place_avg, data.place_stddev = stddev(place)
00401
00402 return {'scheduler': data}
00403
00404
00405 def Processtuners(self):
00406 class CaptureCard( MythTV.database.DBData ): pass
00407
00408 cardtypes = {}
00409 virtual = [0,0]
00410 virtualtypes = ('DVB', 'HDHOMERUN', 'ASI')
00411
00412 for card in CaptureCard.getAllEntries(db=_DB):
00413 isvirt = (card.cardtype in virtualtypes)
00414 loc = card.videodevice+'@'+card.hostname
00415 if card.cardtype not in cardtypes:
00416 cardtypes[card.cardtype] = [loc]
00417 virtual[0] += isvirt
00418 else:
00419 if loc not in cardtypes[card.cardtype]:
00420 cardtypes[card.cardtype].append(loc)
00421 virtual[0] += isvirt
00422 else:
00423 virtual[1] += isvirt
00424
00425 data = {'tuners':dict([(k,len(v)) for k,v in cardtypes.items()])}
00426 if virtual[0]:
00427 data['vtpertuner'] = sum(virtual)/float(virtual[0])
00428 return data
00429
00430 def ProcessSmoltInfo(self):
00431 smoltfile=home+"/.mythtv/smolt.info"
00432 config = {}
00433 try:
00434 config_file= open(smoltfile)
00435 for line in config_file:
00436 line = line.strip()
00437 if line and line[0] is not "#" and line[-1] is not "=":
00438 var,val = line.rsplit("=",1)
00439 config[var.strip()] = val.strip("\"")
00440 except:
00441 pass
00442
00443 try:
00444 myth_systemrole = config["systemtype" ]
00445 except:
00446 myth_systemrole = '0'
00447
00448 try:
00449 mythremote = config["remote" ]
00450 except:
00451 mythremote = 'unknown'
00452
00453
00454 return myth_systemrole , mythremote
00455
00456
00457
00458
00459
00460 def get_data(self,gate):
00461 self._data = OrdDict()
00462 self._data.update(self.ProcessVersion())
00463 self._data.update(self.ProcessPrograms())
00464 self._data.update(self.ProcessHistorical())
00465 self._data.update(self.ProcessSource())
00466 self._data.update(self.ProcessTimeZone())
00467 self._data.update(self.ProcessStorage())
00468 self._data.update(self.ProcessAudio())
00469 self._data.update(self.ProcessVideoProfile())
00470 self._data.update(self.ProcessMySQL())
00471 self._data.update(self.ProcessScheduler())
00472 self._data.update(self.Processtuners())
00473
00474 self._data.theme = _SETTINGS.Theme
00475 self._data.country = _SETTINGS.Country
00476 self._data.channel_count = len([c for c in MythTV.Channel.getAllEntries() if c.visible])
00477 self._data.language = _SETTINGS.Language.lower()
00478 self._data.mythtype, self._data.remote = self.ProcessSmoltInfo()
00479
00480 if _DB.settings.NULL.SystemUUID is None:
00481 _DB.settings.NULL.SystemUUID = UuidDb().gen_uuid()
00482 self._data.uuid = _DB.settings.NULL.SystemUUID
00483
00484
00485
00486
00487 def serialize(self):
00488 res = self._data
00489 return res
00490
00491 def _dump_rst_section(self, lines, title, data, line_break=True):
00492 lines.append(title)
00493 for k,v in sorted(data.items()):
00494 lines.append('- %s:\n %s \n' %(k,v))
00495
00496 if line_break:
00497 lines.append('')
00498
00499 def dump_rst(self, lines):
00500 serialized = self.serialize()
00501 lines.append('MythTV Features')
00502 lines.append('-----------------------------')
00503 self._dump_rst_section(lines, '', serialized)
00504
00505
00506 def _dump(self):
00507 lines = []
00508 self.dump_rst(lines)
00509 print '\n'.join(lines)
00510 print
00511
00512
00513 def create_mythtv_data(gate):
00514 return _Mythtv_data(gate)
00515
00516
00517
00518 if __name__ == '__main__':
00519 import pprint
00520 pp = pprint.PrettyPrinter()
00521 pp.pprint(get_data())