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
|
#!/usr/bin/python3
# based on https://www.mythtv.org/wiki/Find_orphans.py
from MythTV import MythDB, MythBE, Recorded, MythError
from socket import timeout
import os
import sys
def human_size(s):
s = float(s)
o = 0
while s > 1000:
s /= 1000
o += 1
return str(round(s,1))+('B ','KB','MB','GB','TB')[o]
class File( str ):
def __new__(self, host, group, path, name, size):
return str.__new__(self, name)
def __init__(self, host, group, path, name, size):
self.host = host
self.group = group
self.path = path
self.size = int(size)
def pprint(self):
name = '%s: %s' % (self.host, os.path.join(self.path, self))
print(' {0:<90}{1:>8}'.format(name, human_size(self.size)))
def delete(self):
be = MythBE(self.host, db=DB)
be.deleteFile(self, self.group)
class MyRecorded( Recorded ):
_table = 'recorded'
def pprint(self):
name = '{0.hostname}: {0.title}'.format(self)
if self.subtitle:
name += ' - '+self.subtitle
print(' {0:<70}{1:>28}'.format(name,self.basename))
def printrecs(title, recs):
print(title)
for rec in sorted(recs, key=lambda x: x.title):
rec.pprint()
print('{0:>87}{1:>12}'.format('Count:',len(recs)))
def printfiles(title, files):
print(title)
for f in sorted(files, key=lambda x: x.path):
f.pprint()
size = sum([f.size for f in files])
print('{0:>87}{1:>12}'.format('Total:',human_size(size)))
def populate(host=None):
unfiltered = []
kwargs = {'livetv':True}
if host:
with DB as c:
c.execute("""SELECT count(1) FROM settings
WHERE hostname=%s AND value=%s""",
(host, 'BackendServerAddr'))
if c.fetchone()[0] == 0:
raise Exception('Invalid hostname specified on command line.')
hosts = [host]
kwargs['hostname'] = host
else:
with DB as c:
c.execute("""SELECT hostname FROM settings
WHERE value='BackendServerAddr'""")
hosts = [r[0] for r in c.fetchall()]
for host in hosts:
for sg in DB.getStorageGroup():
if sg.groupname in ('Videos','Banners','Coverart','Fanart',\
'Music','MusicArt', 'Photographs',\
'Screenshots','Trailers'):
continue
try:
dirs,files,sizes = BE.getSGList(host, sg.groupname, sg.dirname)
for f,s in zip(files,sizes):
newfile = File(host, sg.groupname, sg.dirname, f, s)
if newfile not in unfiltered:
unfiltered.append(newfile)
except:
pass
recs = list(DB.searchRecorded(**kwargs))
zerorecs = []
orphvids = []
for rec in list(recs):
if rec.basename in unfiltered:
recs.remove(rec)
i = unfiltered.index(rec.basename)
f = unfiltered.pop(i)
if f.size < 1024:
zerorecs.append(rec)
name = rec.basename.rsplit('.',1)[0]
for f in list(unfiltered):
if name in f:
unfiltered.remove(f)
for f in list(unfiltered):
if not (f.endswith('.mpg') or f.endswith('.nuv') or f.endswith('.ts')):
continue
orphvids.append(f)
unfiltered.remove(f)
orphimgs = []
for f in list(unfiltered):
if not f.endswith('.png'):
continue
orphimgs.append(f)
unfiltered.remove(f)
dbbackup = []
for f in list(unfiltered):
if 'sql' not in f:
continue
dbbackup.append(f)
unfiltered.remove(f)
return (recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered)
def delete_recs(recs):
printrecs('The following recordings will be deleted', recs)
print('Are you sure you want to continue? (yes/no)')
try:
res = input('> ')
while True:
if res == 'yes':
for rec in recs:
rec.delete(True, True)
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except MythError:
name = '{0.hostname}: {0.title}'.format(rec)
if rec.subtitle:
name += ' - '+rec.subtitle
print("Warning: Failed to delete '" + name + "'")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def delete_files(files):
printfiles('The following files will be deleted', files)
print('Are you sure you want to continue? (yes/no)')
try:
res = input('> ')
while True:
if res == 'yes':
for f in files:
f.delete()
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def main(host=None):
while True:
recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered = populate(host)
if len(recs):
printrecs("Recordings with missing files", recs)
if len(zerorecs):
printrecs("Zero byte recordings", zerorecs)
if len(orphvids):
printfiles("Orphaned video files", orphvids)
if len(orphimgs):
printfiles("Orphaned snapshots", orphimgs)
if len(dbbackup):
printfiles("Database backups", dbbackup)
if len(unfiltered):
printfiles("Other files", unfiltered)
if not printOnly:
opts = []
if len(recs):
opts.append(['Delete orphaned recording entries', delete_recs, recs])
if len(zerorecs):
opts.append(['Delete zero byte recordings', delete_recs, zerorecs])
if len(orphvids):
opts.append(['Delete orphaned video files', delete_files, orphvids])
if len(orphimgs):
opts.append(['Delete orphaned snapshots', delete_files, orphimgs])
if len(unfiltered):
opts.append(['Delete other files', delete_files, unfiltered])
opts.append(['Refresh list', None, None])
print('Please select from the following:')
for i, opt in enumerate(opts):
if opt[0] == "Refresh list":
print(' R. {1}'.format(i+1, opt[0]))
refreshNum=i+1
else:
print(' {0}. {1}'.format(i+1, opt[0]))
print(' Q. Quit')
try:
inner = True
res = input('> ')
while inner:
try:
res = int(res)
except:
if res == "Q" or res == "q":
sys.exit(0)
elif res == "R" or res == "r":
res = refreshNum
else:
res = input('Invalid selection > ')
continue
if (res <= 0) or (res > len(opts)):
res = input('Invalid selection > ')
continue
break
opt = opts[res-1]
if opt[1] is None:
continue
else:
opt[1](opt[2])
except KeyboardInterrupt:
break
except EOFError:
sys.exit(0)
else:
sys.exit(0)
DB = MythDB()
BE = MythBE(db=DB)
DB.searchRecorded.handler = MyRecorded
DB.searchRecorded.dbclass = MyRecorded
if __name__ == '__main__':
global printOnly
if "--printonly" in sys.argv :
printOnly=True
else:
printOnly=False
if len(sys.argv) == 2 and sys.argv[1] != "--printonly":
main(sys.argv[1])
else:
main()
|