forked from the-black-eagle/script.database.cleaner
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdefault.py
1582 lines (1465 loc) · 69.7 KB
/
default.py
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python
# -*- coding: utf-8 -*-
# script.video.cleaner
# Written by black_eagle and BatterPudding
# Updated to work with Kodi 19 Matrix by kenmills
# Version 27b/7 - Batter Pudding Fix added
# Version 27b/9 - Batter Pudding tweaks the debug logging
# Version 28b/1 - New GUI, several code fixes
# Version 28b/2 - Fix the WINDOWS KODI temp path
# Version 28b/3 - Tidy up temp path code, remove some unused code
# Version 29b/1 - Add ability to rename paths inside the db
# Version 29b/2 - Fix incorrectly altered SQL
# Version 30b/1 - UI improvements - only allow one instance
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
import datetime
import json as jsoninterface
import sqlite3
import xml.etree.ElementTree as ET
import mysql.connector
import xbmc
import xbmcvfs
import xbmcaddon
import xbmcgui
# database versions to scan for
MIN_VIDEODB_VERSION = 116
MAX_VIDEODB_VERSION = 119 # i.e. Matrix, aka Kodi 19
ACTION_PREVIOUS_MENU = 10
ACTION_SELECT_ITEM = 7
ACTION_NAV_BACK = 92
ACTION_MOUSE_LEFT_CLICK = 100
flag = 0
WINDOW = xbmcgui.Window(10000)
class MyClass(xbmcgui.WindowXMLDialog):
def __init__(self, *args, **kwargs):
pass
def onInit(self):
self.container = self.getControl(6)
self.container2 = self.getControl(8)
self.container3 = self.getControl(10)
self.listitems = []
self.excludesitems = []
self.addonsettings = []
# List paths from sources.xml
if not specificpath and not replacepath:
self.display_list = display_list
for i in range(len(self.display_list)):
self.listitems.append('[COLOR yellow]'
+ self.display_list[i] + '[/COLOR]')
if no_sources:
self.listitems.append('[COLOR red][B]No sources are in use[/B][/COLOR]'
)
self.listitems.append('[COLOR red][B]All streaming paths will be removed[/B][/COLOR]'
)
if excluding:
self.listitems.append('')
self.listitems.append('[COLOR red][B]Paths from excludes.xml will be kept[/B][/COLOR]'
)
if replacepath:
self.listitems.append('[COLOR yellow]Replacing a path[/COLOR]'
)
self.listitems.append('[COLOR yellow]in your database[/COLOR]'
)
self.listitems.append('[COLOR yellow]Confirm details below[/COLOR]'
)
if specificpath:
self.listitems.append('[COLOR yellow]Removing a path[/COLOR]'
)
self.listitems.append('[COLOR yellow]in your database[/COLOR]'
)
self.listitems.append('[COLOR yellow]Confirm details below[/COLOR]'
)
self.container.addItems(self.listitems)
# List paths in excludes.xml (if it exists)
self.excludes_list = excludes_list
if excluding:
for i in range(len(self.excludes_list)):
self.excludesitems.append('[COLOR yellow]'
+ self.excludes_list[i] + '[/COLOR]')
else:
self.excludesitems.append('Not Present')
self.container2.addItems(self.excludesitems)
# List the relevant addon settings
if deepclean and not replacepath and not specificpath and not is_mysql and not no_sources and not remote_file:
self.addonsettings.append('Deep clean is on')
if is_pvr and not specificpath and not replacepath:
self.addonsettings.append('Keep PVR information')
if bookmarks and not specificpath and not replacepath:
self.addonsettings.append('Keep bookmark information')
if autoclean:
self.addonsettings.append('Auto call Clean Library')
if autoclean_multiple_times:
self.addonsettings.append('Run Clean Library multiple times')
if promptdelete:
self.addonsettings.append('Show summary window (This window !!)'
)
if autobackup == 'true' and not is_mysql:
self.addonsettings.append('Auto backing up local database')
if no_sources or specificpath or replacepath:
self.addonsettings.append('[COLOR red]Not[/COLOR] using info from sources.xml'
)
if specificpath:
self.addonsettings.append('[COLOR red]Cleaning a specific path[/COLOR]'
)
if replacepath:
self.addonsettings.append('[COLOR red]Replacing a path[/COLOR]'
)
if enable_logging:
self.addonsettings.append('Writing a logfile to Kodi TEMP directory'
)
if debugging:
debug_string = 'Debugging [COLOR red]enabled[/COLOR]'
else:
debug_string = 'Debugging [COLOR green]disabled[/COLOR]'
self.addonsettings.append(debug_string)
self.addonsettings.append('') # blank line
# Display the name of the database we are connected to
self.addonsettings.append('Database is - [COLOR green][B]%s[/B][/COLOR]'
% our_dbname)
wrapped_execute(cursor, our_select, my_command_list, window=self, suppress_notification=True)
data_list = cursor.fetchall()
data_list_size = len(data_list)
if replacepath:
self.addonsettings.append('[COLOR red][B]There are %d paths to be changed[/B][/COLOR]'
% data_list_size)
else:
# Get an additional count for files that will be deleted
temp_start_string = 'SELECT strPath '
if not our_select.startswith(temp_start_string):
dbglog('Error: expected SQL statement to start with "%s". Actual statement: %s' % (temp_start_string, our_select))
xbmcgui.Dialog().ok(addonname,
'Error: expected SQL statement to start with "%s". Actual statement: %s' % (temp_start_string, our_select)
)
self.close()
exit_on_error()
temp_bookmark_string = 'idPath NOT IN (SELECT DISTINCT idPath FROM files INNER JOIN bookmark ON bookmark.idFile = files.idFile UNION SELECT DISTINCT idParentPath FROM path INNER JOIN files ON files.idPath = path.idPath INNER JOIN bookmark ON bookmark.idFile = files.idFile INNER JOIN episode ON episode.idFile = bookmark.idFile)'
if bookmarks and our_select.find(temp_bookmark_string) == -1:
dbglog('Error: expected SQL statement with bookmarks to have the string "%s". Actual statement: %s' % (temp_bookmark_string, our_select))
xbmcgui.Dialog().ok(addonname,
'Error: expected SQL statement with bookmarks to have the string "%s". Actual statement: %s' % (temp_bookmark_string, our_select)
)
self.close()
exit_on_error()
count_files_statement = our_select
if bookmarks:
count_files_statement = count_files_statement.replace(temp_start_string, 'SELECT count(*) FROM files WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = files.idPath) OR (NOT EXISTS (SELECT 1 FROM bookmark WHERE bookmark.idFile = files.idFile) AND idPath IN (SELECT DISTINCT idPath ', 1)
count_files_statement = count_files_statement.replace(temp_bookmark_string, 'TRUE', 1)
count_files_statement = count_files_statement[:-1] + '))' + count_files_statement[-1:]
else:
count_files_statement = count_files_statement.replace(temp_start_string, 'SELECT count(*) FROM files WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = files.idPath) OR idPath IN (SELECT DISTINCT idPath ', 1)
count_files_statement = count_files_statement[:-1] + ')' + count_files_statement[-1:]
wrapped_execute(cursor, count_files_statement, my_command_list, window=self, suppress_notification=True)
# Show the number of records to be removed on the screen
temp_number_of_files_to_delete = cursor.fetchall()[0][0]
dbglog('There are %d entries to be removed from the path table and %d entries to be removed from the files table'
% (data_list_size, temp_number_of_files_to_delete))
self.addonsettings.append('[COLOR red][B]There are %d entries to be removed from the path table and %d entries to be removed from the files table[/B][/COLOR]'
% (data_list_size, temp_number_of_files_to_delete))
if global_prepared_list is not None:
self.addonsettings.append('[COLOR red][B]There are %d entries to be removed in total from "deep clean".[/B][/COLOR]' % (len(global_prepared_list)))
del temp_start_string
del temp_bookmark_string
del temp_number_of_files_to_delete
del count_files_statement
self.container3.addItems(self.addonsettings)
# Show warning about backup if using MySQL
if is_mysql:
self.getControl(20).setLabel('WARNING - MySQL database [COLOR red][B]not[/B][/COLOR] backed up automatically, please do this [B]manually[/B]'
)
if specificpath:
self.getControl(20).setLabel('WARNING - Removing specific path [COLOR yellow]%s[/COLOR] '
% specific_path_to_remove)
if replacepath:
self.getControl(20).setLabel('WARNING - Renaming specific path from [COLOR yellow]%s[/COLOR] '
% old_path)
self.getControl(21).setLabel('TO [COLOR yellow]%s[/COLOR] '
% new_path)
def onAction(self, action):
global flag
dbglog('Got an action %s' % action.getId())
if action == ACTION_PREVIOUS_MENU or action == ACTION_NAV_BACK:
self.close()
if action == ACTION_SELECT_ITEM or action \
== ACTION_MOUSE_LEFT_CLICK:
try:
btn = self.getFocus()
btn_id = btn.getId()
except:
btn_id = None
if btn_id == 1:
dbglog('you pressed abort')
flag = 0
self.close()
elif btn_id == 2:
dbglog('you pressed clean')
flag = 1
self.close()
# Set some variables ###
addon = xbmcaddon.Addon()
addonname = addon.getAddonInfo('name')
addonversion = addon.getAddonInfo('version')
addonpath = addon.getAddonInfo('path')
advanced_file = \
xbmcvfs.translatePath('special://profile/advancedsettings.xml')
sources_file = xbmcvfs.translatePath('special://profile/sources.xml')
excludes_file = \
xbmcvfs.translatePath('special://profile/addon_data/script.database.cleaner/excludes.xml'
)
db_path = xbmcvfs.translatePath('special://database')
userdata_path = xbmcvfs.translatePath('special://userdata')
bp_logfile_path = xbmcvfs.translatePath('special://temp/bp-debuglog.log'
)
type_of_log = ''
is_pvr = addon.getSetting('pvr')
autoclean = addon.getSetting('autoclean')
autoclean_multiple_times = addon.getSetting('autoclean_multiple_times')
bookmarks = addon.getSetting('bookmark')
promptdelete = addon.getSetting('promptdelete')
source_file_path = addon.getSetting('sourcefilepath')
debugging = addon.getSetting('debugging')
no_sources = addon.getSetting('usesources')
autobackup = addon.getSetting('autobackup')
specificpath = addon.getSetting('specificpath')
backup_filename = addon.getSetting('backupname')
forcedbname = addon.getSetting('overridedb')
replacepath = addon.getSetting('replacepath')
enable_logging = addon.getSetting('logtolog')
deletesetswithlessthantwo = addon.getSetting('deletesetswithlessthantwo')
show_notification_for_each_sql_statement = addon.getSetting('show_notification_for_each_sql_statement')
runtexturecache = addon.getSetting('runtexturecache')
debugtexturecache = addon.getSetting('debugtexturecache')
deepclean = addon.getSetting('deepclean')
deepcleanonlyonedirectory = addon.getSetting('deepcleanonlyonedirectory')
deepcleanonlyonedirectory_path = addon.getSetting('deepcleanonlyonedirectory_path')
tc_option_list = []
for tc_option in 'c, C, lc, p, P, Xd, r, R, qa, qax, duplicates'.replace(' ', '').split(','):
if tc_option.lower() != tc_option and tc_option != "Xd":
tc_option_s = tc_option + 'cap'
else:
tc_option_s = tc_option
if addon.getSetting('texturecache_' + tc_option_s) == 'true':
tc_option_list.append(tc_option)
if deletesetswithlessthantwo == 'true':
deletesetswithlessthantwo = True
else:
deletesetswithlessthantwo = False
if show_notification_for_each_sql_statement == 'true':
show_notification_for_each_sql_statement = True
else:
show_notification_for_each_sql_statement = False
if runtexturecache == 'true':
runtexturecache = True
else:
runtexturecache = False
if debugtexturecache == 'true':
debugtexturecache = True
else:
debugtexturecache = False
if deepclean == 'true':
deepclean = True
else:
deepclean = False
if deepcleanonlyonedirectory == 'true':
deepcleanonlyonedirectory = True
else:
deepcleanonlyonedirectory = False
if enable_logging == 'true':
enable_logging = True
type_of_log = addon.getSetting('typeoflog')
else:
enable_logging = False
if replacepath == 'true':
replacepath = True
else:
replacepath = False
old_path = addon.getSetting('oldpath')
new_path = addon.getSetting('newpath')
if forcedbname == 'true':
forcedbname = True
else:
forcedbname = False
forcedname = addon.getSetting('forceddbname')
if specificpath == 'true':
specificpath = True
else:
specificpath = False
specific_path_to_remove = addon.getSetting('spcpathstr')
display_list = []
excludes_list = []
renamepath_list = []
excluding = False
found = False
is_mysql = False
remote_file = False
cleaning = False
path_name = ''
the_path = ''
success = 0
our_source_list = ''
if debugging == 'true':
debugging = True
else:
debugging = False
running_dialog = None
global_logfile = None
global_source_list = []
global_prepared_list = None
def log(txt):
if isinstance(txt, str):
txt = txt
message = u'%s: %s' % (addonname, txt)
xbmc.log(msg=message, level=xbmc.LOGDEBUG)
def dbglog(txt):
if debugging:
log(txt)
def texturecache_dbglog(txt):
if isinstance(txt, str):
message = u'%s: %s' % (addonname + ': From texturecache.py', txt)
xbmc.log(msg=message, level=xbmc.LOGDEBUG)
def exit_on_error():
# Try to make sure we fail gracefully.
try:
cursor.close()
except Exception:
pass
try:
db.rollback()
except Exception:
pass
try:
db.close()
except Exception:
pass
try:
running_dialog.close()
except Exception:
pass
WINDOW.setProperty('database-cleaner-running', 'false')
exit(1)
def wrapped_execute(cursor, sql, params=None, error_message=None, window=None, suppress_notification=False, progress=1):
if not params:
params = []
if not error_message:
error_message = 'Error executing SQL statement. The SQL statement was: %s, called with parameters %s.' % (sql, str(params))
try:
unwrapped_execute(cursor, sql, params, suppress_notification, progress)
except Exception as e:
error_message = error_message + ' Error: %s' % (str(e))
dbglog(error_message)
xbmcgui.Dialog().ok(addonname, error_message)
if window:
try:
window.close()
except Exception:
pass
exit_on_error()
def unwrapped_execute(cursor, sql, params=None, suppress_notification=False, progress=1):
global replacepath
global show_notification_for_each_sql_statement
global running_dialog
if not params:
params = []
dbglog('Executing SQL command - %s - params: %s' % (sql, str(params)))
if not replacepath and show_notification_for_each_sql_statement and not suppress_notification:
running_dialog = xbmcgui.DialogProgressBG()
running_dialog.create('Executing SQL statement - %s - params: %s' % (sql, str(params)))
running_dialog.update(min(int(progress),100))
cursor.execute(sql, params)
if not replacepath and show_notification_for_each_sql_statement and not suppress_notification:
try:
running_dialog.close()
except Exception:
pass
def cleaner_log_file(our_select, my_command_list, cleaning):
cleaner_log = \
xbmcvfs.translatePath('special://temp/database-cleaner.log')
old_cleaner_log = \
xbmcvfs.translatePath('special://temp/database-cleaner.old.log')
old_log_contents = ''
do_progress = False
if not enable_logging:
return
if type_of_log == '0':
dbglog('Writing to new log file')
if cleaning:
if xbmcvfs.exists(cleaner_log):
dbglog('database-cleaner.log exists - renaming to old.log'
)
xbmcvfs.delete(old_cleaner_log)
xbmcvfs.copy(cleaner_log, old_cleaner_log)
xbmcvfs.delete(cleaner_log)
else:
xbmcvfs.delete(cleaner_log)
else:
dbglog('Appending to existing log file')
if cleaning:
if xbmcvfs.exists(cleaner_log):
dbglog('database-cleaner.log exists - backing up to old.log'
)
xbmcvfs.delete(old_cleaner_log)
xbmcvfs.copy(cleaner_log, old_cleaner_log)
old_log = xbmcvfs.File(cleaner_log)
old_log_contents = old_log.read()
old_log.close()
now = datetime.datetime.now()
logfile = xbmcvfs.File(cleaner_log, 'w')
if old_log_contents:
logfile.write(old_log_contents)
date_long_format = xbmc.getRegion('datelong')
time_format = xbmc.getRegion('time')
date_long_format = date_long_format + ' ' + time_format
logfile_header = 'Video Database Cleaner V' + addonversion \
+ ' - Running at ' + now.strftime(date_long_format) + '''
'''
logfile.write(logfile_header)
if deepclean and not replacepath and not specificpath and not no_sources:
global global_prepared_list
global global_source_list
if global_prepared_list is None:
temp_params = []
temp_like_str = ''
temp_atleastonesource = False
if deepcleanonlyonedirectory:
if deepcleanonlyonedirectory_path == '':
xbmcgui.Dialog().ok(addonname, 'Deep clean is set to clean only one directory, but the directory path is empty. Not doing deep clean.')
global_source_list = []
elif not (deepcleanonlyonedirectory_path.endswith('/') or deepcleanonlyonedirectory_path.endswith('\\')):
xbmcgui.Dialog().ok(addonname, 'Deep clean is set to clean only one directory, but the corresponding directory path does not end with a valid path separator. Not doing deep clean.')
global_source_list = []
for s in global_source_list:
if not (s.endswith('/') or s.endswith('\\')):
dbglog('Ignoring source %s because it doesn\'t end with a valid path separator' % (s))
elif not deepcleanonlyonedirectory or s.startswith(deepcleanonlyonedirectory_path):
temp_params.append(s + '_%')
temp_atleastonesource = True
else:
dbglog('Ignoring source %s because it doesn\'t match parameters: deepcleanonlyonedirectory: %r, s.startswith(deepcleanonlyonedirectory_path): %r' % (s, deepcleanonlyonedirectory, s.startswith(deepcleanonlyonedirectory_path)))
temp_like_str = ' OR strPath LIKE '.join('?'*len(temp_params))
temp_like_str += ')'
if excluding:
temp_params.extend([e + '%' for e in excludes_list])
temp_like_str += ' AND (strPath NOT LIKE '
temp_like_str += ' AND strPath NOT LIKE '.join(replstr*len(excludes_list))
temp_like_str += ')'
concat_string = "(path.strPath || files.strFilename)" if not is_mysql else "CONCAT(path.strPath, files.strFilename)"
temp_sql = "SELECT strPath, idPath as id FROM path WHERE (strPath LIKE " + temp_like_str + " UNION SELECT " + concat_string + " as strPath, idFile as id FROM files INNER JOIN path ON files.idPath = path.idPath WHERE (strPath LIKE " + temp_like_str + " ORDER BY strPath"
del concat_string
if temp_atleastonesource:
temp_sql_count = "SELECT count(*) FROM (%s)" % (temp_sql)
wrapped_execute(cursor, temp_sql_count, temp_params*2)
temp_sql_count = cursor.fetchall()[0][0]
temp_files_to_delete_list = []
wrapped_execute(cursor, temp_sql, temp_params*2)
temp_res = cursor.fetchone()
running_dialog = xbmcgui.DialogProgressBG()
running_dialog.create('Please wait. Deep clean is verifying files')
temp_i = -1
while temp_res is not None:
temp_i += 1
if temp_sql_count < 100 or (temp_i % 100) == 0:
running_dialog.update(min(int(100*temp_i/temp_sql_count),100))
if not xbmcvfs.exists(temp_res[0]):
temp_files_to_delete_list.append(temp_res)
temp_res = cursor.fetchone()
running_dialog.close()
del temp_i
del temp_sql_count
else:
dbglog('The "deep clean" option found no valid sources to check.')
if not temp_atleastonesource or len(temp_files_to_delete_list) == 0:
global_prepared_list = []
else:
global_prepared_list = temp_files_to_delete_list
del temp_params
del temp_like_str
del temp_atleastonesource
del temp_sql
if not len(global_prepared_list) == 0:
dbglog('Listsize from "deep clean" is %d' % (len(global_prepared_list)))
if not cleaning:
logfile.write('The following files and paths would be removed from "deep clean" alone (total %d) (check the remainder of the file for additional paths related to the general cleaning option):\n' % (len(global_prepared_list)))
else:
logfile.write('The following files and paths were removed from "deep clean" alone (total %d) (check the remainder of the file for additional paths related to the general cleaning option):\n' % (len(global_prepared_list)))
for s, _ in global_prepared_list:
logfile.write('%s\n' % (s))
else:
logfile.write('No files or paths to be removed by the "deep clean" option')
logfile.write('\n\n\n')
wrapped_execute(cursor, our_select, my_command_list, window=logfile, suppress_notification=True)
counting = 0
my_data = cursor.fetchall()
listsize = len(my_data)
dbglog('Listsize is %d' % listsize)
logfile.write('''There are %d paths in the database that meet your criteria
'''
% listsize)
if listsize > 600:
do_progress = True
dialog = xbmcgui.DialogProgressBG()
dbglog('Creating progress dialog for logfile')
dialog.create('Getting required data. Please wait')
dialog.update(1)
if not cleaning and not replacepath:
logfile.write('The following file paths would be removed from your database'
)
logfile.write('''
''')
elif cleaning and not replacepath:
logfile.write('The following paths were removed from the database'
)
logfile.write('''
''')
elif not cleaning and replacepath:
logfile.write('The following paths will be changed in your database'
)
logfile.write('''
''')
else:
logfile.write('The following paths were changed in your database'
)
logfile.write('''
''')
if not specificpath and not replacepath:
for strPath in my_data:
counting += 1
mystring = u''.join(strPath) + '\n'
outdata = mystring
if do_progress:
dialog.update(percent=int(counting / float(listsize)
* 100))
if cleaning:
dbglog('Removing %s' % strPath)
logfile.write(outdata)
elif specificpath and not replacepath:
dbglog('Removing specific path %s' % specific_path_to_remove)
for strPath in my_data:
counting += 1
mystring = u''.join(strPath) + '\n'
outdata = mystring
if do_progress:
dialog.update(percent=int(counting / float(listsize)
* 100))
if cleaning:
dbglog('Removing unwanted path %s' % strPath)
logfile.write(outdata)
else:
for strPath in my_data:
counting += 1
mystring = u''.join(strPath) + '\n'
outdata = mystring
if do_progress:
dialog.update(percent=int(counting / float(listsize)
* 100))
if cleaning:
dbglog('Changing path %s' % strPath)
logfile.write(outdata)
our_data = cursor
if counting == 0: # nothing to remove
logfile.write('No paths have been found to remove\n')
if do_progress:
dialog.close()
logfile.write('''
''')
if not (runtexturecache and debugtexturecache):
# We'll delay the closing of the logfile if running texturecache.py with debug
logfile.close()
else:
global global_logfile
global_logfile = logfile
def get_texturecache_duplicates_logfile():
texturecache_log = \
xbmcvfs.translatePath('special://temp/database-cleaner-duplicates.log')
old_texturecache_log = \
xbmcvfs.translatePath('special://temp/database-cleaner-duplicates.old.log')
old_log_contents = ''
if type_of_log == '0':
dbglog('Writing to new log file')
if xbmcvfs.exists(texturecache_log):
dbglog('database-cleaner-duplicates.log exists - renaming to old.log'
)
xbmcvfs.delete(old_texturecache_log)
xbmcvfs.copy(texturecache_log, old_texturecache_log)
xbmcvfs.delete(texturecache_log)
else:
dbglog('Appending to existing log file')
if xbmcvfs.exists(texturecache_log):
dbglog('database-cleaner-duplicates.log exists - backing up to old.log'
)
xbmcvfs.delete(old_texturecache_log)
xbmcvfs.copy(texturecache_log, old_texturecache_log)
old_log = xbmcvfs.File(texturecache_log)
old_log_contents = old_log.read()
old_log.close()
now = datetime.datetime.now()
logfile = xbmcvfs.File(texturecache_log, 'w')
if old_log_contents:
logfile.write(old_log_contents)
date_long_format = xbmc.getRegion('datelong')
time_format = xbmc.getRegion('time')
date_long_format = date_long_format + ' ' + time_format
logfile_header = 'Video Database Cleaner V' + addonversion \
+ ' - Running at ' + now.strftime(date_long_format) + ' - "texturecache.py duplicates" invocation' + '''
'''
logfile.write(logfile_header)
return logfile
#### Start Here !! ####
dbglog('script version %s started' % addonversion)
# if WINDOW.getProperty('database-cleaner-running') == 'true':
# log('Video Database Cleaner already running')
# exit(0)
# else:
# WINDOW.setProperty('database-cleaner-running', 'true')
if runtexturecache:
tccfg_option_dict = {}
for tccfg_option in ('userdata', 'dbfile', 'thumbnails', 'xbmc.host', 'webserver.port', 'rpc.port', 'download.threads', 'orphan.limit.check', 'extrajson.albums', 'extrajson.artists', 'extrajson.songs', 'extrajson.movies', 'extrajson.sets', 'extrajson.tvshows.tvshow', 'extrajson.tvshows.season', 'extrajson.tvshows.episode', 'qaperiod', 'qa.file', 'cache.castthumb', 'logfile', 'logfile.verbose', 'network.mac', 'allow.recacheall'):
temp_tccfg_opt_bool = 'false'
temp_tccfg_opt_bool = addon.getSetting('tc_opt_' + tccfg_option + '_bool')
temp_tccfg_opt_bool = True if temp_tccfg_opt_bool == 'true' else False
tccfg_option_dict[tccfg_option] = (temp_tccfg_opt_bool, addon.getSetting('tc_opt_' + tccfg_option + '_value'))
del temp_tccfg_opt_bool
tccfg_from_file_dict = {}
tccfg_from_bfile_dict = {}
tccfg_file_path = xbmcvfs.translatePath('special://home/addons/script.database.cleaner/resources/texturecache.cfg')
tccfg_bfile_path = xbmcvfs.translatePath('special://home/addons/script.database.cleaner/resources/donotedit.tccfg.bak')
tccfg_overwrite_existing = False
temp_filecontents = ''
if not xbmcvfs.exists(tccfg_bfile_path) and not xbmcvfs.exists(tccfg_file_path):
tccfg_overwrite_existing = True
else:
if not xbmcvfs.exists(tccfg_file_path):
tccfg_overwrite_existing = True
else:
if xbmcvfs.exists(tccfg_bfile_path):
with xbmcvfs.File(tccfg_bfile_path) as temp_f:
temp_all_lines = temp_f.read().split('\n')
for temp_line in temp_all_lines:
temp_line += '\n'
if len(temp_line.split('=')) < 2:
continue
if temp_line.startswith('#'):
temp_line = temp_line[1:]
temp_line_active = False
else:
temp_line_active = True
temp_optname = temp_line.split('=')[0].strip()
temp_optvalue = temp_line.split('=')[1].strip()
if temp_optname in tccfg_option_dict:
tccfg_from_bfile_dict[temp_optname] = (temp_line_active, temp_optvalue)
del temp_all_lines
else:
tccfg_from_bfile_dict = tccfg_option_dict
with xbmcvfs.File(tccfg_file_path) as temp_f:
temp_atleastonedifferent = False
temp_all_lines = temp_f.read().split('\n')
temp_filecontents_list = []
for temp_line in temp_all_lines:
temp_line += '\n'
if len(temp_line.split('=')) < 2 or temp_line.startswith('='):
temp_filecontents_list.append(temp_line)
continue
if temp_line.startswith('#'):
temp_line_active = False
temp_optname = temp_line[1:].split('=')[0].strip()
else:
temp_line_active = True
temp_optname = temp_line.split('=')[0].strip()
temp_optvalue = temp_line.split('=')[1].strip()
if temp_optname in tccfg_from_bfile_dict:
if temp_line_active != tccfg_from_bfile_dict[temp_optname][0] or temp_optvalue != tccfg_from_bfile_dict[temp_optname][1]:
temp_atleastonedifferent = True
temp_line = '#' if not tccfg_option_dict[temp_optname][0] else ''
temp_line += temp_optname + ' = '
temp_line += tccfg_option_dict[temp_optname][1]
temp_line += '\n'
tccfg_from_file_dict[temp_optname] = (temp_line_active, temp_optvalue)
if temp_line not in temp_filecontents_list:
temp_filecontents_list.append(temp_line)
temp_filecontents = ''.join(temp_filecontents_list)
if temp_filecontents.endswith('\n'):
temp_filecontents = temp_filecontents[:-1]
del temp_filecontents_list
del temp_all_lines
if len(tccfg_from_file_dict) == len(tccfg_from_bfile_dict) and not temp_atleastonedifferent:
tccfg_overwrite_existing = True
del temp_atleastonedifferent
del temp_line
del temp_line_active
del temp_optname
del temp_optvalue
if not tccfg_overwrite_existing:
temp_overwritevalue = xbmcgui.Dialog().yesnocustom(addonname, 'The texturecache.cfg does not match what the add-on expected to see. Click "Add-on" to continue with the add-on settings (which will be saved to the file), "File" to continue with the file settings (which will be saved to the add-on) or "Abort" to cancel the add-on run.', 'Abort', 'File', 'Add-on')
if temp_overwritevalue == 0:
tccfg_overwrite_existing = False
elif temp_overwritevalue == 1:
tccfg_overwrite_existing = True
else:
dbglog('Aborting add-on run')
exit_on_error()
del temp_overwritevalue
if tccfg_overwrite_existing:
xbmcvfs.delete(tccfg_file_path)
with xbmcvfs.File(tccfg_file_path, 'w') as temp_f:
if temp_filecontents != '':
temp_f.write(temp_filecontents)
else:
for temp_optname in tccfg_option_dict:
temp_line = '#' if not tccfg_option_dict[temp_optname][0] else ''
temp_line += temp_optname + ' = '
temp_line += tccfg_option_dict[temp_optname][1]
temp_line += '\n'
temp_f.write(temp_line)
del temp_optname
del temp_line
else:
for temp_optname in tccfg_option_dict:
if temp_optname not in tccfg_from_file_dict:
addon.setSettingBool('tc_opt_' + temp_optname + '_bool', False)
else:
addon.setSettingBool('tc_opt_' + temp_optname + '_bool', tccfg_from_file_dict[temp_optname][0])
addon.setSetting('tc_opt_' + temp_optname + '_value', tccfg_from_file_dict[temp_optname][1])
del temp_optname
xbmcvfs.copy(tccfg_file_path, tccfg_bfile_path)
del temp_filecontents
del tccfg_option_dict
del tccfg_from_file_dict
del tccfg_from_bfile_dict
xbmcgui.Dialog().notification(addonname, 'Scanning library...',
xbmcgui.NOTIFICATION_INFO, 2000)
xbmc.sleep(2000)
if xbmcvfs.exists(advanced_file):
dbglog('Found advancedsettings.xml')
found = True
if found:
msg = advanced_file
dbglog('looking in advancedsettings for videodatabase info')
try:
advancedsettings = ET.parse(advanced_file)
except ET.ParseError:
dbglog('Error parsing advancedsettings.xml file')
xbmcgui.Dialog().ok(addonname,
'Error parsing advancedsettings.xml file[CR] Possibly a mal-formed comment or missing closing tag - script aborted'
)
exit_on_error()
root = advancedsettings.getroot()
try:
for videodb in root.findall('videodatabase'):
try:
our_host = videodb.find('host').text
except:
log('Unable to find MySQL host address')
try:
our_username = videodb.find('user').text
except:
log('Unable to determine MySQL username')
try:
our_password = videodb.find('pass').text
except:
log('Unable to determine MySQL password')
try:
our_port = videodb.find('port').text
except:
log('Unable to find MySQL port')
try:
our_dbname = videodb.find('name').text
except:
our_dbname = 'MyVideos'
dbglog('MySQL details - %s, %s, %s, %s' % (our_host,
our_port, our_username, our_dbname))
is_mysql = True
except Exception as e:
e = str(e)
dbglog('Error parsing advancedsettings file - %s' % e)
is_mysql = False
if not is_mysql:
our_dbname = 'MyVideos'
for num in range(MAX_VIDEODB_VERSION, MIN_VIDEODB_VERSION, -1):
testname = our_dbname + str(num)
our_test = db_path + testname + '.db'
dbglog('Checking for local database %s' % testname)
if xbmcvfs.exists(our_test):
break
if num != MIN_VIDEODB_VERSION:
our_dbname = testname
if our_dbname == 'MyVideos':
dbglog('No video database found - assuming MySQL database')
dbglog('Database name is %s' % our_dbname)
if is_pvr == 'true':
is_pvr = True
else:
is_pvr = False
if autoclean == 'true':
autoclean = True
else:
autoclean = False
if autoclean_multiple_times == 'true':
autoclean_multiple_times = True
else:
autoclean_multiple_times = False
if bookmarks == 'true':
bookmarks = True
else:
bookmarks = False
if promptdelete == 'true':
promptdelete = True
else:
promptdelete = False
if no_sources == 'true':
no_sources = False
else:
no_sources = True
dbglog('Settings for file cleaning are as follows')
if is_pvr:
dbglog('keeping PVR files')
if bookmarks:
dbglog('Keeping bookmarks')
if autoclean:
dbglog('autocleaning afterwards')
if autoclean_multiple_times:
dbglog('autocleaning multiple times')
if promptdelete:
dbglog('Prompting before deletion')
if no_sources:
dbglog('Not using sources.xml')
if source_file_path != '':
sources_file = source_file_path
remote_file = True
dbglog('Remote sources.xml file path identified')
if xbmcvfs.exists(sources_file) and not remote_file:
try:
source_file = sources_file
tree = ET.parse(source_file)
root = tree.getroot()
dbglog('Got local sources.xml file')
except:
dbglog('Error parsing local sources.xml file')
xbmcgui.Dialog().ok(addonname,
'Error parsing local sources.xml file - script aborted'
)
exit_on_error()
elif xbmcvfs.exists(sources_file):
try:
f = xbmcvfs.File(sources_file)
source_file = f.read()
f.close()
root = ET.fromstring(source_file)
dbglog('Got remote sources.xml')
except:
dbglog('Error parsing remote sources.xml')
xbmcgui.Dialog().ok(addonname,
'Error parsing remote sources.xml file - script aborted'
)
exit_on_error()
else:
xbmcgui.Dialog().notification(addonname,
'Warning - no sources.xml file found'
, xbmcgui.NOTIFICATION_INFO, 3000)
dbglog('No local sources.xml, no remote sources file set in settings'
)
xbmc.sleep(3000)
no_sources = True
my_command = ''
my_command_list = []
first_time = True
if forcedbname:
log('Forcing video db version to %s' % forcedname)
# Open database connection
if is_mysql and not forcedbname:
if our_dbname == '': # no db name in advancedsettings
our_dbname = 'MyVideos'
for num in range(MAX_VIDEODB_VERSION, MIN_VIDEODB_VERSION, -1):
testname = our_dbname + str(num)
try:
dbglog('Attempting MySQL connection to %s' % testname)
db = mysql.connector.connect(user=our_username,
database=testname, password=our_password,
host=our_host)
if db.is_connected():
our_dbname = testname
dbglog('Connected to MySQL database %s'
% our_dbname)
break
except:
pass
else:
# already got db name from ad settings
for num in range(MAX_VIDEODB_VERSION, MIN_VIDEODB_VERSION, -1):
testname = our_dbname + str(num)
try:
dbglog('Attempting MySQL connection to %s' % testname)
db = mysql.connector.connect(user=our_username,
database=testname, password=our_password,
host=our_host, port=our_port)
if db.is_connected():
our_dbname = testname
dbglog('Connected to MySQL database %s'
% our_dbname)
break
except:
pass
if not db.is_connected():
xbmcgui.Dialog().ok(addonname,
"Couldn't connect to MySQL database", s)
log("Error - couldn't connect to MySQL database - %s " % s)
exit_on_error()
elif is_mysql and forcedbname:
try:
db = mysql.connector.connect(user=our_username,
database=forcedname, password=our_password,
host=our_host, port=our_port)
if db.is_connected():
our_dbname = forcedname
dbglog('Connected to forced MySQL database %s' % forcedname)
except:
log('Error connecting to forced database - %s' % forcedname)